From d6d94473e4c27294ecd0da442b94ba35d003ec62 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 13 May 2020 23:34:05 +0200 Subject: [PATCH 001/406] Bumped version to 0.111.0dev0 (#35593) --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index ec8c29de0b7..11a99515d60 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,6 +1,6 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 -MINOR_VERSION = 110 +MINOR_VERSION = 111 PATCH_VERSION = "0.dev0" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" From e2b45915a6f3744b6202104282231e7c419a2931 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Thu, 14 May 2020 01:14:30 +0300 Subject: [PATCH 002/406] Upgrade flake8 to 3.8.1, fix findings (#35578) --- .pre-commit-config.yaml | 2 +- homeassistant/components/hunterdouglas_powerview/__init__.py | 2 +- requirements_test_pre_commit.txt | 2 +- tests/components/zwave_mqtt/conftest.py | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 491cfc05d8a..daf02443f3d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,7 +22,7 @@ repos: - --quiet-level=2 exclude_types: [csv, json] - repo: https://gitlab.com/pycqa/flake8 - rev: 3.7.9 + rev: 3.8.1 hooks: - id: flake8 additional_dependencies: diff --git a/homeassistant/components/hunterdouglas_powerview/__init__.py b/homeassistant/components/hunterdouglas_powerview/__init__.py index 89dc610a6fc..7001425f306 100644 --- a/homeassistant/components/hunterdouglas_powerview/__init__.py +++ b/homeassistant/components/hunterdouglas_powerview/__init__.py @@ -124,7 +124,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): 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.") + raise UpdateFailed("Failed to fetch new shade data.") return _async_map_data_by_id(shade_entries[SHADE_DATA]) coordinator = DataUpdateCoordinator( diff --git a/requirements_test_pre_commit.txt b/requirements_test_pre_commit.txt index 798377780ff..e0882a786ea 100644 --- a/requirements_test_pre_commit.txt +++ b/requirements_test_pre_commit.txt @@ -4,7 +4,7 @@ bandit==1.6.2 black==19.10b0 codespell==1.16.0 flake8-docstrings==1.5.0 -flake8==3.7.9 +flake8==3.8.1 isort==4.3.21 pydocstyle==5.0.2 pyupgrade==2.3.0 diff --git a/tests/components/zwave_mqtt/conftest.py b/tests/components/zwave_mqtt/conftest.py index 2297cee3e4e..67e8382d23d 100644 --- a/tests/components/zwave_mqtt/conftest.py +++ b/tests/components/zwave_mqtt/conftest.py @@ -12,13 +12,13 @@ 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") + return load_fixture("zwave_mqtt/generic_network_dump.csv") @pytest.fixture(name="light_data", scope="session") def light_data_fixture(): """Load light dimmer MQTT data and return it.""" - return load_fixture(f"zwave_mqtt/light_network_dump.csv") + return load_fixture("zwave_mqtt/light_network_dump.csv") @pytest.fixture(name="sent_messages") From 710f2267784c0a13db3771d810c17ef982d38ef4 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Thu, 14 May 2020 00:02:46 +0000 Subject: [PATCH 003/406] [ci skip] Translation update --- .../adguard/translations/es-419.json | 2 ++ .../agent_dvr/translations/es-419.json | 21 +++++++++++++++++ .../airvisual/translations/es-419.json | 2 ++ .../components/atag/translations/es-419.json | 1 + .../blebox/translations/es-419.json | 23 +++++++++++++++++++ .../bsblan/translations/es-419.json | 23 +++++++++++++++++++ 6 files changed, 72 insertions(+) create mode 100644 homeassistant/components/agent_dvr/translations/es-419.json create mode 100644 homeassistant/components/blebox/translations/es-419.json create mode 100644 homeassistant/components/bsblan/translations/es-419.json diff --git a/homeassistant/components/adguard/translations/es-419.json b/homeassistant/components/adguard/translations/es-419.json index c0ce604fbee..f2ce862b083 100644 --- a/homeassistant/components/adguard/translations/es-419.json +++ b/homeassistant/components/adguard/translations/es-419.json @@ -16,7 +16,9 @@ }, "user": { "data": { + "host": "Host", "password": "Contrase\u00f1a", + "port": "Puerto", "ssl": "AdGuard Home utiliza un certificado SSL", "username": "Nombre de usuario", "verify_ssl": "AdGuard Home utiliza un certificado adecuado" diff --git a/homeassistant/components/agent_dvr/translations/es-419.json b/homeassistant/components/agent_dvr/translations/es-419.json new file mode 100644 index 00000000000..4b3e1211c31 --- /dev/null +++ b/homeassistant/components/agent_dvr/translations/es-419.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositivo ya est\u00e1 configurado" + }, + "error": { + "already_in_progress": "El flujo de configuraci\u00f3n para el dispositivo ya est\u00e1 en progreso.", + "device_unavailable": "El dispositivo no est\u00e1 disponible" + }, + "step": { + "user": { + "data": { + "host": "Host", + "port": "Puerto" + }, + "title": "Configurar Agent DVR" + } + } + }, + "title": "Agent DVR" +} \ No newline at end of file diff --git a/homeassistant/components/airvisual/translations/es-419.json b/homeassistant/components/airvisual/translations/es-419.json index 77c7ed8dd71..86fb471df64 100644 --- a/homeassistant/components/airvisual/translations/es-419.json +++ b/homeassistant/components/airvisual/translations/es-419.json @@ -5,11 +5,13 @@ }, "error": { "general_error": "Se ha producido un error desconocido.", + "invalid_api_key": "Se proporciona una clave de API no v\u00e1lida.", "unable_to_connect": "No se puede conectar a la unidad Node/Pro." }, "step": { "geography": { "data": { + "api_key": "Clave API", "latitude": "Latitud", "longitude": "Longitud" }, diff --git a/homeassistant/components/atag/translations/es-419.json b/homeassistant/components/atag/translations/es-419.json index 214dd0e9004..f837491b330 100644 --- a/homeassistant/components/atag/translations/es-419.json +++ b/homeassistant/components/atag/translations/es-419.json @@ -9,6 +9,7 @@ "step": { "user": { "data": { + "email": "Correo electr\u00f3nico (opcional)", "host": "Host", "port": "Puerto (10000)" }, diff --git a/homeassistant/components/blebox/translations/es-419.json b/homeassistant/components/blebox/translations/es-419.json new file mode 100644 index 00000000000..eb0545e4fa4 --- /dev/null +++ b/homeassistant/components/blebox/translations/es-419.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "address_already_configured": "Un dispositivo BleBox ya est\u00e1 configurado en {address} .", + "already_configured": "Este dispositivo BleBox ya est\u00e1 configurado." + }, + "error": { + "cannot_connect": "No se puede conectar al dispositivo BleBox. (Verifique los registros en busca de errores).", + "unknown": "Error desconocido al conectarse al dispositivo BleBox. (Verifique los registros en busca de errores).", + "unsupported_version": "El dispositivo BleBox tiene un firmware desactualizado. Por favor, actual\u00edcelo primero." + }, + "flow_title": "Dispositivo BleBox: {name} ({host})", + "step": { + "user": { + "data": { + "host": "Direcci\u00f3n IP", + "port": "Puerto" + }, + "description": "Configure su BleBox para integrarse con Home Assistant." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/bsblan/translations/es-419.json b/homeassistant/components/bsblan/translations/es-419.json new file mode 100644 index 00000000000..9793144e26b --- /dev/null +++ b/homeassistant/components/bsblan/translations/es-419.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositivo ya est\u00e1 configurado" + }, + "error": { + "connection_error": "No se pudo conectar al dispositivo BSB-Lan." + }, + "flow_title": "BSB-Lan: {name}", + "step": { + "user": { + "data": { + "host": "Host", + "passkey": "Cadena de clave de acceso", + "port": "Puerto" + }, + "description": "Configure su dispositivo BSB-Lan para integrarlo con Home Assistant.", + "title": "Con\u00e9ctese al dispositivo BSB-Lan" + } + } + }, + "title": "BSB-Lan" +} \ No newline at end of file From 99d3046a5b67f863ef90ca776383608597b2006b Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 14 May 2020 03:37:14 +0200 Subject: [PATCH 004/406] Updated frontend to 20200514.0 (#35598) --- 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 dad273218a0..5d4f3ce03d2 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==20200513.0"], + "requirements": ["home-assistant-frontend==20200514.0"], "dependencies": [ "api", "auth", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index d1ed0d476c4..ae80e53a171 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -12,7 +12,7 @@ cryptography==2.9.2 defusedxml==0.6.0 distro==1.5.0 hass-nabucasa==0.34.2 -home-assistant-frontend==20200513.0 +home-assistant-frontend==20200514.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 2abe1d3a6a6..9b671128880 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -731,7 +731,7 @@ hole==0.5.1 holidays==0.10.2 # homeassistant.components.frontend -home-assistant-frontend==20200513.0 +home-assistant-frontend==20200514.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index db9b2e17488..80d873d243f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -312,7 +312,7 @@ hole==0.5.1 holidays==0.10.2 # homeassistant.components.frontend -home-assistant-frontend==20200513.0 +home-assistant-frontend==20200514.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From 7ab19bdfcc5e9a0b0056f87122d74679af2b70fc Mon Sep 17 00:00:00 2001 From: Jason Hunter Date: Wed, 13 May 2020 22:24:38 -0400 Subject: [PATCH 005/406] Additional checks for ONVIF event capabilities (#35599) catch any exceptions when pulling event capabilities and assume it is not supported --- homeassistant/components/onvif/device.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/onvif/device.py b/homeassistant/components/onvif/device.py index 2743ed7eca4..17a0c0c27f0 100644 --- a/homeassistant/components/onvif/device.py +++ b/homeassistant/components/onvif/device.py @@ -209,17 +209,23 @@ class ONVIFDevice: """Obtain information about the available services on the device.""" media_service = self.device.create_media_service() media_capabilities = await media_service.GetServiceCapabilities() - event_service = self.device.create_events_service() - event_capabilities = await event_service.GetServiceCapabilities() + + pullpoint = False + try: + event_service = self.device.create_events_service() + event_capabilities = await event_service.GetServiceCapabilities() + pullpoint = event_capabilities.WSPullPointSupport + except (ONVIFError, Fault): + pass + ptz = False try: self.device.get_definition("ptz") ptz = True except ONVIFError: pass - return Capabilities( - media_capabilities.SnapshotUri, event_capabilities.WSPullPointSupport, ptz - ) + + return Capabilities(media_capabilities.SnapshotUri, pullpoint, ptz) async def async_get_profiles(self) -> List[Profile]: """Obtain media profiles for this device.""" From efb05934736fb4cf9ab11af65cf963c883eb82c2 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk <11290930+bouwew@users.noreply.github.com> Date: Thu, 14 May 2020 07:03:42 +0200 Subject: [PATCH 006/406] Bump haanna to 0.15.0 (#35579) * Link to haanna v0.15.0 * Update requirements_all.txt --- homeassistant/components/plugwise/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/plugwise/manifest.json b/homeassistant/components/plugwise/manifest.json index 9f14f6c6e61..55e43c2a29f 100644 --- a/homeassistant/components/plugwise/manifest.json +++ b/homeassistant/components/plugwise/manifest.json @@ -3,5 +3,5 @@ "name": "Plugwise Anna", "documentation": "https://www.home-assistant.io/integrations/plugwise", "codeowners": ["@laetificat", "@CoMPaTech", "@bouwew"], - "requirements": ["haanna==0.14.3"] + "requirements": ["haanna==0.15.0"] } diff --git a/requirements_all.txt b/requirements_all.txt index 9b671128880..02d03372d85 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -692,7 +692,7 @@ ha-ffmpeg==2.0 ha-philipsjs==0.0.8 # homeassistant.components.plugwise -haanna==0.14.3 +haanna==0.15.0 # homeassistant.components.habitica habitipy==0.2.0 From cf50ccb919ff5e20f9b28a51da683ffccb760f22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Thu, 14 May 2020 08:24:27 +0300 Subject: [PATCH 007/406] Run pre-commit gen_requirements_all on pre-commit config changes (#35588) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index daf02443f3d..42856451494 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -80,8 +80,8 @@ repos: entry: script/run-in-env.sh python3 -m script.gen_requirements_all pass_filenames: false language: script - types: [json] - files: ^homeassistant/.+/manifest\.json$ + types: [text] + files: ^(homeassistant/.+/manifest\.json|\.pre-commit-config\.yaml)$ - id: hassfest name: hassfest entry: script/run-in-env.sh python3 -m script.hassfest From e6c58c9795c1e756c85728dd30151c34e943a8d9 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Thu, 14 May 2020 10:49:27 +0200 Subject: [PATCH 008/406] Axis - Code improvements (#35592) * Adapt library improvements Clean up integration and tests and make them more like latest changes in UniFi integration * Bump dependency to v26 --- homeassistant/components/axis/__init__.py | 47 +---- homeassistant/components/axis/axis_base.py | 5 +- .../components/axis/binary_sensor.py | 34 +-- homeassistant/components/axis/camera.py | 2 +- homeassistant/components/axis/const.py | 3 + homeassistant/components/axis/device.py | 143 +++++++------ homeassistant/components/axis/manifest.json | 2 +- homeassistant/components/axis/switch.py | 11 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/axis/test_binary_sensor.py | 16 +- tests/components/axis/test_camera.py | 10 +- tests/components/axis/test_config_flow.py | 193 +++++++++--------- tests/components/axis/test_device.py | 51 +++-- tests/components/axis/test_init.py | 79 ++++--- tests/components/axis/test_switch.py | 30 ++- 16 files changed, 323 insertions(+), 307 deletions(-) diff --git a/homeassistant/components/axis/__init__.py b/homeassistant/components/axis/__init__.py index 5294e30ed6f..4b9cb7d20cf 100644 --- a/homeassistant/components/axis/__init__.py +++ b/homeassistant/components/axis/__init__.py @@ -2,19 +2,10 @@ import logging -from homeassistant.const import ( - CONF_DEVICE, - CONF_HOST, - CONF_MAC, - CONF_PASSWORD, - CONF_PORT, - CONF_TRIGGER_TIME, - CONF_USERNAME, - EVENT_HOMEASSISTANT_STOP, -) +from homeassistant.const import CONF_DEVICE, EVENT_HOMEASSISTANT_STOP -from .const import CONF_CAMERA, CONF_EVENTS, DEFAULT_TRIGGER_TIME, DOMAIN -from .device import AxisNetworkDevice, get_device +from .const import DOMAIN as AXIS_DOMAIN +from .device import AxisNetworkDevice LOGGER = logging.getLogger(__name__) @@ -26,11 +17,7 @@ async def async_setup(hass, config): async def async_setup_entry(hass, config_entry): """Set up the Axis component.""" - if DOMAIN not in hass.data: - hass.data[DOMAIN] = {} - - if not config_entry.options: - await async_populate_options(hass, config_entry) + hass.data.setdefault(AXIS_DOMAIN, {}) device = AxisNetworkDevice(hass, config_entry) @@ -43,7 +30,7 @@ async def async_setup_entry(hass, config_entry): config_entry, unique_id=device.api.vapix.params.system_serialnumber ) - hass.data[DOMAIN][config_entry.unique_id] = device + hass.data[AXIS_DOMAIN][config_entry.unique_id] = device await device.async_update_device_registry() @@ -54,32 +41,10 @@ async def async_setup_entry(hass, config_entry): async def async_unload_entry(hass, config_entry): """Unload Axis device config entry.""" - device = hass.data[DOMAIN].pop(config_entry.data[CONF_MAC]) + device = hass.data[AXIS_DOMAIN].pop(config_entry.unique_id) return await device.async_reset() -async def async_populate_options(hass, config_entry): - """Populate default options for device.""" - device = await get_device( - hass, - host=config_entry.data[CONF_HOST], - port=config_entry.data[CONF_PORT], - username=config_entry.data[CONF_USERNAME], - password=config_entry.data[CONF_PASSWORD], - ) - - supported_formats = device.vapix.params.image_format - camera = bool(supported_formats) - - options = { - CONF_CAMERA: camera, - CONF_EVENTS: True, - CONF_TRIGGER_TIME: DEFAULT_TRIGGER_TIME, - } - - hass.config_entries.async_update_entry(config_entry, options=options) - - async def async_migrate_entry(hass, config_entry): """Migrate old entry.""" LOGGER.debug("Migrating from version %s", config_entry.version) diff --git a/homeassistant/components/axis/axis_base.py b/homeassistant/components/axis/axis_base.py index 2e848168b49..976e779c20e 100644 --- a/homeassistant/components/axis/axis_base.py +++ b/homeassistant/components/axis/axis_base.py @@ -18,7 +18,7 @@ class AxisEntityBase(Entity): """Subscribe device events.""" self.async_on_remove( async_dispatcher_connect( - self.hass, self.device.event_reachable, self.update_callback + self.hass, self.device.signal_reachable, self.update_callback ) ) @@ -49,15 +49,12 @@ class AxisEventBase(AxisEntityBase): async def async_added_to_hass(self) -> None: """Subscribe sensors events.""" self.event.register_callback(self.update_callback) - await super().async_added_to_hass() async def async_will_remove_from_hass(self) -> None: """Disconnect device object when removed.""" self.event.remove_callback(self.update_callback) - await super().async_will_remove_from_hass() - @property def device_class(self): """Return the class of the event.""" diff --git a/homeassistant/components/axis/binary_sensor.py b/homeassistant/components/axis/binary_sensor.py index 4709d706ad0..83ea325a9dd 100644 --- a/homeassistant/components/axis/binary_sensor.py +++ b/homeassistant/components/axis/binary_sensor.py @@ -5,7 +5,6 @@ from datetime import timedelta from axis.event_stream import CLASS_INPUT, CLASS_OUTPUT 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 from homeassistant.helpers.event import async_track_point_in_utc_time @@ -22,13 +21,13 @@ async def async_setup_entry(hass, config_entry, async_add_entities): @callback def async_add_sensor(event_id): """Add binary sensor from Axis device.""" - event = device.api.event.events[event_id] + event = device.api.event[event_id] if event.CLASS != CLASS_OUTPUT: async_add_entities([AxisBinarySensor(event, device)], True) device.listeners.append( - async_dispatcher_connect(hass, device.event_new_sensor, async_add_sensor) + async_dispatcher_connect(hass, device.signal_new_event, async_add_sensor) ) @@ -38,7 +37,7 @@ class AxisBinarySensor(AxisEventBase, BinarySensorEntity): def __init__(self, event, device): """Initialize the Axis binary sensor.""" super().__init__(event, device) - self.remove_timer = None + self.cancel_scheduled_update = None @callback def update_callback(self, no_delay=False): @@ -46,24 +45,25 @@ class AxisBinarySensor(AxisEventBase, BinarySensorEntity): Parameter no_delay is True when device_event_reachable is sent. """ - delay = self.device.config_entry.options[CONF_TRIGGER_TIME] - if self.remove_timer is not None: - self.remove_timer() - self.remove_timer = None + @callback + def scheduled_update(now): + """Timer callback for sensor update.""" + self.cancel_scheduled_update = None + self.async_write_ha_state() - if self.is_on or delay == 0 or no_delay: + if self.cancel_scheduled_update is not None: + self.cancel_scheduled_update() + self.cancel_scheduled_update = None + + if self.is_on or self.device.option_trigger_time == 0 or no_delay: self.async_write_ha_state() return - @callback - def _delay_update(now): - """Timer callback for sensor update.""" - self.async_write_ha_state() - self.remove_timer = None - - self.remove_timer = async_track_point_in_utc_time( - self.hass, _delay_update, utcnow() + timedelta(seconds=delay) + self.cancel_scheduled_update = async_track_point_in_utc_time( + self.hass, + scheduled_update, + utcnow() + timedelta(seconds=self.device.option_trigger_time), ) @property diff --git a/homeassistant/components/axis/camera.py b/homeassistant/components/axis/camera.py index ca76552a4cc..649e512718c 100644 --- a/homeassistant/components/axis/camera.py +++ b/homeassistant/components/axis/camera.py @@ -59,7 +59,7 @@ class AxisCamera(AxisEntityBase, MjpegCamera): """Subscribe camera events.""" self.async_on_remove( async_dispatcher_connect( - self.hass, self.device.event_new_address, self._new_address + self.hass, self.device.signal_new_address, self._new_address ) ) diff --git a/homeassistant/components/axis/const.py b/homeassistant/components/axis/const.py index 7f0fd9c8947..1d52677b30c 100644 --- a/homeassistant/components/axis/const.py +++ b/homeassistant/components/axis/const.py @@ -5,8 +5,11 @@ LOGGER = logging.getLogger(__package__) DOMAIN = "axis" +ATTR_MANUFACTURER = "Axis Communications AB" + CONF_CAMERA = "camera" CONF_EVENTS = "events" CONF_MODEL = "model" +DEFAULT_EVENTS = True DEFAULT_TRIGGER_TIME = 0 diff --git a/homeassistant/components/axis/device.py b/homeassistant/components/axis/device.py index a204136e018..57d2d1be5d7 100644 --- a/homeassistant/components/axis/device.py +++ b/homeassistant/components/axis/device.py @@ -4,13 +4,18 @@ import asyncio import async_timeout import axis +from axis.event_stream import OPERATION_INITIALIZED from axis.streammanager import SIGNAL_PLAYING +from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN +from homeassistant.components.camera import DOMAIN as CAMERA_DOMAIN +from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.const import ( CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_PORT, + CONF_TRIGGER_TIME, CONF_USERNAME, ) from homeassistant.core import callback @@ -18,7 +23,16 @@ from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from homeassistant.helpers.dispatcher import async_dispatcher_send -from .const import CONF_CAMERA, CONF_EVENTS, CONF_MODEL, DOMAIN, LOGGER +from .const import ( + ATTR_MANUFACTURER, + CONF_CAMERA, + CONF_EVENTS, + CONF_MODEL, + DEFAULT_EVENTS, + DEFAULT_TRIGGER_TIME, + DOMAIN as AXIS_DOMAIN, + LOGGER, +) from .errors import AuthenticationRequired, CannotConnect @@ -57,14 +71,74 @@ class AxisNetworkDevice: """Return the serial number of this device.""" return self.config_entry.unique_id + @property + def option_camera(self): + """Config entry option defining if camera should be used.""" + supported_formats = self.api.vapix.params.image_format + return self.config_entry.options.get(CONF_CAMERA, bool(supported_formats)) + + @property + def option_events(self): + """Config entry option defining if platforms based on events should be created.""" + return self.config_entry.options.get(CONF_EVENTS, DEFAULT_EVENTS) + + @property + def option_trigger_time(self): + """Config entry option defining minimum number of seconds to keep trigger high.""" + return self.config_entry.options.get(CONF_TRIGGER_TIME, DEFAULT_TRIGGER_TIME) + + @property + def signal_reachable(self): + """Device specific event to signal a change in connection status.""" + return f"axis_reachable_{self.serial}" + + @property + def signal_new_event(self): + """Device specific event to signal new device event available.""" + return f"axis_new_event_{self.serial}" + + @property + def signal_new_address(self): + """Device specific event to signal a change in device address.""" + return f"axis_new_address_{self.serial}" + + @callback + def async_connection_status_callback(self, status): + """Handle signals of device connection status. + + This is called on every RTSP keep-alive message. + Only signal state change if state change is true. + """ + + if self.available != (status == SIGNAL_PLAYING): + self.available = not self.available + async_dispatcher_send(self.hass, self.signal_reachable, True) + + @callback + def async_event_callback(self, action, event_id): + """Call to configure events when initialized on event stream.""" + if action == OPERATION_INITIALIZED: + async_dispatcher_send(self.hass, self.signal_new_event, event_id) + + @staticmethod + async def async_new_address_callback(hass, entry): + """Handle signals of device getting new address. + + This is a static method because a class method (bound method), + can not be used with weak references. + """ + device = hass.data[AXIS_DOMAIN][entry.unique_id] + device.api.config.host = device.host + async_dispatcher_send(hass, device.signal_new_address) + async def async_update_device_registry(self): """Update device registry.""" device_registry = await self.hass.helpers.device_registry.async_get_registry() device_registry.async_get_or_create( config_entry_id=self.config_entry.entry_id, connections={(CONNECTION_NETWORK_MAC, self.serial)}, - identifiers={(DOMAIN, self.serial)}, - manufacturer="Axis Communications AB", + identifiers={(AXIS_DOMAIN, self.serial)}, + manufacturer=ATTR_MANUFACTURER, model=f"{self.model} {self.product_type}", name=self.name, sw_version=self.fw_version, @@ -91,15 +165,15 @@ class AxisNetworkDevice: self.fw_version = self.api.vapix.params.firmware_version self.product_type = self.api.vapix.params.prodtype - if self.config_entry.options[CONF_CAMERA]: + if self.option_camera: self.hass.async_create_task( self.hass.config_entries.async_forward_entry_setup( - self.config_entry, "camera" + self.config_entry, CAMERA_DOMAIN ) ) - if self.config_entry.options[CONF_EVENTS]: + if self.option_events: self.api.stream.connection_status_callback = ( self.async_connection_status_callback @@ -110,7 +184,7 @@ class AxisNetworkDevice: self.hass.config_entries.async_forward_entry_setup( self.config_entry, platform ) - for platform in ["binary_sensor", "switch"] + for platform in [BINARY_SENSOR_DOMAIN, SWITCH_DOMAIN] ] self.hass.async_create_task(self.start(platform_tasks)) @@ -118,50 +192,6 @@ class AxisNetworkDevice: return True - @property - def event_new_address(self): - """Device specific event to signal new device address.""" - return f"axis_new_address_{self.serial}" - - @staticmethod - async def async_new_address_callback(hass, entry): - """Handle signals of device getting new address. - - This is a static method because a class method (bound method), - can not be used with weak references. - """ - device = hass.data[DOMAIN][entry.unique_id] - device.api.config.host = device.host - async_dispatcher_send(hass, device.event_new_address) - - @property - def event_reachable(self): - """Device specific event to signal a change in connection status.""" - return f"axis_reachable_{self.serial}" - - @callback - def async_connection_status_callback(self, status): - """Handle signals of device connection status. - - This is called on every RTSP keep-alive message. - Only signal state change if state change is true. - """ - - if self.available != (status == SIGNAL_PLAYING): - self.available = not self.available - async_dispatcher_send(self.hass, self.event_reachable, True) - - @property - def event_new_sensor(self): - """Device specific event to signal new sensor available.""" - return f"axis_add_sensor_{self.serial}" - - @callback - def async_event_callback(self, action, event_id): - """Call to configure events when initialized on event stream.""" - if action == "add": - async_dispatcher_send(self.hass, self.event_new_sensor, event_id) - async def start(self, platform_tasks): """Start the event stream when all platforms are loaded.""" await asyncio.gather(*platform_tasks) @@ -179,7 +209,7 @@ class AxisNetworkDevice: if self.config_entry.options[CONF_CAMERA]: platform_tasks.append( self.hass.config_entries.async_forward_entry_unload( - self.config_entry, "camera" + self.config_entry, CAMERA_DOMAIN ) ) @@ -189,7 +219,7 @@ class AxisNetworkDevice: self.hass.config_entries.async_forward_entry_unload( self.config_entry, platform ) - for platform in ["binary_sensor", "switch"] + for platform in [BINARY_SENSOR_DOMAIN, SWITCH_DOMAIN] ] await asyncio.gather(*platform_tasks) @@ -205,12 +235,7 @@ async def get_device(hass, host, port, username, password): """Create a Axis device.""" device = axis.AxisDevice( - loop=hass.loop, - host=host, - port=port, - username=username, - password=password, - web_proto="http", + host=host, port=port, username=username, password=password, web_proto="http", ) device.vapix.initialize_params(preload_data=False) diff --git a/homeassistant/components/axis/manifest.json b/homeassistant/components/axis/manifest.json index 8a2530b2022..d532ac4c9e4 100644 --- a/homeassistant/components/axis/manifest.json +++ b/homeassistant/components/axis/manifest.json @@ -3,7 +3,7 @@ "name": "Axis", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/axis", - "requirements": ["axis==25"], + "requirements": ["axis==26"], "zeroconf": ["_axis-video._tcp.local."], "codeowners": ["@Kane610"] } diff --git a/homeassistant/components/axis/switch.py b/homeassistant/components/axis/switch.py index be048a510ed..db91115484f 100644 --- a/homeassistant/components/axis/switch.py +++ b/homeassistant/components/axis/switch.py @@ -1,6 +1,7 @@ """Support for Axis switches.""" from axis.event_stream import CLASS_OUTPUT +from axis.port_cgi import ACTION_HIGH, ACTION_LOW from homeassistant.components.switch import SwitchEntity from homeassistant.core import callback @@ -17,13 +18,13 @@ async def async_setup_entry(hass, config_entry, async_add_entities): @callback def async_add_switch(event_id): """Add switch from Axis device.""" - event = device.api.event.events[event_id] + event = device.api.event[event_id] if event.CLASS == CLASS_OUTPUT: async_add_entities([AxisSwitch(event, device)], True) device.listeners.append( - async_dispatcher_connect(hass, device.event_new_sensor, async_add_switch) + async_dispatcher_connect(hass, device.signal_new_event, async_add_switch) ) @@ -37,16 +38,14 @@ class AxisSwitch(AxisEventBase, SwitchEntity): async def async_turn_on(self, **kwargs): """Turn on switch.""" - action = "/" await self.hass.async_add_executor_job( - self.device.api.vapix.ports[self.event.id].action, action + self.device.api.vapix.ports[self.event.id].action, ACTION_HIGH ) async def async_turn_off(self, **kwargs): """Turn off switch.""" - action = "\\" await self.hass.async_add_executor_job( - self.device.api.vapix.ports[self.event.id].action, action + self.device.api.vapix.ports[self.event.id].action, ACTION_LOW ) @property diff --git a/requirements_all.txt b/requirements_all.txt index 02d03372d85..31acf734e6c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -303,7 +303,7 @@ avea==1.4 avri-api==0.1.7 # homeassistant.components.axis -axis==25 +axis==26 # homeassistant.components.azure_event_hub azure-eventhub==1.3.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 80d873d243f..ee29a113dc4 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -141,7 +141,7 @@ async-upnp-client==0.14.13 av==7.0.1 # homeassistant.components.axis -axis==25 +axis==26 # homeassistant.components.homekit base36==0.1.1 diff --git a/tests/components/axis/test_binary_sensor.py b/tests/components/axis/test_binary_sensor.py index d70d55e0d1e..6ff215f2488 100644 --- a/tests/components/axis/test_binary_sensor.py +++ b/tests/components/axis/test_binary_sensor.py @@ -1,7 +1,7 @@ """Axis binary sensor platform tests.""" -from homeassistant.components import axis -import homeassistant.components.binary_sensor as binary_sensor +from homeassistant.components.axis.const import DOMAIN as AXIS_DOMAIN +from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN from homeassistant.setup import async_setup_component from .test_device import NAME, setup_axis_integration @@ -28,19 +28,21 @@ async def test_platform_manually_configured(hass): """Test that nothing happens when platform is manually configured.""" assert ( await async_setup_component( - hass, binary_sensor.DOMAIN, {"binary_sensor": {"platform": axis.DOMAIN}} + hass, + BINARY_SENSOR_DOMAIN, + {BINARY_SENSOR_DOMAIN: {"platform": AXIS_DOMAIN}}, ) is True ) - assert axis.DOMAIN not in hass.data + assert AXIS_DOMAIN not in hass.data async def test_no_binary_sensors(hass): """Test that no sensors in Axis results in no sensor entities.""" await setup_axis_integration(hass) - assert not hass.states.async_entity_ids("binary_sensor") + assert not hass.states.async_entity_ids(BINARY_SENSOR_DOMAIN) async def test_binary_sensors(hass): @@ -48,10 +50,10 @@ async def test_binary_sensors(hass): device = await setup_axis_integration(hass) for event in EVENTS: - device.api.stream.event.manage_event(event) + device.api.event.process_event(event) await hass.async_block_till_done() - assert len(hass.states.async_entity_ids("binary_sensor")) == 2 + assert len(hass.states.async_entity_ids(BINARY_SENSOR_DOMAIN)) == 2 pir = hass.states.get(f"binary_sensor.{NAME}_pir_0") assert pir.state == "off" diff --git a/tests/components/axis/test_camera.py b/tests/components/axis/test_camera.py index 5cbc5e993ca..6281c87740c 100644 --- a/tests/components/axis/test_camera.py +++ b/tests/components/axis/test_camera.py @@ -1,7 +1,7 @@ """Axis camera platform tests.""" -from homeassistant.components import axis -import homeassistant.components.camera as camera +from homeassistant.components.axis.const import DOMAIN as AXIS_DOMAIN +from homeassistant.components.camera import DOMAIN as CAMERA_DOMAIN from homeassistant.setup import async_setup_component from .test_device import NAME, setup_axis_integration @@ -11,19 +11,19 @@ async def test_platform_manually_configured(hass): """Test that nothing happens when platform is manually configured.""" assert ( await async_setup_component( - hass, camera.DOMAIN, {"camera": {"platform": axis.DOMAIN}} + hass, CAMERA_DOMAIN, {"camera": {"platform": AXIS_DOMAIN}} ) is True ) - assert axis.DOMAIN not in hass.data + assert AXIS_DOMAIN not in hass.data async def test_camera(hass): """Test that Axis camera platform is loaded properly.""" await setup_axis_integration(hass) - assert len(hass.states.async_entity_ids("camera")) == 1 + assert len(hass.states.async_entity_ids(CAMERA_DOMAIN)) == 1 cam = hass.states.get(f"camera.{NAME}") assert cam.state == "idle" diff --git a/tests/components/axis/test_config_flow.py b/tests/components/axis/test_config_flow.py index d3972332c89..7cc0e3e535b 100644 --- a/tests/components/axis/test_config_flow.py +++ b/tests/components/axis/test_config_flow.py @@ -1,6 +1,15 @@ """Test Axis config flow.""" from homeassistant.components import axis from homeassistant.components.axis import config_flow +from homeassistant.components.axis.const import CONF_MODEL, DOMAIN as AXIS_DOMAIN +from homeassistant.const import ( + CONF_HOST, + CONF_MAC, + CONF_NAME, + CONF_PASSWORD, + CONF_PORT, + CONF_USERNAME, +) from .test_device import MAC, MODEL, NAME, setup_axis_integration @@ -11,9 +20,8 @@ from tests.common import MockConfigEntry def setup_mock_axis_device(mock_device): """Prepare mock axis device.""" - def mock_constructor(loop, host, username, password, port, web_proto): + def mock_constructor(host, username, password, port, web_proto): """Fake the controller constructor.""" - mock_device.loop = loop mock_device.host = host mock_device.username = username mock_device.password = password @@ -30,7 +38,7 @@ def setup_mock_axis_device(mock_device): async def test_flow_manual_configuration(hass): """Test that config flow works.""" result = await hass.config_entries.flow.async_init( - config_flow.DOMAIN, context={"source": "user"} + AXIS_DOMAIN, context={"source": "user"} ) assert result["type"] == "form" @@ -43,23 +51,23 @@ async def test_flow_manual_configuration(hass): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={ - config_flow.CONF_HOST: "1.2.3.4", - config_flow.CONF_USERNAME: "user", - config_flow.CONF_PASSWORD: "pass", - config_flow.CONF_PORT: 80, + CONF_HOST: "1.2.3.4", + CONF_USERNAME: "user", + CONF_PASSWORD: "pass", + CONF_PORT: 80, }, ) assert result["type"] == "create_entry" assert result["title"] == f"prodnbr - {MAC}" assert result["data"] == { - config_flow.CONF_HOST: "1.2.3.4", - config_flow.CONF_USERNAME: "user", - config_flow.CONF_PASSWORD: "pass", - config_flow.CONF_PORT: 80, - config_flow.CONF_MAC: MAC, - config_flow.CONF_MODEL: "prodnbr", - config_flow.CONF_NAME: "prodnbr 0", + CONF_HOST: "1.2.3.4", + CONF_USERNAME: "user", + CONF_PASSWORD: "pass", + CONF_PORT: 80, + CONF_MAC: MAC, + CONF_MODEL: "prodnbr", + CONF_NAME: "prodnbr 0", } @@ -68,7 +76,7 @@ async def test_manual_configuration_update_configuration(hass): device = await setup_axis_integration(hass) result = await hass.config_entries.flow.async_init( - config_flow.DOMAIN, context={"source": "user"} + AXIS_DOMAIN, context={"source": "user"} ) assert result["type"] == "form" @@ -84,16 +92,16 @@ async def test_manual_configuration_update_configuration(hass): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={ - config_flow.CONF_HOST: "2.3.4.5", - config_flow.CONF_USERNAME: "user", - config_flow.CONF_PASSWORD: "pass", - config_flow.CONF_PORT: 80, + CONF_HOST: "2.3.4.5", + CONF_USERNAME: "user", + CONF_PASSWORD: "pass", + CONF_PORT: 80, }, ) assert result["type"] == "abort" assert result["reason"] == "already_configured" - assert device.config_entry.data[config_flow.CONF_HOST] == "2.3.4.5" + assert device.host == "2.3.4.5" async def test_flow_fails_already_configured(hass): @@ -101,7 +109,7 @@ async def test_flow_fails_already_configured(hass): await setup_axis_integration(hass) result = await hass.config_entries.flow.async_init( - config_flow.DOMAIN, context={"source": "user"} + AXIS_DOMAIN, context={"source": "user"} ) assert result["type"] == "form" @@ -117,10 +125,10 @@ async def test_flow_fails_already_configured(hass): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={ - config_flow.CONF_HOST: "1.2.3.4", - config_flow.CONF_USERNAME: "user", - config_flow.CONF_PASSWORD: "pass", - config_flow.CONF_PORT: 80, + CONF_HOST: "1.2.3.4", + CONF_USERNAME: "user", + CONF_PASSWORD: "pass", + CONF_PORT: 80, }, ) @@ -131,7 +139,7 @@ async def test_flow_fails_already_configured(hass): async def test_flow_fails_faulty_credentials(hass): """Test that config flow fails on faulty credentials.""" result = await hass.config_entries.flow.async_init( - config_flow.DOMAIN, context={"source": "user"} + AXIS_DOMAIN, context={"source": "user"} ) assert result["type"] == "form" @@ -144,10 +152,10 @@ async def test_flow_fails_faulty_credentials(hass): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={ - config_flow.CONF_HOST: "1.2.3.4", - config_flow.CONF_USERNAME: "user", - config_flow.CONF_PASSWORD: "pass", - config_flow.CONF_PORT: 80, + CONF_HOST: "1.2.3.4", + CONF_USERNAME: "user", + CONF_PASSWORD: "pass", + CONF_PORT: 80, }, ) @@ -157,7 +165,7 @@ async def test_flow_fails_faulty_credentials(hass): async def test_flow_fails_device_unavailable(hass): """Test that config flow fails on device unavailable.""" result = await hass.config_entries.flow.async_init( - config_flow.DOMAIN, context={"source": "user"} + AXIS_DOMAIN, context={"source": "user"} ) assert result["type"] == "form" @@ -170,10 +178,10 @@ async def test_flow_fails_device_unavailable(hass): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={ - config_flow.CONF_HOST: "1.2.3.4", - config_flow.CONF_USERNAME: "user", - config_flow.CONF_PASSWORD: "pass", - config_flow.CONF_PORT: 80, + CONF_HOST: "1.2.3.4", + CONF_USERNAME: "user", + CONF_PASSWORD: "pass", + CONF_PORT: 80, }, ) @@ -183,18 +191,16 @@ async def test_flow_fails_device_unavailable(hass): async def test_flow_create_entry_multiple_existing_entries_of_same_model(hass): """Test that create entry can generate a name with other entries.""" entry = MockConfigEntry( - domain=axis.DOMAIN, - data={config_flow.CONF_NAME: "prodnbr 0", config_flow.CONF_MODEL: "prodnbr"}, + domain=AXIS_DOMAIN, data={CONF_NAME: "prodnbr 0", CONF_MODEL: "prodnbr"}, ) entry.add_to_hass(hass) entry2 = MockConfigEntry( - domain=axis.DOMAIN, - data={config_flow.CONF_NAME: "prodnbr 1", config_flow.CONF_MODEL: "prodnbr"}, + domain=AXIS_DOMAIN, data={CONF_NAME: "prodnbr 1", CONF_MODEL: "prodnbr"}, ) entry2.add_to_hass(hass) result = await hass.config_entries.flow.async_init( - config_flow.DOMAIN, context={"source": "user"} + AXIS_DOMAIN, context={"source": "user"} ) assert result["type"] == "form" @@ -207,36 +213,36 @@ async def test_flow_create_entry_multiple_existing_entries_of_same_model(hass): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={ - config_flow.CONF_HOST: "1.2.3.4", - config_flow.CONF_USERNAME: "user", - config_flow.CONF_PASSWORD: "pass", - config_flow.CONF_PORT: 80, + CONF_HOST: "1.2.3.4", + CONF_USERNAME: "user", + CONF_PASSWORD: "pass", + CONF_PORT: 80, }, ) assert result["type"] == "create_entry" assert result["title"] == f"prodnbr - {MAC}" assert result["data"] == { - config_flow.CONF_HOST: "1.2.3.4", - config_flow.CONF_USERNAME: "user", - config_flow.CONF_PASSWORD: "pass", - config_flow.CONF_PORT: 80, - config_flow.CONF_MAC: MAC, - config_flow.CONF_MODEL: "prodnbr", - config_flow.CONF_NAME: "prodnbr 2", + CONF_HOST: "1.2.3.4", + CONF_USERNAME: "user", + CONF_PASSWORD: "pass", + CONF_PORT: 80, + CONF_MAC: MAC, + CONF_MODEL: "prodnbr", + CONF_NAME: "prodnbr 2", } - assert result["data"][config_flow.CONF_NAME] == "prodnbr 2" + assert result["data"][CONF_NAME] == "prodnbr 2" async def test_zeroconf_flow(hass): """Test that zeroconf discovery for new devices work.""" - with patch.object(axis, "get_device", return_value=Mock()): + with patch.object(axis.device, "get_device", return_value=Mock()): result = await hass.config_entries.flow.async_init( - config_flow.DOMAIN, + AXIS_DOMAIN, data={ - config_flow.CONF_HOST: "1.2.3.4", - config_flow.CONF_PORT: 80, + CONF_HOST: "1.2.3.4", + CONF_PORT: 80, "hostname": "name", "properties": {"macaddress": MAC}, }, @@ -253,26 +259,26 @@ async def test_zeroconf_flow(hass): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={ - config_flow.CONF_HOST: "1.2.3.4", - config_flow.CONF_USERNAME: "user", - config_flow.CONF_PASSWORD: "pass", - config_flow.CONF_PORT: 80, + CONF_HOST: "1.2.3.4", + CONF_USERNAME: "user", + CONF_PASSWORD: "pass", + CONF_PORT: 80, }, ) assert result["type"] == "create_entry" assert result["title"] == f"prodnbr - {MAC}" assert result["data"] == { - config_flow.CONF_HOST: "1.2.3.4", - config_flow.CONF_USERNAME: "user", - config_flow.CONF_PASSWORD: "pass", - config_flow.CONF_PORT: 80, - config_flow.CONF_MAC: MAC, - config_flow.CONF_MODEL: "prodnbr", - config_flow.CONF_NAME: "prodnbr 0", + CONF_HOST: "1.2.3.4", + CONF_USERNAME: "user", + CONF_PASSWORD: "pass", + CONF_PORT: 80, + CONF_MAC: MAC, + CONF_MODEL: "prodnbr", + CONF_NAME: "prodnbr 0", } - assert result["data"][config_flow.CONF_NAME] == "prodnbr 0" + assert result["data"][CONF_NAME] == "prodnbr 0" async def test_zeroconf_flow_already_configured(hass): @@ -281,10 +287,10 @@ async def test_zeroconf_flow_already_configured(hass): assert device.host == "1.2.3.4" result = await hass.config_entries.flow.async_init( - config_flow.DOMAIN, + AXIS_DOMAIN, data={ - config_flow.CONF_HOST: "1.2.3.4", - config_flow.CONF_PORT: 80, + CONF_HOST: "1.2.3.4", + CONF_PORT: 80, "hostname": "name", "properties": {"macaddress": MAC}, }, @@ -301,20 +307,20 @@ async def test_zeroconf_flow_updated_configuration(hass): device = await setup_axis_integration(hass) assert device.host == "1.2.3.4" assert device.config_entry.data == { - config_flow.CONF_HOST: "1.2.3.4", - config_flow.CONF_PORT: 80, - config_flow.CONF_USERNAME: "username", - config_flow.CONF_PASSWORD: "password", - config_flow.CONF_MAC: MAC, - config_flow.CONF_MODEL: MODEL, - config_flow.CONF_NAME: NAME, + CONF_HOST: "1.2.3.4", + CONF_PORT: 80, + CONF_USERNAME: "username", + CONF_PASSWORD: "password", + CONF_MAC: MAC, + CONF_MODEL: MODEL, + CONF_NAME: NAME, } result = await hass.config_entries.flow.async_init( - config_flow.DOMAIN, + AXIS_DOMAIN, data={ - config_flow.CONF_HOST: "2.3.4.5", - config_flow.CONF_PORT: 8080, + CONF_HOST: "2.3.4.5", + CONF_PORT: 8080, "hostname": "name", "properties": {"macaddress": MAC}, }, @@ -324,24 +330,21 @@ async def test_zeroconf_flow_updated_configuration(hass): assert result["type"] == "abort" assert result["reason"] == "already_configured" assert device.config_entry.data == { - config_flow.CONF_HOST: "2.3.4.5", - config_flow.CONF_PORT: 8080, - config_flow.CONF_USERNAME: "username", - config_flow.CONF_PASSWORD: "password", - config_flow.CONF_MAC: MAC, - config_flow.CONF_MODEL: MODEL, - config_flow.CONF_NAME: NAME, + CONF_HOST: "2.3.4.5", + CONF_PORT: 8080, + CONF_USERNAME: "username", + CONF_PASSWORD: "password", + CONF_MAC: MAC, + CONF_MODEL: MODEL, + CONF_NAME: NAME, } async def test_zeroconf_flow_ignore_non_axis_device(hass): """Test that zeroconf doesn't setup devices with link local addresses.""" result = await hass.config_entries.flow.async_init( - config_flow.DOMAIN, - data={ - config_flow.CONF_HOST: "169.254.3.4", - "properties": {"macaddress": "01234567890"}, - }, + AXIS_DOMAIN, + data={CONF_HOST: "169.254.3.4", "properties": {"macaddress": "01234567890"}}, context={"source": "zeroconf"}, ) @@ -352,8 +355,8 @@ async def test_zeroconf_flow_ignore_non_axis_device(hass): async def test_zeroconf_flow_ignore_link_local_address(hass): """Test that zeroconf doesn't setup devices with link local addresses.""" result = await hass.config_entries.flow.async_init( - config_flow.DOMAIN, - data={config_flow.CONF_HOST: "169.254.3.4", "properties": {"macaddress": MAC}}, + AXIS_DOMAIN, + data={CONF_HOST: "169.254.3.4", "properties": {"macaddress": MAC}}, context={"source": "zeroconf"}, ) diff --git a/tests/components/axis/test_device.py b/tests/components/axis/test_device.py index 74b0ab3b992..ec350695e6b 100644 --- a/tests/components/axis/test_device.py +++ b/tests/components/axis/test_device.py @@ -2,10 +2,25 @@ from copy import deepcopy import axis as axislib +from axis.event_stream import OPERATION_INITIALIZED import pytest from homeassistant import config_entries from homeassistant.components import axis +from homeassistant.components.axis.const import ( + CONF_CAMERA, + CONF_EVENTS, + CONF_MODEL, + DOMAIN as AXIS_DOMAIN, +) +from homeassistant.const import ( + CONF_HOST, + CONF_MAC, + CONF_NAME, + CONF_PASSWORD, + CONF_PORT, + CONF_USERNAME, +) from tests.async_mock import Mock, patch from tests.common import MockConfigEntry @@ -14,16 +29,16 @@ MAC = "00408C12345" MODEL = "model" NAME = "name" -ENTRY_OPTIONS = {axis.CONF_CAMERA: True, axis.CONF_EVENTS: True} +ENTRY_OPTIONS = {CONF_CAMERA: True, CONF_EVENTS: True} ENTRY_CONFIG = { - axis.CONF_HOST: "1.2.3.4", - axis.CONF_USERNAME: "username", - axis.CONF_PASSWORD: "password", - axis.CONF_PORT: 80, - axis.CONF_MAC: MAC, - axis.device.CONF_MODEL: MODEL, - axis.device.CONF_NAME: NAME, + CONF_HOST: "1.2.3.4", + CONF_USERNAME: "username", + CONF_PASSWORD: "password", + CONF_PORT: 80, + CONF_MAC: MAC, + CONF_MODEL: MODEL, + CONF_NAME: NAME, } DEFAULT_BRAND = """root.Brand.Brand=AXIS @@ -67,7 +82,7 @@ async def setup_axis_integration( ): """Create the Axis device.""" config_entry = MockConfigEntry( - domain=axis.DOMAIN, + domain=AXIS_DOMAIN, data=deepcopy(config), connection_class=config_entries.CONN_CLASS_LOCAL_PUSH, options=deepcopy(options), @@ -95,7 +110,7 @@ async def setup_axis_integration( await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() - return hass.data[axis.DOMAIN].get(config[axis.CONF_MAC]) + return hass.data[AXIS_DOMAIN].get(config[CONF_MAC]) async def test_device_setup(hass): @@ -113,10 +128,10 @@ async def test_device_setup(hass): assert forward_entry_setup.mock_calls[1][1] == (entry, "binary_sensor") assert forward_entry_setup.mock_calls[2][1] == (entry, "switch") - assert device.host == ENTRY_CONFIG[axis.CONF_HOST] - assert device.model == ENTRY_CONFIG[axis.device.CONF_MODEL] - assert device.name == ENTRY_CONFIG[axis.device.CONF_NAME] - assert device.serial == ENTRY_CONFIG[axis.CONF_MAC] + assert device.host == ENTRY_CONFIG[CONF_HOST] + assert device.model == ENTRY_CONFIG[CONF_MODEL] + assert device.name == ENTRY_CONFIG[CONF_NAME] + assert device.serial == ENTRY_CONFIG[CONF_MAC] async def test_update_address(hass): @@ -125,7 +140,7 @@ async def test_update_address(hass): assert device.api.config.host == "1.2.3.4" await hass.config_entries.flow.async_init( - axis.DOMAIN, + AXIS_DOMAIN, data={ "host": "2.3.4.5", "port": 80, @@ -157,14 +172,14 @@ async def test_device_not_accessible(hass): """Failed setup schedules a retry of setup.""" with patch.object(axis.device, "get_device", side_effect=axis.errors.CannotConnect): await setup_axis_integration(hass) - assert hass.data[axis.DOMAIN] == {} + assert hass.data[AXIS_DOMAIN] == {} async def test_device_unknown_error(hass): """Unknown errors are handled.""" with patch.object(axis.device, "get_device", side_effect=Exception): await setup_axis_integration(hass) - assert hass.data[axis.DOMAIN] == {} + assert hass.data[AXIS_DOMAIN] == {} async def test_new_event_sends_signal(hass): @@ -175,7 +190,7 @@ async def test_new_event_sends_signal(hass): axis_device = axis.device.AxisNetworkDevice(hass, entry) with patch.object(axis.device, "async_dispatcher_send") as mock_dispatch_send: - axis_device.async_event_callback(action="add", event_id="event") + axis_device.async_event_callback(action=OPERATION_INITIALIZED, event_id="event") await hass.async_block_till_done() assert len(mock_dispatch_send.mock_calls) == 1 diff --git a/tests/components/axis/test_init.py b/tests/components/axis/test_init.py index b8baf18a67d..3feee94267a 100644 --- a/tests/components/axis/test_init.py +++ b/tests/components/axis/test_init.py @@ -1,5 +1,15 @@ """Test Axis component setup process.""" from homeassistant.components import axis +from homeassistant.components.axis.const import CONF_MODEL, DOMAIN as AXIS_DOMAIN +from homeassistant.const import ( + CONF_DEVICE, + CONF_HOST, + CONF_MAC, + CONF_NAME, + CONF_PASSWORD, + CONF_PORT, + CONF_USERNAME, +) from homeassistant.setup import async_setup_component from .test_device import MAC, setup_axis_integration @@ -10,21 +20,21 @@ from tests.common import MockConfigEntry async def test_setup_no_config(hass): """Test setup without configuration.""" - assert await async_setup_component(hass, axis.DOMAIN, {}) - assert axis.DOMAIN not in hass.data + assert await async_setup_component(hass, AXIS_DOMAIN, {}) + assert AXIS_DOMAIN not in hass.data async def test_setup_entry(hass): """Test successful setup of entry.""" await setup_axis_integration(hass) - assert len(hass.data[axis.DOMAIN]) == 1 - assert MAC in hass.data[axis.DOMAIN] + assert len(hass.data[AXIS_DOMAIN]) == 1 + assert MAC in hass.data[AXIS_DOMAIN] async def test_setup_entry_fails(hass): """Test successful setup of entry.""" config_entry = MockConfigEntry( - domain=axis.DOMAIN, data={axis.CONF_MAC: "0123"}, version=2 + domain=AXIS_DOMAIN, data={CONF_MAC: "0123"}, version=2 ) config_entry.add_to_hass(hass) @@ -36,43 +46,32 @@ async def test_setup_entry_fails(hass): assert not await hass.config_entries.async_setup(config_entry.entry_id) - assert not hass.data[axis.DOMAIN] + assert not hass.data[AXIS_DOMAIN] async def test_unload_entry(hass): """Test successful unload of entry.""" device = await setup_axis_integration(hass) - assert hass.data[axis.DOMAIN] + assert hass.data[AXIS_DOMAIN] assert await hass.config_entries.async_unload(device.config_entry.entry_id) - assert not hass.data[axis.DOMAIN] - - -async def test_populate_options(hass): - """Test successful populate options.""" - device = await setup_axis_integration(hass, options=None) - - assert device.config_entry.options == { - axis.CONF_CAMERA: True, - axis.CONF_EVENTS: True, - axis.CONF_TRIGGER_TIME: axis.DEFAULT_TRIGGER_TIME, - } + assert not hass.data[AXIS_DOMAIN] async def test_migrate_entry(hass): """Test successful migration of entry data.""" legacy_config = { - axis.CONF_DEVICE: { - axis.CONF_HOST: "1.2.3.4", - axis.CONF_USERNAME: "username", - axis.CONF_PASSWORD: "password", - axis.CONF_PORT: 80, + CONF_DEVICE: { + CONF_HOST: "1.2.3.4", + CONF_USERNAME: "username", + CONF_PASSWORD: "password", + CONF_PORT: 80, }, - axis.CONF_MAC: "mac", - axis.device.CONF_MODEL: "model", - axis.device.CONF_NAME: "name", + CONF_MAC: "mac", + CONF_MODEL: "model", + CONF_NAME: "name", } - entry = MockConfigEntry(domain=axis.DOMAIN, data=legacy_config) + entry = MockConfigEntry(domain=AXIS_DOMAIN, data=legacy_config) assert entry.data == legacy_config assert entry.version == 1 @@ -80,18 +79,18 @@ async def test_migrate_entry(hass): await entry.async_migrate(hass) assert entry.data == { - axis.CONF_DEVICE: { - axis.CONF_HOST: "1.2.3.4", - axis.CONF_USERNAME: "username", - axis.CONF_PASSWORD: "password", - axis.CONF_PORT: 80, + CONF_DEVICE: { + CONF_HOST: "1.2.3.4", + CONF_USERNAME: "username", + CONF_PASSWORD: "password", + CONF_PORT: 80, }, - axis.CONF_HOST: "1.2.3.4", - axis.CONF_USERNAME: "username", - axis.CONF_PASSWORD: "password", - axis.CONF_PORT: 80, - axis.CONF_MAC: "mac", - axis.device.CONF_MODEL: "model", - axis.device.CONF_NAME: "name", + CONF_HOST: "1.2.3.4", + CONF_USERNAME: "username", + CONF_PASSWORD: "password", + CONF_PORT: 80, + CONF_MAC: "mac", + CONF_MODEL: "model", + CONF_NAME: "name", } assert entry.version == 2 diff --git a/tests/components/axis/test_switch.py b/tests/components/axis/test_switch.py index d8d69265f3a..98ca5141a81 100644 --- a/tests/components/axis/test_switch.py +++ b/tests/components/axis/test_switch.py @@ -1,7 +1,9 @@ """Axis switch platform tests.""" -from homeassistant.components import axis -import homeassistant.components.switch as switch +from axis.port_cgi import ACTION_HIGH, ACTION_LOW + +from homeassistant.components.axis.const import DOMAIN as AXIS_DOMAIN +from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.setup import async_setup_component from .test_device import NAME, setup_axis_integration @@ -31,17 +33,17 @@ EVENTS = [ async def test_platform_manually_configured(hass): """Test that nothing happens when platform is manually configured.""" assert await async_setup_component( - hass, switch.DOMAIN, {"switch": {"platform": axis.DOMAIN}} + hass, SWITCH_DOMAIN, {SWITCH_DOMAIN: {"platform": AXIS_DOMAIN}} ) - assert axis.DOMAIN not in hass.data + assert AXIS_DOMAIN not in hass.data async def test_no_switches(hass): """Test that no output events in Axis results in no switch entities.""" await setup_axis_integration(hass) - assert not hass.states.async_entity_ids("switch") + assert not hass.states.async_entity_ids(SWITCH_DOMAIN) async def test_switches(hass): @@ -53,10 +55,10 @@ async def test_switches(hass): device.api.vapix.ports["1"].name = "" for event in EVENTS: - device.api.stream.event.manage_event(event) + device.api.event.process_event(event) 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 relay_0 = hass.states.get(f"switch.{NAME}_doorbell") assert relay_0.state == "off" @@ -69,14 +71,20 @@ async def test_switches(hass): device.api.vapix.ports["0"].action = Mock() await hass.services.async_call( - "switch", "turn_on", {"entity_id": f"switch.{NAME}_doorbell"}, blocking=True + SWITCH_DOMAIN, + "turn_on", + {"entity_id": f"switch.{NAME}_doorbell"}, + blocking=True, ) await hass.services.async_call( - "switch", "turn_off", {"entity_id": f"switch.{NAME}_doorbell"}, blocking=True + SWITCH_DOMAIN, + "turn_off", + {"entity_id": f"switch.{NAME}_doorbell"}, + blocking=True, ) assert device.api.vapix.ports["0"].action.call_args_list == [ - mock_call("/"), - mock_call("\\"), + mock_call(ACTION_HIGH), + mock_call(ACTION_LOW), ] From c9b702c4c2471773be4bcb6faca1a184bdadaacb Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Thu, 14 May 2020 03:24:22 -0600 Subject: [PATCH 009/406] Remove Automatic integration (#35029) --- .coveragerc | 1 - CODEOWNERS | 1 - .../components/automatic/__init__.py | 1 - .../components/automatic/device_tracker.py | 361 ------------------ .../components/automatic/manifest.json | 8 - requirements_all.txt | 3 - 6 files changed, 375 deletions(-) delete mode 100644 homeassistant/components/automatic/__init__.py delete mode 100644 homeassistant/components/automatic/device_tracker.py delete mode 100644 homeassistant/components/automatic/manifest.json diff --git a/.coveragerc b/.coveragerc index fb4e88903b4..ffbcda1b56a 100644 --- a/.coveragerc +++ b/.coveragerc @@ -62,7 +62,6 @@ omit = homeassistant/components/aten_pe/* homeassistant/components/atome/* homeassistant/components/aurora_abb_powerone/sensor.py - homeassistant/components/automatic/* homeassistant/components/avea/light.py homeassistant/components/avion/light.py homeassistant/components/avri/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index 07c99d48b86..5a2723a5d83 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -42,7 +42,6 @@ homeassistant/components/atome/* @baqs homeassistant/components/august/* @bdraco homeassistant/components/aurora_abb_powerone/* @davet2001 homeassistant/components/auth/* @home-assistant/core -homeassistant/components/automatic/* @armills homeassistant/components/automation/* @home-assistant/core homeassistant/components/avea/* @pattyland homeassistant/components/avri/* @timvancann diff --git a/homeassistant/components/automatic/__init__.py b/homeassistant/components/automatic/__init__.py deleted file mode 100644 index 8a1cae16f1e..00000000000 --- a/homeassistant/components/automatic/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""The automatic component.""" diff --git a/homeassistant/components/automatic/device_tracker.py b/homeassistant/components/automatic/device_tracker.py deleted file mode 100644 index 0f48ef6376d..00000000000 --- a/homeassistant/components/automatic/device_tracker.py +++ /dev/null @@ -1,361 +0,0 @@ -"""Support for the Automatic platform.""" -import asyncio -from datetime import timedelta -import json -import logging -import os - -import aioautomatic -from aiohttp import web -import voluptuous as vol - -from homeassistant.components.device_tracker import ( - ATTR_ATTRIBUTES, - ATTR_DEV_ID, - ATTR_GPS, - ATTR_GPS_ACCURACY, - ATTR_HOST_NAME, - ATTR_MAC, - PLATFORM_SCHEMA, -) -from homeassistant.components.http import HomeAssistantView -from homeassistant.const import EVENT_HOMEASSISTANT_STOP -from homeassistant.core import callback -from homeassistant.helpers.aiohttp_client import async_get_clientsession -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.event import async_track_time_interval - -_LOGGER = logging.getLogger(__name__) - -ATTR_FUEL_LEVEL = "fuel_level" - -CONF_CLIENT_ID = "client_id" -CONF_CURRENT_LOCATION = "current_location" -CONF_DEVICES = "devices" -CONF_SECRET = "secret" - -DATA_CONFIGURING = "automatic_configurator_clients" -DATA_REFRESH_TOKEN = "refresh_token" -DEFAULT_SCOPE = ["location", "trip", "vehicle:events", "vehicle:profile"] -DEFAULT_TIMEOUT = 5 -EVENT_AUTOMATIC_UPDATE = "automatic_update" - -FULL_SCOPE = DEFAULT_SCOPE + ["current_location"] - -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( - { - vol.Required(CONF_CLIENT_ID): cv.string, - vol.Required(CONF_SECRET): cv.string, - vol.Optional(CONF_CURRENT_LOCATION, default=False): cv.boolean, - vol.Optional(CONF_DEVICES): vol.All(cv.ensure_list, [cv.string]), - } -) - - -def _get_refresh_token_from_file(hass, filename): - """Attempt to load session data from file.""" - path = hass.config.path(filename) - - if not os.path.isfile(path): - return None - - try: - with open(path) as data_file: - data = json.load(data_file) - if data is None: - return None - - return data.get(DATA_REFRESH_TOKEN) - except ValueError: - return None - - -def _write_refresh_token_to_file(hass, filename, refresh_token): - """Attempt to store session data to file.""" - path = hass.config.path(filename) - - os.makedirs(os.path.dirname(path), exist_ok=True) - with open(path, "w+") as data_file: - json.dump({DATA_REFRESH_TOKEN: refresh_token}, data_file) - - -@asyncio.coroutine -def async_setup_scanner(hass, config, async_see, discovery_info=None): - """Validate the configuration and return an Automatic scanner.""" - - hass.http.register_view(AutomaticAuthCallbackView()) - - scope = FULL_SCOPE if config.get(CONF_CURRENT_LOCATION) else DEFAULT_SCOPE - - client = aioautomatic.Client( - client_id=config[CONF_CLIENT_ID], - client_secret=config[CONF_SECRET], - client_session=async_get_clientsession(hass), - request_kwargs={"timeout": DEFAULT_TIMEOUT}, - ) - - filename = f".automatic/session-{config[CONF_CLIENT_ID]}.json" - refresh_token = yield from hass.async_add_job( - _get_refresh_token_from_file, hass, filename - ) - - @asyncio.coroutine - def initialize_data(session): - """Initialize the AutomaticData object from the created session.""" - hass.async_add_job( - _write_refresh_token_to_file, hass, filename, session.refresh_token - ) - data = AutomaticData(hass, client, session, config.get(CONF_DEVICES), async_see) - - # Load the initial vehicle data - vehicles = yield from session.get_vehicles() - for vehicle in vehicles: - hass.async_create_task(data.load_vehicle(vehicle)) - - # Create a task instead of adding a tracking job, since this task will - # run until the websocket connection is closed. - hass.loop.create_task(data.ws_connect()) - - if refresh_token is not None: - try: - session = yield from client.create_session_from_refresh_token(refresh_token) - yield from initialize_data(session) - return True - except aioautomatic.exceptions.AutomaticError as err: - _LOGGER.error(str(err)) - - configurator = hass.components.configurator - request_id = configurator.async_request_config( - "Automatic", - description=("Authorization required for Automatic device tracker."), - link_name="Click here to authorize Home Assistant.", - link_url=client.generate_oauth_url(scope), - entity_picture="/static/images/logo_automatic.png", - ) - - @asyncio.coroutine - def initialize_callback(code, state): - """Call after OAuth2 response is returned.""" - try: - session = yield from client.create_session_from_oauth_code(code, state) - yield from initialize_data(session) - configurator.async_request_done(request_id) - except aioautomatic.exceptions.AutomaticError as err: - _LOGGER.error(str(err)) - configurator.async_notify_errors(request_id, str(err)) - return False - - if DATA_CONFIGURING not in hass.data: - hass.data[DATA_CONFIGURING] = {} - - hass.data[DATA_CONFIGURING][client.state] = initialize_callback - return True - - -class AutomaticAuthCallbackView(HomeAssistantView): - """Handle OAuth finish callback requests.""" - - requires_auth = False - url = "/api/automatic/callback" - name = "api:automatic:callback" - - @callback - def get(self, request): # pylint: disable=no-self-use - """Finish OAuth callback request.""" - hass = request.app["hass"] - params = request.query - response = web.HTTPFound("/lovelace") - - if "state" not in params or "code" not in params: - if "error" in params: - _LOGGER.error("Error authorizing Automatic: %s", params["error"]) - return response - _LOGGER.error("Error authorizing Automatic. Invalid response returned") - return response - - if ( - DATA_CONFIGURING not in hass.data - or params["state"] not in hass.data[DATA_CONFIGURING] - ): - _LOGGER.error("Automatic configuration request not found") - return response - - code = params["code"] - state = params["state"] - initialize_callback = hass.data[DATA_CONFIGURING][state] - hass.async_create_task(initialize_callback(code, state)) - - return response - - -class AutomaticData: - """A class representing an Automatic cloud service connection.""" - - def __init__(self, hass, client, session, devices, async_see): - """Initialize the automatic device scanner.""" - self.hass = hass - self.devices = devices - self.vehicle_info = {} - self.vehicle_seen = {} - self.client = client - self.session = session - self.async_see = async_see - self.ws_reconnect_handle = None - self.ws_close_requested = False - - self.client.on_app_event( - lambda name, event: self.hass.async_create_task( - self.handle_event(name, event) - ) - ) - - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self.ws_close()) - - @asyncio.coroutine - def handle_event(self, name, event): - """Coroutine to update state for a real time event.""" - - self.hass.bus.async_fire(EVENT_AUTOMATIC_UPDATE, event.data) - - if event.vehicle.id not in self.vehicle_info: - # If vehicle hasn't been seen yet, request the detailed - # info for this vehicle. - _LOGGER.info("New vehicle found") - try: - vehicle = yield from event.get_vehicle() - except aioautomatic.exceptions.AutomaticError as err: - _LOGGER.error(str(err)) - return - yield from self.get_vehicle_info(vehicle) - - if event.created_at < self.vehicle_seen[event.vehicle.id]: - # Skip events received out of order - _LOGGER.debug( - "Skipping out of order event. Event Created %s. Last seen event: %s", - event.created_at, - self.vehicle_seen[event.vehicle.id], - ) - return - self.vehicle_seen[event.vehicle.id] = event.created_at - - kwargs = self.vehicle_info[event.vehicle.id] - if kwargs is None: - # Ignored device - return - - # If this is a vehicle status report, update the fuel level - if name == "vehicle:status_report": - fuel_level = event.vehicle.fuel_level_percent - if fuel_level is not None: - kwargs[ATTR_ATTRIBUTES][ATTR_FUEL_LEVEL] = fuel_level - - # Send the device seen notification - if event.location is not None: - kwargs[ATTR_GPS] = (event.location.lat, event.location.lon) - kwargs[ATTR_GPS_ACCURACY] = event.location.accuracy_m - - yield from self.async_see(**kwargs) - - @asyncio.coroutine - def ws_connect(self, now=None): - """Open the websocket connection.""" - - self.ws_close_requested = False - - if self.ws_reconnect_handle is not None: - _LOGGER.debug("Retrying websocket connection") - try: - ws_loop_future = yield from self.client.ws_connect() - except aioautomatic.exceptions.UnauthorizedClientError: - _LOGGER.error( - "Client unauthorized for websocket connection. " - "Ensure Websocket is selected in the Automatic " - "developer application event delivery preferences" - ) - return - except aioautomatic.exceptions.AutomaticError as err: - if self.ws_reconnect_handle is None: - # Show log error and retry connection every 5 minutes - _LOGGER.error("Error opening websocket connection: %s", err) - self.ws_reconnect_handle = async_track_time_interval( - self.hass, self.ws_connect, timedelta(minutes=5) - ) - return - - if self.ws_reconnect_handle is not None: - self.ws_reconnect_handle() - self.ws_reconnect_handle = None - - _LOGGER.info("Websocket connected") - - try: - yield from ws_loop_future - except aioautomatic.exceptions.AutomaticError as err: - _LOGGER.error(str(err)) - - _LOGGER.info("Websocket closed") - - # If websocket was close was not requested, attempt to reconnect - if not self.ws_close_requested: - self.hass.loop.create_task(self.ws_connect()) - - @asyncio.coroutine - def ws_close(self): - """Close the websocket connection.""" - self.ws_close_requested = True - if self.ws_reconnect_handle is not None: - self.ws_reconnect_handle() - self.ws_reconnect_handle = None - - yield from self.client.ws_close() - - @asyncio.coroutine - def load_vehicle(self, vehicle): - """Load the vehicle's initial state and update hass.""" - kwargs = yield from self.get_vehicle_info(vehicle) - yield from self.async_see(**kwargs) - - @asyncio.coroutine - def get_vehicle_info(self, vehicle): - """Fetch the latest vehicle info from automatic.""" - - name = vehicle.display_name - if name is None: - name = " ".join( - filter(None, (str(vehicle.year), vehicle.make, vehicle.model)) - ) - - if self.devices is not None and name not in self.devices: - self.vehicle_info[vehicle.id] = None - return - - self.vehicle_info[vehicle.id] = kwargs = { - ATTR_DEV_ID: vehicle.id, - ATTR_HOST_NAME: name, - ATTR_MAC: vehicle.id, - ATTR_ATTRIBUTES: {ATTR_FUEL_LEVEL: vehicle.fuel_level_percent}, - } - self.vehicle_seen[vehicle.id] = vehicle.updated_at or vehicle.created_at - - if vehicle.latest_location is not None: - location = vehicle.latest_location - kwargs[ATTR_GPS] = (location.lat, location.lon) - kwargs[ATTR_GPS_ACCURACY] = location.accuracy_m - return kwargs - - trips = [] - try: - # Get the most recent trip for this vehicle - trips = yield from self.session.get_trips(vehicle=vehicle.id, limit=1) - except aioautomatic.exceptions.AutomaticError as err: - _LOGGER.error(str(err)) - - if trips: - location = trips[0].end_location - kwargs[ATTR_GPS] = (location.lat, location.lon) - kwargs[ATTR_GPS_ACCURACY] = location.accuracy_m - - if trips[0].ended_at >= self.vehicle_seen[vehicle.id]: - self.vehicle_seen[vehicle.id] = trips[0].ended_at - - return kwargs diff --git a/homeassistant/components/automatic/manifest.json b/homeassistant/components/automatic/manifest.json deleted file mode 100644 index e0d06ff0f1f..00000000000 --- a/homeassistant/components/automatic/manifest.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "domain": "automatic", - "name": "Automatic", - "documentation": "https://www.home-assistant.io/integrations/automatic", - "requirements": ["aioautomatic==0.6.5"], - "dependencies": ["configurator", "http"], - "codeowners": ["@armills"] -} diff --git a/requirements_all.txt b/requirements_all.txt index 31acf734e6c..a4b01b38b3a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -155,9 +155,6 @@ aioambient==1.1.1 # homeassistant.components.asuswrt aioasuswrt==1.2.5 -# homeassistant.components.automatic -aioautomatic==0.6.5 - # homeassistant.components.aws aiobotocore==0.11.1 From 55b444cccf60909496488d0634ee0aafbd7a524a Mon Sep 17 00:00:00 2001 From: zacpotts <59249562+zacpotts@users.noreply.github.com> Date: Thu, 14 May 2020 11:03:53 +0100 Subject: [PATCH 010/406] Fix zwave thermostat specific device type (#35609) --- homeassistant/components/zwave/discovery_schemas.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/zwave/discovery_schemas.py b/homeassistant/components/zwave/discovery_schemas.py index 5e4b83d81e1..f8674a48a32 100644 --- a/homeassistant/components/zwave/discovery_schemas.py +++ b/homeassistant/components/zwave/discovery_schemas.py @@ -56,6 +56,7 @@ DISCOVERY_SCHEMAS = [ const.DISC_SPECIFIC_DEVICE_CLASS: [ const.SPECIFIC_TYPE_THERMOSTAT_HEATING, const.SPECIFIC_TYPE_SETPOINT_THERMOSTAT, + const.SPECIFIC_TYPE_NOT_USED, ], const.DISC_VALUES: dict( DEFAULT_VALUES_SCHEMA, From c67d035366fb5acc42468fd1aac975628ab6c025 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 14 May 2020 12:19:59 +0200 Subject: [PATCH 011/406] Rename zigbee to xbee (#35613) --- .coveragerc | 2 +- .../components/{zigbee => xbee}/__init__.py | 62 +++++++++---------- .../{zigbee => xbee}/binary_sensor.py | 12 ++-- .../components/{zigbee => xbee}/light.py | 10 +-- homeassistant/components/xbee/manifest.json | 7 +++ .../components/{zigbee => xbee}/sensor.py | 20 +++--- .../components/{zigbee => xbee}/switch.py | 12 ++-- homeassistant/components/zigbee/manifest.json | 7 --- requirements_all.txt | 2 +- 9 files changed, 62 insertions(+), 72 deletions(-) rename homeassistant/components/{zigbee => xbee}/__init__.py (88%) rename homeassistant/components/{zigbee => xbee}/binary_sensor.py (54%) rename homeassistant/components/{zigbee => xbee}/light.py (61%) create mode 100644 homeassistant/components/xbee/manifest.json rename homeassistant/components/{zigbee => xbee}/sensor.py (84%) rename homeassistant/components/{zigbee => xbee}/switch.py (50%) delete mode 100644 homeassistant/components/zigbee/manifest.json diff --git a/.coveragerc b/.coveragerc index ffbcda1b56a..282db156e65 100644 --- a/.coveragerc +++ b/.coveragerc @@ -914,7 +914,7 @@ omit = homeassistant/components/zha/light.py homeassistant/components/zha/sensor.py homeassistant/components/zhong_hong/climate.py - homeassistant/components/zigbee/* + homeassistant/components/xbee/* homeassistant/components/ziggo_mediabox_xl/media_player.py homeassistant/components/zoneminder/* homeassistant/components/supla/* diff --git a/homeassistant/components/zigbee/__init__.py b/homeassistant/components/xbee/__init__.py similarity index 88% rename from homeassistant/components/zigbee/__init__.py rename to homeassistant/components/xbee/__init__.py index 2fa8291538e..31e2d6dc495 100644 --- a/homeassistant/components/zigbee/__init__.py +++ b/homeassistant/components/xbee/__init__.py @@ -1,4 +1,4 @@ -"""Support for Zigbee devices.""" +"""Support for XBee Zigbee devices.""" from binascii import hexlify, unhexlify import logging @@ -23,9 +23,9 @@ from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) -DOMAIN = "zigbee" +DOMAIN = "xbee" -SIGNAL_ZIGBEE_FRAME_RECEIVED = "zigbee_frame_received" +SIGNAL_XBEE_FRAME_RECEIVED = "xbee_frame_received" CONF_BAUD = "baud" @@ -58,28 +58,28 @@ PLATFORM_SCHEMA = vol.Schema( def setup(hass, config): - """Set up the connection to the Zigbee device.""" + """Set up the connection to the XBee Zigbee device.""" usb_device = config[DOMAIN].get(CONF_DEVICE, DEFAULT_DEVICE) baud = int(config[DOMAIN].get(CONF_BAUD, DEFAULT_BAUD)) try: ser = Serial(usb_device, baud) except SerialException as exc: - _LOGGER.exception("Unable to open serial port for Zigbee: %s", exc) + _LOGGER.exception("Unable to open serial port for XBee: %s", exc) return False zigbee_device = ZigBee(ser) def close_serial_port(*args): - """Close the serial port we're using to communicate with the Zigbee.""" + """Close the serial port we're using to communicate with the XBee.""" zigbee_device.zb.serial.close() def _frame_received(frame): - """Run when a Zigbee frame is received. + """Run when a XBee Zigbee frame is received. Pickles the frame, then encodes it into base64 since it contains non JSON serializable binary. """ - dispatcher_send(hass, SIGNAL_ZIGBEE_FRAME_RECEIVED, frame) + dispatcher_send(hass, SIGNAL_XBEE_FRAME_RECEIVED, frame) hass.data[DOMAIN] = zigbee_device hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, close_serial_port) @@ -92,12 +92,10 @@ def frame_is_relevant(entity, frame): """Test whether the frame is relevant to the entity.""" if frame.get("source_addr_long") != entity.config.address: return False - if "samples" not in frame: - return False - return True + return "samples" in frame -class ZigBeeConfig: +class XBeeConfig: """Handle the fetching of configuration from the config file.""" def __init__(self, config): @@ -115,7 +113,7 @@ class ZigBeeConfig: """Return the address of the device. If an address has been provided, unhexlify it, otherwise return None - as we're talking to our local Zigbee device. + as we're talking to our local XBee device. """ address = self._config.get("address") if address is not None: @@ -128,7 +126,7 @@ class ZigBeeConfig: return self._should_poll -class ZigBeePinConfig(ZigBeeConfig): +class XBeePinConfig(XBeeConfig): """Handle the fetching of configuration from the configuration file.""" @property @@ -137,11 +135,11 @@ class ZigBeePinConfig(ZigBeeConfig): return self._config["pin"] -class ZigBeeDigitalInConfig(ZigBeePinConfig): - """A subclass of ZigBeePinConfig.""" +class XBeeDigitalInConfig(XBeePinConfig): + """A subclass of XBeePinConfig.""" def __init__(self, config): - """Initialise the Zigbee Digital input config.""" + """Initialise the XBee Zigbee Digital input config.""" super().__init__(config) self._bool2state, self._state2bool = self.boolean_maps @@ -177,15 +175,15 @@ class ZigBeeDigitalInConfig(ZigBeePinConfig): return self._state2bool -class ZigBeeDigitalOutConfig(ZigBeePinConfig): - """A subclass of ZigBeePinConfig. +class XBeeDigitalOutConfig(XBeePinConfig): + """A subclass of XBeePinConfig. Set _should_poll to default as False instead of True. The value will still be overridden by the presence of a 'poll' config entry. """ def __init__(self, config): - """Initialize the Zigbee Digital out.""" + """Initialize the XBee Zigbee Digital out.""" super().__init__(config) self._bool2state, self._state2bool = self.boolean_maps self._should_poll = config.get("poll", False) @@ -227,8 +225,8 @@ class ZigBeeDigitalOutConfig(ZigBeePinConfig): return self._state2bool -class ZigBeeAnalogInConfig(ZigBeePinConfig): - """Representation of a Zigbee GPIO pin set to analog in.""" +class XBeeAnalogInConfig(XBeePinConfig): + """Representation of a XBee Zigbee GPIO pin set to analog in.""" @property def max_voltage(self): @@ -236,7 +234,7 @@ class ZigBeeAnalogInConfig(ZigBeePinConfig): return float(self._config.get("max_volts", DEFAULT_ADC_MAX_VOLTS)) -class ZigBeeDigitalIn(Entity): +class XBeeDigitalIn(Entity): """Representation of a GPIO pin configured as a digital input.""" def __init__(self, config, device): @@ -268,7 +266,7 @@ class ZigBeeDigitalIn(Entity): ] self.schedule_update_ha_state() - async_dispatcher_connect(self.hass, SIGNAL_ZIGBEE_FRAME_RECEIVED, handle_frame) + async_dispatcher_connect(self.hass, SIGNAL_XBEE_FRAME_RECEIVED, handle_frame) @property def name(self): @@ -316,11 +314,11 @@ class ZigBeeDigitalIn(Entity): self._state = self._config.state2bool[sample[pin_name]] -class ZigBeeDigitalOut(ZigBeeDigitalIn): +class XBeeDigitalOut(XBeeDigitalIn): """Representation of a GPIO pin configured as a digital input.""" def _set_state(self, state): - """Initialize the Zigbee digital out device.""" + """Initialize the XBee Zigbee digital out device.""" try: self._device.set_gpio_pin( self._config.pin, self._config.bool2state[state], self._config.address @@ -333,7 +331,7 @@ class ZigBeeDigitalOut(ZigBeeDigitalIn): ) return except ZigBeeException as exc: - _LOGGER.exception("Unable to set digital pin on Zigbee device: %s", exc) + _LOGGER.exception("Unable to set digital pin on XBee device: %s", exc) return self._state = state if not self.should_poll: @@ -348,7 +346,7 @@ class ZigBeeDigitalOut(ZigBeeDigitalIn): self._set_state(False) def update(self): - """Ask the Zigbee device what its output is set to.""" + """Ask the XBee device what its output is set to.""" try: pin_state = self._device.get_gpio_pin( self._config.pin, self._config.address @@ -362,17 +360,17 @@ class ZigBeeDigitalOut(ZigBeeDigitalIn): return except ZigBeeException as exc: _LOGGER.exception( - "Unable to get output pin status from Zigbee device: %s", exc + "Unable to get output pin status from XBee device: %s", exc ) return self._state = self._config.state2bool[pin_state] -class ZigBeeAnalogIn(Entity): +class XBeeAnalogIn(Entity): """Representation of a GPIO pin configured as an analog input.""" def __init__(self, config, device): - """Initialize the ZigBee analog in device.""" + """Initialize the XBee analog in device.""" self._config = config self._device = device self._value = None @@ -398,7 +396,7 @@ class ZigBeeAnalogIn(Entity): ) self.schedule_update_ha_state() - async_dispatcher_connect(self.hass, SIGNAL_ZIGBEE_FRAME_RECEIVED, handle_frame) + async_dispatcher_connect(self.hass, SIGNAL_XBEE_FRAME_RECEIVED, handle_frame) @property def name(self): diff --git a/homeassistant/components/zigbee/binary_sensor.py b/homeassistant/components/xbee/binary_sensor.py similarity index 54% rename from homeassistant/components/zigbee/binary_sensor.py rename to homeassistant/components/xbee/binary_sensor.py index fe35b54a88f..47c7515ddc7 100644 --- a/homeassistant/components/zigbee/binary_sensor.py +++ b/homeassistant/components/xbee/binary_sensor.py @@ -3,7 +3,7 @@ import voluptuous as vol from homeassistant.components.binary_sensor import BinarySensorEntity -from . import DOMAIN, PLATFORM_SCHEMA, ZigBeeDigitalIn, ZigBeeDigitalInConfig +from . import DOMAIN, PLATFORM_SCHEMA, XBeeDigitalIn, XBeeDigitalInConfig CONF_ON_STATE = "on_state" @@ -14,12 +14,10 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({vol.Optional(CONF_ON_STATE): vol.In(ST def setup_platform(hass, config, add_entities, discovery_info=None): - """Set up the Zigbee binary sensor platform.""" + """Set up the XBee Zigbee binary sensor platform.""" zigbee_device = hass.data[DOMAIN] - add_entities( - [ZigBeeBinarySensor(ZigBeeDigitalInConfig(config), zigbee_device)], True - ) + add_entities([XBeeBinarySensor(XBeeDigitalInConfig(config), zigbee_device)], True) -class ZigBeeBinarySensor(ZigBeeDigitalIn, BinarySensorEntity): - """Use ZigBeeDigitalIn as binary sensor.""" +class XBeeBinarySensor(XBeeDigitalIn, BinarySensorEntity): + """Use XBeeDigitalIn as binary sensor.""" diff --git a/homeassistant/components/zigbee/light.py b/homeassistant/components/xbee/light.py similarity index 61% rename from homeassistant/components/zigbee/light.py rename to homeassistant/components/xbee/light.py index 10bb87aa426..76ed8120166 100644 --- a/homeassistant/components/zigbee/light.py +++ b/homeassistant/components/xbee/light.py @@ -1,9 +1,9 @@ -"""Support for Zigbee lights.""" +"""Support for XBee Zigbee lights.""" import voluptuous as vol from homeassistant.components.light import LightEntity -from . import DOMAIN, PLATFORM_SCHEMA, ZigBeeDigitalOut, ZigBeeDigitalOutConfig +from . import DOMAIN, PLATFORM_SCHEMA, XBeeDigitalOut, XBeeDigitalOutConfig CONF_ON_STATE = "on_state" @@ -18,8 +18,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( def setup_platform(hass, config, add_entities, discovery_info=None): """Create and add an entity based on the configuration.""" zigbee_device = hass.data[DOMAIN] - add_entities([ZigBeeLight(ZigBeeDigitalOutConfig(config), zigbee_device)]) + add_entities([XBeeLight(XBeeDigitalOutConfig(config), zigbee_device)]) -class ZigBeeLight(ZigBeeDigitalOut, LightEntity): - """Use ZigBeeDigitalOut as light.""" +class XBeeLight(XBeeDigitalOut, LightEntity): + """Use XBeeDigitalOut as light.""" diff --git a/homeassistant/components/xbee/manifest.json b/homeassistant/components/xbee/manifest.json new file mode 100644 index 00000000000..9d70751e230 --- /dev/null +++ b/homeassistant/components/xbee/manifest.json @@ -0,0 +1,7 @@ +{ + "domain": "xbee", + "name": "XBee", + "documentation": "https://www.home-assistant.io/integrations/xbee", + "requirements": ["xbee-helper==0.0.7"], + "codeowners": [] +} diff --git a/homeassistant/components/zigbee/sensor.py b/homeassistant/components/xbee/sensor.py similarity index 84% rename from homeassistant/components/zigbee/sensor.py rename to homeassistant/components/xbee/sensor.py index 0c709a6d1a5..4a392691032 100644 --- a/homeassistant/components/zigbee/sensor.py +++ b/homeassistant/components/xbee/sensor.py @@ -1,4 +1,4 @@ -"""Support for Zigbee sensors.""" +"""Support for XBee Zigbee sensors.""" from binascii import hexlify import logging @@ -8,13 +8,7 @@ from xbee_helper.exceptions import ZigBeeException, ZigBeeTxFailure from homeassistant.const import TEMP_CELSIUS from homeassistant.helpers.entity import Entity -from . import ( - DOMAIN, - PLATFORM_SCHEMA, - ZigBeeAnalogIn, - ZigBeeAnalogInConfig, - ZigBeeConfig, -) +from . import DOMAIN, PLATFORM_SCHEMA, XBeeAnalogIn, XBeeAnalogInConfig, XBeeConfig _LOGGER = logging.getLogger(__name__) @@ -33,7 +27,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( def setup_platform(hass, config, add_entities, discovery_info=None): - """Set up the Zigbee platform. + """Set up the XBee Zigbee platform. Uses the 'type' config value to work out which type of Zigbee sensor we're dealing with and instantiates the relevant classes to handle it. @@ -44,13 +38,13 @@ def setup_platform(hass, config, add_entities, discovery_info=None): try: sensor_class, config_class = TYPE_CLASSES[typ] except KeyError: - _LOGGER.exception("Unknown Zigbee sensor type: %s", typ) + _LOGGER.exception("Unknown XBee Zigbee sensor type: %s", typ) return add_entities([sensor_class(config_class(config), zigbee_device)], True) -class ZigBeeTemperatureSensor(Entity): +class XBeeTemperatureSensor(Entity): """Representation of XBee Pro temperature sensor.""" def __init__(self, config, device): @@ -90,6 +84,6 @@ class ZigBeeTemperatureSensor(Entity): # This must be below the classes to which it refers. TYPE_CLASSES = { - "temperature": (ZigBeeTemperatureSensor, ZigBeeConfig), - "analog": (ZigBeeAnalogIn, ZigBeeAnalogInConfig), + "temperature": (XBeeTemperatureSensor, XBeeConfig), + "analog": (XBeeAnalogIn, XBeeAnalogInConfig), } diff --git a/homeassistant/components/zigbee/switch.py b/homeassistant/components/xbee/switch.py similarity index 50% rename from homeassistant/components/zigbee/switch.py rename to homeassistant/components/xbee/switch.py index f5b73f5d328..cdb0d2677c5 100644 --- a/homeassistant/components/zigbee/switch.py +++ b/homeassistant/components/xbee/switch.py @@ -1,9 +1,9 @@ -"""Support for Zigbee switches.""" +"""Support for XBee Zigbee switches.""" import voluptuous as vol from homeassistant.components.switch import SwitchEntity -from . import DOMAIN, PLATFORM_SCHEMA, ZigBeeDigitalOut, ZigBeeDigitalOutConfig +from . import DOMAIN, PLATFORM_SCHEMA, XBeeDigitalOut, XBeeDigitalOutConfig CONF_ON_STATE = "on_state" @@ -15,10 +15,10 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({vol.Optional(CONF_ON_STATE): vol.In(ST def setup_platform(hass, config, add_entities, discovery_info=None): - """Set up the Zigbee switch platform.""" + """Set up the XBee Zigbee switch platform.""" zigbee_device = hass.data[DOMAIN] - add_entities([ZigBeeSwitch(ZigBeeDigitalOutConfig(config), zigbee_device)]) + add_entities([XBeeSwitch(XBeeDigitalOutConfig(config), zigbee_device)]) -class ZigBeeSwitch(ZigBeeDigitalOut, SwitchEntity): - """Representation of a Zigbee Digital Out device.""" +class XBeeSwitch(XBeeDigitalOut, SwitchEntity): + """Representation of a XBee Zigbee Digital Out device.""" diff --git a/homeassistant/components/zigbee/manifest.json b/homeassistant/components/zigbee/manifest.json deleted file mode 100644 index 6940aaef7dc..00000000000 --- a/homeassistant/components/zigbee/manifest.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "domain": "zigbee", - "name": "Zigbee", - "documentation": "https://www.home-assistant.io/integrations/zigbee", - "requirements": ["xbee-helper==0.0.7"], - "codeowners": [] -} diff --git a/requirements_all.txt b/requirements_all.txt index a4b01b38b3a..7c6b6d8b7a8 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2194,7 +2194,7 @@ wled==0.3.0 # homeassistant.components.wunderlist wunderpy2==0.1.6 -# homeassistant.components.zigbee +# homeassistant.components.xbee xbee-helper==0.0.7 # homeassistant.components.xbox_live From 256370afa83ea9a9c69a5c4fbf01680e210be019 Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Thu, 14 May 2020 15:59:40 +0200 Subject: [PATCH 012/406] Xiaomi Miio zeroconf discovery (#35352) --- .../components/xiaomi_miio/__init__.py | 6 +- .../xiaomi_miio/alarm_control_panel.py | 4 +- .../components/xiaomi_miio/config_flow.py | 63 ++++++++++--- .../components/xiaomi_miio/manifest.json | 3 +- .../components/xiaomi_miio/strings.json | 3 +- .../xiaomi_miio/translations/en.json | 5 +- homeassistant/generated/zeroconf.py | 3 + .../xiaomi_miio/test_config_flow.py | 88 ++++++++++++++++++- 8 files changed, 151 insertions(+), 24 deletions(-) diff --git a/homeassistant/components/xiaomi_miio/__init__.py b/homeassistant/components/xiaomi_miio/__init__.py index 0dd03e42e7d..9524406a1f9 100644 --- a/homeassistant/components/xiaomi_miio/__init__.py +++ b/homeassistant/components/xiaomi_miio/__init__.py @@ -38,7 +38,11 @@ async def async_setup_gateway_entry( host = entry.data[CONF_HOST] token = entry.data[CONF_TOKEN] name = entry.title - gateway_id = entry.data["gateway_id"] + gateway_id = entry.unique_id + + # For backwards compat + if entry.unique_id.endswith("-gateway"): + hass.config_entries.async_update_entry(entry, unique_id=entry.data["mac"]) # Connect to gateway gateway = ConnectXiaomiGateway(hass) diff --git a/homeassistant/components/xiaomi_miio/alarm_control_panel.py b/homeassistant/components/xiaomi_miio/alarm_control_panel.py index dccd94dc963..7df9dc54e5a 100644 --- a/homeassistant/components/xiaomi_miio/alarm_control_panel.py +++ b/homeassistant/components/xiaomi_miio/alarm_control_panel.py @@ -33,10 +33,10 @@ async def async_setup_entry(hass, config_entry, async_add_entities): f"{config_entry.title} Alarm", config_entry.data["model"], config_entry.data["mac"], - config_entry.data["gateway_id"], + config_entry.unique_id, ) entities.append(entity) - async_add_entities(entities) + async_add_entities(entities, update_before_add=True) class XiaomiGatewayAlarm(AlarmControlPanelEntity): diff --git a/homeassistant/components/xiaomi_miio/config_flow.py b/homeassistant/components/xiaomi_miio/config_flow.py index 092f5d85d30..e35aa0c8b10 100644 --- a/homeassistant/components/xiaomi_miio/config_flow.py +++ b/homeassistant/components/xiaomi_miio/config_flow.py @@ -5,6 +5,7 @@ import voluptuous as vol from homeassistant import config_entries from homeassistant.const import CONF_HOST, CONF_NAME, CONF_TOKEN +from homeassistant.helpers.device_registry import format_mac # pylint: disable=unused-import from .const import DOMAIN @@ -15,14 +16,13 @@ _LOGGER = logging.getLogger(__name__) CONF_FLOW_TYPE = "config_flow_device" CONF_GATEWAY = "gateway" DEFAULT_GATEWAY_NAME = "Xiaomi Gateway" +ZEROCONF_GATEWAY = "lumi-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, - } -) +GATEWAY_SETTINGS = { + vol.Required(CONF_TOKEN): vol.All(str, vol.Length(min=32, max=32)), + vol.Optional(CONF_NAME, default=DEFAULT_GATEWAY_NAME): str, +} +GATEWAY_CONFIG = vol.Schema({vol.Required(CONF_HOST): str}).extend(GATEWAY_SETTINGS) CONFIG_SCHEMA = vol.Schema({vol.Optional(CONF_GATEWAY, default=False): bool}) @@ -33,6 +33,10 @@ class XiaomiMiioFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL + def __init__(self): + """Initialize.""" + self.host = None + async def async_step_user(self, user_input=None): """Handle a flow initialized by the user.""" errors = {} @@ -47,36 +51,67 @@ class XiaomiMiioFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): step_id="user", data_schema=CONFIG_SCHEMA, errors=errors ) + async def async_step_zeroconf(self, discovery_info): + """Handle zeroconf discovery.""" + name = discovery_info.get("name") + self.host = discovery_info.get("host") + mac_address = discovery_info.get("properties", {}).get("mac") + + if not name or not self.host or not mac_address: + return self.async_abort(reason="not_xiaomi_miio") + + # Check which device is discovered. + if name.startswith(ZEROCONF_GATEWAY): + unique_id = format_mac(mac_address) + await self.async_set_unique_id(unique_id) + self._abort_if_unique_id_configured({CONF_HOST: self.host}) + + return await self.async_step_gateway() + + # Discovered device is not yet supported + _LOGGER.debug( + "Not yet supported Xiaomi Miio device '%s' discovered with host %s", + name, + self.host, + ) + return self.async_abort(reason="not_xiaomi_miio") + 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] + if user_input.get(CONF_HOST): + self.host = user_input[CONF_HOST] # Try to connect to a Xiaomi Gateway. connect_gateway_class = ConnectXiaomiGateway(self.hass) - await connect_gateway_class.async_connect_gateway(host, token) + await connect_gateway_class.async_connect_gateway(self.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" + mac = format_mac(gateway_info.mac_address) + unique_id = mac 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_HOST: self.host, CONF_TOKEN: token, - "gateway_id": unique_id, "model": gateway_info.model, - "mac": gateway_info.mac_address, + "mac": mac, }, ) errors["base"] = "connect_error" + if self.host: + schema = vol.Schema(GATEWAY_SETTINGS) + else: + schema = GATEWAY_CONFIG + return self.async_show_form( - step_id="gateway", data_schema=GATEWAY_CONFIG, errors=errors + step_id="gateway", data_schema=schema, errors=errors ) diff --git a/homeassistant/components/xiaomi_miio/manifest.json b/homeassistant/components/xiaomi_miio/manifest.json index 468389b4626..e1ead8d966c 100644 --- a/homeassistant/components/xiaomi_miio/manifest.json +++ b/homeassistant/components/xiaomi_miio/manifest.json @@ -4,5 +4,6 @@ "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"] + "codeowners": ["@rytilahti", "@syssi"], + "zeroconf": ["_miio._udp.local."] } diff --git a/homeassistant/components/xiaomi_miio/strings.json b/homeassistant/components/xiaomi_miio/strings.json index 150cebe084a..0024ffdfe9b 100644 --- a/homeassistant/components/xiaomi_miio/strings.json +++ b/homeassistant/components/xiaomi_miio/strings.json @@ -23,7 +23,8 @@ "no_device_selected": "No device selected, please select one device." }, "abort": { - "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", + "already_in_progress": "Config flow for this Xiaomi Miio device is already in progress." } } } diff --git a/homeassistant/components/xiaomi_miio/translations/en.json b/homeassistant/components/xiaomi_miio/translations/en.json index f67df5b7826..3f081c682d6 100644 --- a/homeassistant/components/xiaomi_miio/translations/en.json +++ b/homeassistant/components/xiaomi_miio/translations/en.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Device is already configured" + "already_configured": "Device is already configured", + "already_in_progress": "Config flow for this Xiaomi Miio device is already in progress." }, "error": { "connect_error": "Failed to connect", @@ -26,4 +27,4 @@ } } } -} \ No newline at end of file +} diff --git a/homeassistant/generated/zeroconf.py b/homeassistant/generated/zeroconf.py index 880bfedf400..02f316c33bc 100644 --- a/homeassistant/generated/zeroconf.py +++ b/homeassistant/generated/zeroconf.py @@ -31,6 +31,9 @@ ZEROCONF = { "_ipps._tcp.local.": [ "ipp" ], + "_miio._udp.local.": [ + "xiaomi_miio" + ], "_printer._tcp.local.": [ "brother" ], diff --git a/tests/components/xiaomi_miio/test_config_flow.py b/tests/components/xiaomi_miio/test_config_flow.py index 619293be676..8ae2f424f2e 100644 --- a/tests/components/xiaomi_miio/test_config_flow.py +++ b/tests/components/xiaomi_miio/test_config_flow.py @@ -2,19 +2,25 @@ from miio import DeviceException from homeassistant import config_entries +from homeassistant.components import zeroconf from homeassistant.components.xiaomi_miio import config_flow, const from homeassistant.const import CONF_HOST, CONF_NAME, CONF_TOKEN from tests.async_mock import Mock, patch +ZEROCONF_NAME = "name" +ZEROCONF_PROP = "properties" +ZEROCONF_MAC = "mac" + 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_MAC = "ab:cd:ef:gh:ij:kl" +TEST_GATEWAY_ID = TEST_MAC TEST_HARDWARE_VERSION = "AB123" TEST_FIRMWARE_VERSION = "1.2.3_456" +TEST_ZEROCONF_NAME = "lumi-gateway-v3_miio12345678._miio._udp.local." def get_mock_info( @@ -119,7 +125,83 @@ async def test_config_flow_gateway_success(hass): 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, } + + +async def test_zeroconf_gateway_success(hass): + """Test a successful zeroconf discovery of a gateway.""" + result = await hass.config_entries.flow.async_init( + const.DOMAIN, + context={"source": config_entries.SOURCE_ZEROCONF}, + data={ + zeroconf.ATTR_HOST: TEST_HOST, + ZEROCONF_NAME: TEST_ZEROCONF_NAME, + ZEROCONF_PROP: {ZEROCONF_MAC: TEST_MAC}, + }, + ) + + 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_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, + "model": TEST_MODEL, + "mac": TEST_MAC, + } + + +async def test_zeroconf_unknown_device(hass): + """Test a failed zeroconf discovery because of a unknown device.""" + result = await hass.config_entries.flow.async_init( + const.DOMAIN, + context={"source": config_entries.SOURCE_ZEROCONF}, + data={ + zeroconf.ATTR_HOST: TEST_HOST, + ZEROCONF_NAME: "not-a-xiaomi-miio-device", + ZEROCONF_PROP: {ZEROCONF_MAC: TEST_MAC}, + }, + ) + + assert result["type"] == "abort" + assert result["reason"] == "not_xiaomi_miio" + + +async def test_zeroconf_no_data(hass): + """Test a failed zeroconf discovery because of no data.""" + result = await hass.config_entries.flow.async_init( + const.DOMAIN, context={"source": config_entries.SOURCE_ZEROCONF}, data={} + ) + + assert result["type"] == "abort" + assert result["reason"] == "not_xiaomi_miio" + + +async def test_zeroconf_missing_data(hass): + """Test a failed zeroconf discovery because of missing data.""" + result = await hass.config_entries.flow.async_init( + const.DOMAIN, + context={"source": config_entries.SOURCE_ZEROCONF}, + data={zeroconf.ATTR_HOST: TEST_HOST, ZEROCONF_NAME: TEST_ZEROCONF_NAME}, + ) + + assert result["type"] == "abort" + assert result["reason"] == "not_xiaomi_miio" From d480cb7b2c12cd3c1145c51324e46e39fe22268a Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Thu, 14 May 2020 16:33:57 +0200 Subject: [PATCH 013/406] Fix zwave_mqtt creating the device name (#35603) * Fix for creating the device name Creating the devicename included a typo and was missing the custom (preferred) name set by the OZW Admin tool. * update comments --- homeassistant/components/zwave_mqtt/entity.py | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/zwave_mqtt/entity.py b/homeassistant/components/zwave_mqtt/entity.py index 97c48266a2d..d64beb0ba34 100644 --- a/homeassistant/components/zwave_mqtt/entity.py +++ b/homeassistant/components/zwave_mqtt/entity.py @@ -265,15 +265,21 @@ class ZWaveDeviceEntity(Entity): 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 + # Prefer custom name set by OZWAdmin if present + if node.node_name: + return node.node_name + # Prefer short devicename from metadata if present + if node.meta_data and node.meta_data.get("Name"): + return node.meta_data["Name"] + # Fallback to productname or devicetype strings + if node.node_product_name: + return node.node_product_name + if node.node_device_type_string: + return node.node_device_type_string + if node.node_specific_string: + return node.node_specific_string + # Last resort: use Node id (should never happen, but just in case) + return f"Node {node.id}" def create_device_id(node: OZWNode, node_instance: int = 1): From 7d02b1543771336691ae129d6b9fb1d801fc6342 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Thu, 14 May 2020 18:22:49 +0300 Subject: [PATCH 014/406] Upgrade huawei-lte-api to 1.4.12 (#35618) https://github.com/Salamek/huawei-lte-api/releases/tag/1.4.12 --- homeassistant/components/huawei_lte/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/huawei_lte/manifest.json b/homeassistant/components/huawei_lte/manifest.json index e1e91c08e9a..0660aa361f5 100644 --- a/homeassistant/components/huawei_lte/manifest.json +++ b/homeassistant/components/huawei_lte/manifest.json @@ -5,7 +5,7 @@ "documentation": "https://www.home-assistant.io/integrations/huawei_lte", "requirements": [ "getmac==0.8.2", - "huawei-lte-api==1.4.11", + "huawei-lte-api==1.4.12", "stringcase==1.2.0", "url-normalize==1.4.1" ], diff --git a/requirements_all.txt b/requirements_all.txt index 7c6b6d8b7a8..b3364269f1e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -747,7 +747,7 @@ horimote==0.4.1 httplib2==0.10.3 # homeassistant.components.huawei_lte -huawei-lte-api==1.4.11 +huawei-lte-api==1.4.12 # homeassistant.components.hydrawise hydrawiser==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ee29a113dc4..29eae6a9d84 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -328,7 +328,7 @@ homematicip==0.10.17 httplib2==0.10.3 # homeassistant.components.huawei_lte -huawei-lte-api==1.4.11 +huawei-lte-api==1.4.12 # homeassistant.components.iaqualink iaqualink==0.3.1 From 4b00dedff5145cc5333d586ee96bb11e0ada0809 Mon Sep 17 00:00:00 2001 From: Anders Melchiorsen Date: Thu, 14 May 2020 17:24:26 +0200 Subject: [PATCH 015/406] Upgrade to pysonos 0.0.29 (#35617) --- homeassistant/components/sonos/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/sonos/manifest.json b/homeassistant/components/sonos/manifest.json index 27631cc8045..d6bc7ff71a4 100644 --- a/homeassistant/components/sonos/manifest.json +++ b/homeassistant/components/sonos/manifest.json @@ -3,7 +3,7 @@ "name": "Sonos", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/sonos", - "requirements": ["pysonos==0.0.28"], + "requirements": ["pysonos==0.0.29"], "ssdp": [ { "st": "urn:schemas-upnp-org:device:ZonePlayer:1" diff --git a/requirements_all.txt b/requirements_all.txt index b3364269f1e..518e91194ff 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1612,7 +1612,7 @@ pysnmp==4.4.12 pysoma==0.0.10 # homeassistant.components.sonos -pysonos==0.0.28 +pysonos==0.0.29 # homeassistant.components.spc pyspcwebgw==0.4.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 29eae6a9d84..862398fb073 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -681,7 +681,7 @@ pysmartthings==0.7.1 pysoma==0.0.10 # homeassistant.components.sonos -pysonos==0.0.28 +pysonos==0.0.29 # homeassistant.components.spc pyspcwebgw==0.4.0 From b7ab07c98757cff4ed8b8228bbfc284ba5b38dc7 Mon Sep 17 00:00:00 2001 From: Jason Hunter Date: Thu, 14 May 2020 11:25:58 -0400 Subject: [PATCH 016/406] additional log info and strings fix (#35622) --- homeassistant/components/onvif/event.py | 9 ++++++--- homeassistant/components/onvif/strings.json | 3 ++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/onvif/event.py b/homeassistant/components/onvif/event.py index 2c56d46ad39..ee7d4349dc4 100644 --- a/homeassistant/components/onvif/event.py +++ b/homeassistant/components/onvif/event.py @@ -143,19 +143,22 @@ class EventManager: async def async_parse_messages(self, messages) -> None: """Parse notification message.""" for msg in messages: - # LOGGER.debug("ONVIF Event Message %s: %s", self.device.host, pformat(msg)) topic = msg.Topic._value_1 parser = PARSERS.get(topic) if not parser: if topic not in UNHANDLED_TOPICS: - LOGGER.info("No registered handler for event: %s", msg) + LOGGER.info( + "No registered handler for event from %s: %s", + self.unique_id, + msg, + ) UNHANDLED_TOPICS.add(topic) continue event = await parser(self.unique_id, msg) if not event: - LOGGER.warning("Unable to parse event: %s", msg) + LOGGER.warning("Unable to parse event from %s: %s", self.unique_id, msg) return self._events[event.uid] = event diff --git a/homeassistant/components/onvif/strings.json b/homeassistant/components/onvif/strings.json index b80e8b57fc7..b6ae9b98d9a 100644 --- a/homeassistant/components/onvif/strings.json +++ b/homeassistant/components/onvif/strings.json @@ -23,6 +23,7 @@ }, "manual_input": { "data": { + "name": "Name", "host": "[%key:common::config_flow::data::host%]", "port": "[%key:common::config_flow::data::port%]" }, @@ -55,4 +56,4 @@ } } } -} \ No newline at end of file +} From a42a654590c91af371d2319f0a04b528e92d6d72 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 14 May 2020 11:58:40 -0500 Subject: [PATCH 017/406] Fix reversed logic in zeroconf homekit pairing check (#35596) * Fix reversed logic in zeroconf homekit pairing check * s/server_info/service_info/ --- homeassistant/components/zeroconf/__init__.py | 8 +++++++- tests/components/zeroconf/test_init.py | 4 ++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/zeroconf/__init__.py b/homeassistant/components/zeroconf/__init__.py index 8376ed09e6c..d699160eed4 100644 --- a/homeassistant/components/zeroconf/__init__.py +++ b/homeassistant/components/zeroconf/__init__.py @@ -178,6 +178,11 @@ def setup(hass, config): return service_info = zeroconf.get_service_info(service_type, name) + if not service_info: + # Prevent the browser thread from collapsing as + # service_info can be None + return + info = info_from_service(service_info) _LOGGER.debug("Discovered new device %s %s", name, info) @@ -196,7 +201,8 @@ def setup(hass, config): and HOMEKIT_PAIRED_STATUS_FLAG in info[HOMEKIT_PROPERTIES] ): try: - if not int(info[HOMEKIT_PROPERTIES][HOMEKIT_PAIRED_STATUS_FLAG]): + # 0 means paired and not discoverable by iOS clients) + if int(info[HOMEKIT_PROPERTIES][HOMEKIT_PAIRED_STATUS_FLAG]): return except ValueError: # HomeKit pairing status unknown diff --git a/tests/components/zeroconf/test_init.py b/tests/components/zeroconf/test_init.py index 9d6d08d5b27..89c9d0c2643 100644 --- a/tests/components/zeroconf/test_init.py +++ b/tests/components/zeroconf/test_init.py @@ -18,8 +18,8 @@ PROPERTIES = { NON_ASCII_KEY: None, } -HOMEKIT_STATUS_UNPAIRED = b"0" -HOMEKIT_STATUS_PAIRED = b"1" +HOMEKIT_STATUS_UNPAIRED = b"1" +HOMEKIT_STATUS_PAIRED = b"0" @pytest.fixture From cb7b8d94c04d83aa79a4102a9aac4be7445f9773 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 14 May 2020 19:33:14 +0200 Subject: [PATCH 018/406] Add check for HTML in translations (#35615) * Add check for HTML in translations and remove existing html * Add test --- .../components/ambiclimate/strings.json | 2 +- .../components/logi_circle/strings.json | 2 +- homeassistant/components/point/strings.json | 2 +- .../components/starline/strings.json | 2 +- homeassistant/helpers/config_validation.py | 9 ++++++ script/hassfest/translations.py | 28 +++++++++---------- tests/helpers/test_config_validation.py | 20 +++++++++++++ 7 files changed, 47 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/ambiclimate/strings.json b/homeassistant/components/ambiclimate/strings.json index 50bc8284b71..26af78f8915 100644 --- a/homeassistant/components/ambiclimate/strings.json +++ b/homeassistant/components/ambiclimate/strings.json @@ -3,7 +3,7 @@ "step": { "auth": { "title": "Authenticate Ambiclimate", - "description": "Please follow this [link]({authorization_url}) and Allow access to your Ambiclimate account, then come back and press Submit below.\n(Make sure the specified callback url is {cb_url})" + "description": "Please follow this [link]({authorization_url}) and **Allow** access to your Ambiclimate account, then come back and press **Submit** below.\n(Make sure the specified callback url is {cb_url})" } }, "create_entry": { diff --git a/homeassistant/components/logi_circle/strings.json b/homeassistant/components/logi_circle/strings.json index 347589c7881..d96f0e041a1 100644 --- a/homeassistant/components/logi_circle/strings.json +++ b/homeassistant/components/logi_circle/strings.json @@ -8,7 +8,7 @@ }, "auth": { "title": "Authenticate with Logi Circle", - "description": "Please follow the link below and Accept access to your Logi Circle account, then come back and press Submit below.\n\n[Link]({authorization_url})" + "description": "Please follow the link below and **Accept** access to your Logi Circle account, then come back and press **Submit** below.\n\n[Link]({authorization_url})" } }, "create_entry": { diff --git a/homeassistant/components/point/strings.json b/homeassistant/components/point/strings.json index b42c6cef198..194121e8e25 100644 --- a/homeassistant/components/point/strings.json +++ b/homeassistant/components/point/strings.json @@ -8,7 +8,7 @@ }, "auth": { "title": "Authenticate Point", - "description": "Please follow the link below and Accept access to your Minut account, then come back and press Submit below.\n\n[Link]({authorization_url})" + "description": "Please follow the link below and **Accept** access to your Minut account, then come back and press **Submit** below.\n\n[Link]({authorization_url})" } }, "create_entry": { diff --git a/homeassistant/components/starline/strings.json b/homeassistant/components/starline/strings.json index 919efb2f024..33e28f9a29d 100644 --- a/homeassistant/components/starline/strings.json +++ b/homeassistant/components/starline/strings.json @@ -3,7 +3,7 @@ "step": { "auth_app": { "title": "Application credentials", - "description": "Application ID and secret code from StarLine developer account", + "description": "Application ID and secret code from [StarLine developer account](https://my.starline.ru/developer)", "data": { "app_id": "App ID", "app_secret": "Secret" diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 01f737b47da..32121958b03 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -465,6 +465,15 @@ def string(value: Any) -> str: return str(value) +def string_with_no_html(value: Any) -> str: + """Validate that the value is a string without HTML.""" + value = string(value) + regex = re.compile(r"<[a-z][\s\S]*>") + if regex.search(value): + raise vol.Invalid("the string should not contain HTML") + return str(value) + + def temperature_unit(value: Any) -> str: """Validate and transform temperature unit.""" value = str(value).upper() diff --git a/script/hassfest/translations.py b/script/hassfest/translations.py index 4c7fc12fbcb..416bfbdb47e 100644 --- a/script/hassfest/translations.py +++ b/script/hassfest/translations.py @@ -91,20 +91,20 @@ def gen_data_entry_schema( """Generate a data entry schema.""" step_title_class = vol.Required if require_step_title else vol.Optional schema = { - vol.Optional("flow_title"): str, + vol.Optional("flow_title"): cv.string_with_no_html, vol.Required("step"): { str: { - step_title_class("title"): str, - vol.Optional("description"): str, - vol.Optional("data"): {str: str}, + step_title_class("title"): cv.string_with_no_html, + vol.Optional("description"): cv.string_with_no_html, + vol.Optional("data"): {str: cv.string_with_no_html}, } }, - vol.Optional("error"): {str: str}, - vol.Optional("abort"): {str: str}, - vol.Optional("create_entry"): {str: str}, + vol.Optional("error"): {str: cv.string_with_no_html}, + vol.Optional("abort"): {str: cv.string_with_no_html}, + vol.Optional("create_entry"): {str: cv.string_with_no_html}, } if flow_title == REQUIRED: - schema[vol.Required("title")] = str + schema[vol.Required("title")] = cv.string_with_no_html elif flow_title == REMOVED: schema[vol.Optional("title", msg=REMOVED_TITLE_MSG)] = partial( removed_title_validator, config, integration @@ -117,7 +117,7 @@ def gen_strings_schema(config: Config, integration: Integration): """Generate a strings schema.""" return vol.Schema( { - vol.Optional("title"): str, + vol.Optional("title"): cv.string_with_no_html, vol.Optional("config"): gen_data_entry_schema( config=config, integration=integration, @@ -131,10 +131,10 @@ def gen_strings_schema(config: Config, integration: Integration): require_step_title=False, ), vol.Optional("device_automation"): { - vol.Optional("action_type"): {str: str}, - vol.Optional("condition_type"): {str: str}, - vol.Optional("trigger_type"): {str: str}, - vol.Optional("trigger_subtype"): {str: str}, + vol.Optional("action_type"): {str: cv.string_with_no_html}, + vol.Optional("condition_type"): {str: cv.string_with_no_html}, + vol.Optional("trigger_type"): {str: cv.string_with_no_html}, + vol.Optional("trigger_subtype"): {str: cv.string_with_no_html}, }, vol.Optional("state"): cv.schema_with_slug_keys( cv.schema_with_slug_keys(str, slug_validator=lowercase_validator), @@ -203,7 +203,7 @@ def gen_platform_strings_schema(config: Config, integration: Integration): ) -ONBOARDING_SCHEMA = vol.Schema({vol.Required("area"): {str: str}}) +ONBOARDING_SCHEMA = vol.Schema({vol.Required("area"): {str: cv.string_with_no_html}}) def validate_translation_file(config: Config, integration: Integration, all_strings): diff --git a/tests/helpers/test_config_validation.py b/tests/helpers/test_config_validation.py index bee463092ff..72eb61bbacb 100644 --- a/tests/helpers/test_config_validation.py +++ b/tests/helpers/test_config_validation.py @@ -351,6 +351,26 @@ def test_string(): schema(value) +def test_string_with_no_html(): + """Test string with no html validation.""" + schema = vol.Schema(cv.string_with_no_html) + + with pytest.raises(vol.Invalid): + schema("This has HTML in it Link") + + with pytest.raises(vol.Invalid): + schema("Bold") + + for value in ( + True, + 3, + "Hello", + "**Hello**", + "This has no HTML [Link](https://home-assistant.io)", + ): + schema(value) + + def test_temperature_unit(): """Test temperature unit validation.""" schema = vol.Schema(cv.temperature_unit) From 9fd6db4b5ff7e3999f8aaf0c0e0d75d7d661600b Mon Sep 17 00:00:00 2001 From: uvjustin <46082645+uvjustin@users.noreply.github.com> Date: Fri, 15 May 2020 01:42:00 +0800 Subject: [PATCH 019/406] Clean up forked_daapd volume saving/setting in async_play_media (#35584) * Clean up volume saving/setting in async_play_media * Set source to pipe when queued externally * Add server version requirement to error string --- .../components/forked_daapd/media_player.py | 42 +++++++++++-------- .../components/forked_daapd/strings.json | 2 +- .../forked_daapd/translations/en.json | 2 +- .../forked_daapd/test_media_player.py | 35 ++++++++++++++-- 4 files changed, 59 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/forked_daapd/media_player.py b/homeassistant/components/forked_daapd/media_player.py index 8224ea68914..1b52d454def 100644 --- a/homeassistant/components/forked_daapd/media_player.py +++ b/homeassistant/components/forked_daapd/media_player.py @@ -237,7 +237,7 @@ class ForkedDaapdMaster(MediaPlayerDevice): self._track_info = defaultdict( str ) # _track info is found by matching _player data with _queue data - self._last_outputs = None # used for device on/off + self._last_outputs = [] # used for device on/off self._last_volume = DEFAULT_UNMUTE_VOLUME self._player_last_updated = None self._pipe_control_api = {} @@ -349,6 +349,13 @@ class ForkedDaapdMaster(MediaPlayerDevice): ): self._tts_requested = False self._tts_queued = True + + if ( + self._queue["count"] >= 1 + and self._queue["items"][0]["data_kind"] == "pipe" + and self._queue["items"][0]["title"] in KNOWN_PIPES + ): # if we're playing a pipe, set the source automatically so we can forward controls + self._source = f"{self._queue['items'][0]['title']} (pipe)" self._update_track_info() event.set() @@ -407,6 +414,7 @@ class ForkedDaapdMaster(MediaPlayerDevice): async def async_turn_on(self): """Restore the last on outputs state.""" # restore state + await self._api.set_volume(volume=self._last_volume * 100) if self._last_outputs: futures = [] for output in self._last_outputs: @@ -418,19 +426,16 @@ class ForkedDaapdMaster(MediaPlayerDevice): ) ) await asyncio.wait(futures) - else: - selected = [] - for output in self._outputs: - selected.append(output["id"]) - await self._api.set_enabled_outputs(selected) + else: # enable all outputs + await self._api.set_enabled_outputs( + [output["id"] for output in self._outputs] + ) async def async_turn_off(self): """Pause player and store outputs state.""" await self.async_media_pause() - if any( - [output["selected"] for output in self._outputs] - ): # only store output state if some output is selected - self._last_outputs = self._outputs + self._last_outputs = self._outputs + if any([output["selected"] for output in self._outputs]): await self._api.set_enabled_outputs([]) async def async_toggle(self): @@ -613,8 +618,12 @@ class ForkedDaapdMaster(MediaPlayerDevice): url = self._api.full_url(url) return url - async def _set_tts_volumes(self): + async def _save_and_set_tts_volumes(self): + if self.volume_level: # save master volume + self._last_volume = self.volume_level + self._last_outputs = self._outputs if self._outputs: + await self._api.set_volume(volume=self._tts_volume * 100) futures = [] for output in self._outputs: futures.append( @@ -623,7 +632,6 @@ class ForkedDaapdMaster(MediaPlayerDevice): ) ) await asyncio.wait(futures) - await self._api.set_volume(volume=self._tts_volume * 100) async def _pause_and_wait_for_callback(self): """Send pause and wait for the pause callback to be received.""" @@ -641,14 +649,12 @@ class ForkedDaapdMaster(MediaPlayerDevice): """Play a URI.""" if media_type == MEDIA_TYPE_MUSIC: saved_state = self.state # save play state - if any([output["selected"] for output in self._outputs]): # save outputs - self._last_outputs = self._outputs - await self._api.set_enabled_outputs([]) # turn off outputs + saved_mute = self.is_volume_muted sleep_future = asyncio.create_task( asyncio.sleep(self._tts_pause_time) ) # start timing now, but not exact because of fd buffer + tts latency await self._pause_and_wait_for_callback() - await self._set_tts_volumes() + await self._save_and_set_tts_volumes() # save position saved_song_position = self._player["item_progress_ms"] saved_queue = ( @@ -678,7 +684,9 @@ class ForkedDaapdMaster(MediaPlayerDevice): _LOGGER.warning("TTS request timed out") self._tts_playing_event.clear() # TTS done, return to normal - await self.async_turn_on() # restores outputs + await self.async_turn_on() # restore outputs and volumes + if saved_mute: # mute if we were muted + await self.async_mute_volume(True) if self._use_pipe_control(): # resume pipe await self._api.add_to_queue( uris=self._sources_uris[self._source], clear=True diff --git a/homeassistant/components/forked_daapd/strings.json b/homeassistant/components/forked_daapd/strings.json index ea822a5559c..33f9c7b91aa 100644 --- a/homeassistant/components/forked_daapd/strings.json +++ b/homeassistant/components/forked_daapd/strings.json @@ -16,7 +16,7 @@ "websocket_not_enabled": "forked-daapd server websocket not enabled.", "wrong_host_or_port": "Unable to connect. Please check host and port.", "wrong_password": "Incorrect password.", - "wrong_server_type": "Not a forked-daapd server.", + "wrong_server_type": "The forked-daapd integration requires a forked-daapd server with version >= 27.0.", "unknown_error": "Unknown error." }, "abort": { diff --git a/homeassistant/components/forked_daapd/translations/en.json b/homeassistant/components/forked_daapd/translations/en.json index 6fe1b006984..0c87c6624ff 100644 --- a/homeassistant/components/forked_daapd/translations/en.json +++ b/homeassistant/components/forked_daapd/translations/en.json @@ -9,7 +9,7 @@ "websocket_not_enabled": "forked-daapd server websocket not enabled.", "wrong_host_or_port": "Unable to connect. Please check host and port.", "wrong_password": "Incorrect password.", - "wrong_server_type": "Not a forked-daapd server." + "wrong_server_type": "The forked-daapd integration requires a forked-daapd server with version >= 27.0." }, "flow_title": "forked-daapd server: {name} ({host})", "step": { diff --git a/tests/components/forked_daapd/test_media_player.py b/tests/components/forked_daapd/test_media_player.py index b0bed5aba3b..8f3a6c2c139 100644 --- a/tests/components/forked_daapd/test_media_player.py +++ b/tests/components/forked_daapd/test_media_player.py @@ -111,7 +111,7 @@ SAMPLE_PLAYER_STOPPED = { "item_progress_ms": 5, } -SAMPLE_TTS_QUEUE = { +SAMPLE_QUEUE_TTS = { "version": 833, "count": 1, "items": [ @@ -127,11 +127,31 @@ SAMPLE_TTS_QUEUE = { "length_ms": 0, "track_number": 1, "media_kind": "music", + "data_kind": "url", "uri": "tts_proxy_somefile.mp3", } ], } +SAMPLE_QUEUE_PIPE = { + "version": 833, + "count": 1, + "items": [ + { + "id": 12322, + "title": "librespot-java", + "artist": "some artist", + "album": "some album", + "album_artist": "The xx", + "length_ms": 0, + "track_number": 1, + "media_kind": "music", + "data_kind": "pipe", + "uri": "pipeuri", + } + ], +} + SAMPLE_CONFIG = { "websocket_port": 3688, "version": "25.0", @@ -272,7 +292,7 @@ async def get_request_return_values_fixture(): "config": SAMPLE_CONFIG, "outputs": SAMPLE_OUTPUTS_ON, "player": SAMPLE_PLAYER_PAUSED, - "queue": SAMPLE_TTS_QUEUE, + "queue": SAMPLE_QUEUE_TTS, } @@ -630,7 +650,9 @@ async def pipe_control_api_object_fixture( return pipe_control_api.return_value -async def test_librespot_java_stuff(hass, pipe_control_api_object): +async def test_librespot_java_stuff( + hass, get_request_return_values, mock_api_object, pipe_control_api_object +): """Test options update and librespot-java stuff.""" state = hass.states.get(TEST_MASTER_ENTITY_NAME) assert state.attributes[ATTR_INPUT_SOURCE] == "librespot-java (pipe)" @@ -652,6 +674,13 @@ async def test_librespot_java_stuff(hass, pipe_control_api_object): ) state = hass.states.get(TEST_MASTER_ENTITY_NAME) assert state.attributes[ATTR_INPUT_SOURCE] == SOURCE_NAME_DEFAULT + # test pipe getting queued externally changes source + get_request_return_values["queue"] = SAMPLE_QUEUE_PIPE + updater_update = mock_api_object.start_websocket_handler.call_args[0][2] + await updater_update(["queue"]) + await hass.async_block_till_done() + state = hass.states.get(TEST_MASTER_ENTITY_NAME) + assert state.attributes[ATTR_INPUT_SOURCE] == "librespot-java (pipe)" async def test_librespot_java_play_media(hass, pipe_control_api_object): From 8cf354c042d054840e101c1d66ab1adfb516b471 Mon Sep 17 00:00:00 2001 From: zewelor Date: Thu, 14 May 2020 19:44:32 +0200 Subject: [PATCH 020/406] Provide yeelight unique_id using ssdp discovery (#35448) * Provide yeelight unique_id using ssdp discovery * Fixes * Comment fix * Cleanup initialization logic and add unique id to binary sensor * Update homeassistant/components/yeelight/__init__.py Co-authored-by: Teemu R. * Update homeassistant/components/yeelight/__init__.py Co-authored-by: Teemu R. * Update comment * Update comment * Fix wrong model docstring Co-authored-by: Teemu R. --- homeassistant/components/yeelight/__init__.py | 32 +++++++++++++------ .../components/yeelight/binary_sensor.py | 9 ++++++ homeassistant/components/yeelight/light.py | 23 +++++++++++++ .../components/yeelight/manifest.json | 2 +- requirements_all.txt | 2 +- 5 files changed, 57 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/yeelight/__init__.py b/homeassistant/components/yeelight/__init__.py index 1e0fe841cac..c36c7be00fa 100644 --- a/homeassistant/components/yeelight/__init__.py +++ b/homeassistant/components/yeelight/__init__.py @@ -2,6 +2,7 @@ from datetime import timedelta import logging +from typing import Optional import voluptuous as vol from yeelight import Bulb, BulbException @@ -201,8 +202,7 @@ class YeelightDevice: self._config = config self._ipaddr = ipaddr self._name = config.get(CONF_NAME) - self._model = config.get(CONF_MODEL) - self._bulb_device = Bulb(self.ipaddr, model=self._model) + self._bulb_device = Bulb(self.ipaddr, model=config.get(CONF_MODEL)) self._device_type = None self._available = False self._initialized = False @@ -234,8 +234,8 @@ class YeelightDevice: @property def model(self): - """Return configured device model.""" - return self._model + """Return configured/autodetected device model.""" + return self._bulb_device.model @property def is_nightlight_supported(self) -> bool: @@ -287,6 +287,11 @@ class YeelightDevice: return self._device_type + @property + def unique_id(self) -> Optional[str]: + """Return a unique ID.""" + return self.bulb.capabilities.get("id") + def turn_on(self, duration=DEFAULT_TRANSITION, light_type=None, power_mode=None): """Turn on device.""" try: @@ -324,7 +329,20 @@ class YeelightDevice: return self._available + def _get_capabilities(self): + """Request device capabilities.""" + try: + self.bulb.get_capabilities() + except BulbException as ex: + _LOGGER.error( + "Unable to get device capabilities %s, %s: %s", + self.ipaddr, + self.name, + ex, + ) + def _initialize_device(self): + self._get_capabilities() self._initialized = True dispatcher_send(self._hass, DEVICE_INITIALIZED, self.ipaddr) @@ -335,8 +353,4 @@ class YeelightDevice: def setup(self): """Fetch initial device properties.""" - initial_update = self._update_properties() - - # We can build correct class anyway. - if not initial_update and self.model: - self._initialize_device() + self._update_properties() diff --git a/homeassistant/components/yeelight/binary_sensor.py b/homeassistant/components/yeelight/binary_sensor.py index 78a20ab0104..cc2145d93b4 100644 --- a/homeassistant/components/yeelight/binary_sensor.py +++ b/homeassistant/components/yeelight/binary_sensor.py @@ -1,5 +1,6 @@ """Sensor platform support for yeelight.""" import logging +from typing import Optional from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -38,6 +39,14 @@ class YeelightNightlightModeSensor(BinarySensorEntity): ) ) + @property + def unique_id(self) -> Optional[str]: + """Return a unique ID.""" + unique = self._device.unique_id + + if unique: + return unique + "-nightlight_sensor" + @property def should_poll(self): """No polling needed.""" diff --git a/homeassistant/components/yeelight/light.py b/homeassistant/components/yeelight/light.py index 29f943906d6..e0ece2afdb9 100644 --- a/homeassistant/components/yeelight/light.py +++ b/homeassistant/components/yeelight/light.py @@ -1,5 +1,6 @@ """Light platform support for yeelight.""" import logging +from typing import Optional import voluptuous as vol import yeelight @@ -470,6 +471,12 @@ class YeelightGenericLight(LightEntity): """No polling needed.""" return False + @property + def unique_id(self) -> Optional[str]: + """Return a unique ID.""" + + return self.device.unique_id + @property def available(self) -> bool: """Return if bulb is available.""" @@ -902,6 +909,14 @@ class YeelightWithNightLight( class YeelightNightLightMode(YeelightGenericLight): """Representation of a Yeelight when in nightlight mode.""" + @property + def unique_id(self) -> Optional[str]: + """Return a unique ID.""" + unique = super().unique_id + + if unique: + return unique + "-nightlight" + @property def name(self) -> str: """Return the name of the device if any.""" @@ -985,6 +1000,14 @@ class YeelightAmbientLight(YeelightColorLightWithoutNightlightSwitch): self._light_type = LightType.Ambient + @property + def unique_id(self) -> Optional[str]: + """Return a unique ID.""" + unique = super().unique_id + + if unique: + return unique + "-ambilight" + @property def name(self) -> str: """Return the name of the device if any.""" diff --git a/homeassistant/components/yeelight/manifest.json b/homeassistant/components/yeelight/manifest.json index ad3022d5d5a..32ccf1c117e 100644 --- a/homeassistant/components/yeelight/manifest.json +++ b/homeassistant/components/yeelight/manifest.json @@ -2,7 +2,7 @@ "domain": "yeelight", "name": "Yeelight", "documentation": "https://www.home-assistant.io/integrations/yeelight", - "requirements": ["yeelight==0.5.1"], + "requirements": ["yeelight==0.5.2"], "after_dependencies": ["discovery"], "codeowners": ["@rytilahti", "@zewelor"] } diff --git a/requirements_all.txt b/requirements_all.txt index 518e91194ff..5f595e0c5a5 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2224,7 +2224,7 @@ ya_ma==0.3.8 yalesmartalarmclient==0.1.6 # homeassistant.components.yeelight -yeelight==0.5.1 +yeelight==0.5.2 # homeassistant.components.yeelightsunflower yeelightsunflower==0.0.10 From 7e1c836acbdcf541ff43d74344113cfb9be01cbf Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Thu, 14 May 2020 13:50:54 -0400 Subject: [PATCH 021/406] Don't remove deprecated ZHA config option yet (#35627) --- homeassistant/components/zha/__init__.py | 2 ++ tests/components/zha/test_init.py | 25 ++++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/homeassistant/components/zha/__init__.py b/homeassistant/components/zha/__init__.py index 4f844613336..8a23c6fc20d 100644 --- a/homeassistant/components/zha/__init__.py +++ b/homeassistant/components/zha/__init__.py @@ -44,6 +44,8 @@ ZHA_CONFIG_SCHEMA = { ), vol.Optional(CONF_ENABLE_QUIRKS, default=True): cv.boolean, vol.Optional(CONF_ZIGPY): dict, + vol.Optional(CONF_RADIO_TYPE): cv.enum(RadioType), + vol.Optional(CONF_USB_PATH): cv.string, } CONFIG_SCHEMA = vol.Schema( { diff --git a/tests/components/zha/test_init.py b/tests/components/zha/test_init.py index 963cea33bdd..f0b82231fa9 100644 --- a/tests/components/zha/test_init.py +++ b/tests/components/zha/test_init.py @@ -9,6 +9,7 @@ from homeassistant.components.zha.core.const import ( CONF_USB_PATH, DOMAIN, ) +from homeassistant.const import MAJOR_VERSION, MINOR_VERSION from homeassistant.setup import async_setup_component from tests.async_mock import AsyncMock, patch @@ -70,3 +71,27 @@ async def test_migration_from_v1_wrong_baudrate(hass, config_entry_v1): assert CONF_USB_PATH not in config_entry_v1.data assert CONF_BAUDRATE not in config_entry_v1.data[CONF_DEVICE] assert config_entry_v1.version == 2 + + +@pytest.mark.skipif( + MAJOR_VERSION != 0 or (MAJOR_VERSION == 0 and MINOR_VERSION >= 112), + reason="Not applicaable for this version", +) +@pytest.mark.parametrize( + "zha_config", + ( + {}, + {CONF_USB_PATH: "str"}, + {CONF_RADIO_TYPE: "ezsp"}, + {CONF_RADIO_TYPE: "ezsp", CONF_USB_PATH: "str"}, + ), +) +async def test_config_depreciation(hass, zha_config): + """Test config option depreciation.""" + await async_setup_component(hass, "persistent_notification", {}) + + with patch( + "homeassistant.components.zha.async_setup", return_value=True + ) as setup_mock: + assert await async_setup_component(hass, DOMAIN, {DOMAIN: zha_config}) + assert setup_mock.call_count == 1 From fca09b2615db37df73434393d3a7989ba04ad721 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 14 May 2020 21:31:35 +0200 Subject: [PATCH 022/406] Updated frontend to 20200514.1 (#35632) --- 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 5d4f3ce03d2..6a1af47a9cf 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==20200514.0"], + "requirements": ["home-assistant-frontend==20200514.1"], "dependencies": [ "api", "auth", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index ae80e53a171..b763b479fe8 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -12,7 +12,7 @@ cryptography==2.9.2 defusedxml==0.6.0 distro==1.5.0 hass-nabucasa==0.34.2 -home-assistant-frontend==20200514.0 +home-assistant-frontend==20200514.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 5f595e0c5a5..577b96429ef 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -728,7 +728,7 @@ hole==0.5.1 holidays==0.10.2 # homeassistant.components.frontend -home-assistant-frontend==20200514.0 +home-assistant-frontend==20200514.1 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 862398fb073..8d9744cf6d6 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -312,7 +312,7 @@ hole==0.5.1 holidays==0.10.2 # homeassistant.components.frontend -home-assistant-frontend==20200514.0 +home-assistant-frontend==20200514.1 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From 626f72b97aa3f9536a248fb75376bc30278a0d85 Mon Sep 17 00:00:00 2001 From: isk0001y Date: Thu, 14 May 2020 21:48:34 +0200 Subject: [PATCH 023/406] Add tado zone variable open window detected (#34969) * openwindow0: this is the part for the HA itself * Update homeassistant/components/tado/sensor.py Co-authored-by: J. Nick Koston * Update homeassistant/components/tado/sensor.py Co-authored-by: J. Nick Koston * new version after review Co-authored-by: J. Nick Koston --- homeassistant/components/tado/sensor.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/tado/sensor.py b/homeassistant/components/tado/sensor.py index ceca0e0170e..231dc419b6b 100644 --- a/homeassistant/components/tado/sensor.py +++ b/homeassistant/components/tado/sensor.py @@ -222,7 +222,10 @@ class TadoZoneSensor(TadoZoneEntity, Entity): self._state = self._tado_zone_data.preparation elif self.zone_variable == "open window": - self._state = self._tado_zone_data.open_window + self._state = bool( + self._tado_zone_data.open_window + or self._tado_zone_data.open_window_detected + ) self._state_attributes = self._tado_zone_data.open_window_attr From 0be20ec6abff8396fb18338e3fe9fa394b688758 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 14 May 2020 22:56:04 +0200 Subject: [PATCH 024/406] Rename zwave_mqtt to ozw (#35631) --- .coveragerc | 8 ++++---- CODEOWNERS | 2 +- .../components/{zwave_mqtt => ozw}/__init__.py | 12 ++++++------ .../{zwave_mqtt => ozw}/binary_sensor.py | 0 .../{zwave_mqtt => ozw}/config_flow.py | 6 +++--- .../components/{zwave_mqtt => ozw}/const.py | 4 ++-- .../components/{zwave_mqtt => ozw}/discovery.py | 0 .../components/{zwave_mqtt => ozw}/entity.py | 0 .../components/{zwave_mqtt => ozw}/light.py | 0 homeassistant/components/ozw/manifest.json | 9 +++++++++ .../components/{zwave_mqtt => ozw}/sensor.py | 0 .../components/{zwave_mqtt => ozw}/services.py | 0 .../{zwave_mqtt => ozw}/services.yaml | 0 .../components/{zwave_mqtt => ozw}/strings.json | 1 - .../components/{zwave_mqtt => ozw}/switch.py | 0 .../{zwave_mqtt => ozw}/translations/ca.json | 0 .../{zwave_mqtt => ozw}/translations/de.json | 0 .../{zwave_mqtt => ozw}/translations/en.json | 0 .../{zwave_mqtt => ozw}/translations/es.json | 0 .../{zwave_mqtt => ozw}/translations/fi.json | 0 .../{zwave_mqtt => ozw}/translations/fr.json | 0 .../{zwave_mqtt => ozw}/translations/it.json | 0 .../{zwave_mqtt => ozw}/translations/ko.json | 0 .../{zwave_mqtt => ozw}/translations/lb.json | 0 .../{zwave_mqtt => ozw}/translations/nl.json | 0 .../{zwave_mqtt => ozw}/translations/no.json | 0 .../{zwave_mqtt => ozw}/translations/pl.json | 0 .../{zwave_mqtt => ozw}/translations/ru.json | 0 .../{zwave_mqtt => ozw}/translations/sl.json | 0 .../{zwave_mqtt => ozw}/translations/sv.json | 0 .../translations/zh-Hans.json | 0 .../translations/zh-Hant.json | 0 .../components/zwave_mqtt/manifest.json | 17 ----------------- homeassistant/generated/config_flows.py | 4 ++-- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/ozw/__init__.py | 1 + tests/components/{zwave_mqtt => ozw}/common.py | 8 ++++---- .../components/{zwave_mqtt => ozw}/conftest.py | 16 +++++++--------- .../{zwave_mqtt => ozw}/test_binary_sensor.py | 8 ++++---- .../{zwave_mqtt => ozw}/test_config_flow.py | 8 ++++---- .../components/{zwave_mqtt => ozw}/test_init.py | 12 ++++++------ .../{zwave_mqtt => ozw}/test_light.py | 6 +++--- .../{zwave_mqtt => ozw}/test_scenes.py | 6 +++--- .../{zwave_mqtt => ozw}/test_sensor.py | 8 ++++---- .../{zwave_mqtt => ozw}/test_switch.py | 4 ++-- tests/components/zwave_mqtt/__init__.py | 1 - .../{zwave_mqtt => ozw}/binary_sensor.json | 0 .../{zwave_mqtt => ozw}/binary_sensor_alt.json | 0 .../generic_network_dump.csv | 0 tests/fixtures/{zwave_mqtt => ozw}/light.json | 0 .../{zwave_mqtt => ozw}/light_network_dump.csv | 0 tests/fixtures/{zwave_mqtt => ozw}/sensor.json | 0 tests/fixtures/{zwave_mqtt => ozw}/switch.json | 0 54 files changed, 67 insertions(+), 78 deletions(-) rename homeassistant/components/{zwave_mqtt => ozw}/__init__.py (97%) rename homeassistant/components/{zwave_mqtt => ozw}/binary_sensor.py (100%) rename homeassistant/components/{zwave_mqtt => ozw}/config_flow.py (86%) rename homeassistant/components/{zwave_mqtt => ozw}/const.py (94%) rename homeassistant/components/{zwave_mqtt => ozw}/discovery.py (100%) rename homeassistant/components/{zwave_mqtt => ozw}/entity.py (100%) rename homeassistant/components/{zwave_mqtt => ozw}/light.py (100%) create mode 100644 homeassistant/components/ozw/manifest.json rename homeassistant/components/{zwave_mqtt => ozw}/sensor.py (100%) rename homeassistant/components/{zwave_mqtt => ozw}/services.py (100%) rename homeassistant/components/{zwave_mqtt => ozw}/services.yaml (100%) rename homeassistant/components/{zwave_mqtt => ozw}/strings.json (89%) rename homeassistant/components/{zwave_mqtt => ozw}/switch.py (100%) rename homeassistant/components/{zwave_mqtt => ozw}/translations/ca.json (100%) rename homeassistant/components/{zwave_mqtt => ozw}/translations/de.json (100%) rename homeassistant/components/{zwave_mqtt => ozw}/translations/en.json (100%) rename homeassistant/components/{zwave_mqtt => ozw}/translations/es.json (100%) rename homeassistant/components/{zwave_mqtt => ozw}/translations/fi.json (100%) rename homeassistant/components/{zwave_mqtt => ozw}/translations/fr.json (100%) rename homeassistant/components/{zwave_mqtt => ozw}/translations/it.json (100%) rename homeassistant/components/{zwave_mqtt => ozw}/translations/ko.json (100%) rename homeassistant/components/{zwave_mqtt => ozw}/translations/lb.json (100%) rename homeassistant/components/{zwave_mqtt => ozw}/translations/nl.json (100%) rename homeassistant/components/{zwave_mqtt => ozw}/translations/no.json (100%) rename homeassistant/components/{zwave_mqtt => ozw}/translations/pl.json (100%) rename homeassistant/components/{zwave_mqtt => ozw}/translations/ru.json (100%) rename homeassistant/components/{zwave_mqtt => ozw}/translations/sl.json (100%) rename homeassistant/components/{zwave_mqtt => ozw}/translations/sv.json (100%) rename homeassistant/components/{zwave_mqtt => ozw}/translations/zh-Hans.json (100%) rename homeassistant/components/{zwave_mqtt => ozw}/translations/zh-Hant.json (100%) delete mode 100644 homeassistant/components/zwave_mqtt/manifest.json create mode 100644 tests/components/ozw/__init__.py rename tests/components/{zwave_mqtt => ozw}/common.py (87%) rename tests/components/{zwave_mqtt => ozw}/conftest.py (80%) rename tests/components/{zwave_mqtt => ozw}/test_binary_sensor.py (89%) rename tests/components/{zwave_mqtt => ozw}/test_config_flow.py (85%) rename tests/components/{zwave_mqtt => ozw}/test_init.py (84%) rename tests/components/{zwave_mqtt => ozw}/test_light.py (96%) rename tests/components/{zwave_mqtt => ozw}/test_scenes.py (93%) rename tests/components/{zwave_mqtt => ozw}/test_sensor.py (92%) rename tests/components/{zwave_mqtt => ozw}/test_switch.py (92%) delete mode 100644 tests/components/zwave_mqtt/__init__.py rename tests/fixtures/{zwave_mqtt => ozw}/binary_sensor.json (100%) rename tests/fixtures/{zwave_mqtt => ozw}/binary_sensor_alt.json (100%) rename tests/fixtures/{zwave_mqtt => ozw}/generic_network_dump.csv (100%) rename tests/fixtures/{zwave_mqtt => ozw}/light.json (100%) rename tests/fixtures/{zwave_mqtt => ozw}/light_network_dump.csv (100%) rename tests/fixtures/{zwave_mqtt => ozw}/sensor.json (100%) rename tests/fixtures/{zwave_mqtt => ozw}/switch.json (100%) diff --git a/.coveragerc b/.coveragerc index 282db156e65..c91b7b2a1a0 100644 --- a/.coveragerc +++ b/.coveragerc @@ -919,10 +919,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 + homeassistant/components/ozw/__init__.py + homeassistant/components/ozw/discovery.py + homeassistant/components/ozw/entity.py + homeassistant/components/ozw/services.py [report] # Regexes for lines to exclude from consideration diff --git a/CODEOWNERS b/CODEOWNERS index 5a2723a5d83..da3d035925c 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -292,6 +292,7 @@ homeassistant/components/openweathermap/* @fabaff homeassistant/components/opnsense/* @mtreinish homeassistant/components/orangepi_gpio/* @pascallj homeassistant/components/oru/* @bvlaicu +homeassistant/components/ozw/* @cgarwood @marcelveldt @MartinHjelmare homeassistant/components/panasonic_viera/* @joogps homeassistant/components/panel_custom/* @home-assistant/frontend homeassistant/components/panel_iframe/* @home-assistant/frontend @@ -474,7 +475,6 @@ 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/ozw/__init__.py similarity index 97% rename from homeassistant/components/zwave_mqtt/__init__.py rename to homeassistant/components/ozw/__init__.py index c391f79a542..3a7d35ddd1d 100644 --- a/homeassistant/components/zwave_mqtt/__init__.py +++ b/homeassistant/components/ozw/__init__.py @@ -1,4 +1,4 @@ -"""The zwave_mqtt integration.""" +"""The ozw integration.""" import asyncio import json import logging @@ -43,7 +43,7 @@ DATA_DEVICES = "zwave-mqtt-devices" async def async_setup(hass: HomeAssistant, config: dict): - """Initialize basic config of zwave_mqtt component.""" + """Initialize basic config of ozw component.""" if "mqtt" not in hass.config.components: _LOGGER.error("MQTT integration is not set up") return False @@ -52,9 +52,9 @@ async def async_setup(hass: HomeAssistant, config: dict): 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] = [] + """Set up ozw from a config entry.""" + ozw_data = hass.data[DOMAIN][entry.entry_id] = {} + ozw_data[DATA_UNSUBSCRIBE] = [] data_nodes = {} data_values = {} @@ -216,7 +216,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): for component in PLATFORMS ] ) - zwave_mqtt_data[DATA_UNSUBSCRIBE].append( + ozw_data[DATA_UNSUBSCRIBE].append( await mqtt.async_subscribe( hass, f"{TOPIC_OPENZWAVE}/#", async_receive_message ) diff --git a/homeassistant/components/zwave_mqtt/binary_sensor.py b/homeassistant/components/ozw/binary_sensor.py similarity index 100% rename from homeassistant/components/zwave_mqtt/binary_sensor.py rename to homeassistant/components/ozw/binary_sensor.py diff --git a/homeassistant/components/zwave_mqtt/config_flow.py b/homeassistant/components/ozw/config_flow.py similarity index 86% rename from homeassistant/components/zwave_mqtt/config_flow.py rename to homeassistant/components/ozw/config_flow.py index ff6ab21994f..8822490132c 100644 --- a/homeassistant/components/zwave_mqtt/config_flow.py +++ b/homeassistant/components/ozw/config_flow.py @@ -1,13 +1,13 @@ -"""Config flow for zwave_mqtt integration.""" +"""Config flow for ozw integration.""" from homeassistant import config_entries from .const import DOMAIN # pylint:disable=unused-import -TITLE = "Z-Wave MQTT" +TITLE = "OpenZWave" class DomainConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): - """Handle a config flow for zwave_mqtt.""" + """Handle a config flow for ozw.""" VERSION = 1 CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH diff --git a/homeassistant/components/zwave_mqtt/const.py b/homeassistant/components/ozw/const.py similarity index 94% rename from homeassistant/components/zwave_mqtt/const.py rename to homeassistant/components/ozw/const.py index 5b1f6dcbac7..59f189d124d 100644 --- a/homeassistant/components/zwave_mqtt/const.py +++ b/homeassistant/components/ozw/const.py @@ -1,10 +1,10 @@ -"""Constants for the zwave_mqtt integration.""" +"""Constants for the ozw integration.""" from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN -DOMAIN = "zwave_mqtt" +DOMAIN = "ozw" DATA_UNSUBSCRIBE = "unsubscribe" PLATFORMS = [BINARY_SENSOR_DOMAIN, LIGHT_DOMAIN, SENSOR_DOMAIN, SWITCH_DOMAIN] diff --git a/homeassistant/components/zwave_mqtt/discovery.py b/homeassistant/components/ozw/discovery.py similarity index 100% rename from homeassistant/components/zwave_mqtt/discovery.py rename to homeassistant/components/ozw/discovery.py diff --git a/homeassistant/components/zwave_mqtt/entity.py b/homeassistant/components/ozw/entity.py similarity index 100% rename from homeassistant/components/zwave_mqtt/entity.py rename to homeassistant/components/ozw/entity.py diff --git a/homeassistant/components/zwave_mqtt/light.py b/homeassistant/components/ozw/light.py similarity index 100% rename from homeassistant/components/zwave_mqtt/light.py rename to homeassistant/components/ozw/light.py diff --git a/homeassistant/components/ozw/manifest.json b/homeassistant/components/ozw/manifest.json new file mode 100644 index 00000000000..3b828845852 --- /dev/null +++ b/homeassistant/components/ozw/manifest.json @@ -0,0 +1,9 @@ +{ + "domain": "ozw", + "name": "OpenZWave (beta)", + "config_flow": true, + "documentation": "https://www.home-assistant.io/integrations/ozw", + "requirements": ["python-openzwave-mqtt==1.0.1"], + "after_dependencies": ["mqtt"], + "codeowners": ["@cgarwood", "@marcelveldt", "@MartinHjelmare"] +} diff --git a/homeassistant/components/zwave_mqtt/sensor.py b/homeassistant/components/ozw/sensor.py similarity index 100% rename from homeassistant/components/zwave_mqtt/sensor.py rename to homeassistant/components/ozw/sensor.py diff --git a/homeassistant/components/zwave_mqtt/services.py b/homeassistant/components/ozw/services.py similarity index 100% rename from homeassistant/components/zwave_mqtt/services.py rename to homeassistant/components/ozw/services.py diff --git a/homeassistant/components/zwave_mqtt/services.yaml b/homeassistant/components/ozw/services.yaml similarity index 100% rename from homeassistant/components/zwave_mqtt/services.yaml rename to homeassistant/components/ozw/services.yaml diff --git a/homeassistant/components/zwave_mqtt/strings.json b/homeassistant/components/ozw/strings.json similarity index 89% rename from homeassistant/components/zwave_mqtt/strings.json rename to homeassistant/components/ozw/strings.json index 949b545086b..dd2aad7e4ce 100644 --- a/homeassistant/components/zwave_mqtt/strings.json +++ b/homeassistant/components/ozw/strings.json @@ -1,5 +1,4 @@ { - "title": "Z-Wave over MQTT", "config": { "step": { "user": { diff --git a/homeassistant/components/zwave_mqtt/switch.py b/homeassistant/components/ozw/switch.py similarity index 100% rename from homeassistant/components/zwave_mqtt/switch.py rename to homeassistant/components/ozw/switch.py diff --git a/homeassistant/components/zwave_mqtt/translations/ca.json b/homeassistant/components/ozw/translations/ca.json similarity index 100% rename from homeassistant/components/zwave_mqtt/translations/ca.json rename to homeassistant/components/ozw/translations/ca.json diff --git a/homeassistant/components/zwave_mqtt/translations/de.json b/homeassistant/components/ozw/translations/de.json similarity index 100% rename from homeassistant/components/zwave_mqtt/translations/de.json rename to homeassistant/components/ozw/translations/de.json diff --git a/homeassistant/components/zwave_mqtt/translations/en.json b/homeassistant/components/ozw/translations/en.json similarity index 100% rename from homeassistant/components/zwave_mqtt/translations/en.json rename to homeassistant/components/ozw/translations/en.json diff --git a/homeassistant/components/zwave_mqtt/translations/es.json b/homeassistant/components/ozw/translations/es.json similarity index 100% rename from homeassistant/components/zwave_mqtt/translations/es.json rename to homeassistant/components/ozw/translations/es.json diff --git a/homeassistant/components/zwave_mqtt/translations/fi.json b/homeassistant/components/ozw/translations/fi.json similarity index 100% rename from homeassistant/components/zwave_mqtt/translations/fi.json rename to homeassistant/components/ozw/translations/fi.json diff --git a/homeassistant/components/zwave_mqtt/translations/fr.json b/homeassistant/components/ozw/translations/fr.json similarity index 100% rename from homeassistant/components/zwave_mqtt/translations/fr.json rename to homeassistant/components/ozw/translations/fr.json diff --git a/homeassistant/components/zwave_mqtt/translations/it.json b/homeassistant/components/ozw/translations/it.json similarity index 100% rename from homeassistant/components/zwave_mqtt/translations/it.json rename to homeassistant/components/ozw/translations/it.json diff --git a/homeassistant/components/zwave_mqtt/translations/ko.json b/homeassistant/components/ozw/translations/ko.json similarity index 100% rename from homeassistant/components/zwave_mqtt/translations/ko.json rename to homeassistant/components/ozw/translations/ko.json diff --git a/homeassistant/components/zwave_mqtt/translations/lb.json b/homeassistant/components/ozw/translations/lb.json similarity index 100% rename from homeassistant/components/zwave_mqtt/translations/lb.json rename to homeassistant/components/ozw/translations/lb.json diff --git a/homeassistant/components/zwave_mqtt/translations/nl.json b/homeassistant/components/ozw/translations/nl.json similarity index 100% rename from homeassistant/components/zwave_mqtt/translations/nl.json rename to homeassistant/components/ozw/translations/nl.json diff --git a/homeassistant/components/zwave_mqtt/translations/no.json b/homeassistant/components/ozw/translations/no.json similarity index 100% rename from homeassistant/components/zwave_mqtt/translations/no.json rename to homeassistant/components/ozw/translations/no.json diff --git a/homeassistant/components/zwave_mqtt/translations/pl.json b/homeassistant/components/ozw/translations/pl.json similarity index 100% rename from homeassistant/components/zwave_mqtt/translations/pl.json rename to homeassistant/components/ozw/translations/pl.json diff --git a/homeassistant/components/zwave_mqtt/translations/ru.json b/homeassistant/components/ozw/translations/ru.json similarity index 100% rename from homeassistant/components/zwave_mqtt/translations/ru.json rename to homeassistant/components/ozw/translations/ru.json diff --git a/homeassistant/components/zwave_mqtt/translations/sl.json b/homeassistant/components/ozw/translations/sl.json similarity index 100% rename from homeassistant/components/zwave_mqtt/translations/sl.json rename to homeassistant/components/ozw/translations/sl.json diff --git a/homeassistant/components/zwave_mqtt/translations/sv.json b/homeassistant/components/ozw/translations/sv.json similarity index 100% rename from homeassistant/components/zwave_mqtt/translations/sv.json rename to homeassistant/components/ozw/translations/sv.json diff --git a/homeassistant/components/zwave_mqtt/translations/zh-Hans.json b/homeassistant/components/ozw/translations/zh-Hans.json similarity index 100% rename from homeassistant/components/zwave_mqtt/translations/zh-Hans.json rename to homeassistant/components/ozw/translations/zh-Hans.json diff --git a/homeassistant/components/zwave_mqtt/translations/zh-Hant.json b/homeassistant/components/ozw/translations/zh-Hant.json similarity index 100% rename from homeassistant/components/zwave_mqtt/translations/zh-Hant.json rename to homeassistant/components/ozw/translations/zh-Hant.json diff --git a/homeassistant/components/zwave_mqtt/manifest.json b/homeassistant/components/zwave_mqtt/manifest.json deleted file mode 100644 index 8d067bf5c35..00000000000 --- a/homeassistant/components/zwave_mqtt/manifest.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "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/generated/config_flows.py b/homeassistant/generated/config_flows.py index 52700a17e2b..43ccb4ef6d1 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -106,6 +106,7 @@ FLOWS = [ "opentherm_gw", "openuv", "owntracks", + "ozw", "panasonic_viera", "pi_hole", "plaato", @@ -164,6 +165,5 @@ FLOWS = [ "xiaomi_miio", "zerproc", "zha", - "zwave", - "zwave_mqtt" + "zwave" ] diff --git a/requirements_all.txt b/requirements_all.txt index 577b96429ef..cc20340939f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1707,7 +1707,7 @@ python-nest==4.1.0 # homeassistant.components.nmap_tracker python-nmap==0.6.1 -# homeassistant.components.zwave_mqtt +# homeassistant.components.ozw python-openzwave-mqtt==1.0.1 # homeassistant.components.qbittorrent diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 8d9744cf6d6..ad01d396b60 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -704,7 +704,7 @@ python-miio==0.5.0.1 # homeassistant.components.nest python-nest==4.1.0 -# homeassistant.components.zwave_mqtt +# homeassistant.components.ozw python-openzwave-mqtt==1.0.1 # homeassistant.components.songpal diff --git a/tests/components/ozw/__init__.py b/tests/components/ozw/__init__.py new file mode 100644 index 00000000000..ce419b9f55b --- /dev/null +++ b/tests/components/ozw/__init__.py @@ -0,0 +1 @@ +"""Tests for the OZW integration.""" diff --git a/tests/components/zwave_mqtt/common.py b/tests/components/ozw/common.py similarity index 87% rename from tests/components/zwave_mqtt/common.py rename to tests/components/ozw/common.py index ef85d2e5533..a71103fdf85 100644 --- a/tests/components/zwave_mqtt/common.py +++ b/tests/components/ozw/common.py @@ -2,14 +2,14 @@ import json from homeassistant import config_entries -from homeassistant.components.zwave_mqtt.const import DOMAIN +from homeassistant.components.ozw.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.""" +async def setup_ozw(hass, entry=None, fixture=None): + """Set up OZW and load a dump.""" hass.config.components.add("mqtt") if entry is None: @@ -26,7 +26,7 @@ async def setup_zwave(hass, entry=None, fixture=None): assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() - assert "zwave_mqtt" in hass.config.components + assert "ozw" in hass.config.components assert len(mock_subscribe.mock_calls) == 1 receive_message = mock_subscribe.mock_calls[0][1][2] diff --git a/tests/components/zwave_mqtt/conftest.py b/tests/components/ozw/conftest.py similarity index 80% rename from tests/components/zwave_mqtt/conftest.py rename to tests/components/ozw/conftest.py index 67e8382d23d..b984172d355 100644 --- a/tests/components/zwave_mqtt/conftest.py +++ b/tests/components/ozw/conftest.py @@ -12,13 +12,13 @@ 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("zwave_mqtt/generic_network_dump.csv") + return load_fixture("ozw/generic_network_dump.csv") @pytest.fixture(name="light_data", scope="session") def light_data_fixture(): """Load light dimmer MQTT data and return it.""" - return load_fixture("zwave_mqtt/light_network_dump.csv") + return load_fixture("ozw/light_network_dump.csv") @pytest.fixture(name="sent_messages") @@ -39,7 +39,7 @@ def sent_messages_fixture(): async def light_msg_fixture(hass): """Return a mock MQTT msg with a light actuator message.""" light_json = json.loads( - await hass.async_add_executor_job(load_fixture, "zwave_mqtt/light.json") + await hass.async_add_executor_job(load_fixture, "ozw/light.json") ) message = MQTTMessage(topic=light_json["topic"], payload=light_json["payload"]) message.encode() @@ -50,7 +50,7 @@ async def light_msg_fixture(hass): 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") + await hass.async_add_executor_job(load_fixture, "ozw/switch.json") ) message = MQTTMessage(topic=switch_json["topic"], payload=switch_json["payload"]) message.encode() @@ -61,7 +61,7 @@ async def switch_msg_fixture(hass): async def sensor_msg_fixture(hass): """Return a mock MQTT msg with a sensor change message.""" sensor_json = json.loads( - await hass.async_add_executor_job(load_fixture, "zwave_mqtt/sensor.json") + await hass.async_add_executor_job(load_fixture, "ozw/sensor.json") ) message = MQTTMessage(topic=sensor_json["topic"], payload=sensor_json["payload"]) message.encode() @@ -72,7 +72,7 @@ async def sensor_msg_fixture(hass): async def binary_sensor_msg_fixture(hass): """Return a mock MQTT msg with a binary_sensor change message.""" sensor_json = json.loads( - await hass.async_add_executor_job(load_fixture, "zwave_mqtt/binary_sensor.json") + await hass.async_add_executor_job(load_fixture, "ozw/binary_sensor.json") ) message = MQTTMessage(topic=sensor_json["topic"], payload=sensor_json["payload"]) message.encode() @@ -83,9 +83,7 @@ async def binary_sensor_msg_fixture(hass): async def binary_sensor_alt_msg_fixture(hass): """Return a mock MQTT msg with a binary_sensor change message.""" sensor_json = json.loads( - await hass.async_add_executor_job( - load_fixture, "zwave_mqtt/binary_sensor_alt.json" - ) + await hass.async_add_executor_job(load_fixture, "ozw/binary_sensor_alt.json") ) message = MQTTMessage(topic=sensor_json["topic"], payload=sensor_json["payload"]) message.encode() diff --git a/tests/components/zwave_mqtt/test_binary_sensor.py b/tests/components/ozw/test_binary_sensor.py similarity index 89% rename from tests/components/zwave_mqtt/test_binary_sensor.py rename to tests/components/ozw/test_binary_sensor.py index 9e4fddbef75..62b23be0cca 100644 --- a/tests/components/zwave_mqtt/test_binary_sensor.py +++ b/tests/components/ozw/test_binary_sensor.py @@ -3,15 +3,15 @@ from homeassistant.components.binary_sensor import ( DEVICE_CLASS_MOTION, DOMAIN as BINARY_SENSOR_DOMAIN, ) -from homeassistant.components.zwave_mqtt.const import DOMAIN +from homeassistant.components.ozw.const import DOMAIN from homeassistant.const import ATTR_DEVICE_CLASS -from .common import setup_zwave +from .common import setup_ozw async def test_binary_sensor(hass, generic_data, binary_sensor_msg): """Test setting up config entry.""" - receive_msg = await setup_zwave(hass, fixture=generic_data) + receive_msg = await setup_ozw(hass, fixture=generic_data) # Test Legacy sensor (disabled by default) registry = await hass.helpers.entity_registry.async_get_registry() @@ -57,7 +57,7 @@ async def test_sensor_enabled(hass, generic_data, binary_sensor_alt_msg): ) assert entry.disabled is False - receive_msg = await setup_zwave(hass, fixture=generic_data) + receive_msg = await setup_ozw(hass, fixture=generic_data) receive_msg(binary_sensor_alt_msg) await hass.async_block_till_done() diff --git a/tests/components/zwave_mqtt/test_config_flow.py b/tests/components/ozw/test_config_flow.py similarity index 85% rename from tests/components/zwave_mqtt/test_config_flow.py rename to tests/components/ozw/test_config_flow.py index fbdf4012009..bfe3f922402 100644 --- a/tests/components/zwave_mqtt/test_config_flow.py +++ b/tests/components/ozw/test_config_flow.py @@ -1,7 +1,7 @@ """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 homeassistant.components.ozw.config_flow import TITLE +from homeassistant.components.ozw.const import DOMAIN from tests.async_mock import patch from tests.common import MockConfigEntry @@ -18,9 +18,9 @@ async def test_user_create_entry(hass): assert result["errors"] is None with patch( - "homeassistant.components.zwave_mqtt.async_setup", return_value=True + "homeassistant.components.ozw.async_setup", return_value=True ) as mock_setup, patch( - "homeassistant.components.zwave_mqtt.async_setup_entry", return_value=True, + "homeassistant.components.ozw.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/zwave_mqtt/test_init.py b/tests/components/ozw/test_init.py similarity index 84% rename from tests/components/zwave_mqtt/test_init.py rename to tests/components/ozw/test_init.py index 0b77905ab9b..9a76b4906fa 100644 --- a/tests/components/zwave_mqtt/test_init.py +++ b/tests/components/ozw/test_init.py @@ -1,18 +1,18 @@ """Test integration initialization.""" from homeassistant import config_entries -from homeassistant.components.zwave_mqtt import DOMAIN, PLATFORMS, const +from homeassistant.components.ozw import DOMAIN, PLATFORMS, const -from .common import setup_zwave +from .common import setup_ozw 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) + await setup_ozw(hass, fixture=generic_data) # Verify integration + platform loaded. - assert "zwave_mqtt" in hass.config.components + assert "ozw" 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}" @@ -32,7 +32,7 @@ async def test_unload_entry(hass, generic_data, switch_msg, caplog): 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) + receive_message = await setup_ozw(hass, entry=entry, fixture=generic_data) assert entry.state == config_entries.ENTRY_STATE_LOADED assert len(hass.states.async_entity_ids("switch")) == 1 @@ -53,7 +53,7 @@ async def test_unload_entry(hass, generic_data, switch_msg, caplog): # 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 setup_ozw(hass, entry=entry, fixture=generic_data) await hass.async_block_till_done() assert entry.state == config_entries.ENTRY_STATE_LOADED diff --git a/tests/components/zwave_mqtt/test_light.py b/tests/components/ozw/test_light.py similarity index 96% rename from tests/components/zwave_mqtt/test_light.py rename to tests/components/ozw/test_light.py index 33230cd36d9..d485ca768c5 100644 --- a/tests/components/zwave_mqtt/test_light.py +++ b/tests/components/ozw/test_light.py @@ -1,12 +1,12 @@ """Test Z-Wave Lights.""" -from homeassistant.components.zwave_mqtt.light import byte_to_zwave_brightness +from homeassistant.components.ozw.light import byte_to_zwave_brightness -from .common import setup_zwave +from .common import setup_ozw async def test_light(hass, light_data, light_msg, sent_messages): """Test setting up config entry.""" - receive_message = await setup_zwave(hass, fixture=light_data) + receive_message = await setup_ozw(hass, fixture=light_data) # Test loaded state = hass.states.get("light.led_bulb_6_multi_colour_level") diff --git a/tests/components/zwave_mqtt/test_scenes.py b/tests/components/ozw/test_scenes.py similarity index 93% rename from tests/components/zwave_mqtt/test_scenes.py rename to tests/components/ozw/test_scenes.py index 10e1f94b229..2d776b7faf4 100644 --- a/tests/components/zwave_mqtt/test_scenes.py +++ b/tests/components/ozw/test_scenes.py @@ -1,5 +1,5 @@ """Test Z-Wave (central) Scenes.""" -from .common import MQTTMessage, setup_zwave +from .common import MQTTMessage, setup_ozw from tests.common import async_capture_events @@ -7,8 +7,8 @@ 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") + receive_message = await setup_ozw(hass, fixture=generic_data) + events = async_capture_events(hass, "ozw.scene_activated") # Publish fake scene event on mqtt message = MQTTMessage( diff --git a/tests/components/zwave_mqtt/test_sensor.py b/tests/components/ozw/test_sensor.py similarity index 92% rename from tests/components/zwave_mqtt/test_sensor.py rename to tests/components/ozw/test_sensor.py index ab7fd26b0b6..4cc0077cdea 100644 --- a/tests/components/zwave_mqtt/test_sensor.py +++ b/tests/components/ozw/test_sensor.py @@ -1,19 +1,19 @@ """Test Z-Wave Sensors.""" +from homeassistant.components.ozw.const import DOMAIN from homeassistant.components.sensor import ( DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_POWER, DEVICE_CLASS_PRESSURE, DOMAIN as SENSOR_DOMAIN, ) -from homeassistant.components.zwave_mqtt.const import DOMAIN from homeassistant.const import ATTR_DEVICE_CLASS -from .common import setup_zwave +from .common import setup_ozw async def test_sensor(hass, generic_data): """Test setting up config entry.""" - await setup_zwave(hass, fixture=generic_data) + await setup_ozw(hass, fixture=generic_data) # Test standard sensor state = hass.states.get("sensor.smart_plug_electric_v") @@ -66,7 +66,7 @@ async def test_sensor_enabled(hass, generic_data, sensor_msg): ) assert entry.disabled is False - receive_msg = await setup_zwave(hass, fixture=generic_data) + receive_msg = await setup_ozw(hass, fixture=generic_data) receive_msg(sensor_msg) await hass.async_block_till_done() diff --git a/tests/components/zwave_mqtt/test_switch.py b/tests/components/ozw/test_switch.py similarity index 92% rename from tests/components/zwave_mqtt/test_switch.py rename to tests/components/ozw/test_switch.py index 84929dabe0a..7af331b3e0f 100644 --- a/tests/components/zwave_mqtt/test_switch.py +++ b/tests/components/ozw/test_switch.py @@ -1,10 +1,10 @@ """Test Z-Wave Switches.""" -from .common import setup_zwave +from .common import setup_ozw 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) + receive_message = await setup_ozw(hass, fixture=generic_data) # Test loaded state = hass.states.get("switch.smart_plug_switch") diff --git a/tests/components/zwave_mqtt/__init__.py b/tests/components/zwave_mqtt/__init__.py deleted file mode 100644 index 95d36355b29..00000000000 --- a/tests/components/zwave_mqtt/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Tests for the Z-Wave MQTT integration.""" diff --git a/tests/fixtures/zwave_mqtt/binary_sensor.json b/tests/fixtures/ozw/binary_sensor.json similarity index 100% rename from tests/fixtures/zwave_mqtt/binary_sensor.json rename to tests/fixtures/ozw/binary_sensor.json diff --git a/tests/fixtures/zwave_mqtt/binary_sensor_alt.json b/tests/fixtures/ozw/binary_sensor_alt.json similarity index 100% rename from tests/fixtures/zwave_mqtt/binary_sensor_alt.json rename to tests/fixtures/ozw/binary_sensor_alt.json diff --git a/tests/fixtures/zwave_mqtt/generic_network_dump.csv b/tests/fixtures/ozw/generic_network_dump.csv similarity index 100% rename from tests/fixtures/zwave_mqtt/generic_network_dump.csv rename to tests/fixtures/ozw/generic_network_dump.csv diff --git a/tests/fixtures/zwave_mqtt/light.json b/tests/fixtures/ozw/light.json similarity index 100% rename from tests/fixtures/zwave_mqtt/light.json rename to tests/fixtures/ozw/light.json diff --git a/tests/fixtures/zwave_mqtt/light_network_dump.csv b/tests/fixtures/ozw/light_network_dump.csv similarity index 100% rename from tests/fixtures/zwave_mqtt/light_network_dump.csv rename to tests/fixtures/ozw/light_network_dump.csv diff --git a/tests/fixtures/zwave_mqtt/sensor.json b/tests/fixtures/ozw/sensor.json similarity index 100% rename from tests/fixtures/zwave_mqtt/sensor.json rename to tests/fixtures/ozw/sensor.json diff --git a/tests/fixtures/zwave_mqtt/switch.json b/tests/fixtures/ozw/switch.json similarity index 100% rename from tests/fixtures/zwave_mqtt/switch.json rename to tests/fixtures/ozw/switch.json From d0d9b4df784657b8ac00ceccbee052506cb66036 Mon Sep 17 00:00:00 2001 From: Steven Looman Date: Thu, 14 May 2020 22:58:41 +0200 Subject: [PATCH 025/406] Properly handle incomplete upnp ssdp discovery (#35553) --- homeassistant/components/upnp/config_flow.py | 9 +++++++ homeassistant/components/upnp/strings.json | 3 ++- tests/components/upnp/test_config_flow.py | 28 ++++++++++++++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/upnp/config_flow.py b/homeassistant/components/upnp/config_flow.py index 8afd3465f07..a85e47c5919 100644 --- a/homeassistant/components/upnp/config_flow.py +++ b/homeassistant/components/upnp/config_flow.py @@ -134,6 +134,14 @@ class UpnpFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """ _LOGGER.debug("async_step_ssdp: discovery_info: %s", discovery_info) + # Ensure complete discovery. + if ( + ssdp.ATTR_UPNP_UDN not in discovery_info + or ssdp.ATTR_SSDP_ST not in discovery_info + ): + _LOGGER.debug("Incomplete discovery, ignoring") + return self.async_abort(reason="incomplete_discovery") + # Ensure not already configuring/configured. udn = discovery_info[ssdp.ATTR_UPNP_UDN] st = discovery_info[ssdp.ATTR_SSDP_ST] # pylint: disable=invalid-name @@ -218,6 +226,7 @@ class UpnpOptionsFlowHandler(config_entries.OptionsFlow): CONFIG_ENTRY_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL ) update_interval = timedelta(seconds=update_interval_sec) + _LOGGER.debug("Updating coordinator, update_interval: %s", update_interval) coordinator.update_interval = update_interval return self.async_create_entry(title="", data=user_input) diff --git a/homeassistant/components/upnp/strings.json b/homeassistant/components/upnp/strings.json index 8936d2ec791..99e58698f2e 100644 --- a/homeassistant/components/upnp/strings.json +++ b/homeassistant/components/upnp/strings.json @@ -17,7 +17,8 @@ "abort": { "already_configured": "UPnP/IGD is already configured", "no_devices_discovered": "No UPnP/IGDs discovered", - "no_devices_found": "No UPnP/IGD devices found on the network." + "no_devices_found": "No UPnP/IGD devices found on the network.", + "incomplete_discovery": "Incomplete discovery" } } } diff --git a/tests/components/upnp/test_config_flow.py b/tests/components/upnp/test_config_flow.py index c0f5b6b4ff2..870aa13fc41 100644 --- a/tests/components/upnp/test_config_flow.py +++ b/tests/components/upnp/test_config_flow.py @@ -65,6 +65,34 @@ async def test_flow_ssdp_discovery(hass: HomeAssistantType): } +async def test_flow_ssdp_discovery_incomplete(hass: HomeAssistantType): + """Test config flow: incomplete discovery 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, # Not provided. + "friendlyName": mock_device.name, + }, + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "incomplete_discovery" + + async def test_flow_user(hass: HomeAssistantType): """Test config flow: discovered + configured through user.""" udn = "uuid:device_1" From 37cabe556289e31c95cc7eb39d9b97f00e7c9f1c Mon Sep 17 00:00:00 2001 From: Boris Kaplounovsky Date: Fri, 15 May 2020 00:06:33 +0300 Subject: [PATCH 026/406] Add icons for upload/download sensors for asuswrt (#35581) --- homeassistant/components/asuswrt/sensor.py | 23 ++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/homeassistant/components/asuswrt/sensor.py b/homeassistant/components/asuswrt/sensor.py index 631e6e9d70f..cbe32a1ec43 100644 --- a/homeassistant/components/asuswrt/sensor.py +++ b/homeassistant/components/asuswrt/sensor.py @@ -10,6 +10,9 @@ from . import DATA_ASUSWRT _LOGGER = logging.getLogger(__name__) +UPLOAD_ICON = "mdi:upload-network" +DOWNLOAD_ICON = "mdi:download-network" + async def async_setup_platform(hass, config, add_entities, discovery_info=None): """Set up the asuswrt sensors.""" @@ -82,6 +85,11 @@ class AsuswrtRXSensor(AsuswrtSensor): _name = "Asuswrt Download Speed" _unit = DATA_RATE_MEGABITS_PER_SECOND + @property + def icon(self): + """Return the icon.""" + return DOWNLOAD_ICON + @property def unit_of_measurement(self): """Return the unit of measurement.""" @@ -100,6 +108,11 @@ class AsuswrtTXSensor(AsuswrtSensor): _name = "Asuswrt Upload Speed" _unit = DATA_RATE_MEGABITS_PER_SECOND + @property + def icon(self): + """Return the icon.""" + return UPLOAD_ICON + @property def unit_of_measurement(self): """Return the unit of measurement.""" @@ -118,6 +131,11 @@ class AsuswrtTotalRXSensor(AsuswrtSensor): _name = "Asuswrt Download" _unit = DATA_GIGABYTES + @property + def icon(self): + """Return the icon.""" + return DOWNLOAD_ICON + @property def unit_of_measurement(self): """Return the unit of measurement.""" @@ -136,6 +154,11 @@ class AsuswrtTotalTXSensor(AsuswrtSensor): _name = "Asuswrt Upload" _unit = DATA_GIGABYTES + @property + def icon(self): + """Return the icon.""" + return UPLOAD_ICON + @property def unit_of_measurement(self): """Return the unit of measurement.""" From 5c0d237c2dd083d2f6f00eed5dd3e1de0808fed4 Mon Sep 17 00:00:00 2001 From: Joakim Plate Date: Thu, 14 May 2020 23:24:19 +0200 Subject: [PATCH 027/406] Add device triggers to support setting turn_on event (#35456) * Add device triggers to support setting turn_on event * Add turn on event * Add unique_id based on config entry * Adjust tests for addition of uuid * Supported features are now always same * Switch to player_setup fixture that actually start platform * Update homeassistant/components/arcam_fmj/const.py Co-authored-by: Martin Hjelmare Co-authored-by: Martin Hjelmare --- homeassistant/components/arcam_fmj/const.py | 2 + .../components/arcam_fmj/device_trigger.py | 70 ++++++++++++++ .../components/arcam_fmj/media_player.py | 22 +++-- .../components/arcam_fmj/strings.json | 7 ++ .../components/arcam_fmj/translations/en.json | 7 +- tests/components/arcam_fmj/conftest.py | 50 +++++++++- .../arcam_fmj/test_device_trigger.py | 91 +++++++++++++++++++ .../components/arcam_fmj/test_media_player.py | 30 +----- 8 files changed, 241 insertions(+), 38 deletions(-) create mode 100644 homeassistant/components/arcam_fmj/device_trigger.py create mode 100644 homeassistant/components/arcam_fmj/strings.json create mode 100644 tests/components/arcam_fmj/test_device_trigger.py diff --git a/homeassistant/components/arcam_fmj/const.py b/homeassistant/components/arcam_fmj/const.py index dc5a576acec..5270eb706dc 100644 --- a/homeassistant/components/arcam_fmj/const.py +++ b/homeassistant/components/arcam_fmj/const.py @@ -5,6 +5,8 @@ SIGNAL_CLIENT_STARTED = "arcam.client_started" SIGNAL_CLIENT_STOPPED = "arcam.client_stopped" SIGNAL_CLIENT_DATA = "arcam.client_data" +EVENT_TURN_ON = "arcam_fmj.turn_on" + DEFAULT_PORT = 50000 DEFAULT_NAME = "Arcam FMJ" DEFAULT_SCAN_INTERVAL = 5 diff --git a/homeassistant/components/arcam_fmj/device_trigger.py b/homeassistant/components/arcam_fmj/device_trigger.py new file mode 100644 index 00000000000..549b4cf4f82 --- /dev/null +++ b/homeassistant/components/arcam_fmj/device_trigger.py @@ -0,0 +1,70 @@ +"""Provides device automations for Arcam FMJ Receiver control.""" +from typing import List + +import voluptuous as vol + +from homeassistant.components.automation import AutomationActionType +from homeassistant.components.device_automation import TRIGGER_BASE_SCHEMA +from homeassistant.const import ( + ATTR_ENTITY_ID, + CONF_DEVICE_ID, + CONF_DOMAIN, + CONF_ENTITY_ID, + CONF_PLATFORM, + CONF_TYPE, +) +from homeassistant.core import CALLBACK_TYPE, Event, HomeAssistant, callback +from homeassistant.helpers import config_validation as cv, entity_registry +from homeassistant.helpers.typing import ConfigType + +from .const import DOMAIN, EVENT_TURN_ON + +TRIGGER_TYPES = {"turn_on"} +TRIGGER_SCHEMA = TRIGGER_BASE_SCHEMA.extend( + { + vol.Required(CONF_ENTITY_ID): cv.entity_id, + vol.Required(CONF_TYPE): vol.In(TRIGGER_TYPES), + } +) + + +async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]: + """List device triggers for Arcam FMJ Receiver control devices.""" + registry = await entity_registry.async_get_registry(hass) + triggers = [] + + # Get all the integrations entities for this device + for entry in entity_registry.async_entries_for_device(registry, device_id): + if entry.domain == "media_player": + triggers.append( + { + CONF_PLATFORM: "device", + CONF_DEVICE_ID: device_id, + CONF_DOMAIN: DOMAIN, + CONF_ENTITY_ID: entry.entity_id, + CONF_TYPE: "turn_on", + } + ) + + return triggers + + +async def async_attach_trigger( + hass: HomeAssistant, + config: ConfigType, + action: AutomationActionType, + automation_info: dict, +) -> CALLBACK_TYPE: + """Attach a trigger.""" + config = TRIGGER_SCHEMA(config) + + if config[CONF_TYPE] == "turn_on": + + @callback + def _handle_event(event: Event): + if event.data[ATTR_ENTITY_ID] == config[CONF_ENTITY_ID]: + hass.async_run_job(action({"trigger": config}, context=event.context)) + + return hass.bus.async_listen(EVENT_TURN_ON, _handle_event) + + return lambda: None diff --git a/homeassistant/components/arcam_fmj/media_player.py b/homeassistant/components/arcam_fmj/media_player.py index 125b3bf96b1..27e1497a32d 100644 --- a/homeassistant/components/arcam_fmj/media_player.py +++ b/homeassistant/components/arcam_fmj/media_player.py @@ -18,6 +18,7 @@ from homeassistant.components.media_player.const import ( SUPPORT_VOLUME_STEP, ) from homeassistant.const import ( + ATTR_ENTITY_ID, CONF_NAME, CONF_ZONE, SERVICE_TURN_ON, @@ -31,6 +32,7 @@ from homeassistant.helpers.typing import ConfigType, HomeAssistantType from .const import ( DOMAIN, DOMAIN_DATA_ENTRIES, + EVENT_TURN_ON, SIGNAL_CLIENT_DATA, SIGNAL_CLIENT_STARTED, SIGNAL_CLIENT_STOPPED, @@ -53,6 +55,7 @@ async def async_setup_entry( [ ArcamFmj( State(client, zone), + config_entry.unique_id or config_entry.entry_id, zone_config[CONF_NAME], zone_config.get(SERVICE_TURN_ON), ) @@ -67,9 +70,12 @@ async def async_setup_entry( class ArcamFmj(MediaPlayerEntity): """Representation of a media device.""" - def __init__(self, state: State, name: str, turn_on: Optional[ConfigType]): + def __init__( + self, state: State, uuid: str, name: str, turn_on: Optional[ConfigType] + ): """Initialize device.""" self._state = state + self._uuid = uuid self._name = name self._turn_on = turn_on self._support = ( @@ -78,6 +84,7 @@ class ArcamFmj(MediaPlayerEntity): | SUPPORT_VOLUME_MUTE | SUPPORT_VOLUME_STEP | SUPPORT_TURN_OFF + | SUPPORT_TURN_ON ) if state.zn == 1: self._support |= SUPPORT_SELECT_SOUND_MODE @@ -95,6 +102,11 @@ class ArcamFmj(MediaPlayerEntity): ) ) + @property + def unique_id(self): + """Return unique identifier if known.""" + return f"{self._uuid}-{self._state.zn}" + @property def device_info(self): """Return a device description for device registry.""" @@ -124,10 +136,7 @@ class ArcamFmj(MediaPlayerEntity): @property def supported_features(self): """Flag media player features that are supported.""" - support = self._support - if self._state.get_power() is not None or self._turn_on: - support |= SUPPORT_TURN_ON - return support + return self._support async def async_added_to_hass(self): """Once registered, add listener for events.""" @@ -230,7 +239,8 @@ class ArcamFmj(MediaPlayerEntity): validate_config=False, ) else: - _LOGGER.error("Unable to turn on") + _LOGGER.debug("Firing event to turn on device") + self.hass.bus.async_fire(EVENT_TURN_ON, {ATTR_ENTITY_ID: self.entity_id}) async def async_turn_off(self): """Turn the media player off.""" diff --git a/homeassistant/components/arcam_fmj/strings.json b/homeassistant/components/arcam_fmj/strings.json new file mode 100644 index 00000000000..6f60c9e2471 --- /dev/null +++ b/homeassistant/components/arcam_fmj/strings.json @@ -0,0 +1,7 @@ +{ + "device_automation": { + "trigger_type": { + "turn_on": "{entity_name} was requested to turn on" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/arcam_fmj/translations/en.json b/homeassistant/components/arcam_fmj/translations/en.json index b78b8cbaa7b..cad1884ea0d 100644 --- a/homeassistant/components/arcam_fmj/translations/en.json +++ b/homeassistant/components/arcam_fmj/translations/en.json @@ -1,3 +1,8 @@ { - "title": "Arcam FMJ" + "title": "Arcam FMJ", + "device_automation": { + "trigger_type": { + "turn_on": "{entity_name} was requested to turn on" + } + } } \ No newline at end of file diff --git a/tests/components/arcam_fmj/conftest.py b/tests/components/arcam_fmj/conftest.py index e515b71468b..386cdf9a2b0 100644 --- a/tests/components/arcam_fmj/conftest.py +++ b/tests/components/arcam_fmj/conftest.py @@ -7,8 +7,9 @@ from homeassistant.components.arcam_fmj import DEVICE_SCHEMA 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 homeassistant.setup import async_setup_component -from tests.async_mock import Mock +from tests.async_mock import Mock, patch MOCK_HOST = "127.0.0.1" MOCK_PORT = 1234 @@ -17,7 +18,8 @@ MOCK_TURN_ON = { "data": {"entity_id": "switch.test"}, } MOCK_NAME = "dummy" -MOCK_ENTITY_ID = "media_player.arcam_fmj_1" +MOCK_UUID = "1234" +MOCK_ENTITY_ID = "media_player.arcam_fmj_127_0_0_1_1234_1" MOCK_CONFIG = DEVICE_SCHEMA({CONF_HOST: MOCK_HOST, CONF_PORT: MOCK_PORT}) @@ -36,8 +38,8 @@ def client_fixture(): return client -@pytest.fixture(name="state") -def state_fixture(client): +@pytest.fixture(name="state_1") +def state_1_fixture(client): """Get a mocked state.""" state = Mock(State) state.client = client @@ -50,11 +52,49 @@ def state_fixture(client): return state +@pytest.fixture(name="state_2") +def state_2_fixture(client): + """Get a mocked state.""" + state = Mock(State) + state.client = client + state.zn = 2 + state.get_power.return_value = True + state.get_volume.return_value = 0.0 + state.get_source_list.return_value = [] + state.get_incoming_audio_format.return_value = (0, 0) + state.get_mute.return_value = None + return state + + +@pytest.fixture(name="state") +def state_fixture(state_1): + """Get a mocked state.""" + return state_1 + + @pytest.fixture(name="player") def player_fixture(hass, state): """Get standard player.""" - player = ArcamFmj(state, MOCK_NAME, None) + player = ArcamFmj(state, MOCK_UUID, MOCK_NAME, None) player.entity_id = MOCK_ENTITY_ID player.hass = hass player.async_write_ha_state = Mock() return player + + +@pytest.fixture(name="player_setup") +async def player_setup_fixture(hass, config, state_1, state_2, client): + """Get standard player.""" + + def state_mock(cli, zone): + if zone == 1: + return state_1 + if zone == 2: + return state_2 + + with patch("homeassistant.components.arcam_fmj.Client", return_value=client), patch( + "homeassistant.components.arcam_fmj.media_player.State", side_effect=state_mock + ), patch("homeassistant.components.arcam_fmj._run_client", return_value=None): + assert await async_setup_component(hass, "arcam_fmj", config) + await hass.async_block_till_done() + yield MOCK_ENTITY_ID diff --git a/tests/components/arcam_fmj/test_device_trigger.py b/tests/components/arcam_fmj/test_device_trigger.py new file mode 100644 index 00000000000..bff8aa8327f --- /dev/null +++ b/tests/components/arcam_fmj/test_device_trigger.py @@ -0,0 +1,91 @@ +"""The tests for Arcam FMJ Receiver control device triggers.""" +import pytest + +from homeassistant.components.arcam_fmj.const import DOMAIN +import homeassistant.components.automation as automation +from homeassistant.setup import async_setup_component + +from tests.common import ( + MockConfigEntry, + assert_lists_same, + async_get_device_automations, + async_mock_service, + mock_device_registry, + mock_registry, +) + + +@pytest.fixture +def device_reg(hass): + """Return an empty, loaded, registry.""" + return mock_device_registry(hass) + + +@pytest.fixture +def entity_reg(hass): + """Return an empty, loaded, registry.""" + return mock_registry(hass) + + +@pytest.fixture +def calls(hass): + """Track calls to a mock service.""" + return async_mock_service(hass, "test", "automation") + + +async def test_get_triggers(hass, device_reg, entity_reg): + """Test we get the expected triggers from a arcam_fmj.""" + config_entry = MockConfigEntry(domain=DOMAIN, data={}) + config_entry.add_to_hass(hass) + device_entry = device_reg.async_get_or_create( + config_entry_id=config_entry.entry_id, identifiers={(DOMAIN, "host", 1234)}, + ) + entity_reg.async_get_or_create( + "media_player", DOMAIN, "5678", device_id=device_entry.id + ) + expected_triggers = [ + { + "platform": "device", + "domain": DOMAIN, + "type": "turn_on", + "device_id": device_entry.id, + "entity_id": f"media_player.arcam_fmj_5678", + }, + ] + triggers = await async_get_device_automations(hass, "trigger", device_entry.id) + assert_lists_same(triggers, expected_triggers) + + +async def test_if_fires_on_turn_on_request(hass, calls, player_setup, state): + """Test for turn_on and turn_off triggers firing.""" + state.get_power.return_value = None + + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: [ + { + "trigger": { + "platform": "device", + "domain": DOMAIN, + "device_id": "", + "entity_id": player_setup, + "type": "turn_on", + }, + "action": { + "service": "test.automation", + "data_template": {"some": "{{ trigger.entity_id }}"}, + }, + } + ] + }, + ) + + await hass.services.async_call( + "media_player", "turn_on", {"entity_id": player_setup}, blocking=True, + ) + + await hass.async_block_till_done() + assert len(calls) == 1 + assert calls[0].data["some"] == player_setup diff --git a/tests/components/arcam_fmj/test_media_player.py b/tests/components/arcam_fmj/test_media_player.py index 5a73e770129..3d88f337e93 100644 --- a/tests/components/arcam_fmj/test_media_player.py +++ b/tests/components/arcam_fmj/test_media_player.py @@ -7,7 +7,7 @@ import pytest from homeassistant.components.media_player.const import MEDIA_TYPE_MUSIC from homeassistant.core import HomeAssistant -from .conftest import MOCK_ENTITY_ID, MOCK_HOST, MOCK_NAME, MOCK_PORT +from .conftest import MOCK_ENTITY_ID, MOCK_HOST, MOCK_NAME, MOCK_PORT, MOCK_UUID from tests.async_mock import ANY, MagicMock, Mock, PropertyMock, patch @@ -25,7 +25,7 @@ async def update(player, force_refresh=False): async def test_properties(player, state): """Test standard properties.""" - assert player.unique_id is None + assert player.unique_id == f"{MOCK_UUID}-1" assert player.device_info == { "identifiers": {("arcam_fmj", MOCK_HOST, MOCK_PORT)}, "model": "FMJ", @@ -54,30 +54,8 @@ async def test_powered_on(player, state): assert data.state == "on" -async def test_supported_features_no_service(player, state): +async def test_supported_features(player, state): """Test support when turn on service exist.""" - state.get_power.return_value = None - data = await update(player) - assert data.attributes["supported_features"] == 68876 - - state.get_power.return_value = False - data = await update(player) - assert data.attributes["supported_features"] == 69004 - - -async def test_supported_features_service(hass, state): - """Test support when turn on service exist.""" - from homeassistant.components.arcam_fmj.media_player import ArcamFmj - - player = ArcamFmj(state, "dummy", MOCK_TURN_ON) - player.hass = hass - player.entity_id = MOCK_ENTITY_ID - - state.get_power.return_value = None - data = await update(player) - assert data.attributes["supported_features"] == 69004 - - state.get_power.return_value = False data = await update(player) assert data.attributes["supported_features"] == 69004 @@ -97,7 +75,7 @@ async def test_turn_on_with_service(hass, state): """Test support when turn on service exist.""" from homeassistant.components.arcam_fmj.media_player import ArcamFmj - player = ArcamFmj(state, "dummy", MOCK_TURN_ON) + player = ArcamFmj(state, MOCK_UUID, "dummy", MOCK_TURN_ON) player.hass = Mock(HomeAssistant) player.entity_id = MOCK_ENTITY_ID with patch( From 9adc48c0b01bfe643428a79693486acf58a1a063 Mon Sep 17 00:00:00 2001 From: Daniel Perna Date: Thu, 14 May 2020 23:35:11 +0200 Subject: [PATCH 028/406] Fix HM-CC-VD unit, #31083 (#35066) --- homeassistant/components/homematic/sensor.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/homematic/sensor.py b/homeassistant/components/homematic/sensor.py index da803b406bf..6fa78602944 100644 --- a/homeassistant/components/homematic/sensor.py +++ b/homeassistant/components/homematic/sensor.py @@ -62,6 +62,7 @@ HM_UNIT_HA_CAST = { "AIR_PRESSURE": "hPa", "FREQUENCY": FREQUENCY_HERTZ, "VALUE": "#", + "VALVE_STATE": UNIT_PERCENTAGE, } HM_DEVICE_CLASS_HA_CAST = { From 047f3d6061027060bdaf4e0154056e34c08318d9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 14 May 2020 18:27:22 -0500 Subject: [PATCH 029/406] Fix CI failure on arcam tests (#35641) --- tests/components/arcam_fmj/test_device_trigger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/components/arcam_fmj/test_device_trigger.py b/tests/components/arcam_fmj/test_device_trigger.py index bff8aa8327f..7d0aca8628e 100644 --- a/tests/components/arcam_fmj/test_device_trigger.py +++ b/tests/components/arcam_fmj/test_device_trigger.py @@ -49,7 +49,7 @@ async def test_get_triggers(hass, device_reg, entity_reg): "domain": DOMAIN, "type": "turn_on", "device_id": device_entry.id, - "entity_id": f"media_player.arcam_fmj_5678", + "entity_id": "media_player.arcam_fmj_5678", }, ] triggers = await async_get_device_automations(hass, "trigger", device_entry.id) From 9586e9ebef23180d9b2dac9c5215832e5b93dda0 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Fri, 15 May 2020 00:02:56 +0000 Subject: [PATCH 030/406] [ci skip] Translation update --- .../components/abode/translations/pl.json | 4 +- .../components/adguard/translations/pl.json | 10 ++--- .../components/agent_dvr/translations/pl.json | 6 +-- .../components/airly/translations/ca.json | 2 +- .../components/airly/translations/pl.json | 2 +- .../components/airvisual/translations/ca.json | 2 +- .../components/airvisual/translations/pl.json | 10 ++--- .../components/almond/translations/pl.json | 2 +- .../ambiclimate/translations/en.json | 2 +- .../ambiclimate/translations/no.json | 2 +- .../ambient_station/translations/pl.json | 2 +- .../components/arcam_fmj/translations/en.json | 4 +- .../components/arcam_fmj/translations/ko.json | 5 +++ .../components/atag/translations/ca.json | 2 +- .../components/atag/translations/es.json | 1 + .../components/atag/translations/pl.json | 6 +-- .../components/august/translations/pl.json | 10 ++--- .../automation/translations/ca.json | 4 +- .../components/axis/translations/pl.json | 12 +++--- .../binary_sensor/translations/ca.json | 4 +- .../components/blebox/translations/es.json | 1 + .../components/blebox/translations/pl.json | 4 +- .../components/blebox/translations/ru.json | 2 +- .../components/blink/translations/ca.json | 10 ++++- .../components/blink/translations/es.json | 25 +++++++++++ .../components/blink/translations/ko.json | 27 ++++++++++++ .../components/blink/translations/lb.json | 12 ++++++ .../components/blink/translations/no.json | 27 ++++++++++++ .../components/blink/translations/pl.json | 19 +++++++++ .../components/braviatv/translations/ca.json | 2 +- .../components/braviatv/translations/pl.json | 2 +- .../components/brother/translations/ca.json | 2 +- .../components/brother/translations/pl.json | 2 +- .../components/bsblan/translations/ca.json | 4 +- .../components/bsblan/translations/pl.json | 4 +- .../cert_expiry/translations/ca.json | 4 +- .../cert_expiry/translations/pl.json | 4 +- .../coolmaster/translations/pl.json | 2 +- .../components/cover/translations/ca.json | 4 +- .../components/daikin/translations/es.json | 2 +- .../components/daikin/translations/pl.json | 14 +++---- .../components/deconz/translations/nl.json | 4 +- .../components/deconz/translations/pl.json | 6 +-- .../devolo_home_control/translations/pl.json | 4 +- .../components/directv/translations/pl.json | 8 ++-- .../components/doorbird/translations/ca.json | 2 +- .../components/doorbird/translations/pl.json | 12 +++--- .../components/ecobee/translations/pl.json | 4 +- .../components/elgato/translations/ca.json | 4 +- .../components/elgato/translations/pl.json | 6 +-- .../components/elkm1/translations/ca.json | 4 +- .../components/elkm1/translations/pl.json | 8 ++-- .../emulated_roku/translations/pl.json | 4 +- .../components/esphome/translations/ca.json | 2 +- .../components/esphome/translations/pl.json | 6 +-- .../components/fan/translations/ca.json | 4 +- .../flick_electric/translations/pl.json | 10 ++--- .../components/flume/translations/pl.json | 10 ++--- .../flunearyou/translations/pl.json | 2 +- .../forked_daapd/translations/ca.json | 38 +++++++++++++++++ .../forked_daapd/translations/es.json | 31 ++++++++++++++ .../forked_daapd/translations/ko.json | 41 +++++++++++++++++++ .../forked_daapd/translations/lb.json | 22 ++++++++++ .../forked_daapd/translations/no.json | 41 +++++++++++++++++++ .../forked_daapd/translations/pl.json | 13 ++++++ .../forked_daapd/translations/ru.json | 2 +- .../components/freebox/translations/pl.json | 6 +-- .../components/fritzbox/translations/ca.json | 2 +- .../components/fritzbox/translations/pl.json | 10 ++--- .../garmin_connect/translations/pl.json | 10 ++--- .../components/glances/translations/ko.json | 2 +- .../components/glances/translations/pl.json | 8 ++-- .../components/griddy/translations/pl.json | 2 +- .../components/group/translations/ca.json | 10 ++--- .../components/hangouts/translations/ca.json | 4 +- .../components/hangouts/translations/pl.json | 6 +-- .../components/harmony/translations/ca.json | 2 +- .../components/harmony/translations/pl.json | 6 +-- .../components/heos/translations/pl.json | 4 +- .../home_connect/translations/pl.json | 4 +- .../components/homekit/translations/ru.json | 4 +- .../homematicip_cloud/translations/pl.json | 2 +- .../huawei_lte/translations/pl.json | 4 +- .../components/hue/translations/ko.json | 4 +- .../components/hue/translations/pl.json | 4 +- .../translations/pl.json | 4 +- .../components/iaqualink/translations/ca.json | 2 +- .../components/iaqualink/translations/pl.json | 4 +- .../components/icloud/translations/es.json | 1 + .../components/icloud/translations/pl.json | 4 +- .../input_boolean/translations/ca.json | 4 +- .../components/ipp/translations/pl.json | 8 ++-- .../components/isy994/translations/ko.json | 1 + .../components/isy994/translations/no.json | 1 + .../components/isy994/translations/pl.json | 12 +++--- .../components/isy994/translations/ru.json | 1 + .../isy994/translations/zh-Hant.json | 1 + .../components/juicenet/translations/pl.json | 6 +-- .../components/konnected/translations/ca.json | 4 +- .../components/konnected/translations/pl.json | 8 ++-- .../components/life360/translations/pl.json | 6 +-- .../components/light/translations/ca.json | 4 +- .../components/linky/translations/pl.json | 4 +- .../logi_circle/translations/en.json | 2 +- .../logi_circle/translations/no.json | 2 +- .../media_player/translations/ca.json | 4 +- .../components/melcloud/translations/ca.json | 4 +- .../components/melcloud/translations/pl.json | 8 ++-- .../components/mikrotik/translations/pl.json | 8 ++-- .../components/mill/translations/es.json | 2 +- .../components/mill/translations/pl.json | 8 ++-- .../minecraft_server/translations/pl.json | 2 +- .../components/monoprice/translations/ca.json | 2 +- .../components/monoprice/translations/pl.json | 6 +-- .../components/mqtt/translations/pl.json | 6 +-- .../components/myq/translations/pl.json | 8 ++-- .../components/neato/translations/pl.json | 6 +-- .../components/nest/translations/pl.json | 2 +- .../components/netatmo/translations/pl.json | 10 ++--- .../components/nexia/translations/pl.json | 8 ++-- .../components/notion/translations/ca.json | 2 +- .../components/notion/translations/pl.json | 4 +- .../components/nuheat/translations/pl.json | 8 ++-- .../components/nut/translations/pl.json | 12 +++--- .../components/nws/translations/ca.json | 2 +- .../components/nws/translations/pl.json | 6 +-- .../components/onvif/translations/ca.json | 1 + .../components/onvif/translations/en.json | 1 + .../components/onvif/translations/es.json | 1 + .../components/onvif/translations/ko.json | 1 + .../components/onvif/translations/no.json | 1 + .../components/onvif/translations/pl.json | 10 ++--- .../components/onvif/translations/ru.json | 1 + .../components/openuv/translations/ca.json | 2 +- .../components/openuv/translations/pl.json | 2 +- .../components/ozw/translations/ca.json | 3 +- .../components/ozw/translations/de.json | 3 +- .../components/ozw/translations/en.json | 3 +- .../components/ozw/translations/es.json | 3 +- .../components/ozw/translations/fr.json | 3 +- .../components/ozw/translations/it.json | 3 +- .../components/ozw/translations/ko.json | 3 +- .../components/ozw/translations/lb.json | 3 +- .../components/ozw/translations/no.json | 3 +- .../components/ozw/translations/pl.json | 3 +- .../components/ozw/translations/ru.json | 3 +- .../components/ozw/translations/sl.json | 3 +- .../components/ozw/translations/sv.json | 3 +- .../components/ozw/translations/zh-Hant.json | 3 +- .../components/pi_hole/translations/ca.json | 6 ++- .../components/pi_hole/translations/es.json | 23 +++++++++++ .../components/pi_hole/translations/ko.json | 23 +++++++++++ .../components/pi_hole/translations/lb.json | 17 ++++++++ .../components/pi_hole/translations/no.json | 23 +++++++++++ .../components/pi_hole/translations/pl.json | 19 +++++++++ .../components/plex/translations/ca.json | 3 +- .../components/plex/translations/es.json | 1 + .../components/plex/translations/ko.json | 1 + .../components/plex/translations/lb.json | 1 + .../components/plex/translations/no.json | 1 + .../components/plex/translations/pl.json | 6 +-- .../components/point/translations/en.json | 2 +- .../components/point/translations/no.json | 2 +- .../components/point/translations/pl.json | 10 ++--- .../components/powerwall/translations/pl.json | 4 +- .../components/ps4/translations/pl.json | 4 +- .../components/rachio/translations/ca.json | 2 +- .../components/rachio/translations/pl.json | 8 ++-- .../rainmachine/translations/pl.json | 6 +-- .../components/remote/translations/ca.json | 4 +- .../components/ring/translations/ca.json | 2 +- .../components/ring/translations/pl.json | 10 ++--- .../components/roku/translations/ca.json | 8 ++-- .../components/roku/translations/pl.json | 8 ++-- .../components/roomba/translations/ca.json | 2 +- .../components/roomba/translations/pl.json | 6 +-- .../components/samsungtv/translations/ca.json | 2 +- .../components/samsungtv/translations/pl.json | 2 +- .../components/script/translations/ca.json | 4 +- .../components/sense/translations/pl.json | 10 ++--- .../components/sensor/translations/ca.json | 6 +-- .../components/sentry/translations/pl.json | 2 +- .../simplisafe/translations/pl.json | 4 +- .../smartthings/translations/pl.json | 2 +- .../components/solaredge/translations/ca.json | 2 +- .../components/solaredge/translations/pl.json | 2 +- .../components/solarlog/translations/ca.json | 2 +- .../components/solarlog/translations/pl.json | 6 +-- .../components/soma/translations/pl.json | 8 ++-- .../components/somfy/translations/pl.json | 6 +-- .../components/songpal/translations/es.json | 2 +- .../components/songpal/translations/pl.json | 4 +- .../components/spotify/translations/pl.json | 4 +- .../components/starline/translations/en.json | 2 +- .../components/starline/translations/no.json | 2 +- .../components/starline/translations/pl.json | 4 +- .../components/switch/translations/ca.json | 4 +- .../synology_dsm/translations/ca.json | 2 +- .../synology_dsm/translations/pl.json | 14 +++---- .../components/tado/translations/pl.json | 10 ++--- .../tellduslive/translations/ca.json | 2 +- .../tellduslive/translations/pl.json | 6 +-- .../components/tesla/translations/pl.json | 4 +- .../components/tibber/translations/pl.json | 6 +-- .../components/timer/translations/ca.json | 2 +- .../components/toon/translations/pl.json | 4 +- .../totalconnect/translations/pl.json | 4 +- .../components/tradfri/translations/pl.json | 2 +- .../transmission/translations/pl.json | 8 ++-- .../components/tuya/translations/es.json | 2 +- .../components/tuya/translations/nl.json | 26 ++++++++++++ .../components/tuya/translations/pl.json | 11 ++--- .../components/tuya/translations/ru.json | 4 +- .../twentemilieu/translations/pl.json | 2 +- .../components/unifi/translations/ca.json | 4 +- .../components/unifi/translations/pl.json | 12 +++--- .../components/upb/translations/lb.json | 3 ++ .../components/upb/translations/pl.json | 2 +- .../components/upnp/translations/en.json | 1 + .../components/upnp/translations/ko.json | 3 +- .../components/upnp/translations/no.json | 1 + .../components/vacuum/translations/ca.json | 4 +- .../components/vesync/translations/pl.json | 4 +- .../components/vilfo/translations/ca.json | 4 +- .../components/vilfo/translations/pl.json | 6 +-- .../components/vizio/translations/ca.json | 2 +- .../components/vizio/translations/pl.json | 6 +-- .../components/wiffi/translations/ca.json | 16 ++++++++ .../components/wiffi/translations/es.json | 15 +++++++ .../components/wiffi/translations/ko.json | 16 ++++++++ .../components/wiffi/translations/lb.json | 15 +++++++ .../components/wiffi/translations/pl.json | 11 +++++ .../components/withings/translations/pl.json | 4 +- .../components/wled/translations/ca.json | 2 +- .../components/wled/translations/pl.json | 8 ++-- .../xiaomi_miio/translations/ca.json | 3 +- .../xiaomi_miio/translations/en.json | 2 +- .../xiaomi_miio/translations/es.json | 3 +- .../xiaomi_miio/translations/ko.json | 3 +- .../xiaomi_miio/translations/no.json | 3 +- .../xiaomi_miio/translations/pl.json | 6 +-- .../xiaomi_miio/translations/ru.json | 3 +- .../xiaomi_miio/translations/zh-Hant.json | 3 +- .../components/zerproc/translations/ca.json | 3 +- .../components/zerproc/translations/es.json | 14 +++++++ .../components/zerproc/translations/lb.json | 3 ++ .../components/zerproc/translations/pl.json | 8 ++++ .../components/zha/translations/nl.json | 17 ++++++++ .../components/zha/translations/pl.json | 2 +- .../components/zwave/translations/ca.json | 2 +- .../components/zwave/translations/pl.json | 2 +- 251 files changed, 1045 insertions(+), 468 deletions(-) create mode 100644 homeassistant/components/blink/translations/es.json create mode 100644 homeassistant/components/blink/translations/ko.json create mode 100644 homeassistant/components/blink/translations/lb.json create mode 100644 homeassistant/components/blink/translations/no.json create mode 100644 homeassistant/components/blink/translations/pl.json create mode 100644 homeassistant/components/forked_daapd/translations/ca.json create mode 100644 homeassistant/components/forked_daapd/translations/es.json create mode 100644 homeassistant/components/forked_daapd/translations/ko.json create mode 100644 homeassistant/components/forked_daapd/translations/lb.json create mode 100644 homeassistant/components/forked_daapd/translations/no.json create mode 100644 homeassistant/components/forked_daapd/translations/pl.json create mode 100644 homeassistant/components/pi_hole/translations/es.json create mode 100644 homeassistant/components/pi_hole/translations/ko.json create mode 100644 homeassistant/components/pi_hole/translations/lb.json create mode 100644 homeassistant/components/pi_hole/translations/no.json create mode 100644 homeassistant/components/pi_hole/translations/pl.json create mode 100644 homeassistant/components/tuya/translations/nl.json create mode 100644 homeassistant/components/wiffi/translations/ca.json create mode 100644 homeassistant/components/wiffi/translations/es.json create mode 100644 homeassistant/components/wiffi/translations/ko.json create mode 100644 homeassistant/components/wiffi/translations/lb.json create mode 100644 homeassistant/components/wiffi/translations/pl.json create mode 100644 homeassistant/components/zerproc/translations/es.json create mode 100644 homeassistant/components/zerproc/translations/lb.json create mode 100644 homeassistant/components/zerproc/translations/pl.json diff --git a/homeassistant/components/abode/translations/pl.json b/homeassistant/components/abode/translations/pl.json index 6efd1ae885c..d7a25bb20b7 100644 --- a/homeassistant/components/abode/translations/pl.json +++ b/homeassistant/components/abode/translations/pl.json @@ -11,8 +11,8 @@ "step": { "user": { "data": { - "password": "[%key_id:common::config_flow::data::password%]", - "username": "[%key_id:common::config_flow::data::email%]" + "password": "Has\u0142o", + "username": "Adres e-mail" }, "title": "Wprowad\u017a informacje logowania Abode" } diff --git a/homeassistant/components/adguard/translations/pl.json b/homeassistant/components/adguard/translations/pl.json index 6cf2de9163d..71264e906e4 100644 --- a/homeassistant/components/adguard/translations/pl.json +++ b/homeassistant/components/adguard/translations/pl.json @@ -7,7 +7,7 @@ "single_instance_allowed": "Dozwolona jest tylko jedna konfiguracja AdGuard Home." }, "error": { - "connection_error": "[%key_id:common::config_flow::error::cannot_connect%]" + "connection_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia." }, "step": { "hassio_confirm": { @@ -16,11 +16,11 @@ }, "user": { "data": { - "host": "[%key_id:common::config_flow::data::host%]", - "password": "[%key_id:common::config_flow::data::password%]", - "port": "[%key_id:common::config_flow::data::port%]", + "host": "Nazwa hosta lub adres IP", + "password": "Has\u0142o", + "port": "Port", "ssl": "AdGuard Home u\u017cywa certyfikatu SSL", - "username": "[%key_id:common::config_flow::data::username%]", + "username": "Nazwa u\u017cytkownika", "verify_ssl": "AdGuard Home u\u017cywa odpowiedniego certyfikatu." }, "description": "Skonfiguruj instancj\u0119 AdGuard Home, aby umo\u017cliwi\u0107 monitorowanie i kontrol\u0119.", diff --git a/homeassistant/components/agent_dvr/translations/pl.json b/homeassistant/components/agent_dvr/translations/pl.json index 6f36cf2d7d4..5045015087f 100644 --- a/homeassistant/components/agent_dvr/translations/pl.json +++ b/homeassistant/components/agent_dvr/translations/pl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "[%key_id:common::config_flow::abort::already_configured_device%]" + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane." }, "error": { "already_in_progress": "Konfiguracja urz\u0105dzenia jest ju\u017c w toku.", @@ -10,8 +10,8 @@ "step": { "user": { "data": { - "host": "[%key_id:common::config_flow::data::host%]", - "port": "[%key_id:common::config_flow::data::port%]" + "host": "Nazwa hosta lub adres IP", + "port": "Port" }, "title": "Konfiguracja Agent DVR" } diff --git a/homeassistant/components/airly/translations/ca.json b/homeassistant/components/airly/translations/ca.json index 3caf870ccdf..9d085481120 100644 --- a/homeassistant/components/airly/translations/ca.json +++ b/homeassistant/components/airly/translations/ca.json @@ -10,7 +10,7 @@ "step": { "user": { "data": { - "api_key": "Clau API d'Airly", + "api_key": "Clau API", "latitude": "Latitud", "longitude": "Longitud", "name": "Nom de la integraci\u00f3" diff --git a/homeassistant/components/airly/translations/pl.json b/homeassistant/components/airly/translations/pl.json index 1b4b56c7656..340b8cb4622 100644 --- a/homeassistant/components/airly/translations/pl.json +++ b/homeassistant/components/airly/translations/pl.json @@ -10,7 +10,7 @@ "step": { "user": { "data": { - "api_key": "[%key_id:common::config_flow::data::api_key%] Airly", + "api_key": "Klucz API", "latitude": "Szeroko\u015b\u0107 geograficzna", "longitude": "D\u0142ugo\u015b\u0107 geograficzna", "name": "Nazwa integracji" diff --git a/homeassistant/components/airvisual/translations/ca.json b/homeassistant/components/airvisual/translations/ca.json index e285e68e186..12a3cccdad6 100644 --- a/homeassistant/components/airvisual/translations/ca.json +++ b/homeassistant/components/airvisual/translations/ca.json @@ -21,7 +21,7 @@ "node_pro": { "data": { "ip_address": "Adre\u00e7a IP o amfitri\u00f3 de la unitat", - "password": "Contrasenya de la unitat" + "password": "Contrasenya" }, "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" diff --git a/homeassistant/components/airvisual/translations/pl.json b/homeassistant/components/airvisual/translations/pl.json index 7873a18bc7e..b90482deee2 100644 --- a/homeassistant/components/airvisual/translations/pl.json +++ b/homeassistant/components/airvisual/translations/pl.json @@ -4,14 +4,14 @@ "already_configured": "Ten klucz API jest ju\u017c w u\u017cyciu." }, "error": { - "general_error": "[%key_id:common::config_flow::error::unknown%]", + "general_error": "Nieoczekiwany 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": { "geography": { "data": { - "api_key": "[%key_id:common::config_flow::data::api_key%]", + "api_key": "Klucz API", "latitude": "Szeroko\u015b\u0107 geograficzna", "longitude": "D\u0142ugo\u015b\u0107 geograficzna" }, @@ -20,15 +20,15 @@ }, "node_pro": { "data": { - "ip_address": "[%key_id:common::config_flow::data::host%]", - "password": "[%key_id:common::config_flow::data::password%] jednostki" + "ip_address": "Nazwa hosta lub adres IP", + "password": "Has\u0142o" }, "description": "Monitoruj jednostk\u0119 AirVisual. Has\u0142o mo\u017cna odzyska\u0107 z interfejsu u\u017cytkownika urz\u0105dzenia.", "title": "Konfiguracja AirVisual Node/Pro" }, "user": { "data": { - "api_key": "[%key_id:common::config_flow::data::api_key%]", + "api_key": "Klucz API", "cloud_api": "Lokalizacja geograficzna", "latitude": "Szeroko\u015b\u0107 geograficzna", "longitude": "D\u0142ugo\u015b\u0107 geograficzna", diff --git a/homeassistant/components/almond/translations/pl.json b/homeassistant/components/almond/translations/pl.json index ed60372d5b3..6b3feb4bd0b 100644 --- a/homeassistant/components/almond/translations/pl.json +++ b/homeassistant/components/almond/translations/pl.json @@ -11,7 +11,7 @@ "title": "Almond poprzez dodatek Hass.io" }, "pick_implementation": { - "title": "[%key_id:common::config_flow::title::oauth2_pick_implementation%]" + "title": "Wybierz metod\u0119 uwierzytelniania" } } } diff --git a/homeassistant/components/ambiclimate/translations/en.json b/homeassistant/components/ambiclimate/translations/en.json index 509b801fa62..177ecd2907f 100644 --- a/homeassistant/components/ambiclimate/translations/en.json +++ b/homeassistant/components/ambiclimate/translations/en.json @@ -14,7 +14,7 @@ }, "step": { "auth": { - "description": "Please follow this [link]({authorization_url}) and Allow access to your Ambiclimate account, then come back and press Submit below.\n(Make sure the specified callback url is {cb_url})", + "description": "Please follow this [link]({authorization_url}) and **Allow** access to your Ambiclimate account, then come back and press **Submit** below.\n(Make sure the specified callback url is {cb_url})", "title": "Authenticate Ambiclimate" } } diff --git a/homeassistant/components/ambiclimate/translations/no.json b/homeassistant/components/ambiclimate/translations/no.json index 13529d2e1af..3cc3f4617e7 100644 --- a/homeassistant/components/ambiclimate/translations/no.json +++ b/homeassistant/components/ambiclimate/translations/no.json @@ -14,7 +14,7 @@ }, "step": { "auth": { - "description": "Vennligst f\u00f8lg denne [linken]({authorization_url}) og Tillat tilgang til din Ambiclimate konto, kom deretter tilbake og trykk Send nedenfor.\n(Kontroller at den angitte URL-adressen for tilbakeringing er {cb_url})", + "description": "Vennligst f\u00f8lg denne [linken]({authorization_url}) og **Tillat** tilgang til din Ambiclimate konto, kom deretter tilbake og trykk **Send** nedenfor.\n(Kontroller at den angitte URL-adressen for tilbakeringing er {cb_url})", "title": "Godkjenn Ambiclimate" } } diff --git a/homeassistant/components/ambient_station/translations/pl.json b/homeassistant/components/ambient_station/translations/pl.json index 01a0c83bd28..bb597971b0c 100644 --- a/homeassistant/components/ambient_station/translations/pl.json +++ b/homeassistant/components/ambient_station/translations/pl.json @@ -10,7 +10,7 @@ "step": { "user": { "data": { - "api_key": "[%key_id:common::config_flow::data::api_key%]", + "api_key": "Klucz API", "app_key": "Klucz aplikacji" }, "title": "Wprowad\u017a dane" diff --git a/homeassistant/components/arcam_fmj/translations/en.json b/homeassistant/components/arcam_fmj/translations/en.json index cad1884ea0d..95fedf09e93 100644 --- a/homeassistant/components/arcam_fmj/translations/en.json +++ b/homeassistant/components/arcam_fmj/translations/en.json @@ -1,8 +1,8 @@ { - "title": "Arcam FMJ", "device_automation": { "trigger_type": { "turn_on": "{entity_name} was requested to turn on" } - } + }, + "title": "Arcam FMJ" } \ No newline at end of file diff --git a/homeassistant/components/arcam_fmj/translations/ko.json b/homeassistant/components/arcam_fmj/translations/ko.json index b78b8cbaa7b..641679e4a84 100644 --- a/homeassistant/components/arcam_fmj/translations/ko.json +++ b/homeassistant/components/arcam_fmj/translations/ko.json @@ -1,3 +1,8 @@ { + "device_automation": { + "trigger_type": { + "turn_on": "{entity_name} \uc774(\uac00) \ucf1c\uc9c0\ub3c4\ub85d \uc694\uccad\ub418\uc5c8\uc744 \ub54c" + } + }, "title": "Arcam FMJ" } \ No newline at end of file diff --git a/homeassistant/components/atag/translations/ca.json b/homeassistant/components/atag/translations/ca.json index 4208873e675..f0c5211e8e5 100644 --- a/homeassistant/components/atag/translations/ca.json +++ b/homeassistant/components/atag/translations/ca.json @@ -11,7 +11,7 @@ "data": { "email": "Correu electr\u00f2nic (opcional)", "host": "Amfitri\u00f3", - "port": "Port (10000)" + "port": "Port" }, "title": "Connexi\u00f3 amb el dispositiu" } diff --git a/homeassistant/components/atag/translations/es.json b/homeassistant/components/atag/translations/es.json index b02a20e09a1..be9ca1b67a3 100644 --- a/homeassistant/components/atag/translations/es.json +++ b/homeassistant/components/atag/translations/es.json @@ -9,6 +9,7 @@ "step": { "user": { "data": { + "email": "Correo electr\u00f3nico (Opcional)", "host": "Host", "port": "Puerto (10000)" }, diff --git a/homeassistant/components/atag/translations/pl.json b/homeassistant/components/atag/translations/pl.json index a1d7c281dc9..cc65fb9c595 100644 --- a/homeassistant/components/atag/translations/pl.json +++ b/homeassistant/components/atag/translations/pl.json @@ -9,9 +9,9 @@ "step": { "user": { "data": { - "email": "[%key_id:common::config_flow::data::email%] (opcjonalnie)", - "host": "[%key_id:common::config_flow::data::host%]", - "port": "[%key_id:common::config_flow::data::port%] (10000)" + "email": "Adres e-mail (opcjonalnie)", + "host": "Nazwa hosta lub adres IP", + "port": "Port" }, "title": "Po\u0142\u0105cz z urz\u0105dzeniem" } diff --git a/homeassistant/components/august/translations/pl.json b/homeassistant/components/august/translations/pl.json index 33ef6431792..eeaa5269da4 100644 --- a/homeassistant/components/august/translations/pl.json +++ b/homeassistant/components/august/translations/pl.json @@ -1,20 +1,20 @@ { "config": { "abort": { - "already_configured": "[%key_id:common::config_flow::abort::already_configured_account%]" + "already_configured": "Konto jest ju\u017c skonfigurowane." }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie.", - "invalid_auth": "[%key_id:common::config_flow::error::invalid_auth%]", - "unknown": "[%key_id:common::config_flow::error::unknown%]" + "invalid_auth": "Niepoprawne uwierzytelnienie.", + "unknown": "Nieoczekiwany b\u0142\u0105d." }, "step": { "user": { "data": { "login_method": "Metoda logowania", - "password": "[%key_id:common::config_flow::data::password%]", + "password": "Has\u0142o", "timeout": "Limit czasu (sekundy)", - "username": "[%key_id:common::config_flow::data::username%]" + "username": "Nazwa u\u017cytkownika" }, "description": "Je\u015bli metod\u0105 logowania jest 'e-mail', nazw\u0105 u\u017cytkownika b\u0119dzie adres e-mail. Je\u015bli metod\u0105 logowania jest 'telefon', nazw\u0105 u\u017cytkownika b\u0119dzie numer telefonu w formacie '+NNNNNNNNN'.", "title": "Konfiguracja konta August" diff --git a/homeassistant/components/automation/translations/ca.json b/homeassistant/components/automation/translations/ca.json index d138d6da6e5..c1d35331e2b 100644 --- a/homeassistant/components/automation/translations/ca.json +++ b/homeassistant/components/automation/translations/ca.json @@ -1,8 +1,8 @@ { "state": { "_": { - "off": "Desactivat", - "on": "Activat" + "off": "OFF", + "on": "ON" } }, "title": "Automatitzaci\u00f3" diff --git a/homeassistant/components/axis/translations/pl.json b/homeassistant/components/axis/translations/pl.json index 4d4961bddb2..7473c62f668 100644 --- a/homeassistant/components/axis/translations/pl.json +++ b/homeassistant/components/axis/translations/pl.json @@ -1,13 +1,13 @@ { "config": { "abort": { - "already_configured": "[%key_id:common::config_flow::abort::already_configured_device%]", + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane.", "bad_config_file": "B\u0142\u0119dne dane z pliku konfiguracyjnego", "link_local_address": "Po\u0142\u0105czenie lokalnego adresu nie jest obs\u0142ugiwane", "not_axis_device": "Wykryte urz\u0105dzenie nie jest urz\u0105dzeniem Axis" }, "error": { - "already_configured": "[%key_id:common::config_flow::abort::already_configured_device%]", + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane.", "already_in_progress": "Konfiguracja urz\u0105dzenia jest ju\u017c w toku.", "device_unavailable": "Urz\u0105dzenie jest niedost\u0119pne", "faulty_credentials": "B\u0142\u0119dne dane uwierzytelniaj\u0105ce" @@ -16,10 +16,10 @@ "step": { "user": { "data": { - "host": "[%key_id:common::config_flow::data::host%]", - "password": "[%key_id:common::config_flow::data::password%]", - "port": "[%key_id:common::config_flow::data::port%]", - "username": "[%key_id:common::config_flow::data::username%]" + "host": "Nazwa hosta lub adres IP", + "password": "Has\u0142o", + "port": "Port", + "username": "Nazwa u\u017cytkownika" }, "title": "Konfiguracja urz\u0105dzenia Axis" } diff --git a/homeassistant/components/binary_sensor/translations/ca.json b/homeassistant/components/binary_sensor/translations/ca.json index 995b5906c53..2abd811d944 100644 --- a/homeassistant/components/binary_sensor/translations/ca.json +++ b/homeassistant/components/binary_sensor/translations/ca.json @@ -91,8 +91,8 @@ }, "state": { "_": { - "off": "Desactivat", - "on": "Activat" + "off": "OFF", + "on": "ON" }, "battery": { "off": "Normal", diff --git a/homeassistant/components/blebox/translations/es.json b/homeassistant/components/blebox/translations/es.json index ceed1592992..991553a2074 100644 --- a/homeassistant/components/blebox/translations/es.json +++ b/homeassistant/components/blebox/translations/es.json @@ -13,6 +13,7 @@ "step": { "user": { "data": { + "host": "Direcci\u00f3n IP", "port": "Puerto" }, "description": "Configura tu BleBox para integrarse con Home Assistant.", diff --git a/homeassistant/components/blebox/translations/pl.json b/homeassistant/components/blebox/translations/pl.json index f158ad2c75d..5b0f124014e 100644 --- a/homeassistant/components/blebox/translations/pl.json +++ b/homeassistant/components/blebox/translations/pl.json @@ -13,8 +13,8 @@ "step": { "user": { "data": { - "host": "[%key_id:common::config_flow::data::ip%]", - "port": "[%key_id:common::config_flow::data::port%]" + "host": "Adres IP", + "port": "Port" }, "description": "Skonfiguruj BleBox, aby zintegrowa\u0107 si\u0119 z Home Assistant.", "title": "Skonfiguruj urz\u0105dzenie BleBox" diff --git a/homeassistant/components/blebox/translations/ru.json b/homeassistant/components/blebox/translations/ru.json index b9374cfb11f..b82261be7f7 100644 --- a/homeassistant/components/blebox/translations/ru.json +++ b/homeassistant/components/blebox/translations/ru.json @@ -17,7 +17,7 @@ "port": "\u041f\u043e\u0440\u0442" }, "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0441 BleBox.", - "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 BleBox" + "title": "BleBox" } } } diff --git a/homeassistant/components/blink/translations/ca.json b/homeassistant/components/blink/translations/ca.json index 68607b0dbcd..afaacd63793 100644 --- a/homeassistant/components/blink/translations/ca.json +++ b/homeassistant/components/blink/translations/ca.json @@ -8,11 +8,19 @@ "unknown": "Error inesperat" }, "step": { + "2fa": { + "data": { + "2fa": "Codi de dos factors" + }, + "description": "Introdueix el PIN que has rebut per correu electr\u00f2nic. Si el correu no cont\u00e9 cap PIN, deixa-ho en blanc.", + "title": "Autenticaci\u00f3 de dos factors" + }, "user": { "data": { "password": "Contrasenya", "username": "Nom d'usuari" - } + }, + "title": "Inici de sessi\u00f3 amb Blink" } } } diff --git a/homeassistant/components/blink/translations/es.json b/homeassistant/components/blink/translations/es.json new file mode 100644 index 00000000000..d4adae6cce9 --- /dev/null +++ b/homeassistant/components/blink/translations/es.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositivo ya est\u00e1 configurado" + }, + "error": { + "invalid_auth": "Autenticaci\u00f3n no v\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": "Usuario" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/blink/translations/ko.json b/homeassistant/components/blink/translations/ko.json new file mode 100644 index 00000000000..0ac9092c723 --- /dev/null +++ b/homeassistant/components/blink/translations/ko.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "error": { + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "2fa": { + "data": { + "2fa": "2\ub2e8\uacc4 \uc778\uc99d \ucf54\ub4dc" + }, + "description": "\uc774\uba54\uc77c\ub85c \ubcf4\ub0b4\ub4dc\ub9b0 PIN \uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694. \uc774\uba54\uc77c\uc5d0 PIN \uc774 \ud3ec\ud568\ub418\uc9c0 \uc54a\uc740 \uacbd\uc6b0 \ube44\uc6cc \ub461\ub2c8\ub2e4", + "title": "2\ub2e8\uacc4 \uc778\uc99d" + }, + "user": { + "data": { + "password": "\ube44\ubc00\ubc88\ud638", + "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" + }, + "title": "Blink \uacc4\uc815\uc73c\ub85c \ub85c\uadf8\uc778\ud558\uae30" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/blink/translations/lb.json b/homeassistant/components/blink/translations/lb.json new file mode 100644 index 00000000000..33c975593c1 --- /dev/null +++ b/homeassistant/components/blink/translations/lb.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "2fa": { + "data": { + "2fa": "2-Faktor Code" + }, + "title": "2-Faktor-Authentifikatioun" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/blink/translations/no.json b/homeassistant/components/blink/translations/no.json new file mode 100644 index 00000000000..910a401b0be --- /dev/null +++ b/homeassistant/components/blink/translations/no.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "Enheten er allerde konfigurert" + }, + "error": { + "invalid_auth": "Ugyldig legitimasjon", + "unknown": "Uventet feil" + }, + "step": { + "2fa": { + "data": { + "2fa": "To-faktorskode" + }, + "description": "Skriv inn pinnen som er sendt til din e-post. Hvis e-posten ikke inneholder en pin, m\u00e5 du la den st\u00e5 tom", + "title": "Totrinnsverifisering" + }, + "user": { + "data": { + "password": "Passord", + "username": "Brukernavn" + }, + "title": "Logg p\u00e5 med Blink-konto" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/blink/translations/pl.json b/homeassistant/components/blink/translations/pl.json new file mode 100644 index 00000000000..1bbec96e5b5 --- /dev/null +++ b/homeassistant/components/blink/translations/pl.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane." + }, + "error": { + "invalid_auth": "Niepoprawne uwierzytelnienie.", + "unknown": "Nieoczekiwany b\u0142\u0105d." + }, + "step": { + "user": { + "data": { + "password": "Has\u0142o", + "username": "Nazwa u\u017cytkownika" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/braviatv/translations/ca.json b/homeassistant/components/braviatv/translations/ca.json index 5a6d50c5c53..6c05b4b2fc4 100644 --- a/homeassistant/components/braviatv/translations/ca.json +++ b/homeassistant/components/braviatv/translations/ca.json @@ -18,7 +18,7 @@ }, "user": { "data": { - "host": "Nom d'amfitri\u00f3 o adre\u00e7a IP del televisor" + "host": "Amfitri\u00f3" }, "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/pl.json b/homeassistant/components/braviatv/translations/pl.json index e20679c4172..337532165f3 100644 --- a/homeassistant/components/braviatv/translations/pl.json +++ b/homeassistant/components/braviatv/translations/pl.json @@ -18,7 +18,7 @@ }, "user": { "data": { - "host": "[%key_id:common::config_flow::data::host%]" + "host": "Nazwa hosta lub adres IP" }, "description": "Konfiguracja integracji telewizora Sony Bravia. Je\u015bli masz problemy z konfiguracj\u0105, przejd\u017a do strony: https://www.home-assistant.io/integrations/braviatv\n\nUpewnij si\u0119, \u017ce telewizor jest w\u0142\u0105czony.", "title": "Sony Bravia TV" diff --git a/homeassistant/components/brother/translations/ca.json b/homeassistant/components/brother/translations/ca.json index bf96f4c3d58..a27c29239d8 100644 --- a/homeassistant/components/brother/translations/ca.json +++ b/homeassistant/components/brother/translations/ca.json @@ -13,7 +13,7 @@ "step": { "user": { "data": { - "host": "Nom de l'amfitri\u00f3 o adre\u00e7a IP de la impressora", + "host": "Amfitri\u00f3", "type": "Tipus d'impressora" }, "description": "Configura la integraci\u00f3 d'impressora Brother. Si tens problemes amb la configuraci\u00f3, visita: https://www.home-assistant.io/integrations/brother", diff --git a/homeassistant/components/brother/translations/pl.json b/homeassistant/components/brother/translations/pl.json index 7572d44dae8..37bbf8bc749 100644 --- a/homeassistant/components/brother/translations/pl.json +++ b/homeassistant/components/brother/translations/pl.json @@ -13,7 +13,7 @@ "step": { "user": { "data": { - "host": "[%key_id:common::config_flow::data::host%]", + "host": "Nazwa hosta lub adres IP", "type": "Typ drukarki" }, "description": "Konfiguracja integracji drukarek Brother. Je\u015bli masz problemy z konfiguracj\u0105, przejd\u017a na stron\u0119: https://www.home-assistant.io/integrations/brother", diff --git a/homeassistant/components/bsblan/translations/ca.json b/homeassistant/components/bsblan/translations/ca.json index c906429568c..5df258a4f6c 100644 --- a/homeassistant/components/bsblan/translations/ca.json +++ b/homeassistant/components/bsblan/translations/ca.json @@ -10,9 +10,9 @@ "step": { "user": { "data": { - "host": "Amfitri\u00f3 o adre\u00e7a IP", + "host": "Amfitri\u00f3", "passkey": "String Passkey", - "port": "N\u00famero de port" + "port": "Port" }, "title": "Connexi\u00f3 amb dispositiu BSB-Lan" } diff --git a/homeassistant/components/bsblan/translations/pl.json b/homeassistant/components/bsblan/translations/pl.json index 4a53a352896..e70e415d73f 100644 --- a/homeassistant/components/bsblan/translations/pl.json +++ b/homeassistant/components/bsblan/translations/pl.json @@ -3,8 +3,8 @@ "step": { "user": { "data": { - "host": "[%key_id:common::config_flow::data::host%]", - "port": "[%key_id:common::config_flow::data::port%]" + "host": "Nazwa hosta lub adres IP", + "port": "Port" } } } diff --git a/homeassistant/components/cert_expiry/translations/ca.json b/homeassistant/components/cert_expiry/translations/ca.json index 5b9b095acbc..50d761f03c2 100644 --- a/homeassistant/components/cert_expiry/translations/ca.json +++ b/homeassistant/components/cert_expiry/translations/ca.json @@ -12,9 +12,9 @@ "step": { "user": { "data": { - "host": "Nom de l'amfitri\u00f3 del certificat", + "host": "Amfitri\u00f3", "name": "Nom del certificat", - "port": "Port del certificat" + "port": "Port" }, "title": "Configuraci\u00f3 del certificat a provar" } diff --git a/homeassistant/components/cert_expiry/translations/pl.json b/homeassistant/components/cert_expiry/translations/pl.json index b41dbdf9622..1d2e1b3405f 100644 --- a/homeassistant/components/cert_expiry/translations/pl.json +++ b/homeassistant/components/cert_expiry/translations/pl.json @@ -12,9 +12,9 @@ "step": { "user": { "data": { - "host": "Nazwa hosta certyfikatu", + "host": "Nazwa hosta lub adres IP", "name": "Nazwa certyfikatu", - "port": "[%key_id:common::config_flow::data::port%] certyfikatu" + "port": "Port" }, "title": "Zdefiniuj certyfikat do przetestowania" } diff --git a/homeassistant/components/coolmaster/translations/pl.json b/homeassistant/components/coolmaster/translations/pl.json index 0edbd6ed379..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": "[%key_id:common::config_flow::data::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/ca.json b/homeassistant/components/cover/translations/ca.json index e54cc563da5..024a2a67b25 100644 --- a/homeassistant/components/cover/translations/ca.json +++ b/homeassistant/components/cover/translations/ca.json @@ -27,9 +27,9 @@ }, "state": { "_": { - "closed": "Tancada", + "closed": "Tancat/da", "closing": "Tancant", - "open": "Oberta", + "open": "Obert/a", "opening": "Obrint", "stopped": "Aturat" } diff --git a/homeassistant/components/daikin/translations/es.json b/homeassistant/components/daikin/translations/es.json index ae3d205e15d..2bada00d0cb 100644 --- a/homeassistant/components/daikin/translations/es.json +++ b/homeassistant/components/daikin/translations/es.json @@ -7,7 +7,7 @@ }, "error": { "device_fail": "Error inesperado", - "device_timeout": "Error al conectar", + "device_timeout": "No se pudo conectar", "forbidden": "Autenticaci\u00f3n no v\u00e1lida" }, "step": { diff --git a/homeassistant/components/daikin/translations/pl.json b/homeassistant/components/daikin/translations/pl.json index 76999ad718c..9eaa748d03c 100644 --- a/homeassistant/components/daikin/translations/pl.json +++ b/homeassistant/components/daikin/translations/pl.json @@ -1,21 +1,21 @@ { "config": { "abort": { - "already_configured": "[%key_id:common::config_flow::abort::already_configured_device%]", + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane.", "device_fail": "Nieoczekiwany b\u0142\u0105d tworzenia urz\u0105dzenia.", "device_timeout": "Przekroczono limit czasu \u0142\u0105czenia z urz\u0105dzeniem." }, "error": { - "device_fail": "[%key_id:common::config_flow::error::unknown%]", - "device_timeout": "[%key_id:common::config_flow::error::cannot_connect%]", - "forbidden": "[%key_id:common::config_flow::error::invalid_auth%]" + "device_fail": "Nieoczekiwany b\u0142\u0105d.", + "device_timeout": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia.", + "forbidden": "Niepoprawne uwierzytelnienie." }, "step": { "user": { "data": { - "host": "[%key_id:common::config_flow::data::host%]", - "key": "Klucz uwierzytelniania (u\u017cywany tylko przez urz\u0105dzenia BRP072C/Zena)", - "password": "Has\u0142o urz\u0105dzenia (u\u017cywane tylko przez urz\u0105dzenia SKYFi)" + "host": "Nazwa hosta lub adres IP", + "key": "Klucz API", + "password": "Has\u0142o" }, "description": "Wprowad\u017a adres IP Daikin AC.", "title": "Konfiguracja Daikin AC" diff --git a/homeassistant/components/deconz/translations/nl.json b/homeassistant/components/deconz/translations/nl.json index 8b0caa869f8..9d932c52123 100644 --- a/homeassistant/components/deconz/translations/nl.json +++ b/homeassistant/components/deconz/translations/nl.json @@ -3,9 +3,9 @@ "abort": { "already_configured": "Bridge is al geconfigureerd", "already_in_progress": "Configuratiestroom voor bridge wordt al ingesteld.", - "no_bridges": "Geen deCONZ bruggen ontdekt", + "no_bridges": "Geen deCONZ apparaten ontdekt", "not_deconz_bridge": "Dit is geen deCONZ bridge", - "one_instance_only": "Component ondersteunt slechts \u00e9\u00e9n deCONZ instance", + "one_instance_only": "Component ondersteunt slechts \u00e9\u00e9n deCONZ instantie", "updated_instance": "DeCONZ-instantie bijgewerkt met nieuw host-adres" }, "error": { diff --git a/homeassistant/components/deconz/translations/pl.json b/homeassistant/components/deconz/translations/pl.json index 6477dfa9445..a9bff098644 100644 --- a/homeassistant/components/deconz/translations/pl.json +++ b/homeassistant/components/deconz/translations/pl.json @@ -26,14 +26,14 @@ }, "manual_confirm": { "data": { - "host": "[%key_id:common::config_flow::data::host%]", - "port": "[%key_id:common::config_flow::data::port%]" + "host": "Nazwa hosta lub adres IP", + "port": "Port" } }, "manual_input": { "data": { "host": "Nazwa hosta lub adres IP", - "port": "[%key_id:common::config_flow::data::port%]" + "port": "Port" }, "title": "Konfiguracja bramki deCONZ" }, diff --git a/homeassistant/components/devolo_home_control/translations/pl.json b/homeassistant/components/devolo_home_control/translations/pl.json index ef8beb5ff01..8386f5686e0 100644 --- a/homeassistant/components/devolo_home_control/translations/pl.json +++ b/homeassistant/components/devolo_home_control/translations/pl.json @@ -13,8 +13,8 @@ "Mydevolo_URL": "URL mydevolo", "home_control_url": "URL Home Control", "mydevolo_url": "URL mydevolo", - "password": "[%key_id:common::config_flow::data::password%]", - "username": "[%key_id:common::config_flow::data::username%]" + "password": "Has\u0142o", + "username": "Nazwa u\u017cytkownika" }, "description": "Konfiguracja devolo Home Control", "title": "devolo Home Control" diff --git a/homeassistant/components/directv/translations/pl.json b/homeassistant/components/directv/translations/pl.json index f5f657c75ab..b23274ec71e 100644 --- a/homeassistant/components/directv/translations/pl.json +++ b/homeassistant/components/directv/translations/pl.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "Odbiornik DirecTV jest ju\u017c skonfigurowany.", - "unknown": "[%key_id:common::config_flow::error::unknown%]" + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane.", + "unknown": "Nieoczekiwany b\u0142\u0105d." }, "error": { - "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie." + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia." }, "flow_title": "DirecTV: {name}", "step": { @@ -21,7 +21,7 @@ }, "user": { "data": { - "host": "[%key_id:common::config_flow::data::host%]" + "host": "Nazwa hosta lub adres IP" }, "title": "Po\u0142\u0105czenie z odbiornikiem DirecTV" } diff --git a/homeassistant/components/doorbird/translations/ca.json b/homeassistant/components/doorbird/translations/ca.json index efc7e92a78d..e01e31e0f0a 100644 --- a/homeassistant/components/doorbird/translations/ca.json +++ b/homeassistant/components/doorbird/translations/ca.json @@ -14,7 +14,7 @@ "step": { "user": { "data": { - "host": "Amfitri\u00f3 (adre\u00e7a IP)", + "host": "Amfitri\u00f3", "name": "Nom del dispositiu", "password": "[%key::common::config_flow::data::password%]", "username": "[%key::common::config_flow::data::username%]" diff --git a/homeassistant/components/doorbird/translations/pl.json b/homeassistant/components/doorbird/translations/pl.json index 8cd30868567..485388d5ce4 100644 --- a/homeassistant/components/doorbird/translations/pl.json +++ b/homeassistant/components/doorbird/translations/pl.json @@ -6,18 +6,18 @@ "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": "[%key_id:common::config_flow::error::invalid_auth%]", - "unknown": "[%key_id:common::config_flow::error::unknown%]" + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia.", + "invalid_auth": "Niepoprawne uwierzytelnienie.", + "unknown": "Nieoczekiwany b\u0142\u0105d." }, "flow_title": "DoorBird {name} ({host})", "step": { "user": { "data": { - "host": "[%key_id:common::config_flow::data::host%]", + "host": "Nazwa hosta lub adres IP", "name": "Nazwa urz\u0105dzenia", - "password": "[%key_id:common::config_flow::data::password%]", - "username": "[%key_id:common::config_flow::data::username%]" + "password": "Has\u0142o", + "username": "Nazwa u\u017cytkownika" }, "title": "Po\u0142\u0105czenie z DoorBird" } diff --git a/homeassistant/components/ecobee/translations/pl.json b/homeassistant/components/ecobee/translations/pl.json index 240acabd093..c0fed1d075c 100644 --- a/homeassistant/components/ecobee/translations/pl.json +++ b/homeassistant/components/ecobee/translations/pl.json @@ -14,10 +14,10 @@ }, "user": { "data": { - "api_key": "[%key_id:common::config_flow::data::api_key%]" + "api_key": "Klucz API" }, "description": "Prosz\u0119 wprowadzi\u0107 klucz API uzyskany na ecobee.com.", - "title": "[%key_id:common::config_flow::data::api_key%]" + "title": "Klucz API" } } } diff --git a/homeassistant/components/elgato/translations/ca.json b/homeassistant/components/elgato/translations/ca.json index 0a15bb12573..b6b53986d36 100644 --- a/homeassistant/components/elgato/translations/ca.json +++ b/homeassistant/components/elgato/translations/ca.json @@ -11,8 +11,8 @@ "step": { "user": { "data": { - "host": "Amfitri\u00f3 o adre\u00e7a IP", - "port": "N\u00famero de port" + "host": "Amfitri\u00f3", + "port": "Port" }, "description": "Configura l'Elgato Key Light per integrar-lo amb Home Assistant.", "title": "Enlla\u00e7 amb Elgato Key Light" diff --git a/homeassistant/components/elgato/translations/pl.json b/homeassistant/components/elgato/translations/pl.json index 769495ee949..3e3aa41a0ae 100644 --- a/homeassistant/components/elgato/translations/pl.json +++ b/homeassistant/components/elgato/translations/pl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "[%key_id:common::config_flow::abort::already_configured_device%]", + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane.", "connection_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia z urz\u0105dzeniem Elgato Key Light." }, "error": { @@ -11,8 +11,8 @@ "step": { "user": { "data": { - "host": "[%key_id:common::config_flow::data::host%]", - "port": "[%key_id:common::config_flow::data::port%]" + "host": "Nazwa hosta lub adres IP", + "port": "Port" }, "description": "Konfiguracja Elgato Key Light w celu integracji z Home Assistant'em.", "title": "Po\u0142\u0105cz swoje Elgato Key Light" diff --git a/homeassistant/components/elkm1/translations/ca.json b/homeassistant/components/elkm1/translations/ca.json index 73ca29cdca0..fd69a52d16d 100644 --- a/homeassistant/components/elkm1/translations/ca.json +++ b/homeassistant/components/elkm1/translations/ca.json @@ -13,11 +13,11 @@ "user": { "data": { "address": "Adre\u00e7a IP, domini o port s\u00e8rie (si es est\u00e0 connectat amb una connexi\u00f3 s\u00e8rie).", - "password": "Contrasenya (nom\u00e9s segur).", + "password": "Contrasenya", "prefix": "Prefix \u00fanic (deixa-ho en blanc si nom\u00e9s tens un \u00fanic controlador Elk-M1).", "protocol": "Protocol", "temperature_unit": "Unitats de temperatura que utilitza l'Elk-M1.", - "username": "Nom d'usuari (nom\u00e9s segur)." + "username": "Nom d'usuari" }, "description": "La cadena de car\u00e0cters (string) de l'adre\u00e7a ha de tenir el format: 'adre\u00e7a[:port]' tant per al mode 'segur' com el 'no segur'. Exemple: '192.168.1.1'. El port \u00e9s opcional, per defecte \u00e9s el 2101 pel mode 'no segur' i el 2601 pel 'segur'. Per al protocol s\u00e8rie, l'adre\u00e7a ha de tenir el format 'tty[:baud]'. Exemple: '/dev/ttyS1'. La velocitat en bauds \u00e9s opcional (115200 per defecte).", "title": "Connexi\u00f3 amb el controlador Elk-M1" diff --git a/homeassistant/components/elkm1/translations/pl.json b/homeassistant/components/elkm1/translations/pl.json index f630376a374..b38c9aaa6d2 100644 --- a/homeassistant/components/elkm1/translations/pl.json +++ b/homeassistant/components/elkm1/translations/pl.json @@ -6,18 +6,18 @@ }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie.", - "invalid_auth": "[%key_id:common::config_flow::error::invalid_auth%]", - "unknown": "[%key_id:common::config_flow::error::unknown%]" + "invalid_auth": "Niepoprawne uwierzytelnienie.", + "unknown": "Nieoczekiwany b\u0142\u0105d." }, "step": { "user": { "data": { "address": "Adres IP, domena lub port szeregowy w przypadku po\u0142\u0105czenia szeregowego.", - "password": "[%key_id:common::config_flow::data::password%] (tylko bezpieczne)", + "password": "Has\u0142o", "prefix": "Unikatowy prefiks (pozostaw pusty, je\u015bli masz tylko jeden ElkM1).", "protocol": "Protok\u00f3\u0142", "temperature_unit": "Jednostka temperatury u\u017cywanej przez ElkM1.", - "username": "[%key_id:common::config_flow::data::username%] (tylko bezpieczne)" + "username": "Nazwa u\u017cytkownika" }, "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" diff --git a/homeassistant/components/emulated_roku/translations/pl.json b/homeassistant/components/emulated_roku/translations/pl.json index 00625d2097c..6ce1c17e5d8 100644 --- a/homeassistant/components/emulated_roku/translations/pl.json +++ b/homeassistant/components/emulated_roku/translations/pl.json @@ -7,9 +7,9 @@ "user": { "data": { "advertise_ip": "IP rozg\u0142aszania", - "advertise_port": "[%key_id:common::config_flow::data::port%] rozg\u0142aszania", + "advertise_port": "Port rozg\u0142aszania", "host_ip": "Adres IP", - "listen_port": "[%key_id:common::config_flow::data::port%] nas\u0142uchu", + "listen_port": "Port nas\u0142uchu", "name": "Nazwa", "upnp_bind_multicast": "Powi\u0105\u017c multicast (prawda/fa\u0142sz)" }, diff --git a/homeassistant/components/esphome/translations/ca.json b/homeassistant/components/esphome/translations/ca.json index 9f9378081dc..828ac08c07b 100644 --- a/homeassistant/components/esphome/translations/ca.json +++ b/homeassistant/components/esphome/translations/ca.json @@ -28,7 +28,7 @@ "port": "Port" }, "description": "Introdueix la informaci\u00f3 de connexi\u00f3 del teu node [ESPHome](https://esphomelib.com/).", - "title": "[%key:component::esphome::title%]" + "title": "ESPHome" } } } diff --git a/homeassistant/components/esphome/translations/pl.json b/homeassistant/components/esphome/translations/pl.json index dedde4f1ad8..6ac0409a1dd 100644 --- a/homeassistant/components/esphome/translations/pl.json +++ b/homeassistant/components/esphome/translations/pl.json @@ -13,7 +13,7 @@ "step": { "authenticate": { "data": { - "password": "[%key_id:common::config_flow::data::password%]" + "password": "Has\u0142o" }, "description": "Wprowad\u017a has\u0142o ustawione w konfiguracji dla {name}.", "title": "Wprowad\u017a has\u0142o" @@ -24,8 +24,8 @@ }, "user": { "data": { - "host": "[%key_id:common::config_flow::data::host%]", - "port": "[%key_id:common::config_flow::data::port%]" + "host": "Nazwa hosta lub adres IP", + "port": "Port" }, "description": "Wprowad\u017a ustawienia po\u0142\u0105czenia [ESPHome](https://esphomelib.com/) w\u0119z\u0142a.", "title": "ESPHome" diff --git a/homeassistant/components/fan/translations/ca.json b/homeassistant/components/fan/translations/ca.json index 2c9ea5f4e70..c7d668ebc0b 100644 --- a/homeassistant/components/fan/translations/ca.json +++ b/homeassistant/components/fan/translations/ca.json @@ -15,8 +15,8 @@ }, "state": { "_": { - "off": "Apagat", - "on": "Enc\u00e8s" + "off": "OFF", + "on": "ON" } }, "title": "Ventiladors" diff --git a/homeassistant/components/flick_electric/translations/pl.json b/homeassistant/components/flick_electric/translations/pl.json index ee7f934e787..716af7ba96d 100644 --- a/homeassistant/components/flick_electric/translations/pl.json +++ b/homeassistant/components/flick_electric/translations/pl.json @@ -1,17 +1,17 @@ { "config": { "abort": { - "already_configured": "[%key_id:common::config_flow::abort::already_configured_account%]" + "already_configured": "Konto jest ju\u017c skonfigurowane." }, "error": { - "invalid_auth": "[%key_id:common::config_flow::error::invalid_auth%]", - "unknown": "[%key_id:common::config_flow::error::unknown%]" + "invalid_auth": "Niepoprawne uwierzytelnienie.", + "unknown": "Nieoczekiwany b\u0142\u0105d." }, "step": { "user": { "data": { - "password": "[%key_id:common::config_flow::data::password%]", - "username": "[%key_id:common::config_flow::data::username%]" + "password": "Has\u0142o", + "username": "Nazwa u\u017cytkownika" } } } diff --git a/homeassistant/components/flume/translations/pl.json b/homeassistant/components/flume/translations/pl.json index 1b10ad0b8a0..6fe75f62ef0 100644 --- a/homeassistant/components/flume/translations/pl.json +++ b/homeassistant/components/flume/translations/pl.json @@ -1,20 +1,20 @@ { "config": { "abort": { - "already_configured": "[%key_id:common::config_flow::abort::already_configured_account%]" + "already_configured": "Konto jest ju\u017c skonfigurowane." }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie.", - "invalid_auth": "[%key_id:common::config_flow::error::invalid_auth%]", - "unknown": "[%key_id:common::config_flow::error::unknown%]" + "invalid_auth": "Niepoprawne uwierzytelnienie.", + "unknown": "Nieoczekiwany b\u0142\u0105d." }, "step": { "user": { "data": { "client_id": "Identyfikator klienta", "client_secret": "Has\u0142o klienta", - "password": "[%key_id:common::config_flow::data::password%]", - "username": "[%key_id:common::config_flow::data::username%]" + "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" diff --git a/homeassistant/components/flunearyou/translations/pl.json b/homeassistant/components/flunearyou/translations/pl.json index 381798ed4ed..de344b82d00 100644 --- a/homeassistant/components/flunearyou/translations/pl.json +++ b/homeassistant/components/flunearyou/translations/pl.json @@ -4,7 +4,7 @@ "already_configured": "Wsp\u00f3\u0142rz\u0119dne s\u0105 ju\u017c zarejestrowane." }, "error": { - "general_error": "[%key_id:common::config_flow::error::unknown%]" + "general_error": "Nieoczekiwany b\u0142\u0105d." }, "step": { "user": { diff --git a/homeassistant/components/forked_daapd/translations/ca.json b/homeassistant/components/forked_daapd/translations/ca.json new file mode 100644 index 00000000000..5c299cbd808 --- /dev/null +++ b/homeassistant/components/forked_daapd/translations/ca.json @@ -0,0 +1,38 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositiu ja est\u00e0 configurat.", + "not_forked_daapd": "El dispositiu no \u00e9s un servidor de forked-daapd." + }, + "error": { + "unknown_error": "Error desconegut.", + "websocket_not_enabled": "El websocket de forked-daapd no est\u00e0 activat.", + "wrong_host_or_port": "No s'ha pogut connectar, verifica l'amfitri\u00f3 i el port.", + "wrong_password": "Contrasenya incorrecta." + }, + "flow_title": "Servidor forked-daapd: {name} ({host})", + "step": { + "user": { + "data": { + "host": "Amfitri\u00f3", + "password": "Contrasenya de l'API (deixa-ho en blanc si no t\u00e9 contrasenya)", + "port": "Port de l'API" + }, + "title": "Configuraci\u00f3 del dispositiu forked-daapd" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "max_playlists": "Nombre m\u00e0xim de llistes de reproducci\u00f3 utilitzades com a fonts", + "tts_pause_time": "Segons de pausa abans i despr\u00e9s de TTS", + "tts_volume": "Volum TTS (valor 'float' entre [0,1])" + }, + "description": "Configura les diferents opcions de la integraci\u00f3 forked-daapd.", + "title": "Configuraci\u00f3 de les opcions de forked-daapd" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/forked_daapd/translations/es.json b/homeassistant/components/forked_daapd/translations/es.json new file mode 100644 index 00000000000..5cc63d67e1f --- /dev/null +++ b/homeassistant/components/forked_daapd/translations/es.json @@ -0,0 +1,31 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositivo ya est\u00e1 configurado." + }, + "error": { + "unknown_error": "Error desconocido.", + "wrong_password": "Contrase\u00f1a incorrecta." + }, + "step": { + "user": { + "data": { + "host": "Host", + "name": "Nombre amigable", + "password": "Contrase\u00f1a API (dejar en blanco si no hay contrase\u00f1a)", + "port": "Puerto API" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "tts_pause_time": "Segundos para pausar antes y despu\u00e9s del TTS", + "tts_volume": "Volumen TTS (decimal en el rango [0,1])" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/forked_daapd/translations/ko.json b/homeassistant/components/forked_daapd/translations/ko.json new file mode 100644 index 00000000000..5522eda3a76 --- /dev/null +++ b/homeassistant/components/forked_daapd/translations/ko.json @@ -0,0 +1,41 @@ +{ + "config": { + "abort": { + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "not_forked_daapd": "\uae30\uae30\uac00 forked-daapd \uc11c\ubc84\uac00 \uc544\ub2d9\ub2c8\ub2e4." + }, + "error": { + "unknown_error": "\uc54c \uc218 \uc5c6\ub294 \uc624\ub958", + "websocket_not_enabled": "forked-daapd \uc11c\ubc84 \uc6f9\uc18c\ucf13\uc774 \ube44\ud65c\uc131\ud654 \ub418\uc5b4\uc788\uc2b5\ub2c8\ub2e4.", + "wrong_host_or_port": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \ud638\uc2a4\ud2b8\uc640 \ud3ec\ud2b8\ub97c \ud655\uc778\ud574\uc8fc\uc138\uc694.", + "wrong_password": "\ube44\ubc00\ubc88\ud638\uac00 \uc77c\uce58\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4.", + "wrong_server_type": "forked-daapd \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub294 forked-daapd \uc11c\ubc84 \ubc84\uc804 27.0 \uc774\uc0c1\uc774 \ud544\uc694\ud569\ub2c8\ub2e4." + }, + "flow_title": "forked-daapd \uc11c\ubc84: {name} ({host})", + "step": { + "user": { + "data": { + "host": "\ud638\uc2a4\ud2b8", + "name": "\uce5c\uc219\ud55c \uc774\ub984", + "password": "API \ube44\ubc00\ubc88\ud638 (\ube44\ubc00\ubc88\ud638\uac00 \uc5c6\uc73c\uba74 \ube44\uc6cc\ub450\uc138\uc694)", + "port": "API \ud3ec\ud2b8" + }, + "title": "forked-daapd \uae30\uae30 \uc124\uc815\ud558\uae30" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "librespot_java_port": "librespot-java \ud30c\uc774\ud504 \ucee8\ud2b8\ub864\uc6a9 \ud3ec\ud2b8 (\uc0ac\uc6a9\ud558\ub294 \uacbd\uc6b0)", + "max_playlists": "\uc18c\uc2a4\ub85c \uc0ac\uc6a9\ub41c \ucd5c\ub300 \uc7ac\uc0dd \ubaa9\ub85d \uc218", + "tts_pause_time": "TTS \uc804\ud6c4\uc5d0 \uc77c\uc2dc\uc911\uc9c0\ud560 \uc2dc\uac04(\ucd08)", + "tts_volume": "TTS \ubcfc\ub968 (0~1 \uc758 \uc2e4\uc218\uac12)" + }, + "description": "forked-daapd \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\uc5d0 \ub300\ud55c \ub2e4\uc591\ud55c \uc635\uc158\uc744 \uc124\uc815\ud574\uc8fc\uc138\uc694.", + "title": "forked-daapd \uc635\uc158 \uc124\uc815\ud558\uae30" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/forked_daapd/translations/lb.json b/homeassistant/components/forked_daapd/translations/lb.json new file mode 100644 index 00000000000..33ddf13e605 --- /dev/null +++ b/homeassistant/components/forked_daapd/translations/lb.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Apparat ass scho konfigur\u00e9iert" + }, + "error": { + "unknown_error": "Onbekannten Feeler.", + "wrong_host_or_port": "Feeler beim verbannen, iwwerpr\u00e9if w.e.g d'Adresse a Port.", + "wrong_password": "Ong\u00ebltegt Passwuert." + }, + "step": { + "user": { + "data": { + "host": "Apparat", + "name": "Numm", + "password": "API Passwuert (eidel loosse fir kee Passwuert)", + "port": "API Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/forked_daapd/translations/no.json b/homeassistant/components/forked_daapd/translations/no.json new file mode 100644 index 00000000000..2bf6b1e2324 --- /dev/null +++ b/homeassistant/components/forked_daapd/translations/no.json @@ -0,0 +1,41 @@ +{ + "config": { + "abort": { + "already_configured": "Enheten er allerede konfigurert.", + "not_forked_daapd": "Enheten er ikke en forked-daapd-server." + }, + "error": { + "unknown_error": "Ukjent feil.", + "websocket_not_enabled": "websocket for forked-daapd server ikke aktivert.", + "wrong_host_or_port": "Kan ikke koble til. Vennligst sjekk vert og port.", + "wrong_password": "Feil passord.", + "wrong_server_type": "Ikke en forked-daapd-server." + }, + "flow_title": "forked-daapd-server: {name} ( {host} )", + "step": { + "user": { + "data": { + "host": "Vert", + "name": "Vennlig navn", + "password": "API-passord (la st\u00e5 tomt hvis ingen passord)", + "port": "API-port" + }, + "title": "Konfigurere forked-daapd-enhet" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "librespot_java_port": "Port for librespot-java pipe control (hvis brukt)", + "max_playlists": "Maks antall spillelister brukt som kilder", + "tts_pause_time": "Sekunder for \u00e5 sette pause f\u00f8r og etter TTS", + "tts_volume": "TTS-volum (flyter i omr\u00e5det [0,1])" + }, + "description": "Angi ulike alternativer for forked-daapd integrasjon.", + "title": "Konfigurer alternativer for forked-daapd" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/forked_daapd/translations/pl.json b/homeassistant/components/forked_daapd/translations/pl.json new file mode 100644 index 00000000000..f0434c4794b --- /dev/null +++ b/homeassistant/components/forked_daapd/translations/pl.json @@ -0,0 +1,13 @@ +{ + "config": { + "step": { + "user": { + "data": { + "host": "Nazwa hosta lub adres IP", + "password": "Has\u0142o API (pozostaw puste, je\u015bli nie ma has\u0142a)", + "port": "Port API" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/forked_daapd/translations/ru.json b/homeassistant/components/forked_daapd/translations/ru.json index 448eff8244a..3774688e68a 100644 --- a/homeassistant/components/forked_daapd/translations/ru.json +++ b/homeassistant/components/forked_daapd/translations/ru.json @@ -20,7 +20,7 @@ "password": "\u041f\u0430\u0440\u043e\u043b\u044c API (\u043e\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u043f\u0443\u0441\u0442\u044b\u043c, \u0435\u0441\u043b\u0438 \u043d\u0435\u0442 \u043f\u0430\u0440\u043e\u043b\u044f)", "port": "\u041f\u043e\u0440\u0442 API" }, - "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 forked-daapd" + "title": "forked-daapd" } } }, diff --git a/homeassistant/components/freebox/translations/pl.json b/homeassistant/components/freebox/translations/pl.json index 770194e338e..64564886882 100644 --- a/homeassistant/components/freebox/translations/pl.json +++ b/homeassistant/components/freebox/translations/pl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "[%key_id:common::config_flow::data::host%]" + "already_configured": "Nazwa hosta lub adres IP" }, "error": { "connection_failed": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie.", @@ -15,8 +15,8 @@ }, "user": { "data": { - "host": "[%key_id:common::config_flow::data::host%]", - "port": "[%key_id:common::config_flow::data::port%]" + "host": "Nazwa hosta lub adres IP", + "port": "Port" }, "title": "Freebox" } diff --git a/homeassistant/components/fritzbox/translations/ca.json b/homeassistant/components/fritzbox/translations/ca.json index 70c2173c641..1951e35a923 100644 --- a/homeassistant/components/fritzbox/translations/ca.json +++ b/homeassistant/components/fritzbox/translations/ca.json @@ -21,7 +21,7 @@ }, "user": { "data": { - "host": "Amfitri\u00f3 o adre\u00e7a IP", + "host": "Amfitri\u00f3", "password": "Contrasenya", "username": "Nom d'usuari" }, diff --git a/homeassistant/components/fritzbox/translations/pl.json b/homeassistant/components/fritzbox/translations/pl.json index be545f40b1a..923f4ba2186 100644 --- a/homeassistant/components/fritzbox/translations/pl.json +++ b/homeassistant/components/fritzbox/translations/pl.json @@ -13,17 +13,17 @@ "step": { "confirm": { "data": { - "password": "[%key_id:common::config_flow::data::password%]", - "username": "[%key_id:common::config_flow::data::username%]" + "password": "Has\u0142o", + "username": "Nazwa u\u017cytkownika" }, "description": "Czy chcesz skonfigurowa\u0107 {name}?", "title": "AVM FRITZ! Box" }, "user": { "data": { - "host": "[%key_id:common::config_flow::data::host%]", - "password": "[%key_id:common::config_flow::data::password%]", - "username": "[%key_id:common::config_flow::data::username%]" + "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" diff --git a/homeassistant/components/garmin_connect/translations/pl.json b/homeassistant/components/garmin_connect/translations/pl.json index 2ecd1114799..982c7b2c50b 100644 --- a/homeassistant/components/garmin_connect/translations/pl.json +++ b/homeassistant/components/garmin_connect/translations/pl.json @@ -1,19 +1,19 @@ { "config": { "abort": { - "already_configured": "To konto jest ju\u017c skonfigurowane." + "already_configured": "Konto jest ju\u017c skonfigurowane." }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie.", - "invalid_auth": "[%key_id:common::config_flow::error::invalid_auth%]", + "invalid_auth": "Niepoprawne uwierzytelnienie.", "too_many_requests": "Zbyt wiele \u017c\u0105da\u0144, spr\u00f3buj ponownie p\u00f3\u017aniej.", - "unknown": "[%key_id:common::config_flow::error::unknown%]" + "unknown": "Nieoczekiwany b\u0142\u0105d." }, "step": { "user": { "data": { - "password": "[%key_id:common::config_flow::data::password%]", - "username": "[%key_id:common::config_flow::data::username%]" + "password": "Has\u0142o", + "username": "Nazwa u\u017cytkownika" }, "description": "Wprowad\u017a dane uwierzytelniaj\u0105ce", "title": "Garmin Connect" diff --git a/homeassistant/components/glances/translations/ko.json b/homeassistant/components/glances/translations/ko.json index 2cf0aa1d595..336c9f3b3e5 100644 --- a/homeassistant/components/glances/translations/ko.json +++ b/homeassistant/components/glances/translations/ko.json @@ -29,7 +29,7 @@ "data": { "scan_interval": "\uc5c5\ub370\uc774\ud2b8 \ube48\ub3c4" }, - "description": "Glances \uc635\uc158 \uad6c\uc131" + "description": "Glances \uc635\uc158 \uc124\uc815\ud558\uae30" } } } diff --git a/homeassistant/components/glances/translations/pl.json b/homeassistant/components/glances/translations/pl.json index 2f36613b360..25179e951ad 100644 --- a/homeassistant/components/glances/translations/pl.json +++ b/homeassistant/components/glances/translations/pl.json @@ -10,12 +10,12 @@ "step": { "user": { "data": { - "host": "[%key_id:common::config_flow::data::host%]", + "host": "Nazwa hosta lub adres IP", "name": "Nazwa", - "password": "[%key_id:common::config_flow::data::password%]", - "port": "[%key_id:common::config_flow::data::port%]", + "password": "Has\u0142o", + "port": "Port", "ssl": "U\u017cyj SSL/TLS, aby po\u0142\u0105czy\u0107 si\u0119 z systemem Glances", - "username": "[%key_id:common::config_flow::data::username%]", + "username": "Nazwa u\u017cytkownika", "verify_ssl": "Sprawd\u017a certyfikacj\u0119 systemu", "version": "Glances wersja API (2 lub 3)" }, diff --git a/homeassistant/components/griddy/translations/pl.json b/homeassistant/components/griddy/translations/pl.json index e62ce8f7bdc..e28b4b6f2e6 100644 --- a/homeassistant/components/griddy/translations/pl.json +++ b/homeassistant/components/griddy/translations/pl.json @@ -5,7 +5,7 @@ }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie.", - "unknown": "[%key_id:common::config_flow::error::unknown%]" + "unknown": "Nieoczekiwany b\u0142\u0105d." }, "step": { "user": { diff --git a/homeassistant/components/group/translations/ca.json b/homeassistant/components/group/translations/ca.json index bbbd84b2147..ec1116f8759 100644 --- a/homeassistant/components/group/translations/ca.json +++ b/homeassistant/components/group/translations/ca.json @@ -1,17 +1,17 @@ { "state": { "_": { - "closed": "Tancat", + "closed": "Tancat/da", "home": "A casa", "locked": "Bloquejat", "not_home": "Fora", - "off": "Desactivat", + "off": "OFF", "ok": "Correcte", - "on": "Activat", - "open": "Obert", + "on": "ON", + "open": "Obert/a", "problem": "Problema", "unlocked": "Desbloquejat" } }, - "title": "Grups" + "title": "Grup" } \ No newline at end of file diff --git a/homeassistant/components/hangouts/translations/ca.json b/homeassistant/components/hangouts/translations/ca.json index a186723f345..7daa1b4f671 100644 --- a/homeassistant/components/hangouts/translations/ca.json +++ b/homeassistant/components/hangouts/translations/ca.json @@ -14,7 +14,7 @@ "data": { "2fa": "Pin 2FA" }, - "description": "buit", + "description": "Buit", "title": "Verificaci\u00f3 en dos passos" }, "user": { @@ -23,7 +23,7 @@ "email": "Correu electr\u00f2nic", "password": "Contrasenya" }, - "description": "buit", + "description": "Buit", "title": "Inici de sessi\u00f3 de Google Hangouts" } } diff --git a/homeassistant/components/hangouts/translations/pl.json b/homeassistant/components/hangouts/translations/pl.json index f4a1d0a0fdd..69c3020bbfb 100644 --- a/homeassistant/components/hangouts/translations/pl.json +++ b/homeassistant/components/hangouts/translations/pl.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Google Hangouts jest ju\u017c skonfigurowany.", - "unknown": "[%key_id:common::config_flow::error::unknown%]" + "unknown": "Nieoczekiwany b\u0142\u0105d." }, "error": { "invalid_2fa": "Nieprawid\u0142owe uwierzytelnienie dwusk\u0142adnikowe, spr\u00f3buj ponownie.", @@ -20,8 +20,8 @@ "user": { "data": { "authorization_code": "Kod autoryzacji (wymagany do r\u0119cznego uwierzytelnienia)", - "email": "[%key_id:common::config_flow::data::email%]", - "password": "[%key_id:common::config_flow::data::password%]" + "email": "Adres e-mail", + "password": "Has\u0142o" }, "description": "Pusty", "title": "Logowanie do Google Hangouts" diff --git a/homeassistant/components/harmony/translations/ca.json b/homeassistant/components/harmony/translations/ca.json index 90d8f064301..5bb279c0482 100644 --- a/homeassistant/components/harmony/translations/ca.json +++ b/homeassistant/components/harmony/translations/ca.json @@ -15,7 +15,7 @@ }, "user": { "data": { - "host": "Nom de l'amfitri\u00f3 o adre\u00e7a IP", + "host": "Amfitri\u00f3", "name": "Nom del Hub" }, "title": "Configuraci\u00f3 de Logitech Harmony Hub" diff --git a/homeassistant/components/harmony/translations/pl.json b/homeassistant/components/harmony/translations/pl.json index a1d7ecfeca8..12bbcfaca18 100644 --- a/homeassistant/components/harmony/translations/pl.json +++ b/homeassistant/components/harmony/translations/pl.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "[%key_id:common::config_flow::abort::already_configured_device%]" + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane." }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie.", - "unknown": "[%key_id:common::config_flow::error::unknown%]" + "unknown": "Nieoczekiwany b\u0142\u0105d." }, "flow_title": "Logitech Harmony Hub {name}", "step": { @@ -15,7 +15,7 @@ }, "user": { "data": { - "host": "[%key_id:common::config_flow::data::host%]", + "host": "Nazwa hosta lub adres IP", "name": "Nazwa huba" }, "title": "Konfiguracja Logitech Harmony Hub" diff --git a/homeassistant/components/heos/translations/pl.json b/homeassistant/components/heos/translations/pl.json index a421e92dd8a..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": "[%key_id:common::config_flow::data::host%]", - "host": "[%key_id:common::config_flow::data::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/home_connect/translations/pl.json b/homeassistant/components/home_connect/translations/pl.json index 08e4860453a..054fe071e74 100644 --- a/homeassistant/components/home_connect/translations/pl.json +++ b/homeassistant/components/home_connect/translations/pl.json @@ -1,14 +1,14 @@ { "config": { "abort": { - "missing_configuration": "[%key_id:common::config_flow::abort::oauth2_missing_configuration%]" + "missing_configuration": "Komponent nie jest skonfigurowany. Post\u0119puj zgodnie z dokumentacj\u0105." }, "create_entry": { "default": "Pomy\u015blnie uwierzytelniono z Home Connect." }, "step": { "pick_implementation": { - "title": "[%key_id:common::config_flow::title::oauth2_pick_implementation%]" + "title": "Wybierz metod\u0119 uwierzytelniania" } } } diff --git a/homeassistant/components/homekit/translations/ru.json b/homeassistant/components/homekit/translations/ru.json index efec33bd187..dca8c10b8de 100644 --- a/homeassistant/components/homekit/translations/ru.json +++ b/homeassistant/components/homekit/translations/ru.json @@ -13,8 +13,8 @@ "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" + "description": "HomeKit Bridge \u043f\u043e\u0437\u0432\u043e\u043b\u0438\u0442 \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u043e\u0431\u044a\u0435\u043a\u0442\u0430\u043c Home Assistant \u0447\u0435\u0440\u0435\u0437 HomeKit. HomeKit Bridge \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d 150 \u0430\u043a\u0441\u0435\u0441\u0441\u0443\u0430\u0440\u0430\u043c\u0438 \u043d\u0430 \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440, \u0432\u043a\u043b\u044e\u0447\u0430\u044f \u0441\u0430\u043c \u0431\u0440\u0438\u0434\u0436. \u0415\u0441\u043b\u0438 \u0412\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0431\u043e\u043b\u044c\u0448\u0435, \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u0442\u0441\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e HomeKit Bridge \u0434\u043b\u044f \u0440\u0430\u0437\u043d\u044b\u0445 \u0434\u043e\u043c\u0435\u043d\u043e\u0432. \u0414\u0435\u0442\u0430\u043b\u044c\u043d\u0430\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043e\u0431\u044a\u0435\u043a\u0442\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0430 \u0442\u043e\u043b\u044c\u043a\u043e \u0447\u0435\u0440\u0435\u0437 YAML \u0434\u043b\u044f \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0433\u043e \u0431\u0440\u0438\u0434\u0436\u0430.", + "title": "HomeKit Bridge" } } }, diff --git a/homeassistant/components/homematicip_cloud/translations/pl.json b/homeassistant/components/homematicip_cloud/translations/pl.json index 2229348efa1..33049a0a0a9 100644 --- a/homeassistant/components/homematicip_cloud/translations/pl.json +++ b/homeassistant/components/homematicip_cloud/translations/pl.json @@ -3,7 +3,7 @@ "abort": { "already_configured": "Punkt dost\u0119pu jest ju\u017c skonfigurowany.", "connection_aborted": "Nie mo\u017cna po\u0142\u0105czy\u0107 si\u0119 z serwerem HMIP", - "unknown": "[%key_id:common::config_flow::error::unknown%]" + "unknown": "Nieoczekiwany b\u0142\u0105d." }, "error": { "invalid_pin": "Nieprawid\u0142owy kod PIN, spr\u00f3buj ponownie.", diff --git a/homeassistant/components/huawei_lte/translations/pl.json b/homeassistant/components/huawei_lte/translations/pl.json index 57768de8fc1..86e6e4e5853 100644 --- a/homeassistant/components/huawei_lte/translations/pl.json +++ b/homeassistant/components/huawei_lte/translations/pl.json @@ -19,9 +19,9 @@ "step": { "user": { "data": { - "password": "[%key_id:common::config_flow::data::password%]", + "password": "Has\u0142o", "url": "URL", - "username": "[%key_id:common::config_flow::data::username%]" + "username": "Nazwa u\u017cytkownika" }, "description": "Wprowad\u017a szczeg\u00f3\u0142y dost\u0119pu do urz\u0105dzenia. Okre\u015blenie nazwy u\u017cytkownika i has\u0142a jest opcjonalne, ale umo\u017cliwia obs\u0142ug\u0119 wi\u0119kszej liczby funkcji integracji. Z drugiej strony u\u017cycie autoryzowanego po\u0142\u0105czenia mo\u017ce powodowa\u0107 problemy z dost\u0119pem do interfejsu internetowego urz\u0105dzenia z zewn\u0105trz Home Assistant'a gdy integracja jest aktywna.", "title": "Konfiguracja Huawei LTE" diff --git a/homeassistant/components/hue/translations/ko.json b/homeassistant/components/hue/translations/ko.json index 62a466565d9..3f4b3fad8d0 100644 --- a/homeassistant/components/hue/translations/ko.json +++ b/homeassistant/components/hue/translations/ko.json @@ -44,8 +44,8 @@ "remote_button_long_release": "\"{subtype}\" \ubc84\ud2bc\uc774 \uae38\uac8c \ub20c\ub838\ub2e4\uac00 \uc190\uc744 \ub5c4 \ub54c", "remote_button_short_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \ub20c\ub9b4 \ub54c", "remote_button_short_release": "\"{subtype}\" \ubc84\ud2bc\uc5d0\uc11c \uc190\uc744 \ub5c4 \ub54c", - "remote_double_button_long_press": "\"{subtype}\" \ubaa8\ub450 \uae38\uac8c \ub20c\ub838\ub2e4\uac00 \uc190\uc744 \ub5c4 \ub54c", - "remote_double_button_short_press": "\"{subtype}\" \ubaa8\ub450 \uc190\uc744 \ub5c4 \ub54c" + "remote_double_button_long_press": "\"{subtype}\"\uc5d0\uc11c \ubaa8\ub450 \uae38\uac8c \ub20c\ub838\ub2e4\uac00 \uc190\uc744 \ub5c4 \ub54c", + "remote_double_button_short_press": "\"{subtype}\"\uc5d0\uc11c \ubaa8\ub450 \uc190\uc744 \ub5c4 \ub54c" } } } \ No newline at end of file diff --git a/homeassistant/components/hue/translations/pl.json b/homeassistant/components/hue/translations/pl.json index acd2161a024..c38f66ac005 100644 --- a/homeassistant/components/hue/translations/pl.json +++ b/homeassistant/components/hue/translations/pl.json @@ -8,7 +8,7 @@ "discover_timeout": "Nie mo\u017cna wykry\u0107 \u017cadnych mostk\u00f3w Hue", "no_bridges": "Nie wykryto \u017cadnych mostk\u00f3w Hue", "not_hue_bridge": "To nie jest mostek Hue", - "unknown": "[%key_id:common::config_flow::error::unknown%]" + "unknown": "Nieoczekiwany b\u0142\u0105d." }, "error": { "linking": "Wyst\u0105pi\u0142 nieznany b\u0142\u0105d w trakcie \u0142\u0105czenia.", @@ -17,7 +17,7 @@ "step": { "init": { "data": { - "host": "[%key_id:common::config_flow::data::host%]" + "host": "Nazwa hosta lub adres IP" }, "title": "Wybierz mostek Hue" }, diff --git a/homeassistant/components/hunterdouglas_powerview/translations/pl.json b/homeassistant/components/hunterdouglas_powerview/translations/pl.json index 774007fc86e..cad41869ced 100644 --- a/homeassistant/components/hunterdouglas_powerview/translations/pl.json +++ b/homeassistant/components/hunterdouglas_powerview/translations/pl.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "[%key_id:common::config_flow::abort::already_configured_device%]" + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane." }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie.", - "unknown": "[%key_id:common::config_flow::error::unknown%]" + "unknown": "Nieoczekiwany b\u0142\u0105d." }, "step": { "link": { diff --git a/homeassistant/components/iaqualink/translations/ca.json b/homeassistant/components/iaqualink/translations/ca.json index b86dd4358b0..196f4970f93 100644 --- a/homeassistant/components/iaqualink/translations/ca.json +++ b/homeassistant/components/iaqualink/translations/ca.json @@ -10,7 +10,7 @@ "user": { "data": { "password": "Contrasenya", - "username": "Nom d'usuari / Correu electr\u00f2nic" + "username": "Nom d'usuari" }, "description": "Introdueix el nom d'usuari i la contrasenya del teu compte d'iAqualink.", "title": "Connexi\u00f3 amb iAqualink" diff --git a/homeassistant/components/iaqualink/translations/pl.json b/homeassistant/components/iaqualink/translations/pl.json index a247cadf1a4..e31922a404d 100644 --- a/homeassistant/components/iaqualink/translations/pl.json +++ b/homeassistant/components/iaqualink/translations/pl.json @@ -9,8 +9,8 @@ "step": { "user": { "data": { - "password": "[%key_id:common::config_flow::data::password%]", - "username": "[%key_id:common::config_flow::data::username%]/adres e-mail" + "password": "Has\u0142o", + "username": "Nazwa u\u017cytkownika" }, "description": "Wprowad\u017a nazw\u0119 u\u017cytkownika i has\u0142o do konta iAqualink.", "title": "Po\u0142\u0105cz z iAqualink" diff --git a/homeassistant/components/icloud/translations/es.json b/homeassistant/components/icloud/translations/es.json index c2cbaf175f9..49bd9d612eb 100644 --- a/homeassistant/components/icloud/translations/es.json +++ b/homeassistant/components/icloud/translations/es.json @@ -20,6 +20,7 @@ "user": { "data": { "password": "Contrase\u00f1a", + "username": "Correo electr\u00f3nico", "with_family": "Con la familia" }, "description": "Ingrese sus credenciales", diff --git a/homeassistant/components/icloud/translations/pl.json b/homeassistant/components/icloud/translations/pl.json index 0bccf920554..20e3f8c2fb4 100644 --- a/homeassistant/components/icloud/translations/pl.json +++ b/homeassistant/components/icloud/translations/pl.json @@ -19,8 +19,8 @@ }, "user": { "data": { - "password": "[%key_id:common::config_flow::data::password%]", - "username": "[%key_id:common::config_flow::data::email%]", + "password": "Has\u0142o", + "username": "Adres e-mail", "with_family": "Z rodzin\u0105" }, "description": "Wprowad\u017a dane uwierzytelniaj\u0105ce", diff --git a/homeassistant/components/input_boolean/translations/ca.json b/homeassistant/components/input_boolean/translations/ca.json index 0ef459d9bb5..23600285d58 100644 --- a/homeassistant/components/input_boolean/translations/ca.json +++ b/homeassistant/components/input_boolean/translations/ca.json @@ -1,8 +1,8 @@ { "state": { "_": { - "off": "Desactivat", - "on": "Activat" + "off": "OFF", + "on": "ON" } }, "title": "Entrada booleana" diff --git a/homeassistant/components/ipp/translations/pl.json b/homeassistant/components/ipp/translations/pl.json index 8ead666dee1..2a212bc1270 100644 --- a/homeassistant/components/ipp/translations/pl.json +++ b/homeassistant/components/ipp/translations/pl.json @@ -2,14 +2,14 @@ "config": { "abort": { "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane.", - "connection_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia z drukark\u0105.", + "connection_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia.", "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 nieobs\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_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia.", "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}", @@ -17,8 +17,8 @@ "user": { "data": { "base_path": "\u015acie\u017cka wzgl\u0119dna do drukarki", - "host": "[%key_id:common::config_flow::data::host%]", - "port": "[%key_id:common::config_flow::data::port%]", + "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" }, diff --git a/homeassistant/components/isy994/translations/ko.json b/homeassistant/components/isy994/translations/ko.json index c0edb400594..289609adbf7 100644 --- a/homeassistant/components/isy994/translations/ko.json +++ b/homeassistant/components/isy994/translations/ko.json @@ -9,6 +9,7 @@ "invalid_host": "\ud638\uc2a4\ud2b8 \ud56d\ubaa9\uc774 \uc644\uc804\ud55c URL \ud615\uc2dd\uc774 \uc544\ub2d9\ub2c8\ub2e4. \uc608: http://192.168.10.100:80", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, + "flow_title": "ISY994 \ubc94\uc6a9 \uae30\uae30: {name} ({host})", "step": { "user": { "data": { diff --git a/homeassistant/components/isy994/translations/no.json b/homeassistant/components/isy994/translations/no.json index 9d9f5b59db9..16945f046ee 100644 --- a/homeassistant/components/isy994/translations/no.json +++ b/homeassistant/components/isy994/translations/no.json @@ -4,6 +4,7 @@ "invalid_host": "Vertsoppf\u00f8ringen var ikke i fullstendig URL-format, for eksempel http://192.168.10.100:80", "unknown": "[%key:common::config_flow::error::unknown%" }, + "flow_title": "Universelle enheter ISY994 {name} ( {host} )", "step": { "user": { "data": { diff --git a/homeassistant/components/isy994/translations/pl.json b/homeassistant/components/isy994/translations/pl.json index a934b3d9f6c..49fdc942541 100644 --- a/homeassistant/components/isy994/translations/pl.json +++ b/homeassistant/components/isy994/translations/pl.json @@ -1,19 +1,19 @@ { "config": { "abort": { - "already_configured": "[%key_id:common::config_flow::abort::already_configured_device%]" + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane." }, "error": { - "cannot_connect": "[%key_id:common::config_flow::error::cannot_connect%]", - "invalid_auth": "[%key_id:common::config_flow::error::invalid_auth%]", - "unknown": "[%key_id:common::config_flow::error::unknown%]" + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia.", + "invalid_auth": "Niepoprawne uwierzytelnienie.", + "unknown": "Nieoczekiwany b\u0142\u0105d." }, "step": { "user": { "data": { "host": "URL", - "password": "[%key_id:common::config_flow::data::password%]", - "username": "[%key_id:common::config_flow::data::username%]" + "password": "Has\u0142o", + "username": "Nazwa u\u017cytkownika" } } } diff --git a/homeassistant/components/isy994/translations/ru.json b/homeassistant/components/isy994/translations/ru.json index 8c19d972f40..ed34d79969f 100644 --- a/homeassistant/components/isy994/translations/ru.json +++ b/homeassistant/components/isy994/translations/ru.json @@ -9,6 +9,7 @@ "invalid_host": "URL-\u0430\u0434\u0440\u0435\u0441 \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0444\u043e\u0440\u043c\u0430\u0442\u0435 'address[:port]' (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440: 'http://192.168.10.100:80').", "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." }, + "flow_title": "Universal Devices ISY994 {name} ({host})", "step": { "user": { "data": { diff --git a/homeassistant/components/isy994/translations/zh-Hant.json b/homeassistant/components/isy994/translations/zh-Hant.json index fa3ef3ebd19..4327cca65e4 100644 --- a/homeassistant/components/isy994/translations/zh-Hant.json +++ b/homeassistant/components/isy994/translations/zh-Hant.json @@ -9,6 +9,7 @@ "invalid_host": "\u4e3b\u6a5f\u7aef\u4e26\u672a\u4ee5\u5b8c\u6574\u7db2\u5740\u683c\u5f0f\u8f38\u5165\uff0c\u4f8b\u5982\uff1ahttp://192.168.10.100:80", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, + "flow_title": "Universal Devices ISY994 {name} ({host})", "step": { "user": { "data": { diff --git a/homeassistant/components/juicenet/translations/pl.json b/homeassistant/components/juicenet/translations/pl.json index 4da73cd5cbf..601ce0c9128 100644 --- a/homeassistant/components/juicenet/translations/pl.json +++ b/homeassistant/components/juicenet/translations/pl.json @@ -1,12 +1,12 @@ { "config": { "abort": { - "already_configured": "[%key_id:common::config_flow::abort::already_configured_account%]" + "already_configured": "Konto jest ju\u017c skonfigurowane." }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie.", - "invalid_auth": "[%key_id:common::config_flow::error::invalid_auth%]", - "unknown": "[%key_id:common::config_flow::error::unknown%]" + "invalid_auth": "Niepoprawne uwierzytelnienie.", + "unknown": "Nieoczekiwany b\u0142\u0105d." }, "step": { "user": { diff --git a/homeassistant/components/konnected/translations/ca.json b/homeassistant/components/konnected/translations/ca.json index d35146410ed..0740d091c94 100644 --- a/homeassistant/components/konnected/translations/ca.json +++ b/homeassistant/components/konnected/translations/ca.json @@ -20,8 +20,8 @@ }, "user": { "data": { - "host": "Adre\u00e7a IP del dispositiu Konnected", - "port": "Port del dispositiu Konnected" + "host": "Adre\u00e7a IP", + "port": "Port" }, "description": "Introdueix la informaci\u00f3 d'amfitri\u00f3 del panell Konnected.", "title": "Descoberta de dispositiu Konnected" diff --git a/homeassistant/components/konnected/translations/pl.json b/homeassistant/components/konnected/translations/pl.json index a71d458bd32..75843bbde58 100644 --- a/homeassistant/components/konnected/translations/pl.json +++ b/homeassistant/components/konnected/translations/pl.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "[%key_id:common::config_flow::abort::already_configured_device%]", + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane.", "already_in_progress": "Konfiguracja urz\u0105dzenia jest ju\u017c w toku.", "not_konn_panel": "Nie rozpoznano urz\u0105dzenia Konnected.io", - "unknown": "[%key_id:common::config_flow::error::unknown%]" + "unknown": "Nieoczekiwany b\u0142\u0105d." }, "error": { "cannot_connect": "Nie mo\u017cna po\u0142\u0105czy\u0107 si\u0119 z panelem Konnected na {host}:{port}" @@ -20,8 +20,8 @@ }, "user": { "data": { - "host": "Adres IP urz\u0105dzenia Konnected", - "port": "[%key_id:common::config_flow::data::port%] urz\u0105dzenia Konnected" + "host": "Adres IP", + "port": "Port" }, "description": "Wprowad\u017a informacje o ho\u015bcie panelu Konnected.", "title": "Wykryj urz\u0105dzenie Konnected" diff --git a/homeassistant/components/life360/translations/pl.json b/homeassistant/components/life360/translations/pl.json index 1bf35e8853b..19a6c6d8828 100644 --- a/homeassistant/components/life360/translations/pl.json +++ b/homeassistant/components/life360/translations/pl.json @@ -2,7 +2,7 @@ "config": { "abort": { "invalid_credentials": "Nieprawid\u0142owe dane uwierzytelniaj\u0105ce", - "user_already_configured": "[%key_id:common::config_flow::abort::already_configured_account%]" + "user_already_configured": "Konto jest ju\u017c skonfigurowane." }, "create_entry": { "default": "Aby skonfigurowa\u0107 zaawansowane ustawienia, zapoznaj si\u0119 z [dokumentacj\u0105 Life360]({docs_url})." @@ -16,8 +16,8 @@ "step": { "user": { "data": { - "password": "[%key_id:common::config_flow::data::password%]", - "username": "[%key_id:common::config_flow::data::username%]" + "password": "Has\u0142o", + "username": "Nazwa u\u017cytkownika" }, "description": "Aby skonfigurowa\u0107 zaawansowane ustawienia, zapoznaj si\u0119 z [dokumentacj\u0105 Life360]({docs_url}). Mo\u017cesz to zrobi\u0107 przed dodaniem kont.", "title": "Informacje o koncie Life360" diff --git a/homeassistant/components/light/translations/ca.json b/homeassistant/components/light/translations/ca.json index ce5bb5c3c7b..9200bbf73db 100644 --- a/homeassistant/components/light/translations/ca.json +++ b/homeassistant/components/light/translations/ca.json @@ -19,8 +19,8 @@ }, "state": { "_": { - "off": "Apagada", - "on": "Encesa" + "off": "OFF", + "on": "ON" } }, "title": "Llums" diff --git a/homeassistant/components/linky/translations/pl.json b/homeassistant/components/linky/translations/pl.json index 5452a549ec1..1fc09298fd7 100644 --- a/homeassistant/components/linky/translations/pl.json +++ b/homeassistant/components/linky/translations/pl.json @@ -12,8 +12,8 @@ "step": { "user": { "data": { - "password": "[%key_id:common::config_flow::data::password%]", - "username": "[%key_id:common::config_flow::data::email%]" + "password": "Has\u0142o", + "username": "Adres e-mail" }, "description": "Wprowad\u017a dane uwierzytelniaj\u0105ce", "title": "Linky" diff --git a/homeassistant/components/logi_circle/translations/en.json b/homeassistant/components/logi_circle/translations/en.json index 4befbe95f60..48e33359bdc 100644 --- a/homeassistant/components/logi_circle/translations/en.json +++ b/homeassistant/components/logi_circle/translations/en.json @@ -16,7 +16,7 @@ }, "step": { "auth": { - "description": "Please follow the link below and Accept access to your Logi Circle account, then come back and press Submit below.\n\n[Link]({authorization_url})", + "description": "Please follow the link below and **Accept** access to your Logi Circle account, then come back and press **Submit** below.\n\n[Link]({authorization_url})", "title": "Authenticate with Logi Circle" }, "user": { diff --git a/homeassistant/components/logi_circle/translations/no.json b/homeassistant/components/logi_circle/translations/no.json index 7c0cae590cd..82fa73d492d 100644 --- a/homeassistant/components/logi_circle/translations/no.json +++ b/homeassistant/components/logi_circle/translations/no.json @@ -16,7 +16,7 @@ }, "step": { "auth": { - "description": "Vennligst f\u00f8lg lenken nedenfor og Godta tilgang til Logi Circle kontoen din, kom deretter tilbake og trykk Send nedenfor. \n\n [Link]({authorization_url})", + "description": "Vennligst f\u00f8lg lenken nedenfor og **Godta** tilgang til Logi Circle kontoen din, kom deretter tilbake og trykk **Send** nedenfor. \n\n [Link]({authorization_url})", "title": "Godkjenn med Logi Circle" }, "user": { diff --git a/homeassistant/components/media_player/translations/ca.json b/homeassistant/components/media_player/translations/ca.json index d4aec480562..afec96fe14f 100644 --- a/homeassistant/components/media_player/translations/ca.json +++ b/homeassistant/components/media_player/translations/ca.json @@ -11,8 +11,8 @@ "state": { "_": { "idle": "Inactiu", - "off": "Apagat", - "on": "Enc\u00e8s", + "off": "OFF", + "on": "ON", "paused": "Pausat", "playing": "Reproduint", "standby": "En espera" diff --git a/homeassistant/components/melcloud/translations/ca.json b/homeassistant/components/melcloud/translations/ca.json index 92472d020c4..2da4f4b57f3 100644 --- a/homeassistant/components/melcloud/translations/ca.json +++ b/homeassistant/components/melcloud/translations/ca.json @@ -11,8 +11,8 @@ "step": { "user": { "data": { - "password": "Contrasenya de MELCloud.", - "username": "Correu electr\u00f2nic d'inici de sessi\u00f3 a MELCloud." + "password": "Contrasenya", + "username": "Correu electr\u00f2nic" }, "description": "Connecta't amb el teu compte de MELCloud.", "title": "Connexi\u00f3 amb MELCloud" diff --git a/homeassistant/components/melcloud/translations/pl.json b/homeassistant/components/melcloud/translations/pl.json index 4b6c4d70b80..44467601826 100644 --- a/homeassistant/components/melcloud/translations/pl.json +++ b/homeassistant/components/melcloud/translations/pl.json @@ -5,14 +5,14 @@ }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie.", - "invalid_auth": "[%key_id:common::config_flow::error::invalid_auth%]", - "unknown": "[%key_id:common::config_flow::error::unknown%]" + "invalid_auth": "Niepoprawne uwierzytelnienie.", + "unknown": "Nieoczekiwany b\u0142\u0105d." }, "step": { "user": { "data": { - "password": "[%key_id:common::config_flow::data::password%] MELCloud", - "username": "[%key_id:common::config_flow::data::email%]" + "password": "Has\u0142o", + "username": "Adres e-mail" }, "description": "Po\u0142\u0105cz u\u017cywaj\u0105c swojego konta MELCloud.", "title": "Po\u0142\u0105czenie z MELCloud" diff --git a/homeassistant/components/mikrotik/translations/pl.json b/homeassistant/components/mikrotik/translations/pl.json index 4dae38268d4..20b0348570c 100644 --- a/homeassistant/components/mikrotik/translations/pl.json +++ b/homeassistant/components/mikrotik/translations/pl.json @@ -11,11 +11,11 @@ "step": { "user": { "data": { - "host": "[%key_id:common::config_flow::data::host%]", + "host": "Nazwa hosta lub adres IP", "name": "Nazwa", - "password": "[%key_id:common::config_flow::data::password%]", - "port": "[%key_id:common::config_flow::data::port%]", - "username": "[%key_id:common::config_flow::data::username%]", + "password": "Has\u0142o", + "port": "Port", + "username": "Nazwa u\u017cytkownika", "verify_ssl": "U\u017cyj SSL" }, "title": "Konfiguracja routera Mikrotik" diff --git a/homeassistant/components/mill/translations/es.json b/homeassistant/components/mill/translations/es.json index fb0c69dfd07..e6b30851b80 100644 --- a/homeassistant/components/mill/translations/es.json +++ b/homeassistant/components/mill/translations/es.json @@ -4,7 +4,7 @@ "already_configured": "La cuenta ya ha sido configurada" }, "error": { - "connection_error": "Fallo al conectarse" + "connection_error": "No se pudo conectar" }, "step": { "user": { diff --git a/homeassistant/components/mill/translations/pl.json b/homeassistant/components/mill/translations/pl.json index 3f41ac78c7d..c9bef09227c 100644 --- a/homeassistant/components/mill/translations/pl.json +++ b/homeassistant/components/mill/translations/pl.json @@ -1,16 +1,16 @@ { "config": { "abort": { - "already_configured": "[%key_id:common::config_flow::abort::already_configured_account%]" + "already_configured": "Konto jest ju\u017c skonfigurowane." }, "error": { - "connection_error": "[%key_id:common::config_flow::error::cannot_connect%]" + "connection_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia." }, "step": { "user": { "data": { - "password": "[%key_id:common::config_flow::data::password%]", - "username": "[%key_id:common::config_flow::data::username%]" + "password": "Has\u0142o", + "username": "Nazwa u\u017cytkownika" } } } diff --git a/homeassistant/components/minecraft_server/translations/pl.json b/homeassistant/components/minecraft_server/translations/pl.json index 24749767b23..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": "[%key_id:common::config_flow::data::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/ca.json b/homeassistant/components/monoprice/translations/ca.json index e6e6208a8a4..6af5204b91e 100644 --- a/homeassistant/components/monoprice/translations/ca.json +++ b/homeassistant/components/monoprice/translations/ca.json @@ -10,7 +10,7 @@ "step": { "user": { "data": { - "port": "Port s\u00e8rie", + "port": "Port", "source_1": "Nom de la font #1", "source_2": "Nom de la font #2", "source_3": "Nom de la font #3", diff --git a/homeassistant/components/monoprice/translations/pl.json b/homeassistant/components/monoprice/translations/pl.json index 38dc9f9402f..9e47f6ace1c 100644 --- a/homeassistant/components/monoprice/translations/pl.json +++ b/homeassistant/components/monoprice/translations/pl.json @@ -1,16 +1,16 @@ { "config": { "abort": { - "already_configured": "[%key_id:common::config_flow::abort::already_configured_device%]" + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane." }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie.", - "unknown": "[%key_id:common::config_flow::error::unknown%]" + "unknown": "Nieoczekiwany b\u0142\u0105d." }, "step": { "user": { "data": { - "port": "[%key_id:common::config_flow::data::port%] szeregowy", + "port": "Port", "source_1": "Nazwa \u017ar\u00f3d\u0142a #1", "source_2": "Nazwa \u017ar\u00f3d\u0142a #2", "source_3": "Nazwa \u017ar\u00f3d\u0142a #3", diff --git a/homeassistant/components/mqtt/translations/pl.json b/homeassistant/components/mqtt/translations/pl.json index 4d5044e1fb0..73a29caced6 100644 --- a/homeassistant/components/mqtt/translations/pl.json +++ b/homeassistant/components/mqtt/translations/pl.json @@ -11,9 +11,9 @@ "data": { "broker": "Po\u015brednik", "discovery": "W\u0142\u0105cz wykrywanie", - "password": "[%key_id:common::config_flow::data::password%]", - "port": "[%key_id:common::config_flow::data::port%]", - "username": "[%key_id:common::config_flow::data::username%]" + "password": "Has\u0142o", + "port": "Port", + "username": "Nazwa u\u017cytkownika" }, "description": "Wprowad\u017a informacje o po\u0142\u0105czeniu po\u015brednika MQTT.", "title": "MQTT" diff --git a/homeassistant/components/myq/translations/pl.json b/homeassistant/components/myq/translations/pl.json index 30a4221134e..aefc8336903 100644 --- a/homeassistant/components/myq/translations/pl.json +++ b/homeassistant/components/myq/translations/pl.json @@ -5,14 +5,14 @@ }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie.", - "invalid_auth": "[%key_id:common::config_flow::error::invalid_auth%]", - "unknown": "[%key_id:common::config_flow::error::unknown%]" + "invalid_auth": "Niepoprawne uwierzytelnienie.", + "unknown": "Nieoczekiwany b\u0142\u0105d." }, "step": { "user": { "data": { - "password": "[%key_id:common::config_flow::data::password%]", - "username": "[%key_id:common::config_flow::data::username%]" + "password": "Has\u0142o", + "username": "Nazwa u\u017cytkownika" }, "title": "Po\u0142\u0105czenie z bramk\u0105 MyQ" } diff --git a/homeassistant/components/neato/translations/pl.json b/homeassistant/components/neato/translations/pl.json index c7b37fa083d..80e0a1df48e 100644 --- a/homeassistant/components/neato/translations/pl.json +++ b/homeassistant/components/neato/translations/pl.json @@ -9,13 +9,13 @@ }, "error": { "invalid_credentials": "Nieprawid\u0142owe dane uwierzytelniaj\u0105ce", - "unexpected_error": "[%key_id:common::config_flow::error::unknown%]" + "unexpected_error": "Nieoczekiwany b\u0142\u0105d." }, "step": { "user": { "data": { - "password": "[%key_id:common::config_flow::data::password%]", - "username": "[%key_id:common::config_flow::data::username%]", + "password": "Has\u0142o", + "username": "Nazwa u\u017cytkownika", "vendor": "Dostawca" }, "description": "Zapoznaj si\u0119 z [dokumentacj\u0105 Neato]({docs_url}).", diff --git a/homeassistant/components/nest/translations/pl.json b/homeassistant/components/nest/translations/pl.json index 775e644a113..b5953decc6b 100644 --- a/homeassistant/components/nest/translations/pl.json +++ b/homeassistant/components/nest/translations/pl.json @@ -3,7 +3,7 @@ "abort": { "already_setup": "Mo\u017cesz skonfigurowa\u0107 tylko jedno konto Nest.", "authorize_url_fail": "Nieznany b\u0142\u0105d podczas generowania url autoryzacji.", - "authorize_url_timeout": "[%key_id:common::config_flow::abort::oauth2_authorize_url_timeout%]", + "authorize_url_timeout": "Przekroczono limit czasu generowania URL autoryzacji.", "no_flows": "Musisz skonfigurowa\u0107 Nest, aby m\u00f3c si\u0119 z nim uwierzytelni\u0107. Zapoznaj si\u0119 z [instrukcj\u0105](https://www.home-assistant.io/components/nest/)." }, "error": { diff --git a/homeassistant/components/netatmo/translations/pl.json b/homeassistant/components/netatmo/translations/pl.json index 718b44d60db..0f1c29801fe 100644 --- a/homeassistant/components/netatmo/translations/pl.json +++ b/homeassistant/components/netatmo/translations/pl.json @@ -1,16 +1,16 @@ { "config": { "abort": { - "already_setup": "Mo\u017cesz skonfigurowa\u0107 tylko jedno konto Netatmo.", - "authorize_url_timeout": "[%key_id:common::config_flow::abort::oauth2_authorize_url_timeout%]", - "missing_configuration": "[%key_id:common::config_flow::abort::oauth2_missing_configuration%]" + "already_setup": "Ju\u017c skonfigurowano. Mo\u017cliwa jest tylko jedna konfiguracja.", + "authorize_url_timeout": "Przekroczono limit czasu generowania URL autoryzacji.", + "missing_configuration": "Komponent nie jest skonfigurowany. Post\u0119puj zgodnie z dokumentacj\u0105." }, "create_entry": { - "default": "Pomy\u015blnie uwierzytelniono z Netatmo." + "default": "Pomy\u015blnie uwierzytelniono" }, "step": { "pick_implementation": { - "title": "[%key_id:common::config_flow::title::oauth2_pick_implementation%]" + "title": "Wybierz metod\u0119 uwierzytelniania" } } } diff --git a/homeassistant/components/nexia/translations/pl.json b/homeassistant/components/nexia/translations/pl.json index b6d13766dee..84844ddbd94 100644 --- a/homeassistant/components/nexia/translations/pl.json +++ b/homeassistant/components/nexia/translations/pl.json @@ -5,14 +5,14 @@ }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie.", - "invalid_auth": "[%key_id:common::config_flow::error::invalid_auth%]", - "unknown": "[%key_id:common::config_flow::error::unknown%]" + "invalid_auth": "Niepoprawne uwierzytelnienie.", + "unknown": "Nieoczekiwany b\u0142\u0105d." }, "step": { "user": { "data": { - "password": "[%key_id:common::config_flow::data::password%]", - "username": "[%key_id:common::config_flow::data::username%]" + "password": "Has\u0142o", + "username": "Nazwa u\u017cytkownika" }, "title": "Po\u0142\u0105czenie z mynexia.com" } diff --git a/homeassistant/components/notion/translations/ca.json b/homeassistant/components/notion/translations/ca.json index 7a4831517f0..05626cbd483 100644 --- a/homeassistant/components/notion/translations/ca.json +++ b/homeassistant/components/notion/translations/ca.json @@ -11,7 +11,7 @@ "user": { "data": { "password": "Contrasenya", - "username": "Nom d'usuari / correu electr\u00f2nic" + "username": "Nom d'usuari" }, "title": "Introdueix la teva informaci\u00f3" } diff --git a/homeassistant/components/notion/translations/pl.json b/homeassistant/components/notion/translations/pl.json index 3350a712504..8e3c60b6956 100644 --- a/homeassistant/components/notion/translations/pl.json +++ b/homeassistant/components/notion/translations/pl.json @@ -10,8 +10,8 @@ "step": { "user": { "data": { - "password": "[%key_id:common::config_flow::data::password%]", - "username": "[%key_id:common::config_flow::data::username%]/adres e-mail" + "password": "Has\u0142o", + "username": "Nazwa u\u017cytkownika" }, "title": "Wprowad\u017a dane" } diff --git a/homeassistant/components/nuheat/translations/pl.json b/homeassistant/components/nuheat/translations/pl.json index d32d1f11e38..cded692dbcd 100644 --- a/homeassistant/components/nuheat/translations/pl.json +++ b/homeassistant/components/nuheat/translations/pl.json @@ -5,16 +5,16 @@ }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie.", - "invalid_auth": "[%key_id:common::config_flow::error::invalid_auth%]", + "invalid_auth": "Niepoprawne uwierzytelnienie.", "invalid_thermostat": "Numer seryjny termostatu jest nieprawid\u0142owy.", - "unknown": "[%key_id:common::config_flow::error::unknown%]" + "unknown": "Nieoczekiwany b\u0142\u0105d." }, "step": { "user": { "data": { - "password": "[%key_id:common::config_flow::data::password%]", + "password": "Has\u0142o", "serial_number": "Numer seryjny termostatu", - "username": "[%key_id:common::config_flow::data::username%]" + "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" diff --git a/homeassistant/components/nut/translations/pl.json b/homeassistant/components/nut/translations/pl.json index 282d1e15ab6..488978276a8 100644 --- a/homeassistant/components/nut/translations/pl.json +++ b/homeassistant/components/nut/translations/pl.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "[%key_id:common::config_flow::abort::already_configured_device%]" + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane." }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie.", - "unknown": "[%key_id:common::config_flow::error::unknown%]" + "unknown": "Nieoczekiwany b\u0142\u0105d." }, "step": { "resources": { @@ -23,10 +23,10 @@ }, "user": { "data": { - "host": "[%key_id:common::config_flow::data::host%]", - "password": "[%key_id:common::config_flow::data::password%]", - "port": "[%key_id:common::config_flow::data::port%]", - "username": "[%key_id:common::config_flow::data::username%]" + "host": "Nazwa hosta lub adres IP", + "password": "Has\u0142o", + "port": "Port", + "username": "Nazwa u\u017cytkownika" }, "title": "Po\u0142\u0105cz z serwerem NUT" } diff --git a/homeassistant/components/nws/translations/ca.json b/homeassistant/components/nws/translations/ca.json index bbb316439e5..a2239f5f97d 100644 --- a/homeassistant/components/nws/translations/ca.json +++ b/homeassistant/components/nws/translations/ca.json @@ -10,7 +10,7 @@ "step": { "user": { "data": { - "api_key": "Clau API (correu electr\u00f2nic)", + "api_key": "Clau API", "latitude": "Latitud", "longitude": "Longitud", "station": "Codi d'estaci\u00f3 METAR" diff --git a/homeassistant/components/nws/translations/pl.json b/homeassistant/components/nws/translations/pl.json index ad94c694837..e3634ad2d31 100644 --- a/homeassistant/components/nws/translations/pl.json +++ b/homeassistant/components/nws/translations/pl.json @@ -1,16 +1,16 @@ { "config": { "abort": { - "already_configured": "[%key_id:common::config_flow::abort::already_configured_device%]" + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane." }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie.", - "unknown": "[%key_id:common::config_flow::error::unknown%]" + "unknown": "Nieoczekiwany b\u0142\u0105d." }, "step": { "user": { "data": { - "api_key": "[%key_id:common::config_flow::data::api_key%] (e-mail)", + "api_key": "Klucz API", "latitude": "Szeroko\u015b\u0107 geograficzna", "longitude": "D\u0142ugo\u015b\u0107 geograficzna", "station": "Kod stacji METAR" diff --git a/homeassistant/components/onvif/translations/ca.json b/homeassistant/components/onvif/translations/ca.json index 9d942f091d4..a0362abbb8f 100644 --- a/homeassistant/components/onvif/translations/ca.json +++ b/homeassistant/components/onvif/translations/ca.json @@ -34,6 +34,7 @@ "manual_input": { "data": { "host": "Amfitri\u00f3", + "name": "Nom", "port": "Port" }, "title": "Configura el dispositiu ONVIF" diff --git a/homeassistant/components/onvif/translations/en.json b/homeassistant/components/onvif/translations/en.json index 91828648cc0..a20b1fcb7e4 100644 --- a/homeassistant/components/onvif/translations/en.json +++ b/homeassistant/components/onvif/translations/en.json @@ -34,6 +34,7 @@ "manual_input": { "data": { "host": "Host", + "name": "Name", "port": "Port" }, "title": "Configure ONVIF device" diff --git a/homeassistant/components/onvif/translations/es.json b/homeassistant/components/onvif/translations/es.json index cf556f792bf..dd65094838d 100644 --- a/homeassistant/components/onvif/translations/es.json +++ b/homeassistant/components/onvif/translations/es.json @@ -34,6 +34,7 @@ "manual_input": { "data": { "host": "Host", + "name": "Nombre", "port": "Puerto" }, "title": "Configurar el dispositivo ONVIF" diff --git a/homeassistant/components/onvif/translations/ko.json b/homeassistant/components/onvif/translations/ko.json index 18428773bdc..9715d89f72b 100644 --- a/homeassistant/components/onvif/translations/ko.json +++ b/homeassistant/components/onvif/translations/ko.json @@ -34,6 +34,7 @@ "manual_input": { "data": { "host": "\ud638\uc2a4\ud2b8", + "name": "\uc774\ub984", "port": "\ud3ec\ud2b8" }, "title": "ONVIF \uae30\uae30 \uad6c\uc131\ud558\uae30" diff --git a/homeassistant/components/onvif/translations/no.json b/homeassistant/components/onvif/translations/no.json index 1dc8a8b4aff..5f55b264117 100644 --- a/homeassistant/components/onvif/translations/no.json +++ b/homeassistant/components/onvif/translations/no.json @@ -34,6 +34,7 @@ "manual_input": { "data": { "host": "Vert", + "name": "Navn", "port": "Port" }, "title": "Konfigurere ONVIF-enhet" diff --git a/homeassistant/components/onvif/translations/pl.json b/homeassistant/components/onvif/translations/pl.json index 213051969d8..afd4df73b66 100644 --- a/homeassistant/components/onvif/translations/pl.json +++ b/homeassistant/components/onvif/translations/pl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "[%key_id:common::config_flow::abort::already_configured_device%]", + "already_configured": "Urz\u0105dzenie 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.", @@ -13,8 +13,8 @@ "step": { "auth": { "data": { - "password": "[%key_id:common::config_flow::data::password%]", - "username": "[%key_id:common::config_flow::data::username%]" + "password": "Has\u0142o", + "username": "Nazwa u\u017cytkownika" }, "title": "Konfigurowanie uwierzytelniania" }, @@ -33,8 +33,8 @@ }, "manual_input": { "data": { - "host": "[%key_id:common::config_flow::data::host%]", - "port": "[%key_id:common::config_flow::data::port%]" + "host": "Nazwa hosta lub adres IP", + "port": "Port" }, "title": "Konfigurowanie urz\u0105dzenia ONVIF" }, diff --git a/homeassistant/components/onvif/translations/ru.json b/homeassistant/components/onvif/translations/ru.json index 20b709a4a01..84ff1775080 100644 --- a/homeassistant/components/onvif/translations/ru.json +++ b/homeassistant/components/onvif/translations/ru.json @@ -34,6 +34,7 @@ "manual_input": { "data": { "host": "\u0425\u043e\u0441\u0442", + "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435", "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" diff --git a/homeassistant/components/openuv/translations/ca.json b/homeassistant/components/openuv/translations/ca.json index e09ec648ee3..3f139b4f74f 100644 --- a/homeassistant/components/openuv/translations/ca.json +++ b/homeassistant/components/openuv/translations/ca.json @@ -7,7 +7,7 @@ "step": { "user": { "data": { - "api_key": "Clau API d'OpenUV", + "api_key": "Clau API", "elevation": "Elevaci\u00f3", "latitude": "Latitud", "longitude": "Longitud" diff --git a/homeassistant/components/openuv/translations/pl.json b/homeassistant/components/openuv/translations/pl.json index 871f701b399..28a43bd10d9 100644 --- a/homeassistant/components/openuv/translations/pl.json +++ b/homeassistant/components/openuv/translations/pl.json @@ -7,7 +7,7 @@ "step": { "user": { "data": { - "api_key": "[%key_id:common::config_flow::data::api_key%] OpenUV", + "api_key": "Klucz API", "elevation": "Wysoko\u015b\u0107", "latitude": "Szeroko\u015b\u0107 geograficzna", "longitude": "D\u0142ugo\u015b\u0107 geograficzna" diff --git a/homeassistant/components/ozw/translations/ca.json b/homeassistant/components/ozw/translations/ca.json index a2a4919a9c3..eba9a7e8757 100644 --- a/homeassistant/components/ozw/translations/ca.json +++ b/homeassistant/components/ozw/translations/ca.json @@ -9,6 +9,5 @@ "title": "Confirmaci\u00f3 de configuraci\u00f3" } } - }, - "title": "Z-Wave sobre MQTT" + } } \ No newline at end of file diff --git a/homeassistant/components/ozw/translations/de.json b/homeassistant/components/ozw/translations/de.json index 5957bd57813..79393cbf865 100644 --- a/homeassistant/components/ozw/translations/de.json +++ b/homeassistant/components/ozw/translations/de.json @@ -9,6 +9,5 @@ "title": "Einrichtung best\u00e4tigen" } } - }, - "title": "Z-Wave \u00fcber MQTT" + } } \ No newline at end of file diff --git a/homeassistant/components/ozw/translations/en.json b/homeassistant/components/ozw/translations/en.json index 4b5b44a76bb..c6a45474880 100644 --- a/homeassistant/components/ozw/translations/en.json +++ b/homeassistant/components/ozw/translations/en.json @@ -9,6 +9,5 @@ "title": "Confirm set up" } } - }, - "title": "Z-Wave over MQTT" + } } \ No newline at end of file diff --git a/homeassistant/components/ozw/translations/es.json b/homeassistant/components/ozw/translations/es.json index eec405ed4b5..f78b62828cb 100644 --- a/homeassistant/components/ozw/translations/es.json +++ b/homeassistant/components/ozw/translations/es.json @@ -9,6 +9,5 @@ "title": "Confirmar configuraci\u00f3n" } } - }, - "title": "Z-Wave sobre MQTT" + } } \ No newline at end of file diff --git a/homeassistant/components/ozw/translations/fr.json b/homeassistant/components/ozw/translations/fr.json index 40403973da3..dbc609b93eb 100644 --- a/homeassistant/components/ozw/translations/fr.json +++ b/homeassistant/components/ozw/translations/fr.json @@ -9,6 +9,5 @@ "title": "Confirmer la configuration" } } - }, - "title": "Z-Wave sur MQTT" + } } \ No newline at end of file diff --git a/homeassistant/components/ozw/translations/it.json b/homeassistant/components/ozw/translations/it.json index b13b5322c8b..0b76d09cf08 100644 --- a/homeassistant/components/ozw/translations/it.json +++ b/homeassistant/components/ozw/translations/it.json @@ -9,6 +9,5 @@ "title": "Confermare la configurazione" } } - }, - "title": "Z-Wave su MQTT" + } } \ No newline at end of file diff --git a/homeassistant/components/ozw/translations/ko.json b/homeassistant/components/ozw/translations/ko.json index 5a6346d1dd3..2412e162c3c 100644 --- a/homeassistant/components/ozw/translations/ko.json +++ b/homeassistant/components/ozw/translations/ko.json @@ -9,6 +9,5 @@ "title": "\uc124\uc815 \ub0b4\uc6a9 \ud655\uc778\ud558\uae30" } } - }, - "title": "MQTT \ub97c \ud1b5\ud55c Z-Wave" + } } \ No newline at end of file diff --git a/homeassistant/components/ozw/translations/lb.json b/homeassistant/components/ozw/translations/lb.json index 8e579b2e399..053fe631133 100644 --- a/homeassistant/components/ozw/translations/lb.json +++ b/homeassistant/components/ozw/translations/lb.json @@ -9,6 +9,5 @@ "title": "Installatioun konfirm\u00e9ieren" } } - }, - "title": "Z-Wave iwwer MQTT" + } } \ No newline at end of file diff --git a/homeassistant/components/ozw/translations/no.json b/homeassistant/components/ozw/translations/no.json index a89707a3ef2..1d4049978f5 100644 --- a/homeassistant/components/ozw/translations/no.json +++ b/homeassistant/components/ozw/translations/no.json @@ -9,6 +9,5 @@ "title": "Bekreft oppsett" } } - }, - "title": "" + } } \ No newline at end of file diff --git a/homeassistant/components/ozw/translations/pl.json b/homeassistant/components/ozw/translations/pl.json index 866f7d38df6..3d57c3908a0 100644 --- a/homeassistant/components/ozw/translations/pl.json +++ b/homeassistant/components/ozw/translations/pl.json @@ -9,6 +9,5 @@ "title": "Potwierd\u017a konfiguracj\u0119" } } - }, - "title": "Z-Wave poprzez MQTT" + } } \ No newline at end of file diff --git a/homeassistant/components/ozw/translations/ru.json b/homeassistant/components/ozw/translations/ru.json index 00d86616d46..ac968e9fdfa 100644 --- a/homeassistant/components/ozw/translations/ru.json +++ b/homeassistant/components/ozw/translations/ru.json @@ -9,6 +9,5 @@ "title": "\u041f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438" } } - }, - "title": "Z-Wave \u0447\u0435\u0440\u0435\u0437 MQTT" + } } \ No newline at end of file diff --git a/homeassistant/components/ozw/translations/sl.json b/homeassistant/components/ozw/translations/sl.json index 39a1ce54f7c..43c78f930f2 100644 --- a/homeassistant/components/ozw/translations/sl.json +++ b/homeassistant/components/ozw/translations/sl.json @@ -9,6 +9,5 @@ "title": "Potrdite nastavitev" } } - }, - "title": "Z-Wave \u010dez MQTT" + } } \ No newline at end of file diff --git a/homeassistant/components/ozw/translations/sv.json b/homeassistant/components/ozw/translations/sv.json index 9a9f3e24a78..68b50437725 100644 --- a/homeassistant/components/ozw/translations/sv.json +++ b/homeassistant/components/ozw/translations/sv.json @@ -5,6 +5,5 @@ "title": "Bekr\u00e4fta inst\u00e4llningen" } } - }, - "title": "Z-Wave \u00f6ver MQTT" + } } \ No newline at end of file diff --git a/homeassistant/components/ozw/translations/zh-Hant.json b/homeassistant/components/ozw/translations/zh-Hant.json index 62b05764083..e9ad87042d2 100644 --- a/homeassistant/components/ozw/translations/zh-Hant.json +++ b/homeassistant/components/ozw/translations/zh-Hant.json @@ -9,6 +9,5 @@ "title": "\u78ba\u8a8d\u8a2d\u5b9a" } } - }, - "title": "Z-Wave over MQTT" + } } \ No newline at end of file diff --git a/homeassistant/components/pi_hole/translations/ca.json b/homeassistant/components/pi_hole/translations/ca.json index c913a799321..d50376c7545 100644 --- a/homeassistant/components/pi_hole/translations/ca.json +++ b/homeassistant/components/pi_hole/translations/ca.json @@ -9,8 +9,12 @@ "step": { "user": { "data": { + "api_key": "Clau API (opcional)", "host": "Amfitri\u00f3", - "port": "Port" + "name": "Nom", + "port": "Port", + "ssl": "Utilitza SSL", + "verify_ssl": "Verifica el certificat SSL" } } } diff --git a/homeassistant/components/pi_hole/translations/es.json b/homeassistant/components/pi_hole/translations/es.json new file mode 100644 index 00000000000..9725843cef6 --- /dev/null +++ b/homeassistant/components/pi_hole/translations/es.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "El servicio ya est\u00e1 configurado", + "duplicated_name": "El nombre ya existe" + }, + "error": { + "cannot_connect": "No se pudo conectar" + }, + "step": { + "user": { + "data": { + "api_key": "Clave API (Opcional)", + "host": "Host", + "name": "Nombre", + "port": "Puerto", + "ssl": "Usar SSL", + "verify_ssl": "Verificar certificado SSL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/pi_hole/translations/ko.json b/homeassistant/components/pi_hole/translations/ko.json new file mode 100644 index 00000000000..8d52c0fce2a --- /dev/null +++ b/homeassistant/components/pi_hole/translations/ko.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "\uc11c\ube44\uc2a4\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "duplicated_name": "\uc774\ub984\uc774 \uc774\ubbf8 \uc874\uc7ac\ud569\ub2c8\ub2e4" + }, + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "user": { + "data": { + "api_key": "API \ud0a4 (\uc120\ud0dd \uc0ac\ud56d)", + "host": "\ud638\uc2a4\ud2b8", + "name": "\uc774\ub984", + "port": "\ud3ec\ud2b8", + "ssl": "SSL \uc0ac\uc6a9", + "verify_ssl": "SSL \uc778\uc99d\uc11c \uac80\uc99d" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/pi_hole/translations/lb.json b/homeassistant/components/pi_hole/translations/lb.json new file mode 100644 index 00000000000..0ce64fc6908 --- /dev/null +++ b/homeassistant/components/pi_hole/translations/lb.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "duplicated_name": "Numm g\u00ebtt et schonn" + }, + "step": { + "user": { + "data": { + "api_key": "API Schl\u00ebssel (Optionell)", + "name": "Numm", + "ssl": "SSL benotzen", + "verify_ssl": "SSL Zertifikat iwwerpr\u00e9iwen" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/pi_hole/translations/no.json b/homeassistant/components/pi_hole/translations/no.json new file mode 100644 index 00000000000..f6e9203505c --- /dev/null +++ b/homeassistant/components/pi_hole/translations/no.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Tjenesten er allerede konfigurert", + "duplicated_name": "Navnet eksisterte allerede" + }, + "error": { + "cannot_connect": "Tilkobling feilet" + }, + "step": { + "user": { + "data": { + "api_key": "API-n\u00f8kkel (valgfritt)", + "host": "Vert", + "name": "Navn", + "port": "", + "ssl": "Bruk SSL", + "verify_ssl": "Verifisere SSL-sertifikat" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/pi_hole/translations/pl.json b/homeassistant/components/pi_hole/translations/pl.json new file mode 100644 index 00000000000..cc7013e98bd --- /dev/null +++ b/homeassistant/components/pi_hole/translations/pl.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Us\u0142uga jest ju\u017c skonfigurowana." + }, + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia." + }, + "step": { + "user": { + "data": { + "api_key": "Klucz API (opcjonalnie)", + "host": "Nazwa hosta lub adres IP", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/plex/translations/ca.json b/homeassistant/components/plex/translations/ca.json index 72cc2d97a30..a5c5c294880 100644 --- a/homeassistant/components/plex/translations/ca.json +++ b/homeassistant/components/plex/translations/ca.json @@ -16,10 +16,11 @@ "not_found": "No s'ha trobat el servidor Plex", "ssl_error": "Problema amb el certificat SSL" }, + "flow_title": "{name} ({host})", "step": { "manual_setup": { "data": { - "host": "Amfitri\u00f3 (opcional si es proporciona un token)", + "host": "Amfitri\u00f3", "port": "Port", "ssl": "Utilitza SSL", "token": "Token (opcional)", diff --git a/homeassistant/components/plex/translations/es.json b/homeassistant/components/plex/translations/es.json index 6959c59deca..d2503fc06b8 100644 --- a/homeassistant/components/plex/translations/es.json +++ b/homeassistant/components/plex/translations/es.json @@ -16,6 +16,7 @@ "not_found": "No se ha encontrado el servidor Plex", "ssl_error": "Problema con el certificado SSL" }, + "flow_title": "{name} ({host})", "step": { "manual_setup": { "data": { diff --git a/homeassistant/components/plex/translations/ko.json b/homeassistant/components/plex/translations/ko.json index 4443e3bc218..98b48e778af 100644 --- a/homeassistant/components/plex/translations/ko.json +++ b/homeassistant/components/plex/translations/ko.json @@ -16,6 +16,7 @@ "not_found": "Plex \uc11c\ubc84\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4", "ssl_error": "SSL \uc778\uc99d\uc11c \uac80\uc99d" }, + "flow_title": "{name} ({host})", "step": { "manual_setup": { "data": { diff --git a/homeassistant/components/plex/translations/lb.json b/homeassistant/components/plex/translations/lb.json index c325389295c..f38c867a8da 100644 --- a/homeassistant/components/plex/translations/lb.json +++ b/homeassistant/components/plex/translations/lb.json @@ -16,6 +16,7 @@ "not_found": "Kee Plex Server fonnt", "ssl_error": "SSL Zertifikat Problem" }, + "flow_title": "{name} ({host})", "step": { "manual_setup": { "data": { diff --git a/homeassistant/components/plex/translations/no.json b/homeassistant/components/plex/translations/no.json index 19a4db4b8a6..20d828356b7 100644 --- a/homeassistant/components/plex/translations/no.json +++ b/homeassistant/components/plex/translations/no.json @@ -16,6 +16,7 @@ "not_found": "Plex-server ikke funnet", "ssl_error": "Problem med SSL-sertifikat" }, + "flow_title": "{name}({host})", "step": { "manual_setup": { "data": { diff --git a/homeassistant/components/plex/translations/pl.json b/homeassistant/components/plex/translations/pl.json index 6b61382458b..95443330c53 100644 --- a/homeassistant/components/plex/translations/pl.json +++ b/homeassistant/components/plex/translations/pl.json @@ -19,10 +19,10 @@ "step": { "manual_setup": { "data": { - "host": "[%key_id:common::config_flow::data::host%] (opcjonalnie, je\u015bli wprowadzono token)", - "port": "[%key_id:common::config_flow::data::port%]", + "host": "Nazwa hosta lub adres IP", + "port": "Port", "ssl": "U\u017cyj SSL", - "token": "[%key_id:common::config_flow::data::access_token%] (opcjonalnie)", + "token": "Token dost\u0119pu (opcjonalnie)", "verify_ssl": "Weryfikacja certyfikatu SSL" }, "title": "Manualna konfiguracja Plex" diff --git a/homeassistant/components/point/translations/en.json b/homeassistant/components/point/translations/en.json index e7f34fa1b76..df13e9a26b9 100644 --- a/homeassistant/components/point/translations/en.json +++ b/homeassistant/components/point/translations/en.json @@ -16,7 +16,7 @@ }, "step": { "auth": { - "description": "Please follow the link below and Accept access to your Minut account, then come back and press Submit below.\n\n[Link]({authorization_url})", + "description": "Please follow the link below and **Accept** access to your Minut account, then come back and press **Submit** below.\n\n[Link]({authorization_url})", "title": "Authenticate Point" }, "user": { diff --git a/homeassistant/components/point/translations/no.json b/homeassistant/components/point/translations/no.json index 30ac5f8e356..eb3fef66166 100644 --- a/homeassistant/components/point/translations/no.json +++ b/homeassistant/components/point/translations/no.json @@ -16,7 +16,7 @@ }, "step": { "auth": { - "description": "Vennligst f\u00f8lg lenken nedenfor og Godta tilgang til Minut-kontoen din, kom tilbake og trykk Send inn nedenfor. \n\n [Link]({authorization_url})", + "description": "Vennligst f\u00f8lg lenken nedenfor og **Godta** tilgang til Minut-kontoen din, kom tilbake og trykk **Send inn** nedenfor. \n\n [Link]({authorization_url})", "title": "Godkjenn Point" }, "user": { diff --git a/homeassistant/components/point/translations/pl.json b/homeassistant/components/point/translations/pl.json index 22fc5da2278..1596ba05916 100644 --- a/homeassistant/components/point/translations/pl.json +++ b/homeassistant/components/point/translations/pl.json @@ -1,18 +1,18 @@ { "config": { "abort": { - "already_setup": "Mo\u017cesz skonfigurowa\u0107 tylko konto Point.", + "already_setup": "Ju\u017c skonfigurowano. Mo\u017cliwa jest tylko jedna konfiguracja.", "authorize_url_fail": "Nieznany b\u0142\u0105d podczas generowania url autoryzacji.", - "authorize_url_timeout": "[%key_id:common::config_flow::abort::oauth2_authorize_url_timeout%]", + "authorize_url_timeout": "Przekroczono limit czasu generowania URL autoryzacji.", "external_setup": "Punkt pomy\u015blnie skonfigurowany.", - "no_flows": "Musisz skonfigurowa\u0107 Point, aby m\u00f3c si\u0119 z nim uwierzytelni\u0107. Zapoznaj si\u0119 z [instrukcj\u0105](https://www.home-assistant.io/components/point/)." + "no_flows": "Komponent nie jest skonfigurowany. Post\u0119puj zgodnie z dokumentacj\u0105." }, "create_entry": { "default": "Pomy\u015blnie uwierzytelniono przy u\u017cyciu Minut dla urz\u0105dze\u0144 Point" }, "error": { "follow_link": "Prosz\u0119 klikn\u0105\u0107 link i uwierzytelni\u0107 przed naci\u015bni\u0119ciem przycisku \"Zatwierd\u017a\"", - "no_token": "Brak uwierzytelnienia za pomoc\u0105 Minut" + "no_token": "Niepoprawny token dost\u0119pu." }, "step": { "auth": { @@ -24,7 +24,7 @@ "flow_impl": "Dostawca" }, "description": "Wybierz, kt\u00f3rego dostawc\u0119 uwierzytelnienia chcesz u\u017cywa\u0107 z Point.", - "title": "Dostawca uwierzytelnienia" + "title": "Wybierz metod\u0119 uwierzytelniania" } } } diff --git a/homeassistant/components/powerwall/translations/pl.json b/homeassistant/components/powerwall/translations/pl.json index 3ccdfce648a..20eb71e7c27 100644 --- a/homeassistant/components/powerwall/translations/pl.json +++ b/homeassistant/components/powerwall/translations/pl.json @@ -5,13 +5,13 @@ }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie.", - "unknown": "[%key_id:common::config_flow::error::unknown%]", + "unknown": "Nieoczekiwany 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": { "data": { - "ip_address": "[%key_id:common::config_flow::data::ip%]" + "ip_address": "Adres IP" }, "title": "Po\u0142\u0105czenie z Powerwall" } diff --git a/homeassistant/components/ps4/translations/pl.json b/homeassistant/components/ps4/translations/pl.json index a701feb6f32..8cbdfe3d0b7 100644 --- a/homeassistant/components/ps4/translations/pl.json +++ b/homeassistant/components/ps4/translations/pl.json @@ -21,7 +21,7 @@ "link": { "data": { "code": "PIN", - "ip_address": "[%key_id:common::config_flow::data::ip%]", + "ip_address": "Adres IP", "name": "Nazwa", "region": "Region" }, @@ -30,7 +30,7 @@ }, "mode": { "data": { - "ip_address": "[%key_id:common::config_flow::data::ip%] (pozostaw puste, je\u015bli u\u017cywasz wykrywania).", + "ip_address": "Adres IP (pozostaw puste, je\u015bli u\u017cywasz wykrywania)", "mode": "Tryb konfiguracji" }, "description": "Wybierz tryb konfiguracji. Pole adresu IP mo\u017cna pozostawi\u0107 puste, je\u015bli wybierzesz opcj\u0119 Auto Discovery, poniewa\u017c urz\u0105dzenia zostan\u0105 automatycznie wykryte.", diff --git a/homeassistant/components/rachio/translations/ca.json b/homeassistant/components/rachio/translations/ca.json index 14b4d8effd8..8da88ec50e9 100644 --- a/homeassistant/components/rachio/translations/ca.json +++ b/homeassistant/components/rachio/translations/ca.json @@ -11,7 +11,7 @@ "step": { "user": { "data": { - "api_key": "Clau API del compte Rachio." + "api_key": "Clau API" }, "description": "Necessitar\u00e0s la clau API de https://app.rach.io/. Selecciona 'Configuraci\u00f3 del compte' (Account Settings) i, a continuaci\u00f3, clica 'Obtenir clau API' (GET API KEY).", "title": "Connexi\u00f3 amb dispositiu Rachio" diff --git a/homeassistant/components/rachio/translations/pl.json b/homeassistant/components/rachio/translations/pl.json index db3fb0466cb..e077fea03a4 100644 --- a/homeassistant/components/rachio/translations/pl.json +++ b/homeassistant/components/rachio/translations/pl.json @@ -1,17 +1,17 @@ { "config": { "abort": { - "already_configured": "[%key_id:common::config_flow::abort::already_configured_device%]" + "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": "[%key_id:common::config_flow::error::invalid_auth%]", - "unknown": "[%key_id:common::config_flow::error::unknown%]" + "invalid_auth": "Niepoprawne uwierzytelnienie.", + "unknown": "Nieoczekiwany b\u0142\u0105d." }, "step": { "user": { "data": { - "api_key": "[%key_id:common::config_flow::data::api_key%] dla konta Rachio." + "api_key": "Klucz API" }, "description": "B\u0119dziesz potrzebowa\u0142 klucza API ze strony https://app.rach.io/. Wybierz 'Account Settings', a nast\u0119pnie kliknij 'GET API KEY'.", "title": "Po\u0142\u0105czenie z urz\u0105dzeniem Rachio" diff --git a/homeassistant/components/rainmachine/translations/pl.json b/homeassistant/components/rainmachine/translations/pl.json index 20a016755b2..8ea3ab6dbc4 100644 --- a/homeassistant/components/rainmachine/translations/pl.json +++ b/homeassistant/components/rainmachine/translations/pl.json @@ -10,9 +10,9 @@ "step": { "user": { "data": { - "ip_address": "[%key_id:common::config_flow::data::host%]", - "password": "[%key_id:common::config_flow::data::password%]", - "port": "[%key_id:common::config_flow::data::port%]" + "ip_address": "Nazwa hosta lub adres IP", + "password": "Has\u0142o", + "port": "Port" }, "title": "Wprowad\u017a dane" } diff --git a/homeassistant/components/remote/translations/ca.json b/homeassistant/components/remote/translations/ca.json index 76252d06ce8..ccf6b2c2640 100644 --- a/homeassistant/components/remote/translations/ca.json +++ b/homeassistant/components/remote/translations/ca.json @@ -1,8 +1,8 @@ { "state": { "_": { - "off": "Apagat", - "on": "Enc\u00e8s" + "off": "OFF", + "on": "ON" } }, "title": "Comandaments" diff --git a/homeassistant/components/ring/translations/ca.json b/homeassistant/components/ring/translations/ca.json index bcd3bd43af4..454956fe46c 100644 --- a/homeassistant/components/ring/translations/ca.json +++ b/homeassistant/components/ring/translations/ca.json @@ -19,7 +19,7 @@ "password": "Contrasenya", "username": "Nom d'usuari" }, - "title": "Inici de sessi\u00f3 amb un compte de Ring" + "title": "Inici de sessi\u00f3 amb Ring" } } } diff --git a/homeassistant/components/ring/translations/pl.json b/homeassistant/components/ring/translations/pl.json index d6f427aab4c..96aa7d39159 100644 --- a/homeassistant/components/ring/translations/pl.json +++ b/homeassistant/components/ring/translations/pl.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "[%key_id:common::config_flow::abort::already_configured_device%]" + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane." }, "error": { - "invalid_auth": "[%key_id:common::config_flow::error::invalid_auth%]", - "unknown": "[%key_id:common::config_flow::error::unknown%]" + "invalid_auth": "Niepoprawne uwierzytelnienie.", + "unknown": "Nieoczekiwany b\u0142\u0105d." }, "step": { "2fa": { @@ -16,8 +16,8 @@ }, "user": { "data": { - "password": "[%key_id:common::config_flow::data::password%]", - "username": "[%key_id:common::config_flow::data::username%]" + "password": "Has\u0142o", + "username": "Nazwa u\u017cytkownika" }, "title": "Zaloguj si\u0119 do konta Ring" } diff --git a/homeassistant/components/roku/translations/ca.json b/homeassistant/components/roku/translations/ca.json index 60382ca137a..3eee84ffdf9 100644 --- a/homeassistant/components/roku/translations/ca.json +++ b/homeassistant/components/roku/translations/ca.json @@ -1,21 +1,21 @@ { "config": { "abort": { - "already_configured": "El dispositiu Roku ja est\u00e0 configurat", + "already_configured": "El dispositiu ja est\u00e0 configurat", "unknown": "Error inesperat" }, "error": { - "cannot_connect": "No s'ha pogut connectar, torna-ho a provar" + "cannot_connect": "No s'ha pogut connectar" }, "flow_title": "Roku: {name}", "step": { "ssdp_confirm": { - "description": "Vols configurar {name}? Es substituiran les configuracions manuals d'aquest dispositiu en els arxius yaml.", + "description": "Vols configurar {name}?", "title": "Roku" }, "user": { "data": { - "host": "Amfitri\u00f3 o adre\u00e7a IP" + "host": "Amfitri\u00f3" }, "description": "Introdueix la teva informaci\u00f3 de Roku.", "title": "Roku" diff --git a/homeassistant/components/roku/translations/pl.json b/homeassistant/components/roku/translations/pl.json index 57f314126cb..2cffb477417 100644 --- a/homeassistant/components/roku/translations/pl.json +++ b/homeassistant/components/roku/translations/pl.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "[%key_id:common::config_flow::abort::already_configured_device%]", - "unknown": "[%key_id:common::config_flow::error::unknown%]" + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane.", + "unknown": "Nieoczekiwany b\u0142\u0105d." }, "error": { - "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie." + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia." }, "flow_title": "Roku: {name}", "step": { @@ -21,7 +21,7 @@ }, "user": { "data": { - "host": "[%key_id:common::config_flow::data::host%]" + "host": "Nazwa hosta lub adres IP" }, "description": "Wprowad\u017a dane Roku.", "title": "Roku" diff --git a/homeassistant/components/roomba/translations/ca.json b/homeassistant/components/roomba/translations/ca.json index a133139a8e9..8d94edc2b02 100644 --- a/homeassistant/components/roomba/translations/ca.json +++ b/homeassistant/components/roomba/translations/ca.json @@ -11,7 +11,7 @@ "certificate": "Certificat", "continuous": "Cont\u00ednua", "delay": "Retard", - "host": "Nom de l'amfitri\u00f3 o adre\u00e7a IP", + "host": "Amfitri\u00f3", "password": "Contrasenya" }, "description": "Actualment la recuperaci\u00f3 de BLID i la contrasenya \u00e9s un proc\u00e9s manual. Segueix els passos de la documentaci\u00f3 a: https://www.home-assistant.io/integrations/roomba/#retrieving-your-credentials", diff --git a/homeassistant/components/roomba/translations/pl.json b/homeassistant/components/roomba/translations/pl.json index 0598a9ea247..1bb7f0dec45 100644 --- a/homeassistant/components/roomba/translations/pl.json +++ b/homeassistant/components/roomba/translations/pl.json @@ -2,7 +2,7 @@ "config": { "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie.", - "unknown": "[%key_id:common::config_flow::error::unknown%]" + "unknown": "Nieoczekiwany b\u0142\u0105d." }, "step": { "user": { @@ -11,8 +11,8 @@ "certificate": "Certyfikat", "continuous": "Ci\u0105g\u0142y", "delay": "Op\u00f3\u017anienie", - "host": "[%key_id:common::config_flow::data::host%]", - "password": "[%key_id:common::config_flow::data::password%]" + "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" diff --git a/homeassistant/components/samsungtv/translations/ca.json b/homeassistant/components/samsungtv/translations/ca.json index 46ea2bfa4c3..8a0843c22a0 100644 --- a/homeassistant/components/samsungtv/translations/ca.json +++ b/homeassistant/components/samsungtv/translations/ca.json @@ -15,7 +15,7 @@ }, "user": { "data": { - "host": "Amfitri\u00f3 o adre\u00e7a IP", + "host": "Amfitri\u00f3", "name": "Nom" }, "description": "Introdeix les dades del televisor Samsung. Si mai abans l'has connectat a Home Assistant haur\u00edes de veure una finestra emergent demanant autenticaci\u00f3.", diff --git a/homeassistant/components/samsungtv/translations/pl.json b/homeassistant/components/samsungtv/translations/pl.json index 94a909680fa..1124b5335a8 100644 --- a/homeassistant/components/samsungtv/translations/pl.json +++ b/homeassistant/components/samsungtv/translations/pl.json @@ -15,7 +15,7 @@ }, "user": { "data": { - "host": "[%key_id:common::config_flow::data::host%]", + "host": "Nazwa hosta lub adres IP", "name": "Nazwa" }, "description": "Wprowad\u017a informacje o telewizorze Samsung. Je\u015bli nigdy wcze\u015bniej ten telewizor nie by\u0142 \u0142\u0105czony z Home Assistant'em na jego ekranie powinna pojawi\u0107 si\u0119 pro\u015bba o uwierzytelnienie.", diff --git a/homeassistant/components/script/translations/ca.json b/homeassistant/components/script/translations/ca.json index af31164cf4c..e736926da5e 100644 --- a/homeassistant/components/script/translations/ca.json +++ b/homeassistant/components/script/translations/ca.json @@ -1,8 +1,8 @@ { "state": { "_": { - "off": "Desactivat", - "on": "Activat" + "off": "OFF", + "on": "ON" } }, "title": "Programes (scripts)" diff --git a/homeassistant/components/sense/translations/pl.json b/homeassistant/components/sense/translations/pl.json index 3a1a75d1c14..c32b61e30ad 100644 --- a/homeassistant/components/sense/translations/pl.json +++ b/homeassistant/components/sense/translations/pl.json @@ -1,18 +1,18 @@ { "config": { "abort": { - "already_configured": "[%key_id:common::config_flow::abort::already_configured_device%]" + "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": "[%key_id:common::config_flow::error::invalid_auth%]", - "unknown": "[%key_id:common::config_flow::error::unknown%]" + "invalid_auth": "Niepoprawne uwierzytelnienie.", + "unknown": "Nieoczekiwany b\u0142\u0105d." }, "step": { "user": { "data": { - "email": "[%key_id:common::config_flow::data::email%]", - "password": "[%key_id:common::config_flow::data::password%]" + "email": "Adres e-mail", + "password": "Has\u0142o" }, "title": "Po\u0142\u0105czenie z monitorem energii Sense" } diff --git a/homeassistant/components/sensor/translations/ca.json b/homeassistant/components/sensor/translations/ca.json index d2b34d0d70e..7b6defd869b 100644 --- a/homeassistant/components/sensor/translations/ca.json +++ b/homeassistant/components/sensor/translations/ca.json @@ -1,7 +1,7 @@ { "device_automation": { "condition_type": { - "is_battery_level": "Nivell de bateria de {entity_name}", + "is_battery_level": "Nivell de bateria actual de {entity_name}", "is_humidity": "Humitat de {entity_name}", "is_illuminance": "Il\u00b7luminaci\u00f3 de {entity_name}", "is_power": "Pot\u00e8ncia de {entity_name}", @@ -25,8 +25,8 @@ }, "state": { "_": { - "off": "Desactivat", - "on": "Activat" + "off": "OFF", + "on": "ON" } }, "title": "Sensors" diff --git a/homeassistant/components/sentry/translations/pl.json b/homeassistant/components/sentry/translations/pl.json index 2e350dbfb0a..a66e217b850 100644 --- a/homeassistant/components/sentry/translations/pl.json +++ b/homeassistant/components/sentry/translations/pl.json @@ -5,7 +5,7 @@ }, "error": { "bad_dsn": "Nieprawid\u0142owy DSN", - "unknown": "[%key_id:common::config_flow::error::unknown%]" + "unknown": "Nieoczekiwany b\u0142\u0105d." }, "step": { "user": { diff --git a/homeassistant/components/simplisafe/translations/pl.json b/homeassistant/components/simplisafe/translations/pl.json index 9746aab1ee4..4b7b3c8b2f1 100644 --- a/homeassistant/components/simplisafe/translations/pl.json +++ b/homeassistant/components/simplisafe/translations/pl.json @@ -11,8 +11,8 @@ "user": { "data": { "code": "Kod (u\u017cywany w interfejsie Home Assistant'a)", - "password": "[%key_id:common::config_flow::data::password%]", - "username": "[%key_id:common::config_flow::data::email%]" + "password": "Has\u0142o", + "username": "Adres e-mail" }, "title": "Wprowad\u017a dane" } diff --git a/homeassistant/components/smartthings/translations/pl.json b/homeassistant/components/smartthings/translations/pl.json index b056a3297fe..7738d79c3e8 100644 --- a/homeassistant/components/smartthings/translations/pl.json +++ b/homeassistant/components/smartthings/translations/pl.json @@ -17,7 +17,7 @@ }, "pat": { "data": { - "access_token": "[%key_id:common::config_flow::data::access_token%]" + "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" diff --git a/homeassistant/components/solaredge/translations/ca.json b/homeassistant/components/solaredge/translations/ca.json index ca5d472c9d6..cce579cd7e5 100644 --- a/homeassistant/components/solaredge/translations/ca.json +++ b/homeassistant/components/solaredge/translations/ca.json @@ -9,7 +9,7 @@ "step": { "user": { "data": { - "api_key": "Clau API d'aquest lloc", + "api_key": "Clau API", "name": "Nom d'aquesta instal\u00b7laci\u00f3", "site_id": "SolarEdge site_id" }, diff --git a/homeassistant/components/solaredge/translations/pl.json b/homeassistant/components/solaredge/translations/pl.json index e304337d3e2..2060cca6702 100644 --- a/homeassistant/components/solaredge/translations/pl.json +++ b/homeassistant/components/solaredge/translations/pl.json @@ -9,7 +9,7 @@ "step": { "user": { "data": { - "api_key": "[%key_id:common::config_flow::data::api_key%] dla tej strony", + "api_key": "Klucz API", "name": "Nazwa tej instalacji", "site_id": "SolarEdge site-id" }, diff --git a/homeassistant/components/solarlog/translations/ca.json b/homeassistant/components/solarlog/translations/ca.json index a159dd1f6e3..c62b69fa976 100644 --- a/homeassistant/components/solarlog/translations/ca.json +++ b/homeassistant/components/solarlog/translations/ca.json @@ -10,7 +10,7 @@ "step": { "user": { "data": { - "host": "Nom de l'amfitri\u00f3 o adre\u00e7a IP del dispositiu Solar-Log", + "host": "Amfitri\u00f3", "name": "Prefix utilitzat pels sensors de Solar-Log" }, "title": "Configuraci\u00f3 de la connexi\u00f3 amb Solar-Log" diff --git a/homeassistant/components/solarlog/translations/pl.json b/homeassistant/components/solarlog/translations/pl.json index 5add902c4fa..6769d51c2c2 100644 --- a/homeassistant/components/solarlog/translations/pl.json +++ b/homeassistant/components/solarlog/translations/pl.json @@ -1,16 +1,16 @@ { "config": { "abort": { - "already_configured": "[%key_id:common::config_flow::abort::already_configured_device%]" + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane." }, "error": { - "already_configured": "[%key_id:common::config_flow::abort::already_configured_device%]", + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane.", "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, sprawd\u017a adres hosta" }, "step": { "user": { "data": { - "host": "[%key_id:common::config_flow::data::host%]", + "host": "Nazwa hosta lub adres IP", "name": "Prefiks dla sensor\u00f3w Solar-Log" }, "title": "Zdefiniuj po\u0142\u0105czenie z Solar-Log" diff --git a/homeassistant/components/soma/translations/pl.json b/homeassistant/components/soma/translations/pl.json index 75f379de3d6..9e43072f4e9 100644 --- a/homeassistant/components/soma/translations/pl.json +++ b/homeassistant/components/soma/translations/pl.json @@ -2,9 +2,9 @@ "config": { "abort": { "already_setup": "Mo\u017cesz skonfigurowa\u0107 tylko jedno konto Soma.", - "authorize_url_timeout": "[%key_id:common::config_flow::abort::oauth2_authorize_url_timeout%]", + "authorize_url_timeout": "Przekroczono limit czasu generowania URL autoryzacji.", "connection_error": "Nie uda\u0142o si\u0119 po\u0142\u0105czy\u0107 z SOMA Connect.", - "missing_configuration": "[%key_id:common::config_flow::abort::oauth2_missing_configuration%]", + "missing_configuration": "Komponent nie jest skonfigurowany. Post\u0119puj zgodnie z dokumentacj\u0105.", "result_error": "SOMA Connect odpowiedzia\u0142 statusem b\u0142\u0119du." }, "create_entry": { @@ -13,8 +13,8 @@ "step": { "user": { "data": { - "host": "[%key_id:common::config_flow::data::host%]", - "port": "[%key_id:common::config_flow::data::port%]" + "host": "Nazwa hosta lub adres IP", + "port": "Port" }, "description": "Wprowad\u017a ustawienia po\u0142\u0105czenia SOMA Connect.", "title": "SOMA Connect" diff --git a/homeassistant/components/somfy/translations/pl.json b/homeassistant/components/somfy/translations/pl.json index 99d00914410..0c3dedfb466 100644 --- a/homeassistant/components/somfy/translations/pl.json +++ b/homeassistant/components/somfy/translations/pl.json @@ -2,15 +2,15 @@ "config": { "abort": { "already_setup": "Mo\u017cesz skonfigurowa\u0107 tylko jedno konto Somfy.", - "authorize_url_timeout": "[%key_id:common::config_flow::abort::oauth2_authorize_url_timeout%]", - "missing_configuration": "[%key_id:common::config_flow::abort::oauth2_missing_configuration%]" + "authorize_url_timeout": "Przekroczono limit czasu generowania URL autoryzacji.", + "missing_configuration": "Komponent nie jest skonfigurowany. Post\u0119puj zgodnie z dokumentacj\u0105." }, "create_entry": { "default": "Pomy\u015blnie uwierzytelniono z Somfy" }, "step": { "pick_implementation": { - "title": "[%key_id:common::config_flow::title::oauth2_pick_implementation%]" + "title": "Wybierz metod\u0119 uwierzytelniania" } } } diff --git a/homeassistant/components/songpal/translations/es.json b/homeassistant/components/songpal/translations/es.json index bb82d0a006d..614da4b6580 100644 --- a/homeassistant/components/songpal/translations/es.json +++ b/homeassistant/components/songpal/translations/es.json @@ -5,7 +5,7 @@ "not_songpal_device": "No es un dispositivo Songpal" }, "error": { - "cannot_connect": "Error al conectar", + "cannot_connect": "No se pudo conectar", "connection": "Error de conexi\u00f3n: comprueba tu endpoint" }, "flow_title": "Sony Songpal {name} ({host})", diff --git a/homeassistant/components/songpal/translations/pl.json b/homeassistant/components/songpal/translations/pl.json index 8d6c913c844..93e589c293a 100644 --- a/homeassistant/components/songpal/translations/pl.json +++ b/homeassistant/components/songpal/translations/pl.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "[%key_id:common::config_flow::abort::already_configured_device%]", + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane.", "not_songpal_device": "To nie jest urz\u0105dzenie Songpal" }, "error": { - "cannot_connect": "[%key_id:common::config_flow::error::cannot_connect%]", + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia.", "connection": "B\u0142\u0105d po\u0142\u0105czenia: sprawd\u017a punkt ko\u0144cowy" }, "flow_title": "Sony Songpal {name} ({host})", diff --git a/homeassistant/components/spotify/translations/pl.json b/homeassistant/components/spotify/translations/pl.json index f3c8413a02a..9dbd73661ee 100644 --- a/homeassistant/components/spotify/translations/pl.json +++ b/homeassistant/components/spotify/translations/pl.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_setup": "Mo\u017cesz skonfigurowa\u0107 tylko jedno konto Spotify.", - "authorize_url_timeout": "[%key_id:common::config_flow::abort::oauth2_authorize_url_timeout%]", + "authorize_url_timeout": "Przekroczono limit czasu generowania URL autoryzacji.", "missing_configuration": "Integracja ze Spotify nie jest skonfigurowana. Post\u0119puj zgodnie z dokumentacj\u0105." }, "create_entry": { @@ -10,7 +10,7 @@ }, "step": { "pick_implementation": { - "title": "[%key_id:common::config_flow::title::oauth2_pick_implementation%]" + "title": "Wybierz metod\u0119 uwierzytelniania" } } } diff --git a/homeassistant/components/starline/translations/en.json b/homeassistant/components/starline/translations/en.json index cf71829bf29..867cdccb19a 100644 --- a/homeassistant/components/starline/translations/en.json +++ b/homeassistant/components/starline/translations/en.json @@ -11,7 +11,7 @@ "app_id": "App ID", "app_secret": "Secret" }, - "description": "Application ID and secret code from StarLine developer account", + "description": "Application ID and secret code from [StarLine developer account](https://my.starline.ru/developer)", "title": "Application credentials" }, "auth_captcha": { diff --git a/homeassistant/components/starline/translations/no.json b/homeassistant/components/starline/translations/no.json index a96b788c90d..36545f3efd7 100644 --- a/homeassistant/components/starline/translations/no.json +++ b/homeassistant/components/starline/translations/no.json @@ -11,7 +11,7 @@ "app_id": "App-ID", "app_secret": "Hemmelig" }, - "description": "Applikasjons-ID og hemmelig kode fra StarLine utviklerkonto ", + "description": "Applikasjons-ID og hemmelig kode fra [StarLine utviklerkonto](https://my.starline.ru/developer)", "title": "Programlegitimasjon" }, "auth_captcha": { diff --git a/homeassistant/components/starline/translations/pl.json b/homeassistant/components/starline/translations/pl.json index 69691db21f8..5e5a293fc82 100644 --- a/homeassistant/components/starline/translations/pl.json +++ b/homeassistant/components/starline/translations/pl.json @@ -30,8 +30,8 @@ }, "auth_user": { "data": { - "password": "[%key_id:common::config_flow::data::password%]", - "username": "[%key_id:common::config_flow::data::username%]" + "password": "Has\u0142o", + "username": "Nazwa u\u017cytkownika" }, "description": "Adres e-mail i has\u0142o do konta StarLine", "title": "Po\u015bwiadczenia u\u017cytkownika" diff --git a/homeassistant/components/switch/translations/ca.json b/homeassistant/components/switch/translations/ca.json index e7d31e28da0..0904d4d08dd 100644 --- a/homeassistant/components/switch/translations/ca.json +++ b/homeassistant/components/switch/translations/ca.json @@ -16,8 +16,8 @@ }, "state": { "_": { - "off": "Apagat", - "on": "Enc\u00e8s" + "off": "OFF", + "on": "ON" } }, "title": "Interruptors" diff --git a/homeassistant/components/synology_dsm/translations/ca.json b/homeassistant/components/synology_dsm/translations/ca.json index 26f5d0c76b7..b4c939429ef 100644 --- a/homeassistant/components/synology_dsm/translations/ca.json +++ b/homeassistant/components/synology_dsm/translations/ca.json @@ -22,7 +22,7 @@ "data": { "api_version": "Versi\u00f3 DSM", "password": "Contrasenya", - "port": "Port (opcional)", + "port": "Port", "ssl": "Utilitza SSL/TLS per connectar-te al servidor NAS", "username": "Nom d'usuari" }, diff --git a/homeassistant/components/synology_dsm/translations/pl.json b/homeassistant/components/synology_dsm/translations/pl.json index 33644773220..ab55803696f 100644 --- a/homeassistant/components/synology_dsm/translations/pl.json +++ b/homeassistant/components/synology_dsm/translations/pl.json @@ -21,10 +21,10 @@ "link": { "data": { "api_version": "Wersja DSM", - "password": "[%key_id:common::config_flow::data::password%]", - "port": "[%key_id:common::config_flow::data::port%] (opcjonalnie)", + "password": "Has\u0142o", + "port": "Port", "ssl": "U\u017cyj SSL/TLS, aby po\u0142\u0105czy\u0107 si\u0119 z serwerem NAS", - "username": "[%key_id:common::config_flow::data::username%]" + "username": "Nazwa u\u017cytkownika" }, "description": "Czy chcesz skonfigurowa\u0107 {name} ({host})?", "title": "Synology DSM" @@ -32,11 +32,11 @@ "user": { "data": { "api_version": "Wersja DSM", - "host": "[%key_id:common::config_flow::data::host%]", - "password": "[%key_id:common::config_flow::data::password%]", - "port": "[%key_id:common::config_flow::data::port%] (opcjonalnie)", + "host": "Nazwa hosta lub adres IP", + "password": "Has\u0142o", + "port": "Port", "ssl": "U\u017cyj SSL/TLS, aby po\u0142\u0105czy\u0107 si\u0119 z serwerem NAS", - "username": "[%key_id:common::config_flow::data::username%]" + "username": "Nazwa u\u017cytkownika" }, "title": "Synology DSM" } diff --git a/homeassistant/components/tado/translations/pl.json b/homeassistant/components/tado/translations/pl.json index afe884ced51..061b4410e9f 100644 --- a/homeassistant/components/tado/translations/pl.json +++ b/homeassistant/components/tado/translations/pl.json @@ -1,19 +1,19 @@ { "config": { "abort": { - "already_configured": "[%key_id:common::config_flow::abort::already_configured_device%]" + "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": "[%key_id:common::config_flow::error::invalid_auth%]", + "invalid_auth": "Niepoprawne uwierzytelnienie.", "no_homes": "Brak dom\u00f3w powi\u0105zanych z tym kontem Tado.", - "unknown": "[%key_id:common::config_flow::error::unknown%]" + "unknown": "Nieoczekiwany b\u0142\u0105d." }, "step": { "user": { "data": { - "password": "[%key_id:common::config_flow::data::password%]", - "username": "[%key_id:common::config_flow::data::username%]" + "password": "Has\u0142o", + "username": "Nazwa u\u017cytkownika" }, "title": "Po\u0142\u0105cz z kontem Tado" } diff --git a/homeassistant/components/tellduslive/translations/ca.json b/homeassistant/components/tellduslive/translations/ca.json index c729dea0dd0..88e4dfbbdae 100644 --- a/homeassistant/components/tellduslive/translations/ca.json +++ b/homeassistant/components/tellduslive/translations/ca.json @@ -18,7 +18,7 @@ "data": { "host": "Amfitri\u00f3" }, - "description": "buit", + "description": "Buit", "title": "Selecci\u00f3 de l'endpoint" } } diff --git a/homeassistant/components/tellduslive/translations/pl.json b/homeassistant/components/tellduslive/translations/pl.json index acf9df68b03..49118f70dd8 100644 --- a/homeassistant/components/tellduslive/translations/pl.json +++ b/homeassistant/components/tellduslive/translations/pl.json @@ -3,8 +3,8 @@ "abort": { "already_setup": "TelldusLive jest ju\u017c skonfigurowany.", "authorize_url_fail": "Nieznany b\u0142\u0105d podczas generowania url autoryzacji.", - "authorize_url_timeout": "[%key_id:common::config_flow::abort::oauth2_authorize_url_timeout%]", - "unknown": "[%key_id:common::config_flow::error::unknown%]" + "authorize_url_timeout": "Przekroczono limit czasu generowania URL autoryzacji.", + "unknown": "Nieoczekiwany b\u0142\u0105d." }, "error": { "auth_error": "B\u0142\u0105d uwierzytelniania, spr\u00f3buj ponownie" @@ -16,7 +16,7 @@ }, "user": { "data": { - "host": "[%key_id:common::config_flow::data::host%]" + "host": "Nazwa hosta lub adres IP" }, "description": "Puste", "title": "Wybierz punkt ko\u0144cowy." diff --git a/homeassistant/components/tesla/translations/pl.json b/homeassistant/components/tesla/translations/pl.json index 9054268f4a5..e2a6b6a09c8 100644 --- a/homeassistant/components/tesla/translations/pl.json +++ b/homeassistant/components/tesla/translations/pl.json @@ -9,8 +9,8 @@ "step": { "user": { "data": { - "password": "[%key_id:common::config_flow::data::password%]", - "username": "[%key_id:common::config_flow::data::email%]" + "password": "Has\u0142o", + "username": "Adres e-mail" }, "description": "Wprowad\u017a dane", "title": "Tesla - konfiguracja" diff --git a/homeassistant/components/tibber/translations/pl.json b/homeassistant/components/tibber/translations/pl.json index 9e6417c33d7..8ef96358301 100644 --- a/homeassistant/components/tibber/translations/pl.json +++ b/homeassistant/components/tibber/translations/pl.json @@ -1,17 +1,17 @@ { "config": { "abort": { - "already_configured": "[%key_id:common::config_flow::abort::already_configured_account%]" + "already_configured": "Konto jest ju\u017c skonfigurowane." }, "error": { "connection_error": "B\u0142\u0105d po\u0142\u0105czenia z Tibber.", - "invalid_access_token": "[%key_id:common::config_flow::error::invalid_access_token%]", + "invalid_access_token": "Niepoprawny token dost\u0119pu.", "timeout": "Przekroczono limit czasu \u0142\u0105czenia z Tibber." }, "step": { "user": { "data": { - "access_token": "[%key_id:common::config_flow::data::access_token%]" + "access_token": "Token dost\u0119pu" }, "description": "Wprowad\u017a token dost\u0119pu z https://developer.tibber.com/settings/accesstoken", "title": "Tibber" diff --git a/homeassistant/components/timer/translations/ca.json b/homeassistant/components/timer/translations/ca.json index b5e9555940d..17e8463080a 100644 --- a/homeassistant/components/timer/translations/ca.json +++ b/homeassistant/components/timer/translations/ca.json @@ -2,7 +2,7 @@ "state": { "_": { "active": "Actiu", - "idle": "inactiu", + "idle": "Inactiu", "paused": "Pausat" } } diff --git a/homeassistant/components/toon/translations/pl.json b/homeassistant/components/toon/translations/pl.json index bbd9b03736d..96002ed932c 100644 --- a/homeassistant/components/toon/translations/pl.json +++ b/homeassistant/components/toon/translations/pl.json @@ -14,9 +14,9 @@ "step": { "authenticate": { "data": { - "password": "[%key_id:common::config_flow::data::password%]", + "password": "Has\u0142o", "tenant": "Najemca", - "username": "[%key_id:common::config_flow::data::username%]" + "username": "Nazwa u\u017cytkownika" }, "description": "Uwierzytelnij konto Eneco Toon (nie konto programisty).", "title": "Po\u0142\u0105cz konto Toon" diff --git a/homeassistant/components/totalconnect/translations/pl.json b/homeassistant/components/totalconnect/translations/pl.json index 426dfebd60e..58a0f7018da 100644 --- a/homeassistant/components/totalconnect/translations/pl.json +++ b/homeassistant/components/totalconnect/translations/pl.json @@ -9,8 +9,8 @@ "step": { "user": { "data": { - "password": "[%key_id:common::config_flow::data::password%]", - "username": "[%key_id:common::config_flow::data::username%]" + "password": "Has\u0142o", + "username": "Nazwa u\u017cytkownika" }, "title": "Total Connect" } diff --git a/homeassistant/components/tradfri/translations/pl.json b/homeassistant/components/tradfri/translations/pl.json index 83190ee37e3..028956ec6b3 100644 --- a/homeassistant/components/tradfri/translations/pl.json +++ b/homeassistant/components/tradfri/translations/pl.json @@ -12,7 +12,7 @@ "step": { "auth": { "data": { - "host": "[%key_id:common::config_flow::data::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 9d2b40f1cf2..52efb32b551 100644 --- a/homeassistant/components/transmission/translations/pl.json +++ b/homeassistant/components/transmission/translations/pl.json @@ -11,11 +11,11 @@ "step": { "user": { "data": { - "host": "[%key_id:common::config_flow::data::host%]", + "host": "Nazwa hosta lub adres IP", "name": "Nazwa", - "password": "[%key_id:common::config_flow::data::password%]", - "port": "[%key_id:common::config_flow::data::port%]", - "username": "[%key_id:common::config_flow::data::username%]" + "password": "Has\u0142o", + "port": "Port", + "username": "Nazwa u\u017cytkownika" }, "title": "Konfiguracja klienta Transmission" } diff --git a/homeassistant/components/tuya/translations/es.json b/homeassistant/components/tuya/translations/es.json index 163b2ed73c8..6493ca39754 100644 --- a/homeassistant/components/tuya/translations/es.json +++ b/homeassistant/components/tuya/translations/es.json @@ -3,7 +3,7 @@ "abort": { "already_in_progress": "La configuraci\u00f3n de Tuya ya est\u00e1 en progreso.", "auth_failed": "Autenticaci\u00f3n no v\u00e1lida", - "conn_error": "Fallo al conectarse", + "conn_error": "No se pudo conectar", "single_instance_allowed": "Ya est\u00e1 configurado. S\u00f3lo es posible una \u00fanica configuraci\u00f3n." }, "error": { diff --git a/homeassistant/components/tuya/translations/nl.json b/homeassistant/components/tuya/translations/nl.json new file mode 100644 index 00000000000..9d991230731 --- /dev/null +++ b/homeassistant/components/tuya/translations/nl.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_in_progress": "Tuya-configuratie is al bezig.", + "auth_failed": "Verkeerde gegevens", + "conn_error": "Niet gelukt om te verbinden.", + "single_instance_allowed": "Al geconfigureerd. Er is maar een configuratie mogelijk." + }, + "error": { + "auth_failed": "Verkeerde gegevens" + }, + "flow_title": "Tuya-configuratie", + "step": { + "user": { + "data": { + "country_code": "De landcode van uw account (bijvoorbeeld 1 voor de VS of 86 voor China)", + "password": "Wachtwoord", + "platform": "De app waar uw account is geregistreerd", + "username": "Gebruikersnaam" + }, + "description": "Voer uw Tuya-referentie in.", + "title": "Tuya" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tuya/translations/pl.json b/homeassistant/components/tuya/translations/pl.json index 5f5f552d4c2..9c6dc119d62 100644 --- a/homeassistant/components/tuya/translations/pl.json +++ b/homeassistant/components/tuya/translations/pl.json @@ -1,17 +1,18 @@ { "config": { "abort": { - "auth_failed": "[%key_id:common::config_flow::error::invalid_auth%]", - "conn_error": "[%key_id:common::config_flow::error::cannot_connect%]" + "auth_failed": "Niepoprawne uwierzytelnienie.", + "conn_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia.", + "single_instance_allowed": "Ju\u017c skonfigurowano. Mo\u017cliwa jest tylko jedna konfiguracja." }, "error": { - "auth_failed": "[%key_id:common::config_flow::error::invalid_auth%]" + "auth_failed": "Niepoprawne uwierzytelnienie." }, "step": { "user": { "data": { - "password": "[%key_id:common::config_flow::data::password%]", - "username": "[%key_id:common::config_flow::data::username%]" + "password": "Has\u0142o", + "username": "Nazwa u\u017cytkownika" } } } diff --git a/homeassistant/components/tuya/translations/ru.json b/homeassistant/components/tuya/translations/ru.json index 1452621b198..c8a3a816b13 100644 --- a/homeassistant/components/tuya/translations/ru.json +++ b/homeassistant/components/tuya/translations/ru.json @@ -13,9 +13,9 @@ "step": { "user": { "data": { - "country_code": "\u041a\u043e\u0434 \u0441\u0442\u0440\u0430\u043d\u044b \u0412\u0430\u0448\u0435\u0433\u043e \u0430\u043a\u043a\u0430\u0443\u043d\u0442\u0430 (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, 1 \u0434\u043b\u044f \u0421\u0428\u0410 \u0438\u043b\u0438 86 \u0434\u043b\u044f \u041a\u0438\u0442\u0430\u044f)", + "country_code": "\u041a\u043e\u0434 \u0441\u0442\u0440\u0430\u043d\u044b \u0430\u043a\u043a\u0430\u0443\u043d\u0442\u0430 (1 \u0434\u043b\u044f \u0421\u0428\u0410 \u0438\u043b\u0438 86 \u0434\u043b\u044f \u041a\u0438\u0442\u0430\u044f)", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", - "platform": "\u041f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u043d \u0412\u0430\u0448 \u0430\u043a\u043a\u0430\u0443\u043d\u0442", + "platform": "\u041f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u043d \u0430\u043a\u043a\u0430\u0443\u043d\u0442", "username": "\u041b\u043e\u0433\u0438\u043d" }, "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0441 Tuya.", diff --git a/homeassistant/components/twentemilieu/translations/pl.json b/homeassistant/components/twentemilieu/translations/pl.json index e8cd67db9db..bfa38f9ef8a 100644 --- a/homeassistant/components/twentemilieu/translations/pl.json +++ b/homeassistant/components/twentemilieu/translations/pl.json @@ -4,7 +4,7 @@ "address_exists": "Adres jest ju\u017c skonfigurowany." }, "error": { - "connection_error": "[%key_id:common::config_flow::error::cannot_connect%]", + "connection_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia.", "invalid_address": "Nie znaleziono adresu w obszarze us\u0142ugi Twente Milieu." }, "step": { diff --git a/homeassistant/components/unifi/translations/ca.json b/homeassistant/components/unifi/translations/ca.json index 0fdbf0cde10..4b8c08d3c7a 100644 --- a/homeassistant/components/unifi/translations/ca.json +++ b/homeassistant/components/unifi/translations/ca.json @@ -44,7 +44,7 @@ "track_wired_clients": "Inclou clients de xarxa per cable" }, "description": "Configuraci\u00f3 de seguiment de dispositius", - "title": "Opcions d'UniFi" + "title": "Opcions d'UniFi 1/3" }, "simple_options": { "data": { @@ -59,7 +59,7 @@ "allow_bandwidth_sensors": "Crea sensors d'\u00fas d'ample de banda per a clients de la xarxa" }, "description": "Configuraci\u00f3 dels sensors d'estad\u00edstiques", - "title": "Opcions d'UniFi" + "title": "Opcions d'UniFi 3/3" } } } diff --git a/homeassistant/components/unifi/translations/pl.json b/homeassistant/components/unifi/translations/pl.json index 27155ff7b5c..4c5a4ed15e7 100644 --- a/homeassistant/components/unifi/translations/pl.json +++ b/homeassistant/components/unifi/translations/pl.json @@ -6,18 +6,18 @@ "user_privilege": "U\u017cytkownik musi by\u0107 administratorem" }, "error": { - "faulty_credentials": "B\u0142\u0119dne dane uwierzytelniaj\u0105ce", - "service_unavailable": "Brak dost\u0119pnych us\u0142ug", + "faulty_credentials": "Niepoprawne uwierzytelnienie.", + "service_unavailable": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia.", "unknown_client_mac": "Brak klienta z tym adresem MAC" }, "step": { "user": { "data": { - "host": "[%key_id:common::config_flow::data::host%]", - "password": "[%key_id:common::config_flow::data::password%]", - "port": "[%key_id:common::config_flow::data::port%]", + "host": "Nazwa hosta lub adres IP", + "password": "Has\u0142o", + "port": "Port", "site": "Identyfikator witryny", - "username": "[%key_id:common::config_flow::data::username%]", + "username": "Nazwa u\u017cytkownika", "verify_ssl": "Kontroler u\u017cywa prawid\u0142owego certyfikatu" }, "title": "Konfiguracja kontrolera UniFi" diff --git a/homeassistant/components/upb/translations/lb.json b/homeassistant/components/upb/translations/lb.json index 26a76f8ab2e..6c1a46067d1 100644 --- a/homeassistant/components/upb/translations/lb.json +++ b/homeassistant/components/upb/translations/lb.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "address_already_configured": "een UPB PIM mat d\u00ebser Adress ass scho konfigur\u00e9iert." + }, "error": { "cannot_connect": "Feeler beim verbannen mat UPB PIM, prob\u00e9iert w.e.g. nach emol.", "invalid_upb_file": "UPB UPStart export fichier feelt oder ong\u00eblteg, iwwerpr\u00e9if den numm a pad vum fichier.", diff --git a/homeassistant/components/upb/translations/pl.json b/homeassistant/components/upb/translations/pl.json index a236c6fded2..0ec840b87f9 100644 --- a/homeassistant/components/upb/translations/pl.json +++ b/homeassistant/components/upb/translations/pl.json @@ -1,7 +1,7 @@ { "config": { "error": { - "unknown": "[%key_id:common::config_flow::error::unknown%]" + "unknown": "Nieoczekiwany b\u0142\u0105d." } } } \ No newline at end of file diff --git a/homeassistant/components/upnp/translations/en.json b/homeassistant/components/upnp/translations/en.json index d5436028cba..c2242031151 100644 --- a/homeassistant/components/upnp/translations/en.json +++ b/homeassistant/components/upnp/translations/en.json @@ -3,6 +3,7 @@ "abort": { "already_configured": "UPnP/IGD is already configured", "incomplete_device": "Ignoring incomplete UPnP device", + "incomplete_discovery": "Incomplete discovery", "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", diff --git a/homeassistant/components/upnp/translations/ko.json b/homeassistant/components/upnp/translations/ko.json index b3a0d822a2b..886fb4a4930 100644 --- a/homeassistant/components/upnp/translations/ko.json +++ b/homeassistant/components/upnp/translations/ko.json @@ -3,6 +3,7 @@ "abort": { "already_configured": "UPnP/IGD \uac00 \uc774\ubbf8 \uc124\uc815\ub41c \uc0c1\ud0dc\uc785\ub2c8\ub2e4", "incomplete_device": "\ubd88\uc644\uc804\ud55c UPnP \uae30\uae30 \ubb34\uc2dc\ud558\uae30", + "incomplete_discovery": "\uae30\uae30 \uac80\uc0c9\uc774 \uc644\uc804\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4", "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", @@ -25,7 +26,7 @@ "scan_interval": "\uc5c5\ub370\uc774\ud2b8 \uac04\uaca9 (\ucd08, \ucd5c\uc18c\uac12 30)", "usn": "\uae30\uae30" }, - "title": "\uc635\uc158 \uad6c\uc131\ud558\uae30" + "title": "\uc635\uc158 \uc124\uc815\ud558\uae30" } } } diff --git a/homeassistant/components/upnp/translations/no.json b/homeassistant/components/upnp/translations/no.json index 99eb7925b5c..08b8ee59931 100644 --- a/homeassistant/components/upnp/translations/no.json +++ b/homeassistant/components/upnp/translations/no.json @@ -3,6 +3,7 @@ "abort": { "already_configured": "UPnP / IGD er allerede konfigurert", "incomplete_device": "Ignorerer ufullstendig UPnP-enhet", + "incomplete_discovery": "Ufullstendig oppdagelse", "no_devices_discovered": "Ingen UPnP / IGDs oppdaget", "no_devices_found": "Ingen UPnP / IGD-enheter funnet p\u00e5 nettverket.", "no_sensors_or_port_mapping": "Aktiver minst sensorer eller port mapping", diff --git a/homeassistant/components/vacuum/translations/ca.json b/homeassistant/components/vacuum/translations/ca.json index f52d7e2536b..19147380fa0 100644 --- a/homeassistant/components/vacuum/translations/ca.json +++ b/homeassistant/components/vacuum/translations/ca.json @@ -19,8 +19,8 @@ "docked": "Aparcat", "error": "Error", "idle": "Inactiu", - "off": "Apagat", - "on": "Enc\u00e8s", + "off": "OFF", + "on": "ON", "paused": "Pausat", "returning": "Retornant a la base" } diff --git a/homeassistant/components/vesync/translations/pl.json b/homeassistant/components/vesync/translations/pl.json index 74fbc7c753b..aa5d4dc587f 100644 --- a/homeassistant/components/vesync/translations/pl.json +++ b/homeassistant/components/vesync/translations/pl.json @@ -9,8 +9,8 @@ "step": { "user": { "data": { - "password": "[%key_id:common::config_flow::data::password%]", - "username": "[%key_id:common::config_flow::data::email%]" + "password": "Has\u0142o", + "username": "Adres e-mail" }, "title": "Wprowad\u017a nazw\u0119 u\u017cytkownika i has\u0142o." } diff --git a/homeassistant/components/vilfo/translations/ca.json b/homeassistant/components/vilfo/translations/ca.json index 8bd6185c094..639167e4cec 100644 --- a/homeassistant/components/vilfo/translations/ca.json +++ b/homeassistant/components/vilfo/translations/ca.json @@ -11,8 +11,8 @@ "step": { "user": { "data": { - "access_token": "Token d'acc\u00e9s per l'API de l'encaminador Vilfo", - "host": "Nom d'amfitri\u00f3 o IP de l'encaminador" + "access_token": "Token d'acc\u00e9s", + "host": "Amfitri\u00f3" }, "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/vilfo/translations/pl.json b/homeassistant/components/vilfo/translations/pl.json index 939d840035f..29a8a056361 100644 --- a/homeassistant/components/vilfo/translations/pl.json +++ b/homeassistant/components/vilfo/translations/pl.json @@ -6,13 +6,13 @@ "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia. Sprawd\u017a wprowadzone dane i spr\u00f3buj ponownie.", "invalid_auth": "Nieudane uwierzytelnienie. Sprawd\u017a token dost\u0119pu i spr\u00f3buj ponownie.", - "unknown": "[%key_id:common::config_flow::error::unknown%]" + "unknown": "Nieoczekiwany b\u0142\u0105d." }, "step": { "user": { "data": { - "access_token": "Token dost\u0119pu do interfejsu API routera Vilfo", - "host": "[%key_id:common::config_flow::data::host%]" + "access_token": "Token dost\u0119pu", + "host": "Nazwa hosta lub adres IP" }, "description": "Skonfiguruj integracj\u0119 routera Vilfo. Potrzebujesz nazwy hosta/adresu IP routera Vilfo i tokena dost\u0119pu do interfejsu API. Aby uzyska\u0107 dodatkowe informacje na temat tej integracji i sposobu uzyskania niezb\u0119dnych danych do konfiguracji, odwied\u017a: https://www.home-assistant.io/integrations/vilfo", "title": "Po\u0142\u0105czenie z routerem Vilfo" diff --git a/homeassistant/components/vizio/translations/ca.json b/homeassistant/components/vizio/translations/ca.json index 2194f33dfff..7061bcb176f 100644 --- a/homeassistant/components/vizio/translations/ca.json +++ b/homeassistant/components/vizio/translations/ca.json @@ -31,7 +31,7 @@ "data": { "access_token": "[%key::common::config_flow::data::access_token%]", "device_class": "Tipus de dispositiu", - "host": ":", + "host": "Amfitri\u00f3", "name": "Nom" }, "description": "Nom\u00e9s es necessita el [%key::common::config_flow::data::access_token%] per als televisors. Si est\u00e0s configurant un televisor i encara no tens un [%key::common::config_flow::data::access_token%], deixa-ho en blanc per poder fer el proc\u00e9s d'emparellament.", diff --git a/homeassistant/components/vizio/translations/pl.json b/homeassistant/components/vizio/translations/pl.json index 699ffdbc437..6b8d1202299 100644 --- a/homeassistant/components/vizio/translations/pl.json +++ b/homeassistant/components/vizio/translations/pl.json @@ -5,7 +5,7 @@ "updated_entry": "Ten wpis zosta\u0142 ju\u017c skonfigurowany, ale nazwa i/lub opcje zdefiniowane w konfiguracji nie pasuj\u0105 do wcze\u015bniej zaimportowanych warto\u015bci, wi\u0119c wpis konfiguracji zosta\u0142 odpowiednio zaktualizowany." }, "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.", + "cant_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia.", "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." @@ -28,9 +28,9 @@ }, "user": { "data": { - "access_token": "[%key_id:common::config_flow::data::access_token%]", + "access_token": "Token dost\u0119pu", "device_class": "Typ urz\u0105dzenia", - "host": ":", + "host": "Nazwa hosta lub adres IP", "name": "Nazwa" }, "description": "Token dost\u0119pu potrzebny jest tylko dla telewizor\u00f3w. Je\u015bli konfigurujesz telewizor i nie masz jeszcze tokenu dost\u0119pu, pozostaw go pusty, aby przej\u015b\u0107 przez proces parowania.", diff --git a/homeassistant/components/wiffi/translations/ca.json b/homeassistant/components/wiffi/translations/ca.json new file mode 100644 index 00000000000..f21470653d7 --- /dev/null +++ b/homeassistant/components/wiffi/translations/ca.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "addr_in_use": "El port del servidor que ja est\u00e0 en us.", + "start_server_failed": "Ha fallat l'inici del servidor." + }, + "step": { + "user": { + "data": { + "port": "Port del servidor" + }, + "title": "Configuraci\u00f3 del servidor TCP per a dispositius WIFFI" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wiffi/translations/es.json b/homeassistant/components/wiffi/translations/es.json new file mode 100644 index 00000000000..d34d94336a0 --- /dev/null +++ b/homeassistant/components/wiffi/translations/es.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "addr_in_use": "El puerto del servidor ya est\u00e1 en uso.", + "start_server_failed": "No se pudo iniciar el servidor." + }, + "step": { + "user": { + "data": { + "port": "Puerto del servidor" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wiffi/translations/ko.json b/homeassistant/components/wiffi/translations/ko.json new file mode 100644 index 00000000000..4643a4a6000 --- /dev/null +++ b/homeassistant/components/wiffi/translations/ko.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "addr_in_use": "\uc11c\ubc84 \ud3ec\ud2b8\uac00 \uc774\ubbf8 \uc0ac\uc6a9 \uc911\uc785\ub2c8\ub2e4.", + "start_server_failed": "\uc11c\ubc84 \uc2e4\ud589\uc774 \uc2e4\ud328\ud588\uc2b5\ub2c8\ub2e4." + }, + "step": { + "user": { + "data": { + "port": "\uc11c\ubc84 \ud3ec\ud2b8" + }, + "title": "WIFFI \uae30\uae30\uc6a9 TCP \uc11c\ubc84 \uc124\uc815\ud558\uae30" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wiffi/translations/lb.json b/homeassistant/components/wiffi/translations/lb.json new file mode 100644 index 00000000000..6612b72901f --- /dev/null +++ b/homeassistant/components/wiffi/translations/lb.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "addr_in_use": "Server Port g\u00ebtt scho benotzt.", + "start_server_failed": "Feeler beim starte vum Server" + }, + "step": { + "user": { + "data": { + "port": "Server Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wiffi/translations/pl.json b/homeassistant/components/wiffi/translations/pl.json new file mode 100644 index 00000000000..4b75faf9af9 --- /dev/null +++ b/homeassistant/components/wiffi/translations/pl.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "port": "Port serwera" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/withings/translations/pl.json b/homeassistant/components/withings/translations/pl.json index 1b59e61a768..9896ba3ad5c 100644 --- a/homeassistant/components/withings/translations/pl.json +++ b/homeassistant/components/withings/translations/pl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "authorize_url_timeout": "[%key_id:common::config_flow::abort::oauth2_authorize_url_timeout%]", + "authorize_url_timeout": "Przekroczono limit czasu generowania URL autoryzacji.", "missing_configuration": "Integracja z Withings nie jest skonfigurowana. Post\u0119puj zgodnie z dokumentacj\u0105." }, "create_entry": { @@ -9,7 +9,7 @@ }, "step": { "pick_implementation": { - "title": "[%key_id:common::config_flow::title::oauth2_pick_implementation%]" + "title": "Wybierz metod\u0119 uwierzytelniania" }, "profile": { "data": { diff --git a/homeassistant/components/wled/translations/ca.json b/homeassistant/components/wled/translations/ca.json index ac47147207e..764c644994f 100644 --- a/homeassistant/components/wled/translations/ca.json +++ b/homeassistant/components/wled/translations/ca.json @@ -11,7 +11,7 @@ "step": { "user": { "data": { - "host": "Amfitri\u00f3 o adre\u00e7a IP" + "host": "Amfitri\u00f3" }, "description": "Configura el teu WLED per integrar-lo amb Home Assistant.", "title": "Enlla\u00e7 amb WLED" diff --git a/homeassistant/components/wled/translations/pl.json b/homeassistant/components/wled/translations/pl.json index 6db76e82a0a..edd5f38b912 100644 --- a/homeassistant/components/wled/translations/pl.json +++ b/homeassistant/components/wled/translations/pl.json @@ -1,17 +1,17 @@ { "config": { "abort": { - "already_configured": "[%key_id:common::config_flow::abort::already_configured_device%]", - "connection_error": "[%key_id:common::config_flow::error::cannot_connect%]" + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane.", + "connection_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia." }, "error": { - "connection_error": "[%key_id:common::config_flow::error::cannot_connect%]" + "connection_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia." }, "flow_title": "WLED: {name}", "step": { "user": { "data": { - "host": "[%key_id:common::config_flow::data::host%]" + "host": "Nazwa hosta lub adres IP" }, "description": "Konfiguracja WLED w celu integracji z Home Assistant'em.", "title": "Po\u0142\u0105cz z WLED" diff --git a/homeassistant/components/xiaomi_miio/translations/ca.json b/homeassistant/components/xiaomi_miio/translations/ca.json index 1c6c21e02f0..81dc08731c3 100644 --- a/homeassistant/components/xiaomi_miio/translations/ca.json +++ b/homeassistant/components/xiaomi_miio/translations/ca.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "[%key::common::config_flow::abort::already_configured_device%]" + "already_configured": "[%key::common::config_flow::abort::already_configured_device%]", + "already_in_progress": "El flux de dades de configuraci\u00f3 pel dispositiu Xiaomi Miio ja est\u00e0 en curs." }, "error": { "connect_error": "[%key::common::config_flow::error::cannot_connect%]", diff --git a/homeassistant/components/xiaomi_miio/translations/en.json b/homeassistant/components/xiaomi_miio/translations/en.json index 3f081c682d6..49c5c683993 100644 --- a/homeassistant/components/xiaomi_miio/translations/en.json +++ b/homeassistant/components/xiaomi_miio/translations/en.json @@ -27,4 +27,4 @@ } } } -} +} \ No newline at end of file diff --git a/homeassistant/components/xiaomi_miio/translations/es.json b/homeassistant/components/xiaomi_miio/translations/es.json index 5760a12ed04..41428edd02f 100644 --- a/homeassistant/components/xiaomi_miio/translations/es.json +++ b/homeassistant/components/xiaomi_miio/translations/es.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "El dispositivo ya est\u00e1 configurado" + "already_configured": "El dispositivo ya est\u00e1 configurado", + "already_in_progress": "El flujo de configuraci\u00f3n para este dispositivo Xiaomi Miio ya est\u00e1 en progreso." }, "error": { "connect_error": "No se ha podido conectar", diff --git a/homeassistant/components/xiaomi_miio/translations/ko.json b/homeassistant/components/xiaomi_miio/translations/ko.json index dbf3e091c5e..158ed8c52b2 100644 --- a/homeassistant/components/xiaomi_miio/translations/ko.json +++ b/homeassistant/components/xiaomi_miio/translations/ko.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "already_in_progress": "Xiaomi Miio \uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4." }, "error": { "connect_error": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", diff --git a/homeassistant/components/xiaomi_miio/translations/no.json b/homeassistant/components/xiaomi_miio/translations/no.json index 5a92830cdb7..a83cf030cc3 100644 --- a/homeassistant/components/xiaomi_miio/translations/no.json +++ b/homeassistant/components/xiaomi_miio/translations/no.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Enheten er allerede konfigurert" + "already_configured": "Enheten er allerede konfigurert", + "already_in_progress": "Konfigurasjonsflyt for denne Xiaomi Miio-enheten p\u00e5g\u00e5r allerede." }, "error": { "no_device_selected": "Ingen enhet valgt, vennligst velg en enhet." diff --git a/homeassistant/components/xiaomi_miio/translations/pl.json b/homeassistant/components/xiaomi_miio/translations/pl.json index da144095ad4..89a462f4f38 100644 --- a/homeassistant/components/xiaomi_miio/translations/pl.json +++ b/homeassistant/components/xiaomi_miio/translations/pl.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "[%key_id:common::config_flow::abort::already_configured_device%]" + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane." }, "error": { - "connect_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie.", + "connect_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia.", "no_device_selected": "Nie wybrano \u017cadnego urz\u0105dzenia, wybierz jedno urz\u0105dzenie." }, "step": { @@ -12,7 +12,7 @@ "data": { "host": "Adres IP", "name": "Nazwa bramki", - "token": "Klucz API" + "token": "Token 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" diff --git a/homeassistant/components/xiaomi_miio/translations/ru.json b/homeassistant/components/xiaomi_miio/translations/ru.json index fef7362537f..edd4365f20b 100644 --- a/homeassistant/components/xiaomi_miio/translations/ru.json +++ b/homeassistant/components/xiaomi_miio/translations/ru.json @@ -1,7 +1,8 @@ { "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_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." }, "error": { "connect_error": "\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f.", diff --git a/homeassistant/components/xiaomi_miio/translations/zh-Hant.json b/homeassistant/components/xiaomi_miio/translations/zh-Hant.json index 05806b15470..83f8143b120 100644 --- a/homeassistant/components/xiaomi_miio/translations/zh-Hant.json +++ b/homeassistant/components/xiaomi_miio/translations/zh-Hant.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "\u8a2d\u5099\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" + "already_configured": "\u8a2d\u5099\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "already_in_progress": "\u5c0f\u7c73 Miio \u8a2d\u5099\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d\u3002" }, "error": { "connect_error": "\u9023\u7dda\u5931\u6557", diff --git a/homeassistant/components/zerproc/translations/ca.json b/homeassistant/components/zerproc/translations/ca.json index dc21c371e60..ddc9a9298cb 100644 --- a/homeassistant/components/zerproc/translations/ca.json +++ b/homeassistant/components/zerproc/translations/ca.json @@ -9,5 +9,6 @@ "description": "Vols comen\u00e7ar la configuraci\u00f3?" } } - } + }, + "title": "Zerproc" } \ No newline at end of file diff --git a/homeassistant/components/zerproc/translations/es.json b/homeassistant/components/zerproc/translations/es.json new file mode 100644 index 00000000000..192afd87e65 --- /dev/null +++ b/homeassistant/components/zerproc/translations/es.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "no_devices_found": "No se encontraron dispositivos en la red", + "single_instance_allowed": "Ya est\u00e1 configurado. S\u00f3lo es posible una \u00fanica configuraci\u00f3n." + }, + "step": { + "confirm": { + "description": "\u00bfQuieres comenzar a configurar?" + } + } + }, + "title": "Zerproc" +} \ No newline at end of file diff --git a/homeassistant/components/zerproc/translations/lb.json b/homeassistant/components/zerproc/translations/lb.json new file mode 100644 index 00000000000..cdfd3890fb8 --- /dev/null +++ b/homeassistant/components/zerproc/translations/lb.json @@ -0,0 +1,3 @@ +{ + "title": "Zerproc" +} \ No newline at end of file diff --git a/homeassistant/components/zerproc/translations/pl.json b/homeassistant/components/zerproc/translations/pl.json new file mode 100644 index 00000000000..0bd69f84746 --- /dev/null +++ b/homeassistant/components/zerproc/translations/pl.json @@ -0,0 +1,8 @@ +{ + "config": { + "abort": { + "no_devices_found": "Nie znaleziono urz\u0105dze\u0144 w sieci.", + "single_instance_allowed": "Ju\u017c skonfigurowano. Mo\u017cliwa jest tylko jedna konfiguracja." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zha/translations/nl.json b/homeassistant/components/zha/translations/nl.json index e06c186cbef..d72d560aafc 100644 --- a/homeassistant/components/zha/translations/nl.json +++ b/homeassistant/components/zha/translations/nl.json @@ -7,10 +7,27 @@ "cannot_connect": "Kan geen verbinding maken met ZHA apparaat." }, "step": { + "pick_radio": { + "data": { + "radio_type": "Radio type" + }, + "description": "Kies een type Zigbee-radio", + "title": "Radio type" + }, + "port_config": { + "data": { + "baudrate": "poort snelheid", + "path": "Serieel apparaatpad" + }, + "description": "Voer poortspecifieke instellingen in", + "title": "Instellingen" + }, "user": { "data": { + "path": "Serieel apparaatpad", "radio_type": "Radio Type" }, + "description": "Selecteer seri\u00eble poort voor Zigbee-radio", "title": "ZHA" } } diff --git a/homeassistant/components/zha/translations/pl.json b/homeassistant/components/zha/translations/pl.json index 438cd44d5b5..69ea79c71a2 100644 --- a/homeassistant/components/zha/translations/pl.json +++ b/homeassistant/components/zha/translations/pl.json @@ -27,7 +27,7 @@ "data": { "path": "\u015acie\u017cka urz\u0105dzenia szeregowego", "radio_type": "Typ radia", - "usb_path": "[%key_id:common::config_flow::data::usb_path%]" + "usb_path": "\u015acie\u017cka urz\u0105dzenia USB" }, "description": "Wyb\u00f3r portu szeregowego dla radia Zigbee", "title": "ZHA" diff --git a/homeassistant/components/zwave/translations/ca.json b/homeassistant/components/zwave/translations/ca.json index 27f543a44c7..dedf7f60e4a 100644 --- a/homeassistant/components/zwave/translations/ca.json +++ b/homeassistant/components/zwave/translations/ca.json @@ -11,7 +11,7 @@ "user": { "data": { "network_key": "Clau de xarxa (deixa-ho en blanc per generar-la autom\u00e0ticament)", - "usb_path": "Ruta del port USB" + "usb_path": "Ruta del port USB del dispositiu" }, "description": "Consulta https://www.home-assistant.io/docs/z-wave/installation/ per obtenir informaci\u00f3 sobre les variables de configuraci\u00f3", "title": "Configuraci\u00f3 de Z-Wave" diff --git a/homeassistant/components/zwave/translations/pl.json b/homeassistant/components/zwave/translations/pl.json index dd7ae5aead9..3ff6d9cd194 100644 --- a/homeassistant/components/zwave/translations/pl.json +++ b/homeassistant/components/zwave/translations/pl.json @@ -11,7 +11,7 @@ "user": { "data": { "network_key": "Klucz sieciowy (pozostaw pusty, by generowa\u0107 automatycznie)", - "usb_path": "\u015acie\u017cka do kontrolera Z-Wave USB" + "usb_path": "\u015acie\u017cka urz\u0105dzenia USB" }, "description": "Przejd\u017a na https://www.home-assistant.io/docs/z-wave/installation/, aby uzyska\u0107 informacje na temat zmiennych konfiguracyjnych", "title": "Konfiguracja Z-Wave" From d8c7a10fd7becb143cf81b994b5f199cfa08599c Mon Sep 17 00:00:00 2001 From: Quentame Date: Fri, 15 May 2020 05:20:53 +0200 Subject: [PATCH 031/406] Bump python-synology to 0.8.1 (#35640) * Bump python-synology to 0.8.1 * Fix tests --- homeassistant/components/synology_dsm/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/synology_dsm/test_config_flow.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/synology_dsm/manifest.json b/homeassistant/components/synology_dsm/manifest.json index f57f1843f45..fcf91bb25b3 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.8.0"], + "requirements": ["python-synology==0.8.1"], "codeowners": ["@ProtoThis", "@Quentame"], "config_flow": true, "ssdp": [ diff --git a/requirements_all.txt b/requirements_all.txt index cc20340939f..f0351969a1b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1723,7 +1723,7 @@ python-sochain-api==0.0.2 python-songpal==0.12 # homeassistant.components.synology_dsm -python-synology==0.8.0 +python-synology==0.8.1 # homeassistant.components.tado python-tado==0.8.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ad01d396b60..8fdd41e2f68 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -711,7 +711,7 @@ python-openzwave-mqtt==1.0.1 python-songpal==0.12 # homeassistant.components.synology_dsm -python-synology==0.8.0 +python-synology==0.8.1 # 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 15a1655fbc1..f592ad90a88 100644 --- a/tests/components/synology_dsm/test_config_flow.py +++ b/tests/components/synology_dsm/test_config_flow.py @@ -309,7 +309,7 @@ async def test_connection_failed(hass: HomeAssistantType, service: MagicMock): async def test_unknown_failed(hass: HomeAssistantType, service: MagicMock): """Test when we have an unknown error.""" - service.return_value.login = Mock(side_effect=SynologyDSMException) + service.return_value.login = Mock(side_effect=SynologyDSMException(None, None)) result = await hass.config_entries.flow.async_init( DOMAIN, From 6d0909134c7ca233c0eaa2b30ad843348b7b2b05 Mon Sep 17 00:00:00 2001 From: Xiaonan Shen Date: Fri, 15 May 2020 03:18:43 -0700 Subject: [PATCH 032/406] Bump roombapy to 1.6.1 (#35650) * Bump roombapy to 1.6.1 * Improve roomba error handling --- homeassistant/components/roomba/irobot_base.py | 13 +++++-------- homeassistant/components/roomba/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 8 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/roomba/irobot_base.py b/homeassistant/components/roomba/irobot_base.py index 524b5a915f9..f510f4965b0 100644 --- a/homeassistant/components/roomba/irobot_base.py +++ b/homeassistant/components/roomba/irobot_base.py @@ -31,6 +31,7 @@ _LOGGER = logging.getLogger(__name__) ATTR_CLEANING_TIME = "cleaning_time" ATTR_CLEANED_AREA = "cleaned_area" ATTR_ERROR = "error" +ATTR_ERROR_CODE = "error_code" ATTR_POSITION = "position" ATTR_SOFTWARE_VERSION = "software_version" @@ -174,11 +175,6 @@ class IRobotVacuum(IRobotEntity, StateVacuumEntity): # Roomba software version software_version = state.get("softwareVer") - # Error message in plain english - error_msg = "None" - if hasattr(self.vacuum, "error_message"): - error_msg = self.vacuum.error_message - # Set properties that are to appear in the GUI state_attrs = {ATTR_SOFTWARE_VERSION: software_version} @@ -198,9 +194,10 @@ class IRobotVacuum(IRobotEntity, StateVacuumEntity): state_attrs[ATTR_CLEANING_TIME] = cleaning_time state_attrs[ATTR_CLEANED_AREA] = cleaned_area - # Skip error attr if there is none - if error_msg and error_msg != "None": - state_attrs[ATTR_ERROR] = error_msg + # Error + if self.vacuum.error_code != 0: + state_attrs[ATTR_ERROR] = self.vacuum.error_message + state_attrs[ATTR_ERROR_CODE] = self.vacuum.error_code # Not all Roombas expose position data # https://github.com/koalazak/dorita980/issues/48 diff --git a/homeassistant/components/roomba/manifest.json b/homeassistant/components/roomba/manifest.json index 45fe9133bca..3d710467b58 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.3"], + "requirements": ["roombapy==1.6.1"], "dependencies": [], "codeowners": ["@pschmitt", "@cyr-ius", "@shenxn"] } diff --git a/requirements_all.txt b/requirements_all.txt index f0351969a1b..17d1ab65373 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1871,7 +1871,7 @@ rocketchat-API==0.6.1 rokuecp==0.4.0 # homeassistant.components.roomba -roombapy==1.5.3 +roombapy==1.6.1 # homeassistant.components.rova rova==0.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 8fdd41e2f68..cdff16b7494 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -765,7 +765,7 @@ ring_doorbell==0.6.0 rokuecp==0.4.0 # homeassistant.components.roomba -roombapy==1.5.3 +roombapy==1.6.1 # homeassistant.components.yamaha rxv==0.6.0 From e74e0d1710e791e31a6918a5b129416da71e1c7e Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Fri, 15 May 2020 12:36:02 +0200 Subject: [PATCH 033/406] Fix caldav event for calendar panel (#35653) --- homeassistant/components/caldav/calendar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/caldav/calendar.py b/homeassistant/components/caldav/calendar.py index 579755709d1..3691f704a13 100644 --- a/homeassistant/components/caldav/calendar.py +++ b/homeassistant/components/caldav/calendar.py @@ -174,7 +174,7 @@ class WebDavCalendarData: uid = vevent.uid.value data = { "uid": uid, - "title": vevent.summary.value, + "summary": vevent.summary.value, "start": self.get_hass_date(vevent.dtstart.value), "end": self.get_hass_date(self.get_end_date(vevent)), "location": self.get_attr_value(vevent, "location"), From ddb5ed9dc89f2645b473ab7aae66e00dce91696e Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 15 May 2020 13:29:55 +0200 Subject: [PATCH 034/406] Add more information to discovery API (#35624) --- homeassistant/components/api/__init__.py | 43 ++++++++++++++++++------ 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/api/__init__.py b/homeassistant/components/api/__init__.py index 8d0cd44070c..001ce5d2a4e 100644 --- a/homeassistant/components/api/__init__.py +++ b/homeassistant/components/api/__init__.py @@ -35,14 +35,18 @@ import homeassistant.core as ha from homeassistant.exceptions import ServiceNotFound, TemplateError, Unauthorized from homeassistant.helpers import template from homeassistant.helpers.json import JSONEncoder +from homeassistant.helpers.network import NoURLAvailableError, get_url from homeassistant.helpers.service import async_get_all_descriptions from homeassistant.helpers.state import AsyncTrackStates _LOGGER = logging.getLogger(__name__) ATTR_BASE_URL = "base_url" +ATTR_EXTERNAL_URL = "external_url" +ATTR_INTERNAL_URL = "internal_url" ATTR_LOCATION_NAME = "location_name" ATTR_REQUIRES_API_PASSWORD = "requires_api_password" +ATTR_UUID = "uuid" ATTR_VERSION = "version" DOMAIN = "api" @@ -173,19 +177,36 @@ class APIDiscoveryView(HomeAssistantView): url = URL_API_DISCOVERY_INFO name = "api:discovery" - @ha.callback - def get(self, request): + async def get(self, request): """Get discovery information.""" hass = request.app["hass"] - return self.json( - { - ATTR_BASE_URL: hass.config.api.base_url, - ATTR_LOCATION_NAME: hass.config.location_name, - # always needs authentication - ATTR_REQUIRES_API_PASSWORD: True, - ATTR_VERSION: __version__, - } - ) + uuid = await hass.helpers.instance_id.async_get() + + data = { + ATTR_UUID: uuid, + ATTR_BASE_URL: None, + ATTR_EXTERNAL_URL: None, + ATTR_INTERNAL_URL: None, + ATTR_LOCATION_NAME: hass.config.location_name, + # always needs authentication + ATTR_REQUIRES_API_PASSWORD: True, + ATTR_VERSION: __version__, + } + + try: + data["external_url"] = get_url(hass, allow_internal=False) + except NoURLAvailableError: + pass + + try: + data["internal_url"] = get_url(hass, allow_external=False) + except NoURLAvailableError: + pass + + # Set old base URL based on external or internal + data["base_url"] = data["external_url"] or data["internal_url"] + + return self.json(data) class APIStatesView(HomeAssistantView): From 92756f9b128feebd651e106452edb57c029e5ab3 Mon Sep 17 00:00:00 2001 From: zewelor Date: Fri, 15 May 2020 17:41:53 +0200 Subject: [PATCH 035/406] Add explicit return none and binary sensor availability to yeelight (#35649) * Add explicit return none and binary sensor availbility * Fix --- homeassistant/components/yeelight/binary_sensor.py | 7 +++++++ homeassistant/components/yeelight/light.py | 2 ++ 2 files changed, 9 insertions(+) diff --git a/homeassistant/components/yeelight/binary_sensor.py b/homeassistant/components/yeelight/binary_sensor.py index cc2145d93b4..1696ca9bcb2 100644 --- a/homeassistant/components/yeelight/binary_sensor.py +++ b/homeassistant/components/yeelight/binary_sensor.py @@ -47,6 +47,13 @@ class YeelightNightlightModeSensor(BinarySensorEntity): if unique: return unique + "-nightlight_sensor" + return None + + @property + def available(self) -> bool: + """Return if bulb is available.""" + return self._device.available + @property def should_poll(self): """No polling needed.""" diff --git a/homeassistant/components/yeelight/light.py b/homeassistant/components/yeelight/light.py index e0ece2afdb9..244ccd5745d 100644 --- a/homeassistant/components/yeelight/light.py +++ b/homeassistant/components/yeelight/light.py @@ -917,6 +917,8 @@ class YeelightNightLightMode(YeelightGenericLight): if unique: return unique + "-nightlight" + return None + @property def name(self) -> str: """Return the name of the device if any.""" From 7e56f2cc0e2ff3ed106bcc0e3bcc14888aefe755 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 15 May 2020 11:09:21 -0500 Subject: [PATCH 036/406] Update cast to use shared zeroconf (#35570) * Update cast to use the shared zeroconf instance * Add zeroconf to after_dependencies * Bump version to 5.2.0 --- homeassistant/components/cast/discovery.py | 5 +++-- homeassistant/components/cast/manifest.json | 4 ++-- homeassistant/components/cast/media_player.py | 2 ++ requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 5 files changed, 9 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/cast/discovery.py b/homeassistant/components/cast/discovery.py index 9f90b074c3d..b15346417de 100644 --- a/homeassistant/components/cast/discovery.py +++ b/homeassistant/components/cast/discovery.py @@ -85,9 +85,10 @@ def setup_internal_discovery(hass: HomeAssistant) -> None: _LOGGER.debug("Starting internal pychromecast discovery.") listener, browser = pychromecast.start_discovery( - internal_add_callback, internal_remove_callback + internal_add_callback, + internal_remove_callback, + ChromeCastZeroconf.get_zeroconf(), ) - ChromeCastZeroconf.set_zeroconf(browser.zc) def stop_discovery(event): """Stop discovery of new chromecasts.""" diff --git a/homeassistant/components/cast/manifest.json b/homeassistant/components/cast/manifest.json index ddb6697d370..baeeef6de65 100644 --- a/homeassistant/components/cast/manifest.json +++ b/homeassistant/components/cast/manifest.json @@ -3,8 +3,8 @@ "name": "Google Cast", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/cast", - "requirements": ["pychromecast==5.1.0"], - "after_dependencies": ["cloud"], + "requirements": ["pychromecast==5.2.0"], + "after_dependencies": ["cloud","zeroconf"], "zeroconf": ["_googlecast._tcp.local."], "codeowners": ["@emontnemery"] } diff --git a/homeassistant/components/cast/media_player.py b/homeassistant/components/cast/media_player.py index fd7443d1c26..84917e0194a 100644 --- a/homeassistant/components/cast/media_player.py +++ b/homeassistant/components/cast/media_player.py @@ -14,6 +14,7 @@ from pychromecast.socket_client import ( ) import voluptuous as vol +from homeassistant.components import zeroconf from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity from homeassistant.components.media_player.const import ( MEDIA_TYPE_MOVIE, @@ -170,6 +171,7 @@ async def _async_setup_platform( for chromecast in list(hass.data[KNOWN_CHROMECAST_INFO_KEY]): async_cast_discovered(chromecast) + ChromeCastZeroconf.set_zeroconf(await zeroconf.async_get_instance(hass)) hass.async_add_executor_job(setup_internal_discovery, hass) diff --git a/requirements_all.txt b/requirements_all.txt index 17d1ab65373..037adcd1126 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1242,7 +1242,7 @@ pycfdns==0.0.1 pychannels==1.0.0 # homeassistant.components.cast -pychromecast==5.1.0 +pychromecast==5.2.0 # homeassistant.components.cmus pycmus==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index cdff16b7494..975998f9f5a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -527,7 +527,7 @@ pyblackbird==0.5 pybotvac==0.0.17 # homeassistant.components.cast -pychromecast==5.1.0 +pychromecast==5.2.0 # homeassistant.components.coolmaster pycoolmasternet==0.0.4 From 3a3f39b64250ff23f9ebca33f3fd3dac8de4dd86 Mon Sep 17 00:00:00 2001 From: Jason Hunter Date: Fri, 15 May 2020 14:05:32 -0400 Subject: [PATCH 037/406] ONVIF: Add check around media capabilities (#35667) --- homeassistant/components/onvif/device.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/onvif/device.py b/homeassistant/components/onvif/device.py index 17a0c0c27f0..c15f4bf6877 100644 --- a/homeassistant/components/onvif/device.py +++ b/homeassistant/components/onvif/device.py @@ -207,8 +207,13 @@ class ONVIFDevice: async def async_get_capabilities(self): """Obtain information about the available services on the device.""" - media_service = self.device.create_media_service() - media_capabilities = await media_service.GetServiceCapabilities() + snapshot = False + try: + media_service = self.device.create_media_service() + media_capabilities = await media_service.GetServiceCapabilities() + snapshot = media_capabilities.SnapshotUri + except (ONVIFError, Fault): + pass pullpoint = False try: @@ -225,7 +230,7 @@ class ONVIFDevice: except ONVIFError: pass - return Capabilities(media_capabilities.SnapshotUri, pullpoint, ptz) + return Capabilities(snapshot, pullpoint, ptz) async def async_get_profiles(self) -> List[Profile]: """Obtain media profiles for this device.""" From 064321c21e181271b15edfec385c1a5151dde738 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Fri, 15 May 2020 23:43:00 +0200 Subject: [PATCH 038/406] UniFi - Remove unused string definition, pointed out by Bram (#35678) --- homeassistant/components/unifi/strings.json | 1 - 1 file changed, 1 deletion(-) diff --git a/homeassistant/components/unifi/strings.json b/homeassistant/components/unifi/strings.json index df1cb753b53..95d273278bd 100644 --- a/homeassistant/components/unifi/strings.json +++ b/homeassistant/components/unifi/strings.json @@ -24,7 +24,6 @@ }, "options": { "step": { - "init": { "data": {} }, "device_tracker": { "data": { "detection_time": "Time in seconds from last seen until considered away", From 890013cecfd5788bcc84d91e07d2fca66d5b0058 Mon Sep 17 00:00:00 2001 From: gadgetmobile <57815233+gadgetmobile@users.noreply.github.com> Date: Fri, 15 May 2020 23:48:17 +0200 Subject: [PATCH 039/406] Add Blebox switch support (#35371) * support BleBox switches * fix tox py37 test failures * refactor BleBox device class map --- homeassistant/components/blebox/__init__.py | 2 +- homeassistant/components/blebox/const.py | 4 +- homeassistant/components/blebox/sensor.py | 4 +- homeassistant/components/blebox/switch.py | 35 ++ tests/components/blebox/conftest.py | 13 +- tests/components/blebox/test_switch.py | 386 ++++++++++++++++++++ 6 files changed, 437 insertions(+), 7 deletions(-) create mode 100644 homeassistant/components/blebox/switch.py create mode 100644 tests/components/blebox/test_switch.py diff --git a/homeassistant/components/blebox/__init__.py b/homeassistant/components/blebox/__init__.py index 1196deb27b7..f250c3f565e 100644 --- a/homeassistant/components/blebox/__init__.py +++ b/homeassistant/components/blebox/__init__.py @@ -17,7 +17,7 @@ from .const import DEFAULT_SETUP_TIMEOUT, DOMAIN, PRODUCT _LOGGER = logging.getLogger(__name__) -PLATFORMS = ["cover", "sensor"] +PLATFORMS = ["cover", "sensor", "switch"] PARALLEL_UPDATES = 0 diff --git a/homeassistant/components/blebox/const.py b/homeassistant/components/blebox/const.py index 71d2193f904..f5eba403c75 100644 --- a/homeassistant/components/blebox/const.py +++ b/homeassistant/components/blebox/const.py @@ -9,6 +9,7 @@ from homeassistant.components.cover import ( STATE_OPEN, STATE_OPENING, ) +from homeassistant.components.switch import DEVICE_CLASS_SWITCH from homeassistant.const import DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS DOMAIN = "blebox" @@ -26,6 +27,8 @@ BLEBOX_TO_HASS_DEVICE_CLASSES = { "shutter": DEVICE_CLASS_SHUTTER, "gatebox": DEVICE_CLASS_DOOR, "gate": DEVICE_CLASS_GATE, + "relay": DEVICE_CLASS_SWITCH, + "temperature": DEVICE_CLASS_TEMPERATURE, } BLEBOX_TO_HASS_COVER_STATES = { @@ -43,7 +46,6 @@ BLEBOX_TO_HASS_COVER_STATES = { } BLEBOX_TO_UNIT_MAP = {"celsius": TEMP_CELSIUS} -BLEBOX_DEV_CLASS_MAP = {"temperature": DEVICE_CLASS_TEMPERATURE} DEFAULT_HOST = "192.168.0.2" DEFAULT_PORT = 80 diff --git a/homeassistant/components/blebox/sensor.py b/homeassistant/components/blebox/sensor.py index b43b4f21da5..7a7aa0bac8d 100644 --- a/homeassistant/components/blebox/sensor.py +++ b/homeassistant/components/blebox/sensor.py @@ -3,7 +3,7 @@ from homeassistant.helpers.entity import Entity from . import BleBoxEntity, create_blebox_entities -from .const import BLEBOX_DEV_CLASS_MAP, BLEBOX_TO_UNIT_MAP, DOMAIN, PRODUCT +from .const import BLEBOX_TO_HASS_DEVICE_CLASSES, BLEBOX_TO_UNIT_MAP, DOMAIN, PRODUCT async def async_setup_entry(hass, config_entry, async_add): @@ -30,4 +30,4 @@ class BleBoxSensorEntity(BleBoxEntity, Entity): @property def device_class(self): """Return the device class.""" - return BLEBOX_DEV_CLASS_MAP[self._feature.device_class] + return BLEBOX_TO_HASS_DEVICE_CLASSES[self._feature.device_class] diff --git a/homeassistant/components/blebox/switch.py b/homeassistant/components/blebox/switch.py new file mode 100644 index 00000000000..116ae1645f4 --- /dev/null +++ b/homeassistant/components/blebox/switch.py @@ -0,0 +1,35 @@ +"""BleBox switch implementation.""" +from homeassistant.components.switch import SwitchDevice + +from . import BleBoxEntity, create_blebox_entities +from .const import BLEBOX_TO_HASS_DEVICE_CLASSES, DOMAIN, PRODUCT + + +async def async_setup_entry(hass, config_entry, async_add): + """Set up a BleBox switch entity.""" + + product = hass.data[DOMAIN][config_entry.entry_id][PRODUCT] + create_blebox_entities(product, async_add, BleBoxSwitchEntity, "switches") + return True + + +class BleBoxSwitchEntity(BleBoxEntity, SwitchDevice): + """Representation of a BleBox switch feature.""" + + @property + def device_class(self): + """Return the device class.""" + return BLEBOX_TO_HASS_DEVICE_CLASSES[self._feature.device_class] + + @property + def is_on(self): + """Return whether switch is on.""" + return self._feature.is_on + + async def async_turn_on(self, **kwargs): + """Turn on the switch.""" + return await self._feature.async_turn_on() + + async def async_turn_off(self, **kwargs): + """Turn off the switch.""" + return await self._feature.async_turn_off() diff --git a/tests/components/blebox/conftest.py b/tests/components/blebox/conftest.py index 69798d01fb8..569c16f813f 100644 --- a/tests/components/blebox/conftest.py +++ b/tests/components/blebox/conftest.py @@ -76,12 +76,19 @@ def feature(request): return request.getfixturevalue(request.param) -async def async_setup_entity(hass, config, entity_id): - """Return a configured entity with the given entity_id.""" +async def async_setup_entities(hass, config, entity_ids): + """Return configured entries with the given entity ids.""" + config_entry = mock_config() config_entry.add_to_hass(hass) assert await async_setup_component(hass, DOMAIN, config) await hass.async_block_till_done() entity_registry = await hass.helpers.entity_registry.async_get_registry() - return entity_registry.async_get(entity_id) + return [entity_registry.async_get(entity_id) for entity_id in entity_ids] + + +async def async_setup_entity(hass, config, entity_id): + """Return a configured entry with the given entity_id.""" + + return (await async_setup_entities(hass, config, [entity_id]))[0] diff --git a/tests/components/blebox/test_switch.py b/tests/components/blebox/test_switch.py new file mode 100644 index 00000000000..c41273757f2 --- /dev/null +++ b/tests/components/blebox/test_switch.py @@ -0,0 +1,386 @@ +"""Blebox switch tests.""" + +import logging + +import blebox_uniapi +import pytest + +from homeassistant.components.switch import DEVICE_CLASS_SWITCH +from homeassistant.const import ( + ATTR_DEVICE_CLASS, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, + STATE_OFF, + STATE_ON, +) + +from .conftest import ( + async_setup_entities, + async_setup_entity, + mock_feature, + mock_only_feature, + setup_product_mock, +) + +from tests.async_mock import AsyncMock, PropertyMock + + +@pytest.fixture(name="switchbox") +def switchbox_fixture(): + """Return a default switchBox switch entity mock.""" + feature = mock_feature( + "switches", + blebox_uniapi.switch.Switch, + unique_id="BleBox-switchBox-1afe34e750b8-0.relay", + full_name="switchBox-0.relay", + device_class="relay", + is_on=False, + ) + feature.async_update = AsyncMock() + product = feature.product + type(product).name = PropertyMock(return_value="My switch box") + type(product).model = PropertyMock(return_value="switchBox") + return (feature, "switch.switchbox_0_relay") + + +async def test_switchbox_init(switchbox, hass, config): + """Test switch default state.""" + + feature_mock, entity_id = switchbox + + feature_mock.async_update = AsyncMock() + entry = await async_setup_entity(hass, config, entity_id) + assert entry.unique_id == "BleBox-switchBox-1afe34e750b8-0.relay" + + state = hass.states.get(entity_id) + assert state.name == "switchBox-0.relay" + + assert state.attributes[ATTR_DEVICE_CLASS] == DEVICE_CLASS_SWITCH + + assert state.state == STATE_OFF + + device_registry = await hass.helpers.device_registry.async_get_registry() + device = device_registry.async_get(entry.device_id) + + assert device.name == "My switch box" + assert device.identifiers == {("blebox", "abcd0123ef5678")} + assert device.manufacturer == "BleBox" + assert device.model == "switchBox" + assert device.sw_version == "1.23" + + +async def test_switchbox_update_when_off(switchbox, hass, config): + """Test switch updating when off.""" + + feature_mock, entity_id = switchbox + + def initial_update(): + feature_mock.is_on = False + + feature_mock.async_update = AsyncMock(side_effect=initial_update) + await async_setup_entity(hass, config, entity_id) + + state = hass.states.get(entity_id) + assert state.state == STATE_OFF + + +async def test_switchbox_update_when_on(switchbox, hass, config): + """Test switch updating when on.""" + + feature_mock, entity_id = switchbox + + def initial_update(): + feature_mock.is_on = True + + feature_mock.async_update = AsyncMock(side_effect=initial_update) + await async_setup_entity(hass, config, entity_id) + + state = hass.states.get(entity_id) + assert state.state == STATE_ON + + +async def test_switchbox_on(switchbox, hass, config): + """Test turning switch on.""" + + feature_mock, entity_id = switchbox + + def initial_update(): + feature_mock.is_on = False + + feature_mock.async_update = AsyncMock(side_effect=initial_update) + await async_setup_entity(hass, config, entity_id) + feature_mock.async_update = AsyncMock() + + def turn_on(): + feature_mock.is_on = True + + feature_mock.async_turn_on = AsyncMock(side_effect=turn_on) + + await hass.services.async_call( + "switch", SERVICE_TURN_ON, {"entity_id": entity_id}, blocking=True, + ) + + state = hass.states.get(entity_id) + assert state.state == STATE_ON + + +async def test_switchbox_off(switchbox, hass, config): + """Test turning switch off.""" + + feature_mock, entity_id = switchbox + + def initial_update(): + feature_mock.is_on = True + + feature_mock.async_update = AsyncMock(side_effect=initial_update) + await async_setup_entity(hass, config, entity_id) + feature_mock.async_update = AsyncMock() + + def turn_off(): + feature_mock.is_on = False + + feature_mock.async_turn_off = AsyncMock(side_effect=turn_off) + + await hass.services.async_call( + "switch", SERVICE_TURN_OFF, {"entity_id": entity_id}, blocking=True, + ) + state = hass.states.get(entity_id) + assert state.state == STATE_OFF + + +def relay_mock(relay_id=0): + """Return a default switchBoxD switch entity mock.""" + + return mock_only_feature( + blebox_uniapi.switch.Switch, + unique_id=f"BleBox-switchBoxD-1afe34e750b8-{relay_id}.relay", + full_name=f"switchBoxD-{relay_id}.relay", + device_class="relay", + is_on=None, + ) + + +@pytest.fixture(name="switchbox_d") +def switchbox_d_fixture(): + """Set up two mocked Switch features representing a switchBoxD.""" + + relay1 = relay_mock(0) + relay2 = relay_mock(1) + features = [relay1, relay2] + + product = setup_product_mock("switches", features) + + type(product).name = PropertyMock(return_value="My relays") + type(product).model = PropertyMock(return_value="switchBoxD") + type(product).brand = PropertyMock(return_value="BleBox") + type(product).firmware_version = PropertyMock(return_value="1.23") + type(product).unique_id = PropertyMock(return_value="abcd0123ef5678") + + type(relay1).product = product + type(relay2).product = product + + return (features, ["switch.switchboxd_0_relay", "switch.switchboxd_1_relay"]) + + +async def test_switchbox_d_init(switchbox_d, hass, config): + """Test switch default state.""" + + feature_mocks, entity_ids = switchbox_d + + feature_mocks[0].async_update = AsyncMock() + feature_mocks[1].async_update = AsyncMock() + entries = await async_setup_entities(hass, config, entity_ids) + + entry = entries[0] + assert entry.unique_id == "BleBox-switchBoxD-1afe34e750b8-0.relay" + + state = hass.states.get(entity_ids[0]) + assert state.name == "switchBoxD-0.relay" + assert state.attributes[ATTR_DEVICE_CLASS] == DEVICE_CLASS_SWITCH + assert state.state == STATE_OFF # NOTE: should instead be STATE_UNKNOWN? + + device_registry = await hass.helpers.device_registry.async_get_registry() + device = device_registry.async_get(entry.device_id) + + assert device.name == "My relays" + assert device.identifiers == {("blebox", "abcd0123ef5678")} + assert device.manufacturer == "BleBox" + assert device.model == "switchBoxD" + assert device.sw_version == "1.23" + + entry = entries[1] + assert entry.unique_id == "BleBox-switchBoxD-1afe34e750b8-1.relay" + + state = hass.states.get(entity_ids[1]) + assert state.name == "switchBoxD-1.relay" + assert state.attributes[ATTR_DEVICE_CLASS] == DEVICE_CLASS_SWITCH + assert state.state == STATE_OFF # NOTE: should instead be STATE_UNKNOWN? + + device_registry = await hass.helpers.device_registry.async_get_registry() + device = device_registry.async_get(entry.device_id) + + assert device.name == "My relays" + assert device.identifiers == {("blebox", "abcd0123ef5678")} + assert device.manufacturer == "BleBox" + assert device.model == "switchBoxD" + assert device.sw_version == "1.23" + + +async def test_switchbox_d_update_when_off(switchbox_d, hass, config): + """Test switch updating when off.""" + + feature_mocks, entity_ids = switchbox_d + + def initial_update0(): + feature_mocks[0].is_on = False + feature_mocks[1].is_on = False + + feature_mocks[0].async_update = AsyncMock(side_effect=initial_update0) + feature_mocks[1].async_update = AsyncMock() + await async_setup_entities(hass, config, entity_ids) + + assert hass.states.get(entity_ids[0]).state == STATE_OFF + assert hass.states.get(entity_ids[1]).state == STATE_OFF + + +async def test_switchbox_d_update_when_second_off(switchbox_d, hass, config): + """Test switch updating when off.""" + + feature_mocks, entity_ids = switchbox_d + + def initial_update0(): + feature_mocks[0].is_on = True + feature_mocks[1].is_on = False + + feature_mocks[0].async_update = AsyncMock(side_effect=initial_update0) + feature_mocks[1].async_update = AsyncMock() + await async_setup_entities(hass, config, entity_ids) + + assert hass.states.get(entity_ids[0]).state == STATE_ON + assert hass.states.get(entity_ids[1]).state == STATE_OFF + + +async def test_switchbox_d_turn_first_on(switchbox_d, hass, config): + """Test turning switch on.""" + + feature_mocks, entity_ids = switchbox_d + + def initial_update0(): + feature_mocks[0].is_on = False + feature_mocks[1].is_on = False + + feature_mocks[0].async_update = AsyncMock(side_effect=initial_update0) + feature_mocks[1].async_update = AsyncMock() + await async_setup_entities(hass, config, entity_ids) + feature_mocks[0].async_update = AsyncMock() + + def turn_on0(): + feature_mocks[0].is_on = True + + feature_mocks[0].async_turn_on = AsyncMock(side_effect=turn_on0) + await hass.services.async_call( + "switch", SERVICE_TURN_ON, {"entity_id": entity_ids[0]}, blocking=True, + ) + + assert hass.states.get(entity_ids[0]).state == STATE_ON + assert hass.states.get(entity_ids[1]).state == STATE_OFF + + +async def test_switchbox_d_second_on(switchbox_d, hass, config): + """Test turning switch on.""" + + feature_mocks, entity_ids = switchbox_d + + def initial_update0(): + feature_mocks[0].is_on = False + feature_mocks[1].is_on = False + + feature_mocks[0].async_update = AsyncMock(side_effect=initial_update0) + feature_mocks[1].async_update = AsyncMock() + await async_setup_entities(hass, config, entity_ids) + feature_mocks[0].async_update = AsyncMock() + + def turn_on1(): + feature_mocks[1].is_on = True + + feature_mocks[1].async_turn_on = AsyncMock(side_effect=turn_on1) + await hass.services.async_call( + "switch", SERVICE_TURN_ON, {"entity_id": entity_ids[1]}, blocking=True, + ) + + assert hass.states.get(entity_ids[0]).state == STATE_OFF + assert hass.states.get(entity_ids[1]).state == STATE_ON + + +async def test_switchbox_d_first_off(switchbox_d, hass, config): + """Test turning switch on.""" + + feature_mocks, entity_ids = switchbox_d + + def initial_update_any(): + feature_mocks[0].is_on = True + feature_mocks[1].is_on = True + + feature_mocks[0].async_update = AsyncMock(side_effect=initial_update_any) + feature_mocks[1].async_update = AsyncMock() + await async_setup_entities(hass, config, entity_ids) + feature_mocks[0].async_update = AsyncMock() + + def turn_off0(): + feature_mocks[0].is_on = False + + feature_mocks[0].async_turn_off = AsyncMock(side_effect=turn_off0) + await hass.services.async_call( + "switch", SERVICE_TURN_OFF, {"entity_id": entity_ids[0]}, blocking=True, + ) + + assert hass.states.get(entity_ids[0]).state == STATE_OFF + assert hass.states.get(entity_ids[1]).state == STATE_ON + + +async def test_switchbox_d_second_off(switchbox_d, hass, config): + """Test turning switch on.""" + + feature_mocks, entity_ids = switchbox_d + + def initial_update_any(): + feature_mocks[0].is_on = True + feature_mocks[1].is_on = True + + feature_mocks[0].async_update = AsyncMock(side_effect=initial_update_any) + feature_mocks[1].async_update = AsyncMock() + await async_setup_entities(hass, config, entity_ids) + feature_mocks[0].async_update = AsyncMock() + + def turn_off1(): + feature_mocks[1].is_on = False + + feature_mocks[1].async_turn_off = AsyncMock(side_effect=turn_off1) + await hass.services.async_call( + "switch", SERVICE_TURN_OFF, {"entity_id": entity_ids[1]}, blocking=True, + ) + assert hass.states.get(entity_ids[0]).state == STATE_ON + assert hass.states.get(entity_ids[1]).state == STATE_OFF + + +ALL_SWITCH_FIXTURES = ["switchbox", "switchbox_d"] + + +@pytest.mark.parametrize("feature", ALL_SWITCH_FIXTURES, indirect=["feature"]) +async def test_update_failure(feature, hass, config, caplog): + """Test that update failures are logged.""" + + caplog.set_level(logging.ERROR) + + feature_mock, entity_id = feature + + if isinstance(feature_mock, list): + feature_mock[0].async_update = AsyncMock() + feature_mock[1].async_update = AsyncMock() + feature_mock = feature_mock[0] + entity_id = entity_id[0] + + feature_mock.async_update = AsyncMock(side_effect=blebox_uniapi.error.ClientError) + await async_setup_entity(hass, config, entity_id) + + assert f"Updating '{feature_mock.full_name}' failed: " in caplog.text From 714047f789c5a55df64dae5e38b94f3eb48e18cd Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Fri, 15 May 2020 23:56:09 +0200 Subject: [PATCH 040/406] Axis - Streamline setup and teardown of integration (#35675) * Streamline setup and teardown of integration * Dont remove integration twice --- homeassistant/components/axis/camera.py | 3 + homeassistant/components/axis/const.py | 6 ++ homeassistant/components/axis/device.py | 78 ++++++++++--------------- tests/components/axis/test_device.py | 6 +- 4 files changed, 42 insertions(+), 51 deletions(-) diff --git a/homeassistant/components/axis/camera.py b/homeassistant/components/axis/camera.py index 649e512718c..c4cc5df68a0 100644 --- a/homeassistant/components/axis/camera.py +++ b/homeassistant/components/axis/camera.py @@ -32,6 +32,9 @@ async def async_setup_entry(hass, config_entry, async_add_entities): device = hass.data[AXIS_DOMAIN][config_entry.unique_id] + if not device.option_camera: + return + config = { CONF_NAME: config_entry.data[CONF_NAME], CONF_USERNAME: config_entry.data[CONF_USERNAME], diff --git a/homeassistant/components/axis/const.py b/homeassistant/components/axis/const.py index 1d52677b30c..05a1211f89d 100644 --- a/homeassistant/components/axis/const.py +++ b/homeassistant/components/axis/const.py @@ -1,6 +1,10 @@ """Constants for the Axis component.""" import logging +from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN +from homeassistant.components.camera import DOMAIN as CAMERA_DOMAIN +from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN + LOGGER = logging.getLogger(__package__) DOMAIN = "axis" @@ -13,3 +17,5 @@ CONF_MODEL = "model" DEFAULT_EVENTS = True DEFAULT_TRIGGER_TIME = 0 + +PLATFORMS = [BINARY_SENSOR_DOMAIN, CAMERA_DOMAIN, SWITCH_DOMAIN] diff --git a/homeassistant/components/axis/device.py b/homeassistant/components/axis/device.py index 57d2d1be5d7..3483bfbea2e 100644 --- a/homeassistant/components/axis/device.py +++ b/homeassistant/components/axis/device.py @@ -7,9 +7,6 @@ import axis from axis.event_stream import OPERATION_INITIALIZED from axis.streammanager import SIGNAL_PLAYING -from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN -from homeassistant.components.camera import DOMAIN as CAMERA_DOMAIN -from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.const import ( CONF_HOST, CONF_NAME, @@ -32,6 +29,7 @@ from .const import ( DEFAULT_TRIGGER_TIME, DOMAIN as AXIS_DOMAIN, LOGGER, + PLATFORMS, ) from .errors import AuthenticationRequired, CannotConnect @@ -165,38 +163,28 @@ class AxisNetworkDevice: self.fw_version = self.api.vapix.params.firmware_version self.product_type = self.api.vapix.params.prodtype - if self.option_camera: - - self.hass.async_create_task( - self.hass.config_entries.async_forward_entry_setup( - self.config_entry, CAMERA_DOMAIN - ) + async def start_platforms(): + await asyncio.gather( + *[ + self.hass.config_entries.async_forward_entry_setup( + self.config_entry, platform + ) + for platform in PLATFORMS + ] ) - - if self.option_events: - - self.api.stream.connection_status_callback = ( - self.async_connection_status_callback - ) - self.api.enable_events(event_callback=self.async_event_callback) - - platform_tasks = [ - self.hass.config_entries.async_forward_entry_setup( - self.config_entry, platform + if self.option_events: + self.api.stream.connection_status_callback = ( + self.async_connection_status_callback ) - for platform in [BINARY_SENSOR_DOMAIN, SWITCH_DOMAIN] - ] - self.hass.async_create_task(self.start(platform_tasks)) + self.api.enable_events(event_callback=self.async_event_callback) + self.api.start() + + self.hass.async_create_task(start_platforms()) self.config_entry.add_update_listener(self.async_new_address_callback) return True - async def start(self, platform_tasks): - """Start the event stream when all platforms are loaded.""" - await asyncio.gather(*platform_tasks) - self.api.start() - @callback def shutdown(self, event): """Stop the event stream.""" @@ -204,29 +192,23 @@ class AxisNetworkDevice: async def async_reset(self): """Reset this device to default state.""" - platform_tasks = [] + self.api.stop() - if self.config_entry.options[CONF_CAMERA]: - platform_tasks.append( - self.hass.config_entries.async_forward_entry_unload( - self.config_entry, CAMERA_DOMAIN - ) + unload_ok = all( + await asyncio.gather( + *[ + self.hass.config_entries.async_forward_entry_unload( + self.config_entry, platform + ) + for platform in PLATFORMS + ] ) + ) + if not unload_ok: + return False - if self.config_entry.options[CONF_EVENTS]: - self.api.stop() - platform_tasks += [ - self.hass.config_entries.async_forward_entry_unload( - self.config_entry, platform - ) - for platform in [BINARY_SENSOR_DOMAIN, SWITCH_DOMAIN] - ] - - await asyncio.gather(*platform_tasks) - - for unsub_dispatcher in self.listeners: - unsub_dispatcher() - self.listeners = [] + for unsubscribe_listener in self.listeners: + unsubscribe_listener() return True diff --git a/tests/components/axis/test_device.py b/tests/components/axis/test_device.py index ec350695e6b..facb6f7de42 100644 --- a/tests/components/axis/test_device.py +++ b/tests/components/axis/test_device.py @@ -110,7 +110,7 @@ async def setup_axis_integration( await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() - return hass.data[AXIS_DOMAIN].get(config[CONF_MAC]) + return hass.data[AXIS_DOMAIN].get(config_entry.unique_id) async def test_device_setup(hass): @@ -124,8 +124,8 @@ async def test_device_setup(hass): entry = device.config_entry assert len(forward_entry_setup.mock_calls) == 3 - assert forward_entry_setup.mock_calls[0][1] == (entry, "camera") - assert forward_entry_setup.mock_calls[1][1] == (entry, "binary_sensor") + assert forward_entry_setup.mock_calls[0][1] == (entry, "binary_sensor") + assert forward_entry_setup.mock_calls[1][1] == (entry, "camera") assert forward_entry_setup.mock_calls[2][1] == (entry, "switch") assert device.host == ENTRY_CONFIG[CONF_HOST] From bdd99024b145c0be92aa014c1e82451b2adde5da Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Sat, 16 May 2020 00:12:58 +0200 Subject: [PATCH 041/406] Updated frontend to 20200515.0 (#35677) --- 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 6a1af47a9cf..f669452b92e 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==20200514.1"], + "requirements": ["home-assistant-frontend==20200515.0"], "dependencies": [ "api", "auth", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index b763b479fe8..0e129e292af 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -12,7 +12,7 @@ cryptography==2.9.2 defusedxml==0.6.0 distro==1.5.0 hass-nabucasa==0.34.2 -home-assistant-frontend==20200514.1 +home-assistant-frontend==20200515.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 037adcd1126..5a4b33a30d1 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -728,7 +728,7 @@ hole==0.5.1 holidays==0.10.2 # homeassistant.components.frontend -home-assistant-frontend==20200514.1 +home-assistant-frontend==20200515.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 975998f9f5a..fce1de85f1b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -312,7 +312,7 @@ hole==0.5.1 holidays==0.10.2 # homeassistant.components.frontend -home-assistant-frontend==20200514.1 +home-assistant-frontend==20200515.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From f3bb370b3afe826f3a1a13a14d7a437539ab2621 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Sat, 16 May 2020 00:03:50 +0000 Subject: [PATCH 042/406] [ci skip] Translation update --- .../airvisual/translations/pt-BR.json | 7 ++++ .../ambiclimate/translations/bg.json | 2 +- .../ambiclimate/translations/ca.json | 2 +- .../ambiclimate/translations/cs.json | 2 +- .../ambiclimate/translations/de.json | 2 +- .../ambiclimate/translations/fr.json | 2 +- .../ambiclimate/translations/nl.json | 2 +- .../ambiclimate/translations/ru.json | 2 +- .../ambiclimate/translations/zh-Hant.json | 2 +- .../components/arcam_fmj/translations/ca.json | 5 +++ .../components/arcam_fmj/translations/de.json | 5 +++ .../components/arcam_fmj/translations/es.json | 5 +++ .../components/arcam_fmj/translations/lb.json | 5 +++ .../components/arcam_fmj/translations/no.json | 5 +++ .../arcam_fmj/translations/pt-BR.json | 5 +++ .../components/arcam_fmj/translations/ru.json | 5 +++ .../arcam_fmj/translations/zh-Hant.json | 5 +++ .../components/atag/translations/pt-BR.json | 11 ++++++ .../components/blebox/translations/pt-BR.json | 11 ++++++ .../components/blink/translations/de.json | 23 ++++++++++++ .../components/blink/translations/es.json | 4 +- .../components/blink/translations/lb.json | 3 ++ .../components/blink/translations/pt-BR.json | 27 ++++++++++++++ .../components/bsblan/translations/ca.json | 1 + .../components/daikin/translations/pt-BR.json | 5 +++ .../flick_electric/translations/pt-BR.json | 12 ++++++ .../forked_daapd/translations/ca.json | 4 +- .../forked_daapd/translations/es.json | 18 +++++++-- .../forked_daapd/translations/lb.json | 8 +++- .../forked_daapd/translations/pt-BR.json | 37 +++++++++++++++++++ .../forked_daapd/translations/ru.json | 2 +- .../forked_daapd/translations/zh-Hant.json | 2 +- .../components/homekit/translations/ca.json | 1 + .../homekit/translations/pt-BR.json | 9 +++++ .../components/icloud/translations/pt-BR.json | 3 +- .../components/isy994/translations/ca.json | 2 + .../components/isy994/translations/es.json | 1 + .../components/isy994/translations/lb.json | 1 + .../components/isy994/translations/pt-BR.json | 25 +++++++++++++ .../juicenet/translations/pt-BR.json | 9 +++++ .../logi_circle/translations/ca.json | 2 +- .../logi_circle/translations/ru.json | 2 +- .../logi_circle/translations/zh-Hant.json | 2 +- .../lutron_caseta/translations/pt-BR.json | 17 +++++++++ .../components/onvif/translations/de.json | 1 + .../components/onvif/translations/lb.json | 1 + .../components/onvif/translations/pt-BR.json | 1 + .../onvif/translations/zh-Hant.json | 1 + .../components/ozw/translations/pt-BR.json | 9 +++++ .../components/pi_hole/translations/ca.json | 3 +- .../components/pi_hole/translations/de.json | 23 ++++++++++++ .../pi_hole/translations/pt-BR.json | 23 ++++++++++++ .../components/plex/translations/pt-BR.json | 8 ++++ .../components/point/translations/ca.json | 2 +- .../components/point/translations/ru.json | 2 +- .../point/translations/zh-Hant.json | 2 +- .../songpal/translations/pt-BR.json | 7 ++++ .../components/starline/translations/bg.json | 2 +- .../components/starline/translations/ca.json | 2 +- .../components/starline/translations/nl.json | 2 +- .../components/starline/translations/ru.json | 2 +- .../starline/translations/zh-Hant.json | 2 +- .../components/tuya/translations/pt-BR.json | 15 ++++++++ .../components/unifi/translations/pt-BR.json | 3 ++ .../components/upb/translations/ca.json | 3 +- .../components/upb/translations/pt-BR.json | 15 ++++++++ .../components/upnp/translations/ca.json | 1 + .../components/upnp/translations/de.json | 1 + .../components/upnp/translations/es.json | 1 + .../components/upnp/translations/lb.json | 1 + .../components/upnp/translations/pt-BR.json | 4 +- .../components/upnp/translations/ru.json | 1 + .../components/upnp/translations/zh-Hant.json | 1 + .../components/vizio/translations/pt-BR.json | 7 ++++ .../components/wiffi/translations/es.json | 3 +- .../components/wiffi/translations/lb.json | 3 +- .../components/wiffi/translations/pt-BR.json | 16 ++++++++ .../xiaomi_miio/translations/de.json | 3 +- .../xiaomi_miio/translations/lb.json | 3 +- .../xiaomi_miio/translations/pt-BR.json | 7 ++++ .../zerproc/translations/pt-BR.json | 14 +++++++ .../components/zha/translations/pt-BR.json | 3 ++ 82 files changed, 466 insertions(+), 37 deletions(-) create mode 100644 homeassistant/components/airvisual/translations/pt-BR.json create mode 100644 homeassistant/components/atag/translations/pt-BR.json create mode 100644 homeassistant/components/blebox/translations/pt-BR.json create mode 100644 homeassistant/components/blink/translations/de.json create mode 100644 homeassistant/components/blink/translations/pt-BR.json create mode 100644 homeassistant/components/flick_electric/translations/pt-BR.json create mode 100644 homeassistant/components/forked_daapd/translations/pt-BR.json create mode 100644 homeassistant/components/homekit/translations/pt-BR.json create mode 100644 homeassistant/components/isy994/translations/pt-BR.json create mode 100644 homeassistant/components/juicenet/translations/pt-BR.json create mode 100644 homeassistant/components/lutron_caseta/translations/pt-BR.json create mode 100644 homeassistant/components/ozw/translations/pt-BR.json create mode 100644 homeassistant/components/pi_hole/translations/de.json create mode 100644 homeassistant/components/pi_hole/translations/pt-BR.json create mode 100644 homeassistant/components/songpal/translations/pt-BR.json create mode 100644 homeassistant/components/tuya/translations/pt-BR.json create mode 100644 homeassistant/components/upb/translations/pt-BR.json create mode 100644 homeassistant/components/vizio/translations/pt-BR.json create mode 100644 homeassistant/components/wiffi/translations/pt-BR.json create mode 100644 homeassistant/components/xiaomi_miio/translations/pt-BR.json create mode 100644 homeassistant/components/zerproc/translations/pt-BR.json diff --git a/homeassistant/components/airvisual/translations/pt-BR.json b/homeassistant/components/airvisual/translations/pt-BR.json new file mode 100644 index 00000000000..1b5834254a8 --- /dev/null +++ b/homeassistant/components/airvisual/translations/pt-BR.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "invalid_api_key": "Chave de API fornecida \u00e9 inv\u00e1lida." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ambiclimate/translations/bg.json b/homeassistant/components/ambiclimate/translations/bg.json index e76a714d5b0..0472cfd33f1 100644 --- a/homeassistant/components/ambiclimate/translations/bg.json +++ b/homeassistant/components/ambiclimate/translations/bg.json @@ -14,7 +14,7 @@ }, "step": { "auth": { - "description": "\u041c\u043e\u043b\u044f, \u043f\u043e\u0441\u043b\u0435\u0434\u0432\u0430\u0439\u0442\u0435 \u0442\u043e\u0437\u0438 [link]({authorization_url}) \u0438 \u0420\u0430\u0437\u0440\u0435\u0448\u0435\u0442\u0435 \u0434\u043e\u0441\u0442\u044a\u043f\u0430 \u0434\u043e \u043f\u0440\u043e\u0444\u0438\u043b\u0430 \u0441\u0438 \u0432 Ambiclimate, \u0441\u043b\u0435\u0434 \u0442\u043e\u0432\u0430 \u0441\u0435 \u0432\u044a\u0440\u043d\u0435\u0442\u0435 \u0438 \u043d\u0430\u0442\u0438\u0441\u043d\u0435\u0442\u0435 \u0418\u0437\u043f\u0440\u0430\u0449\u0430\u043d\u0435 \u043f\u043e-\u0434\u043e\u043b\u0443. \n (\u0423\u0432\u0435\u0440\u0435\u0442\u0435 \u0441\u0435, \u0447\u0435 \u043f\u043e\u0441\u043e\u0447\u0435\u043d\u0438\u044f\u0442 url \u0437\u0430 \u043e\u0431\u0440\u0430\u0442\u043d\u0430 \u043f\u043e\u0432\u0438\u043a\u0432\u0430\u043d\u0435 \u0435 {cb_url})", + "description": "\u041c\u043e\u043b\u044f, \u043f\u043e\u0441\u043b\u0435\u0434\u0432\u0430\u0439\u0442\u0435 \u0442\u043e\u0437\u0438 [link]({authorization_url}) \u0438 **\u0420\u0430\u0437\u0440\u0435\u0448\u0435\u0442\u0435** \u0434\u043e\u0441\u0442\u044a\u043f\u0430 \u0434\u043e \u043f\u0440\u043e\u0444\u0438\u043b\u0430 \u0441\u0438 \u0432 Ambiclimate, \u0441\u043b\u0435\u0434 \u0442\u043e\u0432\u0430 \u0441\u0435 \u0432\u044a\u0440\u043d\u0435\u0442\u0435 \u0438 \u043d\u0430\u0442\u0438\u0441\u043d\u0435\u0442\u0435 **\u0418\u0437\u043f\u0440\u0430\u0449\u0430\u043d\u0435** \u043f\u043e-\u0434\u043e\u043b\u0443. \n (\u0423\u0432\u0435\u0440\u0435\u0442\u0435 \u0441\u0435, \u0447\u0435 \u043f\u043e\u0441\u043e\u0447\u0435\u043d\u0438\u044f\u0442 url \u0437\u0430 \u043e\u0431\u0440\u0430\u0442\u043d\u0430 \u043f\u043e\u0432\u0438\u043a\u0432\u0430\u043d\u0435 \u0435 {cb_url})", "title": "\u0410\u0443\u0442\u0435\u043d\u0442\u0438\u043a\u0438\u0440\u0430\u043d\u0435 \u0441 Ambiclimate" } } diff --git a/homeassistant/components/ambiclimate/translations/ca.json b/homeassistant/components/ambiclimate/translations/ca.json index 0b8ca963813..64ea45410d3 100644 --- a/homeassistant/components/ambiclimate/translations/ca.json +++ b/homeassistant/components/ambiclimate/translations/ca.json @@ -14,7 +14,7 @@ }, "step": { "auth": { - "description": "V\u00e9s a l'[enlla\u00e7]({authorization_url}) i Permet l'acc\u00e9s al teu compte de Ambiclimate, despr\u00e9s torna i prem Envia (a sota).\n(Assegura't que l'enlla\u00e7 de retorn \u00e9s el seg\u00fcent {cb_url})", + "description": "V\u00e9s a l'[enlla\u00e7]({authorization_url}) i **Permet** l'acc\u00e9s al teu compte de Ambiclimate, despr\u00e9s torna i prem **Envia** (a sota).\n(Assegura't que l'enlla\u00e7 de retorn \u00e9s el seg\u00fcent {cb_url})", "title": "Autenticaci\u00f3 amb Ambi Climate" } } diff --git a/homeassistant/components/ambiclimate/translations/cs.json b/homeassistant/components/ambiclimate/translations/cs.json index da0430346a7..2f2369429fe 100644 --- a/homeassistant/components/ambiclimate/translations/cs.json +++ b/homeassistant/components/ambiclimate/translations/cs.json @@ -6,7 +6,7 @@ }, "step": { "auth": { - "description": "N\u00e1sledujte tento [odkaz]({authorization_url}) a Povolit p\u0159\u00edstup k va\u0161emu \u00fa\u010dtu Ambiclimate, pot\u00e9 se vra\u0165te a stiskn\u011bte Odeslat n\u00ed\u017ee. \n (Ujist\u011bte se, \u017ee zadan\u00e1 adresa URL zp\u011btn\u00e9ho vol\u00e1n\u00ed je {cb_url} )", + "description": "N\u00e1sledujte tento [odkaz]({authorization_url}) a **Povolit** p\u0159\u00edstup k va\u0161emu \u00fa\u010dtu Ambiclimate, pot\u00e9 se vra\u0165te a stiskn\u011bte **Odeslat** n\u00ed\u017ee. \n (Ujist\u011bte se, \u017ee zadan\u00e1 adresa URL zp\u011btn\u00e9ho vol\u00e1n\u00ed je {cb_url} )", "title": "Ov\u011b\u0159it Ambiclimate" } } diff --git a/homeassistant/components/ambiclimate/translations/de.json b/homeassistant/components/ambiclimate/translations/de.json index 6fba5772a10..43618a8ab99 100644 --- a/homeassistant/components/ambiclimate/translations/de.json +++ b/homeassistant/components/ambiclimate/translations/de.json @@ -14,7 +14,7 @@ }, "step": { "auth": { - "description": "Bitte folge diesem [link] ({authorization_url}) und Erlaube Zugriff auf dein Ambiclimate-Konto, komme dann zur\u00fcck und dr\u00fccke Senden darunter.\n (Pr\u00fcfe, dass die Callback-URL {cb_url} ist.)", + "description": "Bitte folge diesem [link] ({authorization_url}) und **Erlaube** Zugriff auf dein Ambiclimate-Konto, komme dann zur\u00fcck und dr\u00fccke **Senden** darunter.\n (Pr\u00fcfe, dass die Callback-URL {cb_url} ist.)", "title": "Ambiclimate authentifizieren" } } diff --git a/homeassistant/components/ambiclimate/translations/fr.json b/homeassistant/components/ambiclimate/translations/fr.json index c16b0c10266..9c5864fcb0f 100644 --- a/homeassistant/components/ambiclimate/translations/fr.json +++ b/homeassistant/components/ambiclimate/translations/fr.json @@ -14,7 +14,7 @@ }, "step": { "auth": { - "description": "Suivez ce [lien] ( {authorization_url} ) et Autorisez l'acc\u00e8s \u00e0 votre compte Ambiclimate, puis revenez et appuyez sur Envoyer ci-dessous. \n (Assurez-vous que l'URL de rappel sp\u00e9cifi\u00e9 est {cb_url} )", + "description": "Suivez ce [lien]({authorization_url}) et **Autorisez** l'acc\u00e8s \u00e0 votre compte Ambiclimate, puis revenez et appuyez sur **Envoyer** ci-dessous. \n (Assurez-vous que l'URL de rappel sp\u00e9cifi\u00e9 est {cb_url})", "title": "Authentifier Ambiclimate" } } diff --git a/homeassistant/components/ambiclimate/translations/nl.json b/homeassistant/components/ambiclimate/translations/nl.json index 17e6dfa9c82..e65688af89d 100644 --- a/homeassistant/components/ambiclimate/translations/nl.json +++ b/homeassistant/components/ambiclimate/translations/nl.json @@ -14,7 +14,7 @@ }, "step": { "auth": { - "description": "Volg deze [link] ( {authorization_url} ) en Toestaan toegang tot uw Ambiclimate-account, kom dan terug en druk hieronder op Verzenden . \n (Zorg ervoor dat de opgegeven callback-URL {cb_url} )", + "description": "Volg deze [link]({authorization_url}) en klik op **Toestaan** om toegang te geven tot uw Ambiclimate-account, kom dan terug en druk hieronder op **Verzenden**. \n (Zorg ervoor dat de opgegeven callback-URL {cb_url})", "title": "Authenticatie Ambiclimate" } } diff --git a/homeassistant/components/ambiclimate/translations/ru.json b/homeassistant/components/ambiclimate/translations/ru.json index a1eefc78575..5712c99df22 100644 --- a/homeassistant/components/ambiclimate/translations/ru.json +++ b/homeassistant/components/ambiclimate/translations/ru.json @@ -14,7 +14,7 @@ }, "step": { "auth": { - "description": "\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u043f\u043e [\u0441\u0441\u044b\u043b\u043a\u0435]({authorization_url}) \u0438 \u0420\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u0435 \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u0412\u0430\u0448\u0435\u0439 \u0443\u0447\u0451\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438 Ambi Climate, \u0437\u0430\u0442\u0435\u043c \u0432\u0435\u0440\u043d\u0438\u0442\u0435\u0441\u044c \u0441\u044e\u0434\u0430 \u0438 \u043d\u0430\u0436\u043c\u0438\u0442\u0435 \u041f\u041e\u0414\u0422\u0412\u0415\u0420\u0414\u0418\u0422\u042c. \n(\u0423\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044c, \u0447\u0442\u043e \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0439 URL \u043e\u0431\u0440\u0430\u0442\u043d\u043e\u0433\u043e \u0432\u044b\u0437\u043e\u0432\u0430 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 {cb_url})", + "description": "\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u043f\u043e [\u0441\u0441\u044b\u043b\u043a\u0435]({authorization_url}) \u0438 **\u0420\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u0435** \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u0412\u0430\u0448\u0435\u0439 \u0443\u0447\u0451\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438 Ambi Climate, \u0437\u0430\u0442\u0435\u043c \u0432\u0435\u0440\u043d\u0438\u0442\u0435\u0441\u044c \u0441\u044e\u0434\u0430 \u0438 \u043d\u0430\u0436\u043c\u0438\u0442\u0435 **\u041f\u041e\u0414\u0422\u0412\u0415\u0420\u0414\u0418\u0422\u042c**. \n(\u0423\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044c, \u0447\u0442\u043e \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0439 URL \u043e\u0431\u0440\u0430\u0442\u043d\u043e\u0433\u043e \u0432\u044b\u0437\u043e\u0432\u0430 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 {cb_url})", "title": "Ambi Climate" } } diff --git a/homeassistant/components/ambiclimate/translations/zh-Hant.json b/homeassistant/components/ambiclimate/translations/zh-Hant.json index 2efd9f13549..7b995d09944 100644 --- a/homeassistant/components/ambiclimate/translations/zh-Hant.json +++ b/homeassistant/components/ambiclimate/translations/zh-Hant.json @@ -14,7 +14,7 @@ }, "step": { "auth": { - "description": "\u8acb\u4f7f\u7528\u6b64[\u9023\u7d50]\uff08{authorization_url}\uff09\u4e26\u9ede\u9078\u5141\u8a31\u4ee5\u5b58\u53d6 Ambiclimate \u5e33\u865f\uff0c\u7136\u5f8c\u8fd4\u56de\u6b64\u9801\u9762\u4e26\u9ede\u9078\u4e0b\u65b9\u7684\u50b3\u9001\u3002\n\uff08\u78ba\u5b9a Callback url \u70ba {cb_url}\uff09", + "description": "\u8acb\u4f7f\u7528\u6b64[\u9023\u7d50]\uff08{authorization_url}\uff09\u4e26\u9ede\u9078 **\u5141\u8a31** \u4ee5\u5b58\u53d6 Ambiclimate \u5e33\u865f\uff0c\u7136\u5f8c\u8fd4\u56de\u6b64\u9801\u9762\u4e26\u9ede\u9078\u4e0b\u65b9\u7684 **\u50b3\u9001**\u3002\n\uff08\u78ba\u5b9a Callback url \u70ba {cb_url}\uff09", "title": "\u8a8d\u8b49 Ambiclimate" } } diff --git a/homeassistant/components/arcam_fmj/translations/ca.json b/homeassistant/components/arcam_fmj/translations/ca.json index b78b8cbaa7b..c8cce461571 100644 --- a/homeassistant/components/arcam_fmj/translations/ca.json +++ b/homeassistant/components/arcam_fmj/translations/ca.json @@ -1,3 +1,8 @@ { + "device_automation": { + "trigger_type": { + "turn_on": "S'ha sol\u00b7licitat l'activaci\u00f3 de {entity_name}" + } + }, "title": "Arcam FMJ" } \ No newline at end of file diff --git a/homeassistant/components/arcam_fmj/translations/de.json b/homeassistant/components/arcam_fmj/translations/de.json index b78b8cbaa7b..cc744954121 100644 --- a/homeassistant/components/arcam_fmj/translations/de.json +++ b/homeassistant/components/arcam_fmj/translations/de.json @@ -1,3 +1,8 @@ { + "device_automation": { + "trigger_type": { + "turn_on": "{entity_name} wurde zum Einschalten aufgefordert" + } + }, "title": "Arcam FMJ" } \ No newline at end of file diff --git a/homeassistant/components/arcam_fmj/translations/es.json b/homeassistant/components/arcam_fmj/translations/es.json index b78b8cbaa7b..0205985aa95 100644 --- a/homeassistant/components/arcam_fmj/translations/es.json +++ b/homeassistant/components/arcam_fmj/translations/es.json @@ -1,3 +1,8 @@ { + "device_automation": { + "trigger_type": { + "turn_on": "Se solicit\u00f3 encender {entity_name}" + } + }, "title": "Arcam FMJ" } \ No newline at end of file diff --git a/homeassistant/components/arcam_fmj/translations/lb.json b/homeassistant/components/arcam_fmj/translations/lb.json index b78b8cbaa7b..6a56ce5f7c7 100644 --- a/homeassistant/components/arcam_fmj/translations/lb.json +++ b/homeassistant/components/arcam_fmj/translations/lb.json @@ -1,3 +1,8 @@ { + "device_automation": { + "trigger_type": { + "turn_on": "{entity_name} soll ugeschalt ginn" + } + }, "title": "Arcam FMJ" } \ No newline at end of file diff --git a/homeassistant/components/arcam_fmj/translations/no.json b/homeassistant/components/arcam_fmj/translations/no.json index d8a4c453015..8dcc7852d41 100644 --- a/homeassistant/components/arcam_fmj/translations/no.json +++ b/homeassistant/components/arcam_fmj/translations/no.json @@ -1,3 +1,8 @@ { + "device_automation": { + "trigger_type": { + "turn_on": "{entity_name} ble bedt om \u00e5 sl\u00e5 p\u00e5" + } + }, "title": "" } \ No newline at end of file diff --git a/homeassistant/components/arcam_fmj/translations/pt-BR.json b/homeassistant/components/arcam_fmj/translations/pt-BR.json index b78b8cbaa7b..0936f0bd9d8 100644 --- a/homeassistant/components/arcam_fmj/translations/pt-BR.json +++ b/homeassistant/components/arcam_fmj/translations/pt-BR.json @@ -1,3 +1,8 @@ { + "device_automation": { + "trigger_type": { + "turn_on": "Foi solicitado que {entity_name} ligue" + } + }, "title": "Arcam FMJ" } \ No newline at end of file diff --git a/homeassistant/components/arcam_fmj/translations/ru.json b/homeassistant/components/arcam_fmj/translations/ru.json index b78b8cbaa7b..be8c240918d 100644 --- a/homeassistant/components/arcam_fmj/translations/ru.json +++ b/homeassistant/components/arcam_fmj/translations/ru.json @@ -1,3 +1,8 @@ { + "device_automation": { + "trigger_type": { + "turn_on": "\u0437\u0430\u043f\u0440\u043e\u0448\u0435\u043d\u043e \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 {entity_name}" + } + }, "title": "Arcam FMJ" } \ No newline at end of file diff --git a/homeassistant/components/arcam_fmj/translations/zh-Hant.json b/homeassistant/components/arcam_fmj/translations/zh-Hant.json index b78b8cbaa7b..859aea4b08e 100644 --- a/homeassistant/components/arcam_fmj/translations/zh-Hant.json +++ b/homeassistant/components/arcam_fmj/translations/zh-Hant.json @@ -1,3 +1,8 @@ { + "device_automation": { + "trigger_type": { + "turn_on": "{entity_name} \u4f9d\u9700\u6c42\u958b\u555f" + } + }, "title": "Arcam FMJ" } \ No newline at end of file diff --git a/homeassistant/components/atag/translations/pt-BR.json b/homeassistant/components/atag/translations/pt-BR.json new file mode 100644 index 00000000000..2aec29e8eb0 --- /dev/null +++ b/homeassistant/components/atag/translations/pt-BR.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "email": "E-mail (Opcional)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/blebox/translations/pt-BR.json b/homeassistant/components/blebox/translations/pt-BR.json new file mode 100644 index 00000000000..f7dc708a2d6 --- /dev/null +++ b/homeassistant/components/blebox/translations/pt-BR.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "host": "Endere\u00e7o IP" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/blink/translations/de.json b/homeassistant/components/blink/translations/de.json new file mode 100644 index 00000000000..751d015ffdf --- /dev/null +++ b/homeassistant/components/blink/translations/de.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, + "step": { + "2fa": { + "data": { + "2fa": "Zwei-Faktor Authentifizierungscode" + }, + "description": "Geben Sie die an Ihre E-Mail gesendete Pin ein. Wenn die E-Mail keine PIN enth\u00e4lt, lassen Sie das Feld leer.", + "title": "Zwei-Faktor-Authentifizierung" + }, + "user": { + "data": { + "password": "Passwort", + "username": "Benutzername" + }, + "title": "Anmelden mit Blink-Konto" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/blink/translations/es.json b/homeassistant/components/blink/translations/es.json index d4adae6cce9..0606a28ee1f 100644 --- a/homeassistant/components/blink/translations/es.json +++ b/homeassistant/components/blink/translations/es.json @@ -12,13 +12,15 @@ "data": { "2fa": "C\u00f3digo de dos factores" }, + "description": "Introduce el pin enviado a tu correo electr\u00f3nico. Si el correo electr\u00f3nico no contiene un pin, d\u00e9jalo en blanco", "title": "Autenticaci\u00f3n de dos factores" }, "user": { "data": { "password": "Contrase\u00f1a", "username": "Usuario" - } + }, + "title": "Iniciar sesi\u00f3n con cuenta Blink" } } } diff --git a/homeassistant/components/blink/translations/lb.json b/homeassistant/components/blink/translations/lb.json index 33c975593c1..3e1d5a6c46e 100644 --- a/homeassistant/components/blink/translations/lb.json +++ b/homeassistant/components/blink/translations/lb.json @@ -6,6 +6,9 @@ "2fa": "2-Faktor Code" }, "title": "2-Faktor-Authentifikatioun" + }, + "user": { + "title": "Mam Blink Kont verbannen" } } } diff --git a/homeassistant/components/blink/translations/pt-BR.json b/homeassistant/components/blink/translations/pt-BR.json new file mode 100644 index 00000000000..70d8b8620c4 --- /dev/null +++ b/homeassistant/components/blink/translations/pt-BR.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "Dispositivo j\u00e1 configurado" + }, + "error": { + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "2fa": { + "data": { + "2fa": "C\u00f3digo de dois fatores" + }, + "description": "Digite o pin enviado para o seu e-mail. Se o e-mail n\u00e3o contiver um pin, deixe em branco", + "title": "Autentica\u00e7\u00e3o de dois fatores" + }, + "user": { + "data": { + "password": "Senha", + "username": "Nome de usu\u00e1rio" + }, + "title": "Entrar com a conta Blink" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/bsblan/translations/ca.json b/homeassistant/components/bsblan/translations/ca.json index 5df258a4f6c..2fcab5683de 100644 --- a/homeassistant/components/bsblan/translations/ca.json +++ b/homeassistant/components/bsblan/translations/ca.json @@ -14,6 +14,7 @@ "passkey": "String Passkey", "port": "Port" }, + "description": "Configura un dispositiu BSB-Lan per a integrar-lo amb Home Assistant.", "title": "Connexi\u00f3 amb dispositiu BSB-Lan" } } diff --git a/homeassistant/components/daikin/translations/pt-BR.json b/homeassistant/components/daikin/translations/pt-BR.json index 294e14b1071..8c4eaed25ae 100644 --- a/homeassistant/components/daikin/translations/pt-BR.json +++ b/homeassistant/components/daikin/translations/pt-BR.json @@ -5,6 +5,11 @@ "device_fail": "Erro inesperado ao criar dispositivo.", "device_timeout": "Excedido tempo limite conectando ao dispositivo" }, + "error": { + "device_fail": "Erro inesperado", + "device_timeout": "Falha ao conectar", + "forbidden": "Autentica\u00e7\u00e3o inv\u00e1lida" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/flick_electric/translations/pt-BR.json b/homeassistant/components/flick_electric/translations/pt-BR.json new file mode 100644 index 00000000000..df76dc5bb25 --- /dev/null +++ b/homeassistant/components/flick_electric/translations/pt-BR.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "already_configured": "Essa conta j\u00e1 est\u00e1 configurada" + }, + "error": { + "cannot_connect": "Falha ao conectar, tente novamente", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/forked_daapd/translations/ca.json b/homeassistant/components/forked_daapd/translations/ca.json index 5c299cbd808..2853da35bad 100644 --- a/homeassistant/components/forked_daapd/translations/ca.json +++ b/homeassistant/components/forked_daapd/translations/ca.json @@ -8,7 +8,8 @@ "unknown_error": "Error desconegut.", "websocket_not_enabled": "El websocket de forked-daapd no est\u00e0 activat.", "wrong_host_or_port": "No s'ha pogut connectar, verifica l'amfitri\u00f3 i el port.", - "wrong_password": "Contrasenya incorrecta." + "wrong_password": "Contrasenya incorrecta.", + "wrong_server_type": "La integraci\u00f3 forked-daapd necessita un servidor forked-daapd amb versi\u00f3 >= 27.0." }, "flow_title": "Servidor forked-daapd: {name} ({host})", "step": { @@ -26,6 +27,7 @@ "step": { "init": { "data": { + "librespot_java_port": "Port per al pipe control de librespot-java (si s'utilitza)", "max_playlists": "Nombre m\u00e0xim de llistes de reproducci\u00f3 utilitzades com a fonts", "tts_pause_time": "Segons de pausa abans i despr\u00e9s de TTS", "tts_volume": "Volum TTS (valor 'float' entre [0,1])" diff --git a/homeassistant/components/forked_daapd/translations/es.json b/homeassistant/components/forked_daapd/translations/es.json index 5cc63d67e1f..39215f2667d 100644 --- a/homeassistant/components/forked_daapd/translations/es.json +++ b/homeassistant/components/forked_daapd/translations/es.json @@ -1,12 +1,17 @@ { "config": { "abort": { - "already_configured": "El dispositivo ya est\u00e1 configurado." + "already_configured": "El dispositivo ya est\u00e1 configurado.", + "not_forked_daapd": "El dispositivo no es un servidor forked-daapd." }, "error": { "unknown_error": "Error desconocido.", - "wrong_password": "Contrase\u00f1a incorrecta." + "websocket_not_enabled": "Websocket no activado en servidor forked-daapd.", + "wrong_host_or_port": "No se ha podido conectar. Por favor comprueba host y puerto.", + "wrong_password": "Contrase\u00f1a incorrecta.", + "wrong_server_type": "La integraci\u00f3n forked-daapd requiere un servidor forked-daapd con versi\u00f3n >= 27.0." }, + "flow_title": "Servidor forked-daapd: {name} ({host})", "step": { "user": { "data": { @@ -14,7 +19,8 @@ "name": "Nombre amigable", "password": "Contrase\u00f1a API (dejar en blanco si no hay contrase\u00f1a)", "port": "Puerto API" - } + }, + "title": "Configurar dispositivo forked-daapd" } } }, @@ -22,9 +28,13 @@ "step": { "init": { "data": { + "librespot_java_port": "Puerto para control de tuber\u00eda librespot-java (si se usa)", + "max_playlists": "N\u00famero m\u00e1ximo de listas de reproducci\u00f3n utilizadas como fuentes", "tts_pause_time": "Segundos para pausar antes y despu\u00e9s del TTS", "tts_volume": "Volumen TTS (decimal en el rango [0,1])" - } + }, + "description": "Ajustar varias opciones para la integraci\u00f3n de forked-daapd", + "title": "Configurar opciones para forked-daapd" } } } diff --git a/homeassistant/components/forked_daapd/translations/lb.json b/homeassistant/components/forked_daapd/translations/lb.json index 33ddf13e605..071ab70b90e 100644 --- a/homeassistant/components/forked_daapd/translations/lb.json +++ b/homeassistant/components/forked_daapd/translations/lb.json @@ -1,13 +1,16 @@ { "config": { "abort": { - "already_configured": "Apparat ass scho konfigur\u00e9iert" + "already_configured": "Apparat ass scho konfigur\u00e9iert", + "not_forked_daapd": "Apparat ass kee forked-daapd server." }, "error": { "unknown_error": "Onbekannten Feeler.", + "websocket_not_enabled": "forked-daapd server websocket net aktiv.", "wrong_host_or_port": "Feeler beim verbannen, iwwerpr\u00e9if w.e.g d'Adresse a Port.", "wrong_password": "Ong\u00ebltegt Passwuert." }, + "flow_title": "forked-daapd server: {name} ({host})", "step": { "user": { "data": { @@ -15,7 +18,8 @@ "name": "Numm", "password": "API Passwuert (eidel loosse fir kee Passwuert)", "port": "API Port" - } + }, + "title": "forked-daapd Apparat ariichten" } } } diff --git a/homeassistant/components/forked_daapd/translations/pt-BR.json b/homeassistant/components/forked_daapd/translations/pt-BR.json new file mode 100644 index 00000000000..07645764606 --- /dev/null +++ b/homeassistant/components/forked_daapd/translations/pt-BR.json @@ -0,0 +1,37 @@ +{ + "config": { + "abort": { + "already_configured": "O dispositivo j\u00e1 est\u00e1 configurado.", + "not_forked_daapd": "O dispositivo n\u00e3o \u00e9 um servidor forked-daapd." + }, + "error": { + "unknown_error": "Erro desconhecido.", + "wrong_host_or_port": "N\u00e3o foi poss\u00edvel conectar. Por favor, verifique o endere\u00e7o e a porta.", + "wrong_password": "Senha incorreta." + }, + "step": { + "user": { + "data": { + "host": "Endere\u00e7o (IP)", + "name": "Nome amig\u00e1vel", + "password": "Senha da API (deixe em branco se n\u00e3o houver senha)", + "port": "Porta API" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "librespot_java_port": "Porta para controle de pipe librespot-java (se usado)", + "max_playlists": "N\u00famero m\u00e1ximo de listas de reprodu\u00e7\u00e3o usadas como fontes", + "tts_pause_time": "Segundos para pausar antes e depois do TTS", + "tts_volume": "Volume TTS (flutua\u00e7\u00e3o na faixa [0,1])" + }, + "description": "Defina v\u00e1rias op\u00e7\u00f5es para a integra\u00e7\u00e3o forked-daapd.", + "title": "Configurar op\u00e7\u00f5es forked-daapd" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/forked_daapd/translations/ru.json b/homeassistant/components/forked_daapd/translations/ru.json index 3774688e68a..89bd71cb041 100644 --- a/homeassistant/components/forked_daapd/translations/ru.json +++ b/homeassistant/components/forked_daapd/translations/ru.json @@ -9,7 +9,7 @@ "websocket_not_enabled": "\u0412\u0435\u0431-\u0441\u043e\u043a\u0435\u0442 forked-daapd \u043d\u0435 \u0432\u043a\u043b\u044e\u0447\u0435\u043d.", "wrong_host_or_port": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f, \u043f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u0430\u0434\u0440\u0435\u0441 \u0445\u043e\u0441\u0442\u0430.", "wrong_password": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043f\u0430\u0440\u043e\u043b\u044c.", - "wrong_server_type": "\u042d\u0442\u043e \u043d\u0435 \u0441\u0435\u0440\u0432\u0435\u0440 forked-daapd." + "wrong_server_type": "\u0422\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0441\u0435\u0440\u0432\u0435\u0440 forked-daapd \u0432\u0435\u0440\u0441\u0438\u0438 27.0 \u0438\u043b\u0438 \u0432\u044b\u0448\u0435." }, "flow_title": "\u0421\u0435\u0440\u0432\u0435\u0440 forked-daapd: {name} ({host})", "step": { diff --git a/homeassistant/components/forked_daapd/translations/zh-Hant.json b/homeassistant/components/forked_daapd/translations/zh-Hant.json index b6134c2b720..ec51bbacea3 100644 --- a/homeassistant/components/forked_daapd/translations/zh-Hant.json +++ b/homeassistant/components/forked_daapd/translations/zh-Hant.json @@ -9,7 +9,7 @@ "websocket_not_enabled": "forked-daapd \u4f3a\u670d\u5668 websocket \u672a\u958b\u555f\u3002", "wrong_host_or_port": "\u7121\u6cd5\u9023\u7dda\uff0c\u8acb\u78ba\u8a8d\u4e3b\u6a5f\u8207\u901a\u8a0a\u57e0\u3002", "wrong_password": "\u5bc6\u78bc\u932f\u8aa4\u3002", - "wrong_server_type": "\u975e forked-daapd \u4f3a\u670d\u5668\u3002" + "wrong_server_type": "forked-daapd \u6574\u5408\u9700\u8981\u7248\u6b21 >= 27.0 \u7248\u4e4b forked-daapd \u4f3a\u670d\u5668\u3002" }, "flow_title": "forked-daapd \u4f3a\u670d\u5668\uff1a{name} ({host})", "step": { diff --git a/homeassistant/components/homekit/translations/ca.json b/homeassistant/components/homekit/translations/ca.json index 79d2139fedc..0282b701ad7 100644 --- a/homeassistant/components/homekit/translations/ca.json +++ b/homeassistant/components/homekit/translations/ca.json @@ -33,6 +33,7 @@ "data": { "camera_copy": "C\u00e0meres que admeten fluxos H.264 natius" }, + "description": "Comprova les c\u00e0meres que suporten fluxos nadius H.264. Si alguna c\u00e0mera not proporciona una sortida H.264, el sistema transcodificar\u00e0 el v\u00eddeo a H.264 per a HomeKit. La transcodificaci\u00f3 necessita una CPU potent i probablement no funcioni en ordinadors petits (SBC).", "title": "Selecci\u00f3 del c\u00f2dec de v\u00eddeo de c\u00e0mera" }, "exclude": { diff --git a/homeassistant/components/homekit/translations/pt-BR.json b/homeassistant/components/homekit/translations/pt-BR.json new file mode 100644 index 00000000000..290e588fd5d --- /dev/null +++ b/homeassistant/components/homekit/translations/pt-BR.json @@ -0,0 +1,9 @@ +{ + "options": { + "step": { + "cameras": { + "title": "Selecione o codec de v\u00eddeo da c\u00e2mera." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/icloud/translations/pt-BR.json b/homeassistant/components/icloud/translations/pt-BR.json index 364c0aca85c..219930a94fd 100644 --- a/homeassistant/components/icloud/translations/pt-BR.json +++ b/homeassistant/components/icloud/translations/pt-BR.json @@ -18,7 +18,8 @@ }, "user": { "data": { - "password": "Senha" + "password": "Senha", + "username": "E-mail" }, "description": "Insira suas credenciais", "title": "credenciais do iCloud" diff --git a/homeassistant/components/isy994/translations/ca.json b/homeassistant/components/isy994/translations/ca.json index aa9c188f8dc..1fd7c21793e 100644 --- a/homeassistant/components/isy994/translations/ca.json +++ b/homeassistant/components/isy994/translations/ca.json @@ -6,6 +6,7 @@ "error": { "cannot_connect": "[%key::common::config_flow::error::cannot_connect%]", "invalid_auth": "[%key::common::config_flow::error::invalid_auth%]", + "invalid_host": "L'entrada de l'amfitri\u00f3 no t\u00e9 el fromat d'URL complet, ex: http://192.168.10.100:80", "unknown": "Error inesperat" }, "flow_title": "Dispositius universals ISY994 {name} ({host})", @@ -17,6 +18,7 @@ "tls": "Versi\u00f3 TLS del controlador ISY.", "username": "[%key::common::config_flow::data::username%]" }, + "description": "L'entrada de l'amfitri\u00f3 ha de tenir el format d'URL complet, ex: http://192.168.10.100:80", "title": "Connexi\u00f3 amb ISY994" } } diff --git a/homeassistant/components/isy994/translations/es.json b/homeassistant/components/isy994/translations/es.json index 1edd249e9d7..23ed579d473 100644 --- a/homeassistant/components/isy994/translations/es.json +++ b/homeassistant/components/isy994/translations/es.json @@ -9,6 +9,7 @@ "invalid_host": "La entrada del host no estaba en formato URL completo, por ejemplo, http://192.168.10.100:80", "unknown": "Error inesperado" }, + "flow_title": "Dispositivos Universales ISY994 {nombre} ({host})", "step": { "user": { "data": { diff --git a/homeassistant/components/isy994/translations/lb.json b/homeassistant/components/isy994/translations/lb.json index 4d7d4cc47d7..b60ce03e43a 100644 --- a/homeassistant/components/isy994/translations/lb.json +++ b/homeassistant/components/isy994/translations/lb.json @@ -9,6 +9,7 @@ "invalid_host": "Host Entr\u00e9e muss am URL Format sinn, beispill, http://192.168.10.100:80", "unknown": "Onerwaarte Feeler" }, + "flow_title": "Universal Devices ISY994 {name} ({host})", "step": { "user": { "data": { diff --git a/homeassistant/components/isy994/translations/pt-BR.json b/homeassistant/components/isy994/translations/pt-BR.json new file mode 100644 index 00000000000..b4779e70884 --- /dev/null +++ b/homeassistant/components/isy994/translations/pt-BR.json @@ -0,0 +1,25 @@ +{ + "config": { + "flow_title": "Dispositivos universais ISY994 {name} ({host})", + "step": { + "user": { + "data": { + "host": "URL" + }, + "description": "A entrada do endere\u00e7o deve estar no formato de URL completo, por exemplo, http://192.168.10.100:80", + "title": "Conecte-se ao seu ISY994" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "ignore_string": "Ignorar texto" + }, + "title": "ISY994 Op\u00e7\u00f5es" + } + } + }, + "title": "Dispositivos universais ISY994" +} \ No newline at end of file diff --git a/homeassistant/components/juicenet/translations/pt-BR.json b/homeassistant/components/juicenet/translations/pt-BR.json new file mode 100644 index 00000000000..281a9dc8931 --- /dev/null +++ b/homeassistant/components/juicenet/translations/pt-BR.json @@ -0,0 +1,9 @@ +{ + "config": { + "error": { + "cannot_connect": "Falha ao conectar, tente novamente", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/logi_circle/translations/ca.json b/homeassistant/components/logi_circle/translations/ca.json index 8b81f752058..97fcaf575fc 100644 --- a/homeassistant/components/logi_circle/translations/ca.json +++ b/homeassistant/components/logi_circle/translations/ca.json @@ -16,7 +16,7 @@ }, "step": { "auth": { - "description": "V\u00e9s a l'enlla\u00e7 de sota i Accepta l'acc\u00e9s al teu compte de Logi Circle, despr\u00e9s, torna i prem Envia (tamb\u00e9 a sota).\n\n[Enlla\u00e7]({authorization_url})", + "description": "V\u00e9s a l'enlla\u00e7 de sota i **Accepta** l'acc\u00e9s al teu compte de Logi Circle, despr\u00e9s torna i prem **Envia** (tamb\u00e9 a sota).\n\n[Enlla\u00e7]({authorization_url})", "title": "Autenticaci\u00f3 amb Logi Circle" }, "user": { diff --git a/homeassistant/components/logi_circle/translations/ru.json b/homeassistant/components/logi_circle/translations/ru.json index 906b647bbef..60db9528cef 100644 --- a/homeassistant/components/logi_circle/translations/ru.json +++ b/homeassistant/components/logi_circle/translations/ru.json @@ -16,7 +16,7 @@ }, "step": { "auth": { - "description": "\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u043f\u043e [\u0441\u0441\u044b\u043b\u043a\u0435]({authorization_url}) \u0438 \u0420\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u0435 \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u0412\u0430\u0448\u0435\u0439 \u0443\u0447\u0451\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438 Logi Circle, \u0437\u0430\u0442\u0435\u043c \u0432\u0435\u0440\u043d\u0438\u0442\u0435\u0441\u044c \u0441\u044e\u0434\u0430 \u0438 \u043d\u0430\u0436\u043c\u0438\u0442\u0435 \u041f\u041e\u0414\u0422\u0412\u0415\u0420\u0414\u0418\u0422\u042c.", + "description": "\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u043f\u043e [\u0441\u0441\u044b\u043b\u043a\u0435]({authorization_url}) \u0438 **\u0420\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u0435** \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u0412\u0430\u0448\u0435\u0439 \u0443\u0447\u0451\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438 Logi Circle, \u0437\u0430\u0442\u0435\u043c \u0432\u0435\u0440\u043d\u0438\u0442\u0435\u0441\u044c \u0441\u044e\u0434\u0430 \u0438 \u043d\u0430\u0436\u043c\u0438\u0442\u0435 **\u041f\u041e\u0414\u0422\u0412\u0415\u0420\u0414\u0418\u0422\u042c**.", "title": "Logi Circle" }, "user": { diff --git a/homeassistant/components/logi_circle/translations/zh-Hant.json b/homeassistant/components/logi_circle/translations/zh-Hant.json index a94269808d3..6602491c8a2 100644 --- a/homeassistant/components/logi_circle/translations/zh-Hant.json +++ b/homeassistant/components/logi_circle/translations/zh-Hant.json @@ -16,7 +16,7 @@ }, "step": { "auth": { - "description": "\u8acb\u4f7f\u7528\u4e0b\u65b9\u9023\u7d50\u4e26\u9ede\u9078\u63a5\u53d7\u4ee5\u5b58\u53d6 Logi Circle \u5e33\u865f\uff0c\u7136\u5f8c\u8fd4\u56de\u6b64\u9801\u9762\u4e26\u9ede\u9078\u4e0b\u65b9\u7684\u50b3\u9001\u3002\n\n[Link]({authorization_url})", + "description": "\u8acb\u4f7f\u7528\u4e0b\u65b9\u9023\u7d50\u4e26\u9ede\u9078 **\u63a5\u53d7** \u4ee5\u5b58\u53d6 Logi Circle \u5e33\u865f\uff0c\u7136\u5f8c\u8fd4\u56de\u6b64\u9801\u9762\u4e26\u9ede\u9078\u4e0b\u65b9\u7684 **\u50b3\u9001**\u3002\n\n[\u9023\u7d50]({authorization_url})", "title": "\u4ee5 Logi Circle \u8a8d\u8b49" }, "user": { diff --git a/homeassistant/components/lutron_caseta/translations/pt-BR.json b/homeassistant/components/lutron_caseta/translations/pt-BR.json new file mode 100644 index 00000000000..091f7990989 --- /dev/null +++ b/homeassistant/components/lutron_caseta/translations/pt-BR.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "already_configured": "Ponte Cas\u00e9ta j\u00e1 configurada.", + "cannot_connect": "Instala\u00e7\u00e3o cancelada da ponte Cas\u00e9ta devido \u00e0 falha na conex\u00e3o." + }, + "error": { + "cannot_connect": "Falha ao conectar \u00e0 ponte Cas\u00e9ta; verifique sua configura\u00e7\u00e3o de endere\u00e7o e certificado." + }, + "step": { + "import_failed": { + "description": "N\u00e3o foi poss\u00edvel configurar a ponte (host: {host}) importada do configuration.yaml.", + "title": "Falha ao importar a configura\u00e7\u00e3o da ponte Cas\u00e9ta." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/onvif/translations/de.json b/homeassistant/components/onvif/translations/de.json index 2a8dcb1b67b..46aeb515716 100644 --- a/homeassistant/components/onvif/translations/de.json +++ b/homeassistant/components/onvif/translations/de.json @@ -34,6 +34,7 @@ "manual_input": { "data": { "host": "Host", + "name": "Name", "port": "Port" }, "title": "Konfigurieren Sie das ONVIF-Ger\u00e4t" diff --git a/homeassistant/components/onvif/translations/lb.json b/homeassistant/components/onvif/translations/lb.json index fb91a80dd1c..024a673c442 100644 --- a/homeassistant/components/onvif/translations/lb.json +++ b/homeassistant/components/onvif/translations/lb.json @@ -34,6 +34,7 @@ "manual_input": { "data": { "host": "Apparat", + "name": "Numm", "port": "Port" }, "title": "ONVIF Apparat ariichten" diff --git a/homeassistant/components/onvif/translations/pt-BR.json b/homeassistant/components/onvif/translations/pt-BR.json index 3eb03c86f52..7d8689cfeae 100644 --- a/homeassistant/components/onvif/translations/pt-BR.json +++ b/homeassistant/components/onvif/translations/pt-BR.json @@ -34,6 +34,7 @@ "manual_input": { "data": { "host": "Endere\u00e7o (IP)", + "name": "Nome", "port": "Porta" }, "title": "Configurar dispositivo ONVIF" diff --git a/homeassistant/components/onvif/translations/zh-Hant.json b/homeassistant/components/onvif/translations/zh-Hant.json index c7a0f88d1b9..c96eb2138f1 100644 --- a/homeassistant/components/onvif/translations/zh-Hant.json +++ b/homeassistant/components/onvif/translations/zh-Hant.json @@ -34,6 +34,7 @@ "manual_input": { "data": { "host": "\u4e3b\u6a5f\u7aef", + "name": "\u540d\u7a31", "port": "\u901a\u8a0a\u57e0" }, "title": "\u8a2d\u5b9a ONVIF \u8a2d\u5099" diff --git a/homeassistant/components/ozw/translations/pt-BR.json b/homeassistant/components/ozw/translations/pt-BR.json new file mode 100644 index 00000000000..f08cdc09053 --- /dev/null +++ b/homeassistant/components/ozw/translations/pt-BR.json @@ -0,0 +1,9 @@ +{ + "config": { + "step": { + "user": { + "title": "Confirme a configura\u00e7\u00e3o" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/pi_hole/translations/ca.json b/homeassistant/components/pi_hole/translations/ca.json index d50376c7545..7ba84de21f7 100644 --- a/homeassistant/components/pi_hole/translations/ca.json +++ b/homeassistant/components/pi_hole/translations/ca.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "El servei ja est\u00e0 configurat" + "already_configured": "El servei ja est\u00e0 configurat", + "duplicated_name": "El nom ja existeix" }, "error": { "cannot_connect": "No s'ha pogut connectar" diff --git a/homeassistant/components/pi_hole/translations/de.json b/homeassistant/components/pi_hole/translations/de.json new file mode 100644 index 00000000000..91655e7245d --- /dev/null +++ b/homeassistant/components/pi_hole/translations/de.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Service ist bereits konfiguriert", + "duplicated_name": "Name existiert bereits" + }, + "error": { + "cannot_connect": "Verbindung konnte nicht hergestellt werden" + }, + "step": { + "user": { + "data": { + "api_key": "API-Schl\u00fcssel (optional)", + "host": "Host", + "name": "Name", + "port": "Port", + "ssl": "SSL verwenden", + "verify_ssl": "SSL-Zertifikat \u00fcberpr\u00fcfen" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/pi_hole/translations/pt-BR.json b/homeassistant/components/pi_hole/translations/pt-BR.json new file mode 100644 index 00000000000..c268b1182ce --- /dev/null +++ b/homeassistant/components/pi_hole/translations/pt-BR.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Servi\u00e7o j\u00e1 configurado", + "duplicated_name": "O nome j\u00e1 existe" + }, + "error": { + "cannot_connect": "Falha ao conectar" + }, + "step": { + "user": { + "data": { + "api_key": "Chave de API (Opcional)", + "host": "Endere\u00e7o (IP)", + "name": "Nome", + "port": "Porta", + "ssl": "Usar SSL", + "verify_ssl": "Verifique o certificado SSL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/plex/translations/pt-BR.json b/homeassistant/components/plex/translations/pt-BR.json index 0248fc94857..aabc5499525 100644 --- a/homeassistant/components/plex/translations/pt-BR.json +++ b/homeassistant/components/plex/translations/pt-BR.json @@ -2,6 +2,14 @@ "config": { "abort": { "non-interactive": "Importa\u00e7\u00e3o n\u00e3o interativa" + }, + "flow_title": "{name} ({host})", + "step": { + "manual_setup": { + "data": { + "ssl": "Usar SSL" + } + } } }, "options": { diff --git a/homeassistant/components/point/translations/ca.json b/homeassistant/components/point/translations/ca.json index 84674cafb89..85fffddaf36 100644 --- a/homeassistant/components/point/translations/ca.json +++ b/homeassistant/components/point/translations/ca.json @@ -16,7 +16,7 @@ }, "step": { "auth": { - "description": "V\u00e9s a l'enlla\u00e7 seg\u00fcent i Accepta l'acc\u00e9s al teu compte de Minut, despr\u00e9s torna i prem Envia (a sota). \n\n[Enlla\u00e7]({authorization_url})", + "description": "V\u00e9s a l'enlla\u00e7 seg\u00fcent i **Accepta** l'acc\u00e9s al teu compte de Minut, despr\u00e9s torna i prem **Envia** (a sota). \n\n[Enlla\u00e7]({authorization_url})", "title": "Autenticar Point" }, "user": { diff --git a/homeassistant/components/point/translations/ru.json b/homeassistant/components/point/translations/ru.json index 8c481dc0305..a8dbb47b400 100644 --- a/homeassistant/components/point/translations/ru.json +++ b/homeassistant/components/point/translations/ru.json @@ -16,7 +16,7 @@ }, "step": { "auth": { - "description": "\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u043f\u043e [\u0441\u0441\u044b\u043b\u043a\u0435]({authorization_url}) \u0438 \u0420\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u0435 \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u0412\u0430\u0448\u0435\u0439 \u0443\u0447\u0451\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438 Minut, \u0437\u0430\u0442\u0435\u043c \u0432\u0435\u0440\u043d\u0438\u0442\u0435\u0441\u044c \u0441\u044e\u0434\u0430 \u0438 \u043d\u0430\u0436\u043c\u0438\u0442\u0435 \u041f\u041e\u0414\u0422\u0412\u0415\u0420\u0414\u0418\u0422\u042c.", + "description": "\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u043f\u043e [\u0441\u0441\u044b\u043b\u043a\u0435]({authorization_url}) \u0438 **\u0420\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u0435** \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u0412\u0430\u0448\u0435\u0439 \u0443\u0447\u0451\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438 Minut, \u0437\u0430\u0442\u0435\u043c \u0432\u0435\u0440\u043d\u0438\u0442\u0435\u0441\u044c \u0441\u044e\u0434\u0430 \u0438 \u043d\u0430\u0436\u043c\u0438\u0442\u0435 **\u041f\u041e\u0414\u0422\u0412\u0415\u0420\u0414\u0418\u0422\u042c**.", "title": "Minut Point" }, "user": { diff --git a/homeassistant/components/point/translations/zh-Hant.json b/homeassistant/components/point/translations/zh-Hant.json index 618480cb771..bd0532c1ae2 100644 --- a/homeassistant/components/point/translations/zh-Hant.json +++ b/homeassistant/components/point/translations/zh-Hant.json @@ -16,7 +16,7 @@ }, "step": { "auth": { - "description": "\u8acb\u4f7f\u7528\u4e0b\u65b9\u9023\u7d50\u4e26\u9ede\u9078\u63a5\u53d7\u4ee5\u5b58\u53d6 Minut \u5e33\u865f\uff0c\u7136\u5f8c\u8fd4\u56de\u6b64\u9801\u9762\u4e26\u9ede\u9078\u4e0b\u65b9\u7684\u50b3\u9001\u3002\n\n[Link]({authorization_url})", + "description": "\u8acb\u4f7f\u7528\u4e0b\u65b9\u9023\u7d50\u4e26\u9ede\u9078 **\u63a5\u53d7** \u4ee5\u5b58\u53d6 Minut \u5e33\u865f\uff0c\u7136\u5f8c\u8fd4\u56de\u6b64\u9801\u9762\u4e26\u9ede\u9078\u4e0b\u65b9\u7684 **\u50b3\u9001**\u3002\n\n[\u9023\u7d50]({authorization_url})", "title": "\u8a8d\u8b49 Point" }, "user": { diff --git a/homeassistant/components/songpal/translations/pt-BR.json b/homeassistant/components/songpal/translations/pt-BR.json new file mode 100644 index 00000000000..110e7413121 --- /dev/null +++ b/homeassistant/components/songpal/translations/pt-BR.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "cannot_connect": "Falha ao conectar" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/starline/translations/bg.json b/homeassistant/components/starline/translations/bg.json index 1f3fb32b8e3..af8242ce712 100644 --- a/homeassistant/components/starline/translations/bg.json +++ b/homeassistant/components/starline/translations/bg.json @@ -11,7 +11,7 @@ "app_id": "ID \u043d\u0430 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435", "app_secret": "\u0422\u0430\u0439\u043d\u0430" }, - "description": "\u0418\u0414 \u043d\u0430 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0438 \u0442\u0430\u0435\u043d \u043a\u043e\u0434 \u043e\u0442 StarLine \u0430\u043a\u0430\u0443\u043d\u0442 \u043d\u0430 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a", + "description": "\u0418\u0414 \u043d\u0430 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0438 \u0442\u0430\u0435\u043d \u043a\u043e\u0434 \u043e\u0442 [StarLine \u0430\u043a\u0430\u0443\u043d\u0442 \u043d\u0430 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a](https://my.starline.ru/developer)", "title": "\u0418\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u043e\u043d\u043d\u0438 \u0434\u0430\u043d\u043d\u0438 \u0437\u0430 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u0442\u043e" }, "auth_captcha": { diff --git a/homeassistant/components/starline/translations/ca.json b/homeassistant/components/starline/translations/ca.json index 722b65da2a8..58545ec9bfd 100644 --- a/homeassistant/components/starline/translations/ca.json +++ b/homeassistant/components/starline/translations/ca.json @@ -11,7 +11,7 @@ "app_id": "ID d'aplicaci\u00f3", "app_secret": "Secret" }, - "description": "ID d'aplicaci\u00f3 i codi secret de compte de desenvolupador de StarLine", + "description": "ID d'aplicaci\u00f3 i codi secret del [compte de desenvolupador de StarLine](https://my.starline.ru/developer)", "title": "Credencials d'aplicaci\u00f3" }, "auth_captcha": { diff --git a/homeassistant/components/starline/translations/nl.json b/homeassistant/components/starline/translations/nl.json index e92372f3d86..9763a0422a5 100644 --- a/homeassistant/components/starline/translations/nl.json +++ b/homeassistant/components/starline/translations/nl.json @@ -11,7 +11,7 @@ "app_id": "Toepassings-ID", "app_secret": "Geheime code" }, - "description": "Toepassings-ID en de geheime code van StarLine developer account", + "description": "Applicatie-ID en geheime code van [StarLine-ontwikkelaarsaccount] (https://my.starline.ru/developer)", "title": "Inloggegevens van de applicatie" }, "auth_captcha": { diff --git a/homeassistant/components/starline/translations/ru.json b/homeassistant/components/starline/translations/ru.json index 156f7fb8262..ea6833f5842 100644 --- a/homeassistant/components/starline/translations/ru.json +++ b/homeassistant/components/starline/translations/ru.json @@ -11,7 +11,7 @@ "app_id": "ID \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f", "app_secret": "\u0421\u0435\u043a\u0440\u0435\u0442\u043d\u044b\u0439 \u043a\u043e\u0434" }, - "description": "ID \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0438 \u0441\u0435\u043a\u0440\u0435\u0442\u043d\u044b\u0439 \u043a\u043e\u0434 \u0438\u0437 \u0443\u0447\u0435\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430 StarLine", + "description": "ID \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0438 \u0441\u0435\u043a\u0440\u0435\u0442\u043d\u044b\u0439 \u043a\u043e\u0434 [\u0443\u0447\u0435\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430 StarLine](https://my.starline.ru/developer)", "title": "\u0423\u0447\u0451\u0442\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f" }, "auth_captcha": { diff --git a/homeassistant/components/starline/translations/zh-Hant.json b/homeassistant/components/starline/translations/zh-Hant.json index d1635d0bc27..81a65ac0405 100644 --- a/homeassistant/components/starline/translations/zh-Hant.json +++ b/homeassistant/components/starline/translations/zh-Hant.json @@ -11,7 +11,7 @@ "app_id": "App ID", "app_secret": "\u5bc6\u78bc" }, - "description": "Application ID and secret code \u7531 StarLine \u958b\u767c\u8005\u5e33\u865f \u6240\u53d6\u5f97\u7684\u61c9\u7528\u7a0b\u5f0f ID \u8207\u5bc6\u78bc", + "description": "\u7531 [StarLine \u958b\u767c\u8005\u5e33\u865f] (https://my.starline.ru/developer) \u6240\u53d6\u5f97\u4e4b\u61c9\u7528\u7a0b\u5f0f ID \u8207\u5bc6\u78bc", "title": "\u61c9\u7528\u6191\u8b49" }, "auth_captcha": { diff --git a/homeassistant/components/tuya/translations/pt-BR.json b/homeassistant/components/tuya/translations/pt-BR.json new file mode 100644 index 00000000000..0eb07ce346d --- /dev/null +++ b/homeassistant/components/tuya/translations/pt-BR.json @@ -0,0 +1,15 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "Senha", + "platform": "O aplicativo onde sua conta \u00e9 registrada", + "username": "Nome de usu\u00e1rio" + }, + "description": "Digite sua credencial Tuya.", + "title": "Tuya" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/unifi/translations/pt-BR.json b/homeassistant/components/unifi/translations/pt-BR.json index 6372ed941db..da3368af5b4 100644 --- a/homeassistant/components/unifi/translations/pt-BR.json +++ b/homeassistant/components/unifi/translations/pt-BR.json @@ -37,6 +37,9 @@ "one": "um", "other": "uns" } + }, + "simple_options": { + "description": "Configurar integra\u00e7\u00e3o UniFi" } } } diff --git a/homeassistant/components/upb/translations/ca.json b/homeassistant/components/upb/translations/ca.json index b54e2816572..92785937145 100644 --- a/homeassistant/components/upb/translations/ca.json +++ b/homeassistant/components/upb/translations/ca.json @@ -5,7 +5,7 @@ }, "error": { "cannot_connect": "No s'ha pogut connectar a UPB PIM, torna-ho a provar.", - "invalid_upb_file": "El fitxer d\u2019exportaci\u00f3 UPB UPStart no hi \u00e9s o \u00e9s erroni, comprova el nom i la ruta del fitxer.", + "invalid_upb_file": "El fitxer d'exportaci\u00f3 UPB UPStart no hi \u00e9s o \u00e9s erroni, comprova el nom i la ruta del fitxer.", "unknown": "Error inesperat." }, "step": { @@ -15,6 +15,7 @@ "file_path": "Ruta i nom del fitxer d'exportaci\u00f3 UPStart UPB.", "protocol": "Protocol" }, + "description": "Connexi\u00f3 amb un m\u00f2dul Universal Powerline Bus Powerline Interface (UPB PIM). La cadena de car\u00e0cters (string) de l'adre\u00e7a ha de tenir el format: 'adre\u00e7a[:port]' per a 'TCP'. El port \u00e9s opcional, per defecte \u00e9s el 2101. Exemple: '192.168.1.42'. Per al protocol s\u00e8rie, l'adre\u00e7a ha de tenir el format 'tty[:baud]'. La velocitat en bauds \u00e9s opcional (4800 per defecte). Exemple: '/dev/ttyS1'.", "title": "Connexi\u00f3 amb UPB PIM" } } diff --git a/homeassistant/components/upb/translations/pt-BR.json b/homeassistant/components/upb/translations/pt-BR.json new file mode 100644 index 00000000000..7ce0f1c613a --- /dev/null +++ b/homeassistant/components/upb/translations/pt-BR.json @@ -0,0 +1,15 @@ +{ + "config": { + "error": { + "unknown": "Erro inesperado." + }, + "step": { + "user": { + "data": { + "address": "Endere\u00e7o (veja a descri\u00e7\u00e3o acima)", + "protocol": "Protocolo" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/upnp/translations/ca.json b/homeassistant/components/upnp/translations/ca.json index e0824e5f9bb..0559ee087e7 100644 --- a/homeassistant/components/upnp/translations/ca.json +++ b/homeassistant/components/upnp/translations/ca.json @@ -3,6 +3,7 @@ "abort": { "already_configured": "UPnP/IGD ja est\u00e0 configurat", "incomplete_device": "Ignorant el dispositiu incomplet UPnP", + "incomplete_discovery": "Descoberta incompleta", "no_devices_discovered": "No s'ha trobat cap UPnP/IGD", "no_devices_found": "No s'han trobat dispositius UPnP/IGD a la xarxa.", "no_sensors_or_port_mapping": "Activa, com a m\u00ednim, els sensors o l'assignaci\u00f3 de ports", diff --git a/homeassistant/components/upnp/translations/de.json b/homeassistant/components/upnp/translations/de.json index bfe95b10a39..66e43e22d46 100644 --- a/homeassistant/components/upnp/translations/de.json +++ b/homeassistant/components/upnp/translations/de.json @@ -3,6 +3,7 @@ "abort": { "already_configured": "UPnP/IGD ist bereits konfiguriert", "incomplete_device": "Unvollst\u00e4ndiges UPnP-Ger\u00e4t wird ignoriert", + "incomplete_discovery": "Unvollst\u00e4ndige Suche", "no_devices_discovered": "Keine UPnP/IGDs entdeckt", "no_devices_found": "Keine UPnP/IGD-Ger\u00e4te im Netzwerk gefunden.", "no_sensors_or_port_mapping": "Aktiviere mindestens Sensoren oder Port-Mapping", diff --git a/homeassistant/components/upnp/translations/es.json b/homeassistant/components/upnp/translations/es.json index 6ca30b11339..84b5f2831e2 100644 --- a/homeassistant/components/upnp/translations/es.json +++ b/homeassistant/components/upnp/translations/es.json @@ -3,6 +3,7 @@ "abort": { "already_configured": "UPnP / IGD ya est\u00e1 configurado", "incomplete_device": "Ignorando el dispositivo UPnP incompleto", + "incomplete_discovery": "Descubrimiento incompleto", "no_devices_discovered": "No se descubrieron UPnP / IGDs", "no_devices_found": "No se encuentran dispositivos UPnP/IGD en la red.", "no_sensors_or_port_mapping": "Habilitar al menos sensores o mapeo de puertos", diff --git a/homeassistant/components/upnp/translations/lb.json b/homeassistant/components/upnp/translations/lb.json index e30bae93d06..451d9fe78f0 100644 --- a/homeassistant/components/upnp/translations/lb.json +++ b/homeassistant/components/upnp/translations/lb.json @@ -3,6 +3,7 @@ "abort": { "already_configured": "UPnP/IGD ass scho konfigur\u00e9iert", "incomplete_device": "Ignor\u00e9iert onvollst\u00e4nnegen UPnP-Apparat", + "incomplete_discovery": "Entdeckung net komplett", "no_devices_discovered": "Keng UPnP/IGDs entdeckt", "no_devices_found": "Keng UPnP/IGD Apparater am Netzwierk fonnt.", "no_sensors_or_port_mapping": "Aktiv\u00e9ier op mannst Sensoren oder Port Mapping", diff --git a/homeassistant/components/upnp/translations/pt-BR.json b/homeassistant/components/upnp/translations/pt-BR.json index d472fa18834..07e81226dd2 100644 --- a/homeassistant/components/upnp/translations/pt-BR.json +++ b/homeassistant/components/upnp/translations/pt-BR.json @@ -3,6 +3,7 @@ "abort": { "already_configured": "UPnP / IGD j\u00e1 est\u00e1 configurado", "incomplete_device": "Ignorando o dispositivo UPnP incompleto", + "incomplete_discovery": "Descoberta incompleta", "no_devices_discovered": "Nenhum UPnP/IGD descoberto", "no_devices_found": "Nenhum dispositivo UPnP/IGD encontrado na rede.", "no_sensors_or_port_mapping": "Ative pelo menos sensores ou mapeamento de porta", @@ -17,7 +18,8 @@ "data": { "enable_port_mapping": "Ativar o mapeamento de porta para o Home Assistant", "enable_sensors": "Adicionar sensores de tr\u00e1fego", - "igd": "UPnP/IGD" + "igd": "UPnP/IGD", + "usn": "Dispositivo" }, "title": "Op\u00e7\u00f5es de configura\u00e7\u00e3o para o UPnP/IGD" } diff --git a/homeassistant/components/upnp/translations/ru.json b/homeassistant/components/upnp/translations/ru.json index eaf75d85b09..571897f81e6 100644 --- a/homeassistant/components/upnp/translations/ru.json +++ b/homeassistant/components/upnp/translations/ru.json @@ -3,6 +3,7 @@ "abort": { "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", "incomplete_device": "\u0418\u0433\u043d\u043e\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043d\u0435\u043f\u043e\u043b\u043d\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 UPnP.", + "incomplete_discovery": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u044c \u043f\u0440\u043e\u0446\u0435\u0441\u0441.", "no_devices_discovered": "\u041d\u0435 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u043e UPnP / IGD.", "no_devices_found": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 UPnP / IGD \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b \u0432 \u0441\u0435\u0442\u0438.", "no_sensors_or_port_mapping": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u0441\u0435\u043d\u0441\u043e\u0440\u044b \u0438\u043b\u0438 \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u043f\u043e\u0440\u0442\u043e\u0432.", diff --git a/homeassistant/components/upnp/translations/zh-Hant.json b/homeassistant/components/upnp/translations/zh-Hant.json index c45157ff77d..6895b3baa10 100644 --- a/homeassistant/components/upnp/translations/zh-Hant.json +++ b/homeassistant/components/upnp/translations/zh-Hant.json @@ -3,6 +3,7 @@ "abort": { "already_configured": "UPnP/IGD \u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", "incomplete_device": "\u5ffd\u7565\u4e0d\u76f8\u5bb9 UPnP \u8a2d\u5099", + "incomplete_discovery": "\u672a\u5b8c\u6210\u63a2\u7d22", "no_devices_discovered": "\u672a\u641c\u5c0b\u5230 UPnP/IGD", "no_devices_found": "\u5728\u7db2\u8def\u4e0a\u627e\u4e0d\u5230 UPnP/IGD \u8a2d\u5099\u3002", "no_sensors_or_port_mapping": "\u81f3\u5c11\u958b\u555f\u611f\u61c9\u5668\u6216\u901a\u8a0a\u57e0\u8f49\u767c", diff --git a/homeassistant/components/vizio/translations/pt-BR.json b/homeassistant/components/vizio/translations/pt-BR.json new file mode 100644 index 00000000000..6dcce7df8b5 --- /dev/null +++ b/homeassistant/components/vizio/translations/pt-BR.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "complete_pairing_failed": "N\u00e3o foi poss\u00edvel concluir o pareamento. Verifique se o PIN que voc\u00ea forneceu est\u00e1 correto e a TV ainda est\u00e1 ligada e conectada \u00e0 internet antes de reenviar." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wiffi/translations/es.json b/homeassistant/components/wiffi/translations/es.json index d34d94336a0..d1ff792d5dd 100644 --- a/homeassistant/components/wiffi/translations/es.json +++ b/homeassistant/components/wiffi/translations/es.json @@ -8,7 +8,8 @@ "user": { "data": { "port": "Puerto del servidor" - } + }, + "title": "Configurar servidor TCP para dispositivos WIFFI" } } } diff --git a/homeassistant/components/wiffi/translations/lb.json b/homeassistant/components/wiffi/translations/lb.json index 6612b72901f..29937360f7b 100644 --- a/homeassistant/components/wiffi/translations/lb.json +++ b/homeassistant/components/wiffi/translations/lb.json @@ -8,7 +8,8 @@ "user": { "data": { "port": "Server Port" - } + }, + "title": "TCP Server fir WIFFI Apparater ariichten" } } } diff --git a/homeassistant/components/wiffi/translations/pt-BR.json b/homeassistant/components/wiffi/translations/pt-BR.json new file mode 100644 index 00000000000..cbe6c6f78e7 --- /dev/null +++ b/homeassistant/components/wiffi/translations/pt-BR.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "addr_in_use": "Porta do servidor j\u00e1 em uso.", + "start_server_failed": "Falha ao iniciar o servidor." + }, + "step": { + "user": { + "data": { + "port": "Porta do servidor" + }, + "title": "Configurar servidor TCP para dispositivos WIFFI" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/xiaomi_miio/translations/de.json b/homeassistant/components/xiaomi_miio/translations/de.json index d60099d7538..1ce000e4674 100644 --- a/homeassistant/components/xiaomi_miio/translations/de.json +++ b/homeassistant/components/xiaomi_miio/translations/de.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Ger\u00e4t ist bereits konfiguriert" + "already_configured": "Ger\u00e4t ist bereits konfiguriert", + "already_in_progress": "Der Konfigurationsablauf f\u00fcr dieses Xiaomi Miio-Ger\u00e4t wird bereits ausgef\u00fchrt." }, "error": { "connect_error": "Verbindung fehlgeschlagen", diff --git a/homeassistant/components/xiaomi_miio/translations/lb.json b/homeassistant/components/xiaomi_miio/translations/lb.json index 05c8e2354b8..9be68bfd057 100644 --- a/homeassistant/components/xiaomi_miio/translations/lb.json +++ b/homeassistant/components/xiaomi_miio/translations/lb.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Apparat ass scho konfigur\u00e9iert" + "already_configured": "Apparat ass scho konfigur\u00e9iert", + "already_in_progress": "Konfiguratioun's Oflaf fir d\u00ebse Xiaomi Miio Apparat ass schonn am gaangen." }, "error": { "connect_error": "Feeler beim verbannen, prob\u00e9ier w.e.g. nach emol.", diff --git a/homeassistant/components/xiaomi_miio/translations/pt-BR.json b/homeassistant/components/xiaomi_miio/translations/pt-BR.json new file mode 100644 index 00000000000..2f7f84af27e --- /dev/null +++ b/homeassistant/components/xiaomi_miio/translations/pt-BR.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_in_progress": "O fluxo de configura\u00e7\u00e3o para este dispositivo Xiaomi Miio j\u00e1 est\u00e1 em andamento." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zerproc/translations/pt-BR.json b/homeassistant/components/zerproc/translations/pt-BR.json new file mode 100644 index 00000000000..f00723f833b --- /dev/null +++ b/homeassistant/components/zerproc/translations/pt-BR.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "no_devices_found": "Nenhum dispositivo encontrado na rede", + "single_instance_allowed": "J\u00e1 configurado. Somente uma \u00fanica configura\u00e7\u00e3o poss\u00edvel." + }, + "step": { + "confirm": { + "description": "Deseja iniciar a configura\u00e7\u00e3o?" + } + } + }, + "title": "Zerproc" +} \ No newline at end of file diff --git a/homeassistant/components/zha/translations/pt-BR.json b/homeassistant/components/zha/translations/pt-BR.json index 8a9b2b21677..6b866a93971 100644 --- a/homeassistant/components/zha/translations/pt-BR.json +++ b/homeassistant/components/zha/translations/pt-BR.json @@ -7,6 +7,9 @@ "cannot_connect": "N\u00e3o \u00e9 poss\u00edvel conectar-se ao dispositivo ZHA." }, "step": { + "port_config": { + "title": "Configura\u00e7\u00f5es" + }, "user": { "data": { "radio_type": "Tipo de r\u00e1dio" From 299a4fa1c003dea23202fda40f74e9865364dbae Mon Sep 17 00:00:00 2001 From: uvjustin <46082645+uvjustin@users.noreply.github.com> Date: Sat, 16 May 2020 16:06:35 +0800 Subject: [PATCH 043/406] Change MediaPlayerDevice to MediaPlayerEntity (#35692) --- homeassistant/components/forked_daapd/media_player.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/forked_daapd/media_player.py b/homeassistant/components/forked_daapd/media_player.py index 1b52d454def..e492aa1b454 100644 --- a/homeassistant/components/forked_daapd/media_player.py +++ b/homeassistant/components/forked_daapd/media_player.py @@ -6,7 +6,7 @@ import logging from pyforked_daapd import ForkedDaapdAPI from pylibrespot_java import LibrespotJavaAPI -from homeassistant.components.media_player import MediaPlayerDevice +from homeassistant.components.media_player import MediaPlayerEntity from homeassistant.components.media_player.const import MEDIA_TYPE_MUSIC from homeassistant.const import ( CONF_HOST, @@ -116,7 +116,7 @@ async def update_listener(hass, entry): ) -class ForkedDaapdZone(MediaPlayerDevice): +class ForkedDaapdZone(MediaPlayerEntity): """Representation of a forked-daapd output.""" def __init__(self, api, output, entry_id): @@ -221,7 +221,7 @@ class ForkedDaapdZone(MediaPlayerDevice): return SUPPORTED_FEATURES_ZONE -class ForkedDaapdMaster(MediaPlayerDevice): +class ForkedDaapdMaster(MediaPlayerEntity): """Representation of the main forked-daapd device.""" def __init__( From ab631a51bd9ea581171c4b84e6a72cecfbafa27b Mon Sep 17 00:00:00 2001 From: Glenn Waters Date: Sat, 16 May 2020 04:43:40 -0400 Subject: [PATCH 044/406] Update Universal Powerline Bus event name (#35644) --- homeassistant/components/upb/__init__.py | 4 ++-- homeassistant/components/upb/const.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/upb/__init__.py b/homeassistant/components/upb/__init__.py index b84bab054a2..f2765ff317d 100644 --- a/homeassistant/components/upb/__init__.py +++ b/homeassistant/components/upb/__init__.py @@ -14,7 +14,7 @@ from .const import ( ATTR_COMMAND, ATTR_RATE, DOMAIN, - EVENT_UPB_LINK_CHANGED, + EVENT_UPB_SCENE_CHANGED, ) UPB_PLATFORMS = ["light", "scene"] @@ -49,7 +49,7 @@ async def async_setup_entry(hass, config_entry): return hass.bus.async_fire( - EVENT_UPB_LINK_CHANGED, + EVENT_UPB_SCENE_CHANGED, { ATTR_COMMAND: change["command"], ATTR_ADDRESS: element.addr.index, diff --git a/homeassistant/components/upb/const.py b/homeassistant/components/upb/const.py index f01af0fd39f..75d754087e4 100644 --- a/homeassistant/components/upb/const.py +++ b/homeassistant/components/upb/const.py @@ -13,7 +13,7 @@ ATTR_BRIGHTNESS_PCT = "brightness_pct" ATTR_COMMAND = "command" ATTR_RATE = "rate" CONF_NETWORK = "network" -EVENT_UPB_LINK_CHANGED = "upb.link_changed" +EVENT_UPB_SCENE_CHANGED = "upb.scene_changed" VALID_BRIGHTNESS = vol.All(vol.Coerce(int), vol.Clamp(min=0, max=255)) VALID_BRIGHTNESS_PCT = vol.All(vol.Coerce(float), vol.Range(min=0, max=100)) From aaf515ef674f363529ebb27237d69ba74bb175d8 Mon Sep 17 00:00:00 2001 From: Chris Talkington Date: Sat, 16 May 2020 03:48:36 -0500 Subject: [PATCH 045/406] Prevent discovery of IPP printers lacking identifier (#35630) --- homeassistant/components/ipp/config_flow.py | 1 + homeassistant/components/ipp/strings.json | 3 +- tests/components/ipp/__init__.py | 98 ++++++++++++++---- tests/components/ipp/test_config_flow.py | 86 ++++++--------- tests/components/ipp/test_init.py | 6 +- tests/components/ipp/test_sensor.py | 4 +- .../get-printer-attributes-success-nodata.bin | Bin 0 -> 72 bytes 7 files changed, 117 insertions(+), 81 deletions(-) create mode 100644 tests/fixtures/ipp/get-printer-attributes-success-nodata.bin diff --git a/homeassistant/components/ipp/config_flow.py b/homeassistant/components/ipp/config_flow.py index 7d1c3d1b1b8..3128583f218 100644 --- a/homeassistant/components/ipp/config_flow.py +++ b/homeassistant/components/ipp/config_flow.py @@ -152,6 +152,7 @@ class IPPFlowHandler(ConfigFlow, domain=DOMAIN): _LOGGER.debug( "Unable to determine unique id from discovery info and IPP response" ) + return self.async_abort(reason="unique_id_required") await self.async_set_unique_id(unique_id) self._abort_if_unique_id_configured( diff --git a/homeassistant/components/ipp/strings.json b/homeassistant/components/ipp/strings.json index 999c868f080..09c2424151f 100644 --- a/homeassistant/components/ipp/strings.json +++ b/homeassistant/components/ipp/strings.json @@ -28,7 +28,8 @@ "connection_upgrade": "Failed to connect to printer due to connection upgrade being required.", "ipp_error": "Encountered IPP error.", "ipp_version_error": "IPP version not supported by printer.", - "parse_error": "Failed to parse response from printer." + "parse_error": "Failed to parse response from printer.", + "unique_id_required": "Device missing unique identification required for discovery." } } } diff --git a/tests/components/ipp/__init__.py b/tests/components/ipp/__init__.py index f0dc45417e1..515543f3cf5 100644 --- a/tests/components/ipp/__init__.py +++ b/tests/components/ipp/__init__.py @@ -1,6 +1,9 @@ """Tests for the IPP integration.""" import os +import aiohttp +from pyipp import IPPConnectionUpgradeRequired, IPPError + from homeassistant.components.ipp.const import CONF_BASE_PATH, CONF_UUID, DOMAIN from homeassistant.const import ( CONF_HOST, @@ -18,21 +21,25 @@ from tests.test_util.aiohttp import AiohttpClientMocker ATTR_HOSTNAME = "hostname" ATTR_PROPERTIES = "properties" +HOST = "192.168.1.31" +PORT = 631 +BASE_PATH = "/ipp/print" + IPP_ZEROCONF_SERVICE_TYPE = "_ipp._tcp.local." IPPS_ZEROCONF_SERVICE_TYPE = "_ipps._tcp.local." ZEROCONF_NAME = "EPSON XP-6000 Series" -ZEROCONF_HOST = "192.168.1.31" +ZEROCONF_HOST = HOST ZEROCONF_HOSTNAME = "EPSON123456.local." -ZEROCONF_PORT = 631 - +ZEROCONF_PORT = PORT +ZEROCONF_RP = "ipp/print" MOCK_USER_INPUT = { - CONF_HOST: "192.168.1.31", - CONF_PORT: 361, + CONF_HOST: HOST, + CONF_PORT: PORT, CONF_SSL: False, CONF_VERIFY_SSL: False, - CONF_BASE_PATH: "/ipp/print", + CONF_BASE_PATH: BASE_PATH, } MOCK_ZEROCONF_IPP_SERVICE_INFO = { @@ -41,7 +48,7 @@ MOCK_ZEROCONF_IPP_SERVICE_INFO = { CONF_HOST: ZEROCONF_HOST, ATTR_HOSTNAME: ZEROCONF_HOSTNAME, CONF_PORT: ZEROCONF_PORT, - ATTR_PROPERTIES: {"rp": "ipp/print"}, + ATTR_PROPERTIES: {"rp": ZEROCONF_RP}, } MOCK_ZEROCONF_IPPS_SERVICE_INFO = { @@ -50,7 +57,7 @@ MOCK_ZEROCONF_IPPS_SERVICE_INFO = { CONF_HOST: ZEROCONF_HOST, ATTR_HOSTNAME: ZEROCONF_HOSTNAME, CONF_PORT: ZEROCONF_PORT, - ATTR_PROPERTIES: {"rp": "ipp/print"}, + ATTR_PROPERTIES: {"rp": ZEROCONF_RP}, } @@ -61,30 +68,75 @@ def load_fixture_binary(filename): return fptr.read() +def mock_connection( + aioclient_mock: AiohttpClientMocker, + host: str = HOST, + port: int = PORT, + ssl: bool = False, + base_path: str = BASE_PATH, + conn_error: bool = False, + conn_upgrade_error: bool = False, + ipp_error: bool = False, + no_unique_id: bool = False, + parse_error: bool = False, + version_not_supported: bool = False, +): + """Mock the IPP connection.""" + scheme = "https" if ssl else "http" + ipp_url = f"{scheme}://{host}:{port}" + + if ipp_error: + aioclient_mock.post(f"{ipp_url}{base_path}", exc=IPPError) + return + + if conn_error: + aioclient_mock.post(f"{ipp_url}{base_path}", exc=aiohttp.ClientError) + return + + if conn_upgrade_error: + aioclient_mock.post(f"{ipp_url}{base_path}", exc=IPPConnectionUpgradeRequired) + return + + fixture = "ipp/get-printer-attributes.bin" + if no_unique_id: + fixture = "ipp/get-printer-attributes-success-nodata.bin" + elif version_not_supported: + fixture = "ipp/get-printer-attributes-error-0x0503.bin" + + if parse_error: + content = "BAD" + else: + content = load_fixture_binary(fixture) + + aioclient_mock.post( + f"{ipp_url}{base_path}", + content=content, + headers={"Content-Type": "application/ipp"}, + ) + + async def init_integration( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, skip_setup: bool = False, + host: str = HOST, + port: int = PORT, + ssl: bool = False, + base_path: str = BASE_PATH, uuid: str = "cfe92100-67c4-11d4-a45f-f8d027761251", unique_id: str = "cfe92100-67c4-11d4-a45f-f8d027761251", + conn_error: bool = False, ) -> MockConfigEntry: """Set up the IPP integration in Home Assistant.""" - fixture = "ipp/get-printer-attributes.bin" - aioclient_mock.post( - "http://192.168.1.31:631/ipp/print", - content=load_fixture_binary(fixture), - headers={"Content-Type": "application/ipp"}, - ) - entry = MockConfigEntry( domain=DOMAIN, unique_id=unique_id, data={ - CONF_HOST: "192.168.1.31", - CONF_PORT: 631, - CONF_SSL: False, + CONF_HOST: host, + CONF_PORT: port, + CONF_SSL: ssl, CONF_VERIFY_SSL: True, - CONF_BASE_PATH: "/ipp/print", + CONF_BASE_PATH: base_path, CONF_UUID: uuid, }, ) @@ -92,6 +144,14 @@ async def init_integration( entry.add_to_hass(hass) if not skip_setup: + mock_connection( + aioclient_mock, + host=host, + port=port, + ssl=ssl, + base_path=base_path, + conn_error=conn_error, + ) await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() diff --git a/tests/components/ipp/test_config_flow.py b/tests/components/ipp/test_config_flow.py index fb75ba9caef..0093ba57e5b 100644 --- a/tests/components/ipp/test_config_flow.py +++ b/tests/components/ipp/test_config_flow.py @@ -1,7 +1,4 @@ """Tests for the IPP config flow.""" -import aiohttp -from pyipp import IPPConnectionUpgradeRequired, IPPError - from homeassistant.components.ipp.const import CONF_BASE_PATH, CONF_UUID, DOMAIN from homeassistant.config_entries import SOURCE_USER, SOURCE_ZEROCONF from homeassistant.const import CONF_HOST, CONF_NAME, CONF_SSL @@ -17,7 +14,7 @@ from . import ( MOCK_ZEROCONF_IPP_SERVICE_INFO, MOCK_ZEROCONF_IPPS_SERVICE_INFO, init_integration, - load_fixture_binary, + mock_connection, ) from tests.test_util.aiohttp import AiohttpClientMocker @@ -37,11 +34,7 @@ async def test_show_zeroconf_form( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker ) -> None: """Test that the zeroconf confirmation form is served.""" - aioclient_mock.post( - "http://192.168.1.31:631/ipp/print", - content=load_fixture_binary("ipp/get-printer-attributes.bin"), - headers={"Content-Type": "application/ipp"}, - ) + mock_connection(aioclient_mock) discovery_info = MOCK_ZEROCONF_IPP_SERVICE_INFO.copy() result = await hass.config_entries.flow.async_init( @@ -57,7 +50,7 @@ async def test_connection_error( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker ) -> None: """Test we show user form on IPP connection error.""" - aioclient_mock.post("http://192.168.1.31:631/ipp/print", exc=aiohttp.ClientError) + mock_connection(aioclient_mock, conn_error=True) user_input = MOCK_USER_INPUT.copy() result = await hass.config_entries.flow.async_init( @@ -73,7 +66,7 @@ async def test_zeroconf_connection_error( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker ) -> None: """Test we abort zeroconf flow on IPP connection error.""" - aioclient_mock.post("http://192.168.1.31:631/ipp/print", exc=aiohttp.ClientError) + mock_connection(aioclient_mock, conn_error=True) discovery_info = MOCK_ZEROCONF_IPP_SERVICE_INFO.copy() result = await hass.config_entries.flow.async_init( @@ -88,7 +81,7 @@ async def test_zeroconf_confirm_connection_error( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker ) -> None: """Test we abort zeroconf flow on IPP connection error.""" - aioclient_mock.post("http://192.168.1.31:631/ipp/print", exc=aiohttp.ClientError) + mock_connection(aioclient_mock, conn_error=True) discovery_info = MOCK_ZEROCONF_IPP_SERVICE_INFO.copy() result = await hass.config_entries.flow.async_init( @@ -103,9 +96,7 @@ async def test_user_connection_upgrade_required( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker ) -> None: """Test we show the user form if connection upgrade required by server.""" - aioclient_mock.post( - "http://192.168.1.31:631/ipp/print", exc=IPPConnectionUpgradeRequired - ) + mock_connection(aioclient_mock, conn_upgrade_error=True) user_input = MOCK_USER_INPUT.copy() result = await hass.config_entries.flow.async_init( @@ -121,9 +112,7 @@ async def test_zeroconf_connection_upgrade_required( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker ) -> None: """Test we abort zeroconf flow on IPP connection error.""" - aioclient_mock.post( - "http://192.168.1.31:631/ipp/print", exc=IPPConnectionUpgradeRequired - ) + mock_connection(aioclient_mock, conn_upgrade_error=True) discovery_info = MOCK_ZEROCONF_IPP_SERVICE_INFO.copy() result = await hass.config_entries.flow.async_init( @@ -138,11 +127,7 @@ async def test_user_parse_error( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker ) -> None: """Test we abort user flow on IPP parse error.""" - aioclient_mock.post( - "http://192.168.1.31:631/ipp/print", - content="BAD", - headers={"Content-Type": "application/ipp"}, - ) + mock_connection(aioclient_mock, parse_error=True) user_input = MOCK_USER_INPUT.copy() result = await hass.config_entries.flow.async_init( @@ -157,11 +142,7 @@ async def test_zeroconf_parse_error( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker ) -> None: """Test we abort zeroconf flow on IPP parse error.""" - aioclient_mock.post( - "http://192.168.1.31:631/ipp/print", - content="BAD", - headers={"Content-Type": "application/ipp"}, - ) + mock_connection(aioclient_mock, parse_error=True) discovery_info = MOCK_ZEROCONF_IPP_SERVICE_INFO.copy() result = await hass.config_entries.flow.async_init( @@ -176,7 +157,7 @@ async def test_user_ipp_error( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker ) -> None: """Test we abort the user flow on IPP error.""" - aioclient_mock.post("http://192.168.1.31:631/ipp/print", exc=IPPError) + mock_connection(aioclient_mock, ipp_error=True) user_input = MOCK_USER_INPUT.copy() result = await hass.config_entries.flow.async_init( @@ -191,7 +172,7 @@ async def test_zeroconf_ipp_error( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker ) -> None: """Test we abort zeroconf flow on IPP error.""" - aioclient_mock.post("http://192.168.1.31:631/ipp/print", exc=IPPError) + mock_connection(aioclient_mock, ipp_error=True) discovery_info = MOCK_ZEROCONF_IPP_SERVICE_INFO.copy() result = await hass.config_entries.flow.async_init( @@ -206,11 +187,7 @@ async def test_user_ipp_version_error( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker ) -> None: """Test we abort user flow on IPP version not supported error.""" - aioclient_mock.post( - "http://192.168.1.31:631/ipp/print", - content=load_fixture_binary("ipp/get-printer-attributes-error-0x0503.bin"), - headers={"Content-Type": "application/ipp"}, - ) + mock_connection(aioclient_mock, version_not_supported=True) user_input = {**MOCK_USER_INPUT} result = await hass.config_entries.flow.async_init( @@ -225,11 +202,7 @@ async def test_zeroconf_ipp_version_error( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker ) -> None: """Test we abort zeroconf flow on IPP version not supported error.""" - aioclient_mock.post( - "http://192.168.1.31:631/ipp/print", - content=load_fixture_binary("ipp/get-printer-attributes-error-0x0503.bin"), - headers={"Content-Type": "application/ipp"}, - ) + mock_connection(aioclient_mock, version_not_supported=True) discovery_info = {**MOCK_ZEROCONF_IPP_SERVICE_INFO} result = await hass.config_entries.flow.async_init( @@ -291,15 +264,26 @@ async def test_zeroconf_with_uuid_device_exists_abort( assert result["reason"] == "already_configured" +async def test_zeroconf_unique_id_required_abort( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: + """Test we abort zeroconf flow if printer lacks unique identification.""" + mock_connection(aioclient_mock, no_unique_id=True) + + discovery_info = MOCK_ZEROCONF_IPP_SERVICE_INFO.copy() + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_ZEROCONF}, data=discovery_info, + ) + + assert result["type"] == RESULT_TYPE_ABORT + assert result["reason"] == "unique_id_required" + + async def test_full_user_flow_implementation( hass: HomeAssistant, aioclient_mock ) -> None: """Test the full manual user flow from start to finish.""" - aioclient_mock.post( - "http://192.168.1.31:631/ipp/print", - content=load_fixture_binary("ipp/get-printer-attributes.bin"), - headers={"Content-Type": "application/ipp"}, - ) + mock_connection(aioclient_mock) result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER}, @@ -328,11 +312,7 @@ async def test_full_zeroconf_flow_implementation( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker ) -> None: """Test the full manual user flow from start to finish.""" - aioclient_mock.post( - "http://192.168.1.31:631/ipp/print", - content=load_fixture_binary("ipp/get-printer-attributes.bin"), - headers={"Content-Type": "application/ipp"}, - ) + mock_connection(aioclient_mock) discovery_info = MOCK_ZEROCONF_IPP_SERVICE_INFO.copy() result = await hass.config_entries.flow.async_init( @@ -363,11 +343,7 @@ async def test_full_zeroconf_tls_flow_implementation( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker ) -> None: """Test the full manual user flow from start to finish.""" - aioclient_mock.post( - "https://192.168.1.31:631/ipp/print", - content=load_fixture_binary("ipp/get-printer-attributes.bin"), - headers={"Content-Type": "application/ipp"}, - ) + mock_connection(aioclient_mock, ssl=True) discovery_info = MOCK_ZEROCONF_IPPS_SERVICE_INFO.copy() result = await hass.config_entries.flow.async_init( diff --git a/tests/components/ipp/test_init.py b/tests/components/ipp/test_init.py index 2ec11a1e937..f06be4fc8b5 100644 --- a/tests/components/ipp/test_init.py +++ b/tests/components/ipp/test_init.py @@ -1,6 +1,4 @@ """Tests for the IPP integration.""" -import aiohttp - from homeassistant.components.ipp.const import DOMAIN from homeassistant.config_entries import ( ENTRY_STATE_LOADED, @@ -17,9 +15,7 @@ async def test_config_entry_not_ready( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker ) -> None: """Test the IPP configuration entry not ready.""" - aioclient_mock.post("http://192.168.1.31:631/ipp/print", exc=aiohttp.ClientError) - - entry = await init_integration(hass, aioclient_mock) + entry = await init_integration(hass, aioclient_mock, conn_error=True) assert entry.state == ENTRY_STATE_SETUP_RETRY diff --git a/tests/components/ipp/test_sensor.py b/tests/components/ipp/test_sensor.py index 51caadfceb3..1abf557c93b 100644 --- a/tests/components/ipp/test_sensor.py +++ b/tests/components/ipp/test_sensor.py @@ -8,7 +8,7 @@ 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.components.ipp import init_integration, mock_connection from tests.test_util.aiohttp import AiohttpClientMocker @@ -16,6 +16,8 @@ async def test_sensors( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker ) -> None: """Test the creation and values of the IPP sensors.""" + mock_connection(aioclient_mock) + entry = await init_integration(hass, aioclient_mock, skip_setup=True) registry = await hass.helpers.entity_registry.async_get_registry() diff --git a/tests/fixtures/ipp/get-printer-attributes-success-nodata.bin b/tests/fixtures/ipp/get-printer-attributes-success-nodata.bin new file mode 100644 index 0000000000000000000000000000000000000000..e6061adaccdef6f4705bba31c5166ee7b9cefe5f GIT binary patch literal 72 zcmZQ#00PFkS&Z%sLWw0MMVU#ZC8@=_$r*`7#i=C>tfeJsx)vS`(nxZ7i6x~)i8;DC QiFxUziRq~fOsRRy0Q|TXqyPW_ literal 0 HcmV?d00001 From d3ae8a938cdea9b6e0d443c91c37ac3dbbd459ab Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 16 May 2020 13:31:15 +0200 Subject: [PATCH 046/406] Fix handling of additional data in core config storage (#35660) --- homeassistant/core.py | 25 +++++++++++++++++-------- tests/test_core.py | 17 ++++++++++++++--- 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/homeassistant/core.py b/homeassistant/core.py index 929d8c74da4..34df648a4df 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -1454,10 +1454,6 @@ class Config: ) data = await store.async_load() - if data and "external_url" in data: - self._update(source=SOURCE_STORAGE, **data) - return - async def migrate_base_url(_: Event) -> None: """Migrate base_url to internal_url/external_url.""" if self.hass.config.api is None: @@ -1484,11 +1480,24 @@ class Config: external_url=network.normalize_url(str(base_url)) ) - # Try to migrate base_url to internal_url/external_url - self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, migrate_base_url) - if data: - self._update(source=SOURCE_STORAGE, **data) + # Try to migrate base_url to internal_url/external_url + if "external_url" not in data: + self.hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_START, migrate_base_url + ) + + self._update( + source=SOURCE_STORAGE, + latitude=data.get("latitude"), + longitude=data.get("longitude"), + elevation=data.get("elevation"), + unit_system=data.get("unit_system"), + location_name=data.get("location_name"), + time_zone=data.get("time_zone"), + external_url=data.get("external_url", _UNDEF), + internal_url=data.get("internal_url", _UNDEF), + ) async def async_store(self) -> None: """Store [homeassistant] core config.""" diff --git a/tests/test_core.py b/tests/test_core.py index ced0b96fbed..3bc001b78b6 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1294,17 +1294,17 @@ async def test_migration_base_url(hass, hass_storage): with patch.object(hass.bus, "async_listen_once") as mock_listen: # Empty config await config.async_load() - assert len(mock_listen.mock_calls) == 1 + assert len(mock_listen.mock_calls) == 0 # With just a name stored["data"] = {"location_name": "Test Name"} await config.async_load() - assert len(mock_listen.mock_calls) == 2 + assert len(mock_listen.mock_calls) == 1 # With external url stored["data"]["external_url"] = "https://example.com" await config.async_load() - assert len(mock_listen.mock_calls) == 2 + assert len(mock_listen.mock_calls) == 1 # Test that the event listener works assert mock_listen.mock_calls[0][1][0] == EVENT_HOMEASSISTANT_START @@ -1319,3 +1319,14 @@ async def test_migration_base_url(hass, hass_storage): hass.config.api = Mock(deprecated_base_url=internal) await mock_listen.mock_calls[0][1][1](None) assert config.internal_url == internal + + +async def test_additional_data_in_core_config(hass, hass_storage): + """Test that we can handle additional data in core configuration.""" + config = ha.Config(hass) + hass_storage[ha.CORE_STORAGE_KEY] = { + "version": 1, + "data": {"location_name": "Test Name", "additional_valid_key": "value"}, + } + await config.async_load() + assert config.location_name == "Test Name" From 73616520c004d3e8360d0f59ba345775810f73ac Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 16 May 2020 09:29:58 -0500 Subject: [PATCH 047/406] Use built in queue log handlers to avoid formatting logs in the event loop (#35633) * Use built in queue log handlers to avoid formatting logs in the event loop Logging is now formatted and written in another thread to ensure there is minimal impact on the event loop when a log message is processed. This change replaces the existing AsyncHandler log handler as python 3.7+ now offers an off the shelf solution * add a simple test * s/async_migrate_log_handlers_to_queue/async_activate_log_queue_handler/g --- homeassistant/bootstrap.py | 16 ++--- homeassistant/util/logging.py | 132 +++++++++++----------------------- tests/test_bootstrap.py | 9 +++ tests/util/test_logging.py | 66 ++++++++--------- 4 files changed, 83 insertions(+), 140 deletions(-) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index d53d86f528c..ffcd1b7f3cf 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -14,7 +14,6 @@ import voluptuous as vol from homeassistant import config as conf_util, config_entries, core, loader from homeassistant.components import http from homeassistant.const import ( - EVENT_HOMEASSISTANT_CLOSE, EVENT_HOMEASSISTANT_STOP, REQUIRED_NEXT_PYTHON_DATE, REQUIRED_NEXT_PYTHON_VER, @@ -22,7 +21,7 @@ from homeassistant.const import ( from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.typing import ConfigType from homeassistant.setup import DATA_SETUP, async_setup_component -from homeassistant.util.logging import AsyncHandler +from homeassistant.util.logging import async_activate_log_queue_handler from homeassistant.util.package import async_get_user_site, is_virtual_env from homeassistant.util.yaml import clear_secret_cache @@ -278,17 +277,8 @@ def async_enable_logging( err_handler.setLevel(logging.INFO if verbose else logging.WARNING) err_handler.setFormatter(logging.Formatter(fmt, datefmt=datefmt)) - async_handler = AsyncHandler(hass.loop, err_handler) - - async def async_stop_async_handler(_: Any) -> None: - """Cleanup async handler.""" - logging.getLogger("").removeHandler(async_handler) # type: ignore - await async_handler.async_close(blocking=True) - - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_CLOSE, async_stop_async_handler) - logger = logging.getLogger("") - logger.addHandler(async_handler) # type: ignore + logger.addHandler(err_handler) logger.setLevel(logging.INFO) # Save the log file location for access by other components. @@ -296,6 +286,8 @@ def async_enable_logging( else: _LOGGER.error("Unable to set up error log %s (access denied)", err_log_path) + async_activate_log_queue_handler(hass) + async def async_mount_local_lib_path(config_dir: str) -> str: """Add local library to Python Path. diff --git a/homeassistant/util/logging.py b/homeassistant/util/logging.py index ae59e9bf4f9..07ee6608141 100644 --- a/homeassistant/util/logging.py +++ b/homeassistant/util/logging.py @@ -1,12 +1,15 @@ """Logging utilities.""" import asyncio -from asyncio.events import AbstractEventLoop from functools import partial, wraps import inspect import logging -import threading +import logging.handlers +import queue import traceback -from typing import Any, Callable, Coroutine, Optional +from typing import Any, Callable, Coroutine + +from homeassistant.const import EVENT_HOMEASSISTANT_CLOSE +from homeassistant.core import HomeAssistant, callback class HideSensitiveDataFilter(logging.Filter): @@ -24,104 +27,51 @@ class HideSensitiveDataFilter(logging.Filter): return True -class AsyncHandler: - """Logging handler wrapper to add an async layer.""" +class HomeAssistantQueueHandler(logging.handlers.QueueHandler): + """Process the log in another thread.""" - def __init__(self, loop: AbstractEventLoop, handler: logging.Handler) -> None: - """Initialize async logging handler wrapper.""" - self.handler = handler - self.loop = loop - self._queue: asyncio.Queue = asyncio.Queue(loop=loop) - self._thread = threading.Thread(target=self._process) - - # Delegate from handler - # pylint: disable=invalid-name - self.setLevel = handler.setLevel - self.setFormatter = handler.setFormatter - self.addFilter = handler.addFilter - self.removeFilter = handler.removeFilter - self.filter = handler.filter - self.flush = handler.flush - self.handle = handler.handle - self.handleError = handler.handleError - self.format = handler.format - - self._thread.start() - - def close(self) -> None: - """Wrap close to handler.""" - self.emit(None) - - async def async_close(self, blocking: bool = False) -> None: - """Close the handler. - - When blocking=True, will wait till closed. - """ - await self._queue.put(None) - - if blocking: - while self._thread.is_alive(): - await asyncio.sleep(0) - - def emit(self, record: Optional[logging.LogRecord]) -> None: - """Process a record.""" - ident = self.loop.__dict__.get("_thread_ident") - - # inside eventloop - if ident is not None and ident == threading.get_ident(): - self._queue.put_nowait(record) - # from a thread/executor - else: - self.loop.call_soon_threadsafe(self._queue.put_nowait, record) - - def __repr__(self) -> str: - """Return the string names.""" - return str(self.handler) - - def _process(self) -> None: - """Process log in a thread.""" + def emit(self, record: logging.LogRecord) -> None: + """Emit a log record.""" try: - while True: - record = asyncio.run_coroutine_threadsafe( - self._queue.get(), self.loop - ).result() + self.enqueue(record) + except asyncio.CancelledError: # pylint: disable=try-except-raise + raise + except Exception: # pylint: disable=broad-except + self.handleError(record) - if record is None: - self.handler.close() - return - self.handler.emit(record) - except asyncio.CancelledError: - self.handler.close() +@callback +def async_activate_log_queue_handler(hass: HomeAssistant) -> None: + """ + Migrate the existing log handlers to use the queue. - def createLock(self) -> None: # pylint: disable=invalid-name - """Ignore lock stuff.""" + This allows us to avoid blocking I/O and formatting messages + in the event loop as log messages are written in another thread. + """ + simple_queue = queue.SimpleQueue() # type: ignore + queue_handler = HomeAssistantQueueHandler(simple_queue) + logging.root.addHandler(queue_handler) - def acquire(self) -> None: - """Ignore lock stuff.""" + migrated_handlers = [] + for handler in logging.root.handlers[:]: + if handler is queue_handler: + continue + logging.root.removeHandler(handler) + migrated_handlers.append(handler) - def release(self) -> None: - """Ignore lock stuff.""" + listener = logging.handlers.QueueListener( + simple_queue, *migrated_handlers, respect_handler_level=False + ) - @property - def level(self) -> int: - """Wrap property level to handler.""" - return self.handler.level + listener.start() - @property - def formatter(self) -> Optional[logging.Formatter]: - """Wrap property formatter to handler.""" - return self.handler.formatter + @callback + def _async_stop_queue_handler(_: Any) -> None: + """Cleanup handler.""" + logging.root.removeHandler(queue_handler) + listener.stop() - @property - def name(self) -> str: - """Wrap property set_name to handler.""" - return self.handler.get_name() # type: ignore - - @name.setter - def name(self, name: str) -> None: - """Wrap property get_name to handler.""" - self.handler.set_name(name) # type: ignore + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_CLOSE, _async_stop_queue_handler) def log_exception(format_err: Callable[..., Any], *args: Any) -> None: diff --git a/tests/test_bootstrap.py b/tests/test_bootstrap.py index a639b16893b..fc535d7e3a5 100644 --- a/tests/test_bootstrap.py +++ b/tests/test_bootstrap.py @@ -37,6 +37,15 @@ async def test_home_assistant_core_config_validation(hass): assert result is None +async def test_async_enable_logging(hass): + """Test to ensure logging is migrated to the queue handlers.""" + with patch("logging.getLogger"), patch( + "homeassistant.bootstrap.async_activate_log_queue_handler" + ) as mock_async_activate_log_queue_handler: + bootstrap.async_enable_logging(hass) + mock_async_activate_log_queue_handler.assert_called_once() + + async def test_load_hassio(hass): """Test that we load Hass.io component.""" with patch.dict(os.environ, {}, clear=True): diff --git a/tests/util/test_logging.py b/tests/util/test_logging.py index 2d05157e26f..a1183ee1637 100644 --- a/tests/util/test_logging.py +++ b/tests/util/test_logging.py @@ -1,12 +1,14 @@ """Test Home Assistant logging util methods.""" import asyncio import logging -import threading +import queue import pytest import homeassistant.util.logging as logging_util +from tests.async_mock import patch + def test_sensitive_data_filter(): """Test the logging sensitive data filter.""" @@ -21,50 +23,40 @@ def test_sensitive_data_filter(): assert sensitive_record.msg == "******* log" -async def test_async_handler_loop_log(loop): - """Test logging data inside from inside the event loop.""" - loop._thread_ident = threading.get_ident() +async def test_logging_with_queue_handler(): + """Test logging with HomeAssistantQueueHandler.""" - queue = asyncio.Queue(loop=loop) - base_handler = logging.handlers.QueueHandler(queue) - handler = logging_util.AsyncHandler(loop, base_handler) - - # Test passthrough props and noop functions - assert handler.createLock() is None - assert handler.acquire() is None - assert handler.release() is None - assert handler.formatter is base_handler.formatter - assert handler.name is base_handler.get_name() - handler.name = "mock_name" - assert base_handler.get_name() == "mock_name" + simple_queue = queue.SimpleQueue() # type: ignore + handler = logging_util.HomeAssistantQueueHandler(simple_queue) log_record = logging.makeLogRecord({"msg": "Test Log Record"}) + handler.emit(log_record) - await handler.async_close(True) - assert queue.get_nowait().msg == "Test Log Record" - assert queue.empty() - -async def test_async_handler_thread_log(loop): - """Test logging data from a thread.""" - loop._thread_ident = threading.get_ident() - - queue = asyncio.Queue(loop=loop) - base_handler = logging.handlers.QueueHandler(queue) - handler = logging_util.AsyncHandler(loop, base_handler) - - log_record = logging.makeLogRecord({"msg": "Test Log Record"}) - - def add_log(): - """Emit a mock log.""" + with pytest.raises(asyncio.CancelledError), patch.object( + handler, "enqueue", side_effect=asyncio.CancelledError + ): handler.emit(log_record) - handler.close() - await loop.run_in_executor(None, add_log) - await handler.async_close(True) + with patch.object(handler, "enqueue", side_effect=OSError), patch.object( + handler, "handleError" + ) as mock_handle_error: + handler.emit(log_record) + mock_handle_error.assert_called_once() - assert queue.get_nowait().msg == "Test Log Record" - assert queue.empty() + handler.close() + + assert simple_queue.get_nowait().msg == "Test Log Record" + assert simple_queue.empty() + + +async def test_migrate_log_handler(hass): + """Test migrating log handlers.""" + + logging_util.async_activate_log_queue_handler(hass) + + assert len(logging.root.handlers) == 1 + assert isinstance(logging.root.handlers[0], logging_util.HomeAssistantQueueHandler) @pytest.mark.no_fail_on_log_exception From 6e0359efa6a52d07339db32871a7ac9b36131d2b Mon Sep 17 00:00:00 2001 From: gadgetmobile <57815233+gadgetmobile@users.noreply.github.com> Date: Sat, 16 May 2020 17:51:37 +0200 Subject: [PATCH 048/406] Add Blebox air quality support (#35372) * support BleBox air-quality * fixed switch tests via cherry-pick from #35552 * fix test after cherry-picking * fix flake8 issues --- homeassistant/components/blebox/__init__.py | 6 +- .../components/blebox/air_quality.py | 36 +++++++ homeassistant/components/blebox/cover.py | 11 +-- homeassistant/components/blebox/sensor.py | 6 +- homeassistant/components/blebox/switch.py | 9 +- tests/components/blebox/test_air_quality.py | 94 +++++++++++++++++++ 6 files changed, 142 insertions(+), 20 deletions(-) create mode 100644 homeassistant/components/blebox/air_quality.py create mode 100644 tests/components/blebox/test_air_quality.py diff --git a/homeassistant/components/blebox/__init__.py b/homeassistant/components/blebox/__init__.py index f250c3f565e..634f8384c8c 100644 --- a/homeassistant/components/blebox/__init__.py +++ b/homeassistant/components/blebox/__init__.py @@ -17,7 +17,7 @@ from .const import DEFAULT_SETUP_TIMEOUT, DOMAIN, PRODUCT _LOGGER = logging.getLogger(__name__) -PLATFORMS = ["cover", "sensor", "switch"] +PLATFORMS = ["cover", "sensor", "switch", "air_quality"] PARALLEL_UPDATES = 0 @@ -74,9 +74,11 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): @callback -def create_blebox_entities(product, async_add, entity_klass, entity_type): +def create_blebox_entities(hass, config_entry, async_add, entity_klass, entity_type): """Create entities from a BleBox product's features.""" + product = hass.data[DOMAIN][config_entry.entry_id][PRODUCT] + entities = [] if entity_type in product.features: for feature in product.features[entity_type]: diff --git a/homeassistant/components/blebox/air_quality.py b/homeassistant/components/blebox/air_quality.py new file mode 100644 index 00000000000..656f19109ea --- /dev/null +++ b/homeassistant/components/blebox/air_quality.py @@ -0,0 +1,36 @@ +"""BleBox air quality entity.""" + +from homeassistant.components.air_quality import AirQualityEntity + +from . import BleBoxEntity, create_blebox_entities + + +async def async_setup_entry(hass, config_entry, async_add): + """Set up a BleBox air quality entity.""" + create_blebox_entities( + hass, config_entry, async_add, BleBoxAirQualityEntity, "air_qualities" + ) + + +class BleBoxAirQualityEntity(BleBoxEntity, AirQualityEntity): + """Representation of a BleBox air quality feature.""" + + @property + def icon(self): + """Return the icon.""" + return "mdi:blur" + + @property + def particulate_matter_0_1(self): + """Return the particulate matter 0.1 level.""" + return self._feature.pm1 + + @property + def particulate_matter_2_5(self): + """Return the particulate matter 2.5 level.""" + return self._feature.pm2_5 + + @property + def particulate_matter_10(self): + """Return the particulate matter 10 level.""" + return self._feature.pm10 diff --git a/homeassistant/components/blebox/cover.py b/homeassistant/components/blebox/cover.py index 2a8f0219267..714b642e288 100644 --- a/homeassistant/components/blebox/cover.py +++ b/homeassistant/components/blebox/cover.py @@ -13,20 +13,13 @@ from homeassistant.components.cover import ( ) from . import BleBoxEntity, create_blebox_entities -from .const import ( - BLEBOX_TO_HASS_COVER_STATES, - BLEBOX_TO_HASS_DEVICE_CLASSES, - DOMAIN, - PRODUCT, -) +from .const import BLEBOX_TO_HASS_COVER_STATES, BLEBOX_TO_HASS_DEVICE_CLASSES async def async_setup_entry(hass, config_entry, async_add): """Set up a BleBox entry.""" - product = hass.data[DOMAIN][config_entry.entry_id][PRODUCT] - create_blebox_entities(product, async_add, BleBoxCoverEntity, "covers") - return True + create_blebox_entities(hass, config_entry, async_add, BleBoxCoverEntity, "covers") class BleBoxCoverEntity(BleBoxEntity, CoverEntity): diff --git a/homeassistant/components/blebox/sensor.py b/homeassistant/components/blebox/sensor.py index 7a7aa0bac8d..00ea4e82b9d 100644 --- a/homeassistant/components/blebox/sensor.py +++ b/homeassistant/components/blebox/sensor.py @@ -3,15 +3,13 @@ from homeassistant.helpers.entity import Entity from . import BleBoxEntity, create_blebox_entities -from .const import BLEBOX_TO_HASS_DEVICE_CLASSES, BLEBOX_TO_UNIT_MAP, DOMAIN, PRODUCT +from .const import BLEBOX_TO_HASS_DEVICE_CLASSES, BLEBOX_TO_UNIT_MAP async def async_setup_entry(hass, config_entry, async_add): """Set up a BleBox entry.""" - product = hass.data[DOMAIN][config_entry.entry_id][PRODUCT] - create_blebox_entities(product, async_add, BleBoxSensorEntity, "sensors") - return True + create_blebox_entities(hass, config_entry, async_add, BleBoxSensorEntity, "sensors") class BleBoxSensorEntity(BleBoxEntity, Entity): diff --git a/homeassistant/components/blebox/switch.py b/homeassistant/components/blebox/switch.py index 116ae1645f4..1e6f09a72a4 100644 --- a/homeassistant/components/blebox/switch.py +++ b/homeassistant/components/blebox/switch.py @@ -2,15 +2,14 @@ from homeassistant.components.switch import SwitchDevice from . import BleBoxEntity, create_blebox_entities -from .const import BLEBOX_TO_HASS_DEVICE_CLASSES, DOMAIN, PRODUCT +from .const import BLEBOX_TO_HASS_DEVICE_CLASSES async def async_setup_entry(hass, config_entry, async_add): """Set up a BleBox switch entity.""" - - product = hass.data[DOMAIN][config_entry.entry_id][PRODUCT] - create_blebox_entities(product, async_add, BleBoxSwitchEntity, "switches") - return True + create_blebox_entities( + hass, config_entry, async_add, BleBoxSwitchEntity, "switches" + ) class BleBoxSwitchEntity(BleBoxEntity, SwitchDevice): diff --git a/tests/components/blebox/test_air_quality.py b/tests/components/blebox/test_air_quality.py new file mode 100644 index 00000000000..3467c94411c --- /dev/null +++ b/tests/components/blebox/test_air_quality.py @@ -0,0 +1,94 @@ +"""Blebox air_quality tests.""" + +import logging + +import blebox_uniapi +import pytest + +from homeassistant.components.air_quality import ATTR_PM_0_1, ATTR_PM_2_5, ATTR_PM_10 +from homeassistant.const import ATTR_ICON, STATE_UNKNOWN + +from .conftest import async_setup_entity, mock_feature + +from tests.async_mock import AsyncMock, PropertyMock + + +@pytest.fixture(name="airsensor") +def airsensor_fixture(): + """Return a default air quality fixture.""" + feature = mock_feature( + "air_qualities", + blebox_uniapi.air_quality.AirQuality, + unique_id="BleBox-airSensor-1afe34db9437-0.air", + full_name="airSensor-0.air", + device_class=None, + pm1=None, + pm2_5=None, + pm10=None, + ) + product = feature.product + type(product).name = PropertyMock(return_value="My air sensor") + type(product).model = PropertyMock(return_value="airSensor") + return (feature, "air_quality.airsensor_0_air") + + +async def test_init(airsensor, hass, config): + """Test airSensor default state.""" + + _, entity_id = airsensor + entry = await async_setup_entity(hass, config, entity_id) + assert entry.unique_id == "BleBox-airSensor-1afe34db9437-0.air" + + state = hass.states.get(entity_id) + assert state.name == "airSensor-0.air" + + assert ATTR_PM_0_1 not in state.attributes + assert ATTR_PM_2_5 not in state.attributes + assert ATTR_PM_10 not in state.attributes + + assert state.attributes[ATTR_ICON] == "mdi:blur" + + assert state.state == STATE_UNKNOWN + + device_registry = await hass.helpers.device_registry.async_get_registry() + device = device_registry.async_get(entry.device_id) + + assert device.name == "My air sensor" + assert device.identifiers == {("blebox", "abcd0123ef5678")} + assert device.manufacturer == "BleBox" + assert device.model == "airSensor" + assert device.sw_version == "1.23" + + +async def test_update(airsensor, hass, config): + """Test air quality sensor state after update.""" + + feature_mock, entity_id = airsensor + + def initial_update(): + feature_mock.pm1 = 49 + feature_mock.pm2_5 = 222 + feature_mock.pm10 = 333 + + feature_mock.async_update = AsyncMock(side_effect=initial_update) + await async_setup_entity(hass, config, entity_id) + + state = hass.states.get(entity_id) + + assert state.attributes[ATTR_PM_0_1] == 49 + assert state.attributes[ATTR_PM_2_5] == 222 + assert state.attributes[ATTR_PM_10] == 333 + + assert state.state == "222" + + +async def test_update_failure(airsensor, hass, config, caplog): + """Test that update failures are logged.""" + + caplog.set_level(logging.ERROR) + + feature_mock, entity_id = airsensor + feature_mock.async_update = AsyncMock(side_effect=blebox_uniapi.error.ClientError) + await async_setup_entity(hass, config, entity_id) + + assert f"Updating '{feature_mock.full_name}' failed: " in caplog.text From 11b786a4fc39d3a31c8ab27045d88c9a437003b5 Mon Sep 17 00:00:00 2001 From: Robert Van Gorkom Date: Sat, 16 May 2020 08:53:11 -0700 Subject: [PATCH 049/406] Add config flow to gogogate2 component (#34709) * Add config flow to gogogate2 component. * Using a more stable gogogate api. * Getting config flows working better by using different downstream library. * Fixing options not getting default values. Adding availability to cover entity. * Simplifying return types of function. * Address PR feedback. * Making user config flow not abort. * Using DataUpdateCoordinator. * Addressing PR feedback. * Using standard method for using hass.data * Split auth fail test into separate tests. --- .coveragerc | 1 - CODEOWNERS | 1 + .../components/gogogate2/__init__.py | 35 ++ homeassistant/components/gogogate2/common.py | 99 ++++ .../components/gogogate2/config_flow.py | 74 +++ homeassistant/components/gogogate2/const.py | 4 + homeassistant/components/gogogate2/cover.py | 204 ++++--- .../components/gogogate2/manifest.json | 5 +- .../components/gogogate2/strings.json | 22 + homeassistant/generated/config_flows.py | 1 + requirements_all.txt | 6 +- requirements_test_all.txt | 3 + tests/components/gogogate2/__init__.py | 1 + tests/components/gogogate2/common.py | 162 ++++++ tests/components/gogogate2/conftest.py | 17 + .../components/gogogate2/test_config_flow.py | 64 +++ tests/components/gogogate2/test_cover.py | 531 ++++++++++++++++++ tests/components/gogogate2/test_init.py | 28 + 18 files changed, 1189 insertions(+), 69 deletions(-) create mode 100644 homeassistant/components/gogogate2/common.py create mode 100644 homeassistant/components/gogogate2/config_flow.py create mode 100644 homeassistant/components/gogogate2/const.py create mode 100644 homeassistant/components/gogogate2/strings.json create mode 100644 tests/components/gogogate2/__init__.py create mode 100644 tests/components/gogogate2/common.py create mode 100644 tests/components/gogogate2/conftest.py create mode 100644 tests/components/gogogate2/test_config_flow.py create mode 100644 tests/components/gogogate2/test_cover.py create mode 100644 tests/components/gogogate2/test_init.py diff --git a/.coveragerc b/.coveragerc index c91b7b2a1a0..92caff7127b 100644 --- a/.coveragerc +++ b/.coveragerc @@ -281,7 +281,6 @@ omit = homeassistant/components/glances/sensor.py homeassistant/components/gntp/notify.py homeassistant/components/goalfeed/* - homeassistant/components/gogogate2/cover.py homeassistant/components/google/* homeassistant/components/google_cloud/tts.py homeassistant/components/google_maps/device_tracker.py diff --git a/CODEOWNERS b/CODEOWNERS index da3d035925c..82c9c05bbb5 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -148,6 +148,7 @@ homeassistant/components/gios/* @bieniu homeassistant/components/gitter/* @fabaff homeassistant/components/glances/* @fabaff @engrbm87 homeassistant/components/gntp/* @robbiet480 +homeassistant/components/gogogate2/* @vangorra homeassistant/components/google_assistant/* @home-assistant/cloud homeassistant/components/google_cloud/* @lufton homeassistant/components/google_translate/* @awarecan diff --git a/homeassistant/components/gogogate2/__init__.py b/homeassistant/components/gogogate2/__init__.py index ef802a4aa59..36f623f7895 100644 --- a/homeassistant/components/gogogate2/__init__.py +++ b/homeassistant/components/gogogate2/__init__.py @@ -1 +1,36 @@ """The gogogate2 component.""" +from homeassistant.components.cover import DOMAIN as COVER_DOMAIN +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ConfigEntryNotReady + +from .common import get_data_update_coordinator + + +async def async_setup(hass: HomeAssistant, base_config: dict) -> bool: + """Set up for Gogogate2 controllers.""" + return True + + +async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: + """Do setup of Gogogate2.""" + data_update_coordinator = get_data_update_coordinator(hass, config_entry) + await data_update_coordinator.async_refresh() + + if not data_update_coordinator.last_update_success: + raise ConfigEntryNotReady() + + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(config_entry, COVER_DOMAIN) + ) + + return True + + +async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: + """Unload Gogogate2 config entry.""" + hass.async_create_task( + hass.config_entries.async_forward_entry_unload(config_entry, COVER_DOMAIN) + ) + + return True diff --git a/homeassistant/components/gogogate2/common.py b/homeassistant/components/gogogate2/common.py new file mode 100644 index 00000000000..c69dee662b0 --- /dev/null +++ b/homeassistant/components/gogogate2/common.py @@ -0,0 +1,99 @@ +"""Common code for GogoGate2 component.""" +from datetime import timedelta +import logging +from typing import Awaitable, Callable, NamedTuple, Optional + +import async_timeout +from gogogate2_api import GogoGate2Api +from gogogate2_api.common import Door + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_IP_ADDRESS, CONF_PASSWORD, CONF_USERNAME +from homeassistant.core import HomeAssistant +from homeassistant.helpers.debounce import Debouncer +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed + +from .const import DATA_UPDATE_COORDINATOR, DOMAIN + +_LOGGER = logging.getLogger(__name__) + + +class StateData(NamedTuple): + """State data for a cover entity.""" + + config_unique_id: str + unique_id: Optional[str] + door: Optional[Door] + + +class GogoGateDataUpdateCoordinator(DataUpdateCoordinator): + """Manages polling for state changes from the device.""" + + def __init__( + self, + hass: HomeAssistant, + logger: logging.Logger, + api: GogoGate2Api, + *, + name: str, + update_interval: timedelta, + update_method: Optional[Callable[[], Awaitable]] = None, + request_refresh_debouncer: Optional[Debouncer] = None, + ): + """Initialize the data update coordinator.""" + DataUpdateCoordinator.__init__( + self, + hass, + logger, + name=name, + update_interval=update_interval, + update_method=update_method, + request_refresh_debouncer=request_refresh_debouncer, + ) + self.api = api + + +def get_data_update_coordinator( + hass: HomeAssistant, config_entry: ConfigEntry +) -> GogoGateDataUpdateCoordinator: + """Get an update coordinator.""" + hass.data.setdefault(DOMAIN, {}) + hass.data[DOMAIN].setdefault(config_entry.entry_id, {}) + config_entry_data = hass.data[DOMAIN][config_entry.entry_id] + + if DATA_UPDATE_COORDINATOR not in config_entry_data: + api = get_api(config_entry.data) + + async def async_update_data(): + try: + async with async_timeout.timeout(3): + return await hass.async_add_executor_job(api.info) + except Exception as exception: + raise UpdateFailed(f"Error communicating with API: {exception}") + + config_entry_data[DATA_UPDATE_COORDINATOR] = GogoGateDataUpdateCoordinator( + hass, + _LOGGER, + api, + # Name of the data. For logging purposes. + name="gogogate2", + update_method=async_update_data, + # Polling interval. Will only be polled if there are subscribers. + update_interval=timedelta(seconds=5), + ) + + return config_entry_data[DATA_UPDATE_COORDINATOR] + + +def cover_unique_id(config_entry: ConfigEntry, door: Door) -> str: + """Generate a cover entity unique id.""" + return f"{config_entry.unique_id}_{door.door_id}" + + +def get_api(config_data: dict) -> GogoGate2Api: + """Get an api object for config data.""" + return GogoGate2Api( + config_data[CONF_IP_ADDRESS], + config_data[CONF_USERNAME], + config_data[CONF_PASSWORD], + ) diff --git a/homeassistant/components/gogogate2/config_flow.py b/homeassistant/components/gogogate2/config_flow.py new file mode 100644 index 00000000000..bca340fa62b --- /dev/null +++ b/homeassistant/components/gogogate2/config_flow.py @@ -0,0 +1,74 @@ +"""Config flow for Gogogate2.""" +import logging +import re + +from gogogate2_api.common import ApiError +from gogogate2_api.const import ApiErrorCode +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.config_entries import SOURCE_IMPORT, ConfigFlow +from homeassistant.const import CONF_IP_ADDRESS, CONF_PASSWORD, CONF_USERNAME + +from .common import get_api +from .const import DOMAIN # pylint: disable=unused-import + +_LOGGER = logging.getLogger(__name__) + + +class Gogogate2FlowHandler(ConfigFlow, domain=DOMAIN): + """Gogogate2 config flow.""" + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL + + async def async_step_import(self, config_data: dict = None): + """Handle importing of configuration.""" + result = await self.async_step_user(config_data) + self._abort_if_unique_id_configured() + return result + + async def async_step_user(self, user_input: dict = None): + """Handle user initiated flow.""" + user_input = user_input or {} + errors = {} + + if user_input: + api = get_api(user_input) + try: + data = await self.hass.async_add_executor_job(api.info) + await self.async_set_unique_id(re.sub("\\..*$", "", data.remoteaccess)) + return self.async_create_entry(title=data.gogogatename, data=user_input) + + except ApiError as api_error: + if api_error.code in ( + ApiErrorCode.CREDENTIALS_NOT_SET, + ApiErrorCode.CREDENTIALS_INCORRECT, + ): + errors["base"] = "invalid_auth" + else: + errors["base"] = "cannot_connect" + + except Exception: # pylint: disable=broad-except + errors["base"] = "cannot_connect" + + if errors and self.source == SOURCE_IMPORT: + return self.async_abort(reason="cannot_connect") + + return self.async_show_form( + step_id="user", + data_schema=vol.Schema( + { + vol.Required( + CONF_IP_ADDRESS, default=user_input.get(CONF_IP_ADDRESS, "") + ): str, + vol.Required( + CONF_USERNAME, default=user_input.get(CONF_USERNAME, "") + ): str, + vol.Required( + CONF_PASSWORD, default=user_input.get(CONF_PASSWORD, "") + ): str, + } + ), + errors=errors, + ) diff --git a/homeassistant/components/gogogate2/const.py b/homeassistant/components/gogogate2/const.py new file mode 100644 index 00000000000..359de5f750c --- /dev/null +++ b/homeassistant/components/gogogate2/const.py @@ -0,0 +1,4 @@ +"""Constants for integration.""" + +DOMAIN = "gogogate2" +DATA_UPDATE_COORDINATOR = "data_update_coordinator" diff --git a/homeassistant/components/gogogate2/cover.py b/homeassistant/components/gogogate2/cover.py index 68babd3debe..05fed7621d4 100644 --- a/homeassistant/components/gogogate2/cover.py +++ b/homeassistant/components/gogogate2/cover.py @@ -1,112 +1,190 @@ """Support for Gogogate2 garage Doors.""" +from datetime import datetime, timedelta import logging +from typing import Callable, List, Optional -from pygogogate2 import Gogogate2API as pygogogate2 +from gogogate2_api.common import Door, DoorStatus, get_configured_doors, get_door_by_id import voluptuous as vol -from homeassistant.components.cover import SUPPORT_CLOSE, SUPPORT_OPEN, CoverEntity +from homeassistant.components.cover import ( + DEVICE_CLASS_GARAGE, + SUPPORT_CLOSE, + SUPPORT_OPEN, + CoverEntity, +) +from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import ( CONF_IP_ADDRESS, - CONF_NAME, CONF_PASSWORD, CONF_USERNAME, - STATE_CLOSED, + STATE_CLOSING, + STATE_OPENING, ) +from homeassistant.core import HomeAssistant, callback import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import Entity + +from .common import ( + GogoGateDataUpdateCoordinator, + cover_unique_id, + get_data_update_coordinator, +) +from .const import DOMAIN _LOGGER = logging.getLogger(__name__) -DEFAULT_NAME = "gogogate2" - -NOTIFICATION_ID = "gogogate2_notification" -NOTIFICATION_TITLE = "Gogogate2 Cover Setup" COVER_SCHEMA = vol.Schema( { vol.Required(CONF_IP_ADDRESS): cv.string, vol.Required(CONF_PASSWORD): cv.string, vol.Required(CONF_USERNAME): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, } ) -def setup_platform(hass, config, add_entities, discovery_info=None): - """Set up the Gogogate2 component.""" - - ip_address = config.get(CONF_IP_ADDRESS) - name = config.get(CONF_NAME) - password = config.get(CONF_PASSWORD) - username = config.get(CONF_USERNAME) - - mygogogate2 = pygogogate2(username, password, ip_address) - - try: - devices = mygogogate2.get_devices() - if devices is False: - raise ValueError("Username or Password is incorrect or no devices found") - - add_entities(MyGogogate2Device(mygogogate2, door, name) for door in devices) - - except (TypeError, KeyError, NameError, ValueError) as ex: - _LOGGER.error("%s", ex) - hass.components.persistent_notification.create( - (f"Error: {ex}
You will need to restart hass after fixing."), - title=NOTIFICATION_TITLE, - notification_id=NOTIFICATION_ID, +async def async_setup_platform( + hass: HomeAssistant, config: dict, add_entities: Callable, discovery_info=None +) -> None: + """Convert old style file configs to new style configs.""" + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_IMPORT}, data=config ) + ) -class MyGogogate2Device(CoverEntity): - """Representation of a Gogogate2 cover.""" +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: Callable[[List[Entity], Optional[bool]], None], +) -> None: + """Set up the config entry.""" + data_update_coordinator = get_data_update_coordinator(hass, config_entry) - def __init__(self, mygogogate2, device, name): - """Initialize with API object, device id.""" - self.mygogogate2 = mygogogate2 - self.device_id = device["door"] - self._name = name or device["name"] - self._status = device["status"] - self._available = None + async_add_entities( + [ + Gogogate2Cover(config_entry, data_update_coordinator, door) + for door in get_configured_doors(data_update_coordinator.data) + ] + ) + + +class Gogogate2Cover(CoverEntity): + """Cover entity for goggate2.""" + + def __init__( + self, + config_entry: ConfigEntry, + data_update_coordinator: GogoGateDataUpdateCoordinator, + door: Door, + ) -> None: + """Initialize the object.""" + self._config_entry = config_entry + self._data_update_coordinator = data_update_coordinator + self._door = door + self._api = data_update_coordinator.api + self._unique_id = cover_unique_id(config_entry, door) + self._is_available = True + self._transition_state: Optional[str] = None + self._transition_state_start: Optional[datetime] = None + + @property + def available(self) -> bool: + """Return True if entity is available.""" + return self._is_available + + @property + def should_poll(self) -> bool: + """Return False as the data manager handles dispatching data.""" + return False + + @property + def unique_id(self) -> Optional[str]: + """Return a unique ID.""" + return self._unique_id @property def name(self): - """Return the name of the garage door if any.""" - return self._name if self._name else DEFAULT_NAME + """Return the name of the door.""" + return self._door.name @property def is_closed(self): """Return true if cover is closed, else False.""" - return self._status == STATE_CLOSED + if self._door.status == DoorStatus.OPENED: + return False + if self._door.status == DoorStatus.CLOSED: + return True + + return None + + @property + def is_opening(self): + """Return if the cover is opening or not.""" + return self._transition_state == STATE_OPENING + + @property + def is_closing(self): + """Return if the cover is closing or not.""" + return self._transition_state == STATE_CLOSING @property def device_class(self): """Return the class of this device, from component DEVICE_CLASSES.""" - return "garage" + return DEVICE_CLASS_GARAGE @property def supported_features(self): """Flag supported features.""" return SUPPORT_OPEN | SUPPORT_CLOSE + async def async_open_cover(self, **kwargs): + """Open the door.""" + await self.hass.async_add_executor_job(self._api.open_door, self._door.door_id) + self._transition_state = STATE_OPENING + self._transition_state_start = datetime.now() + + async def async_close_cover(self, **kwargs): + """Close the door.""" + await self.hass.async_add_executor_job(self._api.close_door, self._door.door_id) + self._transition_state = STATE_CLOSING + self._transition_state_start = datetime.now() + @property - def available(self): - """Could the device be accessed during the last update call.""" - return self._available + def state_attributes(self): + """Return the state attributes.""" + attrs = super().state_attributes + attrs["door_id"] = self._door.door_id + return attrs - def close_cover(self, **kwargs): - """Issue close command to cover.""" - self.mygogogate2.close_device(self.device_id) + @callback + def async_on_data_updated(self) -> None: + """Receive data from data dispatcher.""" + if not self._data_update_coordinator.last_update_success: + self._is_available = False + self.async_write_ha_state() + return - def open_cover(self, **kwargs): - """Issue open command to cover.""" - self.mygogogate2.open_device(self.device_id) + door = get_door_by_id(self._door.door_id, self._data_update_coordinator.data) - def update(self): - """Update status of cover.""" - try: - self._status = self.mygogogate2.get_status(self.device_id) - self._available = True - except (TypeError, KeyError, NameError, ValueError) as ex: - _LOGGER.error("%s", ex) - self._status = None - self._available = False + # Check if the transition state should expire. + if self._transition_state: + is_transition_state_expired = ( + datetime.now() - self._transition_state_start + ) > timedelta(seconds=60) + + if is_transition_state_expired or self._door.status != door.status: + self._transition_state = None + self._transition_state_start = None + + # Set the state. + self._door = door + self._is_available = True + self.async_write_ha_state() + + async def async_added_to_hass(self) -> None: + """Register update dispatcher.""" + self.async_on_remove( + self._data_update_coordinator.async_add_listener(self.async_on_data_updated) + ) diff --git a/homeassistant/components/gogogate2/manifest.json b/homeassistant/components/gogogate2/manifest.json index 829df5a1c37..98aabba43b8 100644 --- a/homeassistant/components/gogogate2/manifest.json +++ b/homeassistant/components/gogogate2/manifest.json @@ -1,7 +1,8 @@ { "domain": "gogogate2", "name": "Gogogate2", + "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/gogogate2", - "requirements": ["pygogogate2==0.1.1"], - "codeowners": [] + "requirements": ["gogogate2-api==1.0.3"], + "codeowners": ["@vangorra"] } diff --git a/homeassistant/components/gogogate2/strings.json b/homeassistant/components/gogogate2/strings.json new file mode 100644 index 00000000000..bbd4e8d80d1 --- /dev/null +++ b/homeassistant/components/gogogate2/strings.json @@ -0,0 +1,22 @@ +{ + "config": { + "error": { + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]" + }, + "abort": { + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" + }, + "step": { + "user": { + "title": "Setup GogoGate2", + "description": "Provide requisite information below.", + "data": { + "ip_address": "IP Address", + "username": "[%key:common::config_flow::data::username%]", + "password": "[%key:common::config_flow::data::password%]" + } + } + } + } +} diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 43ccb4ef6d1..e1159064bf8 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -51,6 +51,7 @@ FLOWS = [ "geonetnz_volcano", "gios", "glances", + "gogogate2", "gpslogger", "griddy", "hangouts", diff --git a/requirements_all.txt b/requirements_all.txt index 5a4b33a30d1..bf7430a7df7 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -646,6 +646,9 @@ glances_api==0.2.0 # homeassistant.components.gntp gntp==1.0.3 +# homeassistant.components.gogogate2 +gogogate2-api==1.0.3 + # homeassistant.components.google google-api-python-client==1.6.4 @@ -1344,9 +1347,6 @@ pyfttt==0.3 # homeassistant.components.skybeacon pygatt[GATTTOOL]==4.0.5 -# homeassistant.components.gogogate2 -pygogogate2==0.1.1 - # homeassistant.components.gtfs pygtfs==0.1.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index fce1de85f1b..da24c835e00 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -278,6 +278,9 @@ gios==0.1.1 # homeassistant.components.glances glances_api==0.2.0 +# homeassistant.components.gogogate2 +gogogate2-api==1.0.3 + # homeassistant.components.google google-api-python-client==1.6.4 diff --git a/tests/components/gogogate2/__init__.py b/tests/components/gogogate2/__init__.py new file mode 100644 index 00000000000..bc867ab646b --- /dev/null +++ b/tests/components/gogogate2/__init__.py @@ -0,0 +1 @@ +"""Tests for the GogoGate2 component.""" diff --git a/tests/components/gogogate2/common.py b/tests/components/gogogate2/common.py new file mode 100644 index 00000000000..d344a31cf4b --- /dev/null +++ b/tests/components/gogogate2/common.py @@ -0,0 +1,162 @@ +"""Common test code.""" +from typing import List, NamedTuple, Optional +from unittest.mock import MagicMock, Mock + +from gogogate2_api import GogoGate2Api, InfoResponse +from gogogate2_api.common import Door, DoorMode, DoorStatus, Network, Outputs, Wifi + +from homeassistant.components import persistent_notification +from homeassistant.components.cover import DOMAIN as COVER_DOMAIN +from homeassistant.components.gogogate2 import async_unload_entry +from homeassistant.components.gogogate2.common import ( + GogoGateDataUpdateCoordinator, + get_data_update_coordinator, +) +import homeassistant.components.gogogate2.const as const +from homeassistant.components.homeassistant import DOMAIN as HA_DOMAIN +from homeassistant.config import async_process_ha_core_config +from homeassistant.config_entries import SOURCE_USER +from homeassistant.const import CONF_UNIT_SYSTEM, CONF_UNIT_SYSTEM_METRIC +from homeassistant.core import HomeAssistant +from homeassistant.data_entry_flow import RESULT_TYPE_CREATE_ENTRY, RESULT_TYPE_FORM +from homeassistant.setup import async_setup_component + +INFO_RESPONSE = InfoResponse( + user="user1", + gogogatename="gogogatename1", + model="", + apiversion="", + remoteaccessenabled=False, + remoteaccess="abcdefg.my-gogogate.com", + firmwareversion="", + apicode="API_CODE", + door1=Door( + door_id=1, + permission=True, + name="Door1", + mode=DoorMode.GARAGE, + status=DoorStatus.OPENED, + sensor=True, + sensorid=None, + camera=False, + events=2, + temperature=None, + ), + door2=Door( + door_id=2, + permission=True, + name=None, + mode=DoorMode.GARAGE, + status=DoorStatus.OPENED, + sensor=True, + sensorid=None, + camera=False, + events=2, + temperature=None, + ), + door3=Door( + door_id=3, + permission=True, + name="Door3", + mode=DoorMode.GARAGE, + status=DoorStatus.OPENED, + sensor=True, + sensorid=None, + camera=False, + events=2, + temperature=None, + ), + outputs=Outputs(output1=True, output2=False, output3=True), + network=Network(ip=""), + wifi=Wifi(SSID="", linkquality="", signal=""), +) + + +class ComponentData(NamedTuple): + """Test data for a mocked component.""" + + api: GogoGate2Api + data_update_coordinator: GogoGateDataUpdateCoordinator + + +class ComponentFactory: + """Manages the setup and unloading of the withing component and profiles.""" + + def __init__(self, hass: HomeAssistant, gogogate_api_mock: Mock) -> None: + """Initialize the object.""" + self._hass = hass + self._gogogate_api_mock = gogogate_api_mock + + @property + def api_class_mock(self): + """Get the api class mock.""" + return self._gogogate_api_mock + + async def configure_component( + self, cover_config: Optional[List[dict]] = None + ) -> None: + """Configure the component.""" + hass_config = { + "homeassistant": {CONF_UNIT_SYSTEM: CONF_UNIT_SYSTEM_METRIC}, + "cover": cover_config or [], + } + + await async_process_ha_core_config(self._hass, hass_config.get("homeassistant")) + assert await async_setup_component(self._hass, HA_DOMAIN, {}) + assert await async_setup_component( + self._hass, persistent_notification.DOMAIN, {} + ) + assert await async_setup_component(self._hass, COVER_DOMAIN, hass_config) + assert await async_setup_component(self._hass, const.DOMAIN, hass_config) + await self._hass.async_block_till_done() + + async def run_config_flow( + self, config_data: dict, api_mock: Optional[GogoGate2Api] = None + ) -> ComponentData: + """Run a config flow.""" + if api_mock is None: + api_mock: GogoGate2Api = MagicMock(spec=GogoGate2Api) + api_mock.info.return_value = INFO_RESPONSE + + self._gogogate_api_mock.reset_mocks() + self._gogogate_api_mock.return_value = api_mock + + result = await self._hass.config_entries.flow.async_init( + const.DOMAIN, context={"source": SOURCE_USER} + ) + assert result + assert result["type"] == RESULT_TYPE_FORM + assert result["step_id"] == "user" + + result = await self._hass.config_entries.flow.async_configure( + result["flow_id"], user_input=config_data, + ) + assert result + assert result["type"] == RESULT_TYPE_CREATE_ENTRY + assert result["data"] == config_data + + await self._hass.async_block_till_done() + + config_entry = next( + iter( + entry + for entry in self._hass.config_entries.async_entries(const.DOMAIN) + if entry.unique_id == "abcdefg" + ) + ) + + return ComponentData( + api=api_mock, + data_update_coordinator=get_data_update_coordinator( + self._hass, config_entry + ), + ) + + async def unload(self) -> None: + """Unload all config entries.""" + config_entries = self._hass.config_entries.async_entries(const.DOMAIN) + for config_entry in config_entries: + await async_unload_entry(self._hass, config_entry) + + await self._hass.async_block_till_done() + assert not self._hass.states.async_entity_ids("gogogate") diff --git a/tests/components/gogogate2/conftest.py b/tests/components/gogogate2/conftest.py new file mode 100644 index 00000000000..6e2e58d8f9c --- /dev/null +++ b/tests/components/gogogate2/conftest.py @@ -0,0 +1,17 @@ +"""Fixtures for tests.""" + +from mock import patch +import pytest + +from homeassistant.core import HomeAssistant + +from .common import ComponentFactory + + +@pytest.fixture() +def component_factory(hass: HomeAssistant): + """Return a factory for initializing the gogogate2 api.""" + with patch( + "homeassistant.components.gogogate2.common.GogoGate2Api" + ) as gogogate2_api_mock: + yield ComponentFactory(hass, gogogate2_api_mock) diff --git a/tests/components/gogogate2/test_config_flow.py b/tests/components/gogogate2/test_config_flow.py new file mode 100644 index 00000000000..e921df406d2 --- /dev/null +++ b/tests/components/gogogate2/test_config_flow.py @@ -0,0 +1,64 @@ +"""Tests for the GogoGate2 component.""" +from gogogate2_api import GogoGate2Api +from gogogate2_api.common import ApiError +from gogogate2_api.const import ApiErrorCode + +from homeassistant.config_entries import SOURCE_USER +from homeassistant.const import CONF_IP_ADDRESS, CONF_PASSWORD, CONF_USERNAME +from homeassistant.core import HomeAssistant +from homeassistant.data_entry_flow import RESULT_TYPE_FORM + +from .common import ComponentFactory + +from tests.async_mock import MagicMock, patch + + +async def test_auth_fail( + hass: HomeAssistant, component_factory: ComponentFactory +) -> None: + """Test authorization failures.""" + api_mock: GogoGate2Api = MagicMock(spec=GogoGate2Api) + + with patch( + "homeassistant.components.gogogate2.async_setup", return_value=True + ), patch( + "homeassistant.components.gogogate2.async_setup_entry", return_value=True, + ): + await component_factory.configure_component() + component_factory.api_class_mock.return_value = api_mock + + api_mock.reset_mock() + api_mock.info.side_effect = ApiError(ApiErrorCode.CREDENTIALS_INCORRECT, "blah") + result = await hass.config_entries.flow.async_init( + "gogogate2", context={"source": SOURCE_USER} + ) + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={ + CONF_IP_ADDRESS: "127.0.0.2", + CONF_USERNAME: "user0", + CONF_PASSWORD: "password0", + }, + ) + assert result + assert result["type"] == RESULT_TYPE_FORM + assert result["errors"] == { + "base": "invalid_auth", + } + + api_mock.reset_mock() + api_mock.info.side_effect = Exception("Generic connection error.") + result = await hass.config_entries.flow.async_init( + "gogogate2", context={"source": SOURCE_USER} + ) + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={ + CONF_IP_ADDRESS: "127.0.0.2", + CONF_USERNAME: "user0", + CONF_PASSWORD: "password0", + }, + ) + assert result + assert result["type"] == RESULT_TYPE_FORM + assert result["errors"] == {"base": "cannot_connect"} diff --git a/tests/components/gogogate2/test_cover.py b/tests/components/gogogate2/test_cover.py new file mode 100644 index 00000000000..8cffec47e65 --- /dev/null +++ b/tests/components/gogogate2/test_cover.py @@ -0,0 +1,531 @@ +"""Tests for the GogoGate2 component.""" +from datetime import datetime, timedelta +from unittest.mock import MagicMock, patch + +from gogogate2_api import GogoGate2Api +from gogogate2_api.common import ( + ActivateResponse, + ApiError, + Door, + DoorMode, + DoorStatus, + InfoResponse, + Network, + Outputs, + Wifi, +) + +from homeassistant.components.cover import DOMAIN as COVER_DOMAIN +from homeassistant.components.homeassistant import DOMAIN as HA_DOMAIN +from homeassistant.const import ( + CONF_IP_ADDRESS, + CONF_NAME, + CONF_PASSWORD, + CONF_PLATFORM, + CONF_USERNAME, + STATE_CLOSED, + STATE_CLOSING, + STATE_OPEN, + STATE_OPENING, + STATE_UNAVAILABLE, +) +from homeassistant.core import HomeAssistant + +from .common import ComponentFactory + + +async def test_import_fail( + hass: HomeAssistant, component_factory: ComponentFactory +) -> None: + """Test the failure to import.""" + api = MagicMock(spec=GogoGate2Api) + api.info.side_effect = ApiError(22, "Error") + + component_factory.api_class_mock.return_value = api + + await component_factory.configure_component( + cover_config=[ + { + CONF_PLATFORM: "gogogate2", + CONF_NAME: "cover0", + CONF_IP_ADDRESS: "127.0.1.0", + CONF_USERNAME: "user0", + CONF_PASSWORD: "password0", + } + ] + ) + + entity_ids = hass.states.async_entity_ids(COVER_DOMAIN) + assert not entity_ids + + +async def test_import(hass: HomeAssistant, component_factory: ComponentFactory) -> None: + """Test importing of file based config.""" + api0 = MagicMock(spec=GogoGate2Api) + api0.info.return_value = InfoResponse( + user="user1", + gogogatename="gogogatename0", + model="", + apiversion="", + remoteaccessenabled=False, + remoteaccess="abc123.blah.blah", + firmwareversion="", + apicode="", + door1=Door( + door_id=1, + permission=True, + name="Door1", + mode=DoorMode.GARAGE, + status=DoorStatus.OPENED, + sensor=True, + sensorid=None, + camera=False, + events=2, + temperature=None, + ), + door2=Door( + door_id=2, + permission=True, + name=None, + mode=DoorMode.GARAGE, + status=DoorStatus.UNDEFINED, + sensor=True, + sensorid=None, + camera=False, + events=0, + temperature=None, + ), + door3=Door( + door_id=3, + permission=True, + name=None, + mode=DoorMode.GARAGE, + status=DoorStatus.UNDEFINED, + sensor=True, + sensorid=None, + camera=False, + events=0, + temperature=None, + ), + outputs=Outputs(output1=True, output2=False, output3=True), + network=Network(ip=""), + wifi=Wifi(SSID="", linkquality="", signal=""), + ) + + api1 = MagicMock(spec=GogoGate2Api) + api1.info.return_value = InfoResponse( + user="user1", + gogogatename="gogogatename0", + model="", + apiversion="", + remoteaccessenabled=False, + remoteaccess="321bca.blah.blah", + firmwareversion="", + apicode="", + door1=Door( + door_id=1, + permission=True, + name="Door1", + mode=DoorMode.GARAGE, + status=DoorStatus.CLOSED, + sensor=True, + sensorid=None, + camera=False, + events=2, + temperature=None, + ), + door2=Door( + door_id=2, + permission=True, + name=None, + mode=DoorMode.GARAGE, + status=DoorStatus.UNDEFINED, + sensor=True, + sensorid=None, + camera=False, + events=0, + temperature=None, + ), + door3=Door( + door_id=3, + permission=True, + name=None, + mode=DoorMode.GARAGE, + status=DoorStatus.UNDEFINED, + sensor=True, + sensorid=None, + camera=False, + events=0, + temperature=None, + ), + outputs=Outputs(output1=True, output2=False, output3=True), + network=Network(ip=""), + wifi=Wifi(SSID="", linkquality="", signal=""), + ) + + def new_api(ip_address: str, username: str, password: str) -> GogoGate2Api: + if ip_address == "127.0.1.0": + return api0 + if ip_address == "127.0.1.1": + return api1 + raise Exception(f"Untested ip address {ip_address}") + + component_factory.api_class_mock.side_effect = new_api + + await component_factory.configure_component( + cover_config=[ + { + CONF_PLATFORM: "gogogate2", + CONF_NAME: "cover0", + CONF_IP_ADDRESS: "127.0.1.0", + CONF_USERNAME: "user0", + CONF_PASSWORD: "password0", + }, + { + CONF_PLATFORM: "gogogate2", + CONF_NAME: "cover1", + CONF_IP_ADDRESS: "127.0.1.1", + CONF_USERNAME: "user1", + CONF_PASSWORD: "password1", + }, + ] + ) + entity_ids = hass.states.async_entity_ids(COVER_DOMAIN) + assert entity_ids is not None + assert len(entity_ids) == 2 + assert "cover.door1" in entity_ids + assert "cover.door1_2" in entity_ids + + await component_factory.unload() + + +async def test_cover_update( + hass: HomeAssistant, component_factory: ComponentFactory +) -> None: + """Test cover.""" + await component_factory.configure_component() + component_data = await component_factory.run_config_flow( + config_data={ + CONF_IP_ADDRESS: "127.0.0.2", + CONF_USERNAME: "user0", + CONF_PASSWORD: "password0", + } + ) + + assert hass.states.async_entity_ids(COVER_DOMAIN) + + state = hass.states.get("cover.door1") + assert state + assert state.state == STATE_OPEN + assert state.attributes["friendly_name"] == "Door1" + assert state.attributes["supported_features"] == 3 + assert state.attributes["device_class"] == "garage" + + component_data.data_update_coordinator.api.info.return_value = InfoResponse( + user="user1", + gogogatename="gogogatename0", + model="", + apiversion="", + remoteaccessenabled=False, + remoteaccess="abc123.blah.blah", + firmwareversion="", + apicode="", + door1=Door( + door_id=1, + permission=True, + name="Door1", + mode=DoorMode.GARAGE, + status=DoorStatus.OPENED, + sensor=True, + sensorid=None, + camera=False, + events=2, + temperature=None, + ), + door2=Door( + door_id=2, + permission=True, + name=None, + mode=DoorMode.GARAGE, + status=DoorStatus.UNDEFINED, + sensor=True, + sensorid=None, + camera=False, + events=0, + temperature=None, + ), + door3=Door( + door_id=3, + permission=True, + name=None, + mode=DoorMode.GARAGE, + status=DoorStatus.UNDEFINED, + sensor=True, + sensorid=None, + camera=False, + events=0, + temperature=None, + ), + outputs=Outputs(output1=True, output2=False, output3=True), + network=Network(ip=""), + wifi=Wifi(SSID="", linkquality="", signal=""), + ) + await component_data.data_update_coordinator.async_refresh() + await hass.async_block_till_done() + state = hass.states.get("cover.door1") + assert state + assert state.state == STATE_OPEN + + component_data.data_update_coordinator.api.info.return_value = InfoResponse( + user="user1", + gogogatename="gogogatename0", + model="", + apiversion="", + remoteaccessenabled=False, + remoteaccess="abc123.blah.blah", + firmwareversion="", + apicode="", + door1=Door( + door_id=1, + permission=True, + name="Door1", + mode=DoorMode.GARAGE, + status=DoorStatus.CLOSED, + sensor=True, + sensorid=None, + camera=False, + events=2, + temperature=None, + ), + door2=Door( + door_id=2, + permission=True, + name=None, + mode=DoorMode.GARAGE, + status=DoorStatus.UNDEFINED, + sensor=True, + sensorid=None, + camera=False, + events=0, + temperature=None, + ), + door3=Door( + door_id=3, + permission=True, + name=None, + mode=DoorMode.GARAGE, + status=DoorStatus.UNDEFINED, + sensor=True, + sensorid=None, + camera=False, + events=0, + temperature=None, + ), + outputs=Outputs(output1=True, output2=False, output3=True), + network=Network(ip=""), + wifi=Wifi(SSID="", linkquality="", signal=""), + ) + await component_data.data_update_coordinator.async_refresh() + await hass.async_block_till_done() + state = hass.states.get("cover.door1") + assert state + assert state.state == STATE_CLOSED + + +async def test_open_close( + hass: HomeAssistant, component_factory: ComponentFactory +) -> None: + """Test open and close.""" + closed_door_response = InfoResponse( + user="user1", + gogogatename="gogogatename0", + model="", + apiversion="", + remoteaccessenabled=False, + remoteaccess="abc123.blah.blah", + firmwareversion="", + apicode="", + door1=Door( + door_id=1, + permission=True, + name="Door1", + mode=DoorMode.GARAGE, + status=DoorStatus.CLOSED, + sensor=True, + sensorid=None, + camera=False, + events=2, + temperature=None, + ), + door2=Door( + door_id=2, + permission=True, + name=None, + mode=DoorMode.GARAGE, + status=DoorStatus.UNDEFINED, + sensor=True, + sensorid=None, + camera=False, + events=0, + temperature=None, + ), + door3=Door( + door_id=3, + permission=True, + name=None, + mode=DoorMode.GARAGE, + status=DoorStatus.UNDEFINED, + sensor=True, + sensorid=None, + camera=False, + events=0, + temperature=None, + ), + outputs=Outputs(output1=True, output2=False, output3=True), + network=Network(ip=""), + wifi=Wifi(SSID="", linkquality="", signal=""), + ) + + await component_factory.configure_component() + assert hass.states.get("cover.door1") is None + + component_data = await component_factory.run_config_flow( + config_data={ + CONF_IP_ADDRESS: "127.0.0.2", + CONF_USERNAME: "user0", + CONF_PASSWORD: "password0", + } + ) + + component_data.api.activate.return_value = ActivateResponse(result=True) + + assert hass.states.get("cover.door1").state == STATE_OPEN + await hass.services.async_call( + COVER_DOMAIN, "close_cover", service_data={"entity_id": "cover.door1"}, + ) + await hass.async_block_till_done() + component_data.api.close_door.assert_called_with(1) + await hass.services.async_call( + HA_DOMAIN, "update_entity", service_data={"entity_id": "cover.door1"}, + ) + await hass.async_block_till_done() + assert hass.states.get("cover.door1").state == STATE_CLOSING + + component_data.data_update_coordinator.api.info.return_value = closed_door_response + await component_data.data_update_coordinator.async_refresh() + await hass.async_block_till_done() + assert hass.states.get("cover.door1").state == STATE_CLOSED + + # Assert mid state changed when new status is received. + await hass.services.async_call( + COVER_DOMAIN, "open_cover", service_data={"entity_id": "cover.door1"}, + ) + await hass.async_block_till_done() + component_data.api.open_door.assert_called_with(1) + await hass.services.async_call( + HA_DOMAIN, "update_entity", service_data={"entity_id": "cover.door1"}, + ) + await hass.async_block_till_done() + assert hass.states.get("cover.door1").state == STATE_OPENING + + # Assert the mid state does not change when the same status is returned. + component_data.data_update_coordinator.api.info.return_value = closed_door_response + await component_data.data_update_coordinator.async_refresh() + component_data.data_update_coordinator.api.info.return_value = closed_door_response + await component_data.data_update_coordinator.async_refresh() + await hass.services.async_call( + HA_DOMAIN, "update_entity", service_data={"entity_id": "cover.door1"}, + ) + await hass.async_block_till_done() + assert hass.states.get("cover.door1").state == STATE_OPENING + + # Assert the mid state times out. + with patch("homeassistant.components.gogogate2.cover.datetime") as datetime_mock: + datetime_mock.now.return_value = datetime.now() + timedelta(seconds=60.1) + component_data.data_update_coordinator.api.info.return_value = ( + closed_door_response + ) + await component_data.data_update_coordinator.async_refresh() + await hass.services.async_call( + HA_DOMAIN, "update_entity", service_data={"entity_id": "cover.door1"}, + ) + await hass.async_block_till_done() + assert hass.states.get("cover.door1").state == STATE_CLOSED + + +async def test_availability( + hass: HomeAssistant, component_factory: ComponentFactory +) -> None: + """Test open and close.""" + closed_door_response = InfoResponse( + user="user1", + gogogatename="gogogatename0", + model="", + apiversion="", + remoteaccessenabled=False, + remoteaccess="abc123.blah.blah", + firmwareversion="", + apicode="", + door1=Door( + door_id=1, + permission=True, + name="Door1", + mode=DoorMode.GARAGE, + status=DoorStatus.CLOSED, + sensor=True, + sensorid=None, + camera=False, + events=2, + temperature=None, + ), + door2=Door( + door_id=2, + permission=True, + name=None, + mode=DoorMode.GARAGE, + status=DoorStatus.UNDEFINED, + sensor=True, + sensorid=None, + camera=False, + events=0, + temperature=None, + ), + door3=Door( + door_id=3, + permission=True, + name=None, + mode=DoorMode.GARAGE, + status=DoorStatus.UNDEFINED, + sensor=True, + sensorid=None, + camera=False, + events=0, + temperature=None, + ), + outputs=Outputs(output1=True, output2=False, output3=True), + network=Network(ip=""), + wifi=Wifi(SSID="", linkquality="", signal=""), + ) + + await component_factory.configure_component() + assert hass.states.get("cover.door1") is None + + component_data = await component_factory.run_config_flow( + config_data={ + CONF_IP_ADDRESS: "127.0.0.2", + CONF_USERNAME: "user0", + CONF_PASSWORD: "password0", + } + ) + assert hass.states.get("cover.door1").state == STATE_OPEN + + component_data.api.info.side_effect = Exception("Error") + await component_data.data_update_coordinator.async_refresh() + await hass.async_block_till_done() + assert hass.states.get("cover.door1").state == STATE_UNAVAILABLE + + component_data.api.info.side_effect = None + component_data.api.info.return_value = closed_door_response + await component_data.data_update_coordinator.async_refresh() + await hass.async_block_till_done() + assert hass.states.get("cover.door1").state == STATE_CLOSED diff --git a/tests/components/gogogate2/test_init.py b/tests/components/gogogate2/test_init.py new file mode 100644 index 00000000000..8788590407f --- /dev/null +++ b/tests/components/gogogate2/test_init.py @@ -0,0 +1,28 @@ +"""Tests for the GogoGate2 component.""" +import pytest + +from homeassistant.components.gogogate2 import async_setup_entry +from homeassistant.components.gogogate2.common import GogoGateDataUpdateCoordinator +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ConfigEntryNotReady + +from tests.async_mock import MagicMock, patch +from tests.common import MockConfigEntry + + +async def test_auth_fail(hass: HomeAssistant) -> None: + """Test authorization failures.""" + + coordinator_mock: GogoGateDataUpdateCoordinator = MagicMock( + spec=GogoGateDataUpdateCoordinator + ) + coordinator_mock.last_update_success = False + + config_entry = MockConfigEntry() + config_entry.add_to_hass(hass) + + with patch( + "homeassistant.components.gogogate2.get_data_update_coordinator", + return_value=coordinator_mock, + ), pytest.raises(ConfigEntryNotReady): + await async_setup_entry(hass, config_entry) From cd68f7dc144bd67402937f484c48c85f19978c69 Mon Sep 17 00:00:00 2001 From: Daniel Perna Date: Sat, 16 May 2020 17:56:26 +0200 Subject: [PATCH 050/406] Update pyhomematic to 0.1.67 (#35637) --- homeassistant/components/homematic/binary_sensor.py | 2 ++ homeassistant/components/homematic/const.py | 5 +++++ homeassistant/components/homematic/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 5 files changed, 10 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/homematic/binary_sensor.py b/homeassistant/components/homematic/binary_sensor.py index 041f2f02643..7315a266a30 100644 --- a/homeassistant/components/homematic/binary_sensor.py +++ b/homeassistant/components/homematic/binary_sensor.py @@ -30,6 +30,8 @@ SENSOR_TYPES_CLASS = { "TiltSensor": None, "WeatherSensor": None, "IPContact": DEVICE_CLASS_OPENING, + "MotionIPV2": DEVICE_CLASS_MOTION, + "IPRemoteMotionV2": DEVICE_CLASS_MOTION, } diff --git a/homeassistant/components/homematic/const.py b/homeassistant/components/homematic/const.py index 188ec1e2445..9339fca84e5 100644 --- a/homeassistant/components/homematic/const.py +++ b/homeassistant/components/homematic/const.py @@ -55,6 +55,7 @@ HM_DEVICE_TYPES = { "IPKeySwitch", "IPKeySwitchLevel", "IPMultiIO", + "IPWSwitch", ], DISCOVER_LIGHTS: [ "Dimmer", @@ -106,6 +107,8 @@ HM_DEVICE_TYPES = { "MotionIPV2", "IPMultiIO", "IPThermostatWall2", + "IPRemoteMotionV2", + "HBUNISenWEA", ], DISCOVER_CLIMATE: [ "Thermostat", @@ -145,6 +148,8 @@ HM_DEVICE_TYPES = { "TiltIP", "IPShutterContactSabotage", "IPContact", + "IPRemoteMotionV2", + "IPWInputDevice", ], DISCOVER_COVER: ["Blind", "KeyBlind", "IPKeyBlind", "IPKeyBlindTilt"], DISCOVER_LOCKS: ["KeyMatic"], diff --git a/homeassistant/components/homematic/manifest.json b/homeassistant/components/homematic/manifest.json index 31b26bbd511..de55b941b91 100644 --- a/homeassistant/components/homematic/manifest.json +++ b/homeassistant/components/homematic/manifest.json @@ -2,6 +2,6 @@ "domain": "homematic", "name": "Homematic", "documentation": "https://www.home-assistant.io/integrations/homematic", - "requirements": ["pyhomematic==0.1.66"], + "requirements": ["pyhomematic==0.1.67"], "codeowners": ["@pvizeli", "@danielperna84"] } diff --git a/requirements_all.txt b/requirements_all.txt index bf7430a7df7..54bdf4454d6 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1363,7 +1363,7 @@ pyhik==0.2.7 pyhiveapi==0.2.20.1 # homeassistant.components.homematic -pyhomematic==0.1.66 +pyhomematic==0.1.67 # homeassistant.components.homeworks pyhomeworks==0.0.6 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index da24c835e00..c57362d8634 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -576,7 +576,7 @@ pyhaversion==3.3.0 pyheos==0.6.0 # homeassistant.components.homematic -pyhomematic==0.1.66 +pyhomematic==0.1.67 # homeassistant.components.icloud pyicloud==0.9.7 From 98523fbb61cf1d82c2ee51d33d80efb7b02b61e8 Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Sat, 16 May 2020 14:30:54 -0400 Subject: [PATCH 051/406] Bump up ZHA dependencies (#35706) --- 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 cd153e0d2b8..bf4176db120 100644 --- a/homeassistant/components/zha/manifest.json +++ b/homeassistant/components/zha/manifest.json @@ -9,7 +9,7 @@ "zha-quirks==0.0.39", "zigpy-cc==0.4.2", "zigpy-deconz==0.9.2", - "zigpy==0.20.3", + "zigpy==0.20.4", "zigpy-xbee==0.12.1", "zigpy-zigate==0.6.1" ], diff --git a/requirements_all.txt b/requirements_all.txt index 54bdf4454d6..753d2cc29aa 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2260,7 +2260,7 @@ zigpy-xbee==0.12.1 zigpy-zigate==0.6.1 # homeassistant.components.zha -zigpy==0.20.3 +zigpy==0.20.4 # homeassistant.components.zoneminder zm-py==0.4.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index c57362d8634..1b5f57dbed6 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -921,4 +921,4 @@ zigpy-xbee==0.12.1 zigpy-zigate==0.6.1 # homeassistant.components.zha -zigpy==0.20.3 +zigpy==0.20.4 From fca96799a0fb26d54c5e3d6b70528daf11d9a38c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 16 May 2020 16:34:34 -0500 Subject: [PATCH 052/406] Prevent updater from delaying startup (#35708) * Prevent updater from delaying startup The updater sometimes times out as seen in #33833 and the linked issues. The issue was presenting again today as it appears the service is overloaded again. * s/hass.loop/asyncio/g --- homeassistant/components/updater/__init__.py | 4 +++- homeassistant/components/updater/binary_sensor.py | 12 +++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/updater/__init__.py b/homeassistant/components/updater/__init__.py index 869e3c55271..0b53850733f 100644 --- a/homeassistant/components/updater/__init__.py +++ b/homeassistant/components/updater/__init__.py @@ -1,4 +1,5 @@ """Support to check for available updates.""" +import asyncio from datetime import timedelta from distutils.version import StrictVersion import logging @@ -105,7 +106,8 @@ async def async_setup(hass, config): update_interval=timedelta(days=1), ) - await coordinator.async_refresh() + # This can take up to 15s which can delay startup + asyncio.create_task(coordinator.async_refresh()) hass.async_create_task( discovery.async_load_platform(hass, "binary_sensor", DOMAIN, {}, config) diff --git a/homeassistant/components/updater/binary_sensor.py b/homeassistant/components/updater/binary_sensor.py index 5088856ce6d..5d45b368500 100644 --- a/homeassistant/components/updater/binary_sensor.py +++ b/homeassistant/components/updater/binary_sensor.py @@ -33,6 +33,8 @@ class UpdaterBinary(BinarySensorEntity): @property def is_on(self) -> bool: """Return true if the binary sensor is on.""" + if not self.coordinator.data: + return None return self.coordinator.data.update_available @property @@ -48,6 +50,8 @@ class UpdaterBinary(BinarySensorEntity): @property def device_state_attributes(self) -> dict: """Return the optional state attributes.""" + if not self.coordinator.data: + return None data = {} if self.coordinator.data.release_notes: data[ATTR_RELEASE_NOTES] = self.coordinator.data.release_notes @@ -57,11 +61,9 @@ class UpdaterBinary(BinarySensorEntity): async def async_added_to_hass(self): """Register update dispatcher.""" - self.coordinator.async_add_listener(self.async_write_ha_state) - - async def async_will_remove_from_hass(self): - """When removed from hass.""" - self.coordinator.async_remove_listener(self.async_write_ha_state) + self.async_on_remove( + self.coordinator.async_add_listener(self.async_write_ha_state) + ) async def async_update(self): """Update the entity. From e94f44f2946d5d3db0d6fb44d7cbedb996101a49 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Sun, 17 May 2020 00:02:56 +0000 Subject: [PATCH 053/406] [ci skip] Translation update --- .../components/abode/translations/hu.json | 2 +- .../components/adguard/translations/hu.json | 1 + .../components/agent_dvr/translations/hu.json | 12 ++++ .../agent_dvr/translations/pt-BR.json | 7 ++ .../components/airly/translations/hu.json | 2 +- .../components/airvisual/translations/hu.json | 7 +- .../airvisual/translations/pt-BR.json | 14 ++++ .../translations/pt-BR.json | 7 ++ .../ambiclimate/translations/ko.json | 4 +- .../components/atag/translations/hu.json | 3 +- .../components/atag/translations/pt-BR.json | 3 + .../components/august/translations/hu.json | 12 ++++ .../components/august/translations/ko.json | 2 +- .../components/august/translations/pt-BR.json | 20 ++++++ .../components/blebox/translations/hu.json | 1 + .../components/blink/translations/fr.json | 9 +++ .../components/blink/translations/hu.json | 19 ++++++ .../components/braviatv/translations/hu.json | 11 ++++ .../braviatv/translations/pt-BR.json | 20 ++++++ .../components/brother/translations/hu.json | 2 +- .../components/bsblan/translations/hu.json | 12 ++++ .../components/bsblan/translations/pt-BR.json | 11 ++++ .../cert_expiry/translations/hu.json | 3 +- .../components/climate/translations/ko.json | 4 +- .../coronavirus/translations/pt-BR.json | 15 +++++ .../components/daikin/translations/hu.json | 11 +++- .../components/deconz/translations/hu.json | 6 ++ .../components/deconz/translations/ko.json | 2 +- .../components/deconz/translations/pt-BR.json | 6 ++ .../components/demo/translations/hu.json | 17 +++++ .../components/demo/translations/pt-BR.json | 17 +++++ .../devolo_home_control/translations/hu.json | 11 ++++ .../components/directv/translations/hu.json | 17 +++++ .../directv/translations/pt-BR.json | 10 +++ .../components/doorbird/translations/hu.json | 6 ++ .../components/doorbird/translations/ko.json | 2 +- .../doorbird/translations/pt-BR.json | 9 +++ .../components/ecobee/translations/ko.json | 2 +- .../components/elgato/translations/hu.json | 4 +- .../components/elkm1/translations/hu.json | 12 ++++ .../components/elkm1/translations/ko.json | 6 +- .../flick_electric/translations/hu.json | 12 ++++ .../flick_electric/translations/ko.json | 2 +- .../flick_electric/translations/pt-BR.json | 12 +++- .../components/flume/translations/hu.json | 12 ++++ .../components/flume/translations/pt-BR.json | 22 +++++++ .../forked_daapd/translations/ca.json | 1 + .../forked_daapd/translations/pt-BR.json | 8 ++- .../components/freebox/translations/hu.json | 4 ++ .../components/freebox/translations/ko.json | 2 +- .../components/fritzbox/translations/hu.json | 1 + .../fritzbox/translations/pt-BR.json | 12 ++++ .../garmin_connect/translations/hu.json | 2 +- .../garmin_connect/translations/pt-BR.json | 10 +++ .../components/gdacs/translations/pt-BR.json | 15 +++++ .../geonetnz_quakes/translations/pt-BR.json | 3 + .../components/glances/translations/hu.json | 2 +- .../components/gogogate2/translations/ca.json | 22 +++++++ .../components/gogogate2/translations/en.json | 22 +++++++ .../components/gogogate2/translations/hu.json | 19 ++++++ .../gogogate2/translations/pt-BR.json | 15 +++++ .../components/gogogate2/translations/ru.json | 22 +++++++ .../components/griddy/translations/pt-BR.json | 8 +++ .../components/hangouts/translations/hu.json | 2 +- .../components/harmony/translations/hu.json | 11 ++++ .../components/harmony/translations/ko.json | 2 +- .../harmony/translations/pt-BR.json | 34 ++++++++++ .../components/heos/translations/hu.json | 2 +- .../home_connect/translations/pt-BR.json | 7 ++ .../components/homekit/translations/ko.json | 12 ++-- .../homekit/translations/pt-BR.json | 8 +++ .../homematicip_cloud/translations/ko.json | 2 +- .../huawei_lte/translations/ko.json | 2 +- .../components/hue/translations/pt-BR.json | 6 ++ .../translations/hu.json | 2 +- .../components/iaqualink/translations/hu.json | 2 +- .../components/icloud/translations/hu.json | 3 +- .../components/ipma/translations/pt-BR.json | 1 + .../components/ipp/translations/ca.json | 3 +- .../components/ipp/translations/en.json | 3 +- .../components/ipp/translations/es.json | 3 +- .../components/ipp/translations/hu.json | 17 ++++- .../components/ipp/translations/ko.json | 3 +- .../components/ipp/translations/pt-BR.json | 28 ++++++++ .../components/ipp/translations/ru.json | 3 +- .../translations/pt-BR.json | 7 ++ .../components/isy994/translations/hu.json | 20 ++++++ .../components/isy994/translations/pt-BR.json | 11 +++- .../components/konnected/translations/hu.json | 10 +++ .../konnected/translations/pt-BR.json | 66 +++++++++++++++++++ .../components/life360/translations/hu.json | 6 +- .../components/light/translations/pt-BR.json | 4 ++ .../local_ip/translations/pt-BR.json | 3 + .../logi_circle/translations/ko.json | 4 +- .../components/melcloud/translations/hu.json | 12 ++++ .../meteo_france/translations/pt-BR.json | 15 +++++ .../components/mikrotik/translations/hu.json | 2 +- .../mikrotik/translations/pt-BR.json | 21 ++++++ .../components/mill/translations/hu.json | 18 +++++ .../minecraft_server/translations/hu.json | 2 +- .../minecraft_server/translations/pt-BR.json | 7 ++ .../components/monoprice/translations/hu.json | 11 ++++ .../monoprice/translations/pt-BR.json | 21 ++++++ .../components/mqtt/translations/hu.json | 12 +++- .../components/mqtt/translations/pt-BR.json | 12 ++++ .../components/myq/translations/hu.json | 12 ++++ .../components/netatmo/translations/hu.json | 11 +++- .../components/notion/translations/hu.json | 2 +- .../components/nuheat/translations/pt-BR.json | 22 +++++++ .../components/nut/translations/hu.json | 14 ++++ .../components/nut/translations/pt-BR.json | 35 ++++++++++ .../components/nws/translations/hu.json | 11 ++++ .../components/nws/translations/pt-BR.json | 22 +++++++ .../components/onvif/translations/hu.json | 18 +++++ .../components/onvif/translations/ko.json | 2 +- .../components/openuv/translations/hu.json | 2 +- .../panasonic_viera/translations/hu.json | 11 ++++ .../panasonic_viera/translations/pt-BR.json | 15 +++++ .../components/pi_hole/translations/fr.json | 14 ++++ .../components/pi_hole/translations/hu.json | 18 +++++ .../components/plex/translations/hu.json | 1 + .../components/plex/translations/pt-BR.json | 13 +++- .../components/point/translations/hu.json | 13 ++-- .../components/point/translations/ko.json | 4 +- .../powerwall/translations/pt-BR.json | 19 ++++++ .../components/ps4/translations/ko.json | 4 +- .../translations/pt-BR.json | 17 +++++ .../components/rachio/translations/hu.json | 11 ++++ .../components/roku/translations/hu.json | 11 ++++ .../components/roku/translations/pt-BR.json | 15 +++++ .../components/roomba/translations/hu.json | 12 ++++ .../components/roomba/translations/pt-BR.json | 30 +++++++++ .../components/samsungtv/translations/hu.json | 2 +- .../season/translations/sensor.pt-BR.json | 6 ++ .../components/sense/translations/hu.json | 12 ++++ .../components/sense/translations/pt-BR.json | 12 ++++ .../shopping_list/translations/hu.json | 14 ++++ .../shopping_list/translations/pt-BR.json | 14 ++++ .../simplisafe/translations/hu.json | 2 +- .../smartthings/translations/hu.json | 5 ++ .../smartthings/translations/pt-BR.json | 3 + .../components/solaredge/translations/hu.json | 1 + .../components/solarlog/translations/hu.json | 7 ++ .../components/soma/translations/hu.json | 2 +- .../components/songpal/translations/hu.json | 6 ++ .../components/songpal/translations/ko.json | 2 +- .../components/starline/translations/ko.json | 2 +- .../synology_dsm/translations/hu.json | 5 ++ .../synology_dsm/translations/ko.json | 2 +- .../synology_dsm/translations/pt-BR.json | 33 ++++++++++ .../components/tado/translations/hu.json | 12 ++++ .../components/tado/translations/pt-BR.json | 28 ++++++++ .../tellduslive/translations/ko.json | 2 +- .../components/tesla/translations/hu.json | 2 +- .../components/tesla/translations/pt-BR.json | 9 +++ .../components/tibber/translations/hu.json | 14 ++++ .../components/tibber/translations/ko.json | 2 +- .../components/toon/translations/ko.json | 2 +- .../totalconnect/translations/hu.json | 12 ++++ .../totalconnect/translations/pt-BR.json | 15 +++++ .../transmission/translations/hu.json | 2 +- .../components/tuya/translations/hu.json | 20 ++++++ .../components/tuya/translations/pt-BR.json | 5 ++ .../components/unifi/translations/hu.json | 4 +- .../components/unifi/translations/pt-BR.json | 11 +++- .../components/upnp/translations/pt-BR.json | 1 + .../components/vesync/translations/hu.json | 2 +- .../components/vesync/translations/pt-BR.json | 12 ++++ .../components/vilfo/translations/hu.json | 3 +- .../components/vilfo/translations/pt-BR.json | 17 +++++ .../components/vizio/translations/hu.json | 3 +- .../components/vizio/translations/ko.json | 4 +- .../components/vizio/translations/pt-BR.json | 18 +++++ .../withings/translations/pt-BR.json | 7 ++ .../components/wled/translations/hu.json | 2 +- .../components/wwlln/translations/pt-BR.json | 3 + .../xiaomi_miio/translations/fr.json | 3 + .../xiaomi_miio/translations/hu.json | 7 +- .../xiaomi_miio/translations/ko.json | 2 +- .../components/zerproc/translations/hu.json | 13 ++++ .../components/zha/translations/hu.json | 3 +- .../components/zha/translations/pt-BR.json | 10 +++ .../components/zwave/translations/hu.json | 2 +- 183 files changed, 1635 insertions(+), 95 deletions(-) create mode 100644 homeassistant/components/agent_dvr/translations/hu.json create mode 100644 homeassistant/components/agent_dvr/translations/pt-BR.json create mode 100644 homeassistant/components/august/translations/hu.json create mode 100644 homeassistant/components/august/translations/pt-BR.json create mode 100644 homeassistant/components/blink/translations/fr.json create mode 100644 homeassistant/components/blink/translations/hu.json create mode 100644 homeassistant/components/braviatv/translations/hu.json create mode 100644 homeassistant/components/braviatv/translations/pt-BR.json create mode 100644 homeassistant/components/bsblan/translations/hu.json create mode 100644 homeassistant/components/bsblan/translations/pt-BR.json create mode 100644 homeassistant/components/coronavirus/translations/pt-BR.json create mode 100644 homeassistant/components/devolo_home_control/translations/hu.json create mode 100644 homeassistant/components/directv/translations/hu.json create mode 100644 homeassistant/components/directv/translations/pt-BR.json create mode 100644 homeassistant/components/doorbird/translations/pt-BR.json create mode 100644 homeassistant/components/elkm1/translations/hu.json create mode 100644 homeassistant/components/flick_electric/translations/hu.json create mode 100644 homeassistant/components/flume/translations/hu.json create mode 100644 homeassistant/components/flume/translations/pt-BR.json create mode 100644 homeassistant/components/fritzbox/translations/pt-BR.json create mode 100644 homeassistant/components/garmin_connect/translations/pt-BR.json create mode 100644 homeassistant/components/gdacs/translations/pt-BR.json create mode 100644 homeassistant/components/gogogate2/translations/ca.json create mode 100644 homeassistant/components/gogogate2/translations/en.json create mode 100644 homeassistant/components/gogogate2/translations/hu.json create mode 100644 homeassistant/components/gogogate2/translations/pt-BR.json create mode 100644 homeassistant/components/gogogate2/translations/ru.json create mode 100644 homeassistant/components/griddy/translations/pt-BR.json create mode 100644 homeassistant/components/harmony/translations/hu.json create mode 100644 homeassistant/components/harmony/translations/pt-BR.json create mode 100644 homeassistant/components/home_connect/translations/pt-BR.json create mode 100644 homeassistant/components/ipp/translations/pt-BR.json create mode 100644 homeassistant/components/islamic_prayer_times/translations/pt-BR.json create mode 100644 homeassistant/components/isy994/translations/hu.json create mode 100644 homeassistant/components/konnected/translations/pt-BR.json create mode 100644 homeassistant/components/melcloud/translations/hu.json create mode 100644 homeassistant/components/meteo_france/translations/pt-BR.json create mode 100644 homeassistant/components/mikrotik/translations/pt-BR.json create mode 100644 homeassistant/components/mill/translations/hu.json create mode 100644 homeassistant/components/minecraft_server/translations/pt-BR.json create mode 100644 homeassistant/components/monoprice/translations/hu.json create mode 100644 homeassistant/components/monoprice/translations/pt-BR.json create mode 100644 homeassistant/components/myq/translations/hu.json create mode 100644 homeassistant/components/nuheat/translations/pt-BR.json create mode 100644 homeassistant/components/nut/translations/hu.json create mode 100644 homeassistant/components/nut/translations/pt-BR.json create mode 100644 homeassistant/components/nws/translations/hu.json create mode 100644 homeassistant/components/nws/translations/pt-BR.json create mode 100644 homeassistant/components/onvif/translations/hu.json create mode 100644 homeassistant/components/panasonic_viera/translations/hu.json create mode 100644 homeassistant/components/panasonic_viera/translations/pt-BR.json create mode 100644 homeassistant/components/pi_hole/translations/fr.json create mode 100644 homeassistant/components/pi_hole/translations/hu.json create mode 100644 homeassistant/components/powerwall/translations/pt-BR.json create mode 100644 homeassistant/components/pvpc_hourly_pricing/translations/pt-BR.json create mode 100644 homeassistant/components/rachio/translations/hu.json create mode 100644 homeassistant/components/roku/translations/pt-BR.json create mode 100644 homeassistant/components/roomba/translations/hu.json create mode 100644 homeassistant/components/roomba/translations/pt-BR.json create mode 100644 homeassistant/components/sense/translations/hu.json create mode 100644 homeassistant/components/sense/translations/pt-BR.json create mode 100644 homeassistant/components/shopping_list/translations/hu.json create mode 100644 homeassistant/components/shopping_list/translations/pt-BR.json create mode 100644 homeassistant/components/synology_dsm/translations/pt-BR.json create mode 100644 homeassistant/components/tado/translations/hu.json create mode 100644 homeassistant/components/tado/translations/pt-BR.json create mode 100644 homeassistant/components/tibber/translations/hu.json create mode 100644 homeassistant/components/totalconnect/translations/hu.json create mode 100644 homeassistant/components/totalconnect/translations/pt-BR.json create mode 100644 homeassistant/components/tuya/translations/hu.json create mode 100644 homeassistant/components/vesync/translations/pt-BR.json create mode 100644 homeassistant/components/vilfo/translations/pt-BR.json create mode 100644 homeassistant/components/withings/translations/pt-BR.json create mode 100644 homeassistant/components/zerproc/translations/hu.json diff --git a/homeassistant/components/abode/translations/hu.json b/homeassistant/components/abode/translations/hu.json index 89b695da7d9..90ca7cafb34 100644 --- a/homeassistant/components/abode/translations/hu.json +++ b/homeassistant/components/abode/translations/hu.json @@ -12,7 +12,7 @@ "user": { "data": { "password": "Jelsz\u00f3", - "username": "Email c\u00edm" + "username": "E-mail" }, "title": "T\u00f6ltse ki az Abode bejelentkez\u00e9si adatait" } diff --git a/homeassistant/components/adguard/translations/hu.json b/homeassistant/components/adguard/translations/hu.json index 34b601027c2..1ca56c7684f 100644 --- a/homeassistant/components/adguard/translations/hu.json +++ b/homeassistant/components/adguard/translations/hu.json @@ -3,6 +3,7 @@ "step": { "user": { "data": { + "host": "Hoszt", "password": "Jelsz\u00f3", "port": "Port", "username": "Felhaszn\u00e1l\u00f3n\u00e9v" diff --git a/homeassistant/components/agent_dvr/translations/hu.json b/homeassistant/components/agent_dvr/translations/hu.json new file mode 100644 index 00000000000..45918735010 --- /dev/null +++ b/homeassistant/components/agent_dvr/translations/hu.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "host": "Hoszt", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/agent_dvr/translations/pt-BR.json b/homeassistant/components/agent_dvr/translations/pt-BR.json new file mode 100644 index 00000000000..df74434ffc7 --- /dev/null +++ b/homeassistant/components/agent_dvr/translations/pt-BR.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "device_unavailable": "O dispositivo n\u00e3o est\u00e1 dispon\u00edvel" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/airly/translations/hu.json b/homeassistant/components/airly/translations/hu.json index f91b2de241f..b6b6790c1e0 100644 --- a/homeassistant/components/airly/translations/hu.json +++ b/homeassistant/components/airly/translations/hu.json @@ -10,7 +10,7 @@ "step": { "user": { "data": { - "api_key": "Airly API kulcs", + "api_key": "API kulcs", "latitude": "Sz\u00e9less\u00e9g", "longitude": "Hossz\u00fas\u00e1g", "name": "Az integr\u00e1ci\u00f3 neve" diff --git a/homeassistant/components/airvisual/translations/hu.json b/homeassistant/components/airvisual/translations/hu.json index c8a33625953..655060337e1 100644 --- a/homeassistant/components/airvisual/translations/hu.json +++ b/homeassistant/components/airvisual/translations/hu.json @@ -6,10 +6,15 @@ "step": { "geography": { "data": { - "api_key": "API Kulcs", + "api_key": "API kulcs", "latitude": "Sz\u00e9less\u00e9g", "longitude": "Hossz\u00fas\u00e1g" } + }, + "node_pro": { + "data": { + "password": "Jelsz\u00f3" + } } } } diff --git a/homeassistant/components/airvisual/translations/pt-BR.json b/homeassistant/components/airvisual/translations/pt-BR.json index 1b5834254a8..035782cb320 100644 --- a/homeassistant/components/airvisual/translations/pt-BR.json +++ b/homeassistant/components/airvisual/translations/pt-BR.json @@ -1,7 +1,21 @@ { "config": { "error": { + "general_error": "Ocorreu um erro desconhecido.", "invalid_api_key": "Chave de API fornecida \u00e9 inv\u00e1lida." + }, + "step": { + "geography": { + "data": { + "latitude": "Latitude", + "longitude": "Longitude" + } + }, + "user": { + "data": { + "type": "Tipo de Integra\u00e7\u00e3o" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/alarm_control_panel/translations/pt-BR.json b/homeassistant/components/alarm_control_panel/translations/pt-BR.json index a056e1f4187..07e005cba03 100644 --- a/homeassistant/components/alarm_control_panel/translations/pt-BR.json +++ b/homeassistant/components/alarm_control_panel/translations/pt-BR.json @@ -7,6 +7,13 @@ "disarm": "Desarmar {entity_name}", "trigger": "Disparar {entidade_nome}" }, + "condition_type": { + "is_armed_away": "{entity_name} est\u00e1 armado modo longe", + "is_armed_home": "{entity_name} est\u00e1 armadado modo casa", + "is_armed_night": "{entity_name} est\u00e1 armadado modo noite", + "is_disarmed": "{entity_name} est\u00e1 desarmado", + "is_triggered": "{entity_name} est\u00e1 acionado" + }, "trigger_type": { "armed_away": "{entity_name} armado modo longe", "armed_home": "{entity_name} armadado modo casa", diff --git a/homeassistant/components/ambiclimate/translations/ko.json b/homeassistant/components/ambiclimate/translations/ko.json index 2717d4c4b79..fd55f75bf28 100644 --- a/homeassistant/components/ambiclimate/translations/ko.json +++ b/homeassistant/components/ambiclimate/translations/ko.json @@ -9,12 +9,12 @@ "default": "Ambi Climate \ub85c \uc131\uacf5\uc801\uc73c\ub85c \uc778\uc99d\ub418\uc5c8\uc2b5\ub2c8\ub2e4." }, "error": { - "follow_link": "Submit \ubc84\ud2bc\uc744 \ub204\ub974\uae30 \uc804\uc5d0 \ub9c1\ud06c\ub97c \ub530\ub77c \uc778\uc99d\uc744 \ubc1b\uc544\uc8fc\uc138\uc694", + "follow_link": "\ud655\uc778\uc744 \ud074\ub9ad\ud558\uae30 \uc804\uc5d0 \ub9c1\ud06c\ub97c \ub530\ub77c \uc778\uc99d\uc744 \ubc1b\uc544\uc8fc\uc138\uc694", "no_token": "Ambi Climate \ub85c \uc778\uc99d\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4" }, "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)", + "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 **\ud655\uc778**\uc744 \ud074\ub9ad\ud574\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\ud558\uae30" } } diff --git a/homeassistant/components/atag/translations/hu.json b/homeassistant/components/atag/translations/hu.json index 22687b6944a..45918735010 100644 --- a/homeassistant/components/atag/translations/hu.json +++ b/homeassistant/components/atag/translations/hu.json @@ -3,7 +3,8 @@ "step": { "user": { "data": { - "port": "Port (10000)" + "host": "Hoszt", + "port": "Port" } } } diff --git a/homeassistant/components/atag/translations/pt-BR.json b/homeassistant/components/atag/translations/pt-BR.json index 2aec29e8eb0..eda0a2cc041 100644 --- a/homeassistant/components/atag/translations/pt-BR.json +++ b/homeassistant/components/atag/translations/pt-BR.json @@ -1,5 +1,8 @@ { "config": { + "error": { + "connection_error": "Falha ao conectar, tente novamente" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/august/translations/hu.json b/homeassistant/components/august/translations/hu.json new file mode 100644 index 00000000000..dee4ed9ee0f --- /dev/null +++ b/homeassistant/components/august/translations/hu.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/august/translations/ko.json b/homeassistant/components/august/translations/ko.json index dce916bb788..52f939c45a0 100644 --- a/homeassistant/components/august/translations/ko.json +++ b/homeassistant/components/august/translations/ko.json @@ -16,7 +16,7 @@ "timeout": "\uc81c\ud55c \uc2dc\uac04 (\ucd08)", "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.", + "description": "\ub85c\uadf8\uc778 \ubc29\ubc95\uc774 '\uc774\uba54\uc77c'\uc778 \uacbd\uc6b0, \uc0ac\uc6a9\uc790 \uc774\ub984\uc740 \uc774\uba54\uc77c \uc8fc\uc18c\uc785\ub2c8\ub2e4. \ub85c\uadf8\uc778 \ubc29\ubc95\uc774 '\uc804\ud654\ubc88\ud638'\uc778 \uacbd\uc6b0, \uc0ac\uc6a9\uc790 \uc774\ub984\uc740 '+NNNNNNNNN' \ud615\uc2dd\uc758 \uc804\ud654\ubc88\ud638\uc785\ub2c8\ub2e4.", "title": "August \uacc4\uc815 \uc124\uc815\ud558\uae30" }, "validation": { diff --git a/homeassistant/components/august/translations/pt-BR.json b/homeassistant/components/august/translations/pt-BR.json new file mode 100644 index 00000000000..efb4b3db35f --- /dev/null +++ b/homeassistant/components/august/translations/pt-BR.json @@ -0,0 +1,20 @@ +{ + "config": { + "step": { + "user": { + "data": { + "timeout": "Tempo limite (segundos)" + }, + "description": "Se o m\u00e9todo de login for 'email', Nome de usu\u00e1rio \u00e9 o endere\u00e7o de email. Se o m\u00e9todo de login for 'telefone', Nome de usu\u00e1rio ser\u00e1 o n\u00famero de telefone no formato '+NNNNNNNNN'.", + "title": "Configurar uma conta de August" + }, + "validation": { + "data": { + "code": "C\u00f3digo de verifica\u00e7\u00e3o" + }, + "description": "Por favor, verifique o seu {login_method} ({username}) e digite o c\u00f3digo de verifica\u00e7\u00e3o abaixo", + "title": "Autentica\u00e7\u00e3o de dois fatores" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/blebox/translations/hu.json b/homeassistant/components/blebox/translations/hu.json index a4c3e1bd2d3..9b0bf1c0ddf 100644 --- a/homeassistant/components/blebox/translations/hu.json +++ b/homeassistant/components/blebox/translations/hu.json @@ -7,6 +7,7 @@ "step": { "user": { "data": { + "host": "IP c\u00edm", "port": "Port" } } diff --git a/homeassistant/components/blink/translations/fr.json b/homeassistant/components/blink/translations/fr.json new file mode 100644 index 00000000000..0e46e20a75e --- /dev/null +++ b/homeassistant/components/blink/translations/fr.json @@ -0,0 +1,9 @@ +{ + "config": { + "step": { + "2fa": { + "title": "Authentification \u00e0 deux facteurs" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/blink/translations/hu.json b/homeassistant/components/blink/translations/hu.json new file mode 100644 index 00000000000..1150cda9ea9 --- /dev/null +++ b/homeassistant/components/blink/translations/hu.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba" + }, + "step": { + "user": { + "data": { + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/braviatv/translations/hu.json b/homeassistant/components/braviatv/translations/hu.json new file mode 100644 index 00000000000..cbf055e2fba --- /dev/null +++ b/homeassistant/components/braviatv/translations/hu.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "host": "Hoszt" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/braviatv/translations/pt-BR.json b/homeassistant/components/braviatv/translations/pt-BR.json new file mode 100644 index 00000000000..1a0fedff9d0 --- /dev/null +++ b/homeassistant/components/braviatv/translations/pt-BR.json @@ -0,0 +1,20 @@ +{ + "config": { + "step": { + "user": { + "description": "Configure a integra\u00e7\u00e3o do Sony Bravia TV. Se voc\u00ea tiver problemas com a configura\u00e7\u00e3o, acesse: https://www.home-assistant.io/integrations/braviatv \n\n Verifique se a sua TV est\u00e1 ligada.", + "title": "Sony Bravia TV" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "ignored_sources": "Lista de fontes ignoradas" + }, + "title": "Op\u00e7\u00f5es para a Sony Bravia TV" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/brother/translations/hu.json b/homeassistant/components/brother/translations/hu.json index 77bdb4b6bf1..d952410b609 100644 --- a/homeassistant/components/brother/translations/hu.json +++ b/homeassistant/components/brother/translations/hu.json @@ -13,7 +13,7 @@ "step": { "user": { "data": { - "host": "Nyomtat\u00f3 \u00e1llom\u00e1sneve vagy IP-c\u00edme", + "host": "Hoszt", "type": "A nyomtat\u00f3 t\u00edpusa" }, "description": "A Brother nyomtat\u00f3 integr\u00e1ci\u00f3j\u00e1nak be\u00e1ll\u00edt\u00e1sa. Ha probl\u00e9m\u00e1id vannak a konfigur\u00e1ci\u00f3val, l\u00e1togass el a k\u00f6vetkez\u0151 oldalra: https://www.home-assistant.io/integrations/brother", diff --git a/homeassistant/components/bsblan/translations/hu.json b/homeassistant/components/bsblan/translations/hu.json new file mode 100644 index 00000000000..45918735010 --- /dev/null +++ b/homeassistant/components/bsblan/translations/hu.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "host": "Hoszt", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/bsblan/translations/pt-BR.json b/homeassistant/components/bsblan/translations/pt-BR.json new file mode 100644 index 00000000000..2b5e083220d --- /dev/null +++ b/homeassistant/components/bsblan/translations/pt-BR.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "description": "Configure o seu dispositivo BSB-Lan para integrar com o Home Assistant.", + "title": "Conecte-se ao dispositivo BSB-Lan" + } + } + }, + "title": "BSB-Lan" +} \ No newline at end of file diff --git a/homeassistant/components/cert_expiry/translations/hu.json b/homeassistant/components/cert_expiry/translations/hu.json index 584f4c2b759..22e9312e778 100644 --- a/homeassistant/components/cert_expiry/translations/hu.json +++ b/homeassistant/components/cert_expiry/translations/hu.json @@ -3,8 +3,9 @@ "step": { "user": { "data": { + "host": "Hoszt", "name": "A tan\u00fas\u00edtv\u00e1ny neve", - "port": "A tan\u00fas\u00edtv\u00e1ny portja" + "port": "Port" } } } diff --git a/homeassistant/components/climate/translations/ko.json b/homeassistant/components/climate/translations/ko.json index c707c9c1eba..0923d166040 100644 --- a/homeassistant/components/climate/translations/ko.json +++ b/homeassistant/components/climate/translations/ko.json @@ -2,11 +2,11 @@ "device_automation": { "action_type": { "set_hvac_mode": "{entity_name} \uc758 HVAC \ubaa8\ub4dc \ubcc0\uacbd", - "set_preset_mode": "{entity_name} \uc758 \uc0ac\uc804 \uc124\uc815 \ubcc0\uacbd" + "set_preset_mode": "{entity_name} \uc758 \ud504\ub9ac\uc14b \ubcc0\uacbd" }, "condition_type": { "is_hvac_mode": "{entity_name} \uc774(\uac00) \ud2b9\uc815 HVAC \ubaa8\ub4dc\ub85c \uc124\uc815\ub418\uc5b4\uc788\uc73c\uba74", - "is_preset_mode": "{entity_name} \uc774(\uac00) \ud2b9\uc815 \uc0ac\uc804 \uc124\uc815 \ubaa8\ub4dc\ub85c \uc124\uc815\ub418\uc5b4\uc788\uc73c\uba74" + "is_preset_mode": "{entity_name} \uc774(\uac00) \ud2b9\uc815 \ud504\ub9ac\uc14b \ubaa8\ub4dc\ub85c \uc124\uc815\ub418\uc5b4\uc788\uc73c\uba74" }, "trigger_type": { "current_humidity_changed": "{entity_name} \uc774(\uac00) \uc2b5\ub3c4 \ubcc0\ud654\ub97c \uac10\uc9c0\ud560 \ub54c", diff --git a/homeassistant/components/coronavirus/translations/pt-BR.json b/homeassistant/components/coronavirus/translations/pt-BR.json new file mode 100644 index 00000000000..ab4a4904857 --- /dev/null +++ b/homeassistant/components/coronavirus/translations/pt-BR.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "already_configured": "Este pa\u00eds j\u00e1 est\u00e1 configurado." + }, + "step": { + "user": { + "data": { + "country": "Pa\u00eds" + }, + "title": "Escolha um pa\u00eds para monitorar" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/daikin/translations/hu.json b/homeassistant/components/daikin/translations/hu.json index eef3afdc5ee..c71d4f08fc8 100644 --- a/homeassistant/components/daikin/translations/hu.json +++ b/homeassistant/components/daikin/translations/hu.json @@ -1,14 +1,21 @@ { "config": { "abort": { - "already_configured": "Az eszk\u00f6zt m\u00e1r konfigur\u00e1ltuk", + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", "device_fail": "Az eszk\u00f6z l\u00e9trehoz\u00e1sakor v\u00e1ratlan hiba l\u00e9pett fel.", "device_timeout": "Id\u0151t\u00fall\u00e9p\u00e9s a k\u00e9sz\u00fcl\u00e9k csatlakoz\u00e1sakor." }, + "error": { + "device_fail": "V\u00e1ratlan hiba", + "device_timeout": "Sikertelen csatlakoz\u00e1s", + "forbidden": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s" + }, "step": { "user": { "data": { - "host": "Hoszt" + "host": "Hoszt", + "key": "API kulcs", + "password": "Jelsz\u00f3" }, "description": "Add meg a Daikin l\u00e9gkond\u00edcion\u00e1l\u00f3 IP-c\u00edm\u00e9t.", "title": "A Daikin l\u00e9gkond\u00edcion\u00e1l\u00f3 konfigur\u00e1l\u00e1sa" diff --git a/homeassistant/components/deconz/translations/hu.json b/homeassistant/components/deconz/translations/hu.json index 216d7ddf1a0..a6a80705978 100644 --- a/homeassistant/components/deconz/translations/hu.json +++ b/homeassistant/components/deconz/translations/hu.json @@ -28,6 +28,12 @@ "host": "Hoszt", "port": "Port" } + }, + "manual_input": { + "data": { + "host": "Hoszt", + "port": "Port" + } } } }, diff --git a/homeassistant/components/deconz/translations/ko.json b/homeassistant/components/deconz/translations/ko.json index ba7d0b271b9..92e39523e34 100644 --- a/homeassistant/components/deconz/translations/ko.json +++ b/homeassistant/components/deconz/translations/ko.json @@ -82,7 +82,7 @@ "remote_button_short_release": "\"{subtype}\" \ubc84\ud2bc\uc5d0\uc11c \uc190\uc744 \ub5c4 \ub54c", "remote_button_triple_press": "\"{subtype}\" \ubc84\ud2bc\uc774 \uc138 \ubc88 \ub20c\ub9b4 \ub54c", "remote_double_tap": "\uae30\uae30\uc758 \"{subtype}\" \uac00 \ub354\ube14 \ud0ed \ub420 \ub54c", - "remote_double_tap_any_side": "\uae30\uae30\uc758 \uc544\ubb34 \uba74\uc774 \ub354\ube14 \ud0ed \ub420 \ub54c", + "remote_double_tap_any_side": "\uae30\uae30\uc758 \uc544\ubb34 \uba74\uc774\ub098 \ub354\ube14 \ud0ed \ub420 \ub54c", "remote_falling": "\uae30\uae30\uac00 \ub5a8\uc5b4\uc9c8 \ub54c", "remote_flip_180_degrees": "\uae30\uae30\uac00 180\ub3c4\ub85c \ub4a4\uc9d1\uc5b4\uc9c8 \ub54c", "remote_flip_90_degrees": "\uae30\uae30\uac00 90\ub3c4\ub85c \ub4a4\uc9d1\uc5b4\uc9c8 \ub54c", diff --git a/homeassistant/components/deconz/translations/pt-BR.json b/homeassistant/components/deconz/translations/pt-BR.json index f548b187103..c43ecdb0303 100644 --- a/homeassistant/components/deconz/translations/pt-BR.json +++ b/homeassistant/components/deconz/translations/pt-BR.json @@ -30,5 +30,11 @@ } } } + }, + "device_automation": { + "trigger_subtype": { + "bottom_buttons": "Bot\u00f5es inferiores", + "top_buttons": "Bot\u00f5es superiores" + } } } \ No newline at end of file diff --git a/homeassistant/components/demo/translations/hu.json b/homeassistant/components/demo/translations/hu.json index 996b9c138ef..0f8f1673d43 100644 --- a/homeassistant/components/demo/translations/hu.json +++ b/homeassistant/components/demo/translations/hu.json @@ -1,3 +1,20 @@ { + "options": { + "step": { + "options_1": { + "data": { + "bool": "Opcion\u00e1lis logikai \u00e9rt\u00e9k", + "int": "Numerikus bemenet" + } + }, + "options_2": { + "data": { + "multi": "T\u00f6bbsz\u00f6r\u00f6s kijel\u00f6l\u00e9s", + "select": "V\u00e1lassz egy lehet\u0151s\u00e9get", + "string": "Karakterl\u00e1nc \u00e9rt\u00e9k" + } + } + } + }, "title": "Dem\u00f3" } \ No newline at end of file diff --git a/homeassistant/components/demo/translations/pt-BR.json b/homeassistant/components/demo/translations/pt-BR.json index 9a07b5ebc50..8364f0bc94b 100644 --- a/homeassistant/components/demo/translations/pt-BR.json +++ b/homeassistant/components/demo/translations/pt-BR.json @@ -1,3 +1,20 @@ { + "options": { + "step": { + "options_1": { + "data": { + "bool": "Booleano opcional", + "int": "Entrada num\u00e9rica" + } + }, + "options_2": { + "data": { + "multi": "Sele\u00e7\u00e3o m\u00faltipla", + "select": "Selecione uma op\u00e7\u00e3o", + "string": "Valor do texto" + } + } + } + }, "title": "Demonstra\u00e7\u00e3o" } \ No newline at end of file diff --git a/homeassistant/components/devolo_home_control/translations/hu.json b/homeassistant/components/devolo_home_control/translations/hu.json new file mode 100644 index 00000000000..ff2c2fc87b5 --- /dev/null +++ b/homeassistant/components/devolo_home_control/translations/hu.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "Jelsz\u00f3" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/directv/translations/hu.json b/homeassistant/components/directv/translations/hu.json new file mode 100644 index 00000000000..5d8fc929b92 --- /dev/null +++ b/homeassistant/components/directv/translations/hu.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s" + }, + "step": { + "user": { + "data": { + "host": "Hoszt" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/directv/translations/pt-BR.json b/homeassistant/components/directv/translations/pt-BR.json new file mode 100644 index 00000000000..277606b855b --- /dev/null +++ b/homeassistant/components/directv/translations/pt-BR.json @@ -0,0 +1,10 @@ +{ + "config": { + "flow_title": "DirecTV: {name}", + "step": { + "ssdp_confirm": { + "description": "Voc\u00ea quer configurar o {name}?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/doorbird/translations/hu.json b/homeassistant/components/doorbird/translations/hu.json index dee4ed9ee0f..618368433ac 100644 --- a/homeassistant/components/doorbird/translations/hu.json +++ b/homeassistant/components/doorbird/translations/hu.json @@ -1,8 +1,14 @@ { "config": { + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba" + }, "step": { "user": { "data": { + "host": "Hoszt", "password": "Jelsz\u00f3", "username": "Felhaszn\u00e1l\u00f3n\u00e9v" } diff --git a/homeassistant/components/doorbird/translations/ko.json b/homeassistant/components/doorbird/translations/ko.json index 852d325403f..74057a94d26 100644 --- a/homeassistant/components/doorbird/translations/ko.json +++ b/homeassistant/components/doorbird/translations/ko.json @@ -10,7 +10,7 @@ "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, - "flow_title": "DoorBird {name} ({host})", + "flow_title": "DoorBird: {name} ({host})", "step": { "user": { "data": { diff --git a/homeassistant/components/doorbird/translations/pt-BR.json b/homeassistant/components/doorbird/translations/pt-BR.json new file mode 100644 index 00000000000..828f6a24e84 --- /dev/null +++ b/homeassistant/components/doorbird/translations/pt-BR.json @@ -0,0 +1,9 @@ +{ + "config": { + "abort": { + "already_configured": "Este DoorBird j\u00e1 est\u00e1 configurado", + "link_local_address": "Link de endere\u00e7os locais n\u00e3o s\u00e3o suportados", + "not_doorbird_device": "Este dispositivo n\u00e3o \u00e9 um DoorBird" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ecobee/translations/ko.json b/homeassistant/components/ecobee/translations/ko.json index 1973c3ab8a3..9666f572d8b 100644 --- a/homeassistant/components/ecobee/translations/ko.json +++ b/homeassistant/components/ecobee/translations/ko.json @@ -9,7 +9,7 @@ }, "step": { "authorize": { - "description": "https://www.ecobee.com/consumerportal/index.html \uc5d0\uc11c PIN \ucf54\ub4dc\ub97c \uc0ac\uc6a9\ud558\uc5ec \uc774 \uc571\uc744 \uc2b9\uc778\ud574\uc8fc\uc138\uc694:\n\n {pin} \n \n \uadf8\ub7f0 \ub2e4\uc74c Submit \uc744 \ub20c\ub7ec\uc8fc\uc138\uc694.", + "description": "https://www.ecobee.com/consumerportal/index.html \uc5d0\uc11c PIN \ucf54\ub4dc\ub97c \uc0ac\uc6a9\ud558\uc5ec \uc774 \uc571\uc744 \uc2b9\uc778\ud574\uc8fc\uc138\uc694:\n\n {pin} \n \n \uadf8\ub7f0 \ub2e4\uc74c \ud655\uc778\uc744 \ud074\ub9ad\ud574\uc8fc\uc138\uc694.", "title": "ecobee.com \uc5d0\uc11c \uc571 \uc2b9\uc778\ud558\uae30" }, "user": { diff --git a/homeassistant/components/elgato/translations/hu.json b/homeassistant/components/elgato/translations/hu.json index d3618d0039d..54461a8c553 100644 --- a/homeassistant/components/elgato/translations/hu.json +++ b/homeassistant/components/elgato/translations/hu.json @@ -7,8 +7,8 @@ "step": { "user": { "data": { - "host": "Hosztn\u00e9v vagy IP c\u00edm", - "port": "Portsz\u00e1m" + "host": "Hoszt", + "port": "Port" } } } diff --git a/homeassistant/components/elkm1/translations/hu.json b/homeassistant/components/elkm1/translations/hu.json new file mode 100644 index 00000000000..dee4ed9ee0f --- /dev/null +++ b/homeassistant/components/elkm1/translations/hu.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/elkm1/translations/ko.json b/homeassistant/components/elkm1/translations/ko.json index 0b88308547a..d074946b7ad 100644 --- a/homeassistant/components/elkm1/translations/ko.json +++ b/homeassistant/components/elkm1/translations/ko.json @@ -12,14 +12,14 @@ "step": { "user": { "data": { - "address": "\uc2dc\ub9ac\uc5bc\uc744 \ud1b5\ud574 \uc5f0\uacb0\ud558\ub294 \uacbd\uc6b0\uc758 IP \uc8fc\uc18c \ub098 \ub3c4\uba54\uc778 \ub610\ub294 \uc2dc\ub9ac\uc5bc \ud3ec\ud2b8", + "address": "\uc2dc\ub9ac\uc5bc\uc744 \ud1b5\ud574 \uc5f0\uacb0\ud558\ub294 \uacbd\uc6b0\uc758 IP \uc8fc\uc18c\ub098 \ub3c4\uba54\uc778 \ub610\ub294 \uc2dc\ub9ac\uc5bc \ud3ec\ud2b8.", "password": "\ube44\ubc00\ubc88\ud638", "prefix": "\uace0\uc720\ud55c \uc811\ub450\uc0ac (ElkM1 \uc774 \ud558\ub098\ub9cc \uc788\uc73c\uba74 \ube44\uc6cc\ub450\uc138\uc694).", "protocol": "\ud504\ub85c\ud1a0\ucf5c", - "temperature_unit": "ElkM1 \uc774 \uc0ac\uc6a9\ud558\ub294 \uc628\ub3c4 \ub2e8\uc704", + "temperature_unit": "ElkM1 \uc774 \uc0ac\uc6a9\ud558\ub294 \uc628\ub3c4 \ub2e8\uc704.", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" }, - "description": "\uc8fc\uc18c \ubb38\uc790\uc5f4\uc740 '\ubcf4\uc548' \ubc0f '\ube44\ubcf4\uc548' \uc758 \uacbd\uc6b0 'address[:port]' \ud615\uc2dd\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. \uc608: '192.168.1.1'. \ud3ec\ud2b8\ub294 \uc120\ud0dd \uc0ac\ud56d\uc774\uba70 \uae30\ubcf8\uac12\uc740 '\ube44\ubcf4\uc548' \uc758 \uacbd\uc6b0 2101 \uc774\uace0 '\ubcf4\uc548' \uc758 \uacbd\uc6b0 2601 \uc785\ub2c8\ub2e4. \uc2dc\ub9ac\uc5bc \ud504\ub85c\ud1a0\ucf5c\uc758 \uacbd\uc6b0 \uc8fc\uc18c\ub294 'tty[:baud]' \ud615\uc2dd\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. \uc608: '/dev/ttyS1'. \ud1b5\uc2e0\uc18d\ub3c4 \ubc14\uc6b0\ub4dc\ub294 \uc120\ud0dd \uc0ac\ud56d\uc774\uba70 \uae30\ubcf8\uac12\uc740 115200 \uc785\ub2c8\ub2e4.", + "description": "\uc8fc\uc18c \ubb38\uc790\uc5f4\uc740 '\ubcf4\uc548' \ubc0f '\ube44\ubcf4\uc548'\uc5d0 \ub300\ud574 'address[:port]' \ud615\uc2dd\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. \uc608: '192.168.1.1'. \ud3ec\ud2b8\ub294 \uc120\ud0dd \uc0ac\ud56d\uc774\uba70 \uae30\ubcf8\uac12\uc740 '\ube44\ubcf4\uc548' \uc758 \uacbd\uc6b0 2101 \uc774\uace0 '\ubcf4\uc548' \uc758 \uacbd\uc6b0 2601 \uc785\ub2c8\ub2e4. \uc2dc\ub9ac\uc5bc \ud504\ub85c\ud1a0\ucf5c\uc758 \uacbd\uc6b0 \uc8fc\uc18c\ub294 'tty[:baud]' \ud615\uc2dd\uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4. \uc608: '/dev/ttyS1'. \ud1b5\uc2e0\uc18d\ub3c4 \ubc14\uc6b0\ub4dc\ub294 \uc120\ud0dd \uc0ac\ud56d\uc774\uba70 \uae30\ubcf8\uac12\uc740 115200 \uc785\ub2c8\ub2e4.", "title": "Elk-M1 \uc81c\uc5b4\uc5d0 \uc5f0\uacb0\ud558\uae30" } } diff --git a/homeassistant/components/flick_electric/translations/hu.json b/homeassistant/components/flick_electric/translations/hu.json new file mode 100644 index 00000000000..dee4ed9ee0f --- /dev/null +++ b/homeassistant/components/flick_electric/translations/hu.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/flick_electric/translations/ko.json b/homeassistant/components/flick_electric/translations/ko.json index 65f02204897..9becb915c3a 100644 --- a/homeassistant/components/flick_electric/translations/ko.json +++ b/homeassistant/components/flick_electric/translations/ko.json @@ -12,7 +12,7 @@ "user": { "data": { "client_id": "\ud074\ub77c\uc774\uc5b8\ud2b8 ID (\uc120\ud0dd \uc0ac\ud56d)", - "client_secret": "\ud074\ub77c\uc774\uc5b8\ud2b8 \ube44\ubc00\ud0a4 (\uc120\ud0dd \uc0ac\ud56d)", + "client_secret": "\ud074\ub77c\uc774\uc5b8\ud2b8 \uc2dc\ud06c\ub9bf (\uc120\ud0dd \uc0ac\ud56d)", "password": "\ube44\ubc00\ubc88\ud638", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" }, diff --git a/homeassistant/components/flick_electric/translations/pt-BR.json b/homeassistant/components/flick_electric/translations/pt-BR.json index df76dc5bb25..2f05df0cb4d 100644 --- a/homeassistant/components/flick_electric/translations/pt-BR.json +++ b/homeassistant/components/flick_electric/translations/pt-BR.json @@ -7,6 +7,16 @@ "cannot_connect": "Falha ao conectar, tente novamente", "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "client_id": "ID do cliente (Opcional)", + "client_secret": "Segredo do cliente (Opcional)" + }, + "title": "Credenciais de login do Flick" + } } - } + }, + "title": "Flick Electric" } \ No newline at end of file diff --git a/homeassistant/components/flume/translations/hu.json b/homeassistant/components/flume/translations/hu.json new file mode 100644 index 00000000000..dee4ed9ee0f --- /dev/null +++ b/homeassistant/components/flume/translations/hu.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/flume/translations/pt-BR.json b/homeassistant/components/flume/translations/pt-BR.json new file mode 100644 index 00000000000..033dc26c856 --- /dev/null +++ b/homeassistant/components/flume/translations/pt-BR.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Essa conta j\u00e1 est\u00e1 configurada" + }, + "error": { + "cannot_connect": "Falha ao conectar, tente novamente", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "client_id": "ID do Cliente", + "client_secret": "Segredo do cliente" + }, + "description": "Para acessar a API pessoal do Flume, voc\u00ea precisar\u00e1 solicitar um 'ID do Cliente' e 'Segredo do Cliente' em https://portal.flumetech.com/settings#token", + "title": "Conecte-se \u00e0 sua conta Flume" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/forked_daapd/translations/ca.json b/homeassistant/components/forked_daapd/translations/ca.json index 2853da35bad..cf58af459f6 100644 --- a/homeassistant/components/forked_daapd/translations/ca.json +++ b/homeassistant/components/forked_daapd/translations/ca.json @@ -16,6 +16,7 @@ "user": { "data": { "host": "Amfitri\u00f3", + "name": "Sobrenom", "password": "Contrasenya de l'API (deixa-ho en blanc si no t\u00e9 contrasenya)", "port": "Port de l'API" }, diff --git a/homeassistant/components/forked_daapd/translations/pt-BR.json b/homeassistant/components/forked_daapd/translations/pt-BR.json index 07645764606..c45178d6a72 100644 --- a/homeassistant/components/forked_daapd/translations/pt-BR.json +++ b/homeassistant/components/forked_daapd/translations/pt-BR.json @@ -6,9 +6,12 @@ }, "error": { "unknown_error": "Erro desconhecido.", + "websocket_not_enabled": "websocket do servidor forked-daapd n\u00e3o ativado.", "wrong_host_or_port": "N\u00e3o foi poss\u00edvel conectar. Por favor, verifique o endere\u00e7o e a porta.", - "wrong_password": "Senha incorreta." + "wrong_password": "Senha incorreta.", + "wrong_server_type": "A integra\u00e7\u00e3o forked-daapd requer um servidor forked-daapd com vers\u00e3o >= 27.0." }, + "flow_title": "servidor forked-daapd: {name} ({host})", "step": { "user": { "data": { @@ -16,7 +19,8 @@ "name": "Nome amig\u00e1vel", "password": "Senha da API (deixe em branco se n\u00e3o houver senha)", "port": "Porta API" - } + }, + "title": "Configurar dispositivo forked-daapd" } } }, diff --git a/homeassistant/components/freebox/translations/hu.json b/homeassistant/components/freebox/translations/hu.json index 2b5265bc671..937f441845c 100644 --- a/homeassistant/components/freebox/translations/hu.json +++ b/homeassistant/components/freebox/translations/hu.json @@ -2,6 +2,10 @@ "config": { "step": { "user": { + "data": { + "host": "Hoszt", + "port": "Port" + }, "title": "Freebox" } } diff --git a/homeassistant/components/freebox/translations/ko.json b/homeassistant/components/freebox/translations/ko.json index eca391cbd9f..ce0bc09989e 100644 --- a/homeassistant/components/freebox/translations/ko.json +++ b/homeassistant/components/freebox/translations/ko.json @@ -10,7 +10,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)", + "description": "\ud655\uc778\uc744 \ud074\ub9ad\ud55c \ub2e4\uc74c \ub77c\uc6b0\ud130\uc758 \uc624\ub978\ucabd \ud654\uc0b4\ud45c\ub97c \ud130\uce58\ud558\uc5ec Home Assistant \uc5d0 Freebox \ub97c \ub4f1\ub85d\ud574\uc8fc\uc138\uc694.\n\n![\ub77c\uc6b0\ud130\uc758 \ubc84\ud2bc \uc704\uce58](/static/images/config_freebox.png)", "title": "Freebox \ub77c\uc6b0\ud130 \uc5f0\uacb0\ud558\uae30" }, "user": { diff --git a/homeassistant/components/fritzbox/translations/hu.json b/homeassistant/components/fritzbox/translations/hu.json index 09397b70a7d..6a08b68d863 100644 --- a/homeassistant/components/fritzbox/translations/hu.json +++ b/homeassistant/components/fritzbox/translations/hu.json @@ -9,6 +9,7 @@ }, "user": { "data": { + "host": "Hoszt", "password": "Jelsz\u00f3", "username": "Felhaszn\u00e1l\u00f3n\u00e9v" } diff --git a/homeassistant/components/fritzbox/translations/pt-BR.json b/homeassistant/components/fritzbox/translations/pt-BR.json new file mode 100644 index 00000000000..6fd7f35d8c5 --- /dev/null +++ b/homeassistant/components/fritzbox/translations/pt-BR.json @@ -0,0 +1,12 @@ +{ + "config": { + "error": { + "auth_failed": "Nome de usu\u00e1rio e/ou senha est\u00e3o incorretos." + }, + "step": { + "confirm": { + "description": "Voc\u00ea quer configurar o {name}?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/garmin_connect/translations/hu.json b/homeassistant/components/garmin_connect/translations/hu.json index fd805fc4e4d..2ada884847f 100644 --- a/homeassistant/components/garmin_connect/translations/hu.json +++ b/homeassistant/components/garmin_connect/translations/hu.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Ez a fi\u00f3k m\u00e1r konfigur\u00e1lva van." + "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van" }, "error": { "cannot_connect": "Nem siker\u00fclt csatlakozni, pr\u00f3b\u00e1lkozzon \u00fajra.", diff --git a/homeassistant/components/garmin_connect/translations/pt-BR.json b/homeassistant/components/garmin_connect/translations/pt-BR.json new file mode 100644 index 00000000000..157ac3f0477 --- /dev/null +++ b/homeassistant/components/garmin_connect/translations/pt-BR.json @@ -0,0 +1,10 @@ +{ + "config": { + "step": { + "user": { + "description": "Digite suas credenciais.", + "title": "Garmin Connect" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gdacs/translations/pt-BR.json b/homeassistant/components/gdacs/translations/pt-BR.json new file mode 100644 index 00000000000..1e866fa8059 --- /dev/null +++ b/homeassistant/components/gdacs/translations/pt-BR.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "already_configured": "Local j\u00e1 est\u00e1 configurado." + }, + "step": { + "user": { + "data": { + "radius": "Raio" + }, + "title": "Preencha os detalhes do filtro." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/geonetnz_quakes/translations/pt-BR.json b/homeassistant/components/geonetnz_quakes/translations/pt-BR.json index 9f08d6b820c..ee705850b03 100644 --- a/homeassistant/components/geonetnz_quakes/translations/pt-BR.json +++ b/homeassistant/components/geonetnz_quakes/translations/pt-BR.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "Local j\u00e1 est\u00e1 configurado." + }, "step": { "user": { "data": { diff --git a/homeassistant/components/glances/translations/hu.json b/homeassistant/components/glances/translations/hu.json index ebb44dc1d6e..0958efee4ae 100644 --- a/homeassistant/components/glances/translations/hu.json +++ b/homeassistant/components/glances/translations/hu.json @@ -10,7 +10,7 @@ "step": { "user": { "data": { - "host": "Kiszolg\u00e1l\u00f3", + "host": "Hoszt", "name": "N\u00e9v", "password": "Jelsz\u00f3", "port": "Port", diff --git a/homeassistant/components/gogogate2/translations/ca.json b/homeassistant/components/gogogate2/translations/ca.json new file mode 100644 index 00000000000..43525e1870d --- /dev/null +++ b/homeassistant/components/gogogate2/translations/ca.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "cannot_connect": "No s'ha pogut connectar" + }, + "error": { + "cannot_connect": "No s'ha pogut connectar", + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida" + }, + "step": { + "user": { + "data": { + "ip_address": "Adre\u00e7a IP", + "password": "Contrasenya", + "username": "Nom d'usuari" + }, + "description": "Proporciona, a continuaci\u00f3, la informaci\u00f3 necess\u00e0ria.", + "title": "Configuraci\u00f3 de GogoGate2" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gogogate2/translations/en.json b/homeassistant/components/gogogate2/translations/en.json new file mode 100644 index 00000000000..d5a93091d91 --- /dev/null +++ b/homeassistant/components/gogogate2/translations/en.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "cannot_connect": "Failed to connect" + }, + "error": { + "cannot_connect": "Failed to connect", + "invalid_auth": "Invalid authentication" + }, + "step": { + "user": { + "data": { + "ip_address": "IP Address", + "password": "Password", + "username": "Username" + }, + "description": "Provide requisite information below.", + "title": "Setup GogoGate2" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gogogate2/translations/hu.json b/homeassistant/components/gogogate2/translations/hu.json new file mode 100644 index 00000000000..952a502a72d --- /dev/null +++ b/homeassistant/components/gogogate2/translations/hu.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s" + }, + "step": { + "user": { + "data": { + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gogogate2/translations/pt-BR.json b/homeassistant/components/gogogate2/translations/pt-BR.json new file mode 100644 index 00000000000..79dc7af8131 --- /dev/null +++ b/homeassistant/components/gogogate2/translations/pt-BR.json @@ -0,0 +1,15 @@ +{ + "config": { + "step": { + "user": { + "data": { + "ip_address": "Endere\u00e7o IP", + "password": "Senha", + "username": "Nome de usu\u00e1rio" + }, + "description": "Forne\u00e7a as informa\u00e7\u00f5es necess\u00e1rias abaixo.", + "title": "Configurar GogoGate2" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gogogate2/translations/ru.json b/homeassistant/components/gogogate2/translations/ru.json new file mode 100644 index 00000000000..9f428658820 --- /dev/null +++ b/homeassistant/components/gogogate2/translations/ru.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "cannot_connect": "\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f." + }, + "error": { + "cannot_connect": "\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f.", + "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f." + }, + "step": { + "user": { + "data": { + "ip_address": "IP-\u0430\u0434\u0440\u0435\u0441", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u041b\u043e\u0433\u0438\u043d" + }, + "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0441 GogoGate2.", + "title": "GogoGate2" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/griddy/translations/pt-BR.json b/homeassistant/components/griddy/translations/pt-BR.json new file mode 100644 index 00000000000..dc9c1362dc4 --- /dev/null +++ b/homeassistant/components/griddy/translations/pt-BR.json @@ -0,0 +1,8 @@ +{ + "config": { + "error": { + "cannot_connect": "Falha ao conectar, tente novamente", + "unknown": "Erro inesperado" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hangouts/translations/hu.json b/homeassistant/components/hangouts/translations/hu.json index ea7fd49a548..9a9f5b41598 100644 --- a/homeassistant/components/hangouts/translations/hu.json +++ b/homeassistant/components/hangouts/translations/hu.json @@ -19,7 +19,7 @@ }, "user": { "data": { - "email": "E-Mail C\u00edm", + "email": "E-mail", "password": "Jelsz\u00f3" }, "description": "\u00dcres", diff --git a/homeassistant/components/harmony/translations/hu.json b/homeassistant/components/harmony/translations/hu.json new file mode 100644 index 00000000000..cbf055e2fba --- /dev/null +++ b/homeassistant/components/harmony/translations/hu.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "host": "Hoszt" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/harmony/translations/ko.json b/homeassistant/components/harmony/translations/ko.json index 5751c22bbbe..528f5e9cc7e 100644 --- a/homeassistant/components/harmony/translations/ko.json +++ b/homeassistant/components/harmony/translations/ko.json @@ -7,7 +7,7 @@ "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" }, - "flow_title": "Logitech Harmony Hub {name}", + "flow_title": "Logitech Harmony Hub: {name}", "step": { "link": { "description": "{name} ({host}) \uc744(\ub97c) \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", diff --git a/homeassistant/components/harmony/translations/pt-BR.json b/homeassistant/components/harmony/translations/pt-BR.json new file mode 100644 index 00000000000..7fe3f58cad6 --- /dev/null +++ b/homeassistant/components/harmony/translations/pt-BR.json @@ -0,0 +1,34 @@ +{ + "config": { + "abort": { + "already_configured": "O dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao conectar, tente novamente", + "unknown": "Erro inesperado" + }, + "flow_title": "Logitech Harmony Hub {name}", + "step": { + "link": { + "description": "Voc\u00ea quer configurar o {name} ({host})?", + "title": "Configura\u00e7\u00e3o do Logitech Harmony Hub" + }, + "user": { + "data": { + "name": "Nome do Hub" + }, + "title": "Configura\u00e7\u00e3o do Logitech Harmony Hub" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "activity": "A atividade padr\u00e3o a ser executada quando nenhuma for especificada.", + "delay_secs": "O atraso entre o envio de comandos." + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/heos/translations/hu.json b/homeassistant/components/heos/translations/hu.json index 8cd10b3c246..eb7f856a000 100644 --- a/homeassistant/components/heos/translations/hu.json +++ b/homeassistant/components/heos/translations/hu.json @@ -4,7 +4,7 @@ "user": { "data": { "access_token": "Kiszolg\u00e1l\u00f3", - "host": "Kiszolg\u00e1l\u00f3" + "host": "Hoszt" } } } diff --git a/homeassistant/components/home_connect/translations/pt-BR.json b/homeassistant/components/home_connect/translations/pt-BR.json new file mode 100644 index 00000000000..ff8e13aed1f --- /dev/null +++ b/homeassistant/components/home_connect/translations/pt-BR.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "missing_configuration": "O componente Home Connect n\u00e3o est\u00e1 configurado. Por favor, siga a documenta\u00e7\u00e3o." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homekit/translations/ko.json b/homeassistant/components/homekit/translations/ko.json index 52b329104df..7f9d6d26d11 100644 --- a/homeassistant/components/homekit/translations/ko.json +++ b/homeassistant/components/homekit/translations/ko.json @@ -11,7 +11,7 @@ "user": { "data": { "auto_start": "\uc790\ub3d9 \uc2dc\uc791 (Z-Wave \ub610\ub294 \uae30\ud0c0 \uc9c0\uc5f0\ub41c \uc2dc\uc791 \uc2dc\uc2a4\ud15c\uc744 \uc0ac\uc6a9\ud558\ub294 \uacbd\uc6b0 \ube44\ud65c\uc131\ud654\ud574\uc8fc\uc138\uc694)", - "include_domains": "\ud3ec\ud568 \ud560 \ub3c4\uba54\uc778" + "include_domains": "\ud3ec\ud568\ud560 \ub3c4\uba54\uc778" }, "description": "HomeKit \ube0c\ub9ac\uc9c0\ub97c \uc0ac\uc6a9\ud558\uba74 HomeKit \uc5d0\uc11c Home Assistant \uad6c\uc131\uc694\uc18c\uc5d0 \uc561\uc138\uc2a4\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. HomeKit \ube0c\ub9ac\uc9c0\ub294 \ube0c\ub9ac\uc9c0 \uc790\uccb4\ub97c \ud3ec\ud568\ud558\uc5ec \uc778\uc2a4\ud134\uc2a4\ub2f9 150\uac1c\uc758 \uc561\uc138\uc11c\ub9ac\ub85c \uc81c\ud55c\ub429\ub2c8\ub2e4. \ucd5c\ub300 \uc561\uc138\uc11c\ub9ac \uc218\ub97c \ucd08\uacfc\ud558\uc5ec \ube0c\ub9ac\uc9d5\ud558\ub824\uba74 \uc5ec\ub7ec \ub3c4\uba54\uc778\uc5d0 \ub300\ud574 \uc5ec\ub7ec \uac1c\uc758 \ud648\ud0b7 \ube0c\ub9ac\uc9c0\ub97c \uc0ac\uc6a9\ud558\ub294 \uac83\uc774 \uc88b\uc2b5\ub2c8\ub2e4. \uad6c\uc131\uc694\uc18c\uc5d0 \ub300\ud55c \uc790\uc138\ud55c \ub0b4\uc6a9\uc740 \uae30\ubcf8 \ube0c\ub9ac\uc9c0\uc758 YAML \uc744 \ud1b5\ud574\uc11c\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "title": "HomeKit \ube0c\ub9ac\uc9c0 \ud65c\uc131\ud654\ud558\uae30" @@ -24,9 +24,9 @@ "data": { "auto_start": "\uc790\ub3d9 \uc2dc\uc791 (Z-Wave \ub610\ub294 \uae30\ud0c0 \uc9c0\uc5f0\ub41c \uc2dc\uc791 \uc2dc\uc2a4\ud15c\uc744 \uc0ac\uc6a9\ud558\ub294 \uacbd\uc6b0 \ube44\ud65c\uc131\ud654\ud574\uc8fc\uc138\uc694)", "safe_mode": "\uc548\uc804 \ubaa8\ub4dc (\ud398\uc5b4\ub9c1\uc774 \uc2e4\ud328\ud55c \uacbd\uc6b0\uc5d0\ub9cc \ud65c\uc131\ud654\ud574\uc8fc\uc138\uc694)", - "zeroconf_default_interface": "\uae30\ubcf8 zeroconf \uc778\ud130\ud398\uc774\uc2a4 \uc0ac\uc6a9 (Home \uc571\uc5d0\uc11c \ube0c\ub9ac\uc9c0\ub97c \ucc3e\uc744 \uc218\uc5c6\ub294 \uacbd\uc6b0 \ud65c\uc131\ud654\ud574\uc8fc\uc138\uc694)" + "zeroconf_default_interface": "\uae30\ubcf8 zeroconf \uc778\ud130\ud398\uc774\uc2a4 \uc0ac\uc6a9 (Home \uc571\uc5d0\uc11c \ube0c\ub9ac\uc9c0\ub97c \ucc3e\uc744 \uc218 \uc5c6\ub294 \uacbd\uc6b0 \ud65c\uc131\ud654\ud574\uc8fc\uc138\uc694)" }, - "description": "\uc774 \uc124\uc815\uc740 HomeKit \ube0c\ub9ac\uc9c0\uac00 \uc791\ub3d9\ud558\uc9c0 \uc54a\ub294 \uacbd\uc6b0\uc5d0\ub9cc \uc870\uc815\ud558\uba74 \ub429\ub2c8\ub2e4.", + "description": "\uc774 \uc124\uc815\uc740 HomeKit \ube0c\ub9ac\uc9c0\uac00 \uc791\ub3d9\ud558\uc9c0 \uc54a\ub294 \uacbd\uc6b0\uc5d0\ub9cc \uc124\uc815\ud574\uc8fc\uc138\uc694.", "title": "\uace0\uae09 \uad6c\uc131\ud558\uae30" }, "cameras": { @@ -38,16 +38,16 @@ }, "exclude": { "data": { - "exclude_entities": "\uc81c\uc678 \ud560 \uad6c\uc131\uc694\uc18c" + "exclude_entities": "\uc81c\uc678\ud560 \uad6c\uc131\uc694\uc18c" }, "description": "\ube0c\ub9ac\uc9c0\ud558\uc9c0 \uc54a\uc73c\ub824\ub294 \uad6c\uc131\uc694\uc18c\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694.", "title": "\ube0c\ub9ac\uc9c0\uc5d0\uc11c \uc120\ud0dd\ud55c \ub3c4\uba54\uc778\uc758 \uad6c\uc131\uc694\uc18c \uc81c\uc678\ud558\uae30" }, "init": { "data": { - "include_domains": "\ud3ec\ud568 \ud560 \ub3c4\uba54\uc778" + "include_domains": "\ud3ec\ud568\ud560 \ub3c4\uba54\uc778" }, - "description": "\"\ud3ec\ud568 \ud560 \ub3c4\uba54\uc778\"\uc758 \uad6c\uc131\uc694\uc18c\ub294 HomeKit \uc5d0 \uc5f0\uacb0\ub429\ub2c8\ub2e4. \ub2e4\uc74c \ud654\uba74\uc5d0\uc11c \uc774 \ubaa9\ub85d\uc758 \uc81c\uc678\ud560 \uad6c\uc131\uc694\uc18c\ub97c \uc120\ud0dd\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", + "description": "\"\ud3ec\ud568\ud560 \ub3c4\uba54\uc778\"\uc758 \uad6c\uc131\uc694\uc18c\ub294 HomeKit \uc5d0 \uc5f0\uacb0\ub429\ub2c8\ub2e4. \ub2e4\uc74c \ud654\uba74\uc5d0\uc11c \uc774 \ubaa9\ub85d\uc758 \uc81c\uc678\ud560 \uad6c\uc131\uc694\uc18c\ub97c \uc120\ud0dd\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "title": "\ube0c\ub9ac\uc9c0 \ud560 \ub3c4\uba54\uc778 \uc120\ud0dd\ud558\uae30" }, "yaml": { diff --git a/homeassistant/components/homekit/translations/pt-BR.json b/homeassistant/components/homekit/translations/pt-BR.json index 290e588fd5d..149c16d4288 100644 --- a/homeassistant/components/homekit/translations/pt-BR.json +++ b/homeassistant/components/homekit/translations/pt-BR.json @@ -1,8 +1,16 @@ { "options": { "step": { + "advanced": { + "title": "Configura\u00e7\u00e3o avan\u00e7ada" + }, "cameras": { "title": "Selecione o codec de v\u00eddeo da c\u00e2mera." + }, + "exclude": { + "data": { + "exclude_entities": "Entidades para excluir" + } } } } diff --git a/homeassistant/components/homematicip_cloud/translations/ko.json b/homeassistant/components/homematicip_cloud/translations/ko.json index 733fc9ebf51..54c129bec6d 100644 --- a/homeassistant/components/homematicip_cloud/translations/ko.json +++ b/homeassistant/components/homematicip_cloud/translations/ko.json @@ -21,7 +21,7 @@ "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\ub9ac\uc9c0\uc758 \ubc84\ud2bc \uc704\uce58 \ubcf4\uae30](/static/images/config_flows/config_homematicip_cloud.png)", + "description": "Home Assistant \uc5d0 HomematicIP \ub97c \ub4f1\ub85d\ud558\ub824\uba74 \uc561\uc138\uc2a4 \ud3ec\uc778\ud2b8\uc758 \ud30c\ub780\uc0c9 \ubc84\ud2bc\uacfc \ud655\uc778\uc744 \ud074\ub9ad\ud574\uc8fc\uc138\uc694.\n\n![\ube0c\ub9ac\uc9c0\uc758 \ubc84\ud2bc \uc704\uce58 \ubcf4\uae30](/static/images/config_flows/config_homematicip_cloud.png)", "title": "\uc561\uc138\uc2a4 \ud3ec\uc778\ud2b8 \uc5f0\uacb0\ud558\uae30" } } diff --git a/homeassistant/components/huawei_lte/translations/ko.json b/homeassistant/components/huawei_lte/translations/ko.json index ba39176d04a..53a7c4fc822 100644 --- a/homeassistant/components/huawei_lte/translations/ko.json +++ b/homeassistant/components/huawei_lte/translations/ko.json @@ -23,7 +23,7 @@ "url": "URL \uc8fc\uc18c", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" }, - "description": "\uae30\uae30 \uc561\uc138\uc2a4 \uc138\ubd80 \uc0ac\ud56d\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694. \uc0ac\uc6a9\uc790 \uc774\ub984\uacfc \ube44\ubc00\ubc88\ud638\ub97c \uc124\uc815\ud558\ub294 \uac83\uc740 \uc120\ud0dd \uc0ac\ud56d\uc774\uc9c0\ub9cc \ub354 \ub9ce\uc740 \uae30\ub2a5\uc744 \uc9c0\uc6d0\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \ubc18\uba74, \uc778\uc99d \ub41c \uc5f0\uacb0\uc744 \uc0ac\uc6a9\ud558\uba74, \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\uac00 \ud65c\uc131\ud654 \ub41c \uc0c1\ud0dc\uc5d0\uc11c \ub2e4\ub978 \ubc29\ubc95\uc73c\ub85c Home Assistant \uc758 \uc678\ubd80\uc5d0\uc11c \uae30\uae30\uc758 \uc6f9 \uc778\ud130\ud398\uc774\uc2a4\uc5d0 \uc561\uc138\uc2a4\ud558\ub294 \ub370 \ubb38\uc81c\uac00 \ubc1c\uc0dd\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", + "description": "\uae30\uae30 \uc561\uc138\uc2a4 \uc138\ubd80 \uc0ac\ud56d\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694. \uc0ac\uc6a9\uc790 \uc774\ub984\uacfc \ube44\ubc00\ubc88\ud638\ub97c \uc124\uc815\ud558\ub294 \uac83\uc740 \uc120\ud0dd \uc0ac\ud56d\uc774\uc9c0\ub9cc \ub354 \ub9ce\uc740 \uae30\ub2a5\uc744 \uc9c0\uc6d0\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \ubc18\uba74, \uc778\uc99d\ub41c \uc5f0\uacb0\uc744 \uc0ac\uc6a9\ud558\uba74, \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\uac00 \ud65c\uc131\ud654\ub41c \uc0c1\ud0dc\uc5d0\uc11c \ub2e4\ub978 \ubc29\ubc95\uc73c\ub85c Home Assistant \uc758 \uc678\ubd80\uc5d0\uc11c \uae30\uae30\uc758 \uc6f9 \uc778\ud130\ud398\uc774\uc2a4\uc5d0 \uc561\uc138\uc2a4 \ud558\ub294 \ub370 \ubb38\uc81c\uac00 \ubc1c\uc0dd\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "title": "Huawei LTE \uc124\uc815\ud558\uae30" } } diff --git a/homeassistant/components/hue/translations/pt-BR.json b/homeassistant/components/hue/translations/pt-BR.json index 251ee74c9de..9a7e8094b11 100644 --- a/homeassistant/components/hue/translations/pt-BR.json +++ b/homeassistant/components/hue/translations/pt-BR.json @@ -26,5 +26,11 @@ "title": "Hub de links" } } + }, + "device_automation": { + "trigger_subtype": { + "double_buttons_1_3": "Primeiro e terceiro bot\u00f5es", + "double_buttons_2_4": "Segundo e quarto bot\u00f5es" + } } } \ No newline at end of file diff --git a/homeassistant/components/hunterdouglas_powerview/translations/hu.json b/homeassistant/components/hunterdouglas_powerview/translations/hu.json index baa3b135d42..61461d1796c 100644 --- a/homeassistant/components/hunterdouglas_powerview/translations/hu.json +++ b/homeassistant/components/hunterdouglas_powerview/translations/hu.json @@ -10,7 +10,7 @@ "step": { "user": { "data": { - "host": "IP-c\u00edm" + "host": "IP c\u00edm" } } } diff --git a/homeassistant/components/iaqualink/translations/hu.json b/homeassistant/components/iaqualink/translations/hu.json index b0b9393acde..dee4ed9ee0f 100644 --- a/homeassistant/components/iaqualink/translations/hu.json +++ b/homeassistant/components/iaqualink/translations/hu.json @@ -4,7 +4,7 @@ "user": { "data": { "password": "Jelsz\u00f3", - "username": "Felhaszn\u00e1l\u00f3n\u00e9v / e-mail c\u00edm" + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" } } } diff --git a/homeassistant/components/icloud/translations/hu.json b/homeassistant/components/icloud/translations/hu.json index 194b92eb2fc..4dcf547619c 100644 --- a/homeassistant/components/icloud/translations/hu.json +++ b/homeassistant/components/icloud/translations/hu.json @@ -15,7 +15,8 @@ }, "user": { "data": { - "password": "Jelsz\u00f3" + "password": "Jelsz\u00f3", + "username": "E-mail" }, "description": "Adja meg hiteles\u00edt\u0151 adatait", "title": "iCloud hiteles\u00edt\u0151 adatok" diff --git a/homeassistant/components/ipma/translations/pt-BR.json b/homeassistant/components/ipma/translations/pt-BR.json index 4a0d8e0b01b..f2af40324eb 100644 --- a/homeassistant/components/ipma/translations/pt-BR.json +++ b/homeassistant/components/ipma/translations/pt-BR.json @@ -8,6 +8,7 @@ "data": { "latitude": "Latitude", "longitude": "Longitude", + "mode": "Modo", "name": "Nome" }, "description": "Instituto Portugu\u00eas do Mar e Atmosfera", diff --git a/homeassistant/components/ipp/translations/ca.json b/homeassistant/components/ipp/translations/ca.json index a7bc8d04890..7d372548e22 100644 --- a/homeassistant/components/ipp/translations/ca.json +++ b/homeassistant/components/ipp/translations/ca.json @@ -6,7 +6,8 @@ "connection_upgrade": "No s'ha pogut connectar amb la impressora, es necessita actualitzar la connexi\u00f3.", "ipp_error": "S'ha produ\u00eft un error IPP.", "ipp_version_error": "La versi\u00f3 IPP no \u00e9s compatible amb la impressora.", - "parse_error": "No s'ha pogut analitzar la resposta de la impressora." + "parse_error": "No s'ha pogut analitzar la resposta de la impressora.", + "unique_id_required": "Falta la identificaci\u00f3 \u00fanica al dispositiu, necess\u00e0ria per al descobriment." }, "error": { "connection_error": "No s'ha pogut connectar", diff --git a/homeassistant/components/ipp/translations/en.json b/homeassistant/components/ipp/translations/en.json index abbe4e8a5f8..0267c5b5091 100644 --- a/homeassistant/components/ipp/translations/en.json +++ b/homeassistant/components/ipp/translations/en.json @@ -6,7 +6,8 @@ "connection_upgrade": "Failed to connect to printer due to connection upgrade being required.", "ipp_error": "Encountered IPP error.", "ipp_version_error": "IPP version not supported by printer.", - "parse_error": "Failed to parse response from printer." + "parse_error": "Failed to parse response from printer.", + "unique_id_required": "Device missing unique identification required for discovery." }, "error": { "connection_error": "Failed to connect", diff --git a/homeassistant/components/ipp/translations/es.json b/homeassistant/components/ipp/translations/es.json index 5f4a1370b68..51c7e63677d 100644 --- a/homeassistant/components/ipp/translations/es.json +++ b/homeassistant/components/ipp/translations/es.json @@ -6,7 +6,8 @@ "connection_upgrade": "No se pudo conectar con la impresora debido a que se requiere una actualizaci\u00f3n de la conexi\u00f3n.", "ipp_error": "Error IPP encontrado.", "ipp_version_error": "Versi\u00f3n de IPP no compatible con la impresora.", - "parse_error": "Error al analizar la respuesta de la impresora." + "parse_error": "Error al analizar la respuesta de la impresora.", + "unique_id_required": "El dispositivo no tiene identificaci\u00f3n \u00fanica necesaria para el descubrimiento." }, "error": { "connection_error": "Error al conectar", diff --git a/homeassistant/components/ipp/translations/hu.json b/homeassistant/components/ipp/translations/hu.json index 66e835ec100..657491280df 100644 --- a/homeassistant/components/ipp/translations/hu.json +++ b/homeassistant/components/ipp/translations/hu.json @@ -1,5 +1,20 @@ { "config": { - "flow_title": "Nyomtat\u00f3: {name}" + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", + "connection_error": "Sikertelen csatlakoz\u00e1s" + }, + "error": { + "connection_error": "Sikertelen csatlakoz\u00e1s" + }, + "flow_title": "Nyomtat\u00f3: {name}", + "step": { + "user": { + "data": { + "host": "Hoszt", + "port": "Port" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/ipp/translations/ko.json b/homeassistant/components/ipp/translations/ko.json index abf0c270dac..dd071ed41ec 100644 --- a/homeassistant/components/ipp/translations/ko.json +++ b/homeassistant/components/ipp/translations/ko.json @@ -6,7 +6,8 @@ "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." + "parse_error": "\ud504\ub9b0\ud130\uc758 \uc751\ub2f5\uc744 \uc77d\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4.", + "unique_id_required": "\uae30\uae30 \uac80\uc0c9\uc5d0 \ud544\uc694\ud55c \uace0\uc720\ud55c ID \uac00 \uc874\uc7ac\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4." }, "error": { "connection_error": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", diff --git a/homeassistant/components/ipp/translations/pt-BR.json b/homeassistant/components/ipp/translations/pt-BR.json new file mode 100644 index 00000000000..f992520501e --- /dev/null +++ b/homeassistant/components/ipp/translations/pt-BR.json @@ -0,0 +1,28 @@ +{ + "config": { + "abort": { + "connection_upgrade": "Falha ao conectar \u00e0 impressora devido \u00e0 atualiza\u00e7\u00e3o da conex\u00e3o ser necess\u00e1ria.", + "ipp_error": "Erro IPP encontrado.", + "ipp_version_error": "Vers\u00e3o IPP n\u00e3o suportada pela impressora.", + "unique_id_required": "Dispositivo faltando identifica\u00e7\u00e3o \u00fanica necess\u00e1ria para a descoberta." + }, + "error": { + "connection_upgrade": "Falha ao conectar \u00e0 impressora. Por favor, tente novamente com a op\u00e7\u00e3o SSL/TLS marcada." + }, + "flow_title": "Impressora: {name}", + "step": { + "user": { + "data": { + "base_path": "Caminho relativo para a impressora", + "ssl": "A impressora suporta comunica\u00e7\u00e3o via SSL/TLS", + "verify_ssl": "A impressora usa um certificado SSL adequado" + }, + "description": "Configure sua impressora via IPP (Internet Printing Protocol) para integrar-se ao Home Assistant.", + "title": "Vincule sua impressora" + }, + "zeroconf_confirm": { + "title": "Impressora descoberta" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ipp/translations/ru.json b/homeassistant/components/ipp/translations/ru.json index c2d9e6b5cc0..6fdfd333773 100644 --- a/homeassistant/components/ipp/translations/ru.json +++ b/homeassistant/components/ipp/translations/ru.json @@ -6,7 +6,8 @@ "connection_upgrade": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f \u043a \u043f\u0440\u0438\u043d\u0442\u0435\u0440\u0443 \u0438\u0437-\u0437\u0430 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u0438 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f.", "ipp_error": "\u041e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u0430 \u043e\u0448\u0438\u0431\u043a\u0430 IPP.", "ipp_version_error": "\u0412\u0435\u0440\u0441\u0438\u044f IPP \u043d\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u043f\u0440\u0438\u043d\u0442\u0435\u0440\u043e\u043c.", - "parse_error": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u0442\u044c \u043e\u0442\u0432\u0435\u0442 \u043e\u0442 \u043f\u0440\u0438\u043d\u0442\u0435\u0440\u0430." + "parse_error": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u0442\u044c \u043e\u0442\u0432\u0435\u0442 \u043e\u0442 \u043f\u0440\u0438\u043d\u0442\u0435\u0440\u0430.", + "unique_id_required": "\u041d\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0435 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f, \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u0430\u044f \u0434\u043b\u044f \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u0438\u044f." }, "error": { "connection_error": "\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f.", diff --git a/homeassistant/components/islamic_prayer_times/translations/pt-BR.json b/homeassistant/components/islamic_prayer_times/translations/pt-BR.json new file mode 100644 index 00000000000..ee9946cf834 --- /dev/null +++ b/homeassistant/components/islamic_prayer_times/translations/pt-BR.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "one_instance_allowed": "Apenas uma \u00fanica inst\u00e2ncia \u00e9 necess\u00e1ria." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/isy994/translations/hu.json b/homeassistant/components/isy994/translations/hu.json new file mode 100644 index 00000000000..6177c39b231 --- /dev/null +++ b/homeassistant/components/isy994/translations/hu.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s", + "invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "unknown": "V\u00e1ratlan hiba" + }, + "step": { + "user": { + "data": { + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/isy994/translations/pt-BR.json b/homeassistant/components/isy994/translations/pt-BR.json index b4779e70884..65a087f8789 100644 --- a/homeassistant/components/isy994/translations/pt-BR.json +++ b/homeassistant/components/isy994/translations/pt-BR.json @@ -1,10 +1,14 @@ { "config": { + "error": { + "invalid_host": "A entrada do host n\u00e3o est\u00e1 no formato de URL completo, por exemplo, http://192.168.10.100:80" + }, "flow_title": "Dispositivos universais ISY994 {name} ({host})", "step": { "user": { "data": { - "host": "URL" + "host": "URL", + "tls": "A vers\u00e3o TLS do controlador ISY." }, "description": "A entrada do endere\u00e7o deve estar no formato de URL completo, por exemplo, http://192.168.10.100:80", "title": "Conecte-se ao seu ISY994" @@ -15,7 +19,10 @@ "step": { "init": { "data": { - "ignore_string": "Ignorar texto" + "ignore_string": "Ignorar texto", + "restore_light_state": "Restaurar o brilho da luz", + "sensor_string": "Texto do sensor node", + "variable_sensor_string": "Texto da vari\u00e1vel do sensor" }, "title": "ISY994 Op\u00e7\u00f5es" } diff --git a/homeassistant/components/konnected/translations/hu.json b/homeassistant/components/konnected/translations/hu.json index b7d4017a87e..1cc44a02646 100644 --- a/homeassistant/components/konnected/translations/hu.json +++ b/homeassistant/components/konnected/translations/hu.json @@ -1,4 +1,14 @@ { + "config": { + "step": { + "user": { + "data": { + "host": "IP c\u00edm", + "port": "Port" + } + } + } + }, "options": { "step": { "options_digital": { diff --git a/homeassistant/components/konnected/translations/pt-BR.json b/homeassistant/components/konnected/translations/pt-BR.json new file mode 100644 index 00000000000..501d685e3cd --- /dev/null +++ b/homeassistant/components/konnected/translations/pt-BR.json @@ -0,0 +1,66 @@ +{ + "config": { + "step": { + "user": { + "description": "Por favor, digite as informa\u00e7\u00f5es do host para o seu Painel Konnected.", + "title": "Descubra o dispositivo Konnected" + } + } + }, + "options": { + "abort": { + "not_konn_panel": "N\u00e3o \u00e9 um dispositivo Konnected.io reconhecido" + }, + "error": { + "bad_host": "URL de host da API de substitui\u00e7\u00e3o inv\u00e1lido" + }, + "step": { + "options_binary": { + "data": { + "inverse": "Inverter o estado aberto/fechado", + "name": "Nome (opcional)", + "type": "Tipo de sensor bin\u00e1rio" + }, + "description": "Selecione as op\u00e7\u00f5es para o sensor bin\u00e1rio conectado a {zone}", + "title": "Configurar sensor bin\u00e1rio" + }, + "options_digital": { + "data": { + "name": "Nome (opcional)", + "poll_interval": "Intervalo de vota\u00e7\u00e3o (minutos) (opcional)", + "type": "Tipo de sensor" + }, + "description": "Selecione as op\u00e7\u00f5es para o 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": "SA\u00cdDA" + }, + "title": "Configurar I/O" + }, + "options_io_ext": { + "data": { + "10": "Zona 10", + "11": "Zona 11", + "12": "Zona 12", + "8": "Zona 8", + "9": "Zona 9" + } + }, + "options_misc": { + "data": { + "api_host": "Substituir URL do host da API (opcional)", + "override_api_host": "Substituir o URL padr\u00e3o do painel do host da API do Home Assistant" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/life360/translations/hu.json b/homeassistant/components/life360/translations/hu.json index 7f158a24622..091e23e0d43 100644 --- a/homeassistant/components/life360/translations/hu.json +++ b/homeassistant/components/life360/translations/hu.json @@ -1,8 +1,12 @@ { "config": { + "abort": { + "user_already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van" + }, "error": { "invalid_username": "\u00c9rv\u00e9nytelen felhaszn\u00e1l\u00f3n\u00e9v", - "unexpected": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt a kommunik\u00e1ci\u00f3ban a Life360 szerverrel" + "unexpected": "V\u00e1ratlan hiba t\u00f6rt\u00e9nt a kommunik\u00e1ci\u00f3ban a Life360 szerverrel", + "user_already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van" }, "step": { "user": { diff --git a/homeassistant/components/light/translations/pt-BR.json b/homeassistant/components/light/translations/pt-BR.json index 207305ec08b..27b9b46297a 100644 --- a/homeassistant/components/light/translations/pt-BR.json +++ b/homeassistant/components/light/translations/pt-BR.json @@ -8,6 +8,10 @@ "condition_type": { "is_off": "{entity_name} est\u00e1 desligado", "is_on": "{entity_name} est\u00e1 ligado" + }, + "trigger_type": { + "turned_off": "{entity_name} desligado", + "turned_on": "{entity_name} ligado" } }, "state": { diff --git a/homeassistant/components/local_ip/translations/pt-BR.json b/homeassistant/components/local_ip/translations/pt-BR.json index 24246ca5732..be06de8b7f6 100644 --- a/homeassistant/components/local_ip/translations/pt-BR.json +++ b/homeassistant/components/local_ip/translations/pt-BR.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "single_instance_allowed": "Somente uma \u00fanica configura\u00e7\u00e3o do IP local \u00e9 permitida." + }, "step": { "user": { "data": { diff --git a/homeassistant/components/logi_circle/translations/ko.json b/homeassistant/components/logi_circle/translations/ko.json index cb3e2aab323..f3fe51e2d25 100644 --- a/homeassistant/components/logi_circle/translations/ko.json +++ b/homeassistant/components/logi_circle/translations/ko.json @@ -12,11 +12,11 @@ "error": { "auth_error": "API \uc2b9\uc778\uc5d0 \uc2e4\ud328\ud588\uc2b5\ub2c8\ub2e4.", "auth_timeout": "\uc5d1\uc138\uc2a4 \ud1a0\ud070 \uc694\uccad\uc911 \uc2b9\uc778 \uc2dc\uac04\uc774 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", - "follow_link": "Submit \ubc84\ud2bc\uc744 \ub204\ub974\uae30 \uc804\uc5d0 \ub9c1\ud06c\ub97c \ub530\ub77c \uc778\uc99d\uc744 \ubc1b\uc544\uc8fc\uc138\uc694" + "follow_link": "\ud655\uc778\uc744 \ud074\ub9ad\ud558\uae30 \uc804\uc5d0 \ub9c1\ud06c\ub97c \ub530\ub77c \uc778\uc99d\uc744 \ubc1b\uc544\uc8fc\uc138\uc694" }, "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})", + "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 **\ud655\uc778**\uc744 \ud074\ub9ad\ud574\uc8fc\uc138\uc694.\n\n[\ub9c1\ud06c]({authorization_url})", "title": "Logi Circle \uc778\uc99d\ud558\uae30" }, "user": { diff --git a/homeassistant/components/melcloud/translations/hu.json b/homeassistant/components/melcloud/translations/hu.json new file mode 100644 index 00000000000..62699ecb468 --- /dev/null +++ b/homeassistant/components/melcloud/translations/hu.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "Jelsz\u00f3", + "username": "E-mail" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/meteo_france/translations/pt-BR.json b/homeassistant/components/meteo_france/translations/pt-BR.json new file mode 100644 index 00000000000..f23bdb1379d --- /dev/null +++ b/homeassistant/components/meteo_france/translations/pt-BR.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "already_configured": "Cidade j\u00e1 configurada", + "unknown": "Erro desconhecido: tente novamente mais tarde" + }, + "step": { + "user": { + "data": { + "city": "Cidade" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mikrotik/translations/hu.json b/homeassistant/components/mikrotik/translations/hu.json index fffa8b293da..e68e0ceb414 100644 --- a/homeassistant/components/mikrotik/translations/hu.json +++ b/homeassistant/components/mikrotik/translations/hu.json @@ -11,7 +11,7 @@ "step": { "user": { "data": { - "host": "Kiszolg\u00e1l\u00f3", + "host": "Hoszt", "name": "N\u00e9v", "password": "Jelsz\u00f3", "port": "Port", diff --git a/homeassistant/components/mikrotik/translations/pt-BR.json b/homeassistant/components/mikrotik/translations/pt-BR.json new file mode 100644 index 00000000000..06ad1cba6d0 --- /dev/null +++ b/homeassistant/components/mikrotik/translations/pt-BR.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Mikrotik j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Conex\u00e3o malsucedida", + "name_exists": "O nome j\u00e1 existe", + "wrong_credentials": "Credenciais erradas" + }, + "step": { + "user": { + "data": { + "name": "Nome", + "verify_ssl": "Usar SSL" + }, + "title": "Configurar roteador Mikrotik" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mill/translations/hu.json b/homeassistant/components/mill/translations/hu.json new file mode 100644 index 00000000000..c8dec2dcb2d --- /dev/null +++ b/homeassistant/components/mill/translations/hu.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "connection_error": "Sikertelen csatlakoz\u00e1s" + }, + "step": { + "user": { + "data": { + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/minecraft_server/translations/hu.json b/homeassistant/components/minecraft_server/translations/hu.json index 3a2cb80fc9f..78778568e51 100644 --- a/homeassistant/components/minecraft_server/translations/hu.json +++ b/homeassistant/components/minecraft_server/translations/hu.json @@ -6,7 +6,7 @@ "step": { "user": { "data": { - "host": "Kiszolg\u00e1l\u00f3", + "host": "Hoszt", "name": "N\u00e9v" }, "title": "Kapcsold \u00f6ssze a Minecraft szervered" diff --git a/homeassistant/components/minecraft_server/translations/pt-BR.json b/homeassistant/components/minecraft_server/translations/pt-BR.json new file mode 100644 index 00000000000..5aa2fc3609a --- /dev/null +++ b/homeassistant/components/minecraft_server/translations/pt-BR.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "O host j\u00e1 est\u00e1 configurado." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/monoprice/translations/hu.json b/homeassistant/components/monoprice/translations/hu.json new file mode 100644 index 00000000000..892b8b2cd91 --- /dev/null +++ b/homeassistant/components/monoprice/translations/hu.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/monoprice/translations/pt-BR.json b/homeassistant/components/monoprice/translations/pt-BR.json new file mode 100644 index 00000000000..4eb010468f3 --- /dev/null +++ b/homeassistant/components/monoprice/translations/pt-BR.json @@ -0,0 +1,21 @@ +{ + "config": { + "error": { + "cannot_connect": "Falha ao conectar, tente novamente", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "source_1": "Nome da fonte #1", + "source_2": "Nome da fonte #2", + "source_3": "Nome da fonte #3", + "source_4": "Nome da fonte #4", + "source_5": "Nome da fonte #5", + "source_6": "Nome da fonte #6" + }, + "title": "Conecte-se ao dispositivo" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mqtt/translations/hu.json b/homeassistant/components/mqtt/translations/hu.json index ee0ac26e0cd..f9d8c5f1594 100644 --- a/homeassistant/components/mqtt/translations/hu.json +++ b/homeassistant/components/mqtt/translations/hu.json @@ -23,7 +23,7 @@ "discovery": "Felfedez\u00e9s enged\u00e9lyez\u00e9se" }, "description": "Be szeretn\u00e9d konfigru\u00e1lni, hogy a Home Assistant a(z) {addon} Hass.io add-on \u00e1ltal biztos\u00edtott MQTT br\u00f3kerhez csatlakozzon?", - "title": "MQTT Broker a Hass.io b\u0151v\u00edtm\u00e9nyen kereszt\u00fcl" + "title": "MQTT Br\u00f3ker a Hass.io b\u0151v\u00edtm\u00e9nnyel" } } }, @@ -37,6 +37,16 @@ "button_6": "Hatodik gomb", "turn_off": "Kikapcsol\u00e1s", "turn_on": "Bekapcsol\u00e1s" + }, + "trigger_type": { + "button_double_press": "\"{subtype}\" dupla kattint\u00e1s", + "button_long_press": "\"{subtype}\" folyamatosan nyomva tartva", + "button_long_release": "\"{subtype}\" nyomva tart\u00e1s ut\u00e1n felengedve", + "button_quadruple_press": "\"{subtype}\" n\u00e9gyszeres kattint\u00e1s", + "button_quintuple_press": "\"{subtype}\" \u00f6tsz\u00f6r\u00f6s kattint\u00e1s", + "button_short_press": "\"{subtype}\" lenyomva", + "button_short_release": "\"{subtype}\" felengedve", + "button_triple_press": "\"{subtype}\" tripla kattint\u00e1s" } } } \ No newline at end of file diff --git a/homeassistant/components/mqtt/translations/pt-BR.json b/homeassistant/components/mqtt/translations/pt-BR.json index 1fe0ef5c6e5..392b4c78952 100644 --- a/homeassistant/components/mqtt/translations/pt-BR.json +++ b/homeassistant/components/mqtt/translations/pt-BR.json @@ -26,5 +26,17 @@ "title": "MQTT Broker via add-on Hass.io" } } + }, + "device_automation": { + "trigger_subtype": { + "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", + "turn_off": "Desligar", + "turn_on": "Ligar" + } } } \ No newline at end of file diff --git a/homeassistant/components/myq/translations/hu.json b/homeassistant/components/myq/translations/hu.json new file mode 100644 index 00000000000..dee4ed9ee0f --- /dev/null +++ b/homeassistant/components/myq/translations/hu.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/netatmo/translations/hu.json b/homeassistant/components/netatmo/translations/hu.json index fac2dcb754f..4104aba464c 100644 --- a/homeassistant/components/netatmo/translations/hu.json +++ b/homeassistant/components/netatmo/translations/hu.json @@ -1,10 +1,17 @@ { "config": { "abort": { - "already_setup": "Csak egy Netatmo-fi\u00f3kot \u00e1ll\u00edthatsz be." + "already_setup": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges.", + "authorize_url_timeout": "Id\u0151t\u00fall\u00e9p\u00e9s a hiteles\u00edt\u00e9si URL gener\u00e1l\u00e1sa sor\u00e1n.", + "missing_configuration": "A komponens nincs konfigur\u00e1lva. K\u00e9rlek, k\u00f6vesd a dokument\u00e1ci\u00f3t." }, "create_entry": { - "default": "A Netatmo sikeresen hiteles\u00edtett." + "default": "Sikeres hiteles\u00edt\u00e9s" + }, + "step": { + "pick_implementation": { + "title": "V\u00e1lassz hiteles\u00edt\u00e9si m\u00f3dszert" + } } } } \ No newline at end of file diff --git a/homeassistant/components/notion/translations/hu.json b/homeassistant/components/notion/translations/hu.json index 285e6c7b485..fdd239f03d2 100644 --- a/homeassistant/components/notion/translations/hu.json +++ b/homeassistant/components/notion/translations/hu.json @@ -8,7 +8,7 @@ "user": { "data": { "password": "Jelsz\u00f3", - "username": "Felhaszn\u00e1l\u00f3n\u00e9v/Email C\u00edm" + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" }, "title": "T\u00f6ltsd ki az adataid" } diff --git a/homeassistant/components/nuheat/translations/pt-BR.json b/homeassistant/components/nuheat/translations/pt-BR.json new file mode 100644 index 00000000000..0fb1e678edd --- /dev/null +++ b/homeassistant/components/nuheat/translations/pt-BR.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "O termostato j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao conectar, tente novamente", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "invalid_thermostat": "O n\u00famero de s\u00e9rie do termostato \u00e9 inv\u00e1lido.", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "serial_number": "N\u00famero de s\u00e9rie do termostato." + }, + "description": "Voc\u00ea precisar\u00e1 obter o n\u00famero de s\u00e9rie ou ID num\u00e9rico do seu termostato acessando https://MyNuHeat.com e selecionando seu(s) termostato(s).", + "title": "Conecte-se ao NuHeat" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nut/translations/hu.json b/homeassistant/components/nut/translations/hu.json new file mode 100644 index 00000000000..1ca56c7684f --- /dev/null +++ b/homeassistant/components/nut/translations/hu.json @@ -0,0 +1,14 @@ +{ + "config": { + "step": { + "user": { + "data": { + "host": "Hoszt", + "password": "Jelsz\u00f3", + "port": "Port", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nut/translations/pt-BR.json b/homeassistant/components/nut/translations/pt-BR.json new file mode 100644 index 00000000000..8b6b7538ba8 --- /dev/null +++ b/homeassistant/components/nut/translations/pt-BR.json @@ -0,0 +1,35 @@ +{ + "config": { + "abort": { + "already_configured": "O dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao conectar, tente novamente", + "unknown": "Erro inesperado" + }, + "step": { + "resources": { + "data": { + "resources": "Recursos" + }, + "title": "Escolha os recursos para monitorar" + }, + "ups": { + "data": { + "alias": "Alias", + "resources": "Recursos" + }, + "title": "Escolha o no-break (UPS) para monitorar" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Intervalo de escaneamento (segundos)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nws/translations/hu.json b/homeassistant/components/nws/translations/hu.json new file mode 100644 index 00000000000..5f4f7bb8bee --- /dev/null +++ b/homeassistant/components/nws/translations/hu.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "api_key": "API kulcs" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nws/translations/pt-BR.json b/homeassistant/components/nws/translations/pt-BR.json new file mode 100644 index 00000000000..3d168bcce30 --- /dev/null +++ b/homeassistant/components/nws/translations/pt-BR.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "O dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao conectar, tente novamente", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "latitude": "Latitude", + "longitude": "Longitude", + "station": "C\u00f3digo da esta\u00e7\u00e3o METAR" + }, + "description": "Se um c\u00f3digo de esta\u00e7\u00e3o METAR n\u00e3o for especificado, a latitude e a longitude ser\u00e3o usadas para encontrar a esta\u00e7\u00e3o mais pr\u00f3xima.", + "title": "Conecte-se ao Servi\u00e7o Nacional de Meteorologia" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/onvif/translations/hu.json b/homeassistant/components/onvif/translations/hu.json new file mode 100644 index 00000000000..dfa0aec8765 --- /dev/null +++ b/homeassistant/components/onvif/translations/hu.json @@ -0,0 +1,18 @@ +{ + "config": { + "step": { + "auth": { + "data": { + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + }, + "manual_input": { + "data": { + "host": "Hoszt", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/onvif/translations/ko.json b/homeassistant/components/onvif/translations/ko.json index 9715d89f72b..5619a6daa2b 100644 --- a/homeassistant/components/onvif/translations/ko.json +++ b/homeassistant/components/onvif/translations/ko.json @@ -40,7 +40,7 @@ "title": "ONVIF \uae30\uae30 \uad6c\uc131\ud558\uae30" }, "user": { - "description": "submit \uc744 \ud074\ub9ad\ud558\uba74 \ud504\ub85c\ud544 S \ub97c \uc9c0\uc6d0\ud558\ub294 ONVIF \uae30\uae30\ub97c \ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \uac80\uc0c9\ud569\ub2c8\ub2e4. \n\n\uc77c\ubd80 \uc81c\uc870\uc5c5\uccb4\ub294 \uae30\ubcf8\uac12\uc73c\ub85c ONVIF \ub97c \ube44\ud65c\uc131\ud654 \ud574 \ub193\uc740 \uacbd\uc6b0\uac00 \uc788\uc2b5\ub2c8\ub2e4. \uce74\uba54\ub77c \uad6c\uc131\uc5d0\uc11c ONVIF \uac00 \ud65c\uc131\ud654\ub418\uc5b4 \uc788\ub294\uc9c0 \ud655\uc778\ud574\uc8fc\uc138\uc694.", + "description": "\ud655\uc778\uc744 \ud074\ub9ad\ud558\uba74 \ud504\ub85c\ud544 S \ub97c \uc9c0\uc6d0\ud558\ub294 ONVIF \uae30\uae30\ub97c \ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \uac80\uc0c9\ud569\ub2c8\ub2e4. \n\n\uc77c\ubd80 \uc81c\uc870\uc5c5\uccb4\ub294 \uae30\ubcf8\uac12\uc73c\ub85c ONVIF \ub97c \ube44\ud65c\uc131\ud654 \ud574 \ub193\uc740 \uacbd\uc6b0\uac00 \uc788\uc2b5\ub2c8\ub2e4. \uce74\uba54\ub77c \uad6c\uc131\uc5d0\uc11c ONVIF \uac00 \ud65c\uc131\ud654\ub418\uc5b4 \uc788\ub294\uc9c0 \ud655\uc778\ud574\uc8fc\uc138\uc694.", "title": "ONVIF \uae30\uae30 \uc124\uc815\ud558\uae30" } } diff --git a/homeassistant/components/openuv/translations/hu.json b/homeassistant/components/openuv/translations/hu.json index 6d7e4e99e26..8e06c5d4ad0 100644 --- a/homeassistant/components/openuv/translations/hu.json +++ b/homeassistant/components/openuv/translations/hu.json @@ -7,7 +7,7 @@ "step": { "user": { "data": { - "api_key": "OpenUV API kulcs", + "api_key": "API kulcs", "elevation": "Magass\u00e1g", "latitude": "Sz\u00e9less\u00e9g", "longitude": "Hossz\u00fas\u00e1g" diff --git a/homeassistant/components/panasonic_viera/translations/hu.json b/homeassistant/components/panasonic_viera/translations/hu.json new file mode 100644 index 00000000000..2c3a9a820f9 --- /dev/null +++ b/homeassistant/components/panasonic_viera/translations/hu.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "host": "IP c\u00edm" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/panasonic_viera/translations/pt-BR.json b/homeassistant/components/panasonic_viera/translations/pt-BR.json new file mode 100644 index 00000000000..6e85a695b08 --- /dev/null +++ b/homeassistant/components/panasonic_viera/translations/pt-BR.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "already_configured": "Esta TV Panasonic Viera j\u00e1 est\u00e1 configurada.", + "not_connected": "A conex\u00e3o remota com a sua TV Panasonic Viera foi perdida. Verifique os logs para obter mais informa\u00e7\u00f5es." + }, + "step": { + "user": { + "description": "Digite o endere\u00e7o IP da sua TV Panasonic Viera", + "title": "Configure sua TV" + } + } + }, + "title": "Panasonic Viera" +} \ No newline at end of file diff --git a/homeassistant/components/pi_hole/translations/fr.json b/homeassistant/components/pi_hole/translations/fr.json new file mode 100644 index 00000000000..ddd63a02062 --- /dev/null +++ b/homeassistant/components/pi_hole/translations/fr.json @@ -0,0 +1,14 @@ +{ + "config": { + "step": { + "user": { + "data": { + "api_key": "Cl\u00e9 API (facultatif)", + "name": "Nom", + "ssl": "Utiliser SSL", + "verify_ssl": "V\u00e9rifier le certificat SSL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/pi_hole/translations/hu.json b/homeassistant/components/pi_hole/translations/hu.json new file mode 100644 index 00000000000..f1bd9a106bc --- /dev/null +++ b/homeassistant/components/pi_hole/translations/hu.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s" + }, + "step": { + "user": { + "data": { + "host": "Hoszt", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/plex/translations/hu.json b/homeassistant/components/plex/translations/hu.json index 839eff780de..a9cdf058566 100644 --- a/homeassistant/components/plex/translations/hu.json +++ b/homeassistant/components/plex/translations/hu.json @@ -17,6 +17,7 @@ "step": { "manual_setup": { "data": { + "host": "Hoszt", "port": "Port", "ssl": "Haszn\u00e1ljon SSL-t" } diff --git a/homeassistant/components/plex/translations/pt-BR.json b/homeassistant/components/plex/translations/pt-BR.json index aabc5499525..5768214c1a1 100644 --- a/homeassistant/components/plex/translations/pt-BR.json +++ b/homeassistant/components/plex/translations/pt-BR.json @@ -3,11 +3,22 @@ "abort": { "non-interactive": "Importa\u00e7\u00e3o n\u00e3o interativa" }, + "error": { + "ssl_error": "Problema no certificado SSL" + }, "flow_title": "{name} ({host})", "step": { "manual_setup": { "data": { - "ssl": "Usar SSL" + "ssl": "Usar SSL", + "token": "Token (Opcional)", + "verify_ssl": "Verifique o certificado SSL" + }, + "title": "Configura\u00e7\u00e3o manual do Plex" + }, + "user_advanced": { + "data": { + "setup_method": "M\u00e9todo de configura\u00e7\u00e3o" } } } diff --git a/homeassistant/components/point/translations/hu.json b/homeassistant/components/point/translations/hu.json index d63879b3b5e..c31e2c55e6a 100644 --- a/homeassistant/components/point/translations/hu.json +++ b/homeassistant/components/point/translations/hu.json @@ -1,12 +1,17 @@ { "config": { "abort": { + "already_setup": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges.", "authorize_url_fail": "Ismeretlen hiba t\u00f6rt\u00e9nt a hiteles\u00edt\u00e9si link gener\u00e1l\u00e1sa sor\u00e1n.", - "authorize_url_timeout": "Id\u0151t\u00fall\u00e9p\u00e9s az \u00e9rv\u00e9nyes\u00edt\u00e9si url gener\u00e1l\u00e1sa sor\u00e1n." + "authorize_url_timeout": "Id\u0151t\u00fall\u00e9p\u00e9s a hiteles\u00edt\u00e9si URL gener\u00e1l\u00e1sa sor\u00e1n.", + "no_flows": "A komponens nincs konfigur\u00e1lva. K\u00e9rlek, k\u00f6vesd a dokument\u00e1ci\u00f3t." + }, + "create_entry": { + "default": "Sikeres hiteles\u00edt\u00e9s" }, "error": { "follow_link": "K\u00e9rlek, k\u00f6vesd a hivatkoz\u00e1st \u00e9s hiteles\u00edtsd magad miel\u0151tt megnyomod a K\u00fcld\u00e9s gombot", - "no_token": "A Minut nincs hiteles\u00edtve" + "no_token": "\u00c9rv\u00e9nytelen hozz\u00e1f\u00e9r\u00e9si token" }, "step": { "auth": { @@ -17,8 +22,8 @@ "data": { "flow_impl": "Szolg\u00e1ltat\u00f3" }, - "description": "V\u00e1laszd ki, hogy melyik hiteles\u00edt\u00e9si szolg\u00e1ltat\u00f3n\u00e1l szeretn\u00e9d hiteles\u00edteni a Pointot.", - "title": "Hiteles\u00edt\u00e9si Szolg\u00e1ltat\u00f3" + "description": "El szeretn\u00e9d kezdeni a be\u00e1ll\u00edt\u00e1st?", + "title": "V\u00e1lassz hiteles\u00edt\u00e9si m\u00f3dszert" } } } diff --git a/homeassistant/components/point/translations/ko.json b/homeassistant/components/point/translations/ko.json index 8a4eb14f287..417cbf949f5 100644 --- a/homeassistant/components/point/translations/ko.json +++ b/homeassistant/components/point/translations/ko.json @@ -11,12 +11,12 @@ "default": "\uc131\uacf5\uc801\uc73c\ub85c \uc778\uc99d\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { - "follow_link": "Submit \ubc84\ud2bc\uc744 \ub204\ub974\uae30 \uc804\uc5d0 \ub9c1\ud06c\ub97c \ub530\ub77c \uc778\uc99d\uc744 \ubc1b\uc544\uc8fc\uc138\uc694", + "follow_link": "\ud655\uc778\uc744 \ud074\ub9ad\ud558\uae30 \uc804\uc5d0 \ub9c1\ud06c\ub97c \ub530\ub77c \uc778\uc99d\uc744 \ubc1b\uc544\uc8fc\uc138\uc694", "no_token": "\uc561\uc138\uc2a4 \ud1a0\ud070\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "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})", + "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 **\ud655\uc778**\uc744 \ud074\ub9ad\ud574\uc8fc\uc138\uc694. \n\n[\ub9c1\ud06c]({authorization_url})", "title": "Point \uc778\uc99d\ud558\uae30" }, "user": { diff --git a/homeassistant/components/powerwall/translations/pt-BR.json b/homeassistant/components/powerwall/translations/pt-BR.json new file mode 100644 index 00000000000..e97b93d1e66 --- /dev/null +++ b/homeassistant/components/powerwall/translations/pt-BR.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "O powerwall j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao conectar, tente novamente", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "ip_address": "Endere\u00e7o IP" + }, + "title": "Conecte-se ao powerwall" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ps4/translations/ko.json b/homeassistant/components/ps4/translations/ko.json index 7285ffb97bf..5a9ab246ac2 100644 --- a/homeassistant/components/ps4/translations/ko.json +++ b/homeassistant/components/ps4/translations/ko.json @@ -8,14 +8,14 @@ "port_997_bind_error": "\ud3ec\ud2b8 997 \uc5d0 \ubc14\uc778\ub529 \ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \ucd94\uac00 \uc815\ubcf4\ub294 [\uc548\ub0b4](https://www.home-assistant.io/components/ps4/) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694." }, "error": { - "credential_timeout": "\uc790\uaca9 \uc99d\uba85 \uc11c\ube44\uc2a4 \uc2dc\uac04\uc774 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4. Submit \uc744 \ub20c\ub7ec \ub2e4\uc2dc \uc2dc\uc791\ud574\uc8fc\uc138\uc694.", + "credential_timeout": "\uc790\uaca9 \uc99d\uba85 \uc11c\ube44\uc2a4 \uc2dc\uac04\uc774 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud655\uc778\uc744 \ud074\ub9ad\ud558\uc5ec \ub2e4\uc2dc \uc2dc\uc791\ud574\uc8fc\uc138\uc694.", "login_failed": "PlayStation 4 \uc640 \ud398\uc5b4\ub9c1\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. PIN \uc774 \uc62c\ubc14\ub978\uc9c0 \ud655\uc778\ud574\uc8fc\uc138\uc694.", "no_ipaddress": "\uad6c\uc131\ud558\uace0\uc790 \ud558\ub294 PlayStation 4 \uc758 IP \uc8fc\uc18c\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694.", "not_ready": "PlayStation 4 \uac00 \ucf1c\uc838 \uc788\uc9c0 \uc54a\uac70\ub098 \ub124\ud2b8\uc6cc\ud06c\uc5d0 \uc5f0\uacb0\ub418\uc5b4 \uc788\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4." }, "step": { "creds": { - "description": "\uc790\uaca9 \uc99d\uba85\uc774 \ud544\uc694\ud569\ub2c8\ub2e4. 'Submit' \uc744 \ub204\ub978 \ub2e4\uc74c PS4 \uc138\ucee8\ub4dc \uc2a4\ud06c\ub9b0 \uc571\uc5d0\uc11c \uae30\uae30\ub97c \uc0c8\ub85c \uace0\uce68\ud558\uace0 'Home-Assistant' \uae30\uae30\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694.", + "description": "\uc790\uaca9 \uc99d\uba85\uc774 \ud544\uc694\ud569\ub2c8\ub2e4. '\ud655\uc778'\uc744 \ud074\ub9ad\ud55c \ub2e4\uc74c PS4 \uc138\ucee8\ub4dc \uc2a4\ud06c\ub9b0 \uc571\uc5d0\uc11c \uae30\uae30\ub97c \uc0c8\ub85c \uace0\uce68\ud558\uace0 'Home-Assistant' \uae30\uae30\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694.", "title": "PlayStation 4" }, "link": { diff --git a/homeassistant/components/pvpc_hourly_pricing/translations/pt-BR.json b/homeassistant/components/pvpc_hourly_pricing/translations/pt-BR.json new file mode 100644 index 00000000000..efcaeb801d9 --- /dev/null +++ b/homeassistant/components/pvpc_hourly_pricing/translations/pt-BR.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "already_configured": "A integra\u00e7\u00e3o j\u00e1 est\u00e1 configurada com um sensor existente com essa tarifa" + }, + "step": { + "user": { + "data": { + "name": "Nome do sensor", + "tariff": "Tarifa contratada (1, 2 ou 3 per\u00edodos)" + }, + "description": "Esse sensor usa a API oficial para obter [pre\u00e7os por hora de eletricidade (PVPC)]](https://www.esios.ree.es/es/pvpc) na Espanha. \nPara uma explica\u00e7\u00e3o mais precisa, visite os [documentos de integra\u00e7\u00e3o](https://www.home-assistant.io/integrations/pvpc_hourly_pricing/). \n\nSelecione a taxa contratada com base no n\u00famero de per\u00edodos de cobran\u00e7a por dia: \n- 1 per\u00edodo: normal \n- 2 per\u00edodos: discrimina\u00e7\u00e3o (taxa noturna) \n- 3 per\u00edodos: carro el\u00e9trico (taxa noturna de 3 per\u00edodos)", + "title": "Sele\u00e7\u00e3o de tarifas" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/rachio/translations/hu.json b/homeassistant/components/rachio/translations/hu.json new file mode 100644 index 00000000000..5f4f7bb8bee --- /dev/null +++ b/homeassistant/components/rachio/translations/hu.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "api_key": "API kulcs" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/roku/translations/hu.json b/homeassistant/components/roku/translations/hu.json index ab0e6cbad74..8b4e9f9d54d 100644 --- a/homeassistant/components/roku/translations/hu.json +++ b/homeassistant/components/roku/translations/hu.json @@ -1,7 +1,18 @@ { "config": { "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", "unknown": "V\u00e1ratlan hiba" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s" + }, + "step": { + "user": { + "data": { + "host": "Hoszt" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/roku/translations/pt-BR.json b/homeassistant/components/roku/translations/pt-BR.json new file mode 100644 index 00000000000..8f5916b3f36 --- /dev/null +++ b/homeassistant/components/roku/translations/pt-BR.json @@ -0,0 +1,15 @@ +{ + "config": { + "flow_title": "Roku: {name}", + "step": { + "ssdp_confirm": { + "description": "Voc\u00ea quer configurar o {name}?", + "title": "Roku" + }, + "user": { + "description": "Digite suas informa\u00e7\u00f5es de Roku.", + "title": "Roku" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/roomba/translations/hu.json b/homeassistant/components/roomba/translations/hu.json new file mode 100644 index 00000000000..357ca74746d --- /dev/null +++ b/homeassistant/components/roomba/translations/hu.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "host": "Hoszt", + "password": "Jelsz\u00f3" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/roomba/translations/pt-BR.json b/homeassistant/components/roomba/translations/pt-BR.json new file mode 100644 index 00000000000..bdd6b93a01a --- /dev/null +++ b/homeassistant/components/roomba/translations/pt-BR.json @@ -0,0 +1,30 @@ +{ + "config": { + "error": { + "cannot_connect": "Falha ao conectar, tente novamente", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "data": { + "blid": "BLID", + "certificate": "Certificado", + "continuous": "Cont\u00ednuo", + "delay": "Atraso" + }, + "description": "Atualmente, a recupera\u00e7\u00e3o do BLID e da senha \u00e9 um processo manual. Siga as etapas descritas na documenta\u00e7\u00e3o em: https://www.home-assistant.io/integrations/roomba/#retrieving-your-credentials", + "title": "Conecte-se ao dispositivo" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "continuous": "Cont\u00ednuo", + "delay": "Atraso" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/samsungtv/translations/hu.json b/homeassistant/components/samsungtv/translations/hu.json index 1704fa04897..3f0cdcadfea 100644 --- a/homeassistant/components/samsungtv/translations/hu.json +++ b/homeassistant/components/samsungtv/translations/hu.json @@ -15,7 +15,7 @@ }, "user": { "data": { - "host": "Hosztn\u00e9v vagy IP c\u00edm", + "host": "Hoszt", "name": "N\u00e9v" }, "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.", diff --git a/homeassistant/components/season/translations/sensor.pt-BR.json b/homeassistant/components/season/translations/sensor.pt-BR.json index 4c81e432350..3f157f43c79 100644 --- a/homeassistant/components/season/translations/sensor.pt-BR.json +++ b/homeassistant/components/season/translations/sensor.pt-BR.json @@ -1,5 +1,11 @@ { "state": { + "season__season": { + "autumn": "Outono", + "spring": "Primavera", + "summer": "Ver\u00e3o", + "winter": "Inverno" + }, "season__season__": { "autumn": "Outono", "spring": "Primavera", diff --git a/homeassistant/components/sense/translations/hu.json b/homeassistant/components/sense/translations/hu.json new file mode 100644 index 00000000000..0085d9ea9c4 --- /dev/null +++ b/homeassistant/components/sense/translations/hu.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "email": "E-mail", + "password": "Jelsz\u00f3" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sense/translations/pt-BR.json b/homeassistant/components/sense/translations/pt-BR.json new file mode 100644 index 00000000000..d04d91c034b --- /dev/null +++ b/homeassistant/components/sense/translations/pt-BR.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "already_configured": "O dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao conectar, tente novamente", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "unknown": "Erro inesperado" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/shopping_list/translations/hu.json b/homeassistant/components/shopping_list/translations/hu.json new file mode 100644 index 00000000000..4a093bea379 --- /dev/null +++ b/homeassistant/components/shopping_list/translations/hu.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "already_configured": "A bev\u00e1s\u00e1rl\u00f3lista m\u00e1r konfigur\u00e1lva van." + }, + "step": { + "user": { + "description": "Be szeretn\u00e9d \u00e1ll\u00edtani a bev\u00e1s\u00e1rl\u00f3list\u00e1t?", + "title": "Bev\u00e1s\u00e1rl\u00f3lista" + } + } + }, + "title": "Bev\u00e1s\u00e1rl\u00f3lista" +} \ No newline at end of file diff --git a/homeassistant/components/shopping_list/translations/pt-BR.json b/homeassistant/components/shopping_list/translations/pt-BR.json new file mode 100644 index 00000000000..9e8b24efa29 --- /dev/null +++ b/homeassistant/components/shopping_list/translations/pt-BR.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "already_configured": "A lista de compras j\u00e1 est\u00e1 configurada." + }, + "step": { + "user": { + "description": "Deseja configurar a lista de compras?", + "title": "Lista de compras" + } + } + }, + "title": "Lista de compras" +} \ No newline at end of file diff --git a/homeassistant/components/simplisafe/translations/hu.json b/homeassistant/components/simplisafe/translations/hu.json index 95331d8c8d2..38c48c3a86d 100644 --- a/homeassistant/components/simplisafe/translations/hu.json +++ b/homeassistant/components/simplisafe/translations/hu.json @@ -8,7 +8,7 @@ "user": { "data": { "password": "Jelsz\u00f3", - "username": "Email c\u00edm" + "username": "E-mail" }, "title": "T\u00f6ltsd ki az adataid" } diff --git a/homeassistant/components/smartthings/translations/hu.json b/homeassistant/components/smartthings/translations/hu.json index d71e29e0171..a148bfa04fb 100644 --- a/homeassistant/components/smartthings/translations/hu.json +++ b/homeassistant/components/smartthings/translations/hu.json @@ -8,6 +8,11 @@ "webhook_error": "A SmartThings nem tudta \u00e9rv\u00e9nyes\u00edteni a `base_url`-ben konfigur\u00e1lt v\u00e9gpontot. K\u00e9rlek, tekintsd \u00e1t az \u00f6sszetev\u0151 k\u00f6vetelm\u00e9nyeit." }, "step": { + "pat": { + "data": { + "access_token": "Hozz\u00e1f\u00e9r\u00e9si token" + } + }, "user": { "description": "K\u00e9rlek add meg a SmartThings [Personal Access Tokent]({token_url}), amit az [instrukci\u00f3k] ({component_url}) alapj\u00e1n hozt\u00e1l l\u00e9tre.", "title": "Adja meg a szem\u00e9lyes hozz\u00e1f\u00e9r\u00e9si Tokent" diff --git a/homeassistant/components/smartthings/translations/pt-BR.json b/homeassistant/components/smartthings/translations/pt-BR.json index a28daf34acc..5b6329a530f 100644 --- a/homeassistant/components/smartthings/translations/pt-BR.json +++ b/homeassistant/components/smartthings/translations/pt-BR.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "no_available_locations": "N\u00e3o h\u00e1 Locais SmartThings dispon\u00edveis para configura\u00e7\u00e3o no Home Assistant." + }, "error": { "app_setup_error": "N\u00e3o \u00e9 poss\u00edvel configurar o SmartApp. Por favor, tente novamente.", "token_forbidden": "O token n\u00e3o possui os escopos necess\u00e1rios do OAuth.", diff --git a/homeassistant/components/solaredge/translations/hu.json b/homeassistant/components/solaredge/translations/hu.json index ed182671709..e66bf3b4043 100644 --- a/homeassistant/components/solaredge/translations/hu.json +++ b/homeassistant/components/solaredge/translations/hu.json @@ -3,6 +3,7 @@ "step": { "user": { "data": { + "api_key": "API kulcs", "name": "Ennek az install\u00e1ci\u00f3nak a neve" }, "title": "Az API param\u00e9terek megad\u00e1sa ehhez a telep\u00edt\u00e9shez" diff --git a/homeassistant/components/solarlog/translations/hu.json b/homeassistant/components/solarlog/translations/hu.json index e52cebefda6..3fa8a9620a0 100644 --- a/homeassistant/components/solarlog/translations/hu.json +++ b/homeassistant/components/solarlog/translations/hu.json @@ -2,6 +2,13 @@ "config": { "abort": { "already_configured": "Az eszk\u00f6zt m\u00e1r konfigur\u00e1ltuk" + }, + "step": { + "user": { + "data": { + "host": "Hoszt" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/soma/translations/hu.json b/homeassistant/components/soma/translations/hu.json index 357ff2ac170..82ec28ff4d7 100644 --- a/homeassistant/components/soma/translations/hu.json +++ b/homeassistant/components/soma/translations/hu.json @@ -11,7 +11,7 @@ "step": { "user": { "data": { - "host": "Kiszolg\u00e1l\u00f3", + "host": "Hoszt", "port": "Port" }, "description": "K\u00e9rj\u00fck, adja meg a SOMA Connect csatlakoz\u00e1si be\u00e1ll\u00edt\u00e1sait.", diff --git a/homeassistant/components/songpal/translations/hu.json b/homeassistant/components/songpal/translations/hu.json index b70fdd2d9b5..cd4c501ecf7 100644 --- a/homeassistant/components/songpal/translations/hu.json +++ b/homeassistant/components/songpal/translations/hu.json @@ -1,5 +1,11 @@ { "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "cannot_connect": "Sikertelen csatlakoz\u00e1s" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/songpal/translations/ko.json b/homeassistant/components/songpal/translations/ko.json index b71ca83bf38..1ab96064169 100644 --- a/homeassistant/components/songpal/translations/ko.json +++ b/homeassistant/components/songpal/translations/ko.json @@ -8,7 +8,7 @@ "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "connection": "\uc5f0\uacb0 \uc624\ub958: \uc5d4\ub4dc\ud3ec\uc778\ud2b8\ub97c \ud655\uc778\ud574\uc8fc\uc138\uc694" }, - "flow_title": "Sony Songpal {name} ({host})", + "flow_title": "Sony Songpal: {name} ({host})", "step": { "init": { "description": "{name} ({host}) \uc744(\ub97c) \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", diff --git a/homeassistant/components/starline/translations/ko.json b/homeassistant/components/starline/translations/ko.json index a4afdd6e22f..e9355a4d649 100644 --- a/homeassistant/components/starline/translations/ko.json +++ b/homeassistant/components/starline/translations/ko.json @@ -11,7 +11,7 @@ "app_id": "\uc560\ud50c\ub9ac\ucf00\uc774\uc158 ID", "app_secret": "\ubcf4\uc548\ud0a4" }, - "description": "StarLine \uac1c\ubc1c\uc790 \uacc4\uc815\uc758 \uc560\ud50c\ub9ac\ucf00\uc774\uc158 ID \ubc0f \ube44\ubc00\ubc88\ud638", + "description": "[StarLine \uac1c\ubc1c\uc790 \uacc4\uc815](https://my.starline.ru/developer) \uc758 \uc560\ud50c\ub9ac\ucf00\uc774\uc158 ID \ubc0f \ubcf4\uc548\ud0a4 \ucf54\ub4dc", "title": "\uc560\ud50c\ub9ac\ucf00\uc774\uc158 \uc790\uaca9 \uc99d\uba85" }, "auth_captcha": { diff --git a/homeassistant/components/synology_dsm/translations/hu.json b/homeassistant/components/synology_dsm/translations/hu.json index 0bb810d66e2..23cb168b9a5 100644 --- a/homeassistant/components/synology_dsm/translations/hu.json +++ b/homeassistant/components/synology_dsm/translations/hu.json @@ -3,11 +3,16 @@ "step": { "link": { "data": { + "password": "Jelsz\u00f3", + "port": "Port", "username": "Felhaszn\u00e1l\u00f3n\u00e9v" } }, "user": { "data": { + "host": "Hoszt", + "password": "Jelsz\u00f3", + "port": "Port", "username": "Felhaszn\u00e1l\u00f3n\u00e9v" } } diff --git a/homeassistant/components/synology_dsm/translations/ko.json b/homeassistant/components/synology_dsm/translations/ko.json index 252c68f04ad..8090b6d6c7e 100644 --- a/homeassistant/components/synology_dsm/translations/ko.json +++ b/homeassistant/components/synology_dsm/translations/ko.json @@ -10,7 +10,7 @@ "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})", + "flow_title": "Synology DSM: {name} ({host})", "step": { "2sa": { "data": { diff --git a/homeassistant/components/synology_dsm/translations/pt-BR.json b/homeassistant/components/synology_dsm/translations/pt-BR.json new file mode 100644 index 00000000000..61be8e0e51d --- /dev/null +++ b/homeassistant/components/synology_dsm/translations/pt-BR.json @@ -0,0 +1,33 @@ +{ + "config": { + "error": { + "otp_failed": "Falha na autentica\u00e7\u00e3o em duas etapas, tente novamente com um novo c\u00f3digo", + "unknown": "Erro desconhecido: verifique os logs para obter mais detalhes" + }, + "flow_title": "Synology DSM {name} ({host})", + "step": { + "2sa": { + "data": { + "otp_code": "C\u00f3digo" + } + }, + "link": { + "data": { + "api_version": "Vers\u00e3o DSM", + "ssl": "Use SSL/TLS para conectar-se ao seu NAS" + }, + "description": "Voc\u00ea quer configurar o {name} ({host})?", + "title": "Synology DSM" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Minutos entre os escaneamentos" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tado/translations/hu.json b/homeassistant/components/tado/translations/hu.json new file mode 100644 index 00000000000..dee4ed9ee0f --- /dev/null +++ b/homeassistant/components/tado/translations/hu.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tado/translations/pt-BR.json b/homeassistant/components/tado/translations/pt-BR.json new file mode 100644 index 00000000000..af32cb3c3a6 --- /dev/null +++ b/homeassistant/components/tado/translations/pt-BR.json @@ -0,0 +1,28 @@ +{ + "config": { + "abort": { + "already_configured": "O dispositivo j\u00e1 est\u00e1 configurado" + }, + "error": { + "cannot_connect": "Falha ao conectar, tente novamente", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida", + "no_homes": "N\u00e3o h\u00e1 casas vinculadas a esta conta Tado.", + "unknown": "Erro inesperado" + }, + "step": { + "user": { + "title": "Conecte-se \u00e0 sua conta Tado" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "fallback": "Ative o modo de fallback." + }, + "title": "Ajuste as op\u00e7\u00f5es do Tado." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tellduslive/translations/ko.json b/homeassistant/components/tellduslive/translations/ko.json index fa3cb937baf..3430b256dda 100644 --- a/homeassistant/components/tellduslive/translations/ko.json +++ b/homeassistant/components/tellduslive/translations/ko.json @@ -11,7 +11,7 @@ }, "step": { "auth": { - "description": "TelldusLive \uacc4\uc815\uc744 \uc5f0\uacb0\ud558\ub824\uba74:\n 1. \ud558\ub2e8\uc758 \ub9c1\ud06c\ub97c \ud074\ub9ad\ud574\uc8fc\uc138\uc694\n 2. Telldus Live \uc5d0 \ub85c\uadf8\uc778 \ud558\uc138\uc694\n 3. Authorize **{app_name}** (**Yes** \ub97c \ud074\ub9ad\ud558\uc138\uc694).\n 4. \ub2e4\uc2dc \uc5ec\uae30\ub85c \ub3cc\uc544\uc640\uc11c **SUBMIT** \uc744 \ud074\ub9ad\ud558\uc138\uc694.\n\n [TelldusLive \uacc4\uc815 \uc5f0\uacb0\ud558\uae30]({auth_url})", + "description": "TelldusLive \uacc4\uc815\uc744 \uc5f0\uacb0\ud558\ub824\uba74:\n 1. \ud558\ub2e8\uc758 \ub9c1\ud06c\ub97c \ud074\ub9ad\ud574\uc8fc\uc138\uc694\n 2. Telldus Live \uc5d0 \ub85c\uadf8\uc778 \ud558\uc138\uc694\n 3. Authorize **{app_name}** (**Yes** \ub97c \ud074\ub9ad\ud558\uc138\uc694).\n 4. \ub2e4\uc2dc \uc5ec\uae30\ub85c \ub3cc\uc544\uc640\uc11c **\ud655\uc778**\uc744 \ud074\ub9ad\ud558\uc138\uc694.\n\n [TelldusLive \uacc4\uc815 \uc5f0\uacb0\ud558\uae30]({auth_url})", "title": "TelldusLive \uc778\uc99d\ud558\uae30" }, "user": { diff --git a/homeassistant/components/tesla/translations/hu.json b/homeassistant/components/tesla/translations/hu.json index 0b9dbbf06a7..1e8efeec1a1 100644 --- a/homeassistant/components/tesla/translations/hu.json +++ b/homeassistant/components/tesla/translations/hu.json @@ -10,7 +10,7 @@ "user": { "data": { "password": "Jelsz\u00f3", - "username": "Email c\u00edm" + "username": "E-mail" }, "description": "K\u00e9rlek, add meg az adataidat.", "title": "Tesla - Konfigur\u00e1ci\u00f3" diff --git a/homeassistant/components/tesla/translations/pt-BR.json b/homeassistant/components/tesla/translations/pt-BR.json index c06b4fabf99..7c0a910ca9a 100644 --- a/homeassistant/components/tesla/translations/pt-BR.json +++ b/homeassistant/components/tesla/translations/pt-BR.json @@ -16,5 +16,14 @@ "title": "Tesla - Configura\u00e7\u00e3o" } } + }, + "options": { + "step": { + "init": { + "data": { + "enable_wake_on_start": "For\u00e7ar carros a acordar na inicializa\u00e7\u00e3o" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/tibber/translations/hu.json b/homeassistant/components/tibber/translations/hu.json new file mode 100644 index 00000000000..0b0581d4923 --- /dev/null +++ b/homeassistant/components/tibber/translations/hu.json @@ -0,0 +1,14 @@ +{ + "config": { + "error": { + "invalid_access_token": "\u00c9rv\u00e9nytelen hozz\u00e1f\u00e9r\u00e9si token" + }, + "step": { + "user": { + "data": { + "access_token": "Hozz\u00e1f\u00e9r\u00e9si token" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tibber/translations/ko.json b/homeassistant/components/tibber/translations/ko.json index 92b777e35bb..6229d2fd138 100644 --- a/homeassistant/components/tibber/translations/ko.json +++ b/homeassistant/components/tibber/translations/ko.json @@ -13,7 +13,7 @@ "data": { "access_token": "\uc561\uc138\uc2a4 \ud1a0\ud070" }, - "description": "https://developer.tibber.com/settings/accesstoken \uc5d0\uc11c \uc561\uc138\uc2a4 \ud1a0\ud070\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694", + "description": "https://developer.tibber.com/settings/accesstoken \uc5d0\uc11c \uc0dd\uc131\ud55c \uc561\uc138\uc2a4 \ud1a0\ud070\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694", "title": "Tibber" } } diff --git a/homeassistant/components/toon/translations/ko.json b/homeassistant/components/toon/translations/ko.json index 200eb2d810b..7bbde56dbe1 100644 --- a/homeassistant/components/toon/translations/ko.json +++ b/homeassistant/components/toon/translations/ko.json @@ -2,7 +2,7 @@ "config": { "abort": { "client_id": "\ud074\ub77c\uc774\uc5b8\ud2b8 ID \uac00 \uc720\ud6a8\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4.", - "client_secret": "\ud074\ub77c\uc774\uc5b8\ud2b8 \ube44\ubc00\ubc88\ud638\uac00 \uc720\ud6a8\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4.", + "client_secret": "\ud074\ub77c\uc774\uc5b8\ud2b8 \uc2dc\ud06c\ub9bf\uc774 \uc720\ud6a8\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4.", "no_agreements": "\uc774 \uacc4\uc815\uc5d0\ub294 Toon \ub514\uc2a4\ud50c\ub808\uc774\uac00 \uc5c6\uc2b5\ub2c8\ub2e4.", "no_app": "Toon \uc744 \uc778\uc99d\ud558\ub824\uba74 \uba3c\uc800 Toon \uc744 \uad6c\uc131\ud574\uc57c \ud569\ub2c8\ub2e4. [\uc548\ub0b4](https://www.home-assistant.io/components/toon/) \ub97c \uc77d\uc5b4\ubcf4\uc138\uc694.", "unknown_auth_fail": "\uc778\uc99d\ud558\ub294 \ub3d9\uc548 \uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4." diff --git a/homeassistant/components/totalconnect/translations/hu.json b/homeassistant/components/totalconnect/translations/hu.json new file mode 100644 index 00000000000..dee4ed9ee0f --- /dev/null +++ b/homeassistant/components/totalconnect/translations/hu.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/totalconnect/translations/pt-BR.json b/homeassistant/components/totalconnect/translations/pt-BR.json new file mode 100644 index 00000000000..30b433108ec --- /dev/null +++ b/homeassistant/components/totalconnect/translations/pt-BR.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "already_configured": "Conta j\u00e1 configurada" + }, + "error": { + "login": "Erro de login: verifique seu nome de usu\u00e1rio e senha" + }, + "step": { + "user": { + "title": "Total Connect" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/transmission/translations/hu.json b/homeassistant/components/transmission/translations/hu.json index fd9bb9e64cb..79fdbe9c7f0 100644 --- a/homeassistant/components/transmission/translations/hu.json +++ b/homeassistant/components/transmission/translations/hu.json @@ -8,7 +8,7 @@ "step": { "user": { "data": { - "host": "Kiszolg\u00e1l\u00f3", + "host": "Hoszt", "name": "N\u00e9v", "password": "Jelsz\u00f3", "port": "Port", diff --git a/homeassistant/components/tuya/translations/hu.json b/homeassistant/components/tuya/translations/hu.json new file mode 100644 index 00000000000..69681ee800a --- /dev/null +++ b/homeassistant/components/tuya/translations/hu.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "auth_failed": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "conn_error": "Sikertelen csatlakoz\u00e1s", + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + }, + "error": { + "auth_failed": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s" + }, + "step": { + "user": { + "data": { + "password": "Jelsz\u00f3", + "username": "Felhaszn\u00e1l\u00f3n\u00e9v" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tuya/translations/pt-BR.json b/homeassistant/components/tuya/translations/pt-BR.json index 0eb07ce346d..7ba1ce6967c 100644 --- a/homeassistant/components/tuya/translations/pt-BR.json +++ b/homeassistant/components/tuya/translations/pt-BR.json @@ -1,8 +1,13 @@ { "config": { + "abort": { + "already_in_progress": "A configura\u00e7\u00e3o Tuya j\u00e1 est\u00e1 em andamento." + }, + "flow_title": "Configura\u00e7\u00e3o Tuya", "step": { "user": { "data": { + "country_code": "O c\u00f3digo do pa\u00eds da sua conta (por exemplo, 1 para os EUA ou 86 para a China)", "password": "Senha", "platform": "O aplicativo onde sua conta \u00e9 registrada", "username": "Nome de usu\u00e1rio" diff --git a/homeassistant/components/unifi/translations/hu.json b/homeassistant/components/unifi/translations/hu.json index 49bab5225d9..87055634ce0 100644 --- a/homeassistant/components/unifi/translations/hu.json +++ b/homeassistant/components/unifi/translations/hu.json @@ -4,8 +4,8 @@ "user_privilege": "A felhaszn\u00e1l\u00f3nak rendszergazd\u00e1nak kell lennie" }, "error": { - "faulty_credentials": "Rossz felhaszn\u00e1l\u00f3i hiteles\u00edt\u0151 adatok", - "service_unavailable": "Nincs el\u00e9rhet\u0151 szolg\u00e1ltat\u00e1s" + "faulty_credentials": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", + "service_unavailable": "Sikertelen csatlakoz\u00e1s" }, "step": { "user": { diff --git a/homeassistant/components/unifi/translations/pt-BR.json b/homeassistant/components/unifi/translations/pt-BR.json index da3368af5b4..9816086d75a 100644 --- a/homeassistant/components/unifi/translations/pt-BR.json +++ b/homeassistant/components/unifi/translations/pt-BR.json @@ -6,7 +6,8 @@ }, "error": { "faulty_credentials": "Credenciais do usu\u00e1rio inv\u00e1lidas", - "service_unavailable": "Servi\u00e7o indispon\u00edvel" + "service_unavailable": "Servi\u00e7o indispon\u00edvel", + "unknown_client_mac": "Nenhum cliente dispon\u00edvel nesse endere\u00e7o MAC" }, "step": { "user": { @@ -24,6 +25,14 @@ }, "options": { "step": { + "client_control": { + "data": { + "block_client": "Clientes com acesso controlado \u00e0 rede", + "new_client": "Adicionar novo cliente para controle de acesso \u00e0 rede" + }, + "description": "Configurar controles do cliente \n\nCrie comutadores para os n\u00fameros de s\u00e9rie para os quais deseja controlar o acesso \u00e0 rede.", + "title": "UniFi op\u00e7\u00f5es de 2/3" + }, "device_tracker": { "data": { "detection_time": "Tempo em segundos desde a \u00faltima vez que foi visto at\u00e9 ser considerado afastado", diff --git a/homeassistant/components/upnp/translations/pt-BR.json b/homeassistant/components/upnp/translations/pt-BR.json index 07e81226dd2..a69a18570ea 100644 --- a/homeassistant/components/upnp/translations/pt-BR.json +++ b/homeassistant/components/upnp/translations/pt-BR.json @@ -19,6 +19,7 @@ "enable_port_mapping": "Ativar o mapeamento de porta para o Home Assistant", "enable_sensors": "Adicionar sensores de tr\u00e1fego", "igd": "UPnP/IGD", + "scan_interval": "Intervalo de atualiza\u00e7\u00e3o (segundos, m\u00ednimo 30)", "usn": "Dispositivo" }, "title": "Op\u00e7\u00f5es de configura\u00e7\u00e3o para o UPnP/IGD" diff --git a/homeassistant/components/vesync/translations/hu.json b/homeassistant/components/vesync/translations/hu.json index 4735140216f..ac1da8b714d 100644 --- a/homeassistant/components/vesync/translations/hu.json +++ b/homeassistant/components/vesync/translations/hu.json @@ -7,7 +7,7 @@ "user": { "data": { "password": "Jelsz\u00f3", - "username": "Email c\u00edm" + "username": "E-mail" }, "title": "\u00cdrja be a felhaszn\u00e1l\u00f3nevet \u00e9s a jelsz\u00f3t" } diff --git a/homeassistant/components/vesync/translations/pt-BR.json b/homeassistant/components/vesync/translations/pt-BR.json new file mode 100644 index 00000000000..fada7da0ac7 --- /dev/null +++ b/homeassistant/components/vesync/translations/pt-BR.json @@ -0,0 +1,12 @@ +{ + "config": { + "error": { + "invalid_login": "Usu\u00e1rio ou senha inv\u00e1lidos" + }, + "step": { + "user": { + "title": "Digite o nome de usu\u00e1rio e a senha" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vilfo/translations/hu.json b/homeassistant/components/vilfo/translations/hu.json index 0368349f75a..a75149507fc 100644 --- a/homeassistant/components/vilfo/translations/hu.json +++ b/homeassistant/components/vilfo/translations/hu.json @@ -3,7 +3,8 @@ "step": { "user": { "data": { - "host": "Router hostname vagy IP" + "access_token": "Hozz\u00e1f\u00e9r\u00e9si token", + "host": "Hoszt" }, "title": "Csatlakoz\u00e1s a Vilfo routerhez" } diff --git a/homeassistant/components/vilfo/translations/pt-BR.json b/homeassistant/components/vilfo/translations/pt-BR.json new file mode 100644 index 00000000000..3105455cb8b --- /dev/null +++ b/homeassistant/components/vilfo/translations/pt-BR.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "already_configured": "Este roteador Vilfo j\u00e1 est\u00e1 configurado." + }, + "error": { + "cannot_connect": "Falha ao conectar. Por favor, verifique as informa\u00e7\u00f5es fornecidas por voc\u00ea e tente novamente.", + "invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida. Verifique o token de acesso e tente novamente.", + "unknown": "Ocorreu um erro inesperado ao configurar a integra\u00e7\u00e3o." + }, + "step": { + "user": { + "title": "Conecte-se ao roteador Vilfo" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/vizio/translations/hu.json b/homeassistant/components/vizio/translations/hu.json index 37ef8b3740f..8dadb52211f 100644 --- a/homeassistant/components/vizio/translations/hu.json +++ b/homeassistant/components/vizio/translations/hu.json @@ -5,7 +5,7 @@ "updated_entry": "Ez a bejegyz\u00e9s m\u00e1r be van \u00e1ll\u00edtva, de a konfigur\u00e1ci\u00f3ban defini\u00e1lt n\u00e9v, appok \u00e9s/vagy be\u00e1ll\u00edt\u00e1sok nem egyeznek meg a kor\u00e1bban import\u00e1lt konfigur\u00e1ci\u00f3val, \u00edgy a konfigur\u00e1ci\u00f3s bejegyz\u00e9s ennek megfelel\u0151en friss\u00fclt." }, "error": { - "cant_connect": "Nem lehetett csatlakozni az eszk\u00f6zh\u00f6z. [Tekintsd \u00e1t a dokumentumokat] (https://www.home-assistant.io/integrations/vizio/) \u00e9s \u00fajra ellen\u0151rizd, hogy:\n- A k\u00e9sz\u00fcl\u00e9k be van kapcsolva\n- A k\u00e9sz\u00fcl\u00e9k csatlakozik a h\u00e1l\u00f3zathoz\n- A kit\u00f6lt\u00f6tt \u00e9rt\u00e9kek pontosak\nmiel\u0151tt \u00fajra elk\u00fclden\u00e9d.", + "cant_connect": "Sikertelen csatlakoz\u00e1s", "host_exists": "A megadott kiszolg\u00e1l\u00f3n\u00e9vvel rendelkez\u0151 Vizio-eszk\u00f6z m\u00e1r konfigur\u00e1lva van.", "name_exists": "A megadott n\u00e9vvel rendelkez\u0151 Vizio-eszk\u00f6z m\u00e1r konfigur\u00e1lva van." }, @@ -14,6 +14,7 @@ "data": { "access_token": "Hozz\u00e1f\u00e9r\u00e9si token", "device_class": "Eszk\u00f6zt\u00edpus", + "host": "Hoszt", "name": "N\u00e9v" }, "title": "A Vizio SmartCast Client be\u00e1ll\u00edt\u00e1sa" diff --git a/homeassistant/components/vizio/translations/ko.json b/homeassistant/components/vizio/translations/ko.json index db690798908..8b1a667b9e8 100644 --- a/homeassistant/components/vizio/translations/ko.json +++ b/homeassistant/components/vizio/translations/ko.json @@ -16,7 +16,7 @@ "data": { "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.", + "description": "TV \uc5d0 \ucf54\ub4dc\uac00 \ud45c\uc2dc\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud574\ub2f9 \ucf54\ub4dc\ub97c \uc785\ub825\ub780\uc5d0 \uc785\ub825\ud55c \ud6c4 \ub2e4\uc74c \ub2e8\uacc4\ub97c \uacc4\uc18d\ud558\uc5ec \ud398\uc5b4\ub9c1\uc744 \uc644\ub8cc\ud574\uc8fc\uc138\uc694.", "title": "\ud398\uc5b4\ub9c1 \uacfc\uc815 \ub05d\ub0b4\uae30" }, "pairing_complete": { @@ -43,7 +43,7 @@ "step": { "init": { "data": { - "apps_to_include_or_exclude": "\ud3ec\ud568 \ub610\ub294 \uc81c\uc678 \ud560 \uc571", + "apps_to_include_or_exclude": "\ud3ec\ud568 \ub610\ub294 \uc81c\uc678\ud560 \uc571", "include_or_exclude": "\uc571\uc744 \ud3ec\ud568 \ub610\ub294 \uc81c\uc678\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", "volume_step": "\ubcfc\ub968 \ub2e8\uacc4 \ud06c\uae30" }, diff --git a/homeassistant/components/vizio/translations/pt-BR.json b/homeassistant/components/vizio/translations/pt-BR.json index 6dcce7df8b5..78f72cd05eb 100644 --- a/homeassistant/components/vizio/translations/pt-BR.json +++ b/homeassistant/components/vizio/translations/pt-BR.json @@ -1,7 +1,25 @@ { "config": { "error": { + "complete_pairing failed": "N\u00e3o foi poss\u00edvel concluir o pareamento. Verifique se o PIN que voc\u00ea forneceu est\u00e1 correto e a TV ainda est\u00e1 ligada e conectada \u00e0 internet antes de reenviar.", "complete_pairing_failed": "N\u00e3o foi poss\u00edvel concluir o pareamento. Verifique se o PIN que voc\u00ea forneceu est\u00e1 correto e a TV ainda est\u00e1 ligada e conectada \u00e0 internet antes de reenviar." + }, + "step": { + "pair_tv": { + "data": { + "pin": "PIN" + }, + "description": "Sua TV deve estar exibindo um c\u00f3digo. Digite esse c\u00f3digo no formul\u00e1rio e continue na pr\u00f3xima etapa para concluir o pareamento.", + "title": "Processo de pareamento completo" + }, + "pairing_complete": { + "title": "Pareamento completo" + }, + "user": { + "data": { + "device_class": "Tipo de dispositivo" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/withings/translations/pt-BR.json b/homeassistant/components/withings/translations/pt-BR.json new file mode 100644 index 00000000000..f87b8b64576 --- /dev/null +++ b/homeassistant/components/withings/translations/pt-BR.json @@ -0,0 +1,7 @@ +{ + "config": { + "create_entry": { + "default": "Autenticado com sucesso no Withings." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wled/translations/hu.json b/homeassistant/components/wled/translations/hu.json index 5ffd902214e..3565db0376c 100644 --- a/homeassistant/components/wled/translations/hu.json +++ b/homeassistant/components/wled/translations/hu.json @@ -11,7 +11,7 @@ "step": { "user": { "data": { - "host": "Hosztn\u00e9v vagy IP c\u00edm" + "host": "Hoszt" }, "description": "\u00c1ll\u00edtsd be a WLED-et a Home Assistant-ba val\u00f3 integr\u00e1l\u00e1shoz.", "title": "Csatlakoztasd a WLED-et" diff --git a/homeassistant/components/wwlln/translations/pt-BR.json b/homeassistant/components/wwlln/translations/pt-BR.json index 9119d281dd5..3c64c1534a1 100644 --- a/homeassistant/components/wwlln/translations/pt-BR.json +++ b/homeassistant/components/wwlln/translations/pt-BR.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "Este local j\u00e1 est\u00e1 registrado." + }, "step": { "user": { "data": { diff --git a/homeassistant/components/xiaomi_miio/translations/fr.json b/homeassistant/components/xiaomi_miio/translations/fr.json index bf00d30bc6e..c494456fc68 100644 --- a/homeassistant/components/xiaomi_miio/translations/fr.json +++ b/homeassistant/components/xiaomi_miio/translations/fr.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_in_progress": "Le flux de configuration pour cet appareil Xiaomi Miio est d\u00e9j\u00e0 en cours." + }, "error": { "connect_error": "Impossible de se connecter, veuillez r\u00e9essayer", "no_device_selected": "Aucun appareil s\u00e9lectionn\u00e9, veuillez s\u00e9lectionner un appareil." diff --git a/homeassistant/components/xiaomi_miio/translations/hu.json b/homeassistant/components/xiaomi_miio/translations/hu.json index 9cdda6533b5..1cb7833427e 100644 --- a/homeassistant/components/xiaomi_miio/translations/hu.json +++ b/homeassistant/components/xiaomi_miio/translations/hu.json @@ -1,13 +1,16 @@ { "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, "error": { - "connect_error": "Nem siker\u00fclt csatlakozni, pr\u00f3b\u00e1lkozzon \u00fajra.", + "connect_error": "Sikertelen csatlakoz\u00e1s", "no_device_selected": "Nincs kiv\u00e1lasztva eszk\u00f6z, k\u00e9rj\u00fck, v\u00e1lasszon egyet." }, "step": { "gateway": { "data": { - "host": "IP-c\u00edm" + "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" }, diff --git a/homeassistant/components/xiaomi_miio/translations/ko.json b/homeassistant/components/xiaomi_miio/translations/ko.json index 158ed8c52b2..3a1e6574915 100644 --- a/homeassistant/components/xiaomi_miio/translations/ko.json +++ b/homeassistant/components/xiaomi_miio/translations/ko.json @@ -15,7 +15,7 @@ "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.", + "description": "API \ud1a0\ud070\uc774 \ud544\uc694\ud569\ub2c8\ub2e4. [\uc548\ub0b4](https://www.home-assistant.io/integrations/vacuum.xiaomi_miio/#retrieving-the-access-token) \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694.", "title": "Xiaomi \uac8c\uc774\ud2b8\uc6e8\uc774\uc5d0 \uc5f0\uacb0\ud558\uae30" }, "user": { diff --git a/homeassistant/components/zerproc/translations/hu.json b/homeassistant/components/zerproc/translations/hu.json new file mode 100644 index 00000000000..6c61530acbe --- /dev/null +++ b/homeassistant/components/zerproc/translations/hu.json @@ -0,0 +1,13 @@ +{ + "config": { + "abort": { + "no_devices_found": "Nem tal\u00e1lhat\u00f3 eszk\u00f6z a h\u00e1l\u00f3zaton", + "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." + }, + "step": { + "confirm": { + "description": "El szeretn\u00e9d kezdeni a be\u00e1ll\u00edt\u00e1st?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zha/translations/hu.json b/homeassistant/components/zha/translations/hu.json index 4f9e9796925..36f1cf0ceed 100644 --- a/homeassistant/components/zha/translations/hu.json +++ b/homeassistant/components/zha/translations/hu.json @@ -15,7 +15,8 @@ }, "user": { "data": { - "radio_type": "R\u00e1di\u00f3 t\u00edpusa" + "radio_type": "R\u00e1di\u00f3 t\u00edpusa", + "usb_path": "USB eszk\u00f6z el\u00e9r\u00e9si \u00fat" }, "title": "ZHA" } diff --git a/homeassistant/components/zha/translations/pt-BR.json b/homeassistant/components/zha/translations/pt-BR.json index 6b866a93971..3f5c67ae038 100644 --- a/homeassistant/components/zha/translations/pt-BR.json +++ b/homeassistant/components/zha/translations/pt-BR.json @@ -7,7 +7,17 @@ "cannot_connect": "N\u00e3o \u00e9 poss\u00edvel conectar-se ao dispositivo ZHA." }, "step": { + "pick_radio": { + "data": { + "radio_type": "Tipo de r\u00e1dio" + }, + "description": "Escolha o tipo de seu r\u00e1dio Zigbee", + "title": "Tipo de r\u00e1dio" + }, "port_config": { + "data": { + "baudrate": "velocidade da porta" + }, "title": "Configura\u00e7\u00f5es" }, "user": { diff --git a/homeassistant/components/zwave/translations/hu.json b/homeassistant/components/zwave/translations/hu.json index 72026949c78..b443a30bada 100644 --- a/homeassistant/components/zwave/translations/hu.json +++ b/homeassistant/components/zwave/translations/hu.json @@ -11,7 +11,7 @@ "user": { "data": { "network_key": "H\u00e1l\u00f3zati kulcs (hagyja \u00fcresen az automatikus gener\u00e1l\u00e1shoz)", - "usb_path": "USB el\u00e9r\u00e9si \u00fat" + "usb_path": "USB eszk\u00f6z el\u00e9r\u00e9si \u00fat" }, "description": "A konfigur\u00e1ci\u00f3s v\u00e1ltoz\u00f3kr\u00f3l az inform\u00e1ci\u00f3kat l\u00e1sd a https://www.home-assistant.io/docs/z-wave/installation/ oldalon.", "title": "Z-Wave be\u00e1ll\u00edt\u00e1sa" From d02bb70f0cf50c70f576e1b58679c00e6097660a Mon Sep 17 00:00:00 2001 From: Carlos Giraldo Date: Sun, 17 May 2020 09:57:24 +0200 Subject: [PATCH 054/406] deCONZ - Add support for "Window covering controller" (#35294) * Add support for "Window covering controller" Some window cover devices such as Sunricher SR-ZG9080A are identified in deconz as "Window covering controller". We need to include this device type in const.py to be integrated as type "cover" in home-assistant. * Added Window covering controller to deconz tests --- homeassistant/components/deconz/const.py | 2 +- tests/components/deconz/test_cover.py | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/deconz/const.py b/homeassistant/components/deconz/const.py index cd125613f21..c2190321fdf 100644 --- a/homeassistant/components/deconz/const.py +++ b/homeassistant/components/deconz/const.py @@ -37,7 +37,7 @@ ATTR_ON = "on" ATTR_VALVE = "valve" DAMPERS = ["Level controllable output"] -WINDOW_COVERS = ["Window covering device"] +WINDOW_COVERS = ["Window covering device", "Window covering controller"] COVER_TYPES = DAMPERS + WINDOW_COVERS POWER_PLUGS = ["On/Off light", "On/Off plug-in unit", "Smart plug"] diff --git a/tests/components/deconz/test_cover.py b/tests/components/deconz/test_cover.py index 4f45e36c5b1..095ae7e4bc5 100644 --- a/tests/components/deconz/test_cover.py +++ b/tests/components/deconz/test_cover.py @@ -41,6 +41,14 @@ COVERS = { "modelid": "Not zigbee spec", "uniqueid": "00:00:00:00:00:00:00:03-00", }, + "5": { + "id": "Window covering controller id", + "name": "Window covering controller", + "type": "Window covering controller", + "state": {"bri": 254, "on": True, "reachable": True}, + "modelid": "Motor controller", + "uniqueid": "00:00:00:00:00:00:00:04-00", + }, } @@ -71,7 +79,8 @@ async def test_cover(hass): assert "cover.window_covering_device" in gateway.deconz_ids assert "cover.unsupported_cover" not in gateway.deconz_ids assert "cover.deconz_old_brightness_cover" in gateway.deconz_ids - assert len(hass.states.async_all()) == 4 + assert "cover.window_covering_controller" in gateway.deconz_ids + assert len(hass.states.async_all()) == 5 level_controllable_cover = hass.states.get("cover.level_controllable_cover") assert level_controllable_cover.state == "open" From 51eebb3906008bf93c1a6996fb51a41e54dae7d0 Mon Sep 17 00:00:00 2001 From: Steffen Zimmermann Date: Sun, 17 May 2020 10:31:28 +0200 Subject: [PATCH 055/406] Add config option to set timeout for wiffi devices (#35694) * add config option to set timeout for wiffi devices Wiffi devices allow to configure the update period (= full_loop_minutes). The integration shall respect the configured update period and therefore need a configuration for the timeout, too. * Move timeout from config flow to option flow * add test for option flow --- homeassistant/components/wiffi/__init__.py | 16 ++++++-- .../components/wiffi/binary_sensor.py | 6 +-- homeassistant/components/wiffi/config_flow.py | 41 ++++++++++++++++++- homeassistant/components/wiffi/const.py | 3 ++ homeassistant/components/wiffi/sensor.py | 12 +++--- homeassistant/components/wiffi/strings.json | 9 ++++ .../components/wiffi/translations/en.json | 9 ++++ tests/components/wiffi/test_config_flow.py | 35 +++++++++++++--- 8 files changed, 112 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/wiffi/__init__.py b/homeassistant/components/wiffi/__init__.py index 05d0d381e18..000b961bda9 100644 --- a/homeassistant/components/wiffi/__init__.py +++ b/homeassistant/components/wiffi/__init__.py @@ -7,7 +7,7 @@ import logging from wiffi import WiffiTcpServer from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_PORT +from homeassistant.const import CONF_PORT, CONF_TIMEOUT from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import device_registry @@ -22,6 +22,7 @@ from homeassistant.util.dt import utcnow from .const import ( CHECK_ENTITIES_SIGNAL, CREATE_ENTITY_SIGNAL, + DEFAULT_TIMEOUT, DOMAIN, UPDATE_ENTITY_SIGNAL, ) @@ -39,6 +40,9 @@ async def async_setup(hass: HomeAssistant, config: dict): async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry): """Set up wiffi from a config entry, config_entry contains data from config entry database.""" + if not config_entry.update_listeners: + config_entry.add_update_listener(async_update_options) + # create api object api = WiffiIntegrationApi(hass) api.async_setup(config_entry) @@ -63,6 +67,11 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry): return True +async def async_update_options(hass: HomeAssistant, config_entry: ConfigEntry): + """Update options.""" + await hass.config_entries.async_reload(config_entry.entry_id) + + async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry): """Unload a config entry.""" api: "WiffiIntegrationApi" = hass.data[DOMAIN][config_entry.entry_id] @@ -146,7 +155,7 @@ class WiffiIntegrationApi: class WiffiEntity(Entity): """Common functionality for all wiffi entities.""" - def __init__(self, device, metric): + def __init__(self, device, metric, options): """Initialize the base elements of a wiffi entity.""" self._id = generate_unique_id(device, metric) self._device_info = { @@ -162,6 +171,7 @@ class WiffiEntity(Entity): self._name = metric.description self._expiration_date = None self._value = None + self._timeout = options.get(CONF_TIMEOUT, DEFAULT_TIMEOUT) async def async_added_to_hass(self): """Entity has been added to hass.""" @@ -208,7 +218,7 @@ class WiffiEntity(Entity): Will be called by derived classes after a value update has been received. """ - self._expiration_date = utcnow() + timedelta(minutes=3) + self._expiration_date = utcnow() + timedelta(minutes=self._timeout) @callback def _update_value_callback(self, device, metric): diff --git a/homeassistant/components/wiffi/binary_sensor.py b/homeassistant/components/wiffi/binary_sensor.py index 009fc2b4a67..f6063b3c202 100644 --- a/homeassistant/components/wiffi/binary_sensor.py +++ b/homeassistant/components/wiffi/binary_sensor.py @@ -21,7 +21,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): entities = [] if metric.is_bool: - entities.append(BoolEntity(device, metric)) + entities.append(BoolEntity(device, metric, config_entry.options)) async_add_entities(entities) @@ -31,9 +31,9 @@ async def async_setup_entry(hass, config_entry, async_add_entities): class BoolEntity(WiffiEntity, BinarySensorEntity): """Entity for wiffi metrics which have a boolean value.""" - def __init__(self, device, metric): + def __init__(self, device, metric, options): """Initialize the entity.""" - super().__init__(device, metric) + super().__init__(device, metric, options) self._value = metric.value self.reset_expiration_date() diff --git a/homeassistant/components/wiffi/config_flow.py b/homeassistant/components/wiffi/config_flow.py index 82dbbb040ef..f30ee8792df 100644 --- a/homeassistant/components/wiffi/config_flow.py +++ b/homeassistant/components/wiffi/config_flow.py @@ -8,10 +8,14 @@ import voluptuous as vol from wiffi import WiffiTcpServer from homeassistant import config_entries -from homeassistant.const import CONF_PORT +from homeassistant.const import CONF_PORT, CONF_TIMEOUT from homeassistant.core import callback -from .const import DEFAULT_PORT, DOMAIN # pylint: disable=unused-import +from .const import ( # pylint: disable=unused-import + DEFAULT_PORT, + DEFAULT_TIMEOUT, + DOMAIN, +) class WiffiFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): @@ -20,6 +24,12 @@ class WiffiFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH + @staticmethod + @callback + def async_get_options_flow(config_entry): + """Create Wiffi server setup option flow.""" + return OptionsFlowHandler(config_entry) + async def async_step_user(self, user_input=None): """Handle the start of the config flow. @@ -55,3 +65,30 @@ class WiffiFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): return self.async_show_form( step_id="user", data_schema=vol.Schema(data_schema), errors=errors or {} ) + + +class OptionsFlowHandler(config_entries.OptionsFlow): + """Wiffi server setup option flow.""" + + def __init__(self, config_entry): + """Initialize options flow.""" + self.config_entry = config_entry + + async def async_step_init(self, user_input=None): + """Manage the options.""" + if user_input is not None: + return self.async_create_entry(title="", data=user_input) + + return self.async_show_form( + step_id="init", + data_schema=vol.Schema( + { + vol.Optional( + CONF_TIMEOUT, + default=self.config_entry.options.get( + CONF_TIMEOUT, DEFAULT_TIMEOUT + ), + ): int, + } + ), + ) diff --git a/homeassistant/components/wiffi/const.py b/homeassistant/components/wiffi/const.py index 6b71c89002f..584ab9899b6 100644 --- a/homeassistant/components/wiffi/const.py +++ b/homeassistant/components/wiffi/const.py @@ -6,6 +6,9 @@ DOMAIN = "wiffi" # Default port for TCP server DEFAULT_PORT = 8189 +# Default timeout in minutes +DEFAULT_TIMEOUT = 3 + # Signal name to send create/update to platform (sensor/binary_sensor) CREATE_ENTITY_SIGNAL = "wiffi_create_entity_signal" UPDATE_ENTITY_SIGNAL = "wiffi_update_entity_signal" diff --git a/homeassistant/components/wiffi/sensor.py b/homeassistant/components/wiffi/sensor.py index cc6befaf067..f207e3be3ac 100644 --- a/homeassistant/components/wiffi/sensor.py +++ b/homeassistant/components/wiffi/sensor.py @@ -49,9 +49,9 @@ async def async_setup_entry(hass, config_entry, async_add_entities): entities = [] if metric.is_number: - entities.append(NumberEntity(device, metric)) + entities.append(NumberEntity(device, metric, config_entry.options)) elif metric.is_string: - entities.append(StringEntity(device, metric)) + entities.append(StringEntity(device, metric, config_entry.options)) async_add_entities(entities) @@ -61,9 +61,9 @@ async def async_setup_entry(hass, config_entry, async_add_entities): class NumberEntity(WiffiEntity): """Entity for wiffi metrics which have a number value.""" - def __init__(self, device, metric): + def __init__(self, device, metric, options): """Initialize the entity.""" - super().__init__(device, metric) + super().__init__(device, metric, options) self._device_class = UOM_TO_DEVICE_CLASS_MAP.get(metric.unit_of_measurement) self._unit_of_measurement = UOM_MAP.get( metric.unit_of_measurement, metric.unit_of_measurement @@ -103,9 +103,9 @@ class NumberEntity(WiffiEntity): class StringEntity(WiffiEntity): """Entity for wiffi metrics which have a string value.""" - def __init__(self, device, metric): + def __init__(self, device, metric, options): """Initialize the entity.""" - super().__init__(device, metric) + super().__init__(device, metric, options) self._value = metric.value self.reset_expiration_date() diff --git a/homeassistant/components/wiffi/strings.json b/homeassistant/components/wiffi/strings.json index 36f836366a5..e219b2ecae7 100644 --- a/homeassistant/components/wiffi/strings.json +++ b/homeassistant/components/wiffi/strings.json @@ -12,5 +12,14 @@ "addr_in_use": "Server port already in use.", "start_server_failed": "Start server failed." } + }, + "options": { + "step": { + "init": { + "data": { + "timeout": "Timeout (minutes)" + } + } + } } } diff --git a/homeassistant/components/wiffi/translations/en.json b/homeassistant/components/wiffi/translations/en.json index bcaf0820bd5..0ac1868714d 100644 --- a/homeassistant/components/wiffi/translations/en.json +++ b/homeassistant/components/wiffi/translations/en.json @@ -12,5 +12,14 @@ "title": "Setup TCP server for WIFFI devices" } } + }, + "options": { + "step": { + "init": { + "data": { + "timeout": "Timeout (minutes)" + } + } + } } } \ No newline at end of file diff --git a/tests/components/wiffi/test_config_flow.py b/tests/components/wiffi/test_config_flow.py index ef6ce528623..5c3e96eb959 100644 --- a/tests/components/wiffi/test_config_flow.py +++ b/tests/components/wiffi/test_config_flow.py @@ -4,15 +4,19 @@ import errno from asynctest import patch import pytest -from homeassistant import config_entries +from homeassistant import config_entries, data_entry_flow from homeassistant.components.wiffi.const import DOMAIN -from homeassistant.const import CONF_PORT +from homeassistant.const import CONF_PORT, CONF_TIMEOUT from homeassistant.data_entry_flow import ( RESULT_TYPE_ABORT, RESULT_TYPE_CREATE_ENTRY, RESULT_TYPE_FORM, ) +from tests.common import MockConfigEntry + +MOCK_CONFIG = {CONF_PORT: 8765} + @pytest.fixture(name="dummy_tcp_server") def mock_dummy_tcp_server(): @@ -78,7 +82,7 @@ async def test_form(hass, dummy_tcp_server): assert result["step_id"] == config_entries.SOURCE_USER result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], user_input={CONF_PORT: 8765}, + result["flow_id"], user_input=MOCK_CONFIG, ) assert result2["type"] == RESULT_TYPE_CREATE_ENTRY @@ -90,7 +94,7 @@ async def test_form_addr_in_use(hass, addr_in_use): ) result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], user_input={CONF_PORT: 8765}, + result["flow_id"], user_input=MOCK_CONFIG, ) assert result2["type"] == RESULT_TYPE_ABORT assert result2["reason"] == "addr_in_use" @@ -103,7 +107,28 @@ async def test_form_start_server_failed(hass, start_server_failed): ) result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], user_input={CONF_PORT: 8765}, + result["flow_id"], user_input=MOCK_CONFIG, ) assert result2["type"] == RESULT_TYPE_ABORT assert result2["reason"] == "start_server_failed" + + +async def test_option_flow(hass): + """Test option flow.""" + entry = MockConfigEntry(domain=DOMAIN, data=MOCK_CONFIG) + entry.add_to_hass(hass) + + assert not entry.options + + result = await hass.config_entries.options.async_init(entry.entry_id, data=None) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "init" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], user_input={CONF_TIMEOUT: 9} + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["title"] == "" + assert result["data"][CONF_TIMEOUT] == 9 From df830a50e71caa8adf6cc6c21bda0614fc434775 Mon Sep 17 00:00:00 2001 From: Joao Carreira Date: Sun, 17 May 2020 09:36:28 +0100 Subject: [PATCH 056/406] Add support for custom media_type in mediaroom (#34625) * Add support for custom media_type in mediaroom * Clean logging * Fix formatting Co-authored-by: Martin Hjelmare --- .../components/mediaroom/media_player.py | 28 ++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/mediaroom/media_player.py b/homeassistant/components/mediaroom/media_player.py index abbfa21761f..9c0812af6e5 100644 --- a/homeassistant/components/mediaroom/media_player.py +++ b/homeassistant/components/mediaroom/media_player.py @@ -1,7 +1,13 @@ """Support for the Mediaroom Set-up-box.""" import logging -from pymediaroom import PyMediaroomError, Remote, State, install_mediaroom_protocol +from pymediaroom import ( + COMMANDS, + PyMediaroomError, + Remote, + State, + install_mediaroom_protocol, +) import voluptuous as vol from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity @@ -41,6 +47,8 @@ DEFAULT_NAME = "Mediaroom STB" DEFAULT_TIMEOUT = 9 DISCOVERY_MEDIAROOM = "mediaroom_discovery_installed" +MEDIA_TYPE_MEDIAROOM = "mediaroom" + SIGNAL_STB_NOTIFY = "mediaroom_stb_discovered" SUPPORT_MEDIAROOM = ( SUPPORT_PAUSE @@ -190,15 +198,21 @@ class MediaroomDevice(MediaPlayerEntity): _LOGGER.debug( "STB(%s) Play media: %s (%s)", self.stb.stb_ip, media_id, media_type ) - if media_type != MEDIA_TYPE_CHANNEL: - _LOGGER.error("invalid media type") - return - if not media_id.isdigit(): - _LOGGER.error("media_id must be a channel number") + if media_type == MEDIA_TYPE_CHANNEL: + if not media_id.isdigit(): + _LOGGER.error("Invalid media_id %s: Must be a channel number", media_id) + return + media_id = int(media_id) + elif media_type == MEDIA_TYPE_MEDIAROOM: + if media_id not in COMMANDS: + _LOGGER.error("Invalid media_id %s: Must be a command", media_id) + return + else: + _LOGGER.error("Invalid media type %s", media_type) return try: - await self.stb.send_cmd(int(media_id)) + await self.stb.send_cmd(media_id) if self._optimistic: self._state = STATE_PLAYING self._available = True From 62aa16e6e32b17e1f9e4783d1a2430c1e24f024b Mon Sep 17 00:00:00 2001 From: Joakim Plate Date: Sun, 17 May 2020 12:05:21 +0200 Subject: [PATCH 057/406] Support config entry unload in arcam_fmj (#35656) * Support entry unload * Switch to create_task and skip checking for available task --- .../components/arcam_fmj/__init__.py | 49 +++++++++++++------ homeassistant/components/arcam_fmj/const.py | 1 + .../components/arcam_fmj/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 5 files changed, 39 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/arcam_fmj/__init__.py b/homeassistant/components/arcam_fmj/__init__.py index aa11e66d49c..008266e5a45 100644 --- a/homeassistant/components/arcam_fmj/__init__.py +++ b/homeassistant/components/arcam_fmj/__init__.py @@ -27,6 +27,7 @@ from .const import ( DOMAIN, DOMAIN_DATA_CONFIG, DOMAIN_DATA_ENTRIES, + DOMAIN_DATA_TASKS, SIGNAL_CLIENT_DATA, SIGNAL_CLIENT_STARTED, SIGNAL_CLIENT_STOPPED, @@ -73,6 +74,15 @@ DEVICE_SCHEMA = vol.Schema( ) ) + +async def _await_cancel(task): + task.cancel() + try: + await task + except asyncio.CancelledError: + pass + + CONFIG_SCHEMA = vol.Schema( {DOMAIN: vol.All(cv.ensure_list, [DEVICE_SCHEMA])}, extra=vol.ALLOW_EXTRA ) @@ -81,6 +91,7 @@ CONFIG_SCHEMA = vol.Schema( async def async_setup(hass: HomeAssistantType, config: ConfigType): """Set up the component.""" hass.data[DOMAIN_DATA_ENTRIES] = {} + hass.data[DOMAIN_DATA_TASKS] = {} hass.data[DOMAIN_DATA_CONFIG] = {} for device in config[DOMAIN]: @@ -94,6 +105,13 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType): ) ) + async def _stop(_): + asyncio.gather( + *[_await_cancel(task) for task in hass.data[DOMAIN_DATA_TASKS].values()] + ) + + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _stop) + return True @@ -107,13 +125,15 @@ async def async_setup_entry(hass: HomeAssistantType, entry: config_entries.Confi {CONF_HOST: entry.data[CONF_HOST], CONF_PORT: entry.data[CONF_PORT]} ), ) + tasks = hass.data.setdefault(DOMAIN_DATA_TASKS, {}) hass.data[DOMAIN_DATA_ENTRIES][entry.entry_id] = { "client": client, "config": config, } - asyncio.ensure_future(_run_client(hass, client, config[CONF_SCAN_INTERVAL])) + task = asyncio.create_task(_run_client(hass, client, DEFAULT_SCAN_INTERVAL)) + tasks[entry.entry_id] = task hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, "media_player") @@ -122,22 +142,23 @@ async def async_setup_entry(hass: HomeAssistantType, entry: config_entries.Confi return True +async def async_unload_entry(hass, entry): + """Cleanup before removing config entry.""" + await hass.config_entries.async_forward_entry_unload(entry, "media_player") + + task = hass.data[DOMAIN_DATA_TASKS].pop(entry.entry_id) + await _await_cancel(task) + + hass.data[DOMAIN_DATA_ENTRIES].pop(entry.entry_id) + + return True + + async def _run_client(hass, client, interval): - task = asyncio.Task.current_task() - run = True - - async def _stop(_): - nonlocal run - run = False - task.cancel() - await task - - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _stop) - def _listen(_): hass.helpers.dispatcher.async_dispatcher_send(SIGNAL_CLIENT_DATA, client.host) - while run: + while True: try: with async_timeout.timeout(interval): await client.start() @@ -163,7 +184,7 @@ async def _run_client(hass, client, interval): except asyncio.TimeoutError: continue except asyncio.CancelledError: - return + raise except Exception: # pylint: disable=broad-except _LOGGER.exception("Unexpected exception, aborting arcam client") return diff --git a/homeassistant/components/arcam_fmj/const.py b/homeassistant/components/arcam_fmj/const.py index 5270eb706dc..180abf2c960 100644 --- a/homeassistant/components/arcam_fmj/const.py +++ b/homeassistant/components/arcam_fmj/const.py @@ -12,4 +12,5 @@ DEFAULT_NAME = "Arcam FMJ" DEFAULT_SCAN_INTERVAL = 5 DOMAIN_DATA_ENTRIES = f"{DOMAIN}.entries" +DOMAIN_DATA_TASKS = f"{DOMAIN}.tasks" DOMAIN_DATA_CONFIG = f"{DOMAIN}.config" diff --git a/homeassistant/components/arcam_fmj/manifest.json b/homeassistant/components/arcam_fmj/manifest.json index c304d7bf351..ff89641667a 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.4"], + "requirements": ["arcam-fmj==0.4.6"], "codeowners": ["@elupus"] } diff --git a/requirements_all.txt b/requirements_all.txt index 753d2cc29aa..47176f2b679 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -266,7 +266,7 @@ aprslib==0.6.46 aqualogic==1.0 # homeassistant.components.arcam_fmj -arcam-fmj==0.4.4 +arcam-fmj==0.4.6 # homeassistant.components.arris_tg2492lg arris-tg2492lg==1.0.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 1b5f57dbed6..9fbb1f0d8cd 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -131,7 +131,7 @@ apprise==0.8.5 aprslib==0.6.46 # homeassistant.components.arcam_fmj -arcam-fmj==0.4.4 +arcam-fmj==0.4.6 # homeassistant.components.dlna_dmr # homeassistant.components.upnp From e2551b4e21482830e74de4cf084185c760f8ea7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20H=C3=B8yer=20Iversen?= Date: Sun, 17 May 2020 12:09:16 +0200 Subject: [PATCH 058/406] Upgrade opengarage lib to 0.1.4 (#35729) --- homeassistant/components/opengarage/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/opengarage/manifest.json b/homeassistant/components/opengarage/manifest.json index f15dbd6d726..8bbf8c76c42 100644 --- a/homeassistant/components/opengarage/manifest.json +++ b/homeassistant/components/opengarage/manifest.json @@ -5,5 +5,5 @@ "codeowners": [ "@danielhiversen" ], - "requirements": ["open-garage==0.1.3"] + "requirements": ["open-garage==0.1.4"] } diff --git a/requirements_all.txt b/requirements_all.txt index 47176f2b679..ad64d45be84 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -997,7 +997,7 @@ onkyo-eiscp==1.2.7 onvif-zeep-async==0.3.0 # homeassistant.components.opengarage -open-garage==0.1.3 +open-garage==0.1.4 # homeassistant.components.opencv # opencv-python-headless==4.2.0.32 From eec1b3e7a749a22fc4f85abdf1bdc15357e17a38 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Sun, 17 May 2020 06:13:53 -0400 Subject: [PATCH 059/406] Reduce Vizio API calls (#35726) * reduce calls to API * get apps list every time * fix typo --- .../components/vizio/media_player.py | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/vizio/media_player.py b/homeassistant/components/vizio/media_player.py index 67bb3d3633c..5a191b22cd4 100644 --- a/homeassistant/components/vizio/media_player.py +++ b/homeassistant/components/vizio/media_player.py @@ -137,7 +137,7 @@ class VizioDevice(MediaPlayerEntity): self._current_app = None self._current_app_config = None self._current_sound_mode = None - self._available_sound_modes = None + self._available_sound_modes = [] self._available_inputs = [] self._available_apps = [] self._conf_apps = config_entry.options.get(CONF_APPS, {}) @@ -192,12 +192,9 @@ class VizioDevice(MediaPlayerEntity): self._volume_level = None self._is_volume_muted = None self._current_input = None - self._available_inputs = None self._current_app = None self._current_app_config = None - self._available_apps = None self._current_sound_mode = None - self._available_sound_modes = None return self._state = STATE_ON @@ -215,7 +212,7 @@ class VizioDevice(MediaPlayerEntity): if VIZIO_SOUND_MODE in audio_settings: self._supported_commands |= SUPPORT_SELECT_SOUND_MODE self._current_sound_mode = audio_settings[VIZIO_SOUND_MODE] - if self._available_sound_modes is None: + if not self._available_sound_modes: self._available_sound_modes = await self._device.get_setting_options( VIZIO_AUDIO_SETTINGS, VIZIO_SOUND_MODE ) @@ -226,13 +223,14 @@ class VizioDevice(MediaPlayerEntity): if input_ is not None: self._current_input = input_ - inputs = await self._device.get_inputs_list(log_api_exception=False) + if not self._available_inputs: + inputs = await self._device.get_inputs_list(log_api_exception=False) - # If no inputs returned, end update - if not inputs: - return + # If no inputs returned, end update + if not inputs: + return - self._available_inputs = [input_.name for input_ in inputs] + self._available_inputs = [input_.name for input_ in inputs] # Return before setting app variables if INPUT_APPS isn't in available inputs if self._device_class == DEVICE_CLASS_SPEAKER or not any( @@ -242,8 +240,7 @@ class VizioDevice(MediaPlayerEntity): # Create list of available known apps from known app list after # filtering by CONF_INCLUDE/CONF_EXCLUDE - if not self._available_apps: - self._available_apps = self._apps_list(self._device.get_apps_list()) + self._available_apps = self._apps_list(self._device.get_apps_list()) self._current_app_config = await self._device.get_current_app_config( log_api_exception=False From 65e509ed8f8791409072b9f1281cd70279367989 Mon Sep 17 00:00:00 2001 From: Alan Murray Date: Sun, 17 May 2020 20:15:06 +1000 Subject: [PATCH 060/406] Add Acmeda integration (#33384) * First cut of Rollease Acmeda Pulse Hub integration. * Acmeda integration improvements: - Moved common code into a base entity - Battery level sensor added - Localisation now working * Added requirement for aiopulse now that it has been uploaded to PyPI. * Exclude acmeda integration from coverage check as it relies on a hub being present. * Fix Travis CI build issues. * Remove unused constants. * Remove unused group logic from cover.py * Removed commented code from base.py * Remove sensors (battery entities) on removal of hub. * Remove unused groups from sensor.py * Acmeda device and entity update made fully asynchronous using subscriptions to remove need for config polling. * Updated aiopulse version dependency. Removed non-functional battery charging indication. * Rationalised common code to update entities into helpers.py * Fix linting issue. * Correct additional CI pylint errors. * Index config_entries by entry_id. Move entity loading and unloading to __init__.py Add entry_id to dispatcher signal Removed now unused polling code hub Added config_flow unit tests * Tweak to integration config_entry title. * Bumped aiopulse module to 0.3.2. Reduced verbosity of aiopulse module. * Changed to using direct write of device state. Removed old style async_step_init config_flow step. * Remove superfluous battery_level and device_state_attributes from battery entity. * Removal of unused strings. Removal of unused create_config_flow helper. Removal of stale comment. * Remove use of shared container to track existing enities. Moved removal and deregistration of entities to base class through use of dispatch helper. * Fixed strings.json * Fix incorrect use of remove instead of pop on dict. * Add support for tilting covers, bump aiopulse version number. * Bump aiopulse version to v0.3.4. Fixed bug in cover supported_features. * Bumped aiopulse version to 0.4.0 Update acmeda .coveragerc exclusions * Removed already configured hub check from __init__.py async_setup_entry Removed passing in hass reference to base entity class Renamed entity async_reset to async_will_remove_from_hass Changed device_info and properties Migrated to CoveEntity from CoverDevice Added dispatched_connect cleanup on hub removal Removed unused entries from manifest Removed override of battery icon Renamed translations folder * Reversed unintended change to .coveragerc * Fixed config flow for multi-hub discovery. * Acmeda enhancements as requested by MartinHjelmare * Force import to connect to hub to retrieve id prior to creating entry * Remove YAML configuration support. * Tidied up config_flow and tests: - removed unnecessary steps - fixed typos * Removed storage of hub in config_flow. --- .coveragerc | 8 + CODEOWNERS | 1 + homeassistant/components/acmeda/__init__.py | 59 ++++++++ homeassistant/components/acmeda/base.py | 89 +++++++++++ .../components/acmeda/config_flow.py | 71 +++++++++ homeassistant/components/acmeda/const.py | 8 + homeassistant/components/acmeda/cover.py | 122 +++++++++++++++ homeassistant/components/acmeda/errors.py | 10 ++ homeassistant/components/acmeda/helpers.py | 41 +++++ homeassistant/components/acmeda/hub.py | 88 +++++++++++ homeassistant/components/acmeda/manifest.json | 10 ++ homeassistant/components/acmeda/sensor.py | 46 ++++++ homeassistant/components/acmeda/strings.json | 16 ++ .../components/acmeda/translations/en.json | 16 ++ homeassistant/generated/config_flows.py | 1 + requirements_all.txt | 3 + requirements_test_all.txt | 3 + tests/components/acmeda/__init__.py | 1 + tests/components/acmeda/test_config_flow.py | 143 ++++++++++++++++++ 19 files changed, 736 insertions(+) create mode 100644 homeassistant/components/acmeda/__init__.py create mode 100644 homeassistant/components/acmeda/base.py create mode 100644 homeassistant/components/acmeda/config_flow.py create mode 100644 homeassistant/components/acmeda/const.py create mode 100644 homeassistant/components/acmeda/cover.py create mode 100644 homeassistant/components/acmeda/errors.py create mode 100644 homeassistant/components/acmeda/helpers.py create mode 100644 homeassistant/components/acmeda/hub.py create mode 100644 homeassistant/components/acmeda/manifest.json create mode 100644 homeassistant/components/acmeda/sensor.py create mode 100644 homeassistant/components/acmeda/strings.json create mode 100644 homeassistant/components/acmeda/translations/en.json create mode 100644 tests/components/acmeda/__init__.py create mode 100644 tests/components/acmeda/test_config_flow.py diff --git a/.coveragerc b/.coveragerc index 92caff7127b..c49073f28f0 100644 --- a/.coveragerc +++ b/.coveragerc @@ -10,6 +10,14 @@ omit = # omit pieces of code that rely on external devices being present homeassistant/components/acer_projector/switch.py homeassistant/components/actiontec/device_tracker.py + homeassistant/components/acmeda/__init__.py + homeassistant/components/acmeda/base.py + homeassistant/components/acmeda/const.py + homeassistant/components/acmeda/cover.py + homeassistant/components/acmeda/errors.py + homeassistant/components/acmeda/helpers.py + homeassistant/components/acmeda/hub.py + homeassistant/components/acmeda/sensor.py homeassistant/components/adguard/__init__.py homeassistant/components/adguard/const.py homeassistant/components/adguard/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index 82c9c05bbb5..ff3ae7b8bb4 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -14,6 +14,7 @@ homeassistant/scripts/check_config.py @kellerza # Integrations homeassistant/components/abode/* @shred86 +homeassistant/components/acmeda/* @atmurray homeassistant/components/adguard/* @frenck homeassistant/components/agent_dvr/* @ispysoftware homeassistant/components/airly/* @bieniu diff --git a/homeassistant/components/acmeda/__init__.py b/homeassistant/components/acmeda/__init__.py new file mode 100644 index 00000000000..3b4f135a6fd --- /dev/null +++ b/homeassistant/components/acmeda/__init__.py @@ -0,0 +1,59 @@ +"""The Rollease Acmeda Automate integration.""" +import asyncio + +from homeassistant import config_entries, core + +from .const import DOMAIN +from .hub import PulseHub + +CONF_HUBS = "hubs" + +PLATFORMS = ["cover", "sensor"] + + +async def async_setup(hass: core.HomeAssistant, config: dict): + """Set up the Rollease Acmeda Automate component.""" + return True + + +async def async_setup_entry( + hass: core.HomeAssistant, config_entry: config_entries.ConfigEntry +): + """Set up Rollease Acmeda Automate hub from a config entry.""" + hub = PulseHub(hass, config_entry) + + if not await hub.async_setup(): + return False + + hass.data.setdefault(DOMAIN, {}) + hass.data[DOMAIN][config_entry.entry_id] = hub + + for component in PLATFORMS: + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(config_entry, component) + ) + + return True + + +async def async_unload_entry( + hass: core.HomeAssistant, config_entry: config_entries.ConfigEntry +): + """Unload a config entry.""" + hub = hass.data[DOMAIN][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 not await hub.async_reset(): + return False + + if unload_ok: + hass.data[DOMAIN].pop(config_entry.entry_id) + + return unload_ok diff --git a/homeassistant/components/acmeda/base.py b/homeassistant/components/acmeda/base.py new file mode 100644 index 00000000000..c467fe17ba3 --- /dev/null +++ b/homeassistant/components/acmeda/base.py @@ -0,0 +1,89 @@ +"""Base class for Acmeda Roller Blinds.""" +import aiopulse + +from homeassistant.core import callback +from homeassistant.helpers import entity +from homeassistant.helpers.device_registry import async_get_registry as get_dev_reg +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.entity_registry import async_get_registry as get_ent_reg + +from .const import ACMEDA_ENTITY_REMOVE, DOMAIN, LOGGER + + +class AcmedaBase(entity.Entity): + """Base representation of an Acmeda roller.""" + + def __init__(self, roller: aiopulse.Roller): + """Initialize the roller.""" + self.roller = roller + + async def async_remove_and_unregister(self): + """Unregister from entity and device registry and call entity remove function.""" + LOGGER.error("Removing %s %s", self.__class__.__name__, self.unique_id) + + ent_registry = await get_ent_reg(self.hass) + if self.entity_id in ent_registry.entities: + ent_registry.async_remove(self.entity_id) + + dev_registry = await get_dev_reg(self.hass) + device = dev_registry.async_get_device( + identifiers={(DOMAIN, self.unique_id)}, connections=set() + ) + if device is not None: + dev_registry.async_update_device( + device.id, remove_config_entry_id=self.registry_entry.config_entry_id + ) + + await self.async_remove() + + async def async_added_to_hass(self): + """Entity has been added to hass.""" + self.roller.callback_subscribe(self.notify_update) + + self.async_on_remove( + async_dispatcher_connect( + self.hass, + ACMEDA_ENTITY_REMOVE.format(self.roller.id), + self.async_remove_and_unregister, + ) + ) + + async def async_will_remove_from_hass(self): + """Entity being removed from hass.""" + self.roller.callback_unsubscribe(self.notify_update) + + @callback + def notify_update(self): + """Write updated device state information.""" + LOGGER.debug("Device update notification received: %s", self.name) + self.async_write_ha_state() + + @property + def should_poll(self): + """Report that Acmeda entities do not need polling.""" + return False + + @property + def unique_id(self): + """Return the unique ID of this roller.""" + return self.roller.id + + @property + def device_id(self): + """Return the ID of this roller.""" + return self.roller.id + + @property + def name(self): + """Return the name of roller.""" + return self.roller.name + + @property + def device_info(self): + """Return the device info.""" + return { + "identifiers": {(DOMAIN, self.unique_id)}, + "name": self.roller.name, + "manufacturer": "Rollease Acmeda", + "via_device": (DOMAIN, self.roller.hub.id), + } diff --git a/homeassistant/components/acmeda/config_flow.py b/homeassistant/components/acmeda/config_flow.py new file mode 100644 index 00000000000..33dac4814e5 --- /dev/null +++ b/homeassistant/components/acmeda/config_flow.py @@ -0,0 +1,71 @@ +"""Config flow for Rollease Acmeda Automate Pulse Hub.""" +import asyncio +from typing import Dict, Optional + +import aiopulse +import async_timeout +import voluptuous as vol + +from homeassistant import config_entries + +from .const import DOMAIN # pylint: disable=unused-import + + +class AcmedaFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a Acmeda config flow.""" + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL + + def __init__(self): + """Initialize the config flow.""" + self.discovered_hubs: Optional[Dict[str, aiopulse.Hub]] = None + + async def async_step_user(self, user_input=None): + """Handle a flow initialized by the user.""" + if ( + user_input is not None + and self.discovered_hubs is not None + # pylint: disable=unsupported-membership-test + and user_input["id"] in self.discovered_hubs + ): + # pylint: disable=unsubscriptable-object + return await self.async_create(self.discovered_hubs[user_input["id"]]) + + # Already configured hosts + already_configured = { + entry.unique_id for entry in self._async_current_entries() + } + + hubs = [] + try: + with async_timeout.timeout(5): + async for hub in aiopulse.Hub.discover(): + if hub.id not in already_configured: + hubs.append(hub) + except asyncio.TimeoutError: + pass + + if len(hubs) == 0: + return self.async_abort(reason="all_configured") + + if len(hubs) == 1: + return await self.async_create(hubs[0]) + + self.discovered_hubs = {hub.id: hub for hub in hubs} + + return self.async_show_form( + step_id="user", + data_schema=vol.Schema( + { + vol.Required("id"): vol.In( + {hub.id: f"{hub.id} {hub.host}" for hub in hubs} + ) + } + ), + ) + + async def async_create(self, hub): + """Create the Acmeda Hub entry.""" + await self.async_set_unique_id(hub.id, raise_on_progress=False) + return self.async_create_entry(title=hub.id, data={"host": hub.host}) diff --git a/homeassistant/components/acmeda/const.py b/homeassistant/components/acmeda/const.py new file mode 100644 index 00000000000..b8712fee4ba --- /dev/null +++ b/homeassistant/components/acmeda/const.py @@ -0,0 +1,8 @@ +"""Constants for the Rollease Acmeda Automate integration.""" +import logging + +LOGGER = logging.getLogger(__package__) +DOMAIN = "acmeda" + +ACMEDA_HUB_UPDATE = "acmeda_hub_update_{}" +ACMEDA_ENTITY_REMOVE = "acmeda_entity_remove_{}" diff --git a/homeassistant/components/acmeda/cover.py b/homeassistant/components/acmeda/cover.py new file mode 100644 index 00000000000..0a4436bc073 --- /dev/null +++ b/homeassistant/components/acmeda/cover.py @@ -0,0 +1,122 @@ +"""Support for Acmeda Roller Blinds.""" +from homeassistant.components.cover import ( + ATTR_POSITION, + SUPPORT_CLOSE, + SUPPORT_CLOSE_TILT, + SUPPORT_OPEN, + SUPPORT_OPEN_TILT, + SUPPORT_SET_POSITION, + SUPPORT_SET_TILT_POSITION, + SUPPORT_STOP, + SUPPORT_STOP_TILT, + CoverEntity, +) +from homeassistant.core import callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect + +from .base import AcmedaBase +from .const import ACMEDA_HUB_UPDATE, DOMAIN +from .helpers import async_add_acmeda_entities + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up the Acmeda Rollers from a config entry.""" + hub = hass.data[DOMAIN][config_entry.entry_id] + + current = set() + + @callback + def async_add_acmeda_covers(): + async_add_acmeda_entities( + hass, AcmedaCover, config_entry, current, async_add_entities + ) + + hub.cleanup_callbacks.append( + async_dispatcher_connect( + hass, + ACMEDA_HUB_UPDATE.format(config_entry.entry_id), + async_add_acmeda_covers, + ) + ) + + +class AcmedaCover(AcmedaBase, CoverEntity): + """Representation of a Acmeda cover device.""" + + @property + def current_cover_position(self): + """Return the current position of the roller blind. + + None is unknown, 0 is closed, 100 is fully open. + """ + position = None + if self.roller.type != 7: + position = 100 - self.roller.closed_percent + return position + + @property + def current_cover_tilt_position(self): + """Return the current tilt of the roller blind. + + None is unknown, 0 is closed, 100 is fully open. + """ + position = None + if self.roller.type == 7 or self.roller.type == 10: + position = 100 - self.roller.closed_percent + return position + + @property + def supported_features(self): + """Flag supported features.""" + supported_features = 0 + if self.current_cover_position is not None: + supported_features |= ( + SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_STOP | SUPPORT_SET_POSITION + ) + if self.current_cover_tilt_position is not None: + supported_features |= ( + SUPPORT_OPEN_TILT + | SUPPORT_CLOSE_TILT + | SUPPORT_STOP_TILT + | SUPPORT_SET_TILT_POSITION + ) + + return supported_features + + @property + def is_closed(self): + """Return if the cover is closed.""" + is_closed = self.roller.closed_percent == 100 + return is_closed + + async def close_cover(self, **kwargs): + """Close the roller.""" + await self.roller.move_down() + + async def open_cover(self, **kwargs): + """Open the roller.""" + await self.roller.move_up() + + async def stop_cover(self, **kwargs): + """Stop the roller.""" + await self.roller.move_stop() + + async def set_cover_position(self, **kwargs): + """Move the roller shutter to a specific position.""" + await self.roller.move_to(100 - kwargs[ATTR_POSITION]) + + async def close_cover_tilt(self, **kwargs): + """Close the roller.""" + await self.roller.move_down() + + async def open_cover_tilt(self, **kwargs): + """Open the roller.""" + await self.roller.move_up() + + async def stop_cover_tilt(self, **kwargs): + """Stop the roller.""" + await self.roller.move_stop() + + async def set_cover_tilt(self, **kwargs): + """Tilt the roller shutter to a specific position.""" + await self.roller.move_to(100 - kwargs[ATTR_POSITION]) diff --git a/homeassistant/components/acmeda/errors.py b/homeassistant/components/acmeda/errors.py new file mode 100644 index 00000000000..f26090df03d --- /dev/null +++ b/homeassistant/components/acmeda/errors.py @@ -0,0 +1,10 @@ +"""Errors for the Acmeda Pulse component.""" +from homeassistant.exceptions import HomeAssistantError + + +class PulseException(HomeAssistantError): + """Base class for Acmeda Pulse exceptions.""" + + +class CannotConnect(PulseException): + """Unable to connect to the bridge.""" diff --git a/homeassistant/components/acmeda/helpers.py b/homeassistant/components/acmeda/helpers.py new file mode 100644 index 00000000000..f8ea744be77 --- /dev/null +++ b/homeassistant/components/acmeda/helpers.py @@ -0,0 +1,41 @@ +"""Helper functions for Acmeda Pulse.""" +from homeassistant.core import callback +from homeassistant.helpers.device_registry import async_get_registry as get_dev_reg + +from .const import DOMAIN, LOGGER + + +@callback +def async_add_acmeda_entities( + hass, entity_class, config_entry, current, async_add_entities +): + """Add any new entities.""" + hub = hass.data[DOMAIN][config_entry.entry_id] + LOGGER.debug("Looking for new %s on: %s", entity_class.__name__, hub.host) + + api = hub.api.rollers + + new_items = [] + for unique_id, roller in api.items(): + if unique_id not in current: + LOGGER.debug("New %s %s", entity_class.__name__, unique_id) + new_item = entity_class(roller) + current.add(unique_id) + new_items.append(new_item) + + async_add_entities(new_items) + + +async def update_devices(hass, config_entry, api): + """Tell hass that device info has been updated.""" + dev_registry = await get_dev_reg(hass) + + for api_item in api.values(): + # Update Device name + device = dev_registry.async_get_device( + identifiers={(DOMAIN, api_item.id)}, connections=set() + ) + if device is not None: + dev_registry.async_update_device( + device.id, name=api_item.name, + ) diff --git a/homeassistant/components/acmeda/hub.py b/homeassistant/components/acmeda/hub.py new file mode 100644 index 00000000000..0b74b874dcc --- /dev/null +++ b/homeassistant/components/acmeda/hub.py @@ -0,0 +1,88 @@ +"""Code to handle a Pulse Hub.""" +import asyncio +from typing import Optional + +import aiopulse + +from homeassistant.helpers.dispatcher import async_dispatcher_send + +from .const import ACMEDA_ENTITY_REMOVE, ACMEDA_HUB_UPDATE, LOGGER +from .helpers import update_devices + + +class PulseHub: + """Manages a single Pulse Hub.""" + + def __init__(self, hass, config_entry): + """Initialize the system.""" + self.config_entry = config_entry + self.hass = hass + self.api: Optional[aiopulse.Hub] = None + self.tasks = [] + self.current_rollers = {} + self.cleanup_callbacks = [] + + @property + def title(self): + """Return the title of the hub shown in the integrations list.""" + return f"{self.api.id} ({self.api.host})" + + @property + def host(self): + """Return the host of this hub.""" + return self.config_entry.data["host"] + + async def async_setup(self, tries=0): + """Set up a hub based on host parameter.""" + host = self.host + + hub = aiopulse.Hub(host) + self.api = hub + + hub.callback_subscribe(self.async_notify_update) + self.tasks.append(asyncio.create_task(hub.run())) + + LOGGER.debug("Hub setup complete") + return True + + async def async_reset(self): + """Reset this hub to default state.""" + + for cleanup_callback in self.cleanup_callbacks: + cleanup_callback() + + # If not setup + if self.api is None: + return False + + self.api.callback_unsubscribe(self.async_notify_update) + await self.api.stop() + del self.api + self.api = None + + # Wait for any running tasks to complete + await asyncio.wait(self.tasks) + + return True + + async def async_notify_update(self, update_type): + """Evaluate entities when hub reports that update has occurred.""" + LOGGER.debug("Hub {update_type.name} updated") + + if update_type == aiopulse.UpdateType.rollers: + await update_devices(self.hass, self.config_entry, self.api.rollers) + self.hass.config_entries.async_update_entry( + self.config_entry, title=self.title + ) + + async_dispatcher_send( + self.hass, ACMEDA_HUB_UPDATE.format(self.config_entry.entry_id) + ) + + for unique_id in list(self.current_rollers): + if unique_id not in self.api.rollers: + LOGGER.debug("Notifying remove of %s", unique_id) + self.current_rollers.pop(unique_id) + async_dispatcher_send( + self.hass, ACMEDA_ENTITY_REMOVE.format(unique_id) + ) diff --git a/homeassistant/components/acmeda/manifest.json b/homeassistant/components/acmeda/manifest.json new file mode 100644 index 00000000000..8b76af0c57e --- /dev/null +++ b/homeassistant/components/acmeda/manifest.json @@ -0,0 +1,10 @@ +{ + "domain": "acmeda", + "name": "Rollease Acmeda Automate", + "config_flow": true, + "documentation": "https://www.home-assistant.io/integrations/acmeda", + "requirements": ["aiopulse==0.4.0"], + "codeowners": [ + "@atmurray" + ] +} \ No newline at end of file diff --git a/homeassistant/components/acmeda/sensor.py b/homeassistant/components/acmeda/sensor.py new file mode 100644 index 00000000000..e549160fbdd --- /dev/null +++ b/homeassistant/components/acmeda/sensor.py @@ -0,0 +1,46 @@ +"""Support for Acmeda Roller Blind Batteries.""" +from homeassistant.const import DEVICE_CLASS_BATTERY, UNIT_PERCENTAGE +from homeassistant.core import callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect + +from .base import AcmedaBase +from .const import ACMEDA_HUB_UPDATE, DOMAIN +from .helpers import async_add_acmeda_entities + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up the Acmeda Rollers from a config entry.""" + hub = hass.data[DOMAIN][config_entry.entry_id] + + current = set() + + @callback + def async_add_acmeda_sensors(): + async_add_acmeda_entities( + hass, AcmedaBattery, config_entry, current, async_add_entities + ) + + hub.cleanup_callbacks.append( + async_dispatcher_connect( + hass, + ACMEDA_HUB_UPDATE.format(config_entry.entry_id), + async_add_acmeda_sensors, + ) + ) + + +class AcmedaBattery(AcmedaBase): + """Representation of a Acmeda cover device.""" + + device_class = DEVICE_CLASS_BATTERY + unit_of_measurement = UNIT_PERCENTAGE + + @property + def name(self): + """Return the name of roller.""" + return f"{super().name} Battery" + + @property + def state(self): + """Return the state of the device.""" + return self.roller.battery diff --git a/homeassistant/components/acmeda/strings.json b/homeassistant/components/acmeda/strings.json new file mode 100644 index 00000000000..eb7ed44999b --- /dev/null +++ b/homeassistant/components/acmeda/strings.json @@ -0,0 +1,16 @@ +{ + "title": "Rollease Acmeda Automate", + "config": { + "step": { + "user": { + "title": "Pick a hub to add", + "data": { + "id": "Host ID" + } + } + }, + "abort": { + "all_configured": "No new Pulse hubs discovered." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/acmeda/translations/en.json b/homeassistant/components/acmeda/translations/en.json new file mode 100644 index 00000000000..eb7ed44999b --- /dev/null +++ b/homeassistant/components/acmeda/translations/en.json @@ -0,0 +1,16 @@ +{ + "title": "Rollease Acmeda Automate", + "config": { + "step": { + "user": { + "title": "Pick a hub to add", + "data": { + "id": "Host ID" + } + } + }, + "abort": { + "all_configured": "No new Pulse hubs discovered." + } + } +} \ No newline at end of file diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index e1159064bf8..10d6e11baf4 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -7,6 +7,7 @@ To update, run python3 -m script.hassfest FLOWS = [ "abode", + "acmeda", "adguard", "agent_dvr", "airly", diff --git a/requirements_all.txt b/requirements_all.txt index ad64d45be84..264750a0989 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -205,6 +205,9 @@ aionotify==0.2.0 # homeassistant.components.notion aionotion==1.1.0 +# homeassistant.components.acmeda +aiopulse==0.4.0 + # homeassistant.components.hunterdouglas_powerview aiopvapi==1.6.14 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9fbb1f0d8cd..66676368988 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -94,6 +94,9 @@ aiohue==2.1.0 # homeassistant.components.notion aionotion==1.1.0 +# homeassistant.components.acmeda +aiopulse==0.4.0 + # homeassistant.components.hunterdouglas_powerview aiopvapi==1.6.14 diff --git a/tests/components/acmeda/__init__.py b/tests/components/acmeda/__init__.py new file mode 100644 index 00000000000..126c834d1ee --- /dev/null +++ b/tests/components/acmeda/__init__.py @@ -0,0 +1 @@ +"""Tests for the Rollease Acmeda Automate integration.""" diff --git a/tests/components/acmeda/test_config_flow.py b/tests/components/acmeda/test_config_flow.py new file mode 100644 index 00000000000..1663bc3d443 --- /dev/null +++ b/tests/components/acmeda/test_config_flow.py @@ -0,0 +1,143 @@ +"""Define tests for the Acmeda config flow.""" +import aiopulse +from asynctest.mock import patch +import pytest + +from homeassistant import data_entry_flow +from homeassistant.components.acmeda.const import DOMAIN +from homeassistant.config_entries import SOURCE_USER +from homeassistant.const import CONF_HOST + +from tests.common import MockConfigEntry + +DUMMY_HOST1 = "127.0.0.1" +DUMMY_HOST2 = "127.0.0.2" + +CONFIG = { + CONF_HOST: DUMMY_HOST1, +} + + +@pytest.fixture +def mock_hub_discover(): + """Mock the hub discover method.""" + with patch("aiopulse.Hub.discover") as mock_discover: + yield mock_discover + + +@pytest.fixture +def mock_hub_run(): + """Mock the hub run method.""" + with patch("aiopulse.Hub.run") as mock_run: + yield mock_run + + +async def async_generator(items): + """Async yields items provided in a list.""" + for item in items: + yield item + + +async def test_show_form_no_hubs(hass, mock_hub_discover): + """Test that flow aborts if no hubs are discovered.""" + mock_hub_discover.return_value = async_generator([]) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER} + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "all_configured" + + # Check we performed the discovery + assert len(mock_hub_discover.mock_calls) == 1 + + +async def test_show_form_one_hub(hass, mock_hub_discover, mock_hub_run): + """Test that a config is created when one hub discovered.""" + + dummy_hub_1 = aiopulse.Hub(DUMMY_HOST1) + dummy_hub_1.id = "ABC123" + + mock_hub_discover.return_value = async_generator([dummy_hub_1]) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER} + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["title"] == dummy_hub_1.id + assert result["result"].data == { + "host": DUMMY_HOST1, + } + + # Check we performed the discovery + assert len(mock_hub_discover.mock_calls) == 1 + + +async def test_show_form_two_hubs(hass, mock_hub_discover): + """Test that the form is served when more than one hub discovered.""" + + dummy_hub_1 = aiopulse.Hub(DUMMY_HOST1) + dummy_hub_1.id = "ABC123" + + dummy_hub_2 = aiopulse.Hub(DUMMY_HOST1) + dummy_hub_2.id = "DEF456" + + mock_hub_discover.return_value = async_generator([dummy_hub_1, dummy_hub_2]) + + 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" + + # Check we performed the discovery + assert len(mock_hub_discover.mock_calls) == 1 + + +async def test_create_second_entry(hass, mock_hub_run, mock_hub_discover): + """Test that a config is created when a second hub is discovered.""" + + dummy_hub_1 = aiopulse.Hub(DUMMY_HOST1) + dummy_hub_1.id = "ABC123" + + dummy_hub_2 = aiopulse.Hub(DUMMY_HOST2) + dummy_hub_2.id = "DEF456" + + mock_hub_discover.return_value = async_generator([dummy_hub_1, dummy_hub_2]) + + MockConfigEntry(domain=DOMAIN, unique_id=dummy_hub_1.id, data=CONFIG).add_to_hass( + hass + ) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER} + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["title"] == dummy_hub_2.id + assert result["result"].data == { + "host": DUMMY_HOST2, + } + + +async def test_already_configured(hass, mock_hub_discover): + """Test that flow aborts when all hubs are configured.""" + + dummy_hub_1 = aiopulse.Hub(DUMMY_HOST1) + dummy_hub_1.id = "ABC123" + + mock_hub_discover.return_value = async_generator([dummy_hub_1]) + + MockConfigEntry(domain=DOMAIN, unique_id=dummy_hub_1.id, data=CONFIG).add_to_hass( + hass + ) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER} + ) + + assert result["type"] == "abort" + assert result["reason"] == "all_configured" From 1297a093443ac2243b4fcca402434f7cdf364d3f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 17 May 2020 07:39:27 -0500 Subject: [PATCH 061/406] Avoid locking in the logging queue handler (#35700) * Avoid locking in the logging queue handler We do not need a lock here as the underlying queue is already thread safe. * Add coverage for logging handle --- homeassistant/util/logging.py | 18 ++++++++++++++++++ tests/util/test_logging.py | 11 +++++++++++ 2 files changed, 29 insertions(+) diff --git a/homeassistant/util/logging.py b/homeassistant/util/logging.py index 07ee6608141..943e701a144 100644 --- a/homeassistant/util/logging.py +++ b/homeassistant/util/logging.py @@ -39,6 +39,24 @@ class HomeAssistantQueueHandler(logging.handlers.QueueHandler): except Exception: # pylint: disable=broad-except self.handleError(record) + def handle(self, record: logging.LogRecord) -> Any: + """ + Conditionally emit the specified logging record. + + Depending on which filters have been added to the handler, push the new + records onto the backing Queue. + + The default python logger Handler acquires a lock + in the parent class which we do not need as + SimpleQueue is already thread safe. + + See https://bugs.python.org/issue24645 + """ + return_value = self.filter(record) + if return_value: + self.emit(record) + return return_value + @callback def async_activate_log_queue_handler(hass: HomeAssistant) -> None: diff --git a/tests/util/test_logging.py b/tests/util/test_logging.py index a1183ee1637..04d6f133381 100644 --- a/tests/util/test_logging.py +++ b/tests/util/test_logging.py @@ -38,6 +38,17 @@ async def test_logging_with_queue_handler(): ): handler.emit(log_record) + with patch.object(handler, "emit") as emit_mock: + handler.handle(log_record) + emit_mock.assert_called_once() + + with patch.object(handler, "filter") as filter_mock, patch.object( + handler, "emit" + ) as emit_mock: + filter_mock.return_value = False + handler.handle(log_record) + emit_mock.assert_not_called() + with patch.object(handler, "enqueue", side_effect=OSError), patch.object( handler, "handleError" ) as mock_handle_error: From 06d32baea4f73701db782bf5954ef1334466a1ab Mon Sep 17 00:00:00 2001 From: Xiaonan Shen Date: Sun, 17 May 2020 05:48:56 -0700 Subject: [PATCH 062/406] Fix garmin_connect test (#35724) --- tests/components/garmin_connect/{__init__py => __init__.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/components/garmin_connect/{__init__py => __init__.py} (100%) diff --git a/tests/components/garmin_connect/__init__py b/tests/components/garmin_connect/__init__.py similarity index 100% rename from tests/components/garmin_connect/__init__py rename to tests/components/garmin_connect/__init__.py From d98bd418b1920f189d528369fab19be2780eaab6 Mon Sep 17 00:00:00 2001 From: rhadamantys <46837767+rhadamantys@users.noreply.github.com> Date: Sun, 17 May 2020 15:12:49 +0200 Subject: [PATCH 063/406] Add restore last state for EnOcean Sensors (#34375) --- homeassistant/components/enocean/sensor.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/enocean/sensor.py b/homeassistant/components/enocean/sensor.py index 45a20197a4a..16f6238acdc 100644 --- a/homeassistant/components/enocean/sensor.py +++ b/homeassistant/components/enocean/sensor.py @@ -19,6 +19,7 @@ from homeassistant.const import ( UNIT_PERCENTAGE, ) import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.restore_state import RestoreEntity _LOGGER = logging.getLogger(__name__) @@ -104,7 +105,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([EnOceanWindowHandle(dev_id, dev_name)]) -class EnOceanSensor(enocean.EnOceanDevice): +class EnOceanSensor(enocean.EnOceanDevice, RestoreEntity): """Representation of an EnOcean sensor device such as a power meter.""" def __init__(self, dev_id, dev_name, sensor_type): @@ -142,6 +143,17 @@ class EnOceanSensor(enocean.EnOceanDevice): """Return the unit of measurement.""" return self._unit_of_measurement + async def async_added_to_hass(self): + """Call when entity about to be added to hass.""" + # If not None, we got an initial value. + await super().async_added_to_hass() + if self._state is not None: + return + + state = await self.async_get_last_state() + if state is not None: + self._state = state.state + def value_changed(self, packet): """Update the internal state of the sensor.""" From 71d41cbb71a704f3726a20b2260ea50b7423daa0 Mon Sep 17 00:00:00 2001 From: Tom Harris Date: Sun, 17 May 2020 09:17:05 -0400 Subject: [PATCH 064/406] Add .env file to .gitignore for vscode environment settings (#35200) --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 2473aeb4bf6..75d63d8bacb 100644 --- a/.gitignore +++ b/.gitignore @@ -106,6 +106,7 @@ virtualization/vagrant/config !.vscode/cSpell.json !.vscode/extensions.json !.vscode/tasks.json +.env # Built docs docs/build From 47801e7350eeb7a4cbe36668d9033433d629b3bd Mon Sep 17 00:00:00 2001 From: uvjustin <46082645+uvjustin@users.noreply.github.com> Date: Sun, 17 May 2020 21:23:44 +0800 Subject: [PATCH 065/406] Abort forked-daapd zeroconf flow if version < 27 (#35709) * Change MediaPlayerDevice to MediaPlayerEntity * Abort zeroconf if mtd-version < 27.0 --- homeassistant/components/forked_daapd/config_flow.py | 2 +- tests/components/forked_daapd/test_config_flow.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/forked_daapd/config_flow.py b/homeassistant/components/forked_daapd/config_flow.py index dda11171fe8..697c3f0c7ac 100644 --- a/homeassistant/components/forked_daapd/config_flow.py +++ b/homeassistant/components/forked_daapd/config_flow.py @@ -158,7 +158,7 @@ class ForkedDaapdFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Prepare configuration for a discovered forked-daapd device.""" if not ( discovery_info.get("properties") - and discovery_info["properties"].get("mtd-version") + and float(discovery_info["properties"].get("mtd-version", 0)) >= 27.0 and discovery_info["properties"].get("Machine Name") ): return self.async_abort(reason="not_forked_daapd") diff --git a/tests/components/forked_daapd/test_config_flow.py b/tests/components/forked_daapd/test_config_flow.py index b0b484d8943..b97cc07009c 100644 --- a/tests/components/forked_daapd/test_config_flow.py +++ b/tests/components/forked_daapd/test_config_flow.py @@ -103,7 +103,7 @@ async def test_zeroconf_updates_title(hass, config_entry): discovery_info = { "host": "192.168.1.1", "port": 23, - "properties": {"mtd-version": 1, "Machine Name": "zeroconf_test"}, + "properties": {"mtd-version": 27.0, "Machine Name": "zeroconf_test"}, } result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_ZEROCONF}, data=discovery_info @@ -143,7 +143,7 @@ async def test_config_flow_zeroconf_valid(hass): "host": "192.168.1.1", "port": 23, "properties": { - "mtd-version": 1, + "mtd-version": 27.0, "Machine Name": "zeroconf_test", "Machine ID": "5E55EEFF", }, From dbd821a564fb3adb0e9e9720ba7e8716a3fc597e Mon Sep 17 00:00:00 2001 From: Tom Harris Date: Sun, 17 May 2020 09:27:38 -0400 Subject: [PATCH 066/406] Change Insteon backend module to pyinsteon from insteonplm (#35198) * Migrate to pyinsteon from insteonplm * Rename devices entities * Print ALDB even if not loaded * Add relay to name map * Change insteonplm to pyinsteon * Update requirements_all correctly * Code review updates * async_set_speed receive std speed value * default speed to std medium value * Call async methods for fan on/off * Comment await required in loop * Remove emtpy and add codeowner * Make services async and remove async_add_job call * Remove extra logging * New device as async task and aldb load in loop * Place lock in context bloxk * Limiting lock to min * Remove .env file --- CODEOWNERS | 1 + homeassistant/components/insteon/__init__.py | 144 +++++---- .../components/insteon/binary_sensor.py | 86 +++--- homeassistant/components/insteon/const.py | 117 +++++--- homeassistant/components/insteon/cover.py | 39 +-- homeassistant/components/insteon/fan.py | 71 ++--- .../components/insteon/insteon_entity.py | 68 ++--- homeassistant/components/insteon/ipdb.py | 177 ++++++----- homeassistant/components/insteon/light.py | 37 +-- .../components/insteon/manifest.json | 6 +- homeassistant/components/insteon/schemas.py | 12 +- homeassistant/components/insteon/sensor.py | 31 -- homeassistant/components/insteon/switch.py | 57 +--- homeassistant/components/insteon/utils.py | 279 ++++++++++++------ requirements_all.txt | 6 +- 15 files changed, 617 insertions(+), 514 deletions(-) delete mode 100644 homeassistant/components/insteon/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index ff3ae7b8bb4..20336249168 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -194,6 +194,7 @@ homeassistant/components/input_datetime/* @home-assistant/core homeassistant/components/input_number/* @home-assistant/core homeassistant/components/input_select/* @home-assistant/core homeassistant/components/input_text/* @home-assistant/core +homeassistant/components/insteon/* @teharris1 homeassistant/components/integration/* @dgomes homeassistant/components/intent/* @home-assistant/core homeassistant/components/intesishome/* @jnimmo diff --git a/homeassistant/components/insteon/__init__.py b/homeassistant/components/insteon/__init__.py index ce17cc6c77d..c28c04f589d 100644 --- a/homeassistant/components/insteon/__init__.py +++ b/homeassistant/components/insteon/__init__.py @@ -1,7 +1,8 @@ """Support for INSTEON Modems (PLM and Hub).""" +import asyncio import logging -import insteonplm +from pyinsteon import async_close, async_connect, devices from homeassistant.const import ( CONF_HOST, @@ -24,21 +25,75 @@ from .const import ( CONF_SUBCAT, CONF_UNITCODE, CONF_X10, - CONF_X10_ALL_LIGHTS_OFF, - CONF_X10_ALL_LIGHTS_ON, - CONF_X10_ALL_UNITS_OFF, DOMAIN, - INSTEON_ENTITIES, + INSTEON_COMPONENTS, + ON_OFF_EVENTS, ) from .schemas import CONFIG_SCHEMA # noqa F440 -from .utils import async_register_services, register_new_device_callback +from .utils import ( + add_on_off_event_device, + async_register_services, + get_device_platforms, + register_new_device_callback, +) _LOGGER = logging.getLogger(__name__) +async def async_id_unknown_devices(config_dir): + """Send device ID commands to all unidentified devices.""" + await devices.async_load(id_devices=1) + for addr in devices: + device = devices[addr] + flags = True + for name in device.operating_flags: + if not device.operating_flags[name].is_loaded: + flags = False + break + if flags: + for name in device.properties: + if not device.properties[name].is_loaded: + flags = False + break + + # Cannot be done concurrently due to issues with the underlying protocol. + if not device.aldb.is_loaded or not flags: + await device.async_read_config() + + await devices.async_save(workdir=config_dir) + + +async def async_setup_platforms(hass, config): + """Initiate the connection and services.""" + tasks = [ + hass.helpers.discovery.async_load_platform(component, DOMAIN, {}, config) + for component in INSTEON_COMPONENTS + ] + await asyncio.gather(*tasks) + + for address in devices: + device = devices[address] + platforms = get_device_platforms(device) + if ON_OFF_EVENTS in platforms: + add_on_off_event_device(hass, device) + + _LOGGER.debug("Insteon device count: %s", len(devices)) + register_new_device_callback(hass, config) + async_register_services(hass) + + # Cannot be done concurrently due to issues with the underlying protocol. + for address in devices: + await devices[address].async_status() + await async_id_unknown_devices(hass.config.config_dir) + + +async def close_insteon_connection(*args): + """Close the Insteon connection.""" + await async_close() + + async def async_setup(hass, config): """Set up the connection to the modem.""" - insteon_modem = None conf = config[DOMAIN] port = conf.get(CONF_PORT) @@ -47,68 +102,50 @@ async def async_setup(hass, config): username = conf.get(CONF_HUB_USERNAME) password = conf.get(CONF_HUB_PASSWORD) hub_version = conf.get(CONF_HUB_VERSION) - overrides = conf.get(CONF_OVERRIDE, []) - x10_devices = conf.get(CONF_X10, []) - x10_all_units_off_housecode = conf.get(CONF_X10_ALL_UNITS_OFF) - x10_all_lights_on_housecode = conf.get(CONF_X10_ALL_LIGHTS_ON) - x10_all_lights_off_housecode = conf.get(CONF_X10_ALL_LIGHTS_OFF) if host: - _LOGGER.info("Connecting to Insteon Hub on %s", host) - conn = await insteonplm.Connection.create( + _LOGGER.info("Connecting to Insteon Hub on %s:%d", host, ip_port) + else: + _LOGGER.info("Connecting to Insteon PLM on %s", port) + + try: + await async_connect( + device=port, host=host, port=ip_port, username=username, password=password, hub_version=hub_version, - loop=hass.loop, - workdir=hass.config.config_dir, - ) - else: - _LOGGER.info("Looking for Insteon PLM on %s", port) - conn = await insteonplm.Connection.create( - device=port, loop=hass.loop, workdir=hass.config.config_dir ) + except ConnectionError: + _LOGGER.error("Could not connect to Insteon modem") + return False + _LOGGER.info("Connection to Insteon modem successful") - insteon_modem = conn.protocol + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, close_insteon_connection) + conf = config[DOMAIN] + overrides = conf.get(CONF_OVERRIDE, []) + x10_devices = conf.get(CONF_X10, []) - hass.data[DOMAIN] = {} - hass.data[DOMAIN]["modem"] = insteon_modem - hass.data[DOMAIN][INSTEON_ENTITIES] = set() - - register_new_device_callback(hass, config, insteon_modem) - async_register_services(hass, config, insteon_modem) - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, conn.close) + await devices.async_load( + workdir=hass.config.config_dir, id_devices=0, load_modem_aldb=0 + ) for device_override in overrides: - # # Override the device default capabilities for a specific address - # address = device_override.get("address") - for prop in device_override: - if prop in [CONF_CAT, CONF_SUBCAT]: - insteon_modem.devices.add_override(address, prop, device_override[prop]) - elif prop in [CONF_FIRMWARE, CONF_PRODUCT_KEY]: - insteon_modem.devices.add_override( - address, CONF_PRODUCT_KEY, device_override[prop] - ) + if not devices.get(address): + cat = device_override[CONF_CAT] + subcat = device_override[CONF_SUBCAT] + firmware = device_override.get(CONF_FIRMWARE) + if firmware is None: + firmware = device_override.get(CONF_PRODUCT_KEY, 0) + devices.set_id(address, cat, subcat, firmware) - if x10_all_units_off_housecode: - device = insteon_modem.add_x10_device( - x10_all_units_off_housecode, 20, "allunitsoff" - ) - if x10_all_lights_on_housecode: - device = insteon_modem.add_x10_device( - x10_all_lights_on_housecode, 21, "alllightson" - ) - if x10_all_lights_off_housecode: - device = insteon_modem.add_x10_device( - x10_all_lights_off_housecode, 22, "alllightsoff" - ) for device in x10_devices: housecode = device.get(CONF_HOUSECODE) unitcode = device.get(CONF_UNITCODE) - x10_type = "onoff" + x10_type = "on_off" steps = device.get(CONF_DIM_STEPS, 22) if device.get(CONF_PLATFORM) == "light": x10_type = "dimmable" @@ -117,8 +154,7 @@ async def async_setup(hass, config): _LOGGER.debug( "Adding X10 device to Insteon: %s %d %s", housecode, unitcode, x10_type ) - device = insteon_modem.add_x10_device(housecode, unitcode, x10_type) - if device and hasattr(device.states[0x01], "steps"): - device.states[0x01].steps = steps + device = devices.add_x10_device(housecode, unitcode, x10_type, steps) + asyncio.create_task(async_setup_platforms(hass, config)) return True diff --git a/homeassistant/components/insteon/binary_sensor.py b/homeassistant/components/insteon/binary_sensor.py index 81c3c58ef12..cd74f738187 100644 --- a/homeassistant/components/insteon/binary_sensor.py +++ b/homeassistant/components/insteon/binary_sensor.py @@ -1,50 +1,69 @@ """Support for INSTEON dimmers via PowerLinc Modem.""" import logging -from homeassistant.components.binary_sensor import BinarySensorEntity +from pyinsteon.groups import ( + CO_SENSOR, + DOOR_SENSOR, + HEARTBEAT, + LEAK_SENSOR_WET, + LIGHT_SENSOR, + LOW_BATTERY, + MOTION_SENSOR, + OPEN_CLOSE_SENSOR, + SENSOR_MALFUNCTION, + SMOKE_SENSOR, + TEST_SENSOR, +) + +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_BATTERY, + DEVICE_CLASS_DOOR, + DEVICE_CLASS_GAS, + DEVICE_CLASS_LIGHT, + DEVICE_CLASS_MOISTURE, + DEVICE_CLASS_MOTION, + DEVICE_CLASS_OPENING, + DEVICE_CLASS_PROBLEM, + DEVICE_CLASS_SAFETY, + DEVICE_CLASS_SMOKE, + DOMAIN, + BinarySensorEntity, +) from .insteon_entity import InsteonEntity +from .utils import async_add_insteon_entities _LOGGER = logging.getLogger(__name__) SENSOR_TYPES = { - "openClosedSensor": "opening", - "ioLincSensor": "opening", - "motionSensor": "motion", - "doorSensor": "door", - "wetLeakSensor": "moisture", - "lightSensor": "light", - "batterySensor": "battery", + OPEN_CLOSE_SENSOR: DEVICE_CLASS_OPENING, + MOTION_SENSOR: DEVICE_CLASS_MOTION, + DOOR_SENSOR: DEVICE_CLASS_DOOR, + LEAK_SENSOR_WET: DEVICE_CLASS_MOISTURE, + LIGHT_SENSOR: DEVICE_CLASS_LIGHT, + LOW_BATTERY: DEVICE_CLASS_BATTERY, + CO_SENSOR: DEVICE_CLASS_GAS, + SMOKE_SENSOR: DEVICE_CLASS_SMOKE, + TEST_SENSOR: DEVICE_CLASS_SAFETY, + SENSOR_MALFUNCTION: DEVICE_CLASS_PROBLEM, + HEARTBEAT: DEVICE_CLASS_PROBLEM, } async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Set up the INSTEON device class for the hass platform.""" - insteon_modem = hass.data["insteon"].get("modem") - - address = discovery_info["address"] - device = insteon_modem.devices[address] - state_key = discovery_info["state_key"] - name = device.states[state_key].name - if name != "dryLeakSensor": - _LOGGER.debug( - "Adding device %s entity %s to Binary Sensor platform", - device.address.hex, - name, - ) - - new_entity = InsteonBinarySensor(device, state_key) - - async_add_entities([new_entity]) + """Set up the INSTEON entity class for the hass platform.""" + async_add_insteon_entities( + hass, DOMAIN, InsteonBinarySensorEntity, async_add_entities, discovery_info + ) -class InsteonBinarySensor(InsteonEntity, BinarySensorEntity): - """A Class for an Insteon device entity.""" +class InsteonBinarySensorEntity(InsteonEntity, BinarySensorEntity): + """A Class for an Insteon binary sensor entity.""" - def __init__(self, device, state_key): + def __init__(self, device, group): """Initialize the INSTEON binary sensor.""" - super().__init__(device, state_key) - self._sensor_type = SENSOR_TYPES.get(self._insteon_device_state.name) + super().__init__(device, group) + self._sensor_type = SENSOR_TYPES.get(self._insteon_device_group.name) @property def device_class(self): @@ -54,9 +73,4 @@ class InsteonBinarySensor(InsteonEntity, BinarySensorEntity): @property def is_on(self): """Return the boolean response if the node is on.""" - on_val = bool(self._insteon_device_state.value) - - if self._insteon_device_state.name in ["lightSensor", "ioLincSensor"]: - return not on_val - - return on_val + return bool(self._insteon_device_group.value) diff --git a/homeassistant/components/insteon/const.py b/homeassistant/components/insteon/const.py index b01409f49ff..950efd8dc7f 100644 --- a/homeassistant/components/insteon/const.py +++ b/homeassistant/components/insteon/const.py @@ -1,7 +1,46 @@ """Constants used by insteon component.""" +from pyinsteon.groups import ( + CO_SENSOR, + COVER, + DIMMABLE_FAN, + DIMMABLE_LIGHT, + DIMMABLE_LIGHT_MAIN, + DIMMABLE_OUTLET, + DOOR_SENSOR, + HEARTBEAT, + LEAK_SENSOR_WET, + LIGHT_SENSOR, + LOW_BATTERY, + MOTION_SENSOR, + NEW_SENSOR, + ON_OFF_OUTLET_BOTTOM, + ON_OFF_OUTLET_TOP, + ON_OFF_SWITCH, + ON_OFF_SWITCH_A, + ON_OFF_SWITCH_B, + ON_OFF_SWITCH_C, + ON_OFF_SWITCH_D, + ON_OFF_SWITCH_E, + ON_OFF_SWITCH_F, + ON_OFF_SWITCH_G, + ON_OFF_SWITCH_H, + ON_OFF_SWITCH_MAIN, + OPEN_CLOSE_SENSOR, + RELAY, + SENSOR_MALFUNCTION, + SMOKE_SENSOR, + TEST_SENSOR, +) DOMAIN = "insteon" -INSTEON_ENTITIES = "entities" + +INSTEON_COMPONENTS = [ + "binary_sensor", + "cover", + "fan", + "light", + "switch", +] CONF_IP_PORT = "ip_port" CONF_HUB_USERNAME = "username" @@ -40,6 +79,7 @@ SRV_SCENE_OFF = "scene_off" SIGNAL_LOAD_ALDB = "load_aldb" SIGNAL_PRINT_ALDB = "print_aldb" +SIGNAL_SAVE_DEVICES = "save_devices" HOUSECODES = [ "a", @@ -60,47 +100,42 @@ HOUSECODES = [ "p", ] -BUTTON_PRESSED_STATE_NAME = "onLevelButton" -EVENT_BUTTON_ON = "insteon.button_on" -EVENT_BUTTON_OFF = "insteon.button_off" +EVENT_GROUP_ON = "insteon.button_on" +EVENT_GROUP_OFF = "insteon.button_off" +EVENT_GROUP_ON_FAST = "insteon.button_on_fast" +EVENT_GROUP_OFF_FAST = "insteon.button_off_fast" EVENT_CONF_BUTTON = "button" - +ON_OFF_EVENTS = "on_off_events" STATE_NAME_LABEL_MAP = { - "keypadButtonA": "Button A", - "keypadButtonB": "Button B", - "keypadButtonC": "Button C", - "keypadButtonD": "Button D", - "keypadButtonE": "Button E", - "keypadButtonF": "Button F", - "keypadButtonG": "Button G", - "keypadButtonH": "Button H", - "keypadButtonMain": "Main", - "onOffButtonA": "Button A", - "onOffButtonB": "Button B", - "onOffButtonC": "Button C", - "onOffButtonD": "Button D", - "onOffButtonE": "Button E", - "onOffButtonF": "Button F", - "onOffButtonG": "Button G", - "onOffButtonH": "Button H", - "onOffButtonMain": "Main", - "fanOnLevel": "Fan", - "lightOnLevel": "Light", - "coolSetPoint": "Cool Set", - "heatSetPoint": "HeatSet", - "statusReport": "Status", - "generalSensor": "Sensor", - "motionSensor": "Motion", - "lightSensor": "Light", - "batterySensor": "Battery", - "dryLeakSensor": "Dry", - "wetLeakSensor": "Wet", - "heartbeatLeakSensor": "Heartbeat", - "openClosedRelay": "Relay", - "openClosedSensor": "Sensor", - "lightOnOff": "Light", - "outletTopOnOff": "Top", - "outletBottomOnOff": "Bottom", - "coverOpenLevel": "Cover", + DIMMABLE_LIGHT_MAIN: "Main", + ON_OFF_SWITCH_A: "Button A", + ON_OFF_SWITCH_B: "Button B", + ON_OFF_SWITCH_C: "Button C", + ON_OFF_SWITCH_D: "Button D", + ON_OFF_SWITCH_E: "Button E", + ON_OFF_SWITCH_F: "Button F", + ON_OFF_SWITCH_G: "Button G", + ON_OFF_SWITCH_H: "Button H", + ON_OFF_SWITCH_MAIN: "Main", + DIMMABLE_FAN: "Fan", + DIMMABLE_LIGHT: "Light", + DIMMABLE_OUTLET: "Outlet", + MOTION_SENSOR: "Motion", + LIGHT_SENSOR: "Light", + LOW_BATTERY: "Battery", + LEAK_SENSOR_WET: "Wet", + DOOR_SENSOR: "Door", + SMOKE_SENSOR: "Smoke", + CO_SENSOR: "Carbon Monoxide", + TEST_SENSOR: "Test", + NEW_SENSOR: "New", + SENSOR_MALFUNCTION: "Malfunction", + HEARTBEAT: "Heartbeat", + OPEN_CLOSE_SENSOR: "Sensor", + ON_OFF_SWITCH: "Light", + ON_OFF_OUTLET_TOP: "Top", + ON_OFF_OUTLET_BOTTOM: "Bottom", + COVER: "Cover", + RELAY: "Relay", } diff --git a/homeassistant/components/insteon/cover.py b/homeassistant/components/insteon/cover.py index b325a6ebd84..75c336822d7 100644 --- a/homeassistant/components/insteon/cover.py +++ b/homeassistant/components/insteon/cover.py @@ -4,6 +4,7 @@ import math from homeassistant.components.cover import ( ATTR_POSITION, + DOMAIN, SUPPORT_CLOSE, SUPPORT_OPEN, SUPPORT_SET_POSITION, @@ -11,6 +12,7 @@ from homeassistant.components.cover import ( ) from .insteon_entity import InsteonEntity +from .utils import async_add_insteon_entities _LOGGER = logging.getLogger(__name__) @@ -19,33 +21,18 @@ SUPPORTED_FEATURES = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the Insteon platform.""" - if not discovery_info: - return - - insteon_modem = hass.data["insteon"].get("modem") - - address = discovery_info["address"] - device = insteon_modem.devices[address] - state_key = discovery_info["state_key"] - - _LOGGER.debug( - "Adding device %s entity %s to Cover platform", - device.address.hex, - device.states[state_key].name, + async_add_insteon_entities( + hass, DOMAIN, InsteonCoverEntity, async_add_entities, discovery_info ) - new_entity = InsteonCoverEntity(device, state_key) - - async_add_entities([new_entity]) - class InsteonCoverEntity(InsteonEntity, CoverEntity): - """A Class for an Insteon device.""" + """A Class for an Insteon cover entity.""" @property def current_cover_position(self): """Return the current cover position.""" - return int(math.ceil(self._insteon_device_state.value * 100 / 255)) + return int(math.ceil(self._insteon_device_group.value * 100 / 255)) @property def supported_features(self): @@ -58,17 +45,19 @@ class InsteonCoverEntity(InsteonEntity, CoverEntity): return bool(self.current_cover_position) async def async_open_cover(self, **kwargs): - """Open device.""" - self._insteon_device_state.open() + """Open cover.""" + await self._insteon_device.async_open() async def async_close_cover(self, **kwargs): - """Close device.""" - self._insteon_device_state.close() + """Close cover.""" + await self._insteon_device.async_close() async def async_set_cover_position(self, **kwargs): """Set the cover position.""" position = int(kwargs[ATTR_POSITION] * 255 / 100) if position == 0: - self._insteon_device_state.close() + await self._insteon_device.async_close() else: - self._insteon_device_state.set_position(position) + await self._insteon_device.async_open( + position=position, group=self._insteon_device_group.group + ) diff --git a/homeassistant/components/insteon/fan.py b/homeassistant/components/insteon/fan.py index 6ad7436faf5..3b324b97782 100644 --- a/homeassistant/components/insteon/fan.py +++ b/homeassistant/components/insteon/fan.py @@ -1,7 +1,10 @@ """Support for INSTEON fans via PowerLinc Modem.""" import logging +from pyinsteon.constants import FanSpeed + from homeassistant.components.fan import ( + DOMAIN, SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM, @@ -9,43 +12,40 @@ from homeassistant.components.fan import ( SUPPORT_SET_SPEED, FanEntity, ) -from homeassistant.const import STATE_OFF from .insteon_entity import InsteonEntity +from .utils import async_add_insteon_entities _LOGGER = logging.getLogger(__name__) - -SPEED_TO_HEX = {SPEED_OFF: 0x00, SPEED_LOW: 0x3F, SPEED_MEDIUM: 0xBE, SPEED_HIGH: 0xFF} - -FAN_SPEEDS = [STATE_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH] +FAN_SPEEDS = [SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH] +SPEED_TO_VALUE = { + SPEED_OFF: FanSpeed.OFF, + SPEED_LOW: FanSpeed.LOW, + SPEED_MEDIUM: FanSpeed.MEDIUM, + SPEED_HIGH: FanSpeed.HIGH, +} async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Set up the INSTEON device class for the hass platform.""" - insteon_modem = hass.data["insteon"].get("modem") - - address = discovery_info["address"] - device = insteon_modem.devices[address] - state_key = discovery_info["state_key"] - - _LOGGER.debug( - "Adding device %s entity %s to Fan platform", - device.address.hex, - device.states[state_key].name, + """Set up the INSTEON entity class for the hass platform.""" + async_add_insteon_entities( + hass, DOMAIN, InsteonFanEntity, async_add_entities, discovery_info ) - new_entity = InsteonFan(device, state_key) - async_add_entities([new_entity]) - - -class InsteonFan(InsteonEntity, FanEntity): - """An INSTEON fan component.""" +class InsteonFanEntity(InsteonEntity, FanEntity): + """An INSTEON fan entity.""" @property def speed(self) -> str: """Return the current speed.""" - return self._hex_to_speed(self._insteon_device_state.value) + if self._insteon_device_group.value == FanSpeed.HIGH: + return SPEED_HIGH + if self._insteon_device_group.value == FanSpeed.MEDIUM: + return SPEED_MEDIUM + if self._insteon_device_group.value == FanSpeed.LOW: + return SPEED_LOW + return SPEED_OFF @property def speed_list(self) -> list: @@ -58,30 +58,19 @@ class InsteonFan(InsteonEntity, FanEntity): return SUPPORT_SET_SPEED async def async_turn_on(self, speed: str = None, **kwargs) -> None: - """Turn on the entity.""" + """Turn on the fan.""" if speed is None: speed = SPEED_MEDIUM await self.async_set_speed(speed) async def async_turn_off(self, **kwargs) -> None: - """Turn off the entity.""" - await self.async_set_speed(SPEED_OFF) + """Turn off the fan.""" + await self._insteon_device.async_fan_off() async def async_set_speed(self, speed: str) -> None: """Set the speed of the fan.""" - fan_speed = SPEED_TO_HEX[speed] - if fan_speed == 0x00: - self._insteon_device_state.off() + fan_speed = SPEED_TO_VALUE[speed] + if fan_speed == FanSpeed.OFF: + await self._insteon_device.async_fan_off() else: - self._insteon_device_state.set_level(fan_speed) - - @staticmethod - def _hex_to_speed(speed: int): - hex_speed = SPEED_OFF - if speed > 0xFE: - hex_speed = SPEED_HIGH - elif speed > 0x7F: - hex_speed = SPEED_MEDIUM - elif speed > 0: - hex_speed = SPEED_LOW - return hex_speed + await self._insteon_device.async_fan_on(on_level=fan_speed) diff --git a/homeassistant/components/insteon/insteon_entity.py b/homeassistant/components/insteon/insteon_entity.py index b453cad2e07..80bb860477e 100644 --- a/homeassistant/components/insteon/insteon_entity.py +++ b/homeassistant/components/insteon/insteon_entity.py @@ -2,14 +2,16 @@ import logging from homeassistant.core import callback -from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.dispatcher import ( + async_dispatcher_connect, + async_dispatcher_send, +) from homeassistant.helpers.entity import Entity from .const import ( - DOMAIN, - INSTEON_ENTITIES, SIGNAL_LOAD_ALDB, SIGNAL_PRINT_ALDB, + SIGNAL_SAVE_DEVICES, STATE_NAME_LABEL_MAP, ) from .utils import print_aldb_to_log @@ -20,11 +22,14 @@ _LOGGER = logging.getLogger(__name__) class InsteonEntity(Entity): """INSTEON abstract base entity.""" - def __init__(self, device, state_key): + def __init__(self, device, group): """Initialize the INSTEON binary sensor.""" - self._insteon_device_state = device.states[state_key] + self._insteon_device_group = device.groups[group] self._insteon_device = device - self._insteon_device.aldb.add_loaded_callback(self._aldb_loaded) + + def __hash__(self): + """Return the hash of the Insteon Entity.""" + return hash(self._insteon_device) @property def should_poll(self): @@ -34,20 +39,20 @@ class InsteonEntity(Entity): @property def address(self): """Return the address of the node.""" - return self._insteon_device.address.human + return str(self._insteon_device.address) @property def group(self): """Return the INSTEON group that the entity responds to.""" - return self._insteon_device_state.group + return self._insteon_device_group.group @property def unique_id(self) -> str: """Return a unique ID.""" - if self._insteon_device_state.group == 0x01: + if self._insteon_device_group.group == 0x01: uid = self._insteon_device.id else: - uid = f"{self._insteon_device.id}_{self._insteon_device_state.group}" + uid = f"{self._insteon_device.id}_{self._insteon_device_group.group}" return uid @property @@ -61,7 +66,7 @@ class InsteonEntity(Entity): extension = self._get_label() if extension: extension = f" {extension}" - return f"{description} {self._insteon_device.address.human}{extension}" + return f"{description} {self._insteon_device.address}{extension}" @property def device_state_attributes(self): @@ -69,56 +74,45 @@ class InsteonEntity(Entity): return {"insteon_address": self.address, "insteon_group": self.group} @callback - def async_entity_update(self, deviceid, group, val): + def async_entity_update(self, name, address, value, group): """Receive notification from transport that new data exists.""" _LOGGER.debug( - "Received update for device %s group %d value %s", - deviceid.human, - group, - val, + "Received update for device %s group %d value %s", address, group, value, ) self.async_write_ha_state() async def async_added_to_hass(self): """Register INSTEON update events.""" _LOGGER.debug( - "Tracking updates for device %s group %d statename %s", + "Tracking updates for device %s group %d name %s", self.address, self.group, - self._insteon_device_state.name, + self._insteon_device_group.name, ) - self._insteon_device_state.register_updates(self.async_entity_update) - self.hass.data[DOMAIN][INSTEON_ENTITIES].add(self.entity_id) + self._insteon_device_group.subscribe(self.async_entity_update) load_signal = f"{self.entity_id}_{SIGNAL_LOAD_ALDB}" self.async_on_remove( - async_dispatcher_connect(self.hass, load_signal, self._load_aldb) + async_dispatcher_connect(self.hass, load_signal, self._async_read_aldb) ) print_signal = f"{self.entity_id}_{SIGNAL_PRINT_ALDB}" - self.async_on_remove( - async_dispatcher_connect(self.hass, print_signal, self._print_aldb) - ) + async_dispatcher_connect(self.hass, print_signal, self._print_aldb) - def _load_aldb(self, reload=False): - """Load the device All-Link Database.""" - if reload: - self._insteon_device.aldb.clear() - self._insteon_device.read_aldb() + async def _async_read_aldb(self, reload): + """Call device load process and print to log.""" + await self._insteon_device.aldb.async_load(refresh=reload) + self._print_aldb() + async_dispatcher_send(self.hass, SIGNAL_SAVE_DEVICES) def _print_aldb(self): """Print the device ALDB to the log file.""" print_aldb_to_log(self._insteon_device.aldb) - @callback - def _aldb_loaded(self): - """All-Link Database loaded for the device.""" - self._print_aldb() - def _get_label(self): """Get the device label for grouped devices.""" label = "" - if len(self._insteon_device.states) > 1: - if self._insteon_device_state.name in STATE_NAME_LABEL_MAP: - label = STATE_NAME_LABEL_MAP[self._insteon_device_state.name] + if len(self._insteon_device.groups) > 1: + if self._insteon_device_group.name in STATE_NAME_LABEL_MAP: + label = STATE_NAME_LABEL_MAP[self._insteon_device_group.name] else: label = f"Group {self.group:d}" return label diff --git a/homeassistant/components/insteon/ipdb.py b/homeassistant/components/insteon/ipdb.py index 6aba40d6df9..5d0913185b1 100644 --- a/homeassistant/components/insteon/ipdb.py +++ b/homeassistant/components/insteon/ipdb.py @@ -1,81 +1,112 @@ -"""Insteon product database.""" -import collections +"""Utility methods for the Insteon platform.""" +import logging -from insteonplm.states.cover import Cover -from insteonplm.states.dimmable import ( - DimmableKeypadA, - DimmableRemote, - DimmableSwitch, - DimmableSwitch_Fan, -) -from insteonplm.states.onOff import ( - OnOffKeypad, - OnOffKeypadA, - OnOffSwitch, - OnOffSwitch_OutletBottom, - OnOffSwitch_OutletTop, - OpenClosedRelay, -) -from insteonplm.states.sensor import ( - IoLincSensor, - LeakSensorDryWet, - OnOffSensor, - SmokeCO2Sensor, - VariableSensor, -) -from insteonplm.states.x10 import ( - X10AllLightsOffSensor, - X10AllLightsOnSensor, - X10AllUnitsOffSensor, - X10DimmableSwitch, +from pyinsteon.device_types import ( + DimmableLightingControl, + DimmableLightingControl_DinRail, + DimmableLightingControl_FanLinc, + DimmableLightingControl_InLineLinc, + DimmableLightingControl_KeypadLinc_6, + DimmableLightingControl_KeypadLinc_8, + DimmableLightingControl_LampLinc, + DimmableLightingControl_OutletLinc, + DimmableLightingControl_SwitchLinc, + DimmableLightingControl_ToggleLinc, + GeneralController_ControlLinc, + GeneralController_MiniRemote_4, + GeneralController_MiniRemote_8, + GeneralController_MiniRemote_Switch, + GeneralController_RemoteLinc, + SecurityHealthSafety_DoorSensor, + SecurityHealthSafety_LeakSensor, + SecurityHealthSafety_MotionSensor, + SecurityHealthSafety_OpenCloseSensor, + SecurityHealthSafety_Smokebridge, + SensorsActuators_IOLink, + SwitchedLightingControl, + SwitchedLightingControl_ApplianceLinc, + SwitchedLightingControl_DinRail, + SwitchedLightingControl_InLineLinc, + SwitchedLightingControl_KeypadLinc_6, + SwitchedLightingControl_KeypadLinc_8, + SwitchedLightingControl_OnOffOutlet, + SwitchedLightingControl_OutletLinc, + SwitchedLightingControl_SwitchLinc, + SwitchedLightingControl_ToggleLinc, + WindowCovering, + X10Dimmable, + X10OnOff, X10OnOffSensor, - X10OnOffSwitch, ) -State = collections.namedtuple("Product", "stateType platform") +from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR +from homeassistant.components.cover import DOMAIN as COVER +from homeassistant.components.fan import DOMAIN as FAN +from homeassistant.components.light import DOMAIN as LIGHT +from homeassistant.components.switch import DOMAIN as SWITCH + +from .const import ON_OFF_EVENTS + +_LOGGER = logging.getLogger(__name__) + +DEVICE_PLATFORM = { + DimmableLightingControl: {LIGHT: [1], ON_OFF_EVENTS: [1]}, + DimmableLightingControl_DinRail: {LIGHT: [1], ON_OFF_EVENTS: [1]}, + DimmableLightingControl_FanLinc: {LIGHT: [1], FAN: [2], ON_OFF_EVENTS: [1, 2]}, + DimmableLightingControl_InLineLinc: {LIGHT: [1], ON_OFF_EVENTS: [1]}, + DimmableLightingControl_KeypadLinc_6: { + LIGHT: [1], + SWITCH: [3, 4, 5, 6], + ON_OFF_EVENTS: [1, 3, 4, 5, 6], + }, + DimmableLightingControl_KeypadLinc_8: { + LIGHT: [1], + SWITCH: range(2, 9), + ON_OFF_EVENTS: range(1, 9), + }, + DimmableLightingControl_LampLinc: {LIGHT: [1], ON_OFF_EVENTS: [1]}, + DimmableLightingControl_OutletLinc: {LIGHT: [1], ON_OFF_EVENTS: [1]}, + DimmableLightingControl_SwitchLinc: {LIGHT: [1], ON_OFF_EVENTS: [1]}, + DimmableLightingControl_ToggleLinc: {LIGHT: [1], ON_OFF_EVENTS: [1]}, + GeneralController_ControlLinc: {ON_OFF_EVENTS: [1]}, + GeneralController_MiniRemote_4: {ON_OFF_EVENTS: range(1, 5)}, + GeneralController_MiniRemote_8: {ON_OFF_EVENTS: range(1, 9)}, + GeneralController_MiniRemote_Switch: {ON_OFF_EVENTS: [1, 2]}, + GeneralController_RemoteLinc: {ON_OFF_EVENTS: [1]}, + SecurityHealthSafety_DoorSensor: {BINARY_SENSOR: [1, 3, 4], ON_OFF_EVENTS: [1]}, + SecurityHealthSafety_LeakSensor: {BINARY_SENSOR: [2, 4]}, + SecurityHealthSafety_MotionSensor: {BINARY_SENSOR: [1, 2, 3], ON_OFF_EVENTS: [1]}, + SecurityHealthSafety_OpenCloseSensor: {BINARY_SENSOR: [1]}, + SecurityHealthSafety_Smokebridge: {BINARY_SENSOR: [1]}, + SensorsActuators_IOLink: {SWITCH: [1], BINARY_SENSOR: [2], ON_OFF_EVENTS: [1, 2]}, + SwitchedLightingControl: {SWITCH: [1], ON_OFF_EVENTS: [1]}, + SwitchedLightingControl_ApplianceLinc: {SWITCH: [1], ON_OFF_EVENTS: [1]}, + SwitchedLightingControl_DinRail: {SWITCH: [1], ON_OFF_EVENTS: [1]}, + SwitchedLightingControl_InLineLinc: {SWITCH: [1], ON_OFF_EVENTS: [1]}, + SwitchedLightingControl_KeypadLinc_6: { + SWITCH: [1, 3, 4, 5, 6], + ON_OFF_EVENTS: [1, 3, 4, 5, 6], + }, + SwitchedLightingControl_KeypadLinc_8: { + SWITCH: range(1, 9), + ON_OFF_EVENTS: range(1, 9), + }, + SwitchedLightingControl_OnOffOutlet: {SWITCH: [1, 2], ON_OFF_EVENTS: [1, 2]}, + SwitchedLightingControl_OutletLinc: {SWITCH: [1], ON_OFF_EVENTS: [1]}, + SwitchedLightingControl_SwitchLinc: {SWITCH: [1], ON_OFF_EVENTS: [1]}, + SwitchedLightingControl_ToggleLinc: {SWITCH: [1], ON_OFF_EVENTS: [1]}, + WindowCovering: {COVER: [1]}, + X10Dimmable: {LIGHT: [1]}, + X10OnOff: {SWITCH: [1]}, + X10OnOffSensor: {BINARY_SENSOR: [1]}, +} -class IPDB: - """Embodies the INSTEON Product Database static data and access methods.""" +def get_device_platforms(device): + """Return the HA platforms for a device type.""" + return DEVICE_PLATFORM.get(type(device), {}).keys() - def __init__(self): - """Create the INSTEON Product Database (IPDB).""" - self.states = [ - State(Cover, "cover"), - State(OnOffSwitch_OutletTop, "switch"), - State(OnOffSwitch_OutletBottom, "switch"), - State(OpenClosedRelay, "switch"), - State(OnOffSwitch, "switch"), - State(OnOffKeypadA, "switch"), - State(OnOffKeypad, "switch"), - State(LeakSensorDryWet, "binary_sensor"), - State(IoLincSensor, "binary_sensor"), - State(SmokeCO2Sensor, "sensor"), - State(OnOffSensor, "binary_sensor"), - State(VariableSensor, "sensor"), - State(DimmableSwitch_Fan, "fan"), - State(DimmableSwitch, "light"), - State(DimmableRemote, "on_off_events"), - State(DimmableKeypadA, "light"), - State(X10DimmableSwitch, "light"), - State(X10OnOffSwitch, "switch"), - State(X10OnOffSensor, "binary_sensor"), - State(X10AllUnitsOffSensor, "binary_sensor"), - State(X10AllLightsOnSensor, "binary_sensor"), - State(X10AllLightsOffSensor, "binary_sensor"), - ] - def __len__(self): - """Return the number of INSTEON state types mapped to HA platforms.""" - return len(self.states) - - def __iter__(self): - """Itterate through the INSTEON state types to HA platforms.""" - yield from self.states - - def __getitem__(self, key): - """Return a Home Assistant platform from an INSTEON state type.""" - for state in self.states: - if isinstance(key, state.stateType): - return state - return None +def get_platform_groups(device, domain) -> dict: + """Return the platforms that a device belongs in.""" + return DEVICE_PLATFORM.get(type(device), {}).get(domain, {}) diff --git a/homeassistant/components/insteon/light.py b/homeassistant/components/insteon/light.py index afd575c363b..5ad02b6da5e 100644 --- a/homeassistant/components/insteon/light.py +++ b/homeassistant/components/insteon/light.py @@ -3,11 +3,13 @@ import logging from homeassistant.components.light import ( ATTR_BRIGHTNESS, + DOMAIN, SUPPORT_BRIGHTNESS, LightEntity, ) from .insteon_entity import InsteonEntity +from .utils import async_add_insteon_entities _LOGGER = logging.getLogger(__name__) @@ -16,31 +18,18 @@ MAX_BRIGHTNESS = 255 async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the Insteon component.""" - insteon_modem = hass.data["insteon"].get("modem") - - address = discovery_info["address"] - device = insteon_modem.devices[address] - state_key = discovery_info["state_key"] - - _LOGGER.debug( - "Adding device %s entity %s to Light platform", - device.address.hex, - device.states[state_key].name, + async_add_insteon_entities( + hass, DOMAIN, InsteonDimmerEntity, async_add_entities, discovery_info ) - new_entity = InsteonDimmerDevice(device, state_key) - async_add_entities([new_entity]) - - -class InsteonDimmerDevice(InsteonEntity, LightEntity): - """A Class for an Insteon device.""" +class InsteonDimmerEntity(InsteonEntity, LightEntity): + """A Class for an Insteon light entity.""" @property def brightness(self): """Return the brightness of this light between 0..255.""" - onlevel = self._insteon_device_state.value - return int(onlevel) + return self._insteon_device_group.value @property def is_on(self): @@ -53,13 +42,15 @@ class InsteonDimmerDevice(InsteonEntity, LightEntity): return SUPPORT_BRIGHTNESS async def async_turn_on(self, **kwargs): - """Turn device on.""" + """Turn light on.""" if ATTR_BRIGHTNESS in kwargs: brightness = int(kwargs[ATTR_BRIGHTNESS]) - self._insteon_device_state.set_level(brightness) + await self._insteon_device.async_on( + on_level=brightness, group=self._insteon_device_group.group + ) else: - self._insteon_device_state.on() + await self._insteon_device.async_on(group=self._insteon_device_group.group) async def async_turn_off(self, **kwargs): - """Turn device off.""" - self._insteon_device_state.off() + """Turn light off.""" + await self._insteon_device.async_off(self._insteon_device_group.group) diff --git a/homeassistant/components/insteon/manifest.json b/homeassistant/components/insteon/manifest.json index 8410c6b6ef4..87ad80047d8 100644 --- a/homeassistant/components/insteon/manifest.json +++ b/homeassistant/components/insteon/manifest.json @@ -2,6 +2,6 @@ "domain": "insteon", "name": "Insteon", "documentation": "https://www.home-assistant.io/integrations/insteon", - "requirements": ["insteonplm==0.16.8"], - "codeowners": [] -} + "requirements": ["pyinsteon==1.0.0"], + "codeowners": ["@teharris1"] +} \ No newline at end of file diff --git a/homeassistant/components/insteon/schemas.py b/homeassistant/components/insteon/schemas.py index e3f2644ac56..b3192fc8f66 100644 --- a/homeassistant/components/insteon/schemas.py +++ b/homeassistant/components/insteon/schemas.py @@ -11,7 +11,6 @@ from homeassistant.const import ( CONF_PLATFORM, CONF_PORT, ENTITY_MATCH_ALL, - ENTITY_MATCH_NONE, ) import homeassistant.helpers.config_validation as cv @@ -57,7 +56,6 @@ def set_default_port(schema: Dict) -> Dict: CONF_DEVICE_OVERRIDE_SCHEMA = vol.All( - cv.deprecated(CONF_PLATFORM), vol.Schema( { vol.Required(CONF_ADDRESS): cv.string, @@ -86,6 +84,9 @@ CONF_X10_SCHEMA = vol.All( CONFIG_SCHEMA = vol.Schema( { DOMAIN: vol.All( + cv.deprecated(CONF_X10_ALL_UNITS_OFF), + cv.deprecated(CONF_X10_ALL_LIGHTS_ON), + cv.deprecated(CONF_X10_ALL_LIGHTS_OFF), vol.Schema( { vol.Exclusive( @@ -101,9 +102,6 @@ CONFIG_SCHEMA = vol.Schema( vol.Optional(CONF_OVERRIDE): vol.All( cv.ensure_list_csv, [CONF_DEVICE_OVERRIDE_SCHEMA] ), - vol.Optional(CONF_X10_ALL_UNITS_OFF): vol.In(HOUSECODES), - vol.Optional(CONF_X10_ALL_LIGHTS_ON): vol.In(HOUSECODES), - vol.Optional(CONF_X10_ALL_LIGHTS_OFF): vol.In(HOUSECODES), vol.Optional(CONF_X10): vol.All( cv.ensure_list_csv, [CONF_X10_SCHEMA] ), @@ -134,9 +132,7 @@ DEL_ALL_LINK_SCHEMA = vol.Schema( LOAD_ALDB_SCHEMA = vol.Schema( { - vol.Required(CONF_ENTITY_ID): vol.Any( - cv.entity_id, ENTITY_MATCH_ALL, ENTITY_MATCH_NONE - ), + vol.Required(CONF_ENTITY_ID): vol.Any(cv.entity_id, ENTITY_MATCH_ALL), vol.Optional(SRV_LOAD_DB_RELOAD, default=False): cv.boolean, } ) diff --git a/homeassistant/components/insteon/sensor.py b/homeassistant/components/insteon/sensor.py deleted file mode 100644 index 475723b105d..00000000000 --- a/homeassistant/components/insteon/sensor.py +++ /dev/null @@ -1,31 +0,0 @@ -"""Support for INSTEON dimmers via PowerLinc Modem.""" -import logging - -from homeassistant.helpers.entity import Entity - -from .insteon_entity import InsteonEntity - -_LOGGER = logging.getLogger(__name__) - - -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Set up the INSTEON device class for the hass platform.""" - insteon_modem = hass.data["insteon"].get("modem") - - address = discovery_info["address"] - device = insteon_modem.devices[address] - state_key = discovery_info["state_key"] - - _LOGGER.debug( - "Adding device %s entity %s to Sensor platform", - device.address.hex, - device.states[state_key].name, - ) - - new_entity = InsteonSensorDevice(device, state_key) - - async_add_entities([new_entity]) - - -class InsteonSensorDevice(InsteonEntity, Entity): - """A Class for an Insteon device.""" diff --git a/homeassistant/components/insteon/switch.py b/homeassistant/components/insteon/switch.py index 3a0668459c9..9d4e12b0b46 100644 --- a/homeassistant/components/insteon/switch.py +++ b/homeassistant/components/insteon/switch.py @@ -1,66 +1,33 @@ """Support for INSTEON dimmers via PowerLinc Modem.""" import logging -from homeassistant.components.switch import SwitchEntity +from homeassistant.components.switch import DOMAIN, SwitchEntity from .insteon_entity import InsteonEntity +from .utils import async_add_insteon_entities _LOGGER = logging.getLogger(__name__) async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Set up the INSTEON device class for the hass platform.""" - insteon_modem = hass.data["insteon"].get("modem") - - address = discovery_info["address"] - device = insteon_modem.devices[address] - state_key = discovery_info["state_key"] - - state_name = device.states[state_key].name - - _LOGGER.debug( - "Adding device %s entity %s to Switch platform", device.address.hex, state_name, + """Set up the INSTEON entity class for the hass platform.""" + async_add_insteon_entities( + hass, DOMAIN, InsteonSwitchEntity, async_add_entities, discovery_info ) - new_entity = None - if state_name == "openClosedRelay": - new_entity = InsteonOpenClosedDevice(device, state_key) - else: - new_entity = InsteonSwitchDevice(device, state_key) - if new_entity is not None: - async_add_entities([new_entity]) - - -class InsteonSwitchDevice(InsteonEntity, SwitchEntity): - """A Class for an Insteon device.""" +class InsteonSwitchEntity(InsteonEntity, SwitchEntity): + """A Class for an Insteon switch entity.""" @property def is_on(self): """Return the boolean response if the node is on.""" - return bool(self._insteon_device_state.value) + return bool(self._insteon_device_group.value) async def async_turn_on(self, **kwargs): - """Turn device on.""" - self._insteon_device_state.on() + """Turn switch on.""" + await self._insteon_device.async_on(group=self._insteon_device_group.group) async def async_turn_off(self, **kwargs): - """Turn device off.""" - self._insteon_device_state.off() - - -class InsteonOpenClosedDevice(InsteonEntity, SwitchEntity): - """A Class for an Insteon device.""" - - @property - def is_on(self): - """Return the boolean response if the node is on.""" - return bool(self._insteon_device_state.value) - - async def async_turn_on(self, **kwargs): - """Turn device on.""" - self._insteon_device_state.open() - - async def async_turn_off(self, **kwargs): - """Turn device off.""" - self._insteon_device_state.close() + """Turn switch off.""" + await self._insteon_device.async_off(group=self._insteon_device_group.group) diff --git a/homeassistant/components/insteon/utils.py b/homeassistant/components/insteon/utils.py index 26768936291..c0b93d93485 100644 --- a/homeassistant/components/insteon/utils.py +++ b/homeassistant/components/insteon/utils.py @@ -1,23 +1,44 @@ """Utilities used by insteon component.""" - +import asyncio import logging -from insteonplm.devices import ALDBStatus +from pyinsteon import devices +from pyinsteon.constants import ALDBStatus +from pyinsteon.events import OFF_EVENT, OFF_FAST_EVENT, ON_EVENT, ON_FAST_EVENT +from pyinsteon.managers.link_manager import ( + async_enter_linking_mode, + async_enter_unlinking_mode, +) +from pyinsteon.managers.scene_manager import ( + async_trigger_scene_off, + async_trigger_scene_on, +) +from pyinsteon.managers.x10_manager import ( + async_x10_all_lights_off, + async_x10_all_lights_on, + async_x10_all_units_off, +) from homeassistant.const import CONF_ADDRESS, CONF_ENTITY_ID, ENTITY_MATCH_ALL from homeassistant.core import callback from homeassistant.helpers import discovery -from homeassistant.helpers.dispatcher import dispatcher_send +from homeassistant.helpers.dispatcher import ( + async_dispatcher_connect, + async_dispatcher_send, + dispatcher_send, +) from .const import ( - BUTTON_PRESSED_STATE_NAME, DOMAIN, - EVENT_BUTTON_OFF, - EVENT_BUTTON_ON, EVENT_CONF_BUTTON, - INSTEON_ENTITIES, + EVENT_GROUP_OFF, + EVENT_GROUP_OFF_FAST, + EVENT_GROUP_ON, + EVENT_GROUP_ON_FAST, + ON_OFF_EVENTS, SIGNAL_LOAD_ALDB, SIGNAL_PRINT_ALDB, + SIGNAL_SAVE_DEVICES, SRV_ADD_ALL_LINK, SRV_ALL_LINK_GROUP, SRV_ALL_LINK_MODE, @@ -34,7 +55,7 @@ from .const import ( SRV_X10_ALL_LIGHTS_ON, SRV_X10_ALL_UNITS_OFF, ) -from .ipdb import IPDB +from .ipdb import get_device_platforms, get_platform_groups from .schemas import ( ADD_ALL_LINK_SCHEMA, DEL_ALL_LINK_SCHEMA, @@ -47,91 +68,129 @@ from .schemas import ( _LOGGER = logging.getLogger(__name__) -def register_new_device_callback(hass, config, insteon_modem): - """Register callback for new Insteon device.""" - - def _fire_button_on_off_event(address, group, val): - # Firing an event when a button is pressed. - device = insteon_modem.devices[address.hex] - state_name = device.states[group].name - button = ( - "" if state_name == BUTTON_PRESSED_STATE_NAME else state_name[-1].lower() - ) - schema = {CONF_ADDRESS: address.hex} - if button: - schema[EVENT_CONF_BUTTON] = button - event = EVENT_BUTTON_ON if val else EVENT_BUTTON_OFF - _LOGGER.debug( - "Firing event %s with address %s and button %s", event, address.hex, button - ) - hass.bus.fire(event, schema) +def add_on_off_event_device(hass, device): + """Register an Insteon device as an on/off event device.""" @callback - def async_new_insteon_device(device): + def async_fire_group_on_off_event(name, address, group, button): + # Firing an event when a button is pressed. + if button and button[-2] == "_": + button_id = button[-1].lower() + else: + button_id = None + + schema = {CONF_ADDRESS: address} + if button_id: + schema[EVENT_CONF_BUTTON] = button_id + if name == ON_EVENT: + event = EVENT_GROUP_ON + if name == OFF_EVENT: + event = EVENT_GROUP_OFF + if name == ON_FAST_EVENT: + event = EVENT_GROUP_ON_FAST + if name == OFF_FAST_EVENT: + event = EVENT_GROUP_OFF_FAST + _LOGGER.debug("Firing event %s with %s", event, schema) + hass.bus.async_fire(event, schema) + + for group in device.events: + if isinstance(group, int): + for event in device.events[group]: + if event in [ + OFF_EVENT, + ON_EVENT, + OFF_FAST_EVENT, + ON_FAST_EVENT, + ]: + _LOGGER.debug( + "Registering on/off event for %s %d %s", + str(device.address), + group, + event, + ) + device.events[group][event].subscribe( + async_fire_group_on_off_event, force_strong_ref=True + ) + + +def register_new_device_callback(hass, config): + """Register callback for new Insteon device.""" + new_device_lock = asyncio.Lock() + + @callback + def async_new_insteon_device(address=None): """Detect device from transport to be delegated to platform.""" - ipdb = IPDB() - for state_key in device.states: - platform_info = ipdb[device.states[state_key]] - if platform_info and platform_info.platform: - platform = platform_info.platform + hass.async_create_task(async_create_new_entities(address)) - if platform == "on_off_events": - device.states[state_key].register_updates(_fire_button_on_off_event) + async def async_create_new_entities(address): + _LOGGER.debug( + "Adding new INSTEON device to Home Assistant with address %s", address + ) + async with new_device_lock: + await devices.async_save(workdir=hass.config.config_dir) + device = devices[address] + await device.async_status() + platforms = get_device_platforms(device) + tasks = [] + for platform in platforms: + if platform == ON_OFF_EVENTS: + add_on_off_event_device(hass, device) - else: - _LOGGER.info( - "New INSTEON device: %s (%s) %s", - device.address, - device.states[state_key].name, + else: + tasks.append( + discovery.async_load_platform( + hass, platform, + DOMAIN, + discovered={"address": device.address.id}, + hass_config=config, ) + ) + await asyncio.gather(*tasks) - hass.async_create_task( - discovery.async_load_platform( - hass, - platform, - DOMAIN, - discovered={ - "address": device.address.id, - "state_key": state_key, - }, - hass_config=config, - ) - ) - - insteon_modem.devices.add_device_callback(async_new_insteon_device) + devices.subscribe(async_new_insteon_device, force_strong_ref=True) @callback -def async_register_services(hass, config, insteon_modem): +def async_register_services(hass): """Register services used by insteon component.""" - def add_all_link(service): + async def async_srv_add_all_link(service): """Add an INSTEON All-Link between two devices.""" group = service.data.get(SRV_ALL_LINK_GROUP) mode = service.data.get(SRV_ALL_LINK_MODE) - link_mode = 1 if mode.lower() == SRV_CONTROLLER else 0 - insteon_modem.start_all_linking(link_mode, group) + link_mode = mode.lower() == SRV_CONTROLLER + await async_enter_linking_mode(link_mode, group) - def del_all_link(service): + async def async_srv_del_all_link(service): """Delete an INSTEON All-Link between two devices.""" group = service.data.get(SRV_ALL_LINK_GROUP) - insteon_modem.start_all_linking(255, group) + await async_enter_unlinking_mode(group) - def load_aldb(service): + async def async_srv_load_aldb(service): """Load the device All-Link database.""" entity_id = service.data[CONF_ENTITY_ID] reload = service.data[SRV_LOAD_DB_RELOAD] if entity_id.lower() == ENTITY_MATCH_ALL: - for entity_id in hass.data[DOMAIN][INSTEON_ENTITIES]: - _send_load_aldb_signal(entity_id, reload) + await async_srv_load_aldb_all(reload) else: - _send_load_aldb_signal(entity_id, reload) + signal = f"{entity_id}_{SIGNAL_LOAD_ALDB}" + async_dispatcher_send(hass, signal, reload) - def _send_load_aldb_signal(entity_id, reload): - """Send the load All-Link database signal to INSTEON entity.""" - signal = f"{entity_id}_{SIGNAL_LOAD_ALDB}" - dispatcher_send(hass, signal, reload) + async def async_srv_load_aldb_all(reload): + """Load the All-Link database for all devices.""" + # Cannot be done concurrently due to issues with the underlying protocol. + for address in devices: + device = devices[address] + if device != devices.modem and device.cat != 0x03: + await device.aldb.async_load( + refresh=reload, callback=async_srv_save_devices + ) + + async def async_srv_save_devices(): + """Write the Insteon device configuration to file.""" + _LOGGER.debug("Saving Insteon devices") + await devices.async_save(hass.config.config_dir) def print_aldb(service): """Print the All-Link Database for a device.""" @@ -145,71 +204,85 @@ def async_register_services(hass, config, insteon_modem): """Print the All-Link Database for a device.""" # For now this sends logs to the log file. # Future direction is to create an INSTEON control panel. - print_aldb_to_log(insteon_modem.aldb) + print_aldb_to_log(devices.modem.aldb) - def x10_all_units_off(service): + async def async_srv_x10_all_units_off(service): """Send the X10 All Units Off command.""" housecode = service.data.get(SRV_HOUSECODE) - insteon_modem.x10_all_units_off(housecode) + await async_x10_all_units_off(housecode) - def x10_all_lights_off(service): + async def async_srv_x10_all_lights_off(service): """Send the X10 All Lights Off command.""" housecode = service.data.get(SRV_HOUSECODE) - insteon_modem.x10_all_lights_off(housecode) + await async_x10_all_lights_off(housecode) - def x10_all_lights_on(service): + async def async_srv_x10_all_lights_on(service): """Send the X10 All Lights On command.""" housecode = service.data.get(SRV_HOUSECODE) - insteon_modem.x10_all_lights_on(housecode) + await async_x10_all_lights_on(housecode) - def scene_on(service): + async def async_srv_scene_on(service): """Trigger an INSTEON scene ON.""" group = service.data.get(SRV_ALL_LINK_GROUP) - insteon_modem.trigger_group_on(group) + await async_trigger_scene_on(group) - def scene_off(service): + async def async_srv_scene_off(service): """Trigger an INSTEON scene ON.""" group = service.data.get(SRV_ALL_LINK_GROUP) - insteon_modem.trigger_group_off(group) + await async_trigger_scene_off(group) hass.services.async_register( - DOMAIN, SRV_ADD_ALL_LINK, add_all_link, schema=ADD_ALL_LINK_SCHEMA + DOMAIN, SRV_ADD_ALL_LINK, async_srv_add_all_link, schema=ADD_ALL_LINK_SCHEMA ) hass.services.async_register( - DOMAIN, SRV_DEL_ALL_LINK, del_all_link, schema=DEL_ALL_LINK_SCHEMA + DOMAIN, SRV_DEL_ALL_LINK, async_srv_del_all_link, schema=DEL_ALL_LINK_SCHEMA ) hass.services.async_register( - DOMAIN, SRV_LOAD_ALDB, load_aldb, schema=LOAD_ALDB_SCHEMA + DOMAIN, SRV_LOAD_ALDB, async_srv_load_aldb, schema=LOAD_ALDB_SCHEMA ) hass.services.async_register( DOMAIN, SRV_PRINT_ALDB, print_aldb, schema=PRINT_ALDB_SCHEMA ) hass.services.async_register(DOMAIN, SRV_PRINT_IM_ALDB, print_im_aldb, schema=None) hass.services.async_register( - DOMAIN, SRV_X10_ALL_UNITS_OFF, x10_all_units_off, schema=X10_HOUSECODE_SCHEMA + DOMAIN, + SRV_X10_ALL_UNITS_OFF, + async_srv_x10_all_units_off, + schema=X10_HOUSECODE_SCHEMA, ) hass.services.async_register( - DOMAIN, SRV_X10_ALL_LIGHTS_OFF, x10_all_lights_off, schema=X10_HOUSECODE_SCHEMA + DOMAIN, + SRV_X10_ALL_LIGHTS_OFF, + async_srv_x10_all_lights_off, + schema=X10_HOUSECODE_SCHEMA, ) hass.services.async_register( - DOMAIN, SRV_X10_ALL_LIGHTS_ON, x10_all_lights_on, schema=X10_HOUSECODE_SCHEMA + DOMAIN, + SRV_X10_ALL_LIGHTS_ON, + async_srv_x10_all_lights_on, + schema=X10_HOUSECODE_SCHEMA, ) hass.services.async_register( - DOMAIN, SRV_SCENE_ON, scene_on, schema=TRIGGER_SCENE_SCHEMA + DOMAIN, SRV_SCENE_ON, async_srv_scene_on, schema=TRIGGER_SCENE_SCHEMA ) hass.services.async_register( - DOMAIN, SRV_SCENE_OFF, scene_off, schema=TRIGGER_SCENE_SCHEMA + DOMAIN, SRV_SCENE_OFF, async_srv_scene_off, schema=TRIGGER_SCENE_SCHEMA ) + async_dispatcher_connect(hass, SIGNAL_SAVE_DEVICES, async_srv_save_devices) _LOGGER.debug("Insteon Services registered") def print_aldb_to_log(aldb): """Print the All-Link Database to the log file.""" - _LOGGER.info("ALDB load status is %s", aldb.status.name) + # This service is useless if the log level is not INFO for the + # insteon component. Setting the log level to INFO and resetting it + # back when we are done + orig_log_level = _LOGGER.level + if orig_log_level > logging.INFO: + _LOGGER.setLevel(logging.INFO) + _LOGGER.info("%s ALDB load status is %s", aldb.address, aldb.status.name) if aldb.status not in [ALDBStatus.LOADED, ALDBStatus.PARTIAL]: - _LOGGER.warning("Device All-Link database not loaded") - _LOGGER.warning("Use service insteon.load_aldb first") - return + _LOGGER.warning("All-Link database not loaded") _LOGGER.info("RecID In Use Mode HWM Group Address Data 1 Data 2 Data 3") _LOGGER.info("----- ------ ---- --- ----- -------- ------ ------ ------") @@ -217,12 +290,30 @@ def print_aldb_to_log(aldb): rec = aldb[mem_addr] # For now we write this to the log # Roadmap is to create a configuration panel - in_use = "Y" if rec.control_flags.is_in_use else "N" - mode = "C" if rec.control_flags.is_controller else "R" - hwm = "Y" if rec.control_flags.is_high_water_mark else "N" + in_use = "Y" if rec.is_in_use else "N" + mode = "C" if rec.is_controller else "R" + hwm = "Y" if rec.is_high_water_mark else "N" log_msg = ( f" {rec.mem_addr:04x} {in_use:s} {mode:s} {hwm:s} " - f"{rec.group:3d} {rec.address.human:s} {rec.data1:3d} " + f"{rec.group:3d} {str(rec.target):s} {rec.data1:3d} " f"{rec.data2:3d} {rec.data3:3d}" ) _LOGGER.info(log_msg) + _LOGGER.setLevel(orig_log_level) + + +@callback +def async_add_insteon_entities( + hass, platform, entity_type, async_add_entities, discovery_info +): + """Add Insteon devices to a platform.""" + new_entities = [] + device_list = [discovery_info.get("address")] if discovery_info else devices + + for address in device_list: + device = devices[address] + groups = get_platform_groups(device, platform) + for group in groups: + new_entities.append(entity_type(device, group)) + if new_entities: + async_add_entities(new_entities) diff --git a/requirements_all.txt b/requirements_all.txt index 264750a0989..ce77e2c1148 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -787,9 +787,6 @@ incomfort-client==0.4.0 # homeassistant.components.influxdb influxdb==5.2.3 -# homeassistant.components.insteon -insteonplm==0.16.8 - # homeassistant.components.iperf3 iperf3==0.1.11 @@ -1377,6 +1374,9 @@ pyialarm==0.3 # homeassistant.components.icloud pyicloud==0.9.7 +# homeassistant.components.insteon +pyinsteon==1.0.0 + # homeassistant.components.intesishome pyintesishome==1.7.4 From b42a197293033a37b081e4b969be6a2a77f14f53 Mon Sep 17 00:00:00 2001 From: springstan <46536646+springstan@users.noreply.github.com> Date: Sun, 17 May 2020 15:58:31 +0200 Subject: [PATCH 067/406] Add and use ELECTRICAL_CURRENT_AMPERE, ELECTRICAL_VOLTAGE_AMPERE constants (#33990) * Add and use ELECTRICAL_CURRENT_AMPERE constant * Add and use ELECTRICAL_VOLTAGE_AMPERE constant * Rename ELECTRICAL_VOLTAGE_AMPERE to ELECTRICAL_VOLT_AMPERE * Fix imports --- homeassistant/components/apcupsd/sensor.py | 10 ++++--- .../components/growatt_server/sensor.py | 29 ++++++++++++++++--- homeassistant/components/juicenet/sensor.py | 3 +- homeassistant/components/keba/sensor.py | 15 ++++++++-- homeassistant/components/mysensors/sensor.py | 6 ++-- homeassistant/components/nut/const.py | 29 +++++++++++++++---- homeassistant/components/onewire/sensor.py | 3 +- homeassistant/components/smappee/sensor.py | 3 +- .../components/solaredge_local/sensor.py | 3 +- homeassistant/const.py | 4 +++ 10 files changed, 83 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/apcupsd/sensor.py b/homeassistant/components/apcupsd/sensor.py index 44c1c498c28..3a8619a092f 100644 --- a/homeassistant/components/apcupsd/sensor.py +++ b/homeassistant/components/apcupsd/sensor.py @@ -7,6 +7,8 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( CONF_RESOURCES, + ELECTRICAL_CURRENT_AMPERE, + ELECTRICAL_VOLT_AMPERE, FREQUENCY_HERTZ, POWER_WATT, TEMP_CELSIUS, @@ -67,9 +69,9 @@ SENSOR_TYPES = { "nominv": ["Nominal Input Voltage", VOLT, "mdi:flash"], "nomoutv": ["Nominal Output Voltage", VOLT, "mdi:flash"], "nompower": ["Nominal Output Power", POWER_WATT, "mdi:flash"], - "nomapnt": ["Nominal Apparent Power", "VA", "mdi:flash"], + "nomapnt": ["Nominal Apparent Power", ELECTRICAL_VOLT_AMPERE, "mdi:flash"], "numxfers": ["Transfer Count", "", "mdi:counter"], - "outcurnt": ["Output Current", "A", "mdi:flash"], + "outcurnt": ["Output Current", ELECTRICAL_CURRENT_AMPERE, "mdi:flash"], "outputv": ["Output Voltage", VOLT, "mdi:flash"], "reg1": ["Register 1 Fault", "", "mdi:information-outline"], "reg2": ["Register 2 Fault", "", "mdi:information-outline"], @@ -98,8 +100,8 @@ INFERRED_UNITS = { " Seconds": TIME_SECONDS, " Percent": UNIT_PERCENTAGE, " Volts": VOLT, - " Ampere": "A", - " Volt-Ampere": "VA", + " Ampere": ELECTRICAL_CURRENT_AMPERE, + " Volt-Ampere": ELECTRICAL_VOLT_AMPERE, " Watts": POWER_WATT, " Hz": FREQUENCY_HERTZ, " C": TEMP_CELSIUS, diff --git a/homeassistant/components/growatt_server/sensor.py b/homeassistant/components/growatt_server/sensor.py index 6bfdf7f2552..c228bcbe4ab 100644 --- a/homeassistant/components/growatt_server/sensor.py +++ b/homeassistant/components/growatt_server/sensor.py @@ -12,6 +12,7 @@ from homeassistant.const import ( CONF_NAME, CONF_PASSWORD, CONF_USERNAME, + ELECTRICAL_CURRENT_AMPERE, ENERGY_KILO_WATT_HOUR, FREQUENCY_HERTZ, POWER_WATT, @@ -61,17 +62,37 @@ INVERTER_SENSOR_TYPES = { "power", ), "inverter_voltage_input_1": ("Input 1 voltage", VOLT, "vpv1", None), - "inverter_amperage_input_1": ("Input 1 Amperage", "A", "ipv1", None), + "inverter_amperage_input_1": ( + "Input 1 Amperage", + ELECTRICAL_CURRENT_AMPERE, + "ipv1", + None, + ), "inverter_wattage_input_1": ("Input 1 Wattage", POWER_WATT, "ppv1", "power"), "inverter_voltage_input_2": ("Input 2 voltage", VOLT, "vpv2", None), - "inverter_amperage_input_2": ("Input 2 Amperage", "A", "ipv2", None), + "inverter_amperage_input_2": ( + "Input 2 Amperage", + ELECTRICAL_CURRENT_AMPERE, + "ipv2", + None, + ), "inverter_wattage_input_2": ("Input 2 Wattage", POWER_WATT, "ppv2", "power"), "inverter_voltage_input_3": ("Input 3 voltage", VOLT, "vpv3", None), - "inverter_amperage_input_3": ("Input 3 Amperage", "A", "ipv3", None), + "inverter_amperage_input_3": ( + "Input 3 Amperage", + ELECTRICAL_CURRENT_AMPERE, + "ipv3", + None, + ), "inverter_wattage_input_3": ("Input 3 Wattage", POWER_WATT, "ppv3", "power"), "inverter_internal_wattage": ("Internal wattage", POWER_WATT, "ppv", "power"), "inverter_reactive_voltage": ("Reactive voltage", VOLT, "vacr", None), - "inverter_inverter_reactive_amperage": ("Reactive amperage", "A", "iacr", None), + "inverter_inverter_reactive_amperage": ( + "Reactive amperage", + ELECTRICAL_CURRENT_AMPERE, + "iacr", + None, + ), "inverter_frequency": ("AC frequency", FREQUENCY_HERTZ, "fac", None), "inverter_current_wattage": ("Output power", POWER_WATT, "pac", "power"), "inverter_current_reactive_wattage": ( diff --git a/homeassistant/components/juicenet/sensor.py b/homeassistant/components/juicenet/sensor.py index e7408cf9f85..c5d37cb8180 100644 --- a/homeassistant/components/juicenet/sensor.py +++ b/homeassistant/components/juicenet/sensor.py @@ -2,6 +2,7 @@ import logging from homeassistant.const import ( + ELECTRICAL_CURRENT_AMPERE, ENERGY_WATT_HOUR, POWER_WATT, TEMP_CELSIUS, @@ -19,7 +20,7 @@ SENSOR_TYPES = { "status": ["Charging Status", None], "temperature": ["Temperature", TEMP_CELSIUS], "voltage": ["Voltage", VOLT], - "amps": ["Amps", "A"], + "amps": ["Amps", ELECTRICAL_CURRENT_AMPERE], "watts": ["Watts", POWER_WATT], "charge_time": ["Charge time", TIME_SECONDS], "energy_added": ["Energy added", ENERGY_WATT_HOUR], diff --git a/homeassistant/components/keba/sensor.py b/homeassistant/components/keba/sensor.py index d9e6118ff32..443e1bcd1bc 100644 --- a/homeassistant/components/keba/sensor.py +++ b/homeassistant/components/keba/sensor.py @@ -1,7 +1,11 @@ """Support for KEBA charging station sensors.""" import logging -from homeassistant.const import DEVICE_CLASS_POWER, ENERGY_KILO_WATT_HOUR +from homeassistant.const import ( + DEVICE_CLASS_POWER, + ELECTRICAL_CURRENT_AMPERE, + ENERGY_KILO_WATT_HOUR, +) from homeassistant.helpers.entity import Entity from . import DOMAIN @@ -17,7 +21,14 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= keba = hass.data[DOMAIN] sensors = [ - KebaSensor(keba, "Curr user", "Max Current", "max_current", "mdi:flash", "A"), + KebaSensor( + keba, + "Curr user", + "Max Current", + "max_current", + "mdi:flash", + ELECTRICAL_CURRENT_AMPERE, + ), KebaSensor( keba, "Setenergy", diff --git a/homeassistant/components/mysensors/sensor.py b/homeassistant/components/mysensors/sensor.py index 15876163762..d170f476158 100644 --- a/homeassistant/components/mysensors/sensor.py +++ b/homeassistant/components/mysensors/sensor.py @@ -4,6 +4,8 @@ from homeassistant.components.sensor import DOMAIN from homeassistant.const import ( CONDUCTIVITY, DEGREE, + ELECTRICAL_CURRENT_AMPERE, + ELECTRICAL_VOLT_AMPERE, ENERGY_KILO_WATT_HOUR, FREQUENCY_HERTZ, LENGTH_METERS, @@ -41,12 +43,12 @@ SENSORS = { "S_LIGHT_LEVEL": ["lx", "mdi:white-balance-sunny"], }, "V_VOLTAGE": [VOLT, "mdi:flash"], - "V_CURRENT": ["A", "mdi:flash-auto"], + "V_CURRENT": [ELECTRICAL_CURRENT_AMPERE, "mdi:flash-auto"], "V_PH": ["pH", None], "V_ORP": ["mV", None], "V_EC": [CONDUCTIVITY, None], "V_VAR": ["var", None], - "V_VA": ["VA", None], + "V_VA": [ELECTRICAL_VOLT_AMPERE, None], } diff --git a/homeassistant/components/nut/const.py b/homeassistant/components/nut/const.py index 9b4908530a8..47959108a2c 100644 --- a/homeassistant/components/nut/const.py +++ b/homeassistant/components/nut/const.py @@ -5,6 +5,8 @@ from homeassistant.components.sensor import ( DEVICE_CLASS_TEMPERATURE, ) from homeassistant.const import ( + ELECTRICAL_CURRENT_AMPERE, + ELECTRICAL_VOLT_AMPERE, FREQUENCY_HERTZ, POWER_WATT, TEMP_CELSIUS, @@ -61,8 +63,8 @@ SENSOR_TYPES = { "ups.display.language": ["Language", "", "mdi:information-outline", None], "ups.contacts": ["External Contacts", "", "mdi:information-outline", None], "ups.efficiency": ["Efficiency", UNIT_PERCENTAGE, "mdi:gauge", None], - "ups.power": ["Current Apparent Power", "VA", "mdi:flash", None], - "ups.power.nominal": ["Nominal Power", "VA", "mdi:flash", None], + "ups.power": ["Current Apparent Power", ELECTRICAL_VOLT_AMPERE, "mdi:flash", None], + "ups.power.nominal": ["Nominal Power", ELECTRICAL_VOLT_AMPERE, "mdi:flash", None], "ups.realpower": [ "Current Real Power", POWER_WATT, @@ -107,8 +109,18 @@ SENSOR_TYPES = { "battery.voltage.low": ["Low Battery Voltage", VOLT, "mdi:flash", None], "battery.voltage.high": ["High Battery Voltage", VOLT, "mdi:flash", None], "battery.capacity": ["Battery Capacity", "Ah", "mdi:flash", None], - "battery.current": ["Battery Current", "A", "mdi:flash", None], - "battery.current.total": ["Total Battery Current", "A", "mdi:flash", None], + "battery.current": [ + "Battery Current", + ELECTRICAL_CURRENT_AMPERE, + "mdi:flash", + None, + ], + "battery.current.total": [ + "Total Battery Current", + ELECTRICAL_CURRENT_AMPERE, + "mdi:flash", + None, + ], "battery.temperature": [ "Battery Temperature", TEMP_CELSIUS, @@ -168,8 +180,13 @@ SENSOR_TYPES = { "mdi:information-outline", None, ], - "output.current": ["Output Current", "A", "mdi:flash", None], - "output.current.nominal": ["Nominal Output Current", "A", "mdi:flash", None], + "output.current": ["Output Current", ELECTRICAL_CURRENT_AMPERE, "mdi:flash", None], + "output.current.nominal": [ + "Nominal Output Current", + ELECTRICAL_CURRENT_AMPERE, + "mdi:flash", + None, + ], "output.voltage": ["Output Voltage", VOLT, "mdi:flash", None], "output.voltage.nominal": ["Nominal Output Voltage", VOLT, "mdi:flash", None], "output.frequency": ["Output Frequency", FREQUENCY_HERTZ, "mdi:flash", None], diff --git a/homeassistant/components/onewire/sensor.py b/homeassistant/components/onewire/sensor.py index 12a4546dbeb..f12d3ab2346 100644 --- a/homeassistant/components/onewire/sensor.py +++ b/homeassistant/components/onewire/sensor.py @@ -11,6 +11,7 @@ from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( CONF_HOST, CONF_PORT, + ELECTRICAL_CURRENT_AMPERE, TEMP_CELSIUS, UNIT_PERCENTAGE, VOLT, @@ -84,7 +85,7 @@ SENSOR_TYPES = { "voltage": ["voltage", VOLT], "voltage_VAD": ["voltage", VOLT], "voltage_VDD": ["voltage", VOLT], - "current": ["current", "A"], + "current": ["current", ELECTRICAL_CURRENT_AMPERE], } PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( diff --git a/homeassistant/components/smappee/sensor.py b/homeassistant/components/smappee/sensor.py index cb252dda98b..9558bbc2e62 100644 --- a/homeassistant/components/smappee/sensor.py +++ b/homeassistant/components/smappee/sensor.py @@ -4,6 +4,7 @@ import logging from homeassistant.const import ( DEGREE, + ELECTRICAL_CURRENT_AMPERE, ENERGY_KILO_WATT_HOUR, POWER_WATT, UNIT_PERCENTAGE, @@ -26,7 +27,7 @@ SENSOR_TYPES = { POWER_WATT, "active_power", ], - "current": ["Current", "mdi:gauge", "local", "A", "current"], + "current": ["Current", "mdi:gauge", "local", ELECTRICAL_CURRENT_AMPERE, "current"], "voltage": ["Voltage", "mdi:gauge", "local", VOLT, "voltage"], "active_cosfi": [ "Power Factor", diff --git a/homeassistant/components/solaredge_local/sensor.py b/homeassistant/components/solaredge_local/sensor.py index 94aa06c2647..59b0a5e8856 100644 --- a/homeassistant/components/solaredge_local/sensor.py +++ b/homeassistant/components/solaredge_local/sensor.py @@ -12,6 +12,7 @@ from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( CONF_IP_ADDRESS, CONF_NAME, + ELECTRICAL_CURRENT_AMPERE, ENERGY_WATT_HOUR, FREQUENCY_HERTZ, POWER_WATT, @@ -103,7 +104,7 @@ SENSOR_TYPES = { "optimizer_current": [ "optimizercurrent", "Average Optimizer Current", - "A", + ELECTRICAL_CURRENT_AMPERE, "mdi:solar-panel", None, ], diff --git a/homeassistant/const.py b/homeassistant/const.py index 11a99515d60..2ff1d65223b 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -357,6 +357,10 @@ VOLT = "V" ENERGY_WATT_HOUR = f"{POWER_WATT}h" ENERGY_KILO_WATT_HOUR = f"k{ENERGY_WATT_HOUR}" +# Electrical units +ELECTRICAL_CURRENT_AMPERE = "A" +ELECTRICAL_VOLT_AMPERE = f"{VOLT}{ELECTRICAL_CURRENT_AMPERE}" + # Degree units DEGREE = "°" From 5104b79b4b344cf76f14ecbedd9bd243e50c2db0 Mon Sep 17 00:00:00 2001 From: Adam Belebczuk Date: Sun, 17 May 2020 16:13:38 -0400 Subject: [PATCH 068/406] Bump PyWeMo version to 0.4.43 (#35693) * PyWeMo version bump to 0.4.43 Changes necessary to safely upgrade to pyWeMo 0.4.43. This includes catching ActionExceptions that may be thrown by pyWeMo when it is unable to interact with a physical device. * Black formatting fix * Fix isort issues * Code review changes * More code review fixes * Linting fix * Undo dict.get change * Change a couple instances of dict[key] to dict.get --- .../components/wemo/binary_sensor.py | 14 ++- homeassistant/components/wemo/fan.py | 58 +++++++--- homeassistant/components/wemo/light.py | 108 ++++++++++++------ homeassistant/components/wemo/manifest.json | 2 +- homeassistant/components/wemo/switch.py | 22 +++- requirements_all.txt | 2 +- 6 files changed, 144 insertions(+), 62 deletions(-) diff --git a/homeassistant/components/wemo/binary_sensor.py b/homeassistant/components/wemo/binary_sensor.py index 805a730e3d9..b5ef3dc528b 100644 --- a/homeassistant/components/wemo/binary_sensor.py +++ b/homeassistant/components/wemo/binary_sensor.py @@ -3,6 +3,7 @@ import asyncio import logging import async_timeout +from pywemo.ouimeaux_device.api.service import ActionException from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -40,7 +41,7 @@ class WemoBinarySensor(BinarySensorEntity): self._update_lock = None self._model_name = self.wemo.model_name self._name = self.wemo.name - self._serialnumber = self.wemo.serialnumber + self._serial_number = self.wemo.serialnumber def _subscription_callback(self, _device, _type, _params): """Update the state by the Wemo sensor.""" @@ -98,14 +99,15 @@ class WemoBinarySensor(BinarySensorEntity): if not self._available: _LOGGER.info("Reconnected to %s", self.name) self._available = True - except AttributeError as err: + except (AttributeError, ActionException) as err: _LOGGER.warning("Could not update status for %s (%s)", self.name, err) self._available = False + self.wemo.reconnect_with_device() @property def unique_id(self): """Return the id of this WeMo sensor.""" - return self._serialnumber + return self._serial_number @property def name(self): @@ -126,8 +128,8 @@ class WemoBinarySensor(BinarySensorEntity): def device_info(self): """Return the device info.""" return { - "name": self.wemo.name, - "identifiers": {(WEMO_DOMAIN, self.wemo.serialnumber)}, - "model": self.wemo.model_name, + "name": self._name, + "identifiers": {(WEMO_DOMAIN, self._serial_number)}, + "model": self._model_name, "manufacturer": "Belkin", } diff --git a/homeassistant/components/wemo/fan.py b/homeassistant/components/wemo/fan.py index 24ed68c792d..f040a9f3845 100644 --- a/homeassistant/components/wemo/fan.py +++ b/homeassistant/components/wemo/fan.py @@ -4,6 +4,7 @@ from datetime import timedelta import logging import async_timeout +from pywemo.ouimeaux_device.api.service import ActionException import voluptuous as vol from homeassistant.components.fan import ( @@ -128,7 +129,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): # Register service(s) hass.services.async_register( - WEMO_DOMAIN, SERVICE_SET_HUMIDITY, service_handle, schema=SET_HUMIDITY_SCHEMA + WEMO_DOMAIN, SERVICE_SET_HUMIDITY, service_handle, schema=SET_HUMIDITY_SCHEMA, ) hass.services.async_register( @@ -198,9 +199,9 @@ class WemoHumidifier(FanEntity): def device_info(self): """Return the device info.""" return { - "name": self.wemo.name, - "identifiers": {(WEMO_DOMAIN, self.wemo.serialnumber)}, - "model": self.wemo.model_name, + "name": self._name, + "identifiers": {(WEMO_DOMAIN, self._serialnumber)}, + "model": self._model_name, "manufacturer": "Belkin", } @@ -287,38 +288,67 @@ class WemoHumidifier(FanEntity): if not self._available: _LOGGER.info("Reconnected to %s", self.name) self._available = True - except AttributeError as err: + except (AttributeError, ActionException) as err: _LOGGER.warning("Could not update status for %s (%s)", self.name, err) self._available = False + self.wemo.reconnect_with_device() def turn_on(self, speed: str = None, **kwargs) -> None: """Turn the switch on.""" if speed is None: - self.wemo.set_state(self._last_fan_on_mode) + try: + self.wemo.set_state(self._last_fan_on_mode) + except ActionException as err: + _LOGGER.warning("Error while turning on device %s (%s)", self.name, err) + self._available = False else: self.set_speed(speed) def turn_off(self, **kwargs) -> None: """Turn the switch off.""" - self.wemo.set_state(WEMO_FAN_OFF) + try: + self.wemo.set_state(WEMO_FAN_OFF) + except ActionException as err: + _LOGGER.warning("Error while turning off device %s (%s)", self.name, err) + self._available = False def set_speed(self, speed: str) -> None: """Set the fan_mode of the Humidifier.""" - self.wemo.set_state(HASS_FAN_SPEED_TO_WEMO.get(speed)) + try: + self.wemo.set_state(HASS_FAN_SPEED_TO_WEMO.get(speed)) + except ActionException as err: + _LOGGER.warning( + "Error while setting speed of device %s (%s)", self.name, err + ) + self._available = False def set_humidity(self, humidity: float) -> None: """Set the target humidity level for the Humidifier.""" if humidity < 50: - self.wemo.set_humidity(WEMO_HUMIDITY_45) + target_humidity = WEMO_HUMIDITY_45 elif 50 <= humidity < 55: - self.wemo.set_humidity(WEMO_HUMIDITY_50) + target_humidity = WEMO_HUMIDITY_50 elif 55 <= humidity < 60: - self.wemo.set_humidity(WEMO_HUMIDITY_55) + target_humidity = WEMO_HUMIDITY_55 elif 60 <= humidity < 100: - self.wemo.set_humidity(WEMO_HUMIDITY_60) + target_humidity = WEMO_HUMIDITY_60 elif humidity >= 100: - self.wemo.set_humidity(WEMO_HUMIDITY_100) + target_humidity = WEMO_HUMIDITY_100 + + try: + self.wemo.set_humidity(target_humidity) + except ActionException as err: + _LOGGER.warning( + "Error while setting humidity of device: %s (%s)", self.name, err + ) + self._available = False def reset_filter_life(self) -> None: """Reset the filter life to 100%.""" - self.wemo.reset_filter_life() + try: + self.wemo.reset_filter_life() + except ActionException as err: + _LOGGER.warning( + "Error while resetting filter life on device: %s (%s)", self.name, err + ) + self._available = False diff --git a/homeassistant/components/wemo/light.py b/homeassistant/components/wemo/light.py index 02b5e79d1c6..2a05d42f1f7 100644 --- a/homeassistant/components/wemo/light.py +++ b/homeassistant/components/wemo/light.py @@ -4,6 +4,7 @@ from datetime import timedelta import logging import async_timeout +from pywemo.ouimeaux_device.api.service import ActionException from homeassistant import util from homeassistant.components.light import ( @@ -92,6 +93,7 @@ class WemoLight(LightEntity): self._is_on = None self._name = self.wemo.name self._unique_id = self.wemo.uniqueID + self._model_name = type(self.wemo).__name__ async def async_added_to_hass(self): """Wemo light added to Home Assistant.""" @@ -112,9 +114,9 @@ class WemoLight(LightEntity): def device_info(self): """Return the device info.""" return { - "name": self.wemo.name, - "identifiers": {(WEMO_DOMAIN, self.wemo.uniqueID)}, - "model": type(self.wemo).__name__, + "name": self._name, + "identifiers": {(WEMO_DOMAIN, self._unique_id)}, + "model": self._model_name, "manufacturer": "Belkin", } @@ -150,45 +152,65 @@ class WemoLight(LightEntity): def turn_on(self, **kwargs): """Turn the light on.""" - transitiontime = int(kwargs.get(ATTR_TRANSITION, 0)) + xy_color = None + brightness = kwargs.get(ATTR_BRIGHTNESS, self.brightness or 255) + color_temp = kwargs.get(ATTR_COLOR_TEMP) hs_color = kwargs.get(ATTR_HS_COLOR) + transition_time = int(kwargs.get(ATTR_TRANSITION, 0)) if hs_color is not None: xy_color = color_util.color_hs_to_xy(*hs_color) - self.wemo.set_color(xy_color, transition=transitiontime) - if ATTR_COLOR_TEMP in kwargs: - colortemp = kwargs[ATTR_COLOR_TEMP] - self.wemo.set_temperature(mireds=colortemp, transition=transitiontime) + turn_on_kwargs = { + "level": brightness, + "transition": transition_time, + "force_update": False, + } - if ATTR_BRIGHTNESS in kwargs: - brightness = kwargs.get(ATTR_BRIGHTNESS, self.brightness or 255) - self.wemo.turn_on(level=brightness, transition=transitiontime) - else: - self.wemo.turn_on(transition=transitiontime) + try: + if xy_color is not None: + self.wemo.set_color(xy_color, transition=transition_time) + + if color_temp is not None: + self.wemo.set_temperature(mireds=color_temp, transition=transition_time) + + self.wemo.turn_on(**turn_on_kwargs) + except ActionException as err: + _LOGGER.warning("Error while turning on device %s (%s)", self.name, err) + self._available = False def turn_off(self, **kwargs): """Turn the light off.""" - transitiontime = int(kwargs.get(ATTR_TRANSITION, 0)) - self.wemo.turn_off(transition=transitiontime) + transition_time = int(kwargs.get(ATTR_TRANSITION, 0)) + + try: + self.wemo.turn_off(transition=transition_time) + except ActionException as err: + _LOGGER.warning("Error while turning off device %s (%s)", self.name, err) + self._available = False def _update(self, force_update=True): """Synchronize state with bridge.""" - self._update_lights(no_throttle=force_update) - self._state = self.wemo.state - - self._is_on = self._state.get("onoff") != 0 - self._brightness = self._state.get("level", 255) - self._color_temp = self._state.get("temperature_mireds") - self._available = True - - xy_color = self._state.get("color_xy") - - if xy_color: - self._hs_color = color_util.color_xy_to_hs(*xy_color) + try: + self._update_lights(no_throttle=force_update) + self._state = self.wemo.state + except (AttributeError, ActionException) as err: + _LOGGER.warning("Could not update status for %s (%s)", self.name, err) + self._available = False + self.wemo.reconnect_with_device() else: - self._hs_color = None + self._is_on = self._state.get("onoff") != 0 + self._brightness = self._state.get("level", 255) + self._color_temp = self._state.get("temperature_mireds") + self._available = True + + xy_color = self._state.get("color_xy") + + if xy_color: + self._hs_color = color_util.color_xy_to_hs(*xy_color) + else: + self._hs_color = None async def async_update(self): """Synchronize state with bridge.""" @@ -265,7 +287,6 @@ class WemoDimmer(LightEntity): except asyncio.TimeoutError: _LOGGER.warning("Lost connection to %s", self.name) self._available = False - self.wemo.reconnect_with_device() async def _async_locked_update(self, force_update): """Try updating within an async lock.""" @@ -282,6 +303,16 @@ class WemoDimmer(LightEntity): """Return the name of the dimmer if any.""" return self._name + @property + def device_info(self): + """Return the device info.""" + return { + "name": self._name, + "identifiers": {(WEMO_DOMAIN, self._serialnumber)}, + "model": self._model_name, + "manufacturer": "Belkin", + } + @property def supported_features(self): """Flag supported features.""" @@ -308,14 +339,13 @@ class WemoDimmer(LightEntity): if not self._available: _LOGGER.info("Reconnected to %s", self.name) self._available = True - except AttributeError as err: + except (AttributeError, ActionException) as err: _LOGGER.warning("Could not update status for %s (%s)", self.name, err) self._available = False + self.wemo.reconnect_with_device() def turn_on(self, **kwargs): """Turn the dimmer on.""" - self.wemo.on() - # Wemo dimmer switches use a range of [0, 100] to control # brightness. Level 255 might mean to set it to previous value if ATTR_BRIGHTNESS in kwargs: @@ -323,11 +353,21 @@ class WemoDimmer(LightEntity): brightness = int((brightness / 255) * 100) else: brightness = 255 - self.wemo.set_brightness(brightness) + + try: + self.wemo.on() + self.wemo.set_brightness(brightness) + except ActionException as err: + _LOGGER.warning("Error while turning on device %s (%s)", self.name, err) + self._available = False def turn_off(self, **kwargs): """Turn the dimmer off.""" - self.wemo.off() + try: + self.wemo.off() + except ActionException as err: + _LOGGER.warning("Error while turning on device %s (%s)", self.name, err) + self._available = False @property def available(self): diff --git a/homeassistant/components/wemo/manifest.json b/homeassistant/components/wemo/manifest.json index 0ad4574ecbc..96efb140cee 100644 --- a/homeassistant/components/wemo/manifest.json +++ b/homeassistant/components/wemo/manifest.json @@ -3,7 +3,7 @@ "name": "Belkin WeMo", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/wemo", - "requirements": ["pywemo==0.4.34"], + "requirements": ["pywemo==0.4.43"], "ssdp": [ { "manufacturer": "Belkin International Inc." diff --git a/homeassistant/components/wemo/switch.py b/homeassistant/components/wemo/switch.py index 203c495e974..836ddf0730f 100644 --- a/homeassistant/components/wemo/switch.py +++ b/homeassistant/components/wemo/switch.py @@ -4,6 +4,7 @@ from datetime import datetime, timedelta import logging import async_timeout +from pywemo.ouimeaux_device.api.service import ActionException from homeassistant.components.switch import SwitchEntity from homeassistant.const import STATE_OFF, STATE_ON, STATE_STANDBY, STATE_UNKNOWN @@ -93,9 +94,9 @@ class WemoSwitch(SwitchEntity): def device_info(self): """Return the device info.""" return { - "name": self.wemo.name, - "identifiers": {(WEMO_DOMAIN, self.wemo.serialnumber)}, - "model": self.wemo.model_name, + "name": self._name, + "identifiers": {(WEMO_DOMAIN, self._serialnumber)}, + "model": self._model_name, "manufacturer": "Belkin", } @@ -189,11 +190,19 @@ class WemoSwitch(SwitchEntity): def turn_on(self, **kwargs): """Turn the switch on.""" - self.wemo.on() + try: + self.wemo.on() + except ActionException as err: + _LOGGER.warning("Error while turning on device %s (%s)", self.name, err) + self._available = False def turn_off(self, **kwargs): """Turn the switch off.""" - self.wemo.off() + try: + self.wemo.off() + except ActionException as err: + _LOGGER.warning("Error while turning off device %s (%s)", self.name, err) + self._available = False async def async_added_to_hass(self): """Wemo switch added to Home Assistant.""" @@ -245,6 +254,7 @@ class WemoSwitch(SwitchEntity): if not self._available: _LOGGER.info("Reconnected to %s", self.name) self._available = True - except AttributeError as err: + except (AttributeError, ActionException) as err: _LOGGER.warning("Could not update status for %s (%s)", self.name, err) self._available = False + self.wemo.reconnect_with_device() diff --git a/requirements_all.txt b/requirements_all.txt index ce77e2c1148..44d55995857 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1808,7 +1808,7 @@ pyvlx==0.2.14 pywebpush==1.9.2 # homeassistant.components.wemo -pywemo==0.4.34 +pywemo==0.4.43 # homeassistant.components.xeoma pyxeoma==1.4.1 From 8eb7777561de054ca4de12182704df0df33750ff Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 17 May 2020 15:51:51 -0500 Subject: [PATCH 069/406] Ensure homekit version strings conform to spec (#35741) HomeKit requires all version strings to be in the format MAJOR.MINOR.REVISION --- homeassistant/components/homekit/accessories.py | 3 ++- homeassistant/components/homekit/util.py | 9 +++++++++ tests/components/homekit/test_util.py | 10 ++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/homekit/accessories.py b/homeassistant/components/homekit/accessories.py index 367f43d9560..3cd3c46613b 100644 --- a/homeassistant/components/homekit/accessories.py +++ b/homeassistant/components/homekit/accessories.py @@ -75,6 +75,7 @@ from .const import ( from .util import ( convert_to_float, dismiss_setup_message, + format_sw_version, show_setup_message, validate_media_player_features, ) @@ -253,7 +254,7 @@ class HomeAccessory(Accessory): else: model = domain.title() if ATTR_SOFTWARE_VERSION in self.config: - sw_version = self.config[ATTR_SOFTWARE_VERSION] + sw_version = format_sw_version(self.config[ATTR_SOFTWARE_VERSION]) else: sw_version = __version__ diff --git a/homeassistant/components/homekit/util.py b/homeassistant/components/homekit/util.py index aac0e211975..d35c463ca39 100644 --- a/homeassistant/components/homekit/util.py +++ b/homeassistant/components/homekit/util.py @@ -4,6 +4,7 @@ import io import ipaddress import logging import os +import re import secrets import socket @@ -415,6 +416,14 @@ def get_aid_storage_fullpath_for_entry_id(hass: HomeAssistant, entry_id: str): ) +def format_sw_version(version): + """Extract the version string in a format homekit can consume.""" + match = re.search(r"([0-9]+)(\.[0-9]+)?(\.[0-9]+)?", str(version).replace("-", ".")) + if match: + return match.group(0) + return None + + def migrate_filesystem_state_data_for_primary_imported_entry_id( hass: HomeAssistant, entry_id: str ): diff --git a/tests/components/homekit/test_util.py b/tests/components/homekit/test_util.py index d5ff923270b..48f0a6d270e 100644 --- a/tests/components/homekit/test_util.py +++ b/tests/components/homekit/test_util.py @@ -28,6 +28,7 @@ from homeassistant.components.homekit.util import ( density_to_air_quality, dismiss_setup_message, find_next_available_port, + format_sw_version, port_is_available, show_setup_message, temperature_to_homekit, @@ -315,3 +316,12 @@ async def test_port_is_available(hass): assert next_port assert await hass.async_add_executor_job(port_is_available, next_port) + + +async def test_format_sw_version(): + """Test format_sw_version method.""" + assert format_sw_version("soho+3.6.8+soho-release-rt120+10") == "3.6.8" + assert format_sw_version("undefined-undefined-1.6.8") == "1.6.8" + assert format_sw_version("56.0-76060") == "56.0.76060" + assert format_sw_version(3.6) == "3.6" + assert format_sw_version("unknown") is None From 3815d7d74f921d617f1ebeaffe49afae6a60c61d Mon Sep 17 00:00:00 2001 From: tetienne Date: Sun, 17 May 2020 23:14:11 +0200 Subject: [PATCH 070/406] Add Somfy hub as device (#35209) --- homeassistant/components/somfy/__init__.py | 23 ++++++++++++++++++-- homeassistant/components/somfy/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 24 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/somfy/__init__.py b/homeassistant/components/somfy/__init__.py index 619e5a72602..40692b9d459 100644 --- a/homeassistant/components/somfy/__init__.py +++ b/homeassistant/components/somfy/__init__.py @@ -3,12 +3,17 @@ import asyncio from datetime import timedelta import logging +from pymfy.api.devices.category import Category from requests import HTTPError import voluptuous as vol from homeassistant.components.somfy import config_flow from homeassistant.config_entries import ConfigEntry -from homeassistant.helpers import config_entry_oauth2_flow, config_validation as cv +from homeassistant.helpers import ( + config_entry_oauth2_flow, + config_validation as cv, + device_registry as dr, +) from homeassistant.helpers.entity import Entity from homeassistant.helpers.typing import HomeAssistantType from homeassistant.util import Throttle @@ -86,6 +91,20 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry): await update_all_devices(hass) + device_registry = await dr.async_get_registry(hass) + + devices = hass.data[DOMAIN][DEVICES] + hubs = [device for device in devices if Category.HUB.value in device.categories] + + for hub in hubs: + device_registry.async_get_or_create( + config_entry_id=entry.entry_id, + identifiers={(DOMAIN, hub.id)}, + manufacturer="Somfy", + name=hub.name, + model=hub.type, + ) + for component in SOMFY_COMPONENTS: hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, component) @@ -134,7 +153,7 @@ class SomfyEntity(Entity): "identifiers": {(DOMAIN, self.unique_id)}, "name": self.name, "model": self.device.type, - "via_hub": (DOMAIN, self.device.site_id), + "via_hub": (DOMAIN, self.device.parent_id), # For the moment, Somfy only returns their own device. "manufacturer": "Somfy", } diff --git a/homeassistant/components/somfy/manifest.json b/homeassistant/components/somfy/manifest.json index 6e15b01e961..ab24d21b2e1 100644 --- a/homeassistant/components/somfy/manifest.json +++ b/homeassistant/components/somfy/manifest.json @@ -5,5 +5,5 @@ "documentation": "https://www.home-assistant.io/integrations/somfy", "dependencies": ["http"], "codeowners": ["@tetienne"], - "requirements": ["pymfy==0.7.1"] + "requirements": ["pymfy==0.9.0"] } diff --git a/requirements_all.txt b/requirements_all.txt index 44d55995857..0b305d809c1 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1447,7 +1447,7 @@ pymediaroom==0.6.4 pymelcloud==2.5.2 # homeassistant.components.somfy -pymfy==0.7.1 +pymfy==0.9.0 # homeassistant.components.xiaomi_tv pymitv==1.4.3 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 66676368988..c58ad9fc21d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -621,7 +621,7 @@ pymailgunner==1.4 pymelcloud==2.5.2 # homeassistant.components.somfy -pymfy==0.7.1 +pymfy==0.9.0 # homeassistant.components.mochad pymochad==0.2.0 From f085fb1499873f7ba39c37f4e5263a5a74b0bc7a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 17 May 2020 16:16:09 -0500 Subject: [PATCH 071/406] Set lifx device sw_version (#35739) This change solves an issue where homekit would think the firmware was out of date on the bulb because no version was set. --- homeassistant/components/lifx/light.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/lifx/light.py b/homeassistant/components/lifx/light.py index f36b64f2397..2b7629cdaf2 100644 --- a/homeassistant/components/lifx/light.py +++ b/homeassistant/components/lifx/light.py @@ -371,6 +371,12 @@ class LIFXManager: # Read initial state ack = AwaitAioLIFX().wait + + # Used to populate sw_version + # no need to wait as we do not + # need it until later + bulb.get_hostfirmware() + color_resp = await ack(bulb.get_color) if color_resp: version_resp = await ack(bulb.get_version) @@ -459,7 +465,13 @@ class LIFXLight(LightEntity): "manufacturer": "LIFX", } - model = aiolifx().products.product_map.get(self.bulb.product) + version = self.bulb.host_firmware_version + if version is not None: + info["sw_version"] = version + + product_map = aiolifx().products.product_map + + model = product_map.get(self.bulb.product) or self.bulb.product if model is not None: info["model"] = model From 03b14c9aaeb3b0269ae86cf5478c4adb3f7edda4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 17 May 2020 16:16:50 -0500 Subject: [PATCH 072/406] Add sw_version and connections to sonos devices (#35743) --- homeassistant/components/sonos/media_player.py | 5 +++++ tests/components/sonos/conftest.py | 7 ++++++- tests/components/sonos/test_media_player.py | 15 +++++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/sonos/media_player.py b/homeassistant/components/sonos/media_player.py index b4dc9530b90..59147d727c6 100644 --- a/homeassistant/components/sonos/media_player.py +++ b/homeassistant/components/sonos/media_player.py @@ -40,6 +40,7 @@ from homeassistant.const import ( ) from homeassistant.core import ServiceCall, callback from homeassistant.helpers import config_validation as cv, entity_platform, service +import homeassistant.helpers.device_registry as dr from homeassistant.util.dt import utcnow from . import ( @@ -392,6 +393,8 @@ class SonosEntity(MediaPlayerEntity): speaker_info = self.soco.get_speaker_info(True) self._name = speaker_info["zone_name"] self._model = speaker_info["model_name"] + self._sw_version = speaker_info["software_version"] + self._mac_address = speaker_info["mac_address"] async def async_added_to_hass(self): """Subscribe sonos events.""" @@ -427,6 +430,8 @@ class SonosEntity(MediaPlayerEntity): "identifiers": {(SONOS_DOMAIN, self._unique_id)}, "name": self._name, "model": self._model.replace("Sonos ", ""), + "sw_version": self._sw_version, + "connections": {(dr.CONNECTION_NETWORK_MAC, self._mac_address)}, "manufacturer": "Sonos", } diff --git a/tests/components/sonos/conftest.py b/tests/components/sonos/conftest.py index 20c1eb10320..e8441576013 100644 --- a/tests/components/sonos/conftest.py +++ b/tests/components/sonos/conftest.py @@ -69,4 +69,9 @@ def music_library_fixture(): @pytest.fixture(name="speaker_info") def speaker_info_fixture(): """Create speaker_info fixture.""" - return {"zone_name": "Zone A", "model_name": "Model Name"} + return { + "zone_name": "Zone A", + "model_name": "Model Name", + "software_version": "49.2-64250", + "mac_address": "00-11-22-33-44-55", + } diff --git a/tests/components/sonos/test_media_player.py b/tests/components/sonos/test_media_player.py index 5014ded96bb..54d54e6ed5b 100644 --- a/tests/components/sonos/test_media_player.py +++ b/tests/components/sonos/test_media_player.py @@ -42,3 +42,18 @@ async def test_services(hass, config_entry, config, hass_read_only_user): blocking=True, context=Context(user_id=hass_read_only_user.id), ) + + +async def test_device_registry(hass, config_entry, config, soco): + """Test sonos device registered in the device registry.""" + await setup_platform(hass, config_entry, config) + + device_registry = await hass.helpers.device_registry.async_get_registry() + reg_device = device_registry.async_get_device( + identifiers={("sonos", "RINCON_test")}, connections=set(), + ) + assert reg_device.model == "Model Name" + assert reg_device.sw_version == "49.2-64250" + assert reg_device.connections == {("mac", "00:11:22:33:44:55")} + assert reg_device.manufacturer == "Sonos" + assert reg_device.name == "Zone A" From a03cb93f87528540c7e4c9ba50904c8c2e56cdb8 Mon Sep 17 00:00:00 2001 From: Andrew Fahrenholtz Date: Sun, 17 May 2020 16:17:08 -0500 Subject: [PATCH 073/406] Add ARWN rain total and rain rate sensors (#35751) * add rain total and rain rate sensors * linting * linting --- homeassistant/components/arwn/sensor.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/homeassistant/components/arwn/sensor.py b/homeassistant/components/arwn/sensor.py index 5da860d8a50..7c947be61bf 100644 --- a/homeassistant/components/arwn/sensor.py +++ b/homeassistant/components/arwn/sensor.py @@ -39,6 +39,10 @@ def discover_sensors(topic, payload): return ArwnSensor( "Rain Since Midnight", "since_midnight", "in", "mdi:water" ) + return ( + ArwnSensor("Total Rainfall", "total", unit, "mdi:water"), + ArwnSensor("Rainfall Rate", "rate", unit, "mdi:water"), + ) if domain == "barometer": return ArwnSensor("Barometer", "pressure", unit, "mdi:thermometer-lines") if domain == "wind": From 902eb187ef7ada62f299caa258a423213f14f9c6 Mon Sep 17 00:00:00 2001 From: gadgetmobile <57815233+gadgetmobile@users.noreply.github.com> Date: Mon, 18 May 2020 01:54:32 +0200 Subject: [PATCH 074/406] Add Blebox lights support (#35370) * add BleBox lights support * cherry pick refactoring from #35552 * Inherit from LightEntity instead of Light Co-authored-by: J. Nick Koston * import LightEntity instead of Light Co-authored-by: J. Nick Koston Co-authored-by: J. Nick Koston --- homeassistant/components/blebox/__init__.py | 2 +- homeassistant/components/blebox/light.py | 98 ++++ tests/components/blebox/test_light.py | 597 ++++++++++++++++++++ 3 files changed, 696 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/blebox/light.py create mode 100644 tests/components/blebox/test_light.py diff --git a/homeassistant/components/blebox/__init__.py b/homeassistant/components/blebox/__init__.py index 634f8384c8c..36d319b43fb 100644 --- a/homeassistant/components/blebox/__init__.py +++ b/homeassistant/components/blebox/__init__.py @@ -17,7 +17,7 @@ from .const import DEFAULT_SETUP_TIMEOUT, DOMAIN, PRODUCT _LOGGER = logging.getLogger(__name__) -PLATFORMS = ["cover", "sensor", "switch", "air_quality"] +PLATFORMS = ["cover", "sensor", "switch", "air_quality", "light"] PARALLEL_UPDATES = 0 diff --git a/homeassistant/components/blebox/light.py b/homeassistant/components/blebox/light.py new file mode 100644 index 00000000000..251a14f8fb8 --- /dev/null +++ b/homeassistant/components/blebox/light.py @@ -0,0 +1,98 @@ +"""BleBox light entities implementation.""" +import logging + +from blebox_uniapi.error import BadOnValueError + +from homeassistant.components.light import ( + ATTR_BRIGHTNESS, + ATTR_HS_COLOR, + ATTR_WHITE_VALUE, + SUPPORT_BRIGHTNESS, + SUPPORT_COLOR, + SUPPORT_WHITE_VALUE, + LightEntity, +) +from homeassistant.util.color import ( + color_hs_to_RGB, + color_rgb_to_hex, + color_RGB_to_hs, + rgb_hex_to_rgb_list, +) + +from . import BleBoxEntity, create_blebox_entities + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry(hass, config_entry, async_add): + """Set up a BleBox entry.""" + + create_blebox_entities(hass, config_entry, async_add, BleBoxLightEntity, "lights") + + +class BleBoxLightEntity(BleBoxEntity, LightEntity): + """Representation of BleBox lights.""" + + @property + def supported_features(self): + """Return supported features.""" + white = SUPPORT_WHITE_VALUE if self._feature.supports_white else 0 + color = SUPPORT_COLOR if self._feature.supports_color else 0 + brightness = SUPPORT_BRIGHTNESS if self._feature.supports_brightness else 0 + return white | color | brightness + + @property + def is_on(self): + """Return if light is on.""" + return self._feature.is_on + + @property + def brightness(self): + """Return the name.""" + return self._feature.brightness + + @property + def white_value(self): + """Return the white value.""" + return self._feature.white_value + + @property + def hs_color(self): + """Return the hue and saturation.""" + rgbw_hex = self._feature.rgbw_hex + if rgbw_hex is None: + return None + + rgb = rgb_hex_to_rgb_list(rgbw_hex)[0:3] + return color_RGB_to_hs(*rgb) + + async def async_turn_on(self, **kwargs): + """Turn the light on.""" + + white = kwargs.get(ATTR_WHITE_VALUE, None) + hs_color = kwargs.get(ATTR_HS_COLOR, None) + brightness = kwargs.get(ATTR_BRIGHTNESS, None) + + feature = self._feature + value = feature.sensible_on_value + + if brightness is not None: + value = feature.apply_brightness(value, brightness) + + if white is not None: + value = feature.apply_white(value, white) + + if hs_color is not None: + raw_rgb = color_rgb_to_hex(*color_hs_to_RGB(*hs_color)) + value = feature.apply_color(value, raw_rgb) + + try: + await self._feature.async_on(value) + except BadOnValueError as ex: + _LOGGER.error( + "turning on '%s' failed: Bad value %s (%s)", self.name, value, ex + ) + + async def async_turn_off(self, **kwargs): + """Turn the light off.""" + await self._feature.async_off() diff --git a/tests/components/blebox/test_light.py b/tests/components/blebox/test_light.py new file mode 100644 index 00000000000..e1aa37777fd --- /dev/null +++ b/tests/components/blebox/test_light.py @@ -0,0 +1,597 @@ +"""BleBox light entities tests.""" + +import logging + +import blebox_uniapi +import pytest + +from homeassistant.components.light import ( + ATTR_BRIGHTNESS, + ATTR_HS_COLOR, + ATTR_WHITE_VALUE, + SUPPORT_BRIGHTNESS, + SUPPORT_COLOR, + SUPPORT_WHITE_VALUE, +) +from homeassistant.const import ( + ATTR_SUPPORTED_FEATURES, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, + STATE_OFF, + STATE_ON, +) +from homeassistant.util import color + +from .conftest import async_setup_entity, mock_feature + +from tests.async_mock import AsyncMock, PropertyMock + +ALL_LIGHT_FIXTURES = ["dimmer", "wlightbox_s", "wlightbox"] + + +@pytest.fixture(name="dimmer") +def dimmer_fixture(): + """Return a default light entity mock.""" + + feature = mock_feature( + "lights", + blebox_uniapi.light.Light, + unique_id="BleBox-dimmerBox-1afe34e750b8-brightness", + full_name="dimmerBox-brightness", + device_class=None, + brightness=65, + is_on=True, + supports_color=False, + supports_white=False, + ) + product = feature.product + type(product).name = PropertyMock(return_value="My dimmer") + type(product).model = PropertyMock(return_value="dimmerBox") + return (feature, "light.dimmerbox_brightness") + + +async def test_dimmer_init(dimmer, hass, config): + """Test cover default state.""" + + _, entity_id = dimmer + entry = await async_setup_entity(hass, config, entity_id) + assert entry.unique_id == "BleBox-dimmerBox-1afe34e750b8-brightness" + + state = hass.states.get(entity_id) + assert state.name == "dimmerBox-brightness" + + supported_features = state.attributes[ATTR_SUPPORTED_FEATURES] + assert supported_features & SUPPORT_BRIGHTNESS + + assert state.attributes[ATTR_BRIGHTNESS] == 65 + assert state.state == STATE_ON + + device_registry = await hass.helpers.device_registry.async_get_registry() + device = device_registry.async_get(entry.device_id) + + assert device.name == "My dimmer" + assert device.identifiers == {("blebox", "abcd0123ef5678")} + assert device.manufacturer == "BleBox" + assert device.model == "dimmerBox" + assert device.sw_version == "1.23" + + +async def test_dimmer_update(dimmer, hass, config): + """Test light updating.""" + + feature_mock, entity_id = dimmer + + def initial_update(): + feature_mock.brightness = 53 + + feature_mock.async_update = AsyncMock(side_effect=initial_update) + await async_setup_entity(hass, config, entity_id) + + state = hass.states.get(entity_id) + assert state.attributes[ATTR_BRIGHTNESS] == 53 + assert state.state == STATE_ON + + +async def test_dimmer_on(dimmer, hass, config): + """Test light on.""" + + feature_mock, entity_id = dimmer + + def initial_update(): + feature_mock.is_on = False + feature_mock.brightness = 0 # off + feature_mock.sensible_on_value = 254 + + feature_mock.async_update = AsyncMock(side_effect=initial_update) + await async_setup_entity(hass, config, entity_id) + feature_mock.async_update = AsyncMock() + + state = hass.states.get(entity_id) + assert state.state == STATE_OFF + + def turn_on(brightness): + assert brightness == 254 + feature_mock.brightness = 254 # on + feature_mock.is_on = True # on + + feature_mock.async_on = AsyncMock(side_effect=turn_on) + await hass.services.async_call( + "light", SERVICE_TURN_ON, {"entity_id": entity_id}, blocking=True, + ) + + state = hass.states.get(entity_id) + assert state.state == STATE_ON + assert state.attributes[ATTR_BRIGHTNESS] == 254 + + +async def test_dimmer_on_with_brightness(dimmer, hass, config): + """Test light on with a brightness value.""" + + feature_mock, entity_id = dimmer + + def initial_update(): + feature_mock.is_on = False + feature_mock.brightness = 0 # off + feature_mock.sensible_on_value = 254 + + feature_mock.async_update = AsyncMock(side_effect=initial_update) + await async_setup_entity(hass, config, entity_id) + feature_mock.async_update = AsyncMock() + + state = hass.states.get(entity_id) + assert state.state == STATE_OFF + + def turn_on(brightness): + assert brightness == 202 + feature_mock.brightness = 202 # on + feature_mock.is_on = True # on + + feature_mock.async_on = AsyncMock(side_effect=turn_on) + + def apply(value, brightness): + assert value == 254 + return brightness + + feature_mock.apply_brightness = apply + await hass.services.async_call( + "light", + SERVICE_TURN_ON, + {"entity_id": entity_id, ATTR_BRIGHTNESS: 202}, + blocking=True, + ) + + state = hass.states.get(entity_id) + assert state.attributes[ATTR_BRIGHTNESS] == 202 + assert state.state == STATE_ON + + +async def test_dimmer_off(dimmer, hass, config): + """Test light off.""" + + feature_mock, entity_id = dimmer + + def initial_update(): + feature_mock.is_on = True + + feature_mock.async_update = AsyncMock(side_effect=initial_update) + await async_setup_entity(hass, config, entity_id) + feature_mock.async_update = AsyncMock() + + state = hass.states.get(entity_id) + assert state.state == STATE_ON + + def turn_off(): + feature_mock.is_on = False + feature_mock.brightness = 0 # off + + feature_mock.async_off = AsyncMock(side_effect=turn_off) + await hass.services.async_call( + "light", SERVICE_TURN_OFF, {"entity_id": entity_id}, blocking=True, + ) + + state = hass.states.get(entity_id) + assert state.state == STATE_OFF + assert ATTR_BRIGHTNESS not in state.attributes + + +@pytest.fixture(name="wlightbox_s") +def wlightboxs_fixture(): + """Return a default light entity mock.""" + + feature = mock_feature( + "lights", + blebox_uniapi.light.Light, + unique_id="BleBox-wLightBoxS-1afe34e750b8-color", + full_name="wLightBoxS-color", + device_class=None, + brightness=None, + is_on=None, + supports_color=False, + supports_white=False, + ) + product = feature.product + type(product).name = PropertyMock(return_value="My wLightBoxS") + type(product).model = PropertyMock(return_value="wLightBoxS") + return (feature, "light.wlightboxs_color") + + +async def test_wlightbox_s_init(wlightbox_s, hass, config): + """Test cover default state.""" + + _, entity_id = wlightbox_s + entry = await async_setup_entity(hass, config, entity_id) + assert entry.unique_id == "BleBox-wLightBoxS-1afe34e750b8-color" + + state = hass.states.get(entity_id) + assert state.name == "wLightBoxS-color" + + supported_features = state.attributes[ATTR_SUPPORTED_FEATURES] + assert supported_features & SUPPORT_BRIGHTNESS + + assert ATTR_BRIGHTNESS not in state.attributes + assert state.state == STATE_OFF + + device_registry = await hass.helpers.device_registry.async_get_registry() + device = device_registry.async_get(entry.device_id) + + assert device.name == "My wLightBoxS" + assert device.identifiers == {("blebox", "abcd0123ef5678")} + assert device.manufacturer == "BleBox" + assert device.model == "wLightBoxS" + assert device.sw_version == "1.23" + + +async def test_wlightbox_s_update(wlightbox_s, hass, config): + """Test light updating.""" + + feature_mock, entity_id = wlightbox_s + + def initial_update(): + feature_mock.brightness = 0xAB + feature_mock.is_on = True + + feature_mock.async_update = AsyncMock(side_effect=initial_update) + + await async_setup_entity(hass, config, entity_id) + + state = hass.states.get(entity_id) + assert state.state == STATE_ON + assert state.attributes[ATTR_BRIGHTNESS] == 0xAB + + +async def test_wlightbox_s_on(wlightbox_s, hass, config): + """Test light on.""" + + feature_mock, entity_id = wlightbox_s + + def initial_update(): + feature_mock.is_on = False + feature_mock.sensible_on_value = 254 + + feature_mock.async_update = AsyncMock(side_effect=initial_update) + await async_setup_entity(hass, config, entity_id) + feature_mock.async_update = AsyncMock() + + state = hass.states.get(entity_id) + assert state.state == STATE_OFF + + def turn_on(brightness): + assert brightness == 254 + feature_mock.brightness = 254 # on + feature_mock.is_on = True # on + + feature_mock.async_on = AsyncMock(side_effect=turn_on) + await hass.services.async_call( + "light", SERVICE_TURN_ON, {"entity_id": entity_id}, blocking=True, + ) + + state = hass.states.get(entity_id) + assert state.attributes[ATTR_BRIGHTNESS] == 254 + assert state.state == STATE_ON + + +@pytest.fixture(name="wlightbox") +def wlightbox_fixture(): + """Return a default light entity mock.""" + + feature = mock_feature( + "lights", + blebox_uniapi.light.Light, + unique_id="BleBox-wLightBox-1afe34e750b8-color", + full_name="wLightBox-color", + device_class=None, + is_on=None, + supports_color=True, + supports_white=True, + white_value=None, + rgbw_hex=None, + ) + product = feature.product + type(product).name = PropertyMock(return_value="My wLightBox") + type(product).model = PropertyMock(return_value="wLightBox") + return (feature, "light.wlightbox_color") + + +async def test_wlightbox_init(wlightbox, hass, config): + """Test cover default state.""" + + _, entity_id = wlightbox + entry = await async_setup_entity(hass, config, entity_id) + assert entry.unique_id == "BleBox-wLightBox-1afe34e750b8-color" + + state = hass.states.get(entity_id) + assert state.name == "wLightBox-color" + + supported_features = state.attributes[ATTR_SUPPORTED_FEATURES] + assert supported_features & SUPPORT_WHITE_VALUE + assert supported_features & SUPPORT_COLOR + + assert ATTR_WHITE_VALUE not in state.attributes + assert ATTR_HS_COLOR not in state.attributes + assert ATTR_BRIGHTNESS not in state.attributes + assert state.state == STATE_OFF + + device_registry = await hass.helpers.device_registry.async_get_registry() + device = device_registry.async_get(entry.device_id) + + assert device.name == "My wLightBox" + assert device.identifiers == {("blebox", "abcd0123ef5678")} + assert device.manufacturer == "BleBox" + assert device.model == "wLightBox" + assert device.sw_version == "1.23" + + +async def test_wlightbox_update(wlightbox, hass, config): + """Test light updating.""" + + feature_mock, entity_id = wlightbox + + def initial_update(): + feature_mock.is_on = True + feature_mock.rgbw_hex = "fa00203A" + feature_mock.white_value = 0x3A + + feature_mock.async_update = AsyncMock(side_effect=initial_update) + await async_setup_entity(hass, config, entity_id) + + state = hass.states.get(entity_id) + assert state.attributes[ATTR_HS_COLOR] == (352.32, 100.0) + assert state.attributes[ATTR_WHITE_VALUE] == 0x3A + assert state.state == STATE_ON + + +async def test_wlightbox_on_via_just_whiteness(wlightbox, hass, config): + """Test light on.""" + + feature_mock, entity_id = wlightbox + + def initial_update(): + feature_mock.is_on = False + + feature_mock.async_update = AsyncMock(side_effect=initial_update) + await async_setup_entity(hass, config, entity_id) + feature_mock.async_update = AsyncMock() + + state = hass.states.get(entity_id) + assert state.state == STATE_OFF + + def turn_on(value): + feature_mock.is_on = True + assert value == "f1e2d3c7" + feature_mock.white_value = 0xC7 # on + feature_mock.rgbw_hex = "f1e2d3c7" + + feature_mock.async_on = AsyncMock(side_effect=turn_on) + + def apply_white(value, white): + assert value == "f1e2d305" + assert white == 0xC7 + return "f1e2d3c7" + + feature_mock.apply_white = apply_white + + feature_mock.sensible_on_value = "f1e2d305" + + await hass.services.async_call( + "light", + SERVICE_TURN_ON, + {"entity_id": entity_id, ATTR_WHITE_VALUE: 0xC7}, + blocking=True, + ) + + state = hass.states.get(entity_id) + assert state.state == STATE_ON + assert state.attributes[ATTR_WHITE_VALUE] == 0xC7 + + assert state.attributes[ATTR_HS_COLOR] == color.color_RGB_to_hs(0xF1, 0xE2, 0xD3) + + +async def test_wlightbox_on_via_reset_whiteness(wlightbox, hass, config): + """Test light on.""" + + feature_mock, entity_id = wlightbox + + def initial_update(): + feature_mock.is_on = False + + feature_mock.async_update = AsyncMock(side_effect=initial_update) + await async_setup_entity(hass, config, entity_id) + feature_mock.async_update = AsyncMock() + + state = hass.states.get(entity_id) + assert state.state == STATE_OFF + + def turn_on(value): + feature_mock.is_on = True + feature_mock.white_value = 0x0 + assert value == "f1e2d300" + feature_mock.rgbw_hex = "f1e2d300" + + feature_mock.async_on = AsyncMock(side_effect=turn_on) + + def apply_white(value, white): + assert value == "f1e2d305" + assert white == 0x0 + return "f1e2d300" + + feature_mock.apply_white = apply_white + + feature_mock.sensible_on_value = "f1e2d305" + + await hass.services.async_call( + "light", + SERVICE_TURN_ON, + {"entity_id": entity_id, ATTR_WHITE_VALUE: 0x0}, + blocking=True, + ) + + state = hass.states.get(entity_id) + assert state.state == STATE_ON + assert state.attributes[ATTR_WHITE_VALUE] == 0x0 + assert state.attributes[ATTR_HS_COLOR] == color.color_RGB_to_hs(0xF1, 0xE2, 0xD3) + + +async def test_wlightbox_on_via_just_hsl_color(wlightbox, hass, config): + """Test light on.""" + + feature_mock, entity_id = wlightbox + + def initial_update(): + feature_mock.is_on = False + feature_mock.rgbw_hex = "00000000" + + feature_mock.async_update = AsyncMock(side_effect=initial_update) + await async_setup_entity(hass, config, entity_id) + feature_mock.async_update = AsyncMock() + + state = hass.states.get(entity_id) + assert state.state == STATE_OFF + + hs_color = color.color_RGB_to_hs(0xFF, 0xA1, 0xB2) + + def turn_on(value): + feature_mock.is_on = True + assert value == "ffa1b2e4" + feature_mock.white_value = 0xE4 + feature_mock.rgbw_hex = value + + feature_mock.async_on = AsyncMock(side_effect=turn_on) + + def apply_color(value, color_value): + assert value == "c1a2e3e4" + assert color_value == "ffa0b1" + return "ffa1b2e4" + + feature_mock.apply_color = apply_color + feature_mock.sensible_on_value = "c1a2e3e4" + + await hass.services.async_call( + "light", + SERVICE_TURN_ON, + {"entity_id": entity_id, ATTR_HS_COLOR: hs_color}, + blocking=True, + ) + + state = hass.states.get(entity_id) + assert state.attributes[ATTR_HS_COLOR] == hs_color + assert state.attributes[ATTR_WHITE_VALUE] == 0xE4 + assert state.state == STATE_ON + + +async def test_wlightbox_on_to_last_color(wlightbox, hass, config): + """Test light on.""" + + feature_mock, entity_id = wlightbox + + def initial_update(): + feature_mock.is_on = False + + feature_mock.async_update = AsyncMock(side_effect=initial_update) + await async_setup_entity(hass, config, entity_id) + feature_mock.async_update = AsyncMock() + + state = hass.states.get(entity_id) + assert state.state == STATE_OFF + + def turn_on(value): + feature_mock.is_on = True + assert value == "f1e2d3e4" + feature_mock.white_value = 0xE4 + feature_mock.rgbw_hex = value + + feature_mock.async_on = AsyncMock(side_effect=turn_on) + feature_mock.sensible_on_value = "f1e2d3e4" + + await hass.services.async_call( + "light", SERVICE_TURN_ON, {"entity_id": entity_id}, blocking=True, + ) + + state = hass.states.get(entity_id) + assert state.attributes[ATTR_WHITE_VALUE] == 0xE4 + assert state.attributes[ATTR_HS_COLOR] == color.color_RGB_to_hs(0xF1, 0xE2, 0xD3) + assert state.state == STATE_ON + + +async def test_wlightbox_off(wlightbox, hass, config): + """Test light off.""" + + feature_mock, entity_id = wlightbox + + def initial_update(): + feature_mock.is_on = True + + feature_mock.async_update = AsyncMock(side_effect=initial_update) + await async_setup_entity(hass, config, entity_id) + feature_mock.async_update = AsyncMock() + + state = hass.states.get(entity_id) + assert state.state == STATE_ON + + def turn_off(): + feature_mock.is_on = False + feature_mock.white_value = 0x0 + feature_mock.rgbw_hex = "00000000" + + feature_mock.async_off = AsyncMock(side_effect=turn_off) + + await hass.services.async_call( + "light", SERVICE_TURN_OFF, {"entity_id": entity_id}, blocking=True, + ) + + state = hass.states.get(entity_id) + assert ATTR_WHITE_VALUE not in state.attributes + assert ATTR_HS_COLOR not in state.attributes + assert state.state == STATE_OFF + + +@pytest.mark.parametrize("feature", ALL_LIGHT_FIXTURES, indirect=["feature"]) +async def test_update_failure(feature, hass, config, caplog): + """Test that update failures are logged.""" + + caplog.set_level(logging.ERROR) + + feature_mock, entity_id = feature + feature_mock.async_update = AsyncMock(side_effect=blebox_uniapi.error.ClientError) + await async_setup_entity(hass, config, entity_id) + + assert f"Updating '{feature_mock.full_name}' failed: " in caplog.text + + +@pytest.mark.parametrize("feature", ALL_LIGHT_FIXTURES, indirect=["feature"]) +async def test_turn_on_failure(feature, hass, config, caplog): + """Test that turn_on failures are logged.""" + + caplog.set_level(logging.ERROR) + + feature_mock, entity_id = feature + feature_mock.async_on = AsyncMock(side_effect=blebox_uniapi.error.BadOnValueError) + await async_setup_entity(hass, config, entity_id) + + feature_mock.sensible_on_value = 123 + await hass.services.async_call( + "light", SERVICE_TURN_ON, {"entity_id": entity_id}, blocking=True, + ) + + assert ( + f"turning on '{feature_mock.full_name}' failed: Bad value 123 ()" in caplog.text + ) From 312080de08f59396679ad7513d6e95b51be8d1e9 Mon Sep 17 00:00:00 2001 From: gadgetmobile <57815233+gadgetmobile@users.noreply.github.com> Date: Mon, 18 May 2020 01:56:49 +0200 Subject: [PATCH 075/406] Cleanup BleBox platforms (#35552) --- homeassistant/components/blebox/switch.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/blebox/switch.py b/homeassistant/components/blebox/switch.py index 1e6f09a72a4..6cc05231929 100644 --- a/homeassistant/components/blebox/switch.py +++ b/homeassistant/components/blebox/switch.py @@ -1,5 +1,5 @@ """BleBox switch implementation.""" -from homeassistant.components.switch import SwitchDevice +from homeassistant.components.switch import SwitchEntity from . import BleBoxEntity, create_blebox_entities from .const import BLEBOX_TO_HASS_DEVICE_CLASSES @@ -12,7 +12,7 @@ async def async_setup_entry(hass, config_entry, async_add): ) -class BleBoxSwitchEntity(BleBoxEntity, SwitchDevice): +class BleBoxSwitchEntity(BleBoxEntity, SwitchEntity): """Representation of a BleBox switch feature.""" @property @@ -27,8 +27,8 @@ class BleBoxSwitchEntity(BleBoxEntity, SwitchDevice): async def async_turn_on(self, **kwargs): """Turn on the switch.""" - return await self._feature.async_turn_on() + await self._feature.async_turn_on() async def async_turn_off(self, **kwargs): """Turn off the switch.""" - return await self._feature.async_turn_off() + await self._feature.async_turn_off() From 2e0c0ded513a271bfce85e0e9a142078ad68312b Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Mon, 18 May 2020 00:03:08 +0000 Subject: [PATCH 076/406] [ci skip] Translation update --- .../components/acmeda/translations/ca.json | 11 +++++++++ .../components/acmeda/translations/en.json | 24 +++++++++---------- .../components/acmeda/translations/ru.json | 16 +++++++++++++ .../components/gogogate2/translations/es.json | 22 +++++++++++++++++ .../components/gogogate2/translations/pl.json | 20 ++++++++++++++++ .../gogogate2/translations/zh-Hant.json | 22 +++++++++++++++++ .../components/ipp/translations/zh-Hant.json | 3 ++- .../components/tuya/translations/pl.json | 1 + .../components/upnp/translations/ru.json | 2 +- .../components/wiffi/translations/ca.json | 9 +++++++ .../components/wiffi/translations/es.json | 9 +++++++ .../components/wiffi/translations/ru.json | 9 +++++++ .../xiaomi_miio/translations/pl.json | 3 ++- 13 files changed, 136 insertions(+), 15 deletions(-) create mode 100644 homeassistant/components/acmeda/translations/ca.json create mode 100644 homeassistant/components/acmeda/translations/ru.json create mode 100644 homeassistant/components/gogogate2/translations/es.json create mode 100644 homeassistant/components/gogogate2/translations/pl.json create mode 100644 homeassistant/components/gogogate2/translations/zh-Hant.json diff --git a/homeassistant/components/acmeda/translations/ca.json b/homeassistant/components/acmeda/translations/ca.json new file mode 100644 index 00000000000..13c816a90d5 --- /dev/null +++ b/homeassistant/components/acmeda/translations/ca.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "id": "ID d'amfitri\u00f3" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/acmeda/translations/en.json b/homeassistant/components/acmeda/translations/en.json index eb7ed44999b..ab20d0b1939 100644 --- a/homeassistant/components/acmeda/translations/en.json +++ b/homeassistant/components/acmeda/translations/en.json @@ -1,16 +1,16 @@ { - "title": "Rollease Acmeda Automate", - "config": { - "step": { - "user": { - "title": "Pick a hub to add", - "data": { - "id": "Host ID" + "config": { + "abort": { + "all_configured": "No new Pulse hubs discovered." + }, + "step": { + "user": { + "data": { + "id": "Host ID" + }, + "title": "Pick a hub to add" + } } - } }, - "abort": { - "all_configured": "No new Pulse hubs discovered." - } - } + "title": "Rollease Acmeda Automate" } \ No newline at end of file diff --git a/homeassistant/components/acmeda/translations/ru.json b/homeassistant/components/acmeda/translations/ru.json new file mode 100644 index 00000000000..92922fdbb5d --- /dev/null +++ b/homeassistant/components/acmeda/translations/ru.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "all_configured": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u043d\u0435 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u044b." + }, + "step": { + "user": { + "data": { + "id": "ID \u0445\u043e\u0441\u0442\u0430" + }, + "title": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0445\u0430\u0431, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043d\u0443\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c" + } + } + }, + "title": "Rollease Acmeda Automate" +} \ No newline at end of file diff --git a/homeassistant/components/gogogate2/translations/es.json b/homeassistant/components/gogogate2/translations/es.json new file mode 100644 index 00000000000..1498cc12368 --- /dev/null +++ b/homeassistant/components/gogogate2/translations/es.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "cannot_connect": "No se pudo conectar" + }, + "error": { + "cannot_connect": "No se pudo conectar", + "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida" + }, + "step": { + "user": { + "data": { + "ip_address": "Direcci\u00f3n IP", + "password": "Contrase\u00f1a", + "username": "Usuario" + }, + "description": "Proporciona la informaci\u00f3n requerida a continuaci\u00f3n.", + "title": "Configurar GotoGate2" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gogogate2/translations/pl.json b/homeassistant/components/gogogate2/translations/pl.json new file mode 100644 index 00000000000..acc11bb1d01 --- /dev/null +++ b/homeassistant/components/gogogate2/translations/pl.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia." + }, + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia.", + "invalid_auth": "Niepoprawne uwierzytelnienie." + }, + "step": { + "user": { + "data": { + "ip_address": "Adres IP", + "password": "Has\u0142o", + "username": "Nazwa u\u017cytkownika" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gogogate2/translations/zh-Hant.json b/homeassistant/components/gogogate2/translations/zh-Hant.json new file mode 100644 index 00000000000..7ba01116084 --- /dev/null +++ b/homeassistant/components/gogogate2/translations/zh-Hant.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "cannot_connect": "\u9023\u7dda\u5931\u6557" + }, + "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548" + }, + "step": { + "user": { + "data": { + "ip_address": "IP \u4f4d\u5740", + "password": "\u5bc6\u78bc", + "username": "\u4f7f\u7528\u8005\u540d\u7a31" + }, + "description": "\u8acb\u65bc\u4e0b\u65b9\u63d0\u4f9b\u6240\u9700\u8cc7\u8a0a\u3002", + "title": "\u8a2d\u5b9a GogoGate2" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ipp/translations/zh-Hant.json b/homeassistant/components/ipp/translations/zh-Hant.json index fa7593c1ea2..d1c9c00bdb8 100644 --- a/homeassistant/components/ipp/translations/zh-Hant.json +++ b/homeassistant/components/ipp/translations/zh-Hant.json @@ -6,7 +6,8 @@ "connection_upgrade": "\u7531\u65bc\u9700\u8981\u5148\u5347\u7d1a\u9023\u7dda\u3001\u9023\u7dda\u81f3\u5370\u8868\u6a5f\u5931\u6557\u3002", "ipp_error": "\u767c\u751f IPP \u932f\u8aa4\u3002", "ipp_version_error": "\u4e0d\u652f\u63f4\u5370\u8868\u6a5f\u7684 IPP \u7248\u672c\u3002", - "parse_error": "\u7372\u5f97\u5370\u8868\u6a5f\u56de\u61c9\u5931\u6557\u3002" + "parse_error": "\u7372\u5f97\u5370\u8868\u6a5f\u56de\u61c9\u5931\u6557\u3002", + "unique_id_required": "\u8a2d\u5099\u7f3a\u5c11\u641c\u5c0b\u6240\u9700\u7368\u4e00\u8b58\u5225\u3002" }, "error": { "connection_error": "\u9023\u7dda\u5931\u6557", diff --git a/homeassistant/components/tuya/translations/pl.json b/homeassistant/components/tuya/translations/pl.json index 9c6dc119d62..d79f81e6850 100644 --- a/homeassistant/components/tuya/translations/pl.json +++ b/homeassistant/components/tuya/translations/pl.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "already_in_progress": "Konfiguracja integracji Tuya jest ju\u017c w toku.", "auth_failed": "Niepoprawne uwierzytelnienie.", "conn_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia.", "single_instance_allowed": "Ju\u017c skonfigurowano. Mo\u017cliwa jest tylko jedna konfiguracja." diff --git a/homeassistant/components/upnp/translations/ru.json b/homeassistant/components/upnp/translations/ru.json index 571897f81e6..ee899c998fe 100644 --- a/homeassistant/components/upnp/translations/ru.json +++ b/homeassistant/components/upnp/translations/ru.json @@ -4,7 +4,7 @@ "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", "incomplete_device": "\u0418\u0433\u043d\u043e\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043d\u0435\u043f\u043e\u043b\u043d\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 UPnP.", "incomplete_discovery": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u044c \u043f\u0440\u043e\u0446\u0435\u0441\u0441.", - "no_devices_discovered": "\u041d\u0435 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u043e UPnP / IGD.", + "no_devices_discovered": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 UPnP / IGD \u043d\u0435 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u044b.", "no_devices_found": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 UPnP / IGD \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b \u0432 \u0441\u0435\u0442\u0438.", "no_sensors_or_port_mapping": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u0441\u0435\u043d\u0441\u043e\u0440\u044b \u0438\u043b\u0438 \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u043f\u043e\u0440\u0442\u043e\u0432.", "single_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." diff --git a/homeassistant/components/wiffi/translations/ca.json b/homeassistant/components/wiffi/translations/ca.json index f21470653d7..33fc5015ecd 100644 --- a/homeassistant/components/wiffi/translations/ca.json +++ b/homeassistant/components/wiffi/translations/ca.json @@ -12,5 +12,14 @@ "title": "Configuraci\u00f3 del servidor TCP per a dispositius WIFFI" } } + }, + "options": { + "step": { + "init": { + "data": { + "timeout": "Temps m\u00e0xim d'espera (minuts)" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/wiffi/translations/es.json b/homeassistant/components/wiffi/translations/es.json index d1ff792d5dd..6ab1f5c5407 100644 --- a/homeassistant/components/wiffi/translations/es.json +++ b/homeassistant/components/wiffi/translations/es.json @@ -12,5 +12,14 @@ "title": "Configurar servidor TCP para dispositivos WIFFI" } } + }, + "options": { + "step": { + "init": { + "data": { + "timeout": "Tiempo de espera (minutos)" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/wiffi/translations/ru.json b/homeassistant/components/wiffi/translations/ru.json index e54389a0f34..50d0ec06718 100644 --- a/homeassistant/components/wiffi/translations/ru.json +++ b/homeassistant/components/wiffi/translations/ru.json @@ -12,5 +12,14 @@ "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 TCP-\u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0434\u043b\u044f \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432 WIFFI" } } + }, + "options": { + "step": { + "init": { + "data": { + "timeout": "\u0422\u0430\u0439\u043c-\u0430\u0443\u0442 (\u0432 \u043c\u0438\u043d\u0443\u0442\u0430\u0445)" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/xiaomi_miio/translations/pl.json b/homeassistant/components/xiaomi_miio/translations/pl.json index 89a462f4f38..bd6dd1923d5 100644 --- a/homeassistant/components/xiaomi_miio/translations/pl.json +++ b/homeassistant/components/xiaomi_miio/translations/pl.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane." + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane.", + "already_in_progress": "Konfiguracja tego urz\u0105dzenia Xiaomi Miio jest ju\u017c w toku." }, "error": { "connect_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia.", From 52a7a7175bee86b9029e3e29c0600d6b56a9af01 Mon Sep 17 00:00:00 2001 From: Josef Schlehofer Date: Mon, 18 May 2020 03:07:23 +0200 Subject: [PATCH 077/406] Upgrade sqlalchemy to 1.3.17 (#35745) --- homeassistant/components/recorder/manifest.json | 2 +- homeassistant/components/sql/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/recorder/manifest.json b/homeassistant/components/recorder/manifest.json index 0d662c572bd..44396c6eccb 100644 --- a/homeassistant/components/recorder/manifest.json +++ b/homeassistant/components/recorder/manifest.json @@ -2,7 +2,7 @@ "domain": "recorder", "name": "Recorder", "documentation": "https://www.home-assistant.io/integrations/recorder", - "requirements": ["sqlalchemy==1.3.16"], + "requirements": ["sqlalchemy==1.3.17"], "codeowners": [], "quality_scale": "internal" } diff --git a/homeassistant/components/sql/manifest.json b/homeassistant/components/sql/manifest.json index 9bfc5b35288..a5caa1b1592 100644 --- a/homeassistant/components/sql/manifest.json +++ b/homeassistant/components/sql/manifest.json @@ -2,6 +2,6 @@ "domain": "sql", "name": "SQL", "documentation": "https://www.home-assistant.io/integrations/sql", - "requirements": ["sqlalchemy==1.3.16"], + "requirements": ["sqlalchemy==1.3.17"], "codeowners": ["@dgomes"] } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 0e129e292af..88f6ff5b1ad 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -22,7 +22,7 @@ pytz>=2020.1 pyyaml==5.3.1 requests==2.23.0 ruamel.yaml==0.15.100 -sqlalchemy==1.3.16 +sqlalchemy==1.3.17 voluptuous-serialize==2.3.0 voluptuous==0.11.7 zeroconf==0.26.1 diff --git a/requirements_all.txt b/requirements_all.txt index 0b305d809c1..f488e6c94f5 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2006,7 +2006,7 @@ spotipy==2.12.0 # homeassistant.components.recorder # homeassistant.components.sql -sqlalchemy==1.3.16 +sqlalchemy==1.3.17 # homeassistant.components.starline starline==0.1.3 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index c58ad9fc21d..4dde6361d6c 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -814,7 +814,7 @@ spotipy==2.12.0 # homeassistant.components.recorder # homeassistant.components.sql -sqlalchemy==1.3.16 +sqlalchemy==1.3.17 # homeassistant.components.starline starline==0.1.3 From 796e6141ac2c301e6394cb4b6bef9dd287a3c866 Mon Sep 17 00:00:00 2001 From: Anders Melchiorsen Date: Mon, 18 May 2020 03:20:10 +0200 Subject: [PATCH 078/406] Handle Sonos changing IP address (#35639) --- homeassistant/components/sonos/media_player.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/sonos/media_player.py b/homeassistant/components/sonos/media_player.py index 59147d727c6..b4a0fb7d208 100644 --- a/homeassistant/components/sonos/media_player.py +++ b/homeassistant/components/sonos/media_player.py @@ -152,15 +152,16 @@ async def async_setup_entry(hass, config_entry, async_add_entities): try: _LOGGER.debug("Reached _discovered_player, soco=%s", soco) - if soco not in hass.data[DATA_SONOS].discovered: + if soco.uid not in hass.data[DATA_SONOS].discovered: _LOGGER.debug("Adding new entity") - hass.data[DATA_SONOS].discovered.append(soco) + hass.data[DATA_SONOS].discovered.append(soco.uid) hass.add_job(async_add_entities, [SonosEntity(soco)]) else: entity = _get_entity_from_soco_uid(hass, soco.uid) - if entity: + if entity and (entity.soco == soco or not entity.available): _LOGGER.debug("Seen %s", entity) - hass.add_job(entity.async_seen()) + hass.add_job(entity.async_seen(soco)) + except SoCoException as ex: _LOGGER.debug("SoCoException, ex=%s", ex) @@ -398,7 +399,7 @@ class SonosEntity(MediaPlayerEntity): async def async_added_to_hass(self): """Subscribe sonos events.""" - await self.async_seen() + await self.async_seen(self.soco) self.hass.data[DATA_SONOS].entities.append(self) @@ -464,10 +465,12 @@ class SonosEntity(MediaPlayerEntity): """Return coordinator of this player.""" return self._coordinator - async def async_seen(self): + async def async_seen(self, player): """Record that this player was seen right now.""" was_available = self.available + self._player = player + if self._seen_timer: self._seen_timer() From affd11b372b0ee801d222bbe71f7afb8f6ee1657 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20H=C3=B8yer=20Iversen?= Date: Mon, 18 May 2020 11:00:44 +0200 Subject: [PATCH 079/406] Update mill manifest to reflect config flow (#35748) --- homeassistant/components/mill/manifest.json | 3 ++- homeassistant/generated/config_flows.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/mill/manifest.json b/homeassistant/components/mill/manifest.json index 07eec93bb65..684be0479bd 100644 --- a/homeassistant/components/mill/manifest.json +++ b/homeassistant/components/mill/manifest.json @@ -3,5 +3,6 @@ "name": "Mill", "documentation": "https://www.home-assistant.io/integrations/mill", "requirements": ["millheater==0.3.4"], - "codeowners": ["@danielhiversen"] + "codeowners": ["@danielhiversen"], + "config_flow": true } diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 10d6e11baf4..2918f09d626 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -91,6 +91,7 @@ FLOWS = [ "met", "meteo_france", "mikrotik", + "mill", "minecraft_server", "mobile_app", "monoprice", From 50105eed74417d2fb13062292cdd83db2ccf276c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 18 May 2020 09:23:05 -0500 Subject: [PATCH 080/406] Handle UPS disconnects in NUT (#35758) --- homeassistant/components/nut/__init__.py | 6 ++++-- homeassistant/components/nut/sensor.py | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/nut/__init__.py b/homeassistant/components/nut/__init__.py index 99563ca65d4..5669b8a5c3b 100644 --- a/homeassistant/components/nut/__init__.py +++ b/homeassistant/components/nut/__init__.py @@ -18,7 +18,7 @@ from homeassistant.const import ( ) from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady -from homeassistant.helpers.update_coordinator import DataUpdateCoordinator +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from .const import ( COORDINATOR, @@ -61,7 +61,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): async def async_update_data(): """Fetch data from NUT.""" async with async_timeout.timeout(10): - return await hass.async_add_executor_job(data.update) + await hass.async_add_executor_job(data.update) + if not data.status: + raise UpdateFailed("Error fetching UPS state") coordinator = DataUpdateCoordinator( hass, diff --git a/homeassistant/components/nut/sensor.py b/homeassistant/components/nut/sensor.py index 32daaaa2582..3c2144a0aee 100644 --- a/homeassistant/components/nut/sensor.py +++ b/homeassistant/components/nut/sensor.py @@ -189,6 +189,8 @@ class NUTSensor(Entity): @property def state(self): """Return entity state from ups.""" + if not self._data.status: + return None if self._type == KEY_STATUS_DISPLAY: return _format_display_state(self._data.status) return self._data.status.get(self._type) From d0e8880e483eebfcc24c1fb0ae4c1e4556708232 Mon Sep 17 00:00:00 2001 From: Fredrik Erlandsson Date: Mon, 18 May 2020 16:57:52 +0200 Subject: [PATCH 081/406] Fix daikin discovery flow (#35767) --- .../components/daikin/config_flow.py | 51 +++++++------ homeassistant/components/daikin/manifest.json | 2 +- homeassistant/components/daikin/strings.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/daikin/test_config_flow.py | 71 +++++++++++-------- 6 files changed, 74 insertions(+), 56 deletions(-) diff --git a/homeassistant/components/daikin/config_flow.py b/homeassistant/components/daikin/config_flow.py index 8dc491ac868..cd5be5cef29 100644 --- a/homeassistant/components/daikin/config_flow.py +++ b/homeassistant/components/daikin/config_flow.py @@ -15,14 +15,6 @@ from .const import CONF_KEY, CONF_UUID, KEY_IP, KEY_MAC, TIMEOUT _LOGGER = logging.getLogger(__name__) -DATA_SCHEMA = vol.Schema( - { - vol.Required(CONF_HOST): str, - vol.Optional(CONF_KEY): str, - vol.Optional(CONF_PASSWORD): str, - } -) - @config_entries.HANDLERS.register("daikin") class FlowHandler(config_entries.ConfigFlow): @@ -31,12 +23,26 @@ class FlowHandler(config_entries.ConfigFlow): VERSION = 1 CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL - def _create_entry(self, host, mac, key=None, uuid=None, password=None): + def __init__(self): + """Initialize the Daikin config flow.""" + self.host = None + + @property + def schema(self): + """Return current schema.""" + return vol.Schema( + { + vol.Required(CONF_HOST, default=self.host): str, + vol.Optional(CONF_KEY): str, + vol.Optional(CONF_PASSWORD): str, + } + ) + + async def _create_entry(self, host, mac, key=None, uuid=None, password=None): """Register new entry.""" # Check if mac already is registered - for entry in self._async_current_entries(): - if entry.data[KEY_MAC] == mac: - return self.async_abort(reason="already_configured") + await self.async_set_unique_id(mac) + self._abort_if_unique_id_configured() return self.async_create_entry( title=host, @@ -73,31 +79,31 @@ class FlowHandler(config_entries.ConfigFlow): except asyncio.TimeoutError: return self.async_show_form( step_id="user", - data_schema=DATA_SCHEMA, + data_schema=self.schema, errors={"base": "device_timeout"}, ) except web_exceptions.HTTPForbidden: return self.async_show_form( - step_id="user", data_schema=DATA_SCHEMA, errors={"base": "forbidden"}, + step_id="user", data_schema=self.schema, errors={"base": "forbidden"}, ) except ClientError: _LOGGER.exception("ClientError") return self.async_show_form( - step_id="user", data_schema=DATA_SCHEMA, errors={"base": "device_fail"}, + step_id="user", data_schema=self.schema, errors={"base": "device_fail"}, ) except Exception: # pylint: disable=broad-except _LOGGER.exception("Unexpected error creating device") return self.async_show_form( - step_id="user", data_schema=DATA_SCHEMA, errors={"base": "device_fail"}, + step_id="user", data_schema=self.schema, errors={"base": "device_fail"}, ) mac = device.mac - return self._create_entry(host, mac, key, uuid, password) + return await self._create_entry(host, mac, key, uuid, password) async def async_step_user(self, user_input=None): """User initiated config flow.""" if user_input is None: - return self.async_show_form(step_id="user", data_schema=DATA_SCHEMA,) + return self.async_show_form(step_id="user", data_schema=self.schema) return await self._create_device( user_input[CONF_HOST], user_input.get(CONF_KEY), @@ -111,7 +117,10 @@ class FlowHandler(config_entries.ConfigFlow): return await self.async_step_user() return await self._create_device(host) - async def async_step_discovery(self, user_input): + async def async_step_discovery(self, discovery_info): """Initialize step from discovery.""" - _LOGGER.info("Discovered device: %s", user_input) - return self._create_entry(user_input[KEY_IP], user_input[KEY_MAC]) + _LOGGER.debug("Discovered device: %s", discovery_info) + await self.async_set_unique_id(discovery_info[KEY_MAC]) + self._abort_if_unique_id_configured() + self.host = discovery_info[KEY_IP] + return await self.async_step_user() diff --git a/homeassistant/components/daikin/manifest.json b/homeassistant/components/daikin/manifest.json index c554e882b48..9732962de5a 100644 --- a/homeassistant/components/daikin/manifest.json +++ b/homeassistant/components/daikin/manifest.json @@ -3,7 +3,7 @@ "name": "Daikin AC", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/daikin", - "requirements": ["pydaikin==2.0.1"], + "requirements": ["pydaikin==2.0.2"], "codeowners": ["@fredrike"], "quality_scale": "platinum" } diff --git a/homeassistant/components/daikin/strings.json b/homeassistant/components/daikin/strings.json index 0286ca00c32..c60163577a6 100644 --- a/homeassistant/components/daikin/strings.json +++ b/homeassistant/components/daikin/strings.json @@ -3,7 +3,7 @@ "step": { "user": { "title": "Configure Daikin AC", - "description": "Enter IP address of your Daikin AC.", + "description": "Enter IP address of your Daikin AC.\n\nNote that [%key:common::config_flow::data::api_key%] and [%key:common::config_flow::data::password%] are used by BRP072Cxx and SKYFi devices respectively.", "data": { "host": "[%key:common::config_flow::data::host%]", "key": "[%key:common::config_flow::data::api_key%]", diff --git a/requirements_all.txt b/requirements_all.txt index f488e6c94f5..a1196251b0f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1263,7 +1263,7 @@ pycsspeechtts==1.0.3 # pycups==1.9.73 # homeassistant.components.daikin -pydaikin==2.0.1 +pydaikin==2.0.2 # homeassistant.components.danfoss_air pydanfossair==0.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 4dde6361d6c..b3afbc47a9d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -539,7 +539,7 @@ pychromecast==5.2.0 pycoolmasternet==0.0.4 # homeassistant.components.daikin -pydaikin==2.0.1 +pydaikin==2.0.2 # homeassistant.components.deconz pydeconz==70 diff --git a/tests/components/daikin/test_config_flow.py b/tests/components/daikin/test_config_flow.py index 83e13047f80..25fc8ba26f2 100644 --- a/tests/components/daikin/test_config_flow.py +++ b/tests/components/daikin/test_config_flow.py @@ -6,8 +6,8 @@ from aiohttp import ClientError from aiohttp.web_exceptions import HTTPForbidden import pytest -from homeassistant.components.daikin import config_flow from homeassistant.components.daikin.const import KEY_IP, KEY_MAC +from homeassistant.config_entries import SOURCE_DISCOVERY, SOURCE_IMPORT, SOURCE_USER from homeassistant.const import CONF_HOST from homeassistant.data_entry_flow import ( RESULT_TYPE_ABORT, @@ -22,13 +22,6 @@ MAC = "AABBCCDDEEFF" HOST = "127.0.0.1" -def init_config_flow(hass): - """Init a configuration flow.""" - flow = config_flow.FlowHandler() - flow.hass = hass - return flow - - @pytest.fixture def mock_daikin(): """Mock pydaikin.""" @@ -45,13 +38,16 @@ def mock_daikin(): async def test_user(hass, mock_daikin): """Test user config.""" - flow = init_config_flow(hass) + result = await hass.config_entries.flow.async_init( + "daikin", context={"source": SOURCE_USER}, + ) - result = await flow.async_step_user() assert result["type"] == RESULT_TYPE_FORM assert result["step_id"] == "user" - result = await flow.async_step_user({CONF_HOST: HOST}) + result = await hass.config_entries.flow.async_init( + "daikin", context={"source": SOURCE_USER}, data={CONF_HOST: HOST}, + ) assert result["type"] == RESULT_TYPE_CREATE_ENTRY assert result["title"] == HOST assert result["data"][CONF_HOST] == HOST @@ -60,34 +56,26 @@ async def test_user(hass, mock_daikin): async def test_abort_if_already_setup(hass, mock_daikin): """Test we abort if Daikin is already setup.""" - flow = init_config_flow(hass) - MockConfigEntry(domain="daikin", data={KEY_MAC: MAC}).add_to_hass(hass) + MockConfigEntry(domain="daikin", unique_id=MAC).add_to_hass(hass) + result = await hass.config_entries.flow.async_init( + "daikin", context={"source": SOURCE_USER}, data={CONF_HOST: HOST, KEY_MAC: MAC}, + ) - result = await flow.async_step_user({CONF_HOST: HOST}) assert result["type"] == RESULT_TYPE_ABORT assert result["reason"] == "already_configured" async def test_import(hass, mock_daikin): """Test import step.""" - flow = init_config_flow(hass) - - result = await flow.async_step_import({}) + result = await hass.config_entries.flow.async_init( + "daikin", context={"source": SOURCE_IMPORT}, data={}, + ) assert result["type"] == RESULT_TYPE_FORM assert result["step_id"] == "user" - result = await flow.async_step_import({CONF_HOST: HOST}) - assert result["type"] == RESULT_TYPE_CREATE_ENTRY - assert result["title"] == HOST - assert result["data"][CONF_HOST] == HOST - assert result["data"][KEY_MAC] == MAC - - -async def test_discovery(hass, mock_daikin): - """Test discovery step.""" - flow = init_config_flow(hass) - - result = await flow.async_step_discovery({KEY_IP: HOST, KEY_MAC: MAC}) + result = await hass.config_entries.flow.async_init( + "daikin", context={"source": SOURCE_IMPORT}, data={CONF_HOST: HOST}, + ) assert result["type"] == RESULT_TYPE_CREATE_ENTRY assert result["title"] == HOST assert result["data"][CONF_HOST] == HOST @@ -105,10 +93,31 @@ async def test_discovery(hass, mock_daikin): ) async def test_device_abort(hass, mock_daikin, s_effect, reason): """Test device abort.""" - flow = init_config_flow(hass) mock_daikin.factory.side_effect = s_effect - result = await flow.async_step_user({CONF_HOST: HOST}) + result = await hass.config_entries.flow.async_init( + "daikin", context={"source": SOURCE_USER}, data={CONF_HOST: HOST, KEY_MAC: MAC}, + ) assert result["type"] == RESULT_TYPE_FORM assert result["errors"] == {"base": reason} assert result["step_id"] == "user" + + +@pytest.mark.parametrize( + "source, data, unique_id", [(SOURCE_DISCOVERY, {KEY_IP: HOST, KEY_MAC: MAC}, MAC)], +) +async def test_discovery(hass, mock_daikin, source, data, unique_id): + """Test discovery/zeroconf step.""" + result = await hass.config_entries.flow.async_init( + "daikin", context={"source": source}, data=data, + ) + assert result["type"] == RESULT_TYPE_FORM + assert result["step_id"] == "user" + + MockConfigEntry(domain="daikin", unique_id=unique_id).add_to_hass(hass) + result = await hass.config_entries.flow.async_init( + "daikin", context={"source": source}, data=data, + ) + + assert result["type"] == RESULT_TYPE_ABORT + assert result["reason"] == "already_in_progress" From aec68a9c2d86508c989c5974e8b71399629555b7 Mon Sep 17 00:00:00 2001 From: MatsNl <37705266+MatsNl@users.noreply.github.com> Date: Mon, 18 May 2020 18:12:00 +0200 Subject: [PATCH 082/406] Bump Atag dependency to 0.3.1.2 (#35776) --- 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 b28f6875fa4..5fd77ee5155 100644 --- a/homeassistant/components/atag/manifest.json +++ b/homeassistant/components/atag/manifest.json @@ -3,6 +3,6 @@ "name": "Atag", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/atag/", - "requirements": ["pyatag==0.3.1.1"], + "requirements": ["pyatag==0.3.1.2"], "codeowners": ["@MatsNL"] } diff --git a/requirements_all.txt b/requirements_all.txt index a1196251b0f..a32e34782fe 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1212,7 +1212,7 @@ pyalmond==0.0.2 pyarlo==0.2.3 # homeassistant.components.atag -pyatag==0.3.1.1 +pyatag==0.3.1.2 # homeassistant.components.netatmo pyatmo==3.3.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index b3afbc47a9d..ca39230c767 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -521,7 +521,7 @@ pyalmond==0.0.2 pyarlo==0.2.3 # homeassistant.components.atag -pyatag==0.3.1.1 +pyatag==0.3.1.2 # homeassistant.components.netatmo pyatmo==3.3.1 From 6885d7218090ea626d569f980d4595e4138f72a0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 18 May 2020 11:57:16 -0500 Subject: [PATCH 083/406] Avoid a context switch in the history api (#35716) * Avoid a context switch in the history api The history api was creating a job to fetch the states and another job to convert the states to json. This can be done in a single job which decreases the overhead of the operation. * Revert to original solution to avoid function redefine each call --- homeassistant/components/history/__init__.py | 28 +++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/history/__init__.py b/homeassistant/components/history/__init__.py index 6fc68b2833e..8d2f9baab81 100644 --- a/homeassistant/components/history/__init__.py +++ b/homeassistant/components/history/__init__.py @@ -298,7 +298,6 @@ class HistoryPeriodView(HomeAssistantView): async def get(self, request, datetime=None): """Return history over a period of time.""" - timer_start = time.perf_counter() if datetime: datetime = dt_util.parse_datetime(datetime) @@ -335,8 +334,29 @@ class HistoryPeriodView(HomeAssistantView): hass = request.app["hass"] - result = await hass.async_add_job( - get_significant_states, + return await hass.async_add_executor_job( + self._sorted_significant_states_json, + hass, + start_time, + end_time, + entity_ids, + include_start_time_state, + significant_changes_only, + ) + + def _sorted_significant_states_json( + self, + hass, + start_time, + end_time, + entity_ids, + include_start_time_state, + significant_changes_only, + ): + """Fetch significant stats from the database as json.""" + timer_start = time.perf_counter() + + result = get_significant_states( hass, start_time, end_time, @@ -363,7 +383,7 @@ class HistoryPeriodView(HomeAssistantView): sorted_result.extend(result) result = sorted_result - return await hass.async_add_job(self.json, result) + return self.json(result) class Filters: From b464d276e21eb30af0080e4681c11d184e704923 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Mon, 18 May 2020 12:00:23 -0600 Subject: [PATCH 084/406] Add support for templates in Slack blocks (#34704) --- homeassistant/components/slack/notify.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/slack/notify.py b/homeassistant/components/slack/notify.py index 8cfffc1722a..6f05eebd87d 100644 --- a/homeassistant/components/slack/notify.py +++ b/homeassistant/components/slack/notify.py @@ -18,11 +18,13 @@ from homeassistant.components.notify import ( from homeassistant.const import CONF_API_KEY, CONF_ICON, CONF_USERNAME from homeassistant.core import callback from homeassistant.helpers import aiohttp_client, config_validation as cv +import homeassistant.helpers.template as template _LOGGER = logging.getLogger(__name__) ATTR_ATTACHMENTS = "attachments" ATTR_BLOCKS = "blocks" +ATTR_BLOCKS_TEMPLATE = "blocks_template" ATTR_FILE = "file" CONF_DEFAULT_CHANNEL = "default_channel" @@ -65,6 +67,20 @@ def _async_sanitize_channel_names(channel_list): return [channel.lstrip("#") for channel in channel_list] +@callback +def _async_templatize_blocks(hass, value): + """Recursive template creator helper function.""" + if isinstance(value, list): + return [_async_templatize_blocks(hass, item) for item in value] + if isinstance(value, dict): + return { + key: _async_templatize_blocks(hass, item) for key, item in value.items() + } + + tmpl = template.Template(value, hass=hass) + return tmpl.async_render() + + class SlackNotificationService(BaseNotificationService): """Define the Slack notification logic.""" @@ -142,7 +158,13 @@ class SlackNotificationService(BaseNotificationService): "for them will be dropped in 0.114.0. In most cases, Blocks should be " "used instead: https://www.home-assistant.io/integrations/slack/" ) - blocks = data.get(ATTR_BLOCKS, {}) + + if ATTR_BLOCKS_TEMPLATE in data: + blocks = _async_templatize_blocks(self.hass, data[ATTR_BLOCKS_TEMPLATE]) + elif ATTR_BLOCKS in data: + blocks = data[ATTR_BLOCKS] + else: + blocks = {} return await self._async_send_text_only_message( targets, message, title, attachments, blocks From a8fb627abcf3a9346aac602ed17a450abcf8fcab Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 18 May 2020 21:38:25 +0200 Subject: [PATCH 085/406] Updated frontend to 20200518.0 (#35785) --- 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 f669452b92e..1c33802e655 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==20200515.0"], + "requirements": ["home-assistant-frontend==20200518.0"], "dependencies": [ "api", "auth", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 88f6ff5b1ad..b9fa8969a4d 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -12,7 +12,7 @@ cryptography==2.9.2 defusedxml==0.6.0 distro==1.5.0 hass-nabucasa==0.34.2 -home-assistant-frontend==20200515.0 +home-assistant-frontend==20200518.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 a32e34782fe..25b0b54997b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -734,7 +734,7 @@ hole==0.5.1 holidays==0.10.2 # homeassistant.components.frontend -home-assistant-frontend==20200515.0 +home-assistant-frontend==20200518.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ca39230c767..76b515a789e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -318,7 +318,7 @@ hole==0.5.1 holidays==0.10.2 # homeassistant.components.frontend -home-assistant-frontend==20200515.0 +home-assistant-frontend==20200518.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From 8258fa515d3f40e250d8d1b9f7e719453f183322 Mon Sep 17 00:00:00 2001 From: uvjustin <46082645+uvjustin@users.noreply.github.com> Date: Tue, 19 May 2020 03:40:39 +0800 Subject: [PATCH 086/406] Skip forked_daapd ignored entries with empty entry.data (#35772) --- homeassistant/components/forked_daapd/config_flow.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/forked_daapd/config_flow.py b/homeassistant/components/forked_daapd/config_flow.py index 697c3f0c7ac..c860e08ffc4 100644 --- a/homeassistant/components/forked_daapd/config_flow.py +++ b/homeassistant/components/forked_daapd/config_flow.py @@ -163,18 +163,18 @@ class ForkedDaapdFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): ): return self.async_abort(reason="not_forked_daapd") + await self.async_set_unique_id(discovery_info["properties"]["Machine Name"]) + self._abort_if_unique_id_configured() + # Update title and abort if we already have an entry for this host for entry in self._async_current_entries(): - if entry.data[CONF_HOST] != discovery_info["host"]: + if entry.data.get(CONF_HOST) != discovery_info["host"]: continue self.hass.config_entries.async_update_entry( entry, title=discovery_info["properties"]["Machine Name"], ) return self.async_abort(reason="already_configured") - await self.async_set_unique_id(discovery_info["properties"]["Machine Name"]) - self._abort_if_unique_id_configured() - zeroconf_data = { CONF_HOST: discovery_info["host"], CONF_PORT: int(discovery_info["port"]), From 93fddbed2be83f5421c480e65c7f3bbade628b57 Mon Sep 17 00:00:00 2001 From: gadgetmobile <57815233+gadgetmobile@users.noreply.github.com> Date: Mon, 18 May 2020 22:30:15 +0200 Subject: [PATCH 087/406] Fix review requests for BleBox support from #35370 (#35786) * fix review requests from #35370 * fix pylint W0621 (redefined-outer-name) --- homeassistant/components/blebox/__init__.py | 6 ++++-- homeassistant/components/blebox/air_quality.py | 4 ++-- homeassistant/components/blebox/cover.py | 6 ++++-- homeassistant/components/blebox/light.py | 14 ++++++++------ homeassistant/components/blebox/sensor.py | 6 ++++-- homeassistant/components/blebox/switch.py | 4 ++-- tests/components/blebox/conftest.py | 2 +- tests/components/blebox/test_config_flow.py | 12 ++++++------ tests/components/blebox/test_light.py | 2 +- 9 files changed, 32 insertions(+), 24 deletions(-) diff --git a/homeassistant/components/blebox/__init__.py b/homeassistant/components/blebox/__init__.py index 36d319b43fb..9245392f89a 100644 --- a/homeassistant/components/blebox/__init__.py +++ b/homeassistant/components/blebox/__init__.py @@ -74,7 +74,9 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): @callback -def create_blebox_entities(hass, config_entry, async_add, entity_klass, entity_type): +def create_blebox_entities( + hass, config_entry, async_add_entities, entity_klass, entity_type +): """Create entities from a BleBox product's features.""" product = hass.data[DOMAIN][config_entry.entry_id][PRODUCT] @@ -84,7 +86,7 @@ def create_blebox_entities(hass, config_entry, async_add, entity_klass, entity_t for feature in product.features[entity_type]: entities.append(entity_klass(feature)) - async_add(entities, True) + async_add_entities(entities, True) class BleBoxEntity(Entity): diff --git a/homeassistant/components/blebox/air_quality.py b/homeassistant/components/blebox/air_quality.py index 656f19109ea..e7e9bac1f97 100644 --- a/homeassistant/components/blebox/air_quality.py +++ b/homeassistant/components/blebox/air_quality.py @@ -5,10 +5,10 @@ from homeassistant.components.air_quality import AirQualityEntity from . import BleBoxEntity, create_blebox_entities -async def async_setup_entry(hass, config_entry, async_add): +async def async_setup_entry(hass, config_entry, async_add_entities): """Set up a BleBox air quality entity.""" create_blebox_entities( - hass, config_entry, async_add, BleBoxAirQualityEntity, "air_qualities" + hass, config_entry, async_add_entities, BleBoxAirQualityEntity, "air_qualities" ) diff --git a/homeassistant/components/blebox/cover.py b/homeassistant/components/blebox/cover.py index 714b642e288..620adacf3f6 100644 --- a/homeassistant/components/blebox/cover.py +++ b/homeassistant/components/blebox/cover.py @@ -16,10 +16,12 @@ from . import BleBoxEntity, create_blebox_entities from .const import BLEBOX_TO_HASS_COVER_STATES, BLEBOX_TO_HASS_DEVICE_CLASSES -async def async_setup_entry(hass, config_entry, async_add): +async def async_setup_entry(hass, config_entry, async_add_entities): """Set up a BleBox entry.""" - create_blebox_entities(hass, config_entry, async_add, BleBoxCoverEntity, "covers") + create_blebox_entities( + hass, config_entry, async_add_entities, BleBoxCoverEntity, "covers" + ) class BleBoxCoverEntity(BleBoxEntity, CoverEntity): diff --git a/homeassistant/components/blebox/light.py b/homeassistant/components/blebox/light.py index 251a14f8fb8..a825d102717 100644 --- a/homeassistant/components/blebox/light.py +++ b/homeassistant/components/blebox/light.py @@ -24,10 +24,12 @@ from . import BleBoxEntity, create_blebox_entities _LOGGER = logging.getLogger(__name__) -async def async_setup_entry(hass, config_entry, async_add): +async def async_setup_entry(hass, config_entry, async_add_entities): """Set up a BleBox entry.""" - create_blebox_entities(hass, config_entry, async_add, BleBoxLightEntity, "lights") + create_blebox_entities( + hass, config_entry, async_add_entities, BleBoxLightEntity, "lights" + ) class BleBoxLightEntity(BleBoxEntity, LightEntity): @@ -69,9 +71,9 @@ class BleBoxLightEntity(BleBoxEntity, LightEntity): async def async_turn_on(self, **kwargs): """Turn the light on.""" - white = kwargs.get(ATTR_WHITE_VALUE, None) - hs_color = kwargs.get(ATTR_HS_COLOR, None) - brightness = kwargs.get(ATTR_BRIGHTNESS, None) + white = kwargs.get(ATTR_WHITE_VALUE) + hs_color = kwargs.get(ATTR_HS_COLOR) + brightness = kwargs.get(ATTR_BRIGHTNESS) feature = self._feature value = feature.sensible_on_value @@ -90,7 +92,7 @@ class BleBoxLightEntity(BleBoxEntity, LightEntity): await self._feature.async_on(value) except BadOnValueError as ex: _LOGGER.error( - "turning on '%s' failed: Bad value %s (%s)", self.name, value, ex + "Turning on '%s' failed: Bad value %s (%s)", self.name, value, ex ) async def async_turn_off(self, **kwargs): diff --git a/homeassistant/components/blebox/sensor.py b/homeassistant/components/blebox/sensor.py index 00ea4e82b9d..84f4c19371d 100644 --- a/homeassistant/components/blebox/sensor.py +++ b/homeassistant/components/blebox/sensor.py @@ -6,10 +6,12 @@ from . import BleBoxEntity, create_blebox_entities from .const import BLEBOX_TO_HASS_DEVICE_CLASSES, BLEBOX_TO_UNIT_MAP -async def async_setup_entry(hass, config_entry, async_add): +async def async_setup_entry(hass, config_entry, async_add_entities): """Set up a BleBox entry.""" - create_blebox_entities(hass, config_entry, async_add, BleBoxSensorEntity, "sensors") + create_blebox_entities( + hass, config_entry, async_add_entities, BleBoxSensorEntity, "sensors" + ) class BleBoxSensorEntity(BleBoxEntity, Entity): diff --git a/homeassistant/components/blebox/switch.py b/homeassistant/components/blebox/switch.py index 6cc05231929..e88773db639 100644 --- a/homeassistant/components/blebox/switch.py +++ b/homeassistant/components/blebox/switch.py @@ -5,10 +5,10 @@ from . import BleBoxEntity, create_blebox_entities from .const import BLEBOX_TO_HASS_DEVICE_CLASSES -async def async_setup_entry(hass, config_entry, async_add): +async def async_setup_entry(hass, config_entry, async_add_entities): """Set up a BleBox switch entity.""" create_blebox_entities( - hass, config_entry, async_add, BleBoxSwitchEntity, "switches" + hass, config_entry, async_add_entities, BleBoxSwitchEntity, "switches" ) diff --git a/tests/components/blebox/conftest.py b/tests/components/blebox/conftest.py index 569c16f813f..ece02d50b39 100644 --- a/tests/components/blebox/conftest.py +++ b/tests/components/blebox/conftest.py @@ -71,7 +71,7 @@ def config_fixture(): @pytest.fixture(name="feature") -def feature(request): +def feature_fixture(request): """Return an entity wrapper from given fixture name.""" return request.getfixturevalue(request.param) diff --git a/tests/components/blebox/test_config_flow.py b/tests/components/blebox/test_config_flow.py index fe13dfae15e..a7200b05b28 100644 --- a/tests/components/blebox/test_config_flow.py +++ b/tests/components/blebox/test_config_flow.py @@ -36,14 +36,14 @@ def create_valid_feature_mock(path="homeassistant.components.blebox.Products"): return feature -@pytest.fixture -def valid_feature_mock(): +@pytest.fixture(name="valid_feature_mock") +def valid_feature_mock_fixture(): """Return a valid, complete BleBox feature mock.""" return create_valid_feature_mock() -@pytest.fixture -def flow_feature_mock(): +@pytest.fixture(name="flow_feature_mock") +def flow_feature_mock_fixture(): """Return a mocked user flow feature.""" return create_valid_feature_mock( "homeassistant.components.blebox.config_flow.Products" @@ -74,8 +74,8 @@ async def test_flow_works(hass, valid_feature_mock, flow_feature_mock): } -@pytest.fixture -def product_class_mock(): +@pytest.fixture(name="product_class_mock") +def product_class_mock_fixture(): """Return a mocked feature.""" path = "homeassistant.components.blebox.config_flow.Products" patcher = patch(path, DEFAULT, blebox_uniapi.products.Products, True, True) diff --git a/tests/components/blebox/test_light.py b/tests/components/blebox/test_light.py index e1aa37777fd..fb0d1302e33 100644 --- a/tests/components/blebox/test_light.py +++ b/tests/components/blebox/test_light.py @@ -593,5 +593,5 @@ async def test_turn_on_failure(feature, hass, config, caplog): ) assert ( - f"turning on '{feature_mock.full_name}' failed: Bad value 123 ()" in caplog.text + f"Turning on '{feature_mock.full_name}' failed: Bad value 123 ()" in caplog.text ) From ef6b1f93022ce2340f494b051f67250fa11e735b Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Tue, 19 May 2020 00:04:18 +0000 Subject: [PATCH 088/406] [ci skip] Translation update --- .../components/acmeda/translations/es.json | 16 ++++++++++++++ .../components/acmeda/translations/ko.json | 16 ++++++++++++++ .../components/acmeda/translations/lb.json | 16 ++++++++++++++ .../acmeda/translations/zh-Hant.json | 16 ++++++++++++++ .../components/blink/translations/lb.json | 1 + .../components/daikin/translations/en.json | 2 +- .../components/daikin/translations/ru.json | 2 +- .../components/elgato/translations/es.json | 2 +- .../forked_daapd/translations/lb.json | 17 +++++++++++++- .../components/gogogate2/translations/ko.json | 22 +++++++++++++++++++ .../components/gogogate2/translations/lb.json | 13 +++++++++++ .../components/ipp/translations/lb.json | 3 ++- .../components/wiffi/translations/ko.json | 9 ++++++++ .../components/wiffi/translations/lb.json | 9 ++++++++ .../wiffi/translations/zh-Hant.json | 9 ++++++++ 15 files changed, 148 insertions(+), 5 deletions(-) create mode 100644 homeassistant/components/acmeda/translations/es.json create mode 100644 homeassistant/components/acmeda/translations/ko.json create mode 100644 homeassistant/components/acmeda/translations/lb.json create mode 100644 homeassistant/components/acmeda/translations/zh-Hant.json create mode 100644 homeassistant/components/gogogate2/translations/ko.json create mode 100644 homeassistant/components/gogogate2/translations/lb.json diff --git a/homeassistant/components/acmeda/translations/es.json b/homeassistant/components/acmeda/translations/es.json new file mode 100644 index 00000000000..0ca3dbf6e2f --- /dev/null +++ b/homeassistant/components/acmeda/translations/es.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "all_configured": "No se han descubierto nuevos hubs Pulse." + }, + "step": { + "user": { + "data": { + "id": "ID de host" + }, + "title": "Elige un hub para a\u00f1adir" + } + } + }, + "title": "Rollease Acmeda Automate" +} \ No newline at end of file diff --git a/homeassistant/components/acmeda/translations/ko.json b/homeassistant/components/acmeda/translations/ko.json new file mode 100644 index 00000000000..cc79dada5bd --- /dev/null +++ b/homeassistant/components/acmeda/translations/ko.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "all_configured": "\ubc1c\uacac\ub41c \uc0c8\ub85c\uc6b4 Pulse \ud5c8\ube0c\uac00 \uc5c6\uc2b5\ub2c8\ub2e4." + }, + "step": { + "user": { + "data": { + "id": "\ud638\uc2a4\ud2b8 ID" + }, + "title": "\ucd94\uac00\ud560 \ud5c8\ube0c \uc120\ud0dd\ud558\uae30" + } + } + }, + "title": "Rollease Acmeda Automate" +} \ No newline at end of file diff --git a/homeassistant/components/acmeda/translations/lb.json b/homeassistant/components/acmeda/translations/lb.json new file mode 100644 index 00000000000..27ae3072de5 --- /dev/null +++ b/homeassistant/components/acmeda/translations/lb.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "all_configured": "Keng nei Pulse Hubs entdeckt." + }, + "step": { + "user": { + "data": { + "id": "Host ID" + }, + "title": "Wiel den Hub aus dee soll dob\u00e4igesat ginn." + } + } + }, + "title": "Rollease ACmeda Automate" +} \ No newline at end of file diff --git a/homeassistant/components/acmeda/translations/zh-Hant.json b/homeassistant/components/acmeda/translations/zh-Hant.json new file mode 100644 index 00000000000..5d4263ea5ae --- /dev/null +++ b/homeassistant/components/acmeda/translations/zh-Hant.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "all_configured": "\u672a\u641c\u5c0b\u5230 Pulse hub" + }, + "step": { + "user": { + "data": { + "id": "\u4e3b\u6a5f ID" + }, + "title": "\u9078\u64c7\u6240\u8981\u65b0\u589e\u7684 Hub" + } + } + }, + "title": "Rollease Acmeda Automate" +} \ No newline at end of file diff --git a/homeassistant/components/blink/translations/lb.json b/homeassistant/components/blink/translations/lb.json index 3e1d5a6c46e..bca5231d380 100644 --- a/homeassistant/components/blink/translations/lb.json +++ b/homeassistant/components/blink/translations/lb.json @@ -5,6 +5,7 @@ "data": { "2fa": "2-Faktor Code" }, + "description": "G\u00ebff de PIn un dee per E-Mail versch\u00e9ckt gouf. Falls an der E-Mail kee PIN steht, einfach eidel loossen", "title": "2-Faktor-Authentifikatioun" }, "user": { diff --git a/homeassistant/components/daikin/translations/en.json b/homeassistant/components/daikin/translations/en.json index 30ba908e04f..573a4c5973e 100644 --- a/homeassistant/components/daikin/translations/en.json +++ b/homeassistant/components/daikin/translations/en.json @@ -17,7 +17,7 @@ "key": "API Key", "password": "Password" }, - "description": "Enter IP address of your Daikin AC.", + "description": "Enter IP address of your Daikin AC.\n\nNote that API Key and Password are used by BRP072Cxx and SKYFi devices respectively.", "title": "Configure Daikin AC" } } diff --git a/homeassistant/components/daikin/translations/ru.json b/homeassistant/components/daikin/translations/ru.json index 780945c0537..7aa72c15438 100644 --- a/homeassistant/components/daikin/translations/ru.json +++ b/homeassistant/components/daikin/translations/ru.json @@ -17,7 +17,7 @@ "key": "\u041a\u043b\u044e\u0447 API", "password": "\u041f\u0430\u0440\u043e\u043b\u044c" }, - "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 IP-\u0430\u0434\u0440\u0435\u0441 \u0412\u0430\u0448\u0435\u0433\u043e Daikin AC.", + "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 IP-\u0430\u0434\u0440\u0435\u0441 \u0432\u0430\u0448\u0435\u0433\u043e Daikin AC. \n\n\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435, \u0447\u0442\u043e \u041a\u043b\u044e\u0447 API \u0438 \u041f\u0430\u0440\u043e\u043b\u044c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442\u0441\u044f \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\u043c\u0438 BRP072Cxx \u0438 SKYFi \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u043e.", "title": "Daikin AC" } } diff --git a/homeassistant/components/elgato/translations/es.json b/homeassistant/components/elgato/translations/es.json index 3860a09de92..fd450d391fc 100644 --- a/homeassistant/components/elgato/translations/es.json +++ b/homeassistant/components/elgato/translations/es.json @@ -18,7 +18,7 @@ "title": "Conecte su Elgato Key Light" }, "zeroconf_confirm": { - "description": "\u00bfDesea agregar Elgato Key Light con el n\u00famero de serie `{serial_number}` a Home Assistant?", + "description": "\u00bfDesea a\u00f1adir Elgato Key Light con el n\u00famero de serie `{serial_number}` a Home Assistant?", "title": "Descubierto dispositivo Elgato Key Light" } } diff --git a/homeassistant/components/forked_daapd/translations/lb.json b/homeassistant/components/forked_daapd/translations/lb.json index 071ab70b90e..daa19290219 100644 --- a/homeassistant/components/forked_daapd/translations/lb.json +++ b/homeassistant/components/forked_daapd/translations/lb.json @@ -8,7 +8,8 @@ "unknown_error": "Onbekannten Feeler.", "websocket_not_enabled": "forked-daapd server websocket net aktiv.", "wrong_host_or_port": "Feeler beim verbannen, iwwerpr\u00e9if w.e.g d'Adresse a Port.", - "wrong_password": "Ong\u00ebltegt Passwuert." + "wrong_password": "Ong\u00ebltegt Passwuert.", + "wrong_server_type": "D'forked-daapd Integratioun ben\u00e9idegt een forked-daapd server mat Versioun >= 27.0." }, "flow_title": "forked-daapd server: {name} ({host})", "step": { @@ -22,5 +23,19 @@ "title": "forked-daapd Apparat ariichten" } } + }, + "options": { + "step": { + "init": { + "data": { + "librespot_java_port": "Port fir librespot-java pipe Kontroll (falls benotzt)", + "max_playlists": "Maximal Unzuel vun Playlists d\u00e9i als Quell benotzt ginn", + "tts_pause_time": "Pause an sekonnen vir an no dem TTS", + "tts_volume": "TTS Lautst\u00e4erkt (float an der range [0,1])" + }, + "description": "Verschidden Optioune fir forked-daapd Integratioun d\u00e9fin\u00e9ieren.", + "title": "Optioune fir forked-daapd konfigur\u00e9ieren" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/gogogate2/translations/ko.json b/homeassistant/components/gogogate2/translations/ko.json new file mode 100644 index 00000000000..55b32812bfa --- /dev/null +++ b/homeassistant/components/gogogate2/translations/ko.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" + }, + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "step": { + "user": { + "data": { + "ip_address": "IP \uc8fc\uc18c", + "password": "\ube44\ubc00\ubc88\ud638", + "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" + }, + "description": "\uc544\ub798\uc5d0 \ud544\uc218 \uc815\ubcf4\ub97c \uc81c\uacf5\ud574\uc8fc\uc138\uc694.", + "title": "GogoGate2 \uc124\uce58\ud558\uae30" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gogogate2/translations/lb.json b/homeassistant/components/gogogate2/translations/lb.json new file mode 100644 index 00000000000..b0d5d418ae9 --- /dev/null +++ b/homeassistant/components/gogogate2/translations/lb.json @@ -0,0 +1,13 @@ +{ + "config": { + "step": { + "user": { + "data": { + "ip_address": "IP Adresse" + }, + "description": "G\u00ebff noutwendeg Informatioun hei \u00ebnnen un.", + "title": "GogoGate 2 ariichten" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ipp/translations/lb.json b/homeassistant/components/ipp/translations/lb.json index 7a92b59a47a..5e5c9306061 100644 --- a/homeassistant/components/ipp/translations/lb.json +++ b/homeassistant/components/ipp/translations/lb.json @@ -6,7 +6,8 @@ "connection_upgrade": "Feeler beim verbannen mam Printer well eng Aktualis\u00e9ierung vun der Verbindung erfuerderlech ass.", "ipp_error": "IPP Feeler opgetrueden.", "ipp_version_error": "IPP Versioun net vum Printer \u00ebnnerst\u00ebtzt.", - "parse_error": "Feeler beim ausliesen vun der \u00c4ntwert vum Printer." + "parse_error": "Feeler beim ausliesen vun der \u00c4ntwert vum Printer.", + "unique_id_required": "Dem Apparat feelt eng eenzegarteg Identifikatioun d\u00e9i ben\u00e9idegt ass fir d'Entdeckung." }, "error": { "connection_error": "Feeler beim verbannen mam Printer.", diff --git a/homeassistant/components/wiffi/translations/ko.json b/homeassistant/components/wiffi/translations/ko.json index 4643a4a6000..c332d3e5f26 100644 --- a/homeassistant/components/wiffi/translations/ko.json +++ b/homeassistant/components/wiffi/translations/ko.json @@ -12,5 +12,14 @@ "title": "WIFFI \uae30\uae30\uc6a9 TCP \uc11c\ubc84 \uc124\uc815\ud558\uae30" } } + }, + "options": { + "step": { + "init": { + "data": { + "timeout": "\uc81c\ud55c \uc2dc\uac04 (\ubd84)" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/wiffi/translations/lb.json b/homeassistant/components/wiffi/translations/lb.json index 29937360f7b..f9080d52584 100644 --- a/homeassistant/components/wiffi/translations/lb.json +++ b/homeassistant/components/wiffi/translations/lb.json @@ -12,5 +12,14 @@ "title": "TCP Server fir WIFFI Apparater ariichten" } } + }, + "options": { + "step": { + "init": { + "data": { + "timeout": "Z\u00e4itiwwerscheidung (minutten)" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/wiffi/translations/zh-Hant.json b/homeassistant/components/wiffi/translations/zh-Hant.json index 0135fe86488..77b1488025c 100644 --- a/homeassistant/components/wiffi/translations/zh-Hant.json +++ b/homeassistant/components/wiffi/translations/zh-Hant.json @@ -12,5 +12,14 @@ "title": "\u8a2d\u5b9a WIFFI \u8a2d\u5099 TCP \u4f3a\u670d\u5668" } } + }, + "options": { + "step": { + "init": { + "data": { + "timeout": "\u903e\u6642\uff08\u5206\uff09" + } + } + } } } \ No newline at end of file From aeae4edb74256c575ed318919ed959b00f57d19f Mon Sep 17 00:00:00 2001 From: Jason Hunter Date: Mon, 18 May 2020 23:02:23 -0400 Subject: [PATCH 089/406] Fix ONVIF subscription renewal (#35792) * fix subscription renewal * catch ValueError for #35762 --- homeassistant/components/onvif/device.py | 16 ++++++++++++++-- homeassistant/components/onvif/event.py | 3 ++- homeassistant/components/onvif/parsers.py | 6 +++--- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/onvif/device.py b/homeassistant/components/onvif/device.py index c15f4bf6877..8e69e148da3 100644 --- a/homeassistant/components/onvif/device.py +++ b/homeassistant/components/onvif/device.py @@ -54,6 +54,8 @@ class ONVIFDevice: self.profiles: List[Profile] = [] self.max_resolution: int = 0 + self._dt_diff_seconds: int = 0 + @property def name(self) -> str: """Return the name of this device.""" @@ -100,6 +102,16 @@ class ONVIFDevice: if self.capabilities.ptz: self.device.create_ptz_service() + if self._dt_diff_seconds > 300 and self.capabilities.events: + self.capabilities.events = False + LOGGER.warning( + "The system clock on '%s' is more than 5 minutes off. " + "Although this device supports events, they will be " + "disabled until the device clock is fixed as we will " + "not be able to renew the subscription.", + self.name, + ) + if self.capabilities.events: self.events = EventManager( self.hass, self.device, self.config_entry.unique_id @@ -179,9 +191,9 @@ class ONVIFDevice: ) dt_diff = cam_date - system_date - dt_diff_seconds = dt_diff.total_seconds() + self._dt_diff_seconds = dt_diff.total_seconds() - if dt_diff_seconds > 5: + if self._dt_diff_seconds > 5: LOGGER.warning( "The date/time on the device (UTC) is '%s', " "which is different from the system '%s', " diff --git a/homeassistant/components/onvif/event.py b/homeassistant/components/onvif/event.py index ee7d4349dc4..888fe5bd92b 100644 --- a/homeassistant/components/onvif/event.py +++ b/homeassistant/components/onvif/event.py @@ -104,7 +104,8 @@ class EventManager: if not self._subscription: return - await self._subscription.Renew(dt_util.utcnow() + dt.timedelta(minutes=10)) + termination_time = (dt_util.utcnow() + dt.timedelta(minutes=30)).isoformat() + await self._subscription.Renew(termination_time) async def async_pull_messages(self, _now: dt = None) -> None: """Pull messages from device.""" diff --git a/homeassistant/components/onvif/parsers.py b/homeassistant/components/onvif/parsers.py index 4fd4ffd2891..438601106b5 100644 --- a/homeassistant/components/onvif/parsers.py +++ b/homeassistant/components/onvif/parsers.py @@ -308,7 +308,7 @@ async def async_parse_last_reboot(uid: str, msg) -> Event: dt_util.parse_datetime(msg.Message._value_1.Data.SimpleItem[0].Value) ), ) - except (AttributeError, KeyError): + except (AttributeError, KeyError, ValueError): return None @@ -331,7 +331,7 @@ async def async_parse_last_reset(uid: str, msg) -> Event: ), entity_enabled=False, ) - except (AttributeError, KeyError): + except (AttributeError, KeyError, ValueError): return None @@ -354,5 +354,5 @@ async def async_parse_last_clock_sync(uid: str, msg) -> Event: ), entity_enabled=False, ) - except (AttributeError, KeyError): + except (AttributeError, KeyError, ValueError): return None From ebed1de58167f6e5d89689d5d7a92729826cf1f6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 19 May 2020 00:52:38 -0500 Subject: [PATCH 090/406] Avoid creating multiple sqlalchemy sessions in a single history call (#35721) * Avoid a context switch in the history api The history api was creating a job to fetch the states and another job to convert the states to json. This can be done in a single job which decreases the overhead of the operation. * Ensure there is only one sqlalchemy session created per history query. Most queries created three sqlalchemy sessions which was especially slow with sqlite since it opens and closes the database. In testing the UI is noticeably faster at generating history graphs for entites. * Add additional coverage * pass hass first to _states_to_json and _get_significant_states --- homeassistant/components/history/__init__.py | 235 +++++++++++------- homeassistant/components/recorder/__init__.py | 39 ++- tests/components/history/test_init.py | 5 + 3 files changed, 172 insertions(+), 107 deletions(-) diff --git a/homeassistant/components/history/__init__.py b/homeassistant/components/history/__init__.py index 8d2f9baab81..7538764dcb8 100644 --- a/homeassistant/components/history/__init__.py +++ b/homeassistant/components/history/__init__.py @@ -43,8 +43,15 @@ SIGNIFICANT_DOMAINS = ("climate", "device_tracker", "thermostat", "water_heater" IGNORE_DOMAINS = ("zone", "scene") -def get_significant_states( +def get_significant_states(hass, *args, **kwargs): + """Wrap _get_significant_states with a sql session.""" + with session_scope(hass=hass) as session: + return _get_significant_states(hass, session, *args, **kwargs) + + +def _get_significant_states( hass, + session, start_time, end_time=None, entity_ids=None, @@ -61,38 +68,43 @@ def get_significant_states( """ timer_start = time.perf_counter() - with session_scope(hass=hass) as session: - if significant_changes_only: - query = session.query(States).filter( - ( - States.domain.in_(SIGNIFICANT_DOMAINS) - | (States.last_changed == States.last_updated) - ) - & (States.last_updated > start_time) + if significant_changes_only: + query = session.query(States).filter( + ( + States.domain.in_(SIGNIFICANT_DOMAINS) + | (States.last_changed == States.last_updated) ) - else: - query = session.query(States).filter(States.last_updated > start_time) - - if filters: - query = filters.apply(query, entity_ids) - - if end_time is not None: - query = query.filter(States.last_updated < end_time) - - query = query.order_by(States.last_updated) - - states = ( - state - for state in execute(query) - if (_is_significant(state) and not state.attributes.get(ATTR_HIDDEN, False)) + & (States.last_updated > start_time) ) + else: + query = session.query(States).filter(States.last_updated > start_time) + + if filters: + query = filters.apply(query, entity_ids) + + if end_time is not None: + query = query.filter(States.last_updated < end_time) + + query = query.order_by(States.last_updated) + + states = ( + state + for state in execute(query) + if (_is_significant(state) and not state.attributes.get(ATTR_HIDDEN, False)) + ) if _LOGGER.isEnabledFor(logging.DEBUG): elapsed = time.perf_counter() - timer_start _LOGGER.debug("get_significant_states took %fs", elapsed) - return states_to_json( - hass, states, start_time, entity_ids, filters, include_start_time_state + return _states_to_json( + hass, + session, + states, + start_time, + entity_ids, + filters, + include_start_time_state, ) @@ -115,7 +127,7 @@ def state_changes_during_period(hass, start_time, end_time=None, entity_id=None) states = execute(query.order_by(States.last_updated)) - return states_to_json(hass, states, start_time, entity_ids) + return _states_to_json(hass, session, states, start_time, entity_ids) def get_last_state_changes(hass, number_of_states, entity_id): @@ -135,91 +147,117 @@ def get_last_state_changes(hass, number_of_states, entity_id): query.order_by(States.last_updated.desc()).limit(number_of_states) ) - return states_to_json( - hass, reversed(states), start_time, entity_ids, include_start_time_state=False - ) + return _states_to_json( + hass, + session, + reversed(states), + start_time, + entity_ids, + include_start_time_state=False, + ) def get_states(hass, utc_point_in_time, entity_ids=None, run=None, filters=None): """Return the states at a specific point in time.""" if run is None: - run = recorder.run_information(hass, utc_point_in_time) + run = recorder.run_information_from_instance(hass, utc_point_in_time) # History did not run before utc_point_in_time if run is None: return [] with session_scope(hass=hass) as session: - query = session.query(States) + return _get_states_with_session( + session, utc_point_in_time, entity_ids, run, filters + ) - if entity_ids and len(entity_ids) == 1: - # Use an entirely different (and extremely fast) query if we only - # have a single entity id - query = ( - query.filter( - States.last_updated >= run.start, - States.last_updated < utc_point_in_time, - States.entity_id.in_(entity_ids), - ) - .order_by(States.last_updated.desc()) - .limit(1) + +def _get_states_with_session( + session, utc_point_in_time, entity_ids=None, run=None, filters=None +): + """Return the states at a specific point in time.""" + if run is None: + run = recorder.run_information_with_session(session, utc_point_in_time) + + # History did not run before utc_point_in_time + if run is None: + return [] + + query = session.query(States) + + if entity_ids and len(entity_ids) == 1: + # Use an entirely different (and extremely fast) query if we only + # have a single entity id + query = ( + query.filter( + States.last_updated >= run.start, + States.last_updated < utc_point_in_time, + States.entity_id.in_(entity_ids), ) + .order_by(States.last_updated.desc()) + .limit(1) + ) - else: - # We have more than one entity to look at (most commonly we want - # all entities,) so we need to do a search on all states since the - # last recorder run started. + else: + # We have more than one entity to look at (most commonly we want + # all entities,) so we need to do a search on all states since the + # last recorder run started. - most_recent_states_by_date = session.query( - States.entity_id.label("max_entity_id"), - func.max(States.last_updated).label("max_last_updated"), - ).filter( - (States.last_updated >= run.start) - & (States.last_updated < utc_point_in_time) - ) + most_recent_states_by_date = session.query( + States.entity_id.label("max_entity_id"), + func.max(States.last_updated).label("max_last_updated"), + ).filter( + (States.last_updated >= run.start) + & (States.last_updated < utc_point_in_time) + ) - if entity_ids: - most_recent_states_by_date.filter(States.entity_id.in_(entity_ids)) + if entity_ids: + most_recent_states_by_date.filter(States.entity_id.in_(entity_ids)) - most_recent_states_by_date = most_recent_states_by_date.group_by( - States.entity_id - ) + most_recent_states_by_date = most_recent_states_by_date.group_by( + States.entity_id + ) - most_recent_states_by_date = most_recent_states_by_date.subquery() + most_recent_states_by_date = most_recent_states_by_date.subquery() - most_recent_state_ids = session.query( - func.max(States.state_id).label("max_state_id") - ).join( - most_recent_states_by_date, - and_( - States.entity_id == most_recent_states_by_date.c.max_entity_id, - States.last_updated - == most_recent_states_by_date.c.max_last_updated, - ), - ) + most_recent_state_ids = session.query( + func.max(States.state_id).label("max_state_id") + ).join( + most_recent_states_by_date, + and_( + States.entity_id == most_recent_states_by_date.c.max_entity_id, + States.last_updated == most_recent_states_by_date.c.max_last_updated, + ), + ) - most_recent_state_ids = most_recent_state_ids.group_by(States.entity_id) + most_recent_state_ids = most_recent_state_ids.group_by(States.entity_id) - most_recent_state_ids = most_recent_state_ids.subquery() + most_recent_state_ids = most_recent_state_ids.subquery() - query = query.join( - most_recent_state_ids, - States.state_id == most_recent_state_ids.c.max_state_id, - ).filter(~States.domain.in_(IGNORE_DOMAINS)) + query = query.join( + most_recent_state_ids, + States.state_id == most_recent_state_ids.c.max_state_id, + ).filter(~States.domain.in_(IGNORE_DOMAINS)) - if filters: - query = filters.apply(query, entity_ids) + if filters: + query = filters.apply(query, entity_ids) - return [ - state - for state in execute(query) - if not state.attributes.get(ATTR_HIDDEN, False) - ] + return [ + state + for state in execute(query) + if not state.attributes.get(ATTR_HIDDEN, False) + ] -def states_to_json( - hass, states, start_time, entity_ids, filters=None, include_start_time_state=True +def _states_to_json( + hass, + session, + states, + start_time, + entity_ids, + filters=None, + include_start_time_state=True, ): """Convert SQL results into JSON friendly data structure. @@ -239,7 +277,10 @@ def states_to_json( # Get the states at the start time timer_start = time.perf_counter() if include_start_time_state: - for state in get_states(hass, start_time, entity_ids, filters=filters): + run = recorder.run_information_from_instance(hass, start_time) + for state in _get_states_with_session( + session, start_time, entity_ids, run=run, filters=filters + ): state.last_changed = start_time state.last_updated = start_time result[state.entity_id].append(state) @@ -298,6 +339,7 @@ class HistoryPeriodView(HomeAssistantView): async def get(self, request, datetime=None): """Return history over a period of time.""" + if datetime: datetime = dt_util.parse_datetime(datetime) @@ -356,15 +398,18 @@ class HistoryPeriodView(HomeAssistantView): """Fetch significant stats from the database as json.""" timer_start = time.perf_counter() - result = get_significant_states( - hass, - start_time, - end_time, - entity_ids, - self.filters, - include_start_time_state, - significant_changes_only, - ) + with session_scope(hass=hass) as session: + result = _get_significant_states( + hass, + session, + start_time, + end_time, + entity_ids, + self.filters, + include_start_time_state, + significant_changes_only, + ) + result = list(result.values()) if _LOGGER.isEnabledFor(logging.DEBUG): elapsed = time.perf_counter() - timer_start diff --git a/homeassistant/components/recorder/__init__.py b/homeassistant/components/recorder/__init__.py index fcccaa2fb9f..8cceedb3985 100644 --- a/homeassistant/components/recorder/__init__.py +++ b/homeassistant/components/recorder/__init__.py @@ -123,24 +123,39 @@ def run_information(hass, point_in_time: Optional[datetime] = None): There is also the run that covers point_in_time. """ + run_info = run_information_from_instance(hass, point_in_time) + if run_info: + return run_info + + with session_scope(hass=hass) as session: + return run_information_with_session(session, point_in_time) + + +def run_information_from_instance(hass, point_in_time: Optional[datetime] = None): + """Return information about current run from the existing instance. + + Does not query the database for older runs. + """ ins = hass.data[DATA_INSTANCE] - recorder_runs = RecorderRuns if point_in_time is None or point_in_time > ins.recording_start: return ins.run_info - with session_scope(hass=hass) as session: - res = ( - session.query(recorder_runs) - .filter( - (recorder_runs.start < point_in_time) - & (recorder_runs.end > point_in_time) - ) - .first() + +def run_information_with_session(session, point_in_time: Optional[datetime] = None): + """Return information about current run from the database.""" + recorder_runs = RecorderRuns + + res = ( + session.query(recorder_runs) + .filter( + (recorder_runs.start < point_in_time) & (recorder_runs.end > point_in_time) ) - if res: - session.expunge(res) - return res + .first() + ) + if res: + session.expunge(res) + return res async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: diff --git a/tests/components/history/test_init.py b/tests/components/history/test_init.py index 29e43c8428e..16af2c64271 100644 --- a/tests/components/history/test_init.py +++ b/tests/components/history/test_init.py @@ -103,6 +103,11 @@ class TestComponentHistory(unittest.TestCase): # Test get_state here because we have a DB setup assert states[0] == history.get_state(self.hass, future, states[0].entity_id) + time_before_recorder_ran = now - timedelta(days=1000) + assert history.get_states(self.hass, time_before_recorder_ran) == [] + + assert history.get_state(self.hass, time_before_recorder_ran, "demo.id") is None + def test_state_changes_during_period(self): """Test state change during period.""" self.init_recorder() From d6a5cb6083df5b2194eceff56059f397ceeb6886 Mon Sep 17 00:00:00 2001 From: Anders Melchiorsen Date: Tue, 19 May 2020 08:58:51 +0200 Subject: [PATCH 091/406] Upgrade pysonos to 0.0.30 (#35793) --- homeassistant/components/sonos/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/sonos/manifest.json b/homeassistant/components/sonos/manifest.json index d6bc7ff71a4..e5ce9ede290 100644 --- a/homeassistant/components/sonos/manifest.json +++ b/homeassistant/components/sonos/manifest.json @@ -3,7 +3,7 @@ "name": "Sonos", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/sonos", - "requirements": ["pysonos==0.0.29"], + "requirements": ["pysonos==0.0.30"], "ssdp": [ { "st": "urn:schemas-upnp-org:device:ZonePlayer:1" diff --git a/requirements_all.txt b/requirements_all.txt index 25b0b54997b..d475c2c00eb 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1615,7 +1615,7 @@ pysnmp==4.4.12 pysoma==0.0.10 # homeassistant.components.sonos -pysonos==0.0.29 +pysonos==0.0.30 # homeassistant.components.spc pyspcwebgw==0.4.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 76b515a789e..0e6421e8b9e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -687,7 +687,7 @@ pysmartthings==0.7.1 pysoma==0.0.10 # homeassistant.components.sonos -pysonos==0.0.29 +pysonos==0.0.30 # homeassistant.components.spc pyspcwebgw==0.4.0 From 188255bd8179e923a501c57d2ddc6dd06a42f045 Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Tue, 19 May 2020 06:50:05 -0400 Subject: [PATCH 092/406] Bump up ZHA dependencies. (#35797) --- 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 bf4176db120..ef96d6efef1 100644 --- a/homeassistant/components/zha/manifest.json +++ b/homeassistant/components/zha/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/zha", "requirements": [ - "bellows==0.16.1", + "bellows==0.16.2", "pyserial==3.4", "zha-quirks==0.0.39", "zigpy-cc==0.4.2", diff --git a/requirements_all.txt b/requirements_all.txt index d475c2c00eb..64c93948bb3 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -333,7 +333,7 @@ beautifulsoup4==4.9.0 beewi_smartclim==0.0.7 # homeassistant.components.zha -bellows==0.16.1 +bellows==0.16.2 # homeassistant.components.bmw_connected_drive bimmer_connected==0.7.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 0e6421e8b9e..45daa68f4f9 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -150,7 +150,7 @@ axis==26 base36==0.1.1 # homeassistant.components.zha -bellows==0.16.1 +bellows==0.16.2 # homeassistant.components.blebox blebox_uniapi==1.3.2 From 12ec47682110c54eefbf987c6573211f52d2e4b4 Mon Sep 17 00:00:00 2001 From: Ron Klinkien Date: Tue, 19 May 2020 13:40:31 +0200 Subject: [PATCH 093/406] Update to garminconnect 0.1.13 (#35750) * Update to garminconnect 0.1.13 This will fix body composition sensors being unavailable * Update requirements_all to garminconnect 0.1.13 * Update requirements_test_all.txt --- homeassistant/components/garmin_connect/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/garmin_connect/manifest.json b/homeassistant/components/garmin_connect/manifest.json index 09c916104df..7a57b1bf102 100644 --- a/homeassistant/components/garmin_connect/manifest.json +++ b/homeassistant/components/garmin_connect/manifest.json @@ -2,7 +2,7 @@ "domain": "garmin_connect", "name": "Garmin Connect", "documentation": "https://www.home-assistant.io/integrations/garmin_connect", - "requirements": ["garminconnect==0.1.10"], + "requirements": ["garminconnect==0.1.13"], "codeowners": ["@cyberjunky"], "config_flow": true } diff --git a/requirements_all.txt b/requirements_all.txt index 64c93948bb3..99f1d967eaf 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -604,7 +604,7 @@ fritzconnection==1.2.0 gTTS-token==1.1.3 # homeassistant.components.garmin_connect -garminconnect==0.1.10 +garminconnect==0.1.13 # homeassistant.components.gearbest gearbest_parser==1.0.7 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 45daa68f4f9..4d142255aab 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -251,7 +251,7 @@ foobot_async==0.3.1 gTTS-token==1.1.3 # homeassistant.components.garmin_connect -garminconnect==0.1.10 +garminconnect==0.1.13 # homeassistant.components.geo_json_events # homeassistant.components.usgs_earthquakes_feed From 2b38df27668b721ca8d7ca5bb9bd7992f8f9fcfc Mon Sep 17 00:00:00 2001 From: Felipe Martins Diel <41558831+felipediel@users.noreply.github.com> Date: Tue, 19 May 2020 08:46:12 -0300 Subject: [PATCH 094/406] Allow underscores in broadlink hostnames (#35791) * Allow underscores in hostnames * Ignore case --- homeassistant/components/broadlink/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/broadlink/__init__.py b/homeassistant/components/broadlink/__init__.py index 040b22945fd..a729ac0ff4a 100644 --- a/homeassistant/components/broadlink/__init__.py +++ b/homeassistant/components/broadlink/__init__.py @@ -31,12 +31,12 @@ def data_packet(value): def hostname(value): """Validate a hostname.""" - host = str(value).lower() + host = str(value) if len(host) > 253: raise ValueError if host[-1] == ".": host = host[:-1] - allowed = re.compile(r"(?!-)[a-z\d-]{1,63}(? Date: Tue, 19 May 2020 23:11:04 +1000 Subject: [PATCH 095/406] Fire events on homekit TV remote key press (#29588) * Fire events on homekit TV remote key press * Changes from code review * black * isort * flake8 * Update tests/components/homekit/test_type_media_players.py Co-authored-by: J. Nick Koston Co-authored-by: J. Nick Koston --- homeassistant/components/homekit/const.py | 17 +++++ .../components/homekit/type_media_players.py | 72 ++++++++++++------- .../homekit/test_type_media_players.py | 19 +++++ 3 files changed, 84 insertions(+), 24 deletions(-) diff --git a/homeassistant/components/homekit/const.py b/homeassistant/components/homekit/const.py index 3291fab7a30..8c431830589 100644 --- a/homeassistant/components/homekit/const.py +++ b/homeassistant/components/homekit/const.py @@ -27,6 +27,7 @@ ATTR_INTERGRATION = "platform" ATTR_MANUFACTURER = "manufacturer" ATTR_MODEL = "model" ATTR_SOFTWARE_VERSION = "sw_version" +ATTR_KEY_NAME = "key_name" # #### Config #### CONF_ADVERTISE_IP = "advertise_ip" @@ -79,6 +80,7 @@ FEATURE_TOGGLE_MUTE = "toggle_mute" # #### HomeKit Component Event #### EVENT_HOMEKIT_CHANGED = "homekit_state_change" +EVENT_HOMEKIT_TV_REMOTE_KEY_PRESSED = "homekit_tv_remote_key_pressed" # #### HomeKit Component Services #### SERVICE_HOMEKIT_START = "start" @@ -228,6 +230,21 @@ THRESHOLD_CO2 = 1000 DEFAULT_MIN_TEMP_WATER_HEATER = 40 # °C DEFAULT_MAX_TEMP_WATER_HEATER = 60 # °C +# #### Media Player Key Names #### +KEY_ARROW_DOWN = "arrow_down" +KEY_ARROW_LEFT = "arrow_left" +KEY_ARROW_RIGHT = "arrow_right" +KEY_ARROW_UP = "arrow_up" +KEY_BACK = "back" +KEY_EXIT = "exit" +KEY_FAST_FORWARD = "fast_forward" +KEY_INFORMATION = "information" +KEY_NEXT_TRACK = "next_track" +KEY_PREVIOUS_TRACK = "previous_track" +KEY_REWIND = "rewind" +KEY_SELECT = "select" +KEY_PLAY_PAUSE = "play_pause" + # #### Door states #### HK_DOOR_OPEN = 0 HK_DOOR_CLOSED = 1 diff --git a/homeassistant/components/homekit/type_media_players.py b/homeassistant/components/homekit/type_media_players.py index 4a104972b02..886c15a5fb9 100644 --- a/homeassistant/components/homekit/type_media_players.py +++ b/homeassistant/components/homekit/type_media_players.py @@ -40,6 +40,7 @@ from homeassistant.core import callback from .accessories import TYPES, HomeAccessory from .const import ( + ATTR_KEY_NAME, CHAR_ACTIVE, CHAR_ACTIVE_IDENTIFIER, CHAR_CONFIGURED_NAME, @@ -56,10 +57,24 @@ from .const import ( CHAR_VOLUME_CONTROL_TYPE, CHAR_VOLUME_SELECTOR, CONF_FEATURE_LIST, + EVENT_HOMEKIT_TV_REMOTE_KEY_PRESSED, FEATURE_ON_OFF, FEATURE_PLAY_PAUSE, FEATURE_PLAY_STOP, FEATURE_TOGGLE_MUTE, + KEY_ARROW_DOWN, + KEY_ARROW_LEFT, + KEY_ARROW_RIGHT, + KEY_ARROW_UP, + KEY_BACK, + KEY_EXIT, + KEY_FAST_FORWARD, + KEY_INFORMATION, + KEY_NEXT_TRACK, + KEY_PLAY_PAUSE, + KEY_PREVIOUS_TRACK, + KEY_REWIND, + KEY_SELECT, SERV_INPUT_SOURCE, SERV_SWITCH, SERV_TELEVISION, @@ -70,19 +85,19 @@ from .util import get_media_player_features _LOGGER = logging.getLogger(__name__) MEDIA_PLAYER_KEYS = { - # 0: "Rewind", - # 1: "FastForward", - # 2: "NextTrack", - # 3: "PreviousTrack", - # 4: "ArrowUp", - # 5: "ArrowDown", - # 6: "ArrowLeft", - # 7: "ArrowRight", - # 8: "Select", - # 9: "Back", - # 10: "Exit", - 11: SERVICE_MEDIA_PLAY_PAUSE, - # 15: "Information", + 0: KEY_REWIND, + 1: KEY_FAST_FORWARD, + 2: KEY_NEXT_TRACK, + 3: KEY_PREVIOUS_TRACK, + 4: KEY_ARROW_UP, + 5: KEY_ARROW_DOWN, + 6: KEY_ARROW_LEFT, + 7: KEY_ARROW_RIGHT, + 8: KEY_SELECT, + 9: KEY_BACK, + 10: KEY_EXIT, + 11: KEY_PLAY_PAUSE, + 15: KEY_INFORMATION, } # Names may not contain special characters @@ -363,19 +378,28 @@ class TelevisionMediaPlayer(HomeAccessory): def set_remote_key(self, value): """Send remote key value if call came from HomeKit.""" _LOGGER.debug("%s: Set remote key to %s", self.entity_id, value) - service = MEDIA_PLAYER_KEYS.get(value) - if service: - # Handle Play Pause - if service == SERVICE_MEDIA_PLAY_PAUSE: - state = self.hass.states.get(self.entity_id).state - if state in (STATE_PLAYING, STATE_PAUSED): - service = ( - SERVICE_MEDIA_PLAY - if state == STATE_PAUSED - else SERVICE_MEDIA_PAUSE - ) + key_name = MEDIA_PLAYER_KEYS.get(value) + if key_name is None: + _LOGGER.warning("%s: Unhandled key press for %s", self.entity_id, value) + return + + if key_name == KEY_PLAY_PAUSE: + # Handle Play Pause by directly updating the media player entity. + state = self.hass.states.get(self.entity_id).state + if state in (STATE_PLAYING, STATE_PAUSED): + service = ( + SERVICE_MEDIA_PLAY if state == STATE_PAUSED else SERVICE_MEDIA_PAUSE + ) + else: + service = SERVICE_MEDIA_PLAY_PAUSE params = {ATTR_ENTITY_ID: self.entity_id} self.call_service(DOMAIN, service, params) + else: + # Other keys can be handled by listening to the event bus + self.hass.bus.fire( + EVENT_HOMEKIT_TV_REMOTE_KEY_PRESSED, + {ATTR_KEY_NAME: key_name, ATTR_ENTITY_ID: self.entity_id}, + ) @callback def async_update_state(self, new_state): diff --git a/tests/components/homekit/test_type_media_players.py b/tests/components/homekit/test_type_media_players.py index bd533417121..e4842b93125 100644 --- a/tests/components/homekit/test_type_media_players.py +++ b/tests/components/homekit/test_type_media_players.py @@ -1,12 +1,15 @@ """Test different accessory types: Media Players.""" from homeassistant.components.homekit.const import ( + ATTR_KEY_NAME, ATTR_VALUE, CONF_FEATURE_LIST, + EVENT_HOMEKIT_TV_REMOTE_KEY_PRESSED, FEATURE_ON_OFF, FEATURE_PLAY_PAUSE, FEATURE_PLAY_STOP, FEATURE_TOGGLE_MUTE, + KEY_ARROW_RIGHT, ) from homeassistant.components.homekit.type_media_players import ( MediaPlayer, @@ -342,6 +345,22 @@ async def test_media_player_television(hass, hk_driver, events, caplog): assert len(events) == 11 assert events[-1].data[ATTR_VALUE] is None + events = [] + + def listener(event): + events.append(event) + + hass.bus.async_listen(EVENT_HOMEKIT_TV_REMOTE_KEY_PRESSED, listener) + + await hass.async_add_executor_job(acc.char_remote_key.client_update_value, 20) + await hass.async_block_till_done() + + await hass.async_add_executor_job(acc.char_remote_key.client_update_value, 7) + await hass.async_block_till_done() + + assert len(events) == 1 + assert events[0].data[ATTR_KEY_NAME] == KEY_ARROW_RIGHT + async def test_media_player_television_basic(hass, hk_driver, events, caplog): """Test if basic television accessory and HA are updated accordingly.""" From 8c8feb95a9c9048d655bc1eb263f6bc6ee61ee74 Mon Sep 17 00:00:00 2001 From: uvjustin <46082645+uvjustin@users.noreply.github.com> Date: Tue, 19 May 2020 21:17:02 +0800 Subject: [PATCH 096/406] Change version check in forked-daapd zeroconf step (#35796) --- .../components/forked_daapd/config_flow.py | 3 ++- .../forked_daapd/test_config_flow.py | 27 +++++++++++++++++-- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/forked_daapd/config_flow.py b/homeassistant/components/forked_daapd/config_flow.py index c860e08ffc4..07eaaf4c3fe 100644 --- a/homeassistant/components/forked_daapd/config_flow.py +++ b/homeassistant/components/forked_daapd/config_flow.py @@ -158,7 +158,8 @@ class ForkedDaapdFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Prepare configuration for a discovered forked-daapd device.""" if not ( discovery_info.get("properties") - and float(discovery_info["properties"].get("mtd-version", 0)) >= 27.0 + and int(discovery_info["properties"].get("mtd-version", "0").split(".")[0]) + >= 27 and discovery_info["properties"].get("Machine Name") ): return self.async_abort(reason="not_forked_daapd") diff --git a/tests/components/forked_daapd/test_config_flow.py b/tests/components/forked_daapd/test_config_flow.py index b97cc07009c..3dc62bae8bd 100644 --- a/tests/components/forked_daapd/test_config_flow.py +++ b/tests/components/forked_daapd/test_config_flow.py @@ -103,7 +103,7 @@ async def test_zeroconf_updates_title(hass, config_entry): discovery_info = { "host": "192.168.1.1", "port": 23, - "properties": {"mtd-version": 27.0, "Machine Name": "zeroconf_test"}, + "properties": {"mtd-version": "27.0", "Machine Name": "zeroconf_test"}, } result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_ZEROCONF}, data=discovery_info @@ -129,12 +129,35 @@ async def test_config_flow_no_websocket(hass, config_entry): async def test_config_flow_zeroconf_invalid(hass): """Test that an invalid zeroconf entry doesn't work.""" + # test with no discovery properties discovery_info = {"host": "127.0.0.1", "port": 23} result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_ZEROCONF}, data=discovery_info ) # doesn't create the entry, tries to show form but gets abort assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result["reason"] == "not_forked_daapd" + # test with forked-daapd version < 27 + discovery_info = { + "host": "127.0.0.1", + "port": 23, + "properties": {"mtd-version": "26.3", "Machine Name": "forked-daapd"}, + } + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_ZEROCONF}, data=discovery_info + ) # doesn't create the entry, tries to show form but gets abort + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "not_forked_daapd" + # test with verbose mtd-version from Firefly + discovery_info = { + "host": "127.0.0.1", + "port": 23, + "properties": {"mtd-version": "0.2.4.1", "Machine Name": "firefly"}, + } + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_ZEROCONF}, data=discovery_info + ) # doesn't create the entry, tries to show form but gets abort + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "not_forked_daapd" async def test_config_flow_zeroconf_valid(hass): @@ -143,7 +166,7 @@ async def test_config_flow_zeroconf_valid(hass): "host": "192.168.1.1", "port": 23, "properties": { - "mtd-version": 27.0, + "mtd-version": "27.0", "Machine Name": "zeroconf_test", "Machine ID": "5E55EEFF", }, From 2da718d7e803db9b1b1d3ff13177f3c2c2cd4014 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 19 May 2020 19:07:15 +0200 Subject: [PATCH 097/406] Updated frontend to 20200519.0 (#35813) --- 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 1c33802e655..563e71c2eec 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==20200518.0"], + "requirements": ["home-assistant-frontend==20200519.0"], "dependencies": [ "api", "auth", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index b9fa8969a4d..a58ee6b7cdf 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -12,7 +12,7 @@ cryptography==2.9.2 defusedxml==0.6.0 distro==1.5.0 hass-nabucasa==0.34.2 -home-assistant-frontend==20200518.0 +home-assistant-frontend==20200519.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 99f1d967eaf..e633cc63d15 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -734,7 +734,7 @@ hole==0.5.1 holidays==0.10.2 # homeassistant.components.frontend -home-assistant-frontend==20200518.0 +home-assistant-frontend==20200519.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 4d142255aab..3e3fc02661b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -318,7 +318,7 @@ hole==0.5.1 holidays==0.10.2 # homeassistant.components.frontend -home-assistant-frontend==20200518.0 +home-assistant-frontend==20200519.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From 8970fd8a5645f90cd9b7bdede035bf74ffe91799 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Tue, 19 May 2020 19:08:36 +0200 Subject: [PATCH 098/406] Bump codecov to 2.1.0 (#35808) * Bump codecov to 2.1.0 * Try to invalidate cache --- azure-pipelines-ci.yml | 2 +- requirements_test.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/azure-pipelines-ci.yml b/azure-pipelines-ci.yml index da60db941da..975899d3113 100644 --- a/azure-pipelines-ci.yml +++ b/azure-pipelines-ci.yml @@ -158,7 +158,7 @@ stages: steps: - template: templates/azp-step-cache.yaml@azure parameters: - keyfile: "requirements_test_all.txt | homeassistant/package_constraints.txt" + keyfile: "requirements_test_all.txt | requirements_test.txt | homeassistant/package_constraints.txt" build: | set -e python -m venv venv diff --git a/requirements_test.txt b/requirements_test.txt index 54d6f6b036d..9a8e7ab4510 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.22 +codecov==2.1.0 coverage==5.1 mock-open==1.4.0 mypy==0.770 From 6c4a6568f55def9f74e0d0957882c580e4d83dfa Mon Sep 17 00:00:00 2001 From: Odin Ugedal Date: Tue, 19 May 2020 19:13:27 +0200 Subject: [PATCH 099/406] Fix timezone issues for db fields in recorder (#35719) The database fields are timezoned via DateTime(timezone=True), so the default value should be timezoned too. When using cockroachdb this is fatal and results in the recorder crashing. --- homeassistant/components/recorder/models.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/recorder/models.py b/homeassistant/components/recorder/models.py index f3e80a9a739..8a6f25d57c3 100644 --- a/homeassistant/components/recorder/models.py +++ b/homeassistant/components/recorder/models.py @@ -1,5 +1,4 @@ """Models for SQLAlchemy.""" -from datetime import datetime import json import logging @@ -39,7 +38,7 @@ class Events(Base): # type: ignore event_data = Column(Text) origin = Column(String(32)) time_fired = Column(DateTime(timezone=True), index=True) - created = Column(DateTime(timezone=True), default=datetime.utcnow) + created = Column(DateTime(timezone=True), default=dt_util.utcnow) context_id = Column(String(36), index=True) context_user_id = Column(String(36), index=True) # context_parent_id = Column(String(36), index=True) @@ -84,9 +83,9 @@ class States(Base): # type: ignore state = Column(String(255)) attributes = Column(Text) event_id = Column(Integer, ForeignKey("events.event_id"), index=True) - last_changed = Column(DateTime(timezone=True), default=datetime.utcnow) - last_updated = Column(DateTime(timezone=True), default=datetime.utcnow, index=True) - created = Column(DateTime(timezone=True), default=datetime.utcnow) + last_changed = Column(DateTime(timezone=True), default=dt_util.utcnow) + last_updated = Column(DateTime(timezone=True), default=dt_util.utcnow, index=True) + created = Column(DateTime(timezone=True), default=dt_util.utcnow) context_id = Column(String(36), index=True) context_user_id = Column(String(36), index=True) # context_parent_id = Column(String(36), index=True) @@ -152,10 +151,10 @@ class RecorderRuns(Base): # type: ignore __tablename__ = "recorder_runs" run_id = Column(Integer, primary_key=True) - start = Column(DateTime(timezone=True), default=datetime.utcnow) + start = Column(DateTime(timezone=True), default=dt_util.utcnow) end = Column(DateTime(timezone=True)) closed_incorrect = Column(Boolean, default=False) - created = Column(DateTime(timezone=True), default=datetime.utcnow) + created = Column(DateTime(timezone=True), default=dt_util.utcnow) __table_args__ = (Index("ix_recorder_runs_start_end", "start", "end"),) @@ -191,7 +190,7 @@ class SchemaChanges(Base): # type: ignore __tablename__ = "schema_changes" change_id = Column(Integer, primary_key=True) schema_version = Column(Integer) - changed = Column(DateTime(timezone=True), default=datetime.utcnow) + changed = Column(DateTime(timezone=True), default=dt_util.utcnow) def _process_timestamp(ts): From 28db0cebf08e2a5f2445923a0ab3c89e180d866f Mon Sep 17 00:00:00 2001 From: Tom Harris Date: Tue, 19 May 2020 13:38:00 -0400 Subject: [PATCH 100/406] Fix Insteon cover and smoke detector entities (#35810) * Bump pyinsteon to 1.0.1 * Fix cover open/close calls * Add smokebridge sensors * trigger tests * trigger tests --- homeassistant/components/insteon/cover.py | 8 ++++++-- homeassistant/components/insteon/ipdb.py | 2 +- homeassistant/components/insteon/manifest.json | 2 +- requirements_all.txt | 2 +- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/insteon/cover.py b/homeassistant/components/insteon/cover.py index 75c336822d7..4f6fbd80dce 100644 --- a/homeassistant/components/insteon/cover.py +++ b/homeassistant/components/insteon/cover.py @@ -32,7 +32,11 @@ class InsteonCoverEntity(InsteonEntity, CoverEntity): @property def current_cover_position(self): """Return the current cover position.""" - return int(math.ceil(self._insteon_device_group.value * 100 / 255)) + if self._insteon_device_group.value is not None: + pos = self._insteon_device_group.value + else: + pos = 0 + return int(math.ceil(pos * 100 / 255)) @property def supported_features(self): @@ -59,5 +63,5 @@ class InsteonCoverEntity(InsteonEntity, CoverEntity): await self._insteon_device.async_close() else: await self._insteon_device.async_open( - position=position, group=self._insteon_device_group.group + open_level=position, group=self._insteon_device_group.group ) diff --git a/homeassistant/components/insteon/ipdb.py b/homeassistant/components/insteon/ipdb.py index 5d0913185b1..aa3c0932919 100644 --- a/homeassistant/components/insteon/ipdb.py +++ b/homeassistant/components/insteon/ipdb.py @@ -77,7 +77,7 @@ DEVICE_PLATFORM = { SecurityHealthSafety_LeakSensor: {BINARY_SENSOR: [2, 4]}, SecurityHealthSafety_MotionSensor: {BINARY_SENSOR: [1, 2, 3], ON_OFF_EVENTS: [1]}, SecurityHealthSafety_OpenCloseSensor: {BINARY_SENSOR: [1]}, - SecurityHealthSafety_Smokebridge: {BINARY_SENSOR: [1]}, + SecurityHealthSafety_Smokebridge: {BINARY_SENSOR: [1, 2, 3, 4, 6, 7]}, SensorsActuators_IOLink: {SWITCH: [1], BINARY_SENSOR: [2], ON_OFF_EVENTS: [1, 2]}, SwitchedLightingControl: {SWITCH: [1], ON_OFF_EVENTS: [1]}, SwitchedLightingControl_ApplianceLinc: {SWITCH: [1], ON_OFF_EVENTS: [1]}, diff --git a/homeassistant/components/insteon/manifest.json b/homeassistant/components/insteon/manifest.json index 87ad80047d8..f342cd02291 100644 --- a/homeassistant/components/insteon/manifest.json +++ b/homeassistant/components/insteon/manifest.json @@ -2,6 +2,6 @@ "domain": "insteon", "name": "Insteon", "documentation": "https://www.home-assistant.io/integrations/insteon", - "requirements": ["pyinsteon==1.0.0"], + "requirements": ["pyinsteon==1.0.1"], "codeowners": ["@teharris1"] } \ No newline at end of file diff --git a/requirements_all.txt b/requirements_all.txt index e633cc63d15..e5364311843 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1375,7 +1375,7 @@ pyialarm==0.3 pyicloud==0.9.7 # homeassistant.components.insteon -pyinsteon==1.0.0 +pyinsteon==1.0.1 # homeassistant.components.intesishome pyintesishome==1.7.4 From 648df6d984eae7952813406453d0eb3c0a9cd1b1 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Tue, 19 May 2020 23:57:41 +0200 Subject: [PATCH 101/406] UniFi - Fix disabled entities being enabled after a restart (#35819) * Async remove call removed too much, resulting in disabled entities coming back after a restart * Calling super().async_remove is no longer needed, changed to self.async_remove * Yes, they should be sets... --- .../components/unifi/device_tracker.py | 8 ++-- homeassistant/components/unifi/sensor.py | 2 +- homeassistant/components/unifi/switch.py | 4 +- .../components/unifi/unifi_entity_base.py | 38 +++++++++---------- 4 files changed, 25 insertions(+), 27 deletions(-) diff --git a/homeassistant/components/unifi/device_tracker.py b/homeassistant/components/unifi/device_tracker.py index 15c0ee7d4a7..ebad63acb4e 100644 --- a/homeassistant/components/unifi/device_tracker.py +++ b/homeassistant/components/unifi/device_tracker.py @@ -244,17 +244,17 @@ class UniFiClientTracker(UniFiClient, ScannerEntity): async def options_updated(self) -> None: """Config entry options are updated, remove entity if option is disabled.""" if not self.controller.option_track_clients: - await self.async_remove() + await self.remove_item({self.client.mac}) elif self.is_wired: if not self.controller.option_track_wired_clients: - await self.async_remove() + await self.remove_item({self.client.mac}) elif ( self.controller.option_ssid_filter and self.client.essid not in self.controller.option_ssid_filter ): - await self.async_remove() + await self.remove_item({self.client.mac}) class UniFiDeviceTracker(UniFiBase, ScannerEntity): @@ -383,4 +383,4 @@ class UniFiDeviceTracker(UniFiBase, ScannerEntity): async def options_updated(self) -> None: """Config entry options are updated, remove entity if option is disabled.""" if not self.controller.option_track_devices: - await self.async_remove() + await self.remove_item({self.device.mac}) diff --git a/homeassistant/components/unifi/sensor.py b/homeassistant/components/unifi/sensor.py index cf3cc2b1b5a..8fdb0ac1461 100644 --- a/homeassistant/components/unifi/sensor.py +++ b/homeassistant/components/unifi/sensor.py @@ -73,7 +73,7 @@ class UniFiBandwidthSensor(UniFiClient): async def options_updated(self) -> None: """Config entry options are updated, remove entity if option is disabled.""" if not self.controller.option_allow_bandwidth_sensors: - await self.async_remove() + await self.remove_item({self.client.mac}) class UniFiRxBandwidthSensor(UniFiBandwidthSensor): diff --git a/homeassistant/components/unifi/switch.py b/homeassistant/components/unifi/switch.py index a216daaf9bb..1b9f7147a9a 100644 --- a/homeassistant/components/unifi/switch.py +++ b/homeassistant/components/unifi/switch.py @@ -239,7 +239,7 @@ class UniFiPOEClientSwitch(UniFiClient, SwitchEntity, RestoreEntity): async def options_updated(self) -> None: """Config entry options are updated, remove entity if option is disabled.""" if not self.controller.option_poe_clients: - await self.async_remove() + await self.remove_item({self.client.mac}) class UniFiBlockClientSwitch(UniFiClient, SwitchEntity): @@ -287,4 +287,4 @@ class UniFiBlockClientSwitch(UniFiClient, SwitchEntity): async def options_updated(self) -> None: """Config entry options are updated, remove entity if option is disabled.""" if self.client.mac not in self.controller.option_block_clients: - await self.async_remove() + await self.remove_item({self.client.mac}) diff --git a/homeassistant/components/unifi/unifi_entity_base.py b/homeassistant/components/unifi/unifi_entity_base.py index 6fbc18f75d3..46a7123e4c9 100644 --- a/homeassistant/components/unifi/unifi_entity_base.py +++ b/homeassistant/components/unifi/unifi_entity_base.py @@ -43,18 +43,33 @@ class UniFiBase(Entity): self._item.remove_callback(self.async_update_callback) self.controller.entities[self.DOMAIN][self.TYPE].remove(self._item.mac) - async def async_remove(self): - """Clean up when removing entity. + @callback + def async_update_callback(self) -> None: + """Update the entity's state.""" + LOGGER.debug( + "Updating %s entity %s (%s)", self.TYPE, self.entity_id, self._item.mac + ) + self.async_write_ha_state() + + async def options_updated(self) -> None: + """Config entry options are updated, remove entity if option is disabled.""" + raise NotImplementedError + + async def remove_item(self, mac_addresses: set) -> None: + """Remove entity if MAC is part of set. Remove entity if no entry in entity registry exist. Remove entity registry entry if no entry in device registry exist. Remove device registry entry if there is only one linked entity (this entity). Remove entity registry entry if there are more than one entity linked to the device registry entry. """ + if self._item.mac not in mac_addresses: + return + entity_registry = await self.hass.helpers.entity_registry.async_get_registry() entity_entry = entity_registry.async_get(self.entity_id) if not entity_entry: - await super().async_remove() + await self.async_remove() return device_registry = await self.hass.helpers.device_registry.async_get_registry() @@ -69,23 +84,6 @@ class UniFiBase(Entity): entity_registry.async_remove(self.entity_id) - @callback - def async_update_callback(self) -> None: - """Update the entity's state.""" - LOGGER.debug( - "Updating %s entity %s (%s)", self.TYPE, self.entity_id, self._item.mac - ) - self.async_write_ha_state() - - async def options_updated(self) -> None: - """Config entry options are updated, remove entity if option is disabled.""" - raise NotImplementedError - - async def remove_item(self, mac_addresses: set) -> None: - """Remove entity if MAC is part of set.""" - if self._item.mac in mac_addresses: - await self.async_remove() - @property def should_poll(self) -> bool: """No polling needed.""" From 37f9b24a8567accdc349fab04f0606697c52809a Mon Sep 17 00:00:00 2001 From: Boris Kaplounovsky Date: Wed, 20 May 2020 01:23:18 +0300 Subject: [PATCH 102/406] Add xiaomi miio services remote_set_led_on/off (#35805) * Add services remote_set_led_on/remote_set_led_off to control changmi_ir led * pylint: elif => if * Fix services.async_register => platform.async_register_entity_service * Update homeassistant/components/xiaomi_miio/remote.py Co-authored-by: Martin Hjelmare * more fixes * async * more fixes * fix Co-authored-by: Martin Hjelmare --- homeassistant/components/xiaomi_miio/const.py | 2 + .../components/xiaomi_miio/remote.py | 56 +++++++++---------- .../components/xiaomi_miio/services.yaml | 14 +++++ 3 files changed, 44 insertions(+), 28 deletions(-) diff --git a/homeassistant/components/xiaomi_miio/const.py b/homeassistant/components/xiaomi_miio/const.py index aaeaf19c5f9..77f398aa3ad 100644 --- a/homeassistant/components/xiaomi_miio/const.py +++ b/homeassistant/components/xiaomi_miio/const.py @@ -34,6 +34,8 @@ SERVICE_EYECARE_MODE_OFF = "light_eyecare_mode_off" # Remote Services SERVICE_LEARN = "remote_learn_command" +SERVICE_SET_LED_ON = "remote_set_led_on" +SERVICE_SET_LED_OFF = "remote_set_led_off" # Switch Services SERVICE_SET_WIFI_LED_ON = "switch_set_wifi_led_on" diff --git a/homeassistant/components/xiaomi_miio/remote.py b/homeassistant/components/xiaomi_miio/remote.py index fb188368127..6fbf4b8a0f6 100644 --- a/homeassistant/components/xiaomi_miio/remote.py +++ b/homeassistant/components/xiaomi_miio/remote.py @@ -15,7 +15,6 @@ from homeassistant.components.remote import ( RemoteEntity, ) from homeassistant.const import ( - ATTR_ENTITY_ID, CONF_COMMAND, CONF_HOST, CONF_NAME, @@ -23,10 +22,10 @@ from homeassistant.const import ( CONF_TOKEN, ) from homeassistant.exceptions import PlatformNotReady -import homeassistant.helpers.config_validation as cv +from homeassistant.helpers import config_validation as cv, entity_platform from homeassistant.util.dt import utcnow -from .const import DOMAIN, SERVICE_LEARN +from .const import SERVICE_LEARN, SERVICE_SET_LED_OFF, SERVICE_SET_LED_ON _LOGGER = logging.getLogger(__name__) @@ -38,14 +37,6 @@ CONF_COMMANDS = "commands" DEFAULT_TIMEOUT = 10 DEFAULT_SLOT = 1 -LEARN_COMMAND_SCHEMA = vol.Schema( - { - vol.Required(ATTR_ENTITY_ID): vol.All(str), - vol.Optional(CONF_TIMEOUT, default=10): vol.All(int, vol.Range(min=0)), - vol.Optional(CONF_SLOT, default=1): vol.All(int, vol.Range(min=1, max=1000000)), - } -) - COMMAND_SCHEMA = vol.Schema( {vol.Required(CONF_COMMAND): vol.All(cv.ensure_list, [cv.string])} ) @@ -112,22 +103,16 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities([xiaomi_miio_remote]) - async def async_service_handler(service): + async def async_service_led_off_handler(entity, service): + """Handle set_led_off command.""" + await hass.async_add_executor_job(entity.device.set_indicator_led, False) + + async def async_service_led_on_handler(entity, service): + """Handle set_led_on command.""" + await hass.async_add_executor_job(entity.device.set_indicator_led, True) + + async def async_service_learn_handler(entity, service): """Handle a learn command.""" - if service.service != SERVICE_LEARN: - _LOGGER.error("We should not handle service: %s", service.service) - return - - entity_id = service.data.get(ATTR_ENTITY_ID) - entity = None - for remote in hass.data[DATA_KEY].values(): - if remote.entity_id == entity_id: - entity = remote - - if not entity: - _LOGGER.error("entity_id: '%s' not found", entity_id) - return - device = entity.device slot = service.data.get(CONF_SLOT, entity.slot) @@ -160,8 +145,23 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= "Timeout. No infrared command captured", title="Xiaomi Miio Remote" ) - hass.services.async_register( - DOMAIN, SERVICE_LEARN, async_service_handler, schema=LEARN_COMMAND_SCHEMA + platform = entity_platform.current_platform.get() + + platform.async_register_entity_service( + SERVICE_LEARN, + { + vol.Optional(CONF_TIMEOUT, default=10): vol.All(int, vol.Range(min=0)), + vol.Optional(CONF_SLOT, default=1): vol.All( + int, vol.Range(min=1, max=1000000) + ), + }, + async_service_learn_handler, + ) + platform.async_register_entity_service( + SERVICE_SET_LED_ON, {}, async_service_led_on_handler, + ) + platform.async_register_entity_service( + SERVICE_SET_LED_OFF, {}, async_service_led_off_handler, ) diff --git a/homeassistant/components/xiaomi_miio/services.yaml b/homeassistant/components/xiaomi_miio/services.yaml index a5308b08d6d..a92e46f11a1 100644 --- a/homeassistant/components/xiaomi_miio/services.yaml +++ b/homeassistant/components/xiaomi_miio/services.yaml @@ -224,6 +224,20 @@ remote_learn_command: description: "Define the timeout in seconds, before which the command must be learned." example: "30" +remote_set_led_on: + description: 'Turn on blue LED.' + fields: + entity_id: + description: "Name of the entity to turn LED on." + example: "remote.xiaomi_miio" + +remote_set_led_off: + description: 'Turn off blue LED.' + fields: + entity_id: + description: "Name of the entity to turn LED off." + example: "remote.xiaomi_miio" + switch_set_wifi_led_on: description: Turn the wifi led on. fields: From c10aa13d0b88e1b8fd669ec05232ae549d1e1efe Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Wed, 20 May 2020 00:03:49 +0000 Subject: [PATCH 103/406] [ci skip] Translation update --- .../components/acmeda/translations/no.json | 16 ++++++++++++++++ .../components/acmeda/translations/pl.json | 16 ++++++++++++++++ .../airvisual/translations/zh-Hant.json | 2 +- .../components/arcam_fmj/translations/pl.json | 5 +++++ .../components/atag/translations/pl.json | 2 +- .../components/axis/translations/fi.json | 7 +++++++ .../components/cert_expiry/translations/pl.json | 4 ++-- .../components/daikin/translations/ko.json | 2 +- .../components/daikin/translations/zh-Hant.json | 2 +- .../components/deconz/translations/pl.json | 6 +++--- .../components/doorbird/translations/pl.json | 4 ++-- .../components/ebusd/translations/fi.json | 6 ++++++ .../emulated_roku/translations/fi.json | 4 +++- .../components/esphome/translations/fi.json | 10 ++++++++++ .../flick_electric/translations/pl.json | 9 +++++++-- .../components/flume/translations/pl.json | 2 +- .../components/freebox/translations/pl.json | 2 +- .../components/gogogate2/translations/no.json | 13 +++++++++++++ .../components/gogogate2/translations/pl.json | 4 +++- .../homematicip_cloud/translations/pl.json | 2 +- .../components/hue/translations/pl.json | 14 +++++++------- .../components/iaqualink/translations/pl.json | 2 +- .../components/ifttt/translations/fi.json | 5 +++++ .../components/ipma/translations/fi.json | 3 ++- .../components/ipp/translations/no.json | 3 ++- .../components/ipp/translations/pl.json | 3 ++- .../components/isy994/translations/pl.json | 6 +++++- .../components/konnected/translations/pl.json | 2 +- .../components/logi_circle/translations/fi.json | 6 +++++- .../components/meteo_france/translations/pl.json | 2 +- .../components/monoprice/translations/pl.json | 2 +- .../components/nest/translations/pl.json | 2 +- .../components/nuheat/translations/pl.json | 2 +- .../components/nut/translations/pl.json | 2 +- .../components/nws/translations/pl.json | 2 +- .../components/openuv/translations/zh-Hant.json | 2 +- .../panasonic_viera/translations/pl.json | 2 +- .../components/plex/translations/pl.json | 2 +- .../components/roomba/translations/pl.json | 2 +- .../components/smhi/translations/fi.json | 3 +++ .../components/songpal/translations/pl.json | 2 +- .../components/tado/translations/pl.json | 2 +- .../components/tellduslive/translations/fi.json | 9 +++++++++ .../components/tradfri/translations/fi.json | 3 +++ .../components/unifi/translations/fi.json | 7 ++++++- .../components/upnp/translations/pl.json | 4 +++- .../components/wemo/translations/fi.json | 9 +++++++++ .../components/wiffi/translations/no.json | 9 +++++++++ .../components/wiffi/translations/pl.json | 14 ++++++++++++++ .../components/wled/translations/pl.json | 2 +- 50 files changed, 199 insertions(+), 47 deletions(-) create mode 100644 homeassistant/components/acmeda/translations/no.json create mode 100644 homeassistant/components/acmeda/translations/pl.json create mode 100644 homeassistant/components/ebusd/translations/fi.json create mode 100644 homeassistant/components/gogogate2/translations/no.json create mode 100644 homeassistant/components/tellduslive/translations/fi.json create mode 100644 homeassistant/components/wemo/translations/fi.json diff --git a/homeassistant/components/acmeda/translations/no.json b/homeassistant/components/acmeda/translations/no.json new file mode 100644 index 00000000000..79f618f2b99 --- /dev/null +++ b/homeassistant/components/acmeda/translations/no.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "all_configured": "Ingen nye Pulse-hub oppdaget." + }, + "step": { + "user": { + "data": { + "id": "Verts-ID" + }, + "title": "Velg en hub du vil legge til" + } + } + }, + "title": "Rollease Acmeda Automate" +} \ No newline at end of file diff --git a/homeassistant/components/acmeda/translations/pl.json b/homeassistant/components/acmeda/translations/pl.json new file mode 100644 index 00000000000..4732d44580f --- /dev/null +++ b/homeassistant/components/acmeda/translations/pl.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "all_configured": "Nie wykryto hub\u00f3w Pulse." + }, + "step": { + "user": { + "data": { + "id": "ID hosta" + }, + "title": "Wybierz hub, kt\u00f3ry chcesz doda\u0107" + } + } + }, + "title": "Rollease Acmeda Automate" +} \ No newline at end of file diff --git a/homeassistant/components/airvisual/translations/zh-Hant.json b/homeassistant/components/airvisual/translations/zh-Hant.json index 36dd078d7b5..19a1a42fc7a 100644 --- a/homeassistant/components/airvisual/translations/zh-Hant.json +++ b/homeassistant/components/airvisual/translations/zh-Hant.json @@ -5,7 +5,7 @@ }, "error": { "general_error": "\u767c\u751f\u672a\u77e5\u932f\u8aa4\u3002", - "invalid_api_key": "API \u5bc6\u78bc\u7121\u6548\u3002", + "invalid_api_key": "API \u5bc6\u9470\u7121\u6548\u3002", "unable_to_connect": "\u7121\u6cd5\u9023\u7dda\u81f3 Node/Pro \u8a2d\u5099\u3002" }, "step": { diff --git a/homeassistant/components/arcam_fmj/translations/pl.json b/homeassistant/components/arcam_fmj/translations/pl.json index b78b8cbaa7b..2171454a4c6 100644 --- a/homeassistant/components/arcam_fmj/translations/pl.json +++ b/homeassistant/components/arcam_fmj/translations/pl.json @@ -1,3 +1,8 @@ { + "device_automation": { + "trigger_type": { + "turn_on": "{entity_name} zostanie poproszony o w\u0142\u0105czenie" + } + }, "title": "Arcam FMJ" } \ No newline at end of file diff --git a/homeassistant/components/atag/translations/pl.json b/homeassistant/components/atag/translations/pl.json index cc65fb9c595..971ff7221b9 100644 --- a/homeassistant/components/atag/translations/pl.json +++ b/homeassistant/components/atag/translations/pl.json @@ -13,7 +13,7 @@ "host": "Nazwa hosta lub adres IP", "port": "Port" }, - "title": "Po\u0142\u0105cz z urz\u0105dzeniem" + "title": "Po\u0142\u0105czenie z urz\u0105dzeniem" } } }, diff --git a/homeassistant/components/axis/translations/fi.json b/homeassistant/components/axis/translations/fi.json index b81b9858cd0..c3a755b16a2 100644 --- a/homeassistant/components/axis/translations/fi.json +++ b/homeassistant/components/axis/translations/fi.json @@ -1,3 +1,10 @@ { + "config": { + "step": { + "user": { + "title": "Asenna Axis-laite" + } + } + }, "title": "Axis-laite" } \ No newline at end of file diff --git a/homeassistant/components/cert_expiry/translations/pl.json b/homeassistant/components/cert_expiry/translations/pl.json index 1d2e1b3405f..7f92253507c 100644 --- a/homeassistant/components/cert_expiry/translations/pl.json +++ b/homeassistant/components/cert_expiry/translations/pl.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "already_configured": "Ta kombinacja hosta i portu jest ju\u017c skonfigurowana", - "import_failed": "Import z konfiguracji nie powi\u00f3d\u0142 si\u0119" + "already_configured": "Ta kombinacja hosta i portu jest ju\u017c skonfigurowana.", + "import_failed": "Import z konfiguracji nie powi\u00f3d\u0142 si\u0119." }, "error": { "connection_refused": "Po\u0142\u0105czenie odrzucone podczas \u0142\u0105czenia z hostem", diff --git a/homeassistant/components/daikin/translations/ko.json b/homeassistant/components/daikin/translations/ko.json index 2ce657c8e06..a1b2dd6ee4a 100644 --- a/homeassistant/components/daikin/translations/ko.json +++ b/homeassistant/components/daikin/translations/ko.json @@ -17,7 +17,7 @@ "key": "API \ud0a4", "password": "\ube44\ubc00\ubc88\ud638" }, - "description": "\ub2e4\uc774\ud0a8 \uc5d0\uc5b4\ucee8\uc758 IP \uc8fc\uc18c\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694.", + "description": "\ub2e4\uc774\ud0a8 \uc5d0\uc5b4\ucee8\uc758 IP \uc8fc\uc18c\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694.\n\nAPI \ud0a4 \ubc0f \ube44\ubc00\ubc88\ud638\ub294 BRP072Cxx \uc640 SKYFi \uae30\uae30\uc5d0\uc11c \uc0ac\uc6a9\ub41c\ub2e4\ub294 \uc810\uc5d0 \uc720\uc758\ud558\uc138\uc694.", "title": "\ub2e4\uc774\ud0a8 \uc5d0\uc5b4\ucee8 \uad6c\uc131\ud558\uae30" } } diff --git a/homeassistant/components/daikin/translations/zh-Hant.json b/homeassistant/components/daikin/translations/zh-Hant.json index c5f367684d5..9bffbc2b728 100644 --- a/homeassistant/components/daikin/translations/zh-Hant.json +++ b/homeassistant/components/daikin/translations/zh-Hant.json @@ -17,7 +17,7 @@ "key": "API \u5bc6\u9470", "password": "\u5bc6\u78bc" }, - "description": "\u8f38\u5165\u60a8\u7684\u5927\u91d1\u7a7a\u8abf IP \u4f4d\u5740\u3002", + "description": "\u8f38\u5165\u60a8\u7684\u5927\u91d1\u7a7a\u8abf IP \u4f4d\u5740\u3002\n\n\u8acb\u6ce8\u610f\uff1aBRP072Cxx \u8207 SKYFi \u8a2d\u5099\u4e4b API \u5bc6\u9470\u8207\u5bc6\u78bc\u70ba\u5206\u958b\u4f7f\u7528\u3002", "title": "\u8a2d\u5b9a\u5927\u91d1\u7a7a\u8abf" } } diff --git a/homeassistant/components/deconz/translations/pl.json b/homeassistant/components/deconz/translations/pl.json index a9bff098644..2edcb2a2611 100644 --- a/homeassistant/components/deconz/translations/pl.json +++ b/homeassistant/components/deconz/translations/pl.json @@ -22,7 +22,7 @@ }, "link": { "description": "Odblokuj bramk\u0119 deCONZ, aby zarejestrowa\u0107 j\u0105 w Home Assistant. \n\n 1. Przejd\u017a do ustawienia deCONZ > bramka > Zaawansowane\n 2. Naci\u015bnij przycisk \"Uwierzytelnij aplikacj\u0119\"", - "title": "Po\u0142\u0105cz z deCONZ" + "title": "Po\u0142\u0105czenie z deCONZ" }, "manual_confirm": { "data": { @@ -48,7 +48,7 @@ "device_automation": { "trigger_subtype": { "both_buttons": "oba przyciski", - "bottom_buttons": "Dolne przyciski", + "bottom_buttons": "dolne przyciski", "button_1": "pierwszy przycisk", "button_2": "drugi przycisk", "button_3": "trzeci przycisk", @@ -65,7 +65,7 @@ "side_4": "strona 4", "side_5": "strona 5", "side_6": "strona 6", - "top_buttons": "G\u00f3rne przyciski", + "top_buttons": "g\u00f3rne przyciski", "turn_off": "nast\u0105pi wy\u0142\u0105czenie", "turn_on": "nast\u0105pi w\u0142\u0105czenie" }, diff --git a/homeassistant/components/doorbird/translations/pl.json b/homeassistant/components/doorbird/translations/pl.json index 485388d5ce4..a24febcd94a 100644 --- a/homeassistant/components/doorbird/translations/pl.json +++ b/homeassistant/components/doorbird/translations/pl.json @@ -2,8 +2,8 @@ "config": { "abort": { "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" + "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.", diff --git a/homeassistant/components/ebusd/translations/fi.json b/homeassistant/components/ebusd/translations/fi.json new file mode 100644 index 00000000000..1d4f3f71a47 --- /dev/null +++ b/homeassistant/components/ebusd/translations/fi.json @@ -0,0 +1,6 @@ +{ + "state": { + "day": "P\u00e4iv\u00e4", + "night": "Y\u00f6" + } +} \ No newline at end of file diff --git a/homeassistant/components/emulated_roku/translations/fi.json b/homeassistant/components/emulated_roku/translations/fi.json index bad7fe71985..c3ad7765899 100644 --- a/homeassistant/components/emulated_roku/translations/fi.json +++ b/homeassistant/components/emulated_roku/translations/fi.json @@ -6,7 +6,9 @@ "step": { "user": { "data": { - "advertise_ip": "Mainosta IP-osoitetta" + "advertise_ip": "Mainosta IP-osoitetta", + "host_ip": "Palvelimen IP-osoite", + "listen_port": "Kuuntele porttia" }, "title": "M\u00e4\u00e4rit\u00e4 palvelimen asetukset" } diff --git a/homeassistant/components/esphome/translations/fi.json b/homeassistant/components/esphome/translations/fi.json index 3be6a5a5142..367df7e1704 100644 --- a/homeassistant/components/esphome/translations/fi.json +++ b/homeassistant/components/esphome/translations/fi.json @@ -3,9 +3,19 @@ "abort": { "already_configured": "ESP on jo m\u00e4\u00e4ritetty" }, + "error": { + "invalid_password": "Virheellinen salasana!" + }, "flow_title": "ESPHome: {name}", "step": { + "authenticate": { + "title": "Sy\u00f6t\u00e4 salasana" + }, "user": { + "data": { + "host": "Palvelin", + "port": "Portti" + }, "title": "[%key:component::esphome::title%]" } } diff --git a/homeassistant/components/flick_electric/translations/pl.json b/homeassistant/components/flick_electric/translations/pl.json index 716af7ba96d..20e2aa21902 100644 --- a/homeassistant/components/flick_electric/translations/pl.json +++ b/homeassistant/components/flick_electric/translations/pl.json @@ -4,16 +4,21 @@ "already_configured": "Konto jest ju\u017c skonfigurowane." }, "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia.", "invalid_auth": "Niepoprawne uwierzytelnienie.", "unknown": "Nieoczekiwany b\u0142\u0105d." }, "step": { "user": { "data": { + "client_id": "Identyfikator klienta (opcjonalnie)", + "client_secret": "Tajny klucz klienta (opcjonalnie)", "password": "Has\u0142o", "username": "Nazwa u\u017cytkownika" - } + }, + "title": "Logowanie do Flick" } } - } + }, + "title": "Flick Electric" } \ No newline at end of file diff --git a/homeassistant/components/flume/translations/pl.json b/homeassistant/components/flume/translations/pl.json index 6fe75f62ef0..ff1d73fe0ce 100644 --- a/homeassistant/components/flume/translations/pl.json +++ b/homeassistant/components/flume/translations/pl.json @@ -17,7 +17,7 @@ "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" + "title": "Po\u0142\u0105czenie z kontem Flume" } } } diff --git a/homeassistant/components/freebox/translations/pl.json b/homeassistant/components/freebox/translations/pl.json index 64564886882..2cb9a840544 100644 --- a/homeassistant/components/freebox/translations/pl.json +++ b/homeassistant/components/freebox/translations/pl.json @@ -11,7 +11,7 @@ "step": { "link": { "description": "Kliknij \"Zatwierd\u017a\", a nast\u0119pnie naci\u015bnij przycisk strza\u0142ki w prawo na routerze, aby zarejestrowa\u0107 Freebox w Home Assistan'cie. \n\n ![Lokalizacja przycisku na routerze] (/static/images/config_freebox.png)", - "title": "Po\u0142\u0105cz z routerem Freebox" + "title": "Po\u0142\u0105czenie z routerem Freebox" }, "user": { "data": { diff --git a/homeassistant/components/gogogate2/translations/no.json b/homeassistant/components/gogogate2/translations/no.json new file mode 100644 index 00000000000..1f67db7bfd2 --- /dev/null +++ b/homeassistant/components/gogogate2/translations/no.json @@ -0,0 +1,13 @@ +{ + "config": { + "step": { + "user": { + "data": { + "ip_address": "IP adresse" + }, + "description": "Oppgi n\u00f8dvendig informasjon nedenfor.", + "title": "Konfigurer GogoGate2" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gogogate2/translations/pl.json b/homeassistant/components/gogogate2/translations/pl.json index acc11bb1d01..84a683b4dbd 100644 --- a/homeassistant/components/gogogate2/translations/pl.json +++ b/homeassistant/components/gogogate2/translations/pl.json @@ -13,7 +13,9 @@ "ip_address": "Adres IP", "password": "Has\u0142o", "username": "Nazwa u\u017cytkownika" - } + }, + "description": "Wprowad\u017a wymagane informacje poni\u017cej.", + "title": "Konfiguracja GogoGate2" } } } diff --git a/homeassistant/components/homematicip_cloud/translations/pl.json b/homeassistant/components/homematicip_cloud/translations/pl.json index 33049a0a0a9..d51af1e32ea 100644 --- a/homeassistant/components/homematicip_cloud/translations/pl.json +++ b/homeassistant/components/homematicip_cloud/translations/pl.json @@ -22,7 +22,7 @@ }, "link": { "description": "Naci\u015bnij niebieski przycisk na punkcie dost\u0119pu i przycisk przesy\u0142ania, aby zarejestrowa\u0107 HomematicIP w Home Assistant. \n\n![Location of button on bridge](/static/images/config_flows/config_homematicip_cloud.png)", - "title": "Po\u0142\u0105cz z punktem dost\u0119pu" + "title": "Po\u0142\u0105czenie z punktem dost\u0119pu" } } } diff --git a/homeassistant/components/hue/translations/pl.json b/homeassistant/components/hue/translations/pl.json index c38f66ac005..46df7ce48e8 100644 --- a/homeassistant/components/hue/translations/pl.json +++ b/homeassistant/components/hue/translations/pl.json @@ -6,7 +6,7 @@ "already_in_progress": "Konfiguracja mostka jest ju\u017c w toku.", "cannot_connect": "Nie mo\u017cna po\u0142\u0105czy\u0107 si\u0119 z mostkiem", "discover_timeout": "Nie mo\u017cna wykry\u0107 \u017cadnych mostk\u00f3w Hue", - "no_bridges": "Nie wykryto \u017cadnych mostk\u00f3w Hue", + "no_bridges": "Nie wykryto mostk\u00f3w Hue.", "not_hue_bridge": "To nie jest mostek Hue", "unknown": "Nieoczekiwany b\u0142\u0105d." }, @@ -35,17 +35,17 @@ "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" + "double_buttons_1_3": "przyciski pierwszy i trzeci", + "double_buttons_2_4": "przyciski drugi i czwarty", + "turn_off": "wy\u0142\u0105cznik", + "turn_on": "w\u0142\u0105cznik" }, "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" + "remote_double_button_long_press": "oba \"{subtype}\" zostan\u0105 zwolnione po d\u0142ugim naci\u015bni\u0119ciu", + "remote_double_button_short_press": "oba \"{subtype}\" zostan\u0105 zwolnione" } } } \ No newline at end of file diff --git a/homeassistant/components/iaqualink/translations/pl.json b/homeassistant/components/iaqualink/translations/pl.json index e31922a404d..f0582b633f1 100644 --- a/homeassistant/components/iaqualink/translations/pl.json +++ b/homeassistant/components/iaqualink/translations/pl.json @@ -13,7 +13,7 @@ "username": "Nazwa u\u017cytkownika" }, "description": "Wprowad\u017a nazw\u0119 u\u017cytkownika i has\u0142o do konta iAqualink.", - "title": "Po\u0142\u0105cz z iAqualink" + "title": "Po\u0142\u0105czenie z iAqualink" } } } diff --git a/homeassistant/components/ifttt/translations/fi.json b/homeassistant/components/ifttt/translations/fi.json index 0570e4a1a66..b01accbb371 100644 --- a/homeassistant/components/ifttt/translations/fi.json +++ b/homeassistant/components/ifttt/translations/fi.json @@ -2,6 +2,11 @@ "config": { "abort": { "one_instance_allowed": "Vain yksi instanssi on tarpeen." + }, + "step": { + "user": { + "description": "Haluatko varmasti m\u00e4\u00e4ritt\u00e4\u00e4 IFTTT:n?" + } } } } \ No newline at end of file diff --git a/homeassistant/components/ipma/translations/fi.json b/homeassistant/components/ipma/translations/fi.json index 00ae9b3df2f..b0c5fcc15cd 100644 --- a/homeassistant/components/ipma/translations/fi.json +++ b/homeassistant/components/ipma/translations/fi.json @@ -9,7 +9,8 @@ "latitude": "Leveysaste", "longitude": "Pituusaste", "name": "Nimi" - } + }, + "title": "Sijainti" } } } diff --git a/homeassistant/components/ipp/translations/no.json b/homeassistant/components/ipp/translations/no.json index afc03cf90b2..59565eb0053 100644 --- a/homeassistant/components/ipp/translations/no.json +++ b/homeassistant/components/ipp/translations/no.json @@ -6,7 +6,8 @@ "connection_upgrade": "Kunne ikke koble til skriveren fordi tilkoblingsoppgradering var n\u00f8dvendig.", "ipp_error": "Oppdaget IPP-feil.", "ipp_version_error": "IPP-versjon st\u00f8ttes ikke av skriveren.", - "parse_error": "Kan ikke analysere svar fra skriveren." + "parse_error": "Kan ikke analysere svar fra skriveren.", + "unique_id_required": "Enheten mangler unik identifikasjon som kreves for oppdagelse." }, "error": { "connection_error": "Klarte ikke \u00e5 koble til skriveren.", diff --git a/homeassistant/components/ipp/translations/pl.json b/homeassistant/components/ipp/translations/pl.json index 2a212bc1270..877bdb0cd0d 100644 --- a/homeassistant/components/ipp/translations/pl.json +++ b/homeassistant/components/ipp/translations/pl.json @@ -6,7 +6,8 @@ "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 nieobs\u0142ugiwana przez drukark\u0119.", - "parse_error": "Nie mo\u017cna przeanalizowa\u0107 odpowiedzi z drukarki." + "parse_error": "Nie mo\u017cna przeanalizowa\u0107 odpowiedzi z drukarki.", + "unique_id_required": "Urz\u0105dzenie nie posiada unikalnej identyfikacji wymaganej do wykrycia." }, "error": { "connection_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia.", diff --git a/homeassistant/components/isy994/translations/pl.json b/homeassistant/components/isy994/translations/pl.json index 49fdc942541..f9f98e9f6cd 100644 --- a/homeassistant/components/isy994/translations/pl.json +++ b/homeassistant/components/isy994/translations/pl.json @@ -6,6 +6,7 @@ "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia.", "invalid_auth": "Niepoprawne uwierzytelnienie.", + "invalid_host": "Wpis hosta nie by\u0142 w pe\u0142nym formacie URL, np. http://192.168.10.100:80.", "unknown": "Nieoczekiwany b\u0142\u0105d." }, "step": { @@ -13,8 +14,11 @@ "data": { "host": "URL", "password": "Has\u0142o", + "tls": "Wersja TLS kontrolera ISY", "username": "Nazwa u\u017cytkownika" - } + }, + "description": "Wpis hosta musi by\u0107 w pe\u0142nym formacie URL, np. http://192.168.10.100:80.", + "title": "Po\u0142\u0105czenie z ISY994" } } } diff --git a/homeassistant/components/konnected/translations/pl.json b/homeassistant/components/konnected/translations/pl.json index 75843bbde58..29a2155c8fc 100644 --- a/homeassistant/components/konnected/translations/pl.json +++ b/homeassistant/components/konnected/translations/pl.json @@ -3,7 +3,7 @@ "abort": { "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane.", "already_in_progress": "Konfiguracja urz\u0105dzenia jest ju\u017c w toku.", - "not_konn_panel": "Nie rozpoznano urz\u0105dzenia Konnected.io", + "not_konn_panel": "Nie rozpoznano urz\u0105dzenia Konnected.io.", "unknown": "Nieoczekiwany b\u0142\u0105d." }, "error": { diff --git a/homeassistant/components/logi_circle/translations/fi.json b/homeassistant/components/logi_circle/translations/fi.json index 8b7c30df298..84fe07d445e 100644 --- a/homeassistant/components/logi_circle/translations/fi.json +++ b/homeassistant/components/logi_circle/translations/fi.json @@ -1,10 +1,14 @@ { "config": { + "error": { + "auth_error": "API-todennus ep\u00e4onnistui." + }, "step": { "user": { "data": { "flow_impl": "Tarjoaja" - } + }, + "title": "Todentamisen tarjoaja" } } } diff --git a/homeassistant/components/meteo_france/translations/pl.json b/homeassistant/components/meteo_france/translations/pl.json index 12e74728fc8..46f3c1fdc27 100644 --- a/homeassistant/components/meteo_france/translations/pl.json +++ b/homeassistant/components/meteo_france/translations/pl.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Miasto jest ju\u017c skonfigurowane.", - "unknown": "Nieznany b\u0142\u0105d: spr\u00f3buj ponownie p\u00f3\u017aniej" + "unknown": "Nieznany b\u0142\u0105d: spr\u00f3buj ponownie p\u00f3\u017aniej." }, "step": { "user": { diff --git a/homeassistant/components/monoprice/translations/pl.json b/homeassistant/components/monoprice/translations/pl.json index 9e47f6ace1c..b5af0e8851f 100644 --- a/homeassistant/components/monoprice/translations/pl.json +++ b/homeassistant/components/monoprice/translations/pl.json @@ -18,7 +18,7 @@ "source_5": "Nazwa \u017ar\u00f3d\u0142a #5", "source_6": "Nazwa \u017ar\u00f3d\u0142a #6" }, - "title": "Po\u0142\u0105cz z urz\u0105dzeniem" + "title": "Po\u0142\u0105czenie z urz\u0105dzeniem" } } }, diff --git a/homeassistant/components/nest/translations/pl.json b/homeassistant/components/nest/translations/pl.json index b5953decc6b..75d0d99f5d8 100644 --- a/homeassistant/components/nest/translations/pl.json +++ b/homeassistant/components/nest/translations/pl.json @@ -25,7 +25,7 @@ "code": "Kod PIN" }, "description": "Aby po\u0142\u0105czy\u0107 z kontem Nest, [wykonaj autoryzacj\u0119]({url}). \n\n Po autoryzacji skopiuj i wklej podany kod PIN poni\u017cej.", - "title": "Po\u0142\u0105cz z kontem Nest" + "title": "Po\u0142\u0105czenie z kontem Nest" } } } diff --git a/homeassistant/components/nuheat/translations/pl.json b/homeassistant/components/nuheat/translations/pl.json index cded692dbcd..d55b545d040 100644 --- a/homeassistant/components/nuheat/translations/pl.json +++ b/homeassistant/components/nuheat/translations/pl.json @@ -17,7 +17,7 @@ "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" + "title": "Po\u0142\u0105czenie z NuHeat" } } } diff --git a/homeassistant/components/nut/translations/pl.json b/homeassistant/components/nut/translations/pl.json index 488978276a8..5fb9082d676 100644 --- a/homeassistant/components/nut/translations/pl.json +++ b/homeassistant/components/nut/translations/pl.json @@ -28,7 +28,7 @@ "port": "Port", "username": "Nazwa u\u017cytkownika" }, - "title": "Po\u0142\u0105cz z serwerem NUT" + "title": "Po\u0142\u0105czenie z serwerem NUT" } } }, diff --git a/homeassistant/components/nws/translations/pl.json b/homeassistant/components/nws/translations/pl.json index e3634ad2d31..ab1011d9d56 100644 --- a/homeassistant/components/nws/translations/pl.json +++ b/homeassistant/components/nws/translations/pl.json @@ -16,7 +16,7 @@ "station": "Kod stacji METAR" }, "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" + "title": "Po\u0142\u0105czenie z National Weather Service" } } } diff --git a/homeassistant/components/openuv/translations/zh-Hant.json b/homeassistant/components/openuv/translations/zh-Hant.json index d9d4abc0161..70e4b1da62c 100644 --- a/homeassistant/components/openuv/translations/zh-Hant.json +++ b/homeassistant/components/openuv/translations/zh-Hant.json @@ -2,7 +2,7 @@ "config": { "error": { "identifier_exists": "\u8a72\u5ea7\u6a19\u5df2\u8a3b\u518a", - "invalid_api_key": "API \u5bc6\u78bc\u7121\u6548" + "invalid_api_key": "API \u5bc6\u9470\u7121\u6548" }, "step": { "user": { diff --git a/homeassistant/components/panasonic_viera/translations/pl.json b/homeassistant/components/panasonic_viera/translations/pl.json index 5746a08acf3..4d956896e9e 100644 --- a/homeassistant/components/panasonic_viera/translations/pl.json +++ b/homeassistant/components/panasonic_viera/translations/pl.json @@ -3,7 +3,7 @@ "abort": { "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane.", "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/plex/translations/pl.json b/homeassistant/components/plex/translations/pl.json index 95443330c53..2c002d77bbd 100644 --- a/homeassistant/components/plex/translations/pl.json +++ b/homeassistant/components/plex/translations/pl.json @@ -36,7 +36,7 @@ }, "start_website_auth": { "description": "Kontynuuj, by dokona\u0107 autoryzacji w plex.tv.", - "title": "Po\u0142\u0105cz z serwerem Plex" + "title": "Po\u0142\u0105czenie z serwerem Plex" }, "user": { "description": "Przejd\u017a do [plex.tv](https://plex.tv), aby po\u0142\u0105czy\u0107 serwer Plex.", diff --git a/homeassistant/components/roomba/translations/pl.json b/homeassistant/components/roomba/translations/pl.json index 1bb7f0dec45..312768889e4 100644 --- a/homeassistant/components/roomba/translations/pl.json +++ b/homeassistant/components/roomba/translations/pl.json @@ -15,7 +15,7 @@ "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" + "title": "Po\u0142\u0105czenie z urz\u0105dzeniem" } } }, diff --git a/homeassistant/components/smhi/translations/fi.json b/homeassistant/components/smhi/translations/fi.json index c11e9dbdf70..88aeef608d1 100644 --- a/homeassistant/components/smhi/translations/fi.json +++ b/homeassistant/components/smhi/translations/fi.json @@ -1,5 +1,8 @@ { "config": { + "error": { + "wrong_location": "Sijainti vain Ruotsi" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/songpal/translations/pl.json b/homeassistant/components/songpal/translations/pl.json index 93e589c293a..c56e1a9d005 100644 --- a/homeassistant/components/songpal/translations/pl.json +++ b/homeassistant/components/songpal/translations/pl.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane.", - "not_songpal_device": "To nie jest urz\u0105dzenie Songpal" + "not_songpal_device": "To nie jest urz\u0105dzenie Songpal." }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia.", diff --git a/homeassistant/components/tado/translations/pl.json b/homeassistant/components/tado/translations/pl.json index 061b4410e9f..f95374e0329 100644 --- a/homeassistant/components/tado/translations/pl.json +++ b/homeassistant/components/tado/translations/pl.json @@ -15,7 +15,7 @@ "password": "Has\u0142o", "username": "Nazwa u\u017cytkownika" }, - "title": "Po\u0142\u0105cz z kontem Tado" + "title": "Po\u0142\u0105czenie z kontem Tado" } } }, diff --git a/homeassistant/components/tellduslive/translations/fi.json b/homeassistant/components/tellduslive/translations/fi.json new file mode 100644 index 00000000000..95b8fe62dbb --- /dev/null +++ b/homeassistant/components/tellduslive/translations/fi.json @@ -0,0 +1,9 @@ +{ + "config": { + "step": { + "user": { + "title": "Valitse p\u00e4\u00e4tepiste." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tradfri/translations/fi.json b/homeassistant/components/tradfri/translations/fi.json index 5b7b417977d..31984784ee6 100644 --- a/homeassistant/components/tradfri/translations/fi.json +++ b/homeassistant/components/tradfri/translations/fi.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "Silta on jo m\u00e4\u00e4ritetty" + }, "step": { "auth": { "data": { diff --git a/homeassistant/components/unifi/translations/fi.json b/homeassistant/components/unifi/translations/fi.json index 3b9bceb1d0f..7fde27cb308 100644 --- a/homeassistant/components/unifi/translations/fi.json +++ b/homeassistant/components/unifi/translations/fi.json @@ -3,13 +3,18 @@ "abort": { "user_privilege": "K\u00e4ytt\u00e4j\u00e4n on oltava j\u00e4rjestelm\u00e4nvalvoja" }, + "error": { + "faulty_credentials": "Virheellinen tunnistautuminen", + "service_unavailable": "Yhdist\u00e4minen ep\u00e4onnistui" + }, "step": { "user": { "data": { "host": "Palvelin", "password": "Salasana", "port": "Portti", - "site": "Sivuston ID" + "site": "Sivuston ID", + "username": "K\u00e4ytt\u00e4j\u00e4tunnus" } } } diff --git a/homeassistant/components/upnp/translations/pl.json b/homeassistant/components/upnp/translations/pl.json index 08e20e75679..f30dab02a4b 100644 --- a/homeassistant/components/upnp/translations/pl.json +++ b/homeassistant/components/upnp/translations/pl.json @@ -3,7 +3,8 @@ "abort": { "already_configured": "UPnP/IGD jest ju\u017c skonfigurowane.", "incomplete_device": "Ignorowanie niekompletnego urz\u0105dzenia UPnP", - "no_devices_discovered": "Nie wykryto urz\u0105dze\u0144 UPnP/IGD", + "incomplete_discovery": "Wykrywanie niekompletne", + "no_devices_discovered": "Nie wykryto urz\u0105dze\u0144 UPnP/IGD.", "no_devices_found": "Nie znaleziono w sieci urz\u0105dze\u0144 UPnP/IGD.", "no_sensors_or_port_mapping": "W\u0142\u0105cz przynajmniej sensory lub mapowanie port\u00f3w", "single_instance_allowed": "Wymagana jest tylko jedna konfiguracja UPnP/IGD." @@ -22,6 +23,7 @@ "enable_port_mapping": "W\u0142\u0105cz mapowanie port\u00f3w dla Home Assistant'a", "enable_sensors": "Dodaj sensor ruchu sieciowego", "igd": "UPnP/IGD", + "scan_interval": "Cz\u0119stotliwo\u015b\u0107 aktualizacji (sekundy, minimum 30)", "usn": "Urz\u0105dzenie" }, "title": "Opcje konfiguracji dla UPnP/IGD" diff --git a/homeassistant/components/wemo/translations/fi.json b/homeassistant/components/wemo/translations/fi.json new file mode 100644 index 00000000000..afce1415ba7 --- /dev/null +++ b/homeassistant/components/wemo/translations/fi.json @@ -0,0 +1,9 @@ +{ + "config": { + "step": { + "confirm": { + "title": "Wemo" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wiffi/translations/no.json b/homeassistant/components/wiffi/translations/no.json index 8e966a19c00..9f745e0e4a8 100644 --- a/homeassistant/components/wiffi/translations/no.json +++ b/homeassistant/components/wiffi/translations/no.json @@ -12,5 +12,14 @@ "title": "Sett opp TCP-server for WIFFI-enheter" } } + }, + "options": { + "step": { + "init": { + "data": { + "timeout": "Timeout (minutter)" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/wiffi/translations/pl.json b/homeassistant/components/wiffi/translations/pl.json index 4b75faf9af9..810bc4fb1ec 100644 --- a/homeassistant/components/wiffi/translations/pl.json +++ b/homeassistant/components/wiffi/translations/pl.json @@ -1,9 +1,23 @@ { "config": { + "abort": { + "addr_in_use": "Port serwera jest ju\u017c w u\u017cyciu.", + "start_server_failed": "Uruchomienie serwera nie powiod\u0142o si\u0119." + }, "step": { "user": { "data": { "port": "Port serwera" + }, + "title": "Konfiguracja serwera TCP dla urz\u0105dze\u0144 WIFFI" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "timeout": "Limit czasu (minuty)" } } } diff --git a/homeassistant/components/wled/translations/pl.json b/homeassistant/components/wled/translations/pl.json index edd5f38b912..c5551b0927d 100644 --- a/homeassistant/components/wled/translations/pl.json +++ b/homeassistant/components/wled/translations/pl.json @@ -14,7 +14,7 @@ "host": "Nazwa hosta lub adres IP" }, "description": "Konfiguracja WLED w celu integracji z Home Assistant'em.", - "title": "Po\u0142\u0105cz z WLED" + "title": "Po\u0142\u0105czenie z WLED" }, "zeroconf_confirm": { "description": "Czy chcesz doda\u0107 WLED o nazwie `{name}` do Home Assistant'a?", From b452db8b67188a36958a38a5267804afe0947aed Mon Sep 17 00:00:00 2001 From: Xiaonan Shen Date: Tue, 19 May 2020 22:19:27 -0700 Subject: [PATCH 104/406] Add dynamic icon to roomba battery sensor (#35647) * Add dynamic icon to roomba battery sensor * Fix lint * Rename _state to _robot_state --- .../components/roomba/irobot_base.py | 59 ++++++++++--------- homeassistant/components/roomba/sensor.py | 18 ++++-- 2 files changed, 44 insertions(+), 33 deletions(-) diff --git a/homeassistant/components/roomba/irobot_base.py b/homeassistant/components/roomba/irobot_base.py index f510f4965b0..035428192c0 100644 --- a/homeassistant/components/roomba/irobot_base.py +++ b/homeassistant/components/roomba/irobot_base.py @@ -68,10 +68,10 @@ class IRobotEntity(Entity): """Initialize the iRobot handler.""" self.vacuum = roomba self._blid = blid - vacuum_state = roomba_reported_state(roomba) - self._name = vacuum_state.get("name") - self._version = vacuum_state.get("softwareVer") - self._sku = vacuum_state.get("sku") + self.vacuum_state = roomba_reported_state(roomba) + self._name = self.vacuum_state.get("name") + self._version = self.vacuum_state.get("softwareVer") + self._sku = self.vacuum_state.get("sku") @property def should_poll(self): @@ -99,13 +99,32 @@ class IRobotEntity(Entity): "model": self._sku, } + @property + def _battery_level(self): + """Return the battery level of the vacuum cleaner.""" + return self.vacuum_state.get("batPct") + + @property + def _robot_state(self): + """Return the state of the vacuum cleaner.""" + clean_mission_status = self.vacuum_state.get("cleanMissionStatus", {}) + cycle = clean_mission_status.get("cycle") + phase = clean_mission_status.get("phase") + try: + state = STATE_MAP[phase] + except KeyError: + return STATE_ERROR + if cycle != "none" and state in (STATE_IDLE, STATE_DOCKED): + state = STATE_PAUSED + return state + async def async_added_to_hass(self): """Register callback function.""" self.vacuum.register_on_message_callback(self.on_message) - def new_state_filter(self, new_state): - """Filter the new state.""" - raise NotImplementedError + def new_state_filter(self, new_state): # pylint: disable=no-self-use + """Filter out wifi state messages.""" + return len(new_state) > 1 or "signal" not in new_state def on_message(self, json_data): """Update state on message change.""" @@ -120,7 +139,6 @@ class IRobotVacuum(IRobotEntity, StateVacuumEntity): def __init__(self, roomba, blid): """Initialize the iRobot handler.""" super().__init__(roomba, blid) - self.vacuum_state = roomba_reported_state(roomba) self._cap_position = self.vacuum_state.get("cap", {}).get("pose") == 1 @property @@ -141,21 +159,12 @@ class IRobotVacuum(IRobotEntity, StateVacuumEntity): @property def battery_level(self): """Return the battery level of the vacuum cleaner.""" - return self.vacuum_state.get("batPct") + return self._battery_level @property def state(self): """Return the state of the vacuum cleaner.""" - clean_mission_status = self.vacuum_state.get("cleanMissionStatus", {}) - cycle = clean_mission_status.get("cycle") - phase = clean_mission_status.get("phase") - try: - state = STATE_MAP[phase] - except KeyError: - return STATE_ERROR - if cycle != "none" and state in (STATE_IDLE, STATE_DOCKED): - state = STATE_PAUSED - return state + return self._robot_state @property def available(self) -> bool: @@ -215,14 +224,10 @@ class IRobotVacuum(IRobotEntity, StateVacuumEntity): def on_message(self, json_data): """Update state on message change.""" - new_state = json_data.get("state", {}).get("reported", {}) - if ( - len(new_state) == 1 and "signal" in new_state - ): # filter out wifi stat messages - return - _LOGGER.debug("Got new state from the vacuum: %s", json_data) - self.vacuum_state = roomba_reported_state(self.vacuum) - self.schedule_update_ha_state() + state = json_data.get("state", {}).get("reported", {}) + if self.new_state_filter(state): + _LOGGER.debug("Got new state from the vacuum: %s", json_data) + self.schedule_update_ha_state() async def async_start(self): """Start or resume the cleaning task.""" diff --git a/homeassistant/components/roomba/sensor.py b/homeassistant/components/roomba/sensor.py index 40dbd52e158..435f26510f8 100644 --- a/homeassistant/components/roomba/sensor.py +++ b/homeassistant/components/roomba/sensor.py @@ -1,9 +1,10 @@ """Sensor for checking the battery level of Roomba.""" import logging +from homeassistant.components.vacuum import STATE_DOCKED from homeassistant.const import DEVICE_CLASS_BATTERY, UNIT_PERCENTAGE +from homeassistant.helpers.icon import icon_for_battery_level -from . import roomba_reported_state from .const import BLID, DOMAIN, ROOMBA_SESSION from .irobot_base import IRobotEntity @@ -42,11 +43,16 @@ class RoombaBattery(IRobotEntity): """Return the unit_of_measurement of the device.""" return UNIT_PERCENTAGE + @property + def icon(self): + """Return the icon for the battery.""" + charging = bool(self._robot_state == STATE_DOCKED) + + return icon_for_battery_level( + battery_level=self._battery_level, charging=charging + ) + @property def state(self): """Return the state of the sensor.""" - return roomba_reported_state(self.vacuum).get("batPct") - - def new_state_filter(self, new_state): - """Filter the new state.""" - return "batPct" in new_state + return self._battery_level From ee92d64088c4f51ff6ddf4c3059216954560b5aa Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 20 May 2020 08:37:32 +0200 Subject: [PATCH 105/406] Add UUID to ZeroConf service info (#35623) --- homeassistant/components/zeroconf/__init__.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/zeroconf/__init__.py b/homeassistant/components/zeroconf/__init__.py index d699160eed4..6c7e0ff8103 100644 --- a/homeassistant/components/zeroconf/__init__.py +++ b/homeassistant/components/zeroconf/__init__.py @@ -1,4 +1,5 @@ """Support for exposing Home Assistant via Zeroconf.""" +import asyncio import ipaddress import logging import socket @@ -116,9 +117,15 @@ def setup(hass, config): zeroconf = hass.data[DOMAIN] = _get_instance( hass, config.get(DOMAIN, {}).get(CONF_DEFAULT_INTERFACE) ) - zeroconf_name = f"{hass.config.location_name}.{ZEROCONF_TYPE}" + + # Get instance UUID + uuid = asyncio.run_coroutine_threadsafe( + hass.helpers.instance_id.async_get(), hass.loop + ).result() params = { + "location_name": hass.config.location_name, + "uuid": uuid, "version": __version__, "external_url": None, "internal_url": None, @@ -128,6 +135,7 @@ def setup(hass, config): "requires_api_password": True, } + # Get instance URL's try: params["external_url"] = get_url(hass, allow_internal=False) except NoURLAvailableError: @@ -150,8 +158,8 @@ def setup(hass, config): info = ServiceInfo( ZEROCONF_TYPE, - zeroconf_name, - None, + name=f"{hass.config.location_name}.{ZEROCONF_TYPE}", + server=f"{uuid}.local.", addresses=[host_ip_pton], port=hass.http.server_port, properties=params, From 2b3cf97979ff6ba0ffab56fc5a823fb34da1a722 Mon Sep 17 00:00:00 2001 From: Fredrik Erlandsson Date: Wed, 20 May 2020 13:25:42 +0200 Subject: [PATCH 106/406] Fix Daikin duplicate entries (#35833) --- homeassistant/components/daikin/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/daikin/__init__.py b/homeassistant/components/daikin/__init__.py index 1e03938dfcf..06735d7e3b8 100644 --- a/homeassistant/components/daikin/__init__.py +++ b/homeassistant/components/daikin/__init__.py @@ -16,7 +16,7 @@ from homeassistant.helpers.typing import HomeAssistantType from homeassistant.util import Throttle from . import config_flow # noqa: F401 -from .const import CONF_KEY, CONF_UUID, TIMEOUT +from .const import CONF_KEY, CONF_UUID, KEY_MAC, TIMEOUT _LOGGER = logging.getLogger(__name__) @@ -61,6 +61,9 @@ async def async_setup(hass, config): async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry): """Establish connection with Daikin.""" conf = entry.data + # For backwards compat, set unique ID + if entry.unique_id is None: + hass.config_entries.async_update_entry(entry, unique_id=conf[KEY_MAC]) daikin_api = await daikin_api_setup( hass, conf[CONF_HOST], From 4f317353e08d28661d67682e5f6437802ae8b5dc Mon Sep 17 00:00:00 2001 From: thomkaufmann Date: Wed, 20 May 2020 14:44:57 +0200 Subject: [PATCH 107/406] Add Nuki Opener integration (#35702) * Add Nuki Opener integration * Update pynuki version requirement; fix typo * Update requirements_all.txt * Create base class of shared lock and opener code * Clean up code formatting * Update requirements_all; Run isort * Remove unnecessary pass statements --- homeassistant/components/nuki/lock.py | 84 ++++++++++++++++----- homeassistant/components/nuki/manifest.json | 2 +- requirements_all.txt | 2 +- 3 files changed, 67 insertions(+), 21 deletions(-) diff --git a/homeassistant/components/nuki/lock.py b/homeassistant/components/nuki/lock.py index 3d382496b28..13825cede94 100644 --- a/homeassistant/components/nuki/lock.py +++ b/homeassistant/components/nuki/lock.py @@ -1,4 +1,5 @@ """Nuki.io lock platform.""" +from abc import ABC, abstractmethod from datetime import timedelta import logging @@ -50,9 +51,10 @@ LOCK_N_GO_SERVICE_SCHEMA = vol.Schema( def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Nuki lock platform.""" bridge = NukiBridge( - config[CONF_HOST], config[CONF_TOKEN], config[CONF_PORT], DEFAULT_TIMEOUT + config[CONF_HOST], config[CONF_TOKEN], config[CONF_PORT], DEFAULT_TIMEOUT, ) - devices = [NukiLock(lock) for lock in bridge.locks] + + devices = [NukiLockEntity(lock) for lock in bridge.locks] def service_handler(service): """Service handler for nuki services.""" @@ -65,41 +67,43 @@ def setup_platform(hass, config, add_entities, discovery_info=None): lock.lock_n_go(unlatch=unlatch) hass.services.register( - DOMAIN, SERVICE_LOCK_N_GO, service_handler, schema=LOCK_N_GO_SERVICE_SCHEMA + DOMAIN, SERVICE_LOCK_N_GO, service_handler, schema=LOCK_N_GO_SERVICE_SCHEMA, ) + devices.extend([NukiOpenerEntity(opener) for opener in bridge.openers]) + add_entities(devices) -class NukiLock(LockEntity): - """Representation of a Nuki lock.""" +class NukiDeviceEntity(LockEntity, ABC): + """Representation of a Nuki device.""" - def __init__(self, nuki_lock): + def __init__(self, nuki_device): """Initialize the lock.""" - self._nuki_lock = nuki_lock - self._available = nuki_lock.state not in ERROR_STATES + self._nuki_device = nuki_device + self._available = nuki_device.state not in ERROR_STATES @property def name(self): """Return the name of the lock.""" - return self._nuki_lock.name + return self._nuki_device.name @property def unique_id(self) -> str: """Return a unique ID.""" - return self._nuki_lock.nuki_id + return self._nuki_device.nuki_id @property + @abstractmethod def is_locked(self): """Return true if lock is locked.""" - return self._nuki_lock.is_locked @property def device_state_attributes(self): """Return the device specific state attributes.""" data = { - ATTR_BATTERY_CRITICAL: self._nuki_lock.battery_critical, - ATTR_NUKI_ID: self._nuki_lock.nuki_id, + ATTR_BATTERY_CRITICAL: self._nuki_device.battery_critical, + ATTR_NUKI_ID: self._nuki_device.nuki_id, } return data @@ -117,28 +121,49 @@ class NukiLock(LockEntity): """Update the nuki lock properties.""" for level in (False, True): try: - self._nuki_lock.update(aggressive=level) + self._nuki_device.update(aggressive=level) except RequestException: _LOGGER.warning("Network issues detect with %s", self.name) self._available = False continue # If in error state, we force an update and repoll data - self._available = self._nuki_lock.state not in ERROR_STATES + self._available = self._nuki_device.state not in ERROR_STATES if self._available: break + @abstractmethod def lock(self, **kwargs): """Lock the device.""" - self._nuki_lock.lock() + + @abstractmethod + def unlock(self, **kwargs): + """Unlock the device.""" + + @abstractmethod + def open(self, **kwargs): + """Open the door latch.""" + + +class NukiLockEntity(NukiDeviceEntity): + """Representation of a Nuki lock.""" + + @property + def is_locked(self): + """Return true if lock is locked.""" + return self._nuki_device.is_locked + + def lock(self, **kwargs): + """Lock the device.""" + self._nuki_device.lock() def unlock(self, **kwargs): """Unlock the device.""" - self._nuki_lock.unlock() + self._nuki_device.unlock() def open(self, **kwargs): """Open the door latch.""" - self._nuki_lock.unlatch() + self._nuki_device.unlatch() def lock_n_go(self, unlatch=False, **kwargs): """Lock and go. @@ -146,4 +171,25 @@ class NukiLock(LockEntity): This will first unlock the door, then wait for 20 seconds (or another amount of time depending on the lock settings) and relock. """ - self._nuki_lock.lock_n_go(unlatch, kwargs) + self._nuki_device.lock_n_go(unlatch, kwargs) + + +class NukiOpenerEntity(NukiDeviceEntity): + """Representation of a Nuki opener.""" + + @property + def is_locked(self): + """Return true if ring-to-open is enabled.""" + return not self._nuki_device.is_rto_activated + + def lock(self, **kwargs): + """Disable ring-to-open.""" + self._nuki_device.deactivate_rto() + + def unlock(self, **kwargs): + """Enable ring-to-open.""" + self._nuki_device.activate_rto() + + def open(self, **kwargs): + """Buzz open the door.""" + self._nuki_device.electric_strike_actuation() diff --git a/homeassistant/components/nuki/manifest.json b/homeassistant/components/nuki/manifest.json index a51ff3752a5..386b36a3ca9 100644 --- a/homeassistant/components/nuki/manifest.json +++ b/homeassistant/components/nuki/manifest.json @@ -2,6 +2,6 @@ "domain": "nuki", "name": "Nuki", "documentation": "https://www.home-assistant.io/integrations/nuki", - "requirements": ["pynuki==1.3.3"], + "requirements": ["pynuki==1.3.7"], "codeowners": ["@pvizeli"] } diff --git a/requirements_all.txt b/requirements_all.txt index e5364311843..ebdad2e0803 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1486,7 +1486,7 @@ pynetgear==0.6.1 pynetio==0.1.9.1 # homeassistant.components.nuki -pynuki==1.3.3 +pynuki==1.3.7 # homeassistant.components.nut pynut2==2.1.2 From 53a9d39a81a8f3e4df9274ca1d584bff95046b45 Mon Sep 17 00:00:00 2001 From: Emilv2 Date: Wed, 20 May 2020 14:53:01 +0200 Subject: [PATCH 108/406] Fix Delijn sensor naming (#35789) --- homeassistant/components/delijn/manifest.json | 2 +- homeassistant/components/delijn/sensor.py | 48 +++++++++++-------- requirements_all.txt | 2 +- 3 files changed, 30 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/delijn/manifest.json b/homeassistant/components/delijn/manifest.json index 3f6efd0a3d7..8727dd25139 100644 --- a/homeassistant/components/delijn/manifest.json +++ b/homeassistant/components/delijn/manifest.json @@ -3,5 +3,5 @@ "name": "De Lijn", "documentation": "https://www.home-assistant.io/integrations/delijn", "codeowners": ["@bollewolle"], - "requirements": ["pydelijn==0.5.1"] + "requirements": ["pydelijn==0.6.0"] } diff --git a/homeassistant/components/delijn/sensor.py b/homeassistant/components/delijn/sensor.py index 538e071e194..2c7eec1691c 100644 --- a/homeassistant/components/delijn/sensor.py +++ b/homeassistant/components/delijn/sensor.py @@ -2,6 +2,7 @@ import logging from pydelijn.api import Passages +from pydelijn.common import HttpException import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA @@ -37,22 +38,23 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Create the sensor.""" api_key = config[CONF_API_KEY] - name = DEFAULT_NAME session = async_get_clientsession(hass) sensors = [] for nextpassage in config[CONF_NEXT_DEPARTURE]: - stop_id = nextpassage[CONF_STOP_ID] - number_of_departures = nextpassage[CONF_NUMBER_OF_DEPARTURES] - line = Passages( - hass.loop, stop_id, number_of_departures, api_key, session, True + sensors.append( + DeLijnPublicTransportSensor( + Passages( + hass.loop, + nextpassage[CONF_STOP_ID], + nextpassage[CONF_NUMBER_OF_DEPARTURES], + api_key, + session, + True, + ) + ) ) - await line.get_passages() - if line.passages is None: - _LOGGER.warning("No data received from De Lijn") - return - sensors.append(DeLijnPublicTransportSensor(line, name)) async_add_entities(sensors, True) @@ -60,20 +62,28 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= class DeLijnPublicTransportSensor(Entity): """Representation of a Ruter sensor.""" - def __init__(self, line, name): + def __init__(self, line): """Initialize the sensor.""" self.line = line self._attributes = {ATTR_ATTRIBUTION: ATTRIBUTION} - self._name = name + self._name = None self._state = None - self._available = False + self._available = True async def async_update(self): """Get the latest data from the De Lijn API.""" - await self.line.get_passages() - if self.line.passages is None: - _LOGGER.warning("No data received from De Lijn") + try: + await self.line.get_passages() + self._name = await self.line.get_stopname() + except HttpException: + self._available = False + _LOGGER.error("De Lijn http error") return + + self._attributes["stopname"] = self._name + for passage in self.line.passages: + passage["stopname"] = self._name + try: first = self.line.passages[0] if first["due_at_realtime"] is not None: @@ -81,8 +91,6 @@ class DeLijnPublicTransportSensor(Entity): else: first_passage = first["due_at_schedule"] self._state = first_passage - self._name = first["stopname"] - self._attributes["stopname"] = first["stopname"] self._attributes["line_number_public"] = first["line_number_public"] self._attributes["line_transport_type"] = first["line_transport_type"] self._attributes["final_destination"] = first["final_destination"] @@ -90,8 +98,8 @@ class DeLijnPublicTransportSensor(Entity): self._attributes["due_at_realtime"] = first["due_at_realtime"] self._attributes["next_passages"] = self.line.passages self._available = True - except (KeyError, IndexError) as error: - _LOGGER.debug("Error getting data from De Lijn: %s", error) + except (KeyError, IndexError): + _LOGGER.error("Invalid data received from De Lijn") self._available = False @property diff --git a/requirements_all.txt b/requirements_all.txt index ebdad2e0803..62db537fa92 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1272,7 +1272,7 @@ pydanfossair==0.1.0 pydeconz==70 # homeassistant.components.delijn -pydelijn==0.5.1 +pydelijn==0.6.0 # homeassistant.components.zwave pydispatcher==2.0.5 From b3459d91907b9105c0058d60538bb2dfb4ed10e1 Mon Sep 17 00:00:00 2001 From: uvjustin <46082645+uvjustin@users.noreply.github.com> Date: Thu, 21 May 2020 03:06:51 +0800 Subject: [PATCH 109/406] Handle None received from pyforked-daapd (#35830) * Handle None received from API in forked-daapd * Bump pyforked-daapd version in requirements * Add test --- .../components/forked_daapd/manifest.json | 2 +- .../components/forked_daapd/media_player.py | 62 ++++++++++--------- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- .../forked_daapd/test_media_player.py | 31 +++++++++- 5 files changed, 66 insertions(+), 33 deletions(-) diff --git a/homeassistant/components/forked_daapd/manifest.json b/homeassistant/components/forked_daapd/manifest.json index ee57f678601..171776a290a 100644 --- a/homeassistant/components/forked_daapd/manifest.json +++ b/homeassistant/components/forked_daapd/manifest.json @@ -3,7 +3,7 @@ "name": "forked-daapd", "documentation": "https://www.home-assistant.io/integrations/forked-daapd", "codeowners": ["@uvjustin"], - "requirements": ["pyforked-daapd==0.1.8", "pylibrespot-java==0.1.0"], + "requirements": ["pyforked-daapd==0.1.9", "pylibrespot-java==0.1.0"], "config_flow": true, "zeroconf": ["_daap._tcp.local."] } diff --git a/homeassistant/components/forked_daapd/media_player.py b/homeassistant/components/forked_daapd/media_player.py index e492aa1b454..07cc11807fd 100644 --- a/homeassistant/components/forked_daapd/media_player.py +++ b/homeassistant/components/forked_daapd/media_player.py @@ -794,26 +794,29 @@ class ForkedDaapdUpdater: "queue" in update_types ): # update queue, queue before player for async_play_media queue = await self._api.get_request("queue") - update_events["queue"] = asyncio.Event() - async_dispatcher_send( - self.hass, - SIGNAL_UPDATE_QUEUE.format(self._entry_id), - queue, - update_events["queue"], - ) + if queue: + update_events["queue"] = asyncio.Event() + async_dispatcher_send( + self.hass, + SIGNAL_UPDATE_QUEUE.format(self._entry_id), + queue, + update_events["queue"], + ) # order of below don't matter if not {"outputs", "volume"}.isdisjoint(update_types): # update outputs - outputs = (await self._api.get_request("outputs"))["outputs"] - update_events[ - "outputs" - ] = asyncio.Event() # only for master, zones should ignore - async_dispatcher_send( - self.hass, - SIGNAL_UPDATE_OUTPUTS.format(self._entry_id), - outputs, - update_events["outputs"], - ) - self._add_zones(outputs) + outputs = await self._api.get_request("outputs") + if outputs: + outputs = outputs["outputs"] + update_events[ + "outputs" + ] = asyncio.Event() # only for master, zones should ignore + async_dispatcher_send( + self.hass, + SIGNAL_UPDATE_OUTPUTS.format(self._entry_id), + outputs, + update_events["outputs"], + ) + self._add_zones(outputs) if not {"database"}.isdisjoint(update_types): pipes, playlists = await asyncio.gather( self._api.get_pipes(), self._api.get_playlists() @@ -832,17 +835,18 @@ class ForkedDaapdUpdater: update_types ): # update player player = await self._api.get_request("player") - update_events["player"] = asyncio.Event() - if update_events.get("queue"): - await update_events[ - "queue" - ].wait() # make sure queue done before player for async_play_media - async_dispatcher_send( - self.hass, - SIGNAL_UPDATE_PLAYER.format(self._entry_id), - player, - update_events["player"], - ) + if player: + update_events["player"] = asyncio.Event() + if update_events.get("queue"): + await update_events[ + "queue" + ].wait() # make sure queue done before player for async_play_media + async_dispatcher_send( + self.hass, + SIGNAL_UPDATE_PLAYER.format(self._entry_id), + player, + update_events["player"], + ) if update_events: await asyncio.wait( [event.wait() for event in update_events.values()] diff --git a/requirements_all.txt b/requirements_all.txt index 62db537fa92..3d5189a5bae 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1332,7 +1332,7 @@ pyflunearyou==1.0.7 pyfnip==0.2 # homeassistant.components.forked_daapd -pyforked-daapd==0.1.8 +pyforked-daapd==0.1.9 # homeassistant.components.fritzbox pyfritzhome==0.4.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 3e3fc02661b..65725b04173 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -560,7 +560,7 @@ pyflume==0.4.0 pyflunearyou==1.0.7 # homeassistant.components.forked_daapd -pyforked-daapd==0.1.8 +pyforked-daapd==0.1.9 # homeassistant.components.fritzbox pyfritzhome==0.4.2 diff --git a/tests/components/forked_daapd/test_media_player.py b/tests/components/forked_daapd/test_media_player.py index 8f3a6c2c139..43a39146199 100644 --- a/tests/components/forked_daapd/test_media_player.py +++ b/tests/components/forked_daapd/test_media_player.py @@ -8,6 +8,9 @@ from homeassistant.components.forked_daapd.const import ( CONF_TTS_PAUSE_TIME, CONF_TTS_VOLUME, DOMAIN, + SIGNAL_UPDATE_OUTPUTS, + SIGNAL_UPDATE_PLAYER, + SIGNAL_UPDATE_QUEUE, SOURCE_NAME_CLEAR, SOURCE_NAME_DEFAULT, SUPPORTED_FEATURES, @@ -63,7 +66,7 @@ from homeassistant.const import ( ) from tests.async_mock import patch -from tests.common import MockConfigEntry +from tests.common import MockConfigEntry, async_mock_signal TEST_MASTER_ENTITY_NAME = "media_player.forked_daapd_server" TEST_ZONE_ENTITY_NAMES = [ @@ -369,6 +372,32 @@ def test_master_state(hass, mock_api_object): assert not state.attributes[ATTR_MEDIA_SHUFFLE] +async def test_no_update_when_get_request_returns_none( + hass, config_entry, mock_api_object +): + """Test when get request returns None.""" + + async def get_request_side_effect(update_type): + return None + + mock_api_object.get_request.side_effect = get_request_side_effect + updater_update = mock_api_object.start_websocket_handler.call_args[0][2] + signal_output_call = async_mock_signal( + hass, SIGNAL_UPDATE_OUTPUTS.format(config_entry.entry_id) + ) + signal_player_call = async_mock_signal( + hass, SIGNAL_UPDATE_PLAYER.format(config_entry.entry_id) + ) + signal_queue_call = async_mock_signal( + hass, SIGNAL_UPDATE_QUEUE.format(config_entry.entry_id) + ) + await updater_update(["outputs", "player", "queue"]) + await hass.async_block_till_done() + assert len(signal_output_call) == 0 + assert len(signal_player_call) == 0 + assert len(signal_queue_call) == 0 + + async def _service_call( hass, entity_name, service, additional_service_data=None, blocking=True ): From 9907e95c34d0763838756e06497cc0bf905d78e4 Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Wed, 20 May 2020 16:10:50 -0400 Subject: [PATCH 110/406] Add zha climate (#35682) * Initial ZHA climate implementation. * Add retryable_request decorator. sort helpers imports. * Check manufacturer for Climate implementation. * Default zha.climate.operation_list to [Off] * Migrate to climate 1.0 * Sort imports, properties and methods. * Handle 'PRESET_NONE' presets. * Use pi_heating/cooling_demand for HVAC action prop. * Implement `running_state` HVAC channel property. For ZHA thermostats which don't support `pi_heating_demand` or `pi_cooling_demand' attributes. * wip fan support * Refactor retryable request logging. * Rebase cleanup. * Update climate discovery. * Fix ZHA climate restoration. * Bulk configure attribute reports. * Use configure_reporting_multiple command for Light More detailed response parsing of configure_reporting_multiple. * Use ordered list for HVAC cluster attribute reports. * Don't mutilate HVAC mode list. * Add fan_mode property to fan channel. * Fix type hinting. * Expose fan mode only. * Implement fan mode setting. Drop support for HVAC_FAN_ONLY mode. * Use ClimateEntity as base class. * Cleanup debug code. * Update time display for Sinope. * Don't do many retries. * Don't use multi attr reporting configuration. * Make tests pass. * Drop support for setpoint change source/amount. * Cleanups. * Drop aux heat * Update tests. * Drop Sinope temperature display code. * Update tests. * Refactor temperature setting. * Update tests. * Update Fan tests. * Lint * Black. * Use correct logging levels --- homeassistant/components/zha/climate.py | 550 +++++++++ .../components/zha/core/channels/hvac.py | 403 ++++++- homeassistant/components/zha/core/const.py | 14 +- .../components/zha/core/discovery.py | 1 + homeassistant/components/zha/core/helpers.py | 60 +- .../components/zha/core/registries.py | 4 + tests/components/zha/common.py | 4 +- tests/components/zha/test_climate.py | 1060 +++++++++++++++++ tests/components/zha/zha_devices_list.py | 22 +- 9 files changed, 2099 insertions(+), 19 deletions(-) create mode 100644 homeassistant/components/zha/climate.py create mode 100644 tests/components/zha/test_climate.py diff --git a/homeassistant/components/zha/climate.py b/homeassistant/components/zha/climate.py new file mode 100644 index 00000000000..92f17e70af0 --- /dev/null +++ b/homeassistant/components/zha/climate.py @@ -0,0 +1,550 @@ +""" +Climate on Zigbee Home Automation networks. + +For more details on this platform, please refer to the documentation +at https://home-assistant.io/components/zha.climate/ +""" +from datetime import datetime, timedelta +import enum +import functools +import logging +from random import randint +from typing import List, Optional, Tuple + +from homeassistant.components.climate import ClimateEntity +from homeassistant.components.climate.const import ( + ATTR_HVAC_MODE, + ATTR_TARGET_TEMP_HIGH, + ATTR_TARGET_TEMP_LOW, + CURRENT_HVAC_COOL, + CURRENT_HVAC_FAN, + CURRENT_HVAC_HEAT, + CURRENT_HVAC_IDLE, + CURRENT_HVAC_OFF, + DOMAIN, + FAN_AUTO, + FAN_ON, + HVAC_MODE_COOL, + HVAC_MODE_DRY, + HVAC_MODE_FAN_ONLY, + HVAC_MODE_HEAT, + HVAC_MODE_HEAT_COOL, + HVAC_MODE_OFF, + PRESET_AWAY, + PRESET_NONE, + SUPPORT_FAN_MODE, + SUPPORT_PRESET_MODE, + SUPPORT_TARGET_TEMPERATURE, + SUPPORT_TARGET_TEMPERATURE_RANGE, +) +from homeassistant.const import ATTR_TEMPERATURE, PRECISION_HALVES, TEMP_CELSIUS +from homeassistant.core import callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.event import async_track_time_interval +import homeassistant.util.dt as dt_util + +from .core import discovery +from .core.const import ( + CHANNEL_FAN, + CHANNEL_THERMOSTAT, + DATA_ZHA, + DATA_ZHA_DISPATCHERS, + SIGNAL_ADD_ENTITIES, + SIGNAL_ATTR_UPDATED, +) +from .core.registries import ZHA_ENTITIES +from .entity import ZhaEntity + +DEPENDENCIES = ["zha"] + +ATTR_SYS_MODE = "system_mode" +ATTR_RUNNING_MODE = "running_mode" +ATTR_SETPT_CHANGE_SRC = "setpoint_change_source" +ATTR_SETPT_CHANGE_AMT = "setpoint_change_amount" +ATTR_OCCUPANCY = "occupancy" +ATTR_PI_COOLING_DEMAND = "pi_cooling_demand" +ATTR_PI_HEATING_DEMAND = "pi_heating_demand" +ATTR_OCCP_COOL_SETPT = "occupied_cooling_setpoint" +ATTR_OCCP_HEAT_SETPT = "occupied_heating_setpoint" +ATTR_UNOCCP_HEAT_SETPT = "unoccupied_heating_setpoint" +ATTR_UNOCCP_COOL_SETPT = "unoccupied_cooling_setpoint" + + +STRICT_MATCH = functools.partial(ZHA_ENTITIES.strict_match, DOMAIN) +RUNNING_MODE = {0x00: HVAC_MODE_OFF, 0x03: HVAC_MODE_COOL, 0x04: HVAC_MODE_HEAT} + + +class ThermostatFanMode(enum.IntEnum): + """Fan channel enum for thermostat Fans.""" + + OFF = 0x00 + ON = 0x04 + AUTO = 0x05 + + +class RunningState(enum.IntFlag): + """ZCL Running state enum.""" + + HEAT = 0x0001 + COOL = 0x0002 + FAN = 0x0004 + HEAT_STAGE_2 = 0x0008 + COOL_STAGE_2 = 0x0010 + FAN_STAGE_2 = 0x0020 + FAN_STAGE_3 = 0x0040 + + +SEQ_OF_OPERATION = { + 0x00: (HVAC_MODE_OFF, HVAC_MODE_COOL), # cooling only + 0x01: (HVAC_MODE_OFF, HVAC_MODE_COOL), # cooling with reheat + 0x02: (HVAC_MODE_OFF, HVAC_MODE_HEAT), # heating only + 0x03: (HVAC_MODE_OFF, HVAC_MODE_HEAT), # heating with reheat + # cooling and heating 4-pipes + 0x04: (HVAC_MODE_OFF, HVAC_MODE_HEAT_COOL, HVAC_MODE_COOL, HVAC_MODE_HEAT), + # cooling and heating 4-pipes + 0x05: (HVAC_MODE_OFF, HVAC_MODE_HEAT_COOL, HVAC_MODE_COOL, HVAC_MODE_HEAT), +} + + +class SystemMode(enum.IntEnum): + """ZCL System Mode attribute enum.""" + + OFF = 0x00 + HEAT_COOL = 0x01 + COOL = 0x03 + HEAT = 0x04 + AUX_HEAT = 0x05 + PRE_COOL = 0x06 + FAN_ONLY = 0x07 + DRY = 0x08 + SLEEP = 0x09 + + +HVAC_MODE_2_SYSTEM = { + HVAC_MODE_OFF: SystemMode.OFF, + HVAC_MODE_HEAT_COOL: SystemMode.HEAT_COOL, + HVAC_MODE_COOL: SystemMode.COOL, + HVAC_MODE_HEAT: SystemMode.HEAT, + HVAC_MODE_FAN_ONLY: SystemMode.FAN_ONLY, + HVAC_MODE_DRY: SystemMode.DRY, +} + +SYSTEM_MODE_2_HVAC = { + SystemMode.OFF: HVAC_MODE_OFF, + SystemMode.HEAT_COOL: HVAC_MODE_HEAT_COOL, + SystemMode.COOL: HVAC_MODE_COOL, + SystemMode.HEAT: HVAC_MODE_HEAT, + SystemMode.AUX_HEAT: HVAC_MODE_HEAT, + SystemMode.PRE_COOL: HVAC_MODE_COOL, # this is 'precooling'. is it the same? + SystemMode.FAN_ONLY: HVAC_MODE_FAN_ONLY, + SystemMode.DRY: HVAC_MODE_DRY, + SystemMode.SLEEP: HVAC_MODE_OFF, +} + +ZCL_TEMP = 100 + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up the Zigbee Home Automation sensor from config entry.""" + entities_to_create = hass.data[DATA_ZHA][DOMAIN] + unsub = async_dispatcher_connect( + hass, + SIGNAL_ADD_ENTITIES, + functools.partial( + discovery.async_add_entities, async_add_entities, entities_to_create + ), + ) + hass.data[DATA_ZHA][DATA_ZHA_DISPATCHERS].append(unsub) + + +@STRICT_MATCH(channel_names=CHANNEL_THERMOSTAT, aux_channels=CHANNEL_FAN) +class Thermostat(ZhaEntity, ClimateEntity): + """Representation of a ZHA Thermostat device.""" + + DEFAULT_MAX_TEMP = 35 + DEFAULT_MIN_TEMP = 7 + + _domain = DOMAIN + value_attribute = 0x0000 + + def __init__(self, unique_id, zha_device, channels, **kwargs): + """Initialize ZHA Thermostat instance.""" + super().__init__(unique_id, zha_device, channels, **kwargs) + self._thrm = self.cluster_channels.get(CHANNEL_THERMOSTAT) + self._preset = PRESET_NONE + self._presets = [] + self._supported_flags = SUPPORT_TARGET_TEMPERATURE + self._fan = self.cluster_channels.get(CHANNEL_FAN) + + @property + def current_temperature(self): + """Return the current temperature.""" + if self._thrm.local_temp is None: + return None + return self._thrm.local_temp / ZCL_TEMP + + @property + def device_state_attributes(self): + """Return device specific state attributes.""" + data = {} + if self.hvac_mode: + mode = SYSTEM_MODE_2_HVAC.get(self._thrm.system_mode, "unknown") + data[ATTR_SYS_MODE] = f"[{self._thrm.system_mode}]/{mode}" + if self._thrm.occupancy is not None: + data[ATTR_OCCUPANCY] = self._thrm.occupancy + if self._thrm.occupied_cooling_setpoint is not None: + data[ATTR_OCCP_COOL_SETPT] = self._thrm.occupied_cooling_setpoint + if self._thrm.occupied_heating_setpoint is not None: + data[ATTR_OCCP_HEAT_SETPT] = self._thrm.occupied_heating_setpoint + + unoccupied_cooling_setpoint = self._thrm.unoccupied_cooling_setpoint + if unoccupied_cooling_setpoint is not None: + data[ATTR_UNOCCP_HEAT_SETPT] = unoccupied_cooling_setpoint + + unoccupied_heating_setpoint = self._thrm.unoccupied_heating_setpoint + if unoccupied_heating_setpoint is not None: + data[ATTR_UNOCCP_COOL_SETPT] = unoccupied_heating_setpoint + return data + + @property + def fan_mode(self) -> Optional[str]: + """Return current FAN mode.""" + if self._thrm.running_state is None: + return FAN_AUTO + + if self._thrm.running_state & ( + RunningState.FAN | RunningState.FAN_STAGE_2 | RunningState.FAN_STAGE_3 + ): + return FAN_ON + return FAN_AUTO + + @property + def fan_modes(self) -> Optional[List[str]]: + """Return supported FAN modes.""" + if not self._fan: + return None + return [FAN_AUTO, FAN_ON] + + @property + def hvac_action(self) -> Optional[str]: + """Return the current HVAC action.""" + if ( + self._thrm.pi_heating_demand is None + and self._thrm.pi_cooling_demand is None + ): + self.debug("Running mode: %s", self._thrm.running_mode) + self.debug("Running state: %s", self._thrm.running_state) + running_state = self._thrm.running_state + if running_state is None: + return None + if running_state & (RunningState.HEAT | RunningState.HEAT_STAGE_2): + return CURRENT_HVAC_HEAT + if running_state & (RunningState.COOL | RunningState.COOL_STAGE_2): + return CURRENT_HVAC_COOL + if running_state & ( + RunningState.FAN | RunningState.FAN_STAGE_2 | RunningState.FAN_STAGE_3 + ): + return CURRENT_HVAC_FAN + else: + heating_demand = self._thrm.pi_heating_demand + if heating_demand is not None and heating_demand > 0: + return CURRENT_HVAC_HEAT + cooling_demand = self._thrm.pi_cooling_demand + if cooling_demand is not None and cooling_demand > 0: + return CURRENT_HVAC_COOL + if self.hvac_mode != HVAC_MODE_OFF: + return CURRENT_HVAC_IDLE + return CURRENT_HVAC_OFF + + @property + def hvac_mode(self) -> Optional[str]: + """Return HVAC operation mode.""" + try: + return SYSTEM_MODE_2_HVAC[self._thrm.system_mode] + except KeyError: + self.error( + "can't map 'system_mode: %s' to a HVAC mode", self._thrm.system_mode + ) + return None + + @property + def hvac_modes(self) -> Tuple[str, ...]: + """Return the list of available HVAC operation modes.""" + return SEQ_OF_OPERATION.get(self._thrm.ctrl_seqe_of_oper, (HVAC_MODE_OFF,)) + + @property + def precision(self): + """Return the precision of the system.""" + return PRECISION_HALVES + + @property + def preset_mode(self) -> Optional[str]: + """Return current preset mode.""" + return self._preset + + @property + def preset_modes(self) -> Optional[List[str]]: + """Return supported preset modes.""" + return self._presets + + @property + def supported_features(self): + """Return the list of supported features.""" + features = self._supported_flags + if HVAC_MODE_HEAT_COOL in self.hvac_modes: + features |= SUPPORT_TARGET_TEMPERATURE_RANGE + if self._fan is not None: + self._supported_flags |= SUPPORT_FAN_MODE + return features + + @property + def target_temperature(self): + """Return the temperature we try to reach.""" + temp = None + if self.hvac_mode == HVAC_MODE_COOL: + if self.preset_mode == PRESET_AWAY: + temp = self._thrm.unoccupied_cooling_setpoint + else: + temp = self._thrm.occupied_cooling_setpoint + elif self.hvac_mode == HVAC_MODE_HEAT: + if self.preset_mode == PRESET_AWAY: + temp = self._thrm.unoccupied_heating_setpoint + else: + temp = self._thrm.occupied_heating_setpoint + if temp is None: + return temp + return round(temp / ZCL_TEMP, 1) + + @property + def target_temperature_high(self): + """Return the upper bound temperature we try to reach.""" + if self.hvac_mode != HVAC_MODE_HEAT_COOL: + return None + if self.preset_mode == PRESET_AWAY: + temp = self._thrm.unoccupied_cooling_setpoint + else: + temp = self._thrm.occupied_cooling_setpoint + + if temp is None: + return temp + + return round(temp / ZCL_TEMP, 1) + + @property + def target_temperature_low(self): + """Return the lower bound temperature we try to reach.""" + if self.hvac_mode != HVAC_MODE_HEAT_COOL: + return None + if self.preset_mode == PRESET_AWAY: + temp = self._thrm.unoccupied_heating_setpoint + else: + temp = self._thrm.occupied_heating_setpoint + + if temp is None: + return temp + return round(temp / ZCL_TEMP, 1) + + @property + def temperature_unit(self): + """Return the unit of measurement used by the platform.""" + return TEMP_CELSIUS + + @property + def max_temp(self) -> float: + """Return the maximum temperature.""" + temps = [] + if HVAC_MODE_HEAT in self.hvac_modes: + temps.append(self._thrm.max_heat_setpoint_limit) + if HVAC_MODE_COOL in self.hvac_modes: + temps.append(self._thrm.max_cool_setpoint_limit) + + if not temps: + return self.DEFAULT_MAX_TEMP + return round(max(temps) / ZCL_TEMP, 1) + + @property + def min_temp(self) -> float: + """Return the minimum temperature.""" + temps = [] + if HVAC_MODE_HEAT in self.hvac_modes: + temps.append(self._thrm.min_heat_setpoint_limit) + if HVAC_MODE_COOL in self.hvac_modes: + temps.append(self._thrm.min_cool_setpoint_limit) + + if not temps: + return self.DEFAULT_MIN_TEMP + return round(min(temps) / ZCL_TEMP, 1) + + async def async_added_to_hass(self): + """Run when about to be added to hass.""" + await super().async_added_to_hass() + await self.async_accept_signal( + self._thrm, SIGNAL_ATTR_UPDATED, self.async_attribute_updated + ) + + async def async_attribute_updated(self, record): + """Handle attribute update from device.""" + if ( + record.attr_name in (ATTR_OCCP_COOL_SETPT, ATTR_OCCP_HEAT_SETPT) + and self.preset_mode == PRESET_AWAY + ): + # occupancy attribute is an unreportable attribute, but if we get + # an attribute update for an "occupied" setpoint, there's a chance + # occupancy has changed + occupancy = await self._thrm.get_occupancy() + if occupancy is True: + self._preset = PRESET_NONE + + self.async_write_ha_state() + + async def async_set_fan_mode(self, fan_mode: str) -> None: + """Set fan mode.""" + if self.fan_modes is None: + self.warning("Fan is not supported") + return + + if fan_mode not in self.fan_modes: + self.warning("Unsupported '%s' fan mode", fan_mode) + return + + if fan_mode == FAN_ON: + mode = ThermostatFanMode.ON + else: + mode = ThermostatFanMode.AUTO + + await self._fan.async_set_speed(mode) + + async def async_set_hvac_mode(self, hvac_mode: str) -> None: + """Set new target operation mode.""" + if hvac_mode not in self.hvac_modes: + self.warning( + "can't set '%s' mode. Supported modes are: %s", + hvac_mode, + self.hvac_modes, + ) + return + + if await self._thrm.async_set_operation_mode(HVAC_MODE_2_SYSTEM[hvac_mode]): + self.async_write_ha_state() + + async def async_set_preset_mode(self, preset_mode: str) -> None: + """Set new preset mode.""" + if preset_mode not in self.preset_modes: + self.debug("preset mode '%s' is not supported", preset_mode) + return + + if self.preset_mode not in (preset_mode, PRESET_NONE): + if not await self.async_preset_handler(self.preset_mode, enable=False): + self.debug("Couldn't turn off '%s' preset", self.preset_mode) + return + + if preset_mode != PRESET_NONE: + if not await self.async_preset_handler(preset_mode, enable=True): + self.debug("Couldn't turn on '%s' preset", preset_mode) + return + self._preset = preset_mode + self.async_write_ha_state() + + async def async_set_temperature(self, **kwargs): + """Set new target temperature.""" + low_temp = kwargs.get(ATTR_TARGET_TEMP_LOW) + high_temp = kwargs.get(ATTR_TARGET_TEMP_HIGH) + temp = kwargs.get(ATTR_TEMPERATURE) + hvac_mode = kwargs.get(ATTR_HVAC_MODE) + + if hvac_mode is not None: + await self.async_set_hvac_mode(hvac_mode) + + thrm = self._thrm + if self.hvac_mode == HVAC_MODE_HEAT_COOL: + success = True + if low_temp is not None: + low_temp = int(low_temp * ZCL_TEMP) + success = success and await thrm.async_set_heating_setpoint( + low_temp, self.preset_mode == PRESET_AWAY + ) + self.debug("Setting heating %s setpoint: %s", low_temp, success) + if high_temp is not None: + high_temp = int(high_temp * ZCL_TEMP) + success = success and await thrm.async_set_cooling_setpoint( + high_temp, self.preset_mode == PRESET_AWAY + ) + self.debug("Setting cooling %s setpoint: %s", low_temp, success) + elif temp is not None: + temp = int(temp * ZCL_TEMP) + if self.hvac_mode == HVAC_MODE_COOL: + success = await thrm.async_set_cooling_setpoint( + temp, self.preset_mode == PRESET_AWAY + ) + elif self.hvac_mode == HVAC_MODE_HEAT: + success = await thrm.async_set_heating_setpoint( + temp, self.preset_mode == PRESET_AWAY + ) + else: + self.debug("Not setting temperature for '%s' mode", self.hvac_mode) + return + else: + self.debug("incorrect %s setting for '%s' mode", kwargs, self.hvac_mode) + return + + if success: + self.async_write_ha_state() + + async def async_preset_handler(self, preset: str, enable: bool = False) -> bool: + """Set the preset mode via handler.""" + + handler = getattr(self, f"async_preset_handler_{preset}") + return await handler(enable) + + +@STRICT_MATCH( + channel_names={CHANNEL_THERMOSTAT, "sinope_manufacturer_specific"}, + manufacturers="Sinope Technologies", +) +class SinopeTechnologiesThermostat(Thermostat): + """Sinope Technologies Thermostat.""" + + manufacturer = 0x119C + update_time_interval = timedelta(minutes=randint(45, 75)) + + def __init__(self, unique_id, zha_device, channels, **kwargs): + """Initialize ZHA Thermostat instance.""" + super().__init__(unique_id, zha_device, channels, **kwargs) + self._presets = [PRESET_AWAY, PRESET_NONE] + self._supported_flags |= SUPPORT_PRESET_MODE + self._manufacturer_ch = self.cluster_channels["sinope_manufacturer_specific"] + + @callback + def _async_update_time(self, timestamp=None) -> None: + """Update thermostat's time display.""" + + secs_2k = ( + dt_util.now().replace(tzinfo=None) - datetime(2000, 1, 1, 0, 0, 0, 0) + ).total_seconds() + + self.debug("Updating time: %s", secs_2k) + self._manufacturer_ch.cluster.create_catching_task( + self._manufacturer_ch.cluster.write_attributes( + {"secs_since_2k": secs_2k}, manufacturer=self.manufacturer + ) + ) + + async def async_added_to_hass(self): + """Run when about to be added to Hass.""" + await super().async_added_to_hass() + async_track_time_interval( + self.hass, self._async_update_time, self.update_time_interval + ) + self._async_update_time() + + async def async_preset_handler_away(self, is_away: bool = False) -> bool: + """Set occupancy.""" + mfg_code = self._zha_device.manufacturer_code + res = await self._thrm.write_attributes( + {"set_occupancy": 0 if is_away else 1}, manufacturer=mfg_code + ) + + self.debug("set occupancy to %s. Status: %s", 0 if is_away else 1, res) + return res diff --git a/homeassistant/components/zha/core/channels/hvac.py b/homeassistant/components/zha/core/channels/hvac.py index 3c58ff946b9..56345916cd3 100644 --- a/homeassistant/components/zha/core/channels/hvac.py +++ b/homeassistant/components/zha/core/channels/hvac.py @@ -1,17 +1,37 @@ -"""HVAC channels module for Zigbee Home Automation.""" +""" +HVAC channels module for Zigbee Home Automation. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/integrations/zha/ +""" +import asyncio +from collections import namedtuple import logging +from typing import Any, Dict, List, Optional, Tuple, Union from zigpy.exceptions import ZigbeeException import zigpy.zcl.clusters.hvac as hvac +from zigpy.zcl.foundation import Status from homeassistant.core import callback -from .. import registries -from ..const import REPORT_CONFIG_OP, SIGNAL_ATTR_UPDATED +from .. import registries, typing as zha_typing +from ..const import ( + REPORT_CONFIG_MAX_INT, + REPORT_CONFIG_MIN_INT, + REPORT_CONFIG_OP, + SIGNAL_ATTR_UPDATED, +) +from ..helpers import retryable_req from .base import ZigbeeChannel _LOGGER = logging.getLogger(__name__) +AttributeUpdateRecord = namedtuple("AttributeUpdateRecord", "attr_id, attr_name, value") +REPORT_CONFIG_CLIMATE = (REPORT_CONFIG_MIN_INT, REPORT_CONFIG_MAX_INT, 25) +REPORT_CONFIG_CLIMATE_DEMAND = (REPORT_CONFIG_MIN_INT, REPORT_CONFIG_MAX_INT, 5) +REPORT_CONFIG_CLIMATE_DISCRETE = (REPORT_CONFIG_MIN_INT, REPORT_CONFIG_MAX_INT, 1) + @registries.ZIGBEE_CHANNEL_REGISTRY.register(hvac.Dehumidification.cluster_id) class Dehumidification(ZigbeeChannel): @@ -26,6 +46,18 @@ class FanChannel(ZigbeeChannel): REPORT_CONFIG = ({"attr": "fan_mode", "config": REPORT_CONFIG_OP},) + def __init__( + self, cluster: zha_typing.ZigpyClusterType, ch_pool: zha_typing.ChannelPoolType + ): + """Init Thermostat channel instance.""" + super().__init__(cluster, ch_pool) + self._fan_mode = None + + @property + def fan_mode(self) -> Optional[int]: + """Return current fan mode.""" + return self._fan_mode + async def async_set_speed(self, value) -> None: """Set the speed of the fan.""" @@ -35,41 +67,390 @@ class FanChannel(ZigbeeChannel): self.error("Could not set speed: %s", ex) return - async def async_update(self): + async def async_update(self) -> None: """Retrieve latest state.""" result = await self.get_attribute_value("fan_mode", from_cache=True) if result is not None: + self._fan_mode = result self.async_send_signal( f"{self.unique_id}_{SIGNAL_ATTR_UPDATED}", 0, "fan_mode", result ) @callback - def attribute_updated(self, attrid, value): + def attribute_updated(self, attrid: int, value: Any) -> None: """Handle attribute update from fan cluster.""" attr_name = self.cluster.attributes.get(attrid, [attrid])[0] self.debug( "Attribute report '%s'[%s] = %s", self.cluster.name, attr_name, value ) if attrid == self._value_attribute: + self._fan_mode = value self.async_send_signal( f"{self.unique_id}_{SIGNAL_ATTR_UPDATED}", attrid, attr_name, value ) - async def async_initialize(self, from_cache): - """Initialize channel.""" - await self.get_attribute_value(self._value_attribute, from_cache=from_cache) - await super().async_initialize(from_cache) - @registries.ZIGBEE_CHANNEL_REGISTRY.register(hvac.Pump.cluster_id) class Pump(ZigbeeChannel): """Pump channel.""" +@registries.CLIMATE_CLUSTERS.register(hvac.Thermostat.cluster_id) @registries.ZIGBEE_CHANNEL_REGISTRY.register(hvac.Thermostat.cluster_id) -class Thermostat(ZigbeeChannel): +class ThermostatChannel(ZigbeeChannel): """Thermostat channel.""" + def __init__( + self, cluster: zha_typing.ZigpyClusterType, ch_pool: zha_typing.ChannelPoolType + ) -> None: + """Init Thermostat channel instance.""" + super().__init__(cluster, ch_pool) + self._init_attrs = { + "abs_min_heat_setpoint_limit": True, + "abs_max_heat_setpoint_limit": True, + "abs_min_cool_setpoint_limit": True, + "abs_max_cool_setpoint_limit": True, + "ctrl_seqe_of_oper": False, + "local_temp": False, + "max_cool_setpoint_limit": True, + "max_heat_setpoint_limit": True, + "min_cool_setpoint_limit": True, + "min_heat_setpoint_limit": True, + "occupancy": False, + "occupied_cooling_setpoint": False, + "occupied_heating_setpoint": False, + "pi_cooling_demand": False, + "pi_heating_demand": False, + "running_mode": False, + "running_state": False, + "system_mode": False, + "unoccupied_heating_setpoint": False, + "unoccupied_cooling_setpoint": False, + } + self._abs_max_cool_setpoint_limit = 3200 # 32C + self._abs_min_cool_setpoint_limit = 1600 # 16C + self._ctrl_seqe_of_oper = 0xFF + self._abs_max_heat_setpoint_limit = 3000 # 30C + self._abs_min_heat_setpoint_limit = 700 # 7C + self._running_mode = None + self._max_cool_setpoint_limit = None + self._max_heat_setpoint_limit = None + self._min_cool_setpoint_limit = None + self._min_heat_setpoint_limit = None + self._local_temp = None + self._occupancy = None + self._occupied_cooling_setpoint = None + self._occupied_heating_setpoint = None + self._pi_cooling_demand = None + self._pi_heating_demand = None + self._running_state = None + self._system_mode = None + self._unoccupied_cooling_setpoint = None + self._unoccupied_heating_setpoint = None + self._report_config = [ + {"attr": "local_temp", "config": REPORT_CONFIG_CLIMATE}, + {"attr": "occupied_cooling_setpoint", "config": REPORT_CONFIG_CLIMATE}, + {"attr": "occupied_heating_setpoint", "config": REPORT_CONFIG_CLIMATE}, + {"attr": "unoccupied_cooling_setpoint", "config": REPORT_CONFIG_CLIMATE}, + {"attr": "unoccupied_heating_setpoint", "config": REPORT_CONFIG_CLIMATE}, + {"attr": "running_mode", "config": REPORT_CONFIG_CLIMATE}, + {"attr": "running_state", "config": REPORT_CONFIG_CLIMATE_DEMAND}, + {"attr": "system_mode", "config": REPORT_CONFIG_CLIMATE}, + {"attr": "occupancy", "config": REPORT_CONFIG_CLIMATE_DISCRETE}, + {"attr": "pi_cooling_demand", "config": REPORT_CONFIG_CLIMATE_DEMAND}, + {"attr": "pi_heating_demand", "config": REPORT_CONFIG_CLIMATE_DEMAND}, + ] + + @property + def abs_max_cool_setpoint_limit(self) -> int: + """Absolute maximum cooling setpoint.""" + return self._abs_max_cool_setpoint_limit + + @property + def abs_min_cool_setpoint_limit(self) -> int: + """Absolute minimum cooling setpoint.""" + return self._abs_min_cool_setpoint_limit + + @property + def abs_max_heat_setpoint_limit(self) -> int: + """Absolute maximum heating setpoint.""" + return self._abs_max_heat_setpoint_limit + + @property + def abs_min_heat_setpoint_limit(self) -> int: + """Absolute minimum heating setpoint.""" + return self._abs_min_heat_setpoint_limit + + @property + def ctrl_seqe_of_oper(self) -> int: + """Control Sequence of operations attribute.""" + return self._ctrl_seqe_of_oper + + @property + def max_cool_setpoint_limit(self) -> int: + """Maximum cooling setpoint.""" + if self._max_cool_setpoint_limit is None: + return self.abs_max_cool_setpoint_limit + return self._max_cool_setpoint_limit + + @property + def min_cool_setpoint_limit(self) -> int: + """Minimum cooling setpoint.""" + if self._min_cool_setpoint_limit is None: + return self.abs_min_cool_setpoint_limit + return self._min_cool_setpoint_limit + + @property + def max_heat_setpoint_limit(self) -> int: + """Maximum heating setpoint.""" + if self._max_heat_setpoint_limit is None: + return self.abs_max_heat_setpoint_limit + return self._max_heat_setpoint_limit + + @property + def min_heat_setpoint_limit(self) -> int: + """Minimum heating setpoint.""" + if self._min_heat_setpoint_limit is None: + return self.abs_min_heat_setpoint_limit + return self._min_heat_setpoint_limit + + @property + def local_temp(self) -> Optional[int]: + """Thermostat temperature.""" + return self._local_temp + + @property + def occupancy(self) -> Optional[int]: + """Is occupancy detected.""" + return self._occupancy + + @property + def occupied_cooling_setpoint(self) -> Optional[int]: + """Temperature when room is occupied.""" + return self._occupied_cooling_setpoint + + @property + def occupied_heating_setpoint(self) -> Optional[int]: + """Temperature when room is occupied.""" + return self._occupied_heating_setpoint + + @property + def pi_cooling_demand(self) -> int: + """Cooling demand.""" + return self._pi_cooling_demand + + @property + def pi_heating_demand(self) -> int: + """Heating demand.""" + return self._pi_heating_demand + + @property + def running_mode(self) -> Optional[int]: + """Thermostat running mode.""" + return self._running_mode + + @property + def running_state(self) -> Optional[int]: + """Thermostat running state, state of heat, cool, fan relays.""" + return self._running_state + + @property + def system_mode(self) -> Optional[int]: + """System mode.""" + return self._system_mode + + @property + def unoccupied_cooling_setpoint(self) -> Optional[int]: + """Temperature when room is not occupied.""" + return self._unoccupied_cooling_setpoint + + @property + def unoccupied_heating_setpoint(self) -> Optional[int]: + """Temperature when room is not occupied.""" + return self._unoccupied_heating_setpoint + + @callback + def attribute_updated(self, attrid, value): + """Handle attribute update cluster.""" + attr_name = self.cluster.attributes.get(attrid, [attrid])[0] + self.debug( + "Attribute report '%s'[%s] = %s", self.cluster.name, attr_name, value + ) + setattr(self, f"_{attr_name}", value) + self.async_send_signal( + f"{self.unique_id}_{SIGNAL_ATTR_UPDATED}", + AttributeUpdateRecord(attrid, attr_name, value), + ) + + async def _chunk_attr_read(self, attrs, cached=False): + chunk, attrs = attrs[:4], attrs[4:] + while chunk: + res, fail = await self.cluster.read_attributes(chunk, allow_cache=cached) + self.debug("read attributes: Success: %s. Failed: %s", res, fail) + for attr in chunk: + self._init_attrs.pop(attr, None) + if attr in fail: + continue + if isinstance(attr, str): + setattr(self, f"_{attr}", res[attr]) + self.async_send_signal( + f"{self.unique_id}_{SIGNAL_ATTR_UPDATED}", + AttributeUpdateRecord(None, attr, res[attr]), + ) + + chunk, attrs = attrs[:4], attrs[4:] + + async def configure_reporting(self): + """Configure attribute reporting for a cluster. + + This also swallows DeliveryError exceptions that are thrown when + devices are unreachable. + """ + kwargs = {} + if self.cluster.cluster_id >= 0xFC00 and self._ch_pool.manufacturer_code: + kwargs["manufacturer"] = self._ch_pool.manufacturer_code + + chunk, rest = self._report_config[:4], self._report_config[4:] + while chunk: + attrs = {record["attr"]: record["config"] for record in chunk} + try: + res = await self.cluster.configure_reporting_multiple(attrs, **kwargs) + self._configure_reporting_status(attrs, res[0]) + except (ZigbeeException, asyncio.TimeoutError) as ex: + self.debug( + "failed to set reporting on '%s' cluster for: %s", + self.cluster.ep_attribute, + str(ex), + ) + break + chunk, rest = rest[:4], rest[4:] + + def _configure_reporting_status( + self, attrs: Dict[Union[int, str], Tuple], res: Union[List, Tuple] + ) -> None: + """Parse configure reporting result.""" + if not isinstance(res, list): + # assume default response + self.debug( + "attr reporting for '%s' on '%s': %s", attrs, self.name, res, + ) + return + if res[0].status == Status.SUCCESS and len(res) == 1: + self.debug( + "Successfully configured reporting for '%s' on '%s' cluster: %s", + attrs, + self.name, + res, + ) + return + + failed = [ + self.cluster.attributes.get(r.attrid, [r.attrid])[0] + for r in res + if r.status != Status.SUCCESS + ] + attrs = {self.cluster.attributes.get(r, [r])[0] for r in attrs} + self.debug( + "Successfully configured reporting for '%s' on '%s' cluster", + attrs - set(failed), + self.name, + ) + self.debug( + "Failed to configure reporting for '%s' on '%s' cluster: %s", + failed, + self.name, + res, + ) + + @retryable_req(delays=(1, 1, 3)) + async def async_initialize(self, from_cache): + """Initialize channel.""" + + cached = [a for a, cached in self._init_attrs.items() if cached] + uncached = [a for a, cached in self._init_attrs.items() if not cached] + + await self._chunk_attr_read(cached, cached=True) + await self._chunk_attr_read(uncached, cached=False) + await super().async_initialize(from_cache) + + async def async_set_operation_mode(self, mode) -> bool: + """Set Operation mode.""" + if not await self.write_attributes({"system_mode": mode}): + self.debug("couldn't set '%s' operation mode", mode) + return False + + self._system_mode = mode + self.debug("set system to %s", mode) + return True + + async def async_set_heating_setpoint( + self, temperature: int, is_away: bool = False + ) -> bool: + """Set heating setpoint.""" + if is_away: + data = {"unoccupied_heating_setpoint": temperature} + else: + data = {"occupied_heating_setpoint": temperature} + if not await self.write_attributes(data): + self.debug("couldn't set heating setpoint") + return False + + if is_away: + self._unoccupied_heating_setpoint = temperature + else: + self._occupied_heating_setpoint = temperature + self.debug("set heating setpoint to %s", temperature) + return True + + async def async_set_cooling_setpoint( + self, temperature: int, is_away: bool = False + ) -> bool: + """Set cooling setpoint.""" + if is_away: + data = {"unoccupied_cooling_setpoint": temperature} + else: + data = {"occupied_cooling_setpoint": temperature} + if not await self.write_attributes(data): + self.debug("couldn't set cooling setpoint") + return False + if is_away: + self._unoccupied_cooling_setpoint = temperature + else: + self._occupied_cooling_setpoint = temperature + self.debug("set cooling setpoint to %s", temperature) + return True + + async def get_occupancy(self) -> Optional[bool]: + """Get unreportable occupancy attribute.""" + try: + res, fail = await self.cluster.read_attributes(["occupancy"]) + self.debug("read 'occupancy' attr, success: %s, fail: %s", res, fail) + if "occupancy" not in res: + return None + self._occupancy = res["occupancy"] + return bool(self.occupancy) + except ZigbeeException as ex: + self.debug("Couldn't read 'occupancy' attribute: %s", ex) + + async def write_attributes(self, data, **kwargs): + """Write attributes helper.""" + try: + res = await self.cluster.write_attributes(data, **kwargs) + except ZigbeeException as exc: + self.debug("couldn't write %s: %s", data, exc) + return False + + self.debug("wrote %s attrs, Status: %s", data, res) + return self.check_result(res) + + @staticmethod + def check_result(res: list) -> bool: + """Normalize the result.""" + if not isinstance(res, list): + return False + + return all([record.status == Status.SUCCESS for record in res[0]]) + @registries.ZIGBEE_CHANNEL_REGISTRY.register(hvac.UserInterface.cluster_id) class UserInterface(ZigbeeChannel): diff --git a/homeassistant/components/zha/core/const.py b/homeassistant/components/zha/core/const.py index 08252ab1c97..6bc93354af7 100644 --- a/homeassistant/components/zha/core/const.py +++ b/homeassistant/components/zha/core/const.py @@ -11,6 +11,7 @@ import zigpy_xbee.zigbee.application import zigpy_zigate.zigbee.application from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR +from homeassistant.components.climate import DOMAIN as CLIMATE from homeassistant.components.cover import DOMAIN as COVER from homeassistant.components.device_tracker import DOMAIN as DEVICE_TRACKER from homeassistant.components.fan import DOMAIN as FAN @@ -87,6 +88,7 @@ CHANNEL_POWER_CONFIGURATION = "power" CHANNEL_PRESSURE = "pressure" CHANNEL_SMARTENERGY_METERING = "smartenergy_metering" CHANNEL_TEMPERATURE = "temperature" +CHANNEL_THERMOSTAT = "thermostat" CHANNEL_ZDO = "zdo" CHANNEL_ZONE = ZONE = "ias_zone" @@ -96,7 +98,17 @@ CLUSTER_COMMANDS_SERVER = "server_commands" CLUSTER_TYPE_IN = "in" CLUSTER_TYPE_OUT = "out" -COMPONENTS = (BINARY_SENSOR, COVER, DEVICE_TRACKER, FAN, LIGHT, LOCK, SENSOR, SWITCH) +COMPONENTS = ( + BINARY_SENSOR, + CLIMATE, + COVER, + DEVICE_TRACKER, + FAN, + LIGHT, + LOCK, + SENSOR, + SWITCH, +) CONF_BAUDRATE = "baudrate" CONF_DATABASE = "database_path" diff --git a/homeassistant/components/zha/core/discovery.py b/homeassistant/components/zha/core/discovery.py index f72ac2161ec..25f320b0bf1 100644 --- a/homeassistant/components/zha/core/discovery.py +++ b/homeassistant/components/zha/core/discovery.py @@ -16,6 +16,7 @@ from homeassistant.helpers.typing import HomeAssistantType from . import const as zha_const, registries as zha_regs, typing as zha_typing from .. import ( # noqa: F401 pylint: disable=unused-import, binary_sensor, + climate, cover, device_tracker, fan, diff --git a/homeassistant/components/zha/core/helpers.py b/homeassistant/components/zha/core/helpers.py index bb8a202e789..7813c7133ad 100644 --- a/homeassistant/components/zha/core/helpers.py +++ b/homeassistant/components/zha/core/helpers.py @@ -1,8 +1,19 @@ -"""Helpers for Zigbee Home Automation.""" +""" +Helpers for Zigbee Home Automation. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/integrations/zha/ +""" + +import asyncio import collections +import functools +import itertools import logging +from random import uniform from typing import Any, Callable, Iterator, List, Optional +import zigpy.exceptions import zigpy.types from homeassistant.core import State, callback @@ -147,3 +158,50 @@ class LogMixin: def error(self, msg, *args): """Error level log.""" return self.log(logging.ERROR, msg, *args) + + +def retryable_req( + delays=(1, 5, 10, 15, 30, 60, 120, 180, 360, 600, 900, 1800), raise_=False +): + """Make a method with ZCL requests retryable. + + This adds delays keyword argument to function. + len(delays) is number of tries. + raise_ if the final attempt should raise the exception. + """ + + def decorator(func): + @functools.wraps(func) + async def wrapper(channel, *args, **kwargs): + + exceptions = (zigpy.exceptions.ZigbeeException, asyncio.TimeoutError) + try_count, errors = 1, [] + for delay in itertools.chain(delays, [None]): + try: + return await func(channel, *args, **kwargs) + except exceptions as ex: + errors.append(ex) + if delay: + delay = uniform(delay * 0.75, delay * 1.25) + channel.debug( + ( + "%s: retryable request #%d failed: %s. " + "Retrying in %ss" + ), + func.__name__, + try_count, + ex, + round(delay, 1), + ) + try_count += 1 + await asyncio.sleep(delay) + else: + channel.warning( + "%s: all attempts have failed: %s", func.__name__, errors + ) + if raise_: + raise + + return wrapper + + return decorator diff --git a/homeassistant/components/zha/core/registries.py b/homeassistant/components/zha/core/registries.py index 6ddf48de5aa..4b68b9675a9 100644 --- a/homeassistant/components/zha/core/registries.py +++ b/homeassistant/components/zha/core/registries.py @@ -8,6 +8,7 @@ import zigpy.profiles.zll import zigpy.zcl as zcl from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR +from homeassistant.components.climate import DOMAIN as CLIMATE from homeassistant.components.cover import DOMAIN as COVER from homeassistant.components.device_tracker import DOMAIN as DEVICE_TRACKER from homeassistant.components.fan import DOMAIN as FAN @@ -84,11 +85,13 @@ BINARY_SENSOR_CLUSTERS.add(SMARTTHINGS_ACCELERATION_CLUSTER) BINDABLE_CLUSTERS = SetRegistry() CHANNEL_ONLY_CLUSTERS = SetRegistry() +CLIMATE_CLUSTERS = SetRegistry() CUSTOM_CLUSTER_MAPPINGS = {} DEVICE_CLASS = { zigpy.profiles.zha.PROFILE_ID: { SMARTTHINGS_ARRIVAL_SENSOR_DEVICE_TYPE: DEVICE_TRACKER, + zigpy.profiles.zha.DeviceType.THERMOSTAT: CLIMATE, zigpy.profiles.zha.DeviceType.COLOR_DIMMABLE_LIGHT: LIGHT, zigpy.profiles.zha.DeviceType.COLOR_TEMPERATURE_LIGHT: LIGHT, zigpy.profiles.zha.DeviceType.DIMMABLE_BALLAST: LIGHT, @@ -120,6 +123,7 @@ CLIENT_CHANNELS_REGISTRY = DictRegistry() COMPONENT_CLUSTERS = { BINARY_SENSOR: BINARY_SENSOR_CLUSTERS, + CLIMATE: CLIMATE_CLUSTERS, DEVICE_TRACKER: DEVICE_TRACKER_CLUSTERS, LIGHT: LIGHT_CLUSTERS, SWITCH: SWITCH_CLUSTERS, diff --git a/tests/components/zha/common.py b/tests/components/zha/common.py index f10ee25018f..fd5621137ae 100644 --- a/tests/components/zha/common.py +++ b/tests/components/zha/common.py @@ -75,7 +75,9 @@ def patch_cluster(cluster): cluster.read_attributes = AsyncMock(return_value=[{}, {}]) cluster.read_attributes_raw = Mock() cluster.unbind = AsyncMock(return_value=[0]) - cluster.write_attributes = AsyncMock(return_value=[0]) + cluster.write_attributes = AsyncMock( + return_value=[zcl_f.WriteAttributesResponse.deserialize(b"\x00")[0]] + ) if cluster.cluster_id == 4: cluster.add = AsyncMock(return_value=[0]) diff --git a/tests/components/zha/test_climate.py b/tests/components/zha/test_climate.py new file mode 100644 index 00000000000..81050dc63fb --- /dev/null +++ b/tests/components/zha/test_climate.py @@ -0,0 +1,1060 @@ +"""Test zha climate.""" +import logging + +import pytest +import zigpy.zcl.clusters +from zigpy.zcl.clusters.hvac import Thermostat +import zigpy.zcl.foundation as zcl_f + +from homeassistant.components.climate.const import ( + ATTR_CURRENT_TEMPERATURE, + ATTR_FAN_MODE, + ATTR_FAN_MODES, + ATTR_HVAC_ACTION, + ATTR_HVAC_MODE, + ATTR_HVAC_MODES, + ATTR_PRESET_MODE, + ATTR_TARGET_TEMP_HIGH, + ATTR_TARGET_TEMP_LOW, + CURRENT_HVAC_COOL, + CURRENT_HVAC_FAN, + CURRENT_HVAC_HEAT, + CURRENT_HVAC_IDLE, + CURRENT_HVAC_OFF, + FAN_AUTO, + FAN_LOW, + FAN_ON, + HVAC_MODE_AUTO, + HVAC_MODE_COOL, + HVAC_MODE_DRY, + HVAC_MODE_FAN_ONLY, + HVAC_MODE_HEAT, + HVAC_MODE_HEAT_COOL, + HVAC_MODE_OFF, + PRESET_AWAY, + PRESET_NONE, + SERVICE_SET_FAN_MODE, + SERVICE_SET_HVAC_MODE, + SERVICE_SET_PRESET_MODE, + SERVICE_SET_TEMPERATURE, +) +from homeassistant.components.zha.climate import ( + DOMAIN, + HVAC_MODE_2_SYSTEM, + SEQ_OF_OPERATION, +) +from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE, STATE_UNKNOWN + +from .common import async_enable_traffic, find_entity_id, send_attributes_report + +from tests.async_mock import patch + +CLIMATE = { + 1: { + "device_type": zigpy.profiles.zha.DeviceType.THERMOSTAT, + "in_clusters": [ + zigpy.zcl.clusters.general.Basic.cluster_id, + zigpy.zcl.clusters.general.Identify.cluster_id, + zigpy.zcl.clusters.hvac.Thermostat.cluster_id, + zigpy.zcl.clusters.hvac.UserInterface.cluster_id, + ], + "out_clusters": [zigpy.zcl.clusters.general.Ota.cluster_id], + } +} + +CLIMATE_FAN = { + 1: { + "device_type": zigpy.profiles.zha.DeviceType.THERMOSTAT, + "in_clusters": [ + zigpy.zcl.clusters.general.Basic.cluster_id, + zigpy.zcl.clusters.general.Identify.cluster_id, + zigpy.zcl.clusters.hvac.Fan.cluster_id, + zigpy.zcl.clusters.hvac.Thermostat.cluster_id, + zigpy.zcl.clusters.hvac.UserInterface.cluster_id, + ], + "out_clusters": [zigpy.zcl.clusters.general.Ota.cluster_id], + } +} + +CLIMATE_SINOPE = { + 1: { + "device_type": zigpy.profiles.zha.DeviceType.THERMOSTAT, + "in_clusters": [ + zigpy.zcl.clusters.general.Basic.cluster_id, + zigpy.zcl.clusters.general.Identify.cluster_id, + zigpy.zcl.clusters.hvac.Thermostat.cluster_id, + zigpy.zcl.clusters.hvac.UserInterface.cluster_id, + 65281, + ], + "out_clusters": [zigpy.zcl.clusters.general.Ota.cluster_id, 65281], + "profile_id": 260, + }, +} +SINOPE = "Sinope Technologies" + +ZCL_ATTR_PLUG = { + "abs_min_heat_setpoint_limit": 800, + "abs_max_heat_setpoint_limit": 3000, + "abs_min_cool_setpoint_limit": 2000, + "abs_max_cool_setpoint_limit": 4000, + "ctrl_seqe_of_oper": Thermostat.ControlSequenceOfOperation.Cooling_and_Heating, + "local_temp": None, + "max_cool_setpoint_limit": 3900, + "max_heat_setpoint_limit": 2900, + "min_cool_setpoint_limit": 2100, + "min_heat_setpoint_limit": 700, + "occupancy": 1, + "occupied_cooling_setpoint": 2500, + "occupied_heating_setpoint": 2200, + "pi_cooling_demand": None, + "pi_heating_demand": None, + "running_mode": Thermostat.RunningMode.Off, + "running_state": None, + "system_mode": Thermostat.SystemMode.Off, + "unoccupied_heating_setpoint": 2200, + "unoccupied_cooling_setpoint": 2300, +} + + +@pytest.fixture +def device_climate_mock(hass, zigpy_device_mock, zha_device_joined): + """Test regular thermostat device.""" + + async def _dev(clusters, plug=None, manuf=None): + if plug is None: + plugged_attrs = ZCL_ATTR_PLUG + else: + plugged_attrs = {**ZCL_ATTR_PLUG, **plug} + + async def _read_attr(attrs, *args, **kwargs): + res = {} + failed = {} + + for attr in attrs: + if attr in plugged_attrs: + res[attr] = plugged_attrs[attr] + else: + failed[attr] = zcl_f.Status.UNSUPPORTED_ATTRIBUTE + return res, failed + + zigpy_device = zigpy_device_mock(clusters, manufacturer=manuf) + zigpy_device.endpoints[1].thermostat.read_attributes.side_effect = _read_attr + zha_device = await zha_device_joined(zigpy_device) + await async_enable_traffic(hass, [zha_device]) + await hass.async_block_till_done() + return zha_device + + return _dev + + +@pytest.fixture +async def device_climate(device_climate_mock): + """Plain Climate device.""" + + return await device_climate_mock(CLIMATE) + + +@pytest.fixture +async def device_climate_fan(device_climate_mock): + """Test thermostat with fan device.""" + + return await device_climate_mock(CLIMATE_FAN) + + +@pytest.fixture +@patch.object( + zigpy.zcl.clusters.manufacturer_specific.ManufacturerSpecificCluster, + "ep_attribute", + "sinope_manufacturer_specific", +) +async def device_climate_sinope(device_climate_mock): + """Sinope thermostat.""" + + return await device_climate_mock(CLIMATE_SINOPE, manuf=SINOPE) + + +def test_sequence_mappings(): + """Test correct mapping between control sequence -> HVAC Mode -> Sysmode.""" + + for hvac_modes in SEQ_OF_OPERATION.values(): + for hvac_mode in hvac_modes: + assert hvac_mode in HVAC_MODE_2_SYSTEM + assert Thermostat.SystemMode(HVAC_MODE_2_SYSTEM[hvac_mode]) is not None + + +async def test_climate_local_temp(hass, device_climate): + """Test local temperature.""" + + thrm_cluster = device_climate.device.endpoints[1].thermostat + entity_id = await find_entity_id(DOMAIN, device_climate, hass) + + state = hass.states.get(entity_id) + assert state.attributes[ATTR_CURRENT_TEMPERATURE] is None + + await send_attributes_report(hass, thrm_cluster, {0: 2100}) + state = hass.states.get(entity_id) + assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 21.0 + + +async def test_climate_hvac_action_running_state(hass, device_climate): + """Test hvac action via running state.""" + + thrm_cluster = device_climate.device.endpoints[1].thermostat + entity_id = await find_entity_id(DOMAIN, device_climate, hass) + + state = hass.states.get(entity_id) + assert ATTR_HVAC_ACTION not in state.attributes + + await send_attributes_report( + hass, thrm_cluster, {0x0029: Thermostat.RunningState.Cool_2nd_Stage_On} + ) + state = hass.states.get(entity_id) + assert state.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_COOL + + await send_attributes_report( + hass, thrm_cluster, {0x0029: Thermostat.RunningState.Fan_State_On} + ) + state = hass.states.get(entity_id) + assert state.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_FAN + + await send_attributes_report( + hass, thrm_cluster, {0x0029: Thermostat.RunningState.Heat_2nd_Stage_On} + ) + state = hass.states.get(entity_id) + assert state.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_HEAT + + await send_attributes_report( + hass, thrm_cluster, {0x0029: Thermostat.RunningState.Fan_2nd_Stage_On} + ) + state = hass.states.get(entity_id) + assert state.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_FAN + + await send_attributes_report( + hass, thrm_cluster, {0x0029: Thermostat.RunningState.Cool_State_On} + ) + state = hass.states.get(entity_id) + assert state.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_COOL + + await send_attributes_report( + hass, thrm_cluster, {0x0029: Thermostat.RunningState.Fan_3rd_Stage_On} + ) + state = hass.states.get(entity_id) + assert state.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_FAN + + await send_attributes_report( + hass, thrm_cluster, {0x0029: Thermostat.RunningState.Heat_State_On} + ) + state = hass.states.get(entity_id) + assert state.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_HEAT + + await send_attributes_report( + hass, thrm_cluster, {0x0029: Thermostat.RunningState.Idle} + ) + state = hass.states.get(entity_id) + assert state.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_OFF + + await send_attributes_report( + hass, thrm_cluster, {0x001C: Thermostat.SystemMode.Heat} + ) + state = hass.states.get(entity_id) + assert state.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_IDLE + + +async def test_climate_hvac_action_pi_demand(hass, device_climate): + """Test hvac action based on pi_heating/cooling_demand attrs.""" + + thrm_cluster = device_climate.device.endpoints[1].thermostat + entity_id = await find_entity_id(DOMAIN, device_climate, hass) + + state = hass.states.get(entity_id) + assert ATTR_HVAC_ACTION not in state.attributes + + await send_attributes_report(hass, thrm_cluster, {0x0007: 10}) + state = hass.states.get(entity_id) + assert state.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_COOL + + await send_attributes_report(hass, thrm_cluster, {0x0008: 20}) + state = hass.states.get(entity_id) + assert state.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_HEAT + + await send_attributes_report(hass, thrm_cluster, {0x0007: 0}) + await send_attributes_report(hass, thrm_cluster, {0x0008: 0}) + + state = hass.states.get(entity_id) + assert state.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_OFF + + await send_attributes_report( + hass, thrm_cluster, {0x001C: Thermostat.SystemMode.Heat} + ) + state = hass.states.get(entity_id) + assert state.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_IDLE + + await send_attributes_report( + hass, thrm_cluster, {0x001C: Thermostat.SystemMode.Cool} + ) + state = hass.states.get(entity_id) + assert state.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_IDLE + + +@pytest.mark.parametrize( + "sys_mode, hvac_mode", + ( + (Thermostat.SystemMode.Auto, HVAC_MODE_HEAT_COOL), + (Thermostat.SystemMode.Cool, HVAC_MODE_COOL), + (Thermostat.SystemMode.Heat, HVAC_MODE_HEAT), + (Thermostat.SystemMode.Pre_cooling, HVAC_MODE_COOL), + (Thermostat.SystemMode.Fan_only, HVAC_MODE_FAN_ONLY), + (Thermostat.SystemMode.Dry, HVAC_MODE_DRY), + ), +) +async def test_hvac_mode(hass, device_climate, sys_mode, hvac_mode): + """Test HVAC modee.""" + + thrm_cluster = device_climate.device.endpoints[1].thermostat + entity_id = await find_entity_id(DOMAIN, device_climate, hass) + + state = hass.states.get(entity_id) + assert state.state == HVAC_MODE_OFF + + await send_attributes_report(hass, thrm_cluster, {0x001C: sys_mode}) + state = hass.states.get(entity_id) + assert state.state == hvac_mode + + await send_attributes_report( + hass, thrm_cluster, {0x001C: Thermostat.SystemMode.Off} + ) + state = hass.states.get(entity_id) + assert state.state == HVAC_MODE_OFF + + await send_attributes_report(hass, thrm_cluster, {0x001C: 0xFF}) + state = hass.states.get(entity_id) + assert state.state == STATE_UNKNOWN + + +@pytest.mark.parametrize( + "seq_of_op, modes", + ( + (0xFF, {HVAC_MODE_OFF}), + (0x00, {HVAC_MODE_OFF, HVAC_MODE_COOL}), + (0x01, {HVAC_MODE_OFF, HVAC_MODE_COOL}), + (0x02, {HVAC_MODE_OFF, HVAC_MODE_HEAT}), + (0x03, {HVAC_MODE_OFF, HVAC_MODE_HEAT}), + (0x04, {HVAC_MODE_OFF, HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_HEAT_COOL}), + (0x05, {HVAC_MODE_OFF, HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_HEAT_COOL}), + ), +) +async def test_hvac_modes(hass, device_climate_mock, seq_of_op, modes): + """Test HVAC modes from sequence of operations.""" + + device_climate = await device_climate_mock( + CLIMATE, {"ctrl_seqe_of_oper": seq_of_op} + ) + entity_id = await find_entity_id(DOMAIN, device_climate, hass) + state = hass.states.get(entity_id) + assert set(state.attributes[ATTR_HVAC_MODES]) == modes + + +@pytest.mark.parametrize( + "sys_mode, preset, target_temp", + ( + (Thermostat.SystemMode.Heat, None, 22), + (Thermostat.SystemMode.Heat, PRESET_AWAY, 16), + (Thermostat.SystemMode.Cool, None, 25), + (Thermostat.SystemMode.Cool, PRESET_AWAY, 27), + ), +) +async def test_target_temperature( + hass, device_climate_mock, sys_mode, preset, target_temp +): + """Test target temperature property.""" + + with patch.object( + zigpy.zcl.clusters.manufacturer_specific.ManufacturerSpecificCluster, + "ep_attribute", + "sinope_manufacturer_specific", + ): + device_climate = await device_climate_mock( + CLIMATE_SINOPE, + { + "occupied_cooling_setpoint": 2500, + "occupied_heating_setpoint": 2200, + "system_mode": sys_mode, + "unoccupied_heating_setpoint": 1600, + "unoccupied_cooling_setpoint": 2700, + }, + manuf=SINOPE, + ) + entity_id = await find_entity_id(DOMAIN, device_climate, hass) + if preset: + await hass.services.async_call( + DOMAIN, + SERVICE_SET_PRESET_MODE, + {ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: preset}, + blocking=True, + ) + + state = hass.states.get(entity_id) + assert state.attributes[ATTR_TEMPERATURE] == target_temp + + +@pytest.mark.parametrize( + "preset, unoccupied, target_temp", + ((None, 1800, 17), (PRESET_AWAY, 1800, 18), (PRESET_AWAY, None, None),), +) +async def test_target_temperature_high( + hass, device_climate_mock, preset, unoccupied, target_temp +): + """Test target temperature high property.""" + + with patch.object( + zigpy.zcl.clusters.manufacturer_specific.ManufacturerSpecificCluster, + "ep_attribute", + "sinope_manufacturer_specific", + ): + device_climate = await device_climate_mock( + CLIMATE_SINOPE, + { + "occupied_cooling_setpoint": 1700, + "system_mode": Thermostat.SystemMode.Auto, + "unoccupied_cooling_setpoint": unoccupied, + }, + manuf=SINOPE, + ) + entity_id = await find_entity_id(DOMAIN, device_climate, hass) + if preset: + await hass.services.async_call( + DOMAIN, + SERVICE_SET_PRESET_MODE, + {ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: preset}, + blocking=True, + ) + + state = hass.states.get(entity_id) + assert state.attributes[ATTR_TARGET_TEMP_HIGH] == target_temp + + +@pytest.mark.parametrize( + "preset, unoccupied, target_temp", + ((None, 1600, 21), (PRESET_AWAY, 1600, 16), (PRESET_AWAY, None, None),), +) +async def test_target_temperature_low( + hass, device_climate_mock, preset, unoccupied, target_temp +): + """Test target temperature low property.""" + + with patch.object( + zigpy.zcl.clusters.manufacturer_specific.ManufacturerSpecificCluster, + "ep_attribute", + "sinope_manufacturer_specific", + ): + device_climate = await device_climate_mock( + CLIMATE_SINOPE, + { + "occupied_heating_setpoint": 2100, + "system_mode": Thermostat.SystemMode.Auto, + "unoccupied_heating_setpoint": unoccupied, + }, + manuf=SINOPE, + ) + entity_id = await find_entity_id(DOMAIN, device_climate, hass) + if preset: + await hass.services.async_call( + DOMAIN, + SERVICE_SET_PRESET_MODE, + {ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: preset}, + blocking=True, + ) + + state = hass.states.get(entity_id) + assert state.attributes[ATTR_TARGET_TEMP_LOW] == target_temp + + +@pytest.mark.parametrize( + "hvac_mode, sys_mode", + ( + (HVAC_MODE_AUTO, None), + (HVAC_MODE_COOL, Thermostat.SystemMode.Cool), + (HVAC_MODE_DRY, None), + (HVAC_MODE_FAN_ONLY, None), + (HVAC_MODE_HEAT, Thermostat.SystemMode.Heat), + (HVAC_MODE_HEAT_COOL, Thermostat.SystemMode.Auto), + ), +) +async def test_set_hvac_mode(hass, device_climate, hvac_mode, sys_mode): + """Test setting hvac mode.""" + + thrm_cluster = device_climate.device.endpoints[1].thermostat + entity_id = await find_entity_id(DOMAIN, device_climate, hass) + + state = hass.states.get(entity_id) + assert state.state == HVAC_MODE_OFF + + await hass.services.async_call( + DOMAIN, + SERVICE_SET_HVAC_MODE, + {ATTR_ENTITY_ID: entity_id, ATTR_HVAC_MODE: hvac_mode}, + blocking=True, + ) + state = hass.states.get(entity_id) + if sys_mode is not None: + assert state.state == hvac_mode + assert thrm_cluster.write_attributes.call_count == 1 + assert thrm_cluster.write_attributes.call_args[0][0] == { + "system_mode": sys_mode + } + else: + assert thrm_cluster.write_attributes.call_count == 0 + assert state.state == HVAC_MODE_OFF + + # turn off + thrm_cluster.write_attributes.reset_mock() + await hass.services.async_call( + DOMAIN, + SERVICE_SET_HVAC_MODE, + {ATTR_ENTITY_ID: entity_id, ATTR_HVAC_MODE: HVAC_MODE_OFF}, + blocking=True, + ) + state = hass.states.get(entity_id) + assert state.state == HVAC_MODE_OFF + assert thrm_cluster.write_attributes.call_count == 1 + assert thrm_cluster.write_attributes.call_args[0][0] == { + "system_mode": Thermostat.SystemMode.Off + } + + +async def test_preset_setting(hass, device_climate_sinope): + """Test preset setting.""" + + entity_id = await find_entity_id(DOMAIN, device_climate_sinope, hass) + thrm_cluster = device_climate_sinope.device.endpoints[1].thermostat + + state = hass.states.get(entity_id) + assert state.attributes[ATTR_PRESET_MODE] == PRESET_NONE + + # unsuccessful occupancy change + thrm_cluster.write_attributes.return_value = [ + zcl_f.WriteAttributesResponse.deserialize(b"\x01\x00\x00")[0] + ] + + await hass.services.async_call( + DOMAIN, + SERVICE_SET_PRESET_MODE, + {ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: PRESET_AWAY}, + blocking=True, + ) + + state = hass.states.get(entity_id) + assert state.attributes[ATTR_PRESET_MODE] == PRESET_NONE + assert thrm_cluster.write_attributes.call_count == 1 + assert thrm_cluster.write_attributes.call_args[0][0] == {"set_occupancy": 0} + + # successful occupancy change + thrm_cluster.write_attributes.reset_mock() + thrm_cluster.write_attributes.return_value = [ + zcl_f.WriteAttributesResponse.deserialize(b"\x00")[0] + ] + await hass.services.async_call( + DOMAIN, + SERVICE_SET_PRESET_MODE, + {ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: PRESET_AWAY}, + blocking=True, + ) + + state = hass.states.get(entity_id) + assert state.attributes[ATTR_PRESET_MODE] == PRESET_AWAY + assert thrm_cluster.write_attributes.call_count == 1 + assert thrm_cluster.write_attributes.call_args[0][0] == {"set_occupancy": 0} + + # unsuccessful occupancy change + thrm_cluster.write_attributes.reset_mock() + thrm_cluster.write_attributes.return_value = [ + zcl_f.WriteAttributesResponse.deserialize(b"\x01\x01\x01")[0] + ] + await hass.services.async_call( + DOMAIN, + SERVICE_SET_PRESET_MODE, + {ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: PRESET_NONE}, + blocking=True, + ) + + state = hass.states.get(entity_id) + assert state.attributes[ATTR_PRESET_MODE] == PRESET_AWAY + assert thrm_cluster.write_attributes.call_count == 1 + assert thrm_cluster.write_attributes.call_args[0][0] == {"set_occupancy": 1} + + # successful occupancy change + thrm_cluster.write_attributes.reset_mock() + thrm_cluster.write_attributes.return_value = [ + zcl_f.WriteAttributesResponse.deserialize(b"\x00")[0] + ] + await hass.services.async_call( + DOMAIN, + SERVICE_SET_PRESET_MODE, + {ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: PRESET_NONE}, + blocking=True, + ) + + state = hass.states.get(entity_id) + assert state.attributes[ATTR_PRESET_MODE] == PRESET_NONE + assert thrm_cluster.write_attributes.call_count == 1 + assert thrm_cluster.write_attributes.call_args[0][0] == {"set_occupancy": 1} + + +async def test_preset_setting_invalid(hass, device_climate_sinope): + """Test invalid preset setting.""" + + entity_id = await find_entity_id(DOMAIN, device_climate_sinope, hass) + thrm_cluster = device_climate_sinope.device.endpoints[1].thermostat + + state = hass.states.get(entity_id) + assert state.attributes[ATTR_PRESET_MODE] == PRESET_NONE + + await hass.services.async_call( + DOMAIN, + SERVICE_SET_PRESET_MODE, + {ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: "invalid_preset"}, + blocking=True, + ) + + state = hass.states.get(entity_id) + assert state.attributes[ATTR_PRESET_MODE] == PRESET_NONE + assert thrm_cluster.write_attributes.call_count == 0 + + +async def test_set_temperature_hvac_mode(hass, device_climate): + """Test setting HVAC mode in temperature service call.""" + + entity_id = await find_entity_id(DOMAIN, device_climate, hass) + thrm_cluster = device_climate.device.endpoints[1].thermostat + + state = hass.states.get(entity_id) + assert state.state == HVAC_MODE_OFF + + await hass.services.async_call( + DOMAIN, + SERVICE_SET_TEMPERATURE, + { + ATTR_ENTITY_ID: entity_id, + ATTR_HVAC_MODE: HVAC_MODE_HEAT_COOL, + ATTR_TEMPERATURE: 20, + }, + blocking=True, + ) + + state = hass.states.get(entity_id) + assert state.state == HVAC_MODE_HEAT_COOL + assert thrm_cluster.write_attributes.await_count == 1 + assert thrm_cluster.write_attributes.call_args[0][0] == { + "system_mode": Thermostat.SystemMode.Auto + } + + +async def test_set_temperature_heat_cool(hass, device_climate_mock): + """Test setting temperature service call in heating/cooling HVAC mode.""" + + with patch.object( + zigpy.zcl.clusters.manufacturer_specific.ManufacturerSpecificCluster, + "ep_attribute", + "sinope_manufacturer_specific", + ): + device_climate = await device_climate_mock( + CLIMATE_SINOPE, + { + "occupied_cooling_setpoint": 2500, + "occupied_heating_setpoint": 2000, + "system_mode": Thermostat.SystemMode.Auto, + "unoccupied_heating_setpoint": 1600, + "unoccupied_cooling_setpoint": 2700, + }, + manuf=SINOPE, + ) + entity_id = await find_entity_id(DOMAIN, device_climate, hass) + thrm_cluster = device_climate.device.endpoints[1].thermostat + + state = hass.states.get(entity_id) + assert state.state == HVAC_MODE_HEAT_COOL + + await hass.services.async_call( + DOMAIN, + SERVICE_SET_TEMPERATURE, + {ATTR_ENTITY_ID: entity_id, ATTR_TEMPERATURE: 21}, + blocking=True, + ) + + state = hass.states.get(entity_id) + assert state.attributes[ATTR_TARGET_TEMP_LOW] == 20.0 + assert state.attributes[ATTR_TARGET_TEMP_HIGH] == 25.0 + assert thrm_cluster.write_attributes.await_count == 0 + + await hass.services.async_call( + DOMAIN, + SERVICE_SET_TEMPERATURE, + { + ATTR_ENTITY_ID: entity_id, + ATTR_TARGET_TEMP_HIGH: 26, + ATTR_TARGET_TEMP_LOW: 19, + }, + blocking=True, + ) + + state = hass.states.get(entity_id) + assert state.attributes[ATTR_TARGET_TEMP_LOW] == 19.0 + assert state.attributes[ATTR_TARGET_TEMP_HIGH] == 26.0 + assert thrm_cluster.write_attributes.await_count == 2 + assert thrm_cluster.write_attributes.call_args_list[0][0][0] == { + "occupied_heating_setpoint": 1900 + } + assert thrm_cluster.write_attributes.call_args_list[1][0][0] == { + "occupied_cooling_setpoint": 2600 + } + + await hass.services.async_call( + DOMAIN, + SERVICE_SET_PRESET_MODE, + {ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: PRESET_AWAY}, + blocking=True, + ) + thrm_cluster.write_attributes.reset_mock() + + await hass.services.async_call( + DOMAIN, + SERVICE_SET_TEMPERATURE, + { + ATTR_ENTITY_ID: entity_id, + ATTR_TARGET_TEMP_HIGH: 30, + ATTR_TARGET_TEMP_LOW: 15, + }, + blocking=True, + ) + + state = hass.states.get(entity_id) + assert state.attributes[ATTR_TARGET_TEMP_LOW] == 15.0 + assert state.attributes[ATTR_TARGET_TEMP_HIGH] == 30.0 + assert thrm_cluster.write_attributes.await_count == 2 + assert thrm_cluster.write_attributes.call_args_list[0][0][0] == { + "unoccupied_heating_setpoint": 1500 + } + assert thrm_cluster.write_attributes.call_args_list[1][0][0] == { + "unoccupied_cooling_setpoint": 3000 + } + + +async def test_set_temperature_heat(hass, device_climate_mock): + """Test setting temperature service call in heating HVAC mode.""" + + with patch.object( + zigpy.zcl.clusters.manufacturer_specific.ManufacturerSpecificCluster, + "ep_attribute", + "sinope_manufacturer_specific", + ): + device_climate = await device_climate_mock( + CLIMATE_SINOPE, + { + "occupied_cooling_setpoint": 2500, + "occupied_heating_setpoint": 2000, + "system_mode": Thermostat.SystemMode.Heat, + "unoccupied_heating_setpoint": 1600, + "unoccupied_cooling_setpoint": 2700, + }, + manuf=SINOPE, + ) + entity_id = await find_entity_id(DOMAIN, device_climate, hass) + thrm_cluster = device_climate.device.endpoints[1].thermostat + + state = hass.states.get(entity_id) + assert state.state == HVAC_MODE_HEAT + + await hass.services.async_call( + DOMAIN, + SERVICE_SET_TEMPERATURE, + { + ATTR_ENTITY_ID: entity_id, + ATTR_TARGET_TEMP_HIGH: 30, + ATTR_TARGET_TEMP_LOW: 15, + }, + blocking=True, + ) + + state = hass.states.get(entity_id) + assert state.attributes[ATTR_TARGET_TEMP_LOW] is None + assert state.attributes[ATTR_TARGET_TEMP_HIGH] is None + assert state.attributes[ATTR_TEMPERATURE] == 20.0 + assert thrm_cluster.write_attributes.await_count == 0 + + await hass.services.async_call( + DOMAIN, + SERVICE_SET_TEMPERATURE, + {ATTR_ENTITY_ID: entity_id, ATTR_TEMPERATURE: 21}, + blocking=True, + ) + + state = hass.states.get(entity_id) + assert state.attributes[ATTR_TARGET_TEMP_LOW] is None + assert state.attributes[ATTR_TARGET_TEMP_HIGH] is None + assert state.attributes[ATTR_TEMPERATURE] == 21.0 + assert thrm_cluster.write_attributes.await_count == 1 + assert thrm_cluster.write_attributes.call_args_list[0][0][0] == { + "occupied_heating_setpoint": 2100 + } + + await hass.services.async_call( + DOMAIN, + SERVICE_SET_PRESET_MODE, + {ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: PRESET_AWAY}, + blocking=True, + ) + thrm_cluster.write_attributes.reset_mock() + + await hass.services.async_call( + DOMAIN, + SERVICE_SET_TEMPERATURE, + {ATTR_ENTITY_ID: entity_id, ATTR_TEMPERATURE: 22}, + blocking=True, + ) + + state = hass.states.get(entity_id) + assert state.attributes[ATTR_TARGET_TEMP_LOW] is None + assert state.attributes[ATTR_TARGET_TEMP_HIGH] is None + assert state.attributes[ATTR_TEMPERATURE] == 22.0 + assert thrm_cluster.write_attributes.await_count == 1 + assert thrm_cluster.write_attributes.call_args_list[0][0][0] == { + "unoccupied_heating_setpoint": 2200 + } + + +async def test_set_temperature_cool(hass, device_climate_mock): + """Test setting temperature service call in cooling HVAC mode.""" + + with patch.object( + zigpy.zcl.clusters.manufacturer_specific.ManufacturerSpecificCluster, + "ep_attribute", + "sinope_manufacturer_specific", + ): + device_climate = await device_climate_mock( + CLIMATE_SINOPE, + { + "occupied_cooling_setpoint": 2500, + "occupied_heating_setpoint": 2000, + "system_mode": Thermostat.SystemMode.Cool, + "unoccupied_cooling_setpoint": 1600, + "unoccupied_heating_setpoint": 2700, + }, + manuf=SINOPE, + ) + entity_id = await find_entity_id(DOMAIN, device_climate, hass) + thrm_cluster = device_climate.device.endpoints[1].thermostat + + state = hass.states.get(entity_id) + assert state.state == HVAC_MODE_COOL + + await hass.services.async_call( + DOMAIN, + SERVICE_SET_TEMPERATURE, + { + ATTR_ENTITY_ID: entity_id, + ATTR_TARGET_TEMP_HIGH: 30, + ATTR_TARGET_TEMP_LOW: 15, + }, + blocking=True, + ) + + state = hass.states.get(entity_id) + assert state.attributes[ATTR_TARGET_TEMP_LOW] is None + assert state.attributes[ATTR_TARGET_TEMP_HIGH] is None + assert state.attributes[ATTR_TEMPERATURE] == 25.0 + assert thrm_cluster.write_attributes.await_count == 0 + + await hass.services.async_call( + DOMAIN, + SERVICE_SET_TEMPERATURE, + {ATTR_ENTITY_ID: entity_id, ATTR_TEMPERATURE: 21}, + blocking=True, + ) + + state = hass.states.get(entity_id) + assert state.attributes[ATTR_TARGET_TEMP_LOW] is None + assert state.attributes[ATTR_TARGET_TEMP_HIGH] is None + assert state.attributes[ATTR_TEMPERATURE] == 21.0 + assert thrm_cluster.write_attributes.await_count == 1 + assert thrm_cluster.write_attributes.call_args_list[0][0][0] == { + "occupied_cooling_setpoint": 2100 + } + + await hass.services.async_call( + DOMAIN, + SERVICE_SET_PRESET_MODE, + {ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: PRESET_AWAY}, + blocking=True, + ) + thrm_cluster.write_attributes.reset_mock() + + await hass.services.async_call( + DOMAIN, + SERVICE_SET_TEMPERATURE, + {ATTR_ENTITY_ID: entity_id, ATTR_TEMPERATURE: 22}, + blocking=True, + ) + + state = hass.states.get(entity_id) + assert state.attributes[ATTR_TARGET_TEMP_LOW] is None + assert state.attributes[ATTR_TARGET_TEMP_HIGH] is None + assert state.attributes[ATTR_TEMPERATURE] == 22.0 + assert thrm_cluster.write_attributes.await_count == 1 + assert thrm_cluster.write_attributes.call_args_list[0][0][0] == { + "unoccupied_cooling_setpoint": 2200 + } + + +async def test_set_temperature_wrong_mode(hass, device_climate_mock): + """Test setting temperature service call for wrong HVAC mode.""" + + with patch.object( + zigpy.zcl.clusters.manufacturer_specific.ManufacturerSpecificCluster, + "ep_attribute", + "sinope_manufacturer_specific", + ): + device_climate = await device_climate_mock( + CLIMATE_SINOPE, + { + "occupied_cooling_setpoint": 2500, + "occupied_heating_setpoint": 2000, + "system_mode": Thermostat.SystemMode.Dry, + "unoccupied_cooling_setpoint": 1600, + "unoccupied_heating_setpoint": 2700, + }, + manuf=SINOPE, + ) + entity_id = await find_entity_id(DOMAIN, device_climate, hass) + thrm_cluster = device_climate.device.endpoints[1].thermostat + + state = hass.states.get(entity_id) + assert state.state == HVAC_MODE_DRY + + await hass.services.async_call( + DOMAIN, + SERVICE_SET_TEMPERATURE, + {ATTR_ENTITY_ID: entity_id, ATTR_TEMPERATURE: 24}, + blocking=True, + ) + + state = hass.states.get(entity_id) + assert state.attributes[ATTR_TARGET_TEMP_LOW] is None + assert state.attributes[ATTR_TARGET_TEMP_HIGH] is None + assert state.attributes[ATTR_TEMPERATURE] is None + assert thrm_cluster.write_attributes.await_count == 0 + + +async def test_occupancy_reset(hass, device_climate_sinope): + """Test away preset reset.""" + + entity_id = await find_entity_id(DOMAIN, device_climate_sinope, hass) + thrm_cluster = device_climate_sinope.device.endpoints[1].thermostat + + state = hass.states.get(entity_id) + assert state.attributes[ATTR_PRESET_MODE] == PRESET_NONE + + await hass.services.async_call( + DOMAIN, + SERVICE_SET_PRESET_MODE, + {ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: PRESET_AWAY}, + blocking=True, + ) + thrm_cluster.write_attributes.reset_mock() + + state = hass.states.get(entity_id) + assert state.attributes[ATTR_PRESET_MODE] == PRESET_AWAY + + thrm_cluster.read_attributes.return_value = [True], {} + await send_attributes_report( + hass, thrm_cluster, {"occupied_heating_setpoint": 1950} + ) + state = hass.states.get(entity_id) + assert state.attributes[ATTR_PRESET_MODE] == PRESET_NONE + + +async def test_fan_mode(hass, device_climate_fan): + """Test fan mode.""" + + entity_id = await find_entity_id(DOMAIN, device_climate_fan, hass) + thrm_cluster = device_climate_fan.device.endpoints[1].thermostat + + state = hass.states.get(entity_id) + assert set(state.attributes[ATTR_FAN_MODES]) == {FAN_AUTO, FAN_ON} + assert state.attributes[ATTR_FAN_MODE] == FAN_AUTO + + await send_attributes_report( + hass, thrm_cluster, {"running_state": Thermostat.RunningState.Fan_State_On} + ) + state = hass.states.get(entity_id) + assert state.attributes[ATTR_FAN_MODE] == FAN_ON + + await send_attributes_report( + hass, thrm_cluster, {"running_state": Thermostat.RunningState.Idle} + ) + state = hass.states.get(entity_id) + assert state.attributes[ATTR_FAN_MODE] == FAN_AUTO + + await send_attributes_report( + hass, thrm_cluster, {"running_state": Thermostat.RunningState.Fan_2nd_Stage_On} + ) + state = hass.states.get(entity_id) + assert state.attributes[ATTR_FAN_MODE] == FAN_ON + + +async def test_set_fan_mode_no_fan(hass, device_climate, caplog): + """Test setting fan mode on fun less climate.""" + + entity_id = await find_entity_id(DOMAIN, device_climate, hass) + + with caplog.at_level(logging.DEBUG): + await hass.services.async_call( + DOMAIN, + SERVICE_SET_FAN_MODE, + {ATTR_ENTITY_ID: entity_id, ATTR_FAN_MODE: FAN_ON}, + blocking=True, + ) + assert "Fan is not supported" in caplog.text + + +async def test_set_fan_mode_not_supported(hass, device_climate_fan, caplog): + """Test fan setting unsupported mode.""" + + entity_id = await find_entity_id(DOMAIN, device_climate_fan, hass) + + with caplog.at_level(logging.DEBUG): + await hass.services.async_call( + DOMAIN, + SERVICE_SET_FAN_MODE, + {ATTR_ENTITY_ID: entity_id, ATTR_FAN_MODE: FAN_LOW}, + blocking=True, + ) + assert "Unsupported 'low' fan mode" in caplog.text + + +async def test_set_fan_mode(hass, device_climate_fan): + """Test fan mode setting.""" + + entity_id = await find_entity_id(DOMAIN, device_climate_fan, hass) + fan_cluster = device_climate_fan.device.endpoints[1].fan + + state = hass.states.get(entity_id) + assert state.attributes[ATTR_FAN_MODE] == FAN_AUTO + + await hass.services.async_call( + DOMAIN, + SERVICE_SET_FAN_MODE, + {ATTR_ENTITY_ID: entity_id, ATTR_FAN_MODE: FAN_ON}, + blocking=True, + ) + assert fan_cluster.write_attributes.await_count == 1 + assert fan_cluster.write_attributes.call_args[0][0] == {"fan_mode": 4} + + fan_cluster.write_attributes.reset_mock() + await hass.services.async_call( + DOMAIN, + SERVICE_SET_FAN_MODE, + {ATTR_ENTITY_ID: entity_id, ATTR_FAN_MODE: FAN_AUTO}, + blocking=True, + ) + assert fan_cluster.write_attributes.await_count == 1 + assert fan_cluster.write_attributes.call_args[0][0] == {"fan_mode": 5} diff --git a/tests/components/zha/zha_devices_list.py b/tests/components/zha/zha_devices_list.py index 1d88ba69e8d..01144cde694 100644 --- a/tests/components/zha/zha_devices_list.py +++ b/tests/components/zha/zha_devices_list.py @@ -3100,10 +3100,16 @@ DEVICES = [ }, }, "entities": [ + "climate.sinope_technologies_th1123zb_77665544_thermostat", "sensor.sinope_technologies_th1123zb_77665544_electrical_measurement", "sensor.sinope_technologies_th1123zb_77665544_temperature", ], "entity_map": { + ("climate", "00:11:22:33:44:55:66:77-1"): { + "channels": ["thermostat"], + "entity_class": "Thermostat", + "entity_id": "climate.sinope_technologies_th1123zb_77665544_thermostat", + }, ("sensor", "00:11:22:33:44:55:66:77-1-1026"): { "channels": ["temperature"], "entity_class": "Temperature", @@ -3142,8 +3148,14 @@ DEVICES = [ "entities": [ "sensor.sinope_technologies_th1124zb_77665544_electrical_measurement", "sensor.sinope_technologies_th1124zb_77665544_temperature", + "climate.sinope_technologies_th1124zb_77665544_thermostat", ], "entity_map": { + ("climate", "00:11:22:33:44:55:66:77-1"): { + "channels": ["thermostat"], + "entity_class": "Thermostat", + "entity_id": "climate.sinope_technologies_th1124zb_77665544_thermostat", + }, ("sensor", "00:11:22:33:44:55:66:77-1-1026"): { "channels": ["temperature"], "entity_class": "Temperature", @@ -3326,7 +3338,7 @@ DEVICES = [ } }, "entities": [ - "fan.zen_within_zen_01_77665544_fan", + "climate.zen_within_zen_01_77665544_fan_thermostat", "sensor.zen_within_zen_01_77665544_power", ], "entity_map": { @@ -3335,10 +3347,10 @@ DEVICES = [ "entity_class": "Battery", "entity_id": "sensor.zen_within_zen_01_77665544_power", }, - ("fan", "00:11:22:33:44:55:66:77-1-514"): { - "channels": ["fan"], - "entity_class": "ZhaFan", - "entity_id": "fan.zen_within_zen_01_77665544_fan", + ("climate", "00:11:22:33:44:55:66:77-1"): { + "channels": ["thermostat", "fan"], + "entity_class": "Thermostat", + "entity_id": "climate.zen_within_zen_01_77665544_fan_thermostat", }, }, "event_channels": ["1:0x0019"], From 1593bdf2e9cc4840a7dc3cbca843c98f10a023c9 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Wed, 20 May 2020 23:47:30 +0200 Subject: [PATCH 111/406] Add climate services required features (#35804) --- homeassistant/components/climate/__init__.py | 10 +++++++++- homeassistant/components/demo/climate.py | 4 ++-- tests/components/climate/test_init.py | 10 ++++++++++ 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/climate/__init__.py b/homeassistant/components/climate/__init__.py index d3241791cf2..32dfaa0e8fb 100644 --- a/homeassistant/components/climate/__init__.py +++ b/homeassistant/components/climate/__init__.py @@ -118,29 +118,37 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: SERVICE_SET_PRESET_MODE, {vol.Required(ATTR_PRESET_MODE): cv.string}, "async_set_preset_mode", + [SUPPORT_PRESET_MODE], ) component.async_register_entity_service( SERVICE_SET_AUX_HEAT, {vol.Required(ATTR_AUX_HEAT): cv.boolean}, async_service_aux_heat, + [SUPPORT_AUX_HEAT], ) component.async_register_entity_service( - SERVICE_SET_TEMPERATURE, SET_TEMPERATURE_SCHEMA, async_service_temperature_set, + SERVICE_SET_TEMPERATURE, + SET_TEMPERATURE_SCHEMA, + async_service_temperature_set, + [SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE_RANGE], ) component.async_register_entity_service( SERVICE_SET_HUMIDITY, {vol.Required(ATTR_HUMIDITY): vol.Coerce(float)}, "async_set_humidity", + [SUPPORT_TARGET_HUMIDITY], ) component.async_register_entity_service( SERVICE_SET_FAN_MODE, {vol.Required(ATTR_FAN_MODE): cv.string}, "async_set_fan_mode", + [SUPPORT_FAN_MODE], ) component.async_register_entity_service( SERVICE_SET_SWING_MODE, {vol.Required(ATTR_SWING_MODE): cv.string}, "async_set_swing_mode", + [SUPPORT_SWING_MODE], ) return True diff --git a/homeassistant/components/demo/climate.py b/homeassistant/components/demo/climate.py index fd5615c82bd..57feae64a89 100644 --- a/homeassistant/components/demo/climate.py +++ b/homeassistant/components/demo/climate.py @@ -309,12 +309,12 @@ class DemoClimate(ClimateEntity): self._preset = preset_mode self.async_write_ha_state() - def turn_aux_heat_on(self): + async def async_turn_aux_heat_on(self): """Turn auxiliary heater on.""" self._aux = True self.async_write_ha_state() - def turn_aux_heat_off(self): + async def async_turn_aux_heat_off(self): """Turn auxiliary heater off.""" self._aux = False self.async_write_ha_state() diff --git a/tests/components/climate/test_init.py b/tests/components/climate/test_init.py index e42bf8c7e3c..89c12b4c517 100644 --- a/tests/components/climate/test_init.py +++ b/tests/components/climate/test_init.py @@ -65,6 +65,12 @@ class MockClimateEntity(ClimateEntity): """ return [HVAC_MODE_OFF, HVAC_MODE_HEAT] + def turn_on(self) -> None: + """Turn on.""" + + def turn_off(self) -> None: + """Turn off.""" + async def test_sync_turn_on(hass): """Test if async turn_on calls sync turn_on.""" @@ -92,9 +98,13 @@ def test_deprecated_base_class(caplog): """Test deprecated base class.""" class CustomClimate(ClimateDevice): + """Custom climate entity class.""" + + @property def hvac_mode(self): pass + @property def hvac_modes(self): pass From 19573a9cbeb3e62f0747b913d2ae0f07925ab20f Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Wed, 20 May 2020 16:09:00 -0600 Subject: [PATCH 112/406] Fix Prezzibenzina doing I/O in the event loop (#35881) * Fix Prezzibenzina doing I/O in the event loop * Linting --- homeassistant/components/prezzibenzina/sensor.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/prezzibenzina/sensor.py b/homeassistant/components/prezzibenzina/sensor.py index c985f96e6c6..f45d9d84669 100644 --- a/homeassistant/components/prezzibenzina/sensor.py +++ b/homeassistant/components/prezzibenzina/sensor.py @@ -42,7 +42,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( ) -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): +def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the PrezziBenzina sensor platform.""" station = config[CONF_STATION] @@ -65,7 +65,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) ) - async_add_entities(dev, True) + add_entities(dev, True) class PrezziBenzinaSensor(Entity): @@ -114,6 +114,6 @@ class PrezziBenzinaSensor(Entity): } return attrs - async def async_update(self): + def update(self): """Get the latest data and updates the states.""" self._data = self._client.get_by_id(self._station)[self._index] From b32ec950ac430db5564ad45ddd2b68ab4b4eb1b2 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Thu, 21 May 2020 00:02:31 +0000 Subject: [PATCH 113/406] [ci skip] Translation update --- .../components/acmeda/translations/ca.json | 6 ++++-- .../components/daikin/translations/ca.json | 2 +- .../components/isy994/translations/no.json | 2 +- .../components/isy994/translations/pl.json | 18 +++++++++++++++++- .../components/onvif/translations/pl.json | 1 + 5 files changed, 24 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/acmeda/translations/ca.json b/homeassistant/components/acmeda/translations/ca.json index 13c816a90d5..81bceddfcff 100644 --- a/homeassistant/components/acmeda/translations/ca.json +++ b/homeassistant/components/acmeda/translations/ca.json @@ -4,8 +4,10 @@ "user": { "data": { "id": "ID d'amfitri\u00f3" - } + }, + "title": "Selecci\u00f3 del Hub a afegir" } } - } + }, + "title": "Rollease Acmeda Automate" } \ No newline at end of file diff --git a/homeassistant/components/daikin/translations/ca.json b/homeassistant/components/daikin/translations/ca.json index 9497a57ea49..4445c05e7d8 100644 --- a/homeassistant/components/daikin/translations/ca.json +++ b/homeassistant/components/daikin/translations/ca.json @@ -17,7 +17,7 @@ "key": "Clau API", "password": "Contrasenya" }, - "description": "Introdueix l'adre\u00e7a IP del teu Daikin AC.", + "description": "Introdueix l'adre\u00e7a IP del teu AC Daikin.\n\nTingues en compte que la Clau API i la Contrasenya s'utilitzen als dispositius BRP072Cxx i SKYFi respectivament.", "title": "Configuraci\u00f3 de Daikin AC" } } diff --git a/homeassistant/components/isy994/translations/no.json b/homeassistant/components/isy994/translations/no.json index 16945f046ee..0e2d4fec686 100644 --- a/homeassistant/components/isy994/translations/no.json +++ b/homeassistant/components/isy994/translations/no.json @@ -4,7 +4,7 @@ "invalid_host": "Vertsoppf\u00f8ringen var ikke i fullstendig URL-format, for eksempel http://192.168.10.100:80", "unknown": "[%key:common::config_flow::error::unknown%" }, - "flow_title": "Universelle enheter ISY994 {name} ( {host} )", + "flow_title": "Universelle enheter ISY994 {name} ({host})", "step": { "user": { "data": { diff --git a/homeassistant/components/isy994/translations/pl.json b/homeassistant/components/isy994/translations/pl.json index f9f98e9f6cd..27f79ef2801 100644 --- a/homeassistant/components/isy994/translations/pl.json +++ b/homeassistant/components/isy994/translations/pl.json @@ -9,6 +9,7 @@ "invalid_host": "Wpis hosta nie by\u0142 w pe\u0142nym formacie URL, np. http://192.168.10.100:80.", "unknown": "Nieoczekiwany b\u0142\u0105d." }, + "flow_title": "Urz\u0105dzenia uniwersalne ISY994 {name} ({host})", "step": { "user": { "data": { @@ -21,5 +22,20 @@ "title": "Po\u0142\u0105czenie z ISY994" } } - } + }, + "options": { + "step": { + "init": { + "data": { + "ignore_string": "Ci\u0105g ignorowania", + "restore_light_state": "Przywr\u00f3\u0107 jasno\u015b\u0107 \u015bwiat\u0142a", + "sensor_string": "Ci\u0105g sensora w\u0119z\u0142a", + "variable_sensor_string": "Ci\u0105g zmiennej sensora" + }, + "description": "Ustaw opcje dla integracji ISY: \n \u2022 Ci\u0105g sensora w\u0119z\u0142a: ka\u017cde urz\u0105dzenie lub folder, kt\u00f3ry zawiera w nazwie ci\u0105g sensora w\u0119z\u0142a, b\u0119dzie traktowane jako sensor lub sensor binarny. \n \u2022 Ci\u0105g ignorowania: ka\u017cde urz\u0105dzenie z 'ci\u0105giem ignorowania' w nazwie zostanie zignorowane. \n \u2022 Ci\u0105g sensora zmiennej: ka\u017cda zmienna zawieraj\u0105ca 'ci\u0105g sensora zmiennej' zostanie dodana jako sensor. \n \u2022 Przywr\u00f3\u0107 jasno\u015b\u0107 \u015bwiat\u0142a: je\u015bli ta opcja jest w\u0142\u0105czona, poprzednia warto\u015b\u0107 jasno\u015bci zostanie przywr\u00f3cona po w\u0142\u0105czeniu \u015bwiat\u0142a zamiast domy\u015blnej warto\u015bci jasno\u015bci dla urz\u0105dzenia.", + "title": "Opcje ISY994" + } + } + }, + "title": "Urz\u0105dzenia uniwersalne ISY994" } \ No newline at end of file diff --git a/homeassistant/components/onvif/translations/pl.json b/homeassistant/components/onvif/translations/pl.json index afd4df73b66..f1e0ea0bd69 100644 --- a/homeassistant/components/onvif/translations/pl.json +++ b/homeassistant/components/onvif/translations/pl.json @@ -34,6 +34,7 @@ "manual_input": { "data": { "host": "Nazwa hosta lub adres IP", + "name": "Nazwa", "port": "Port" }, "title": "Konfigurowanie urz\u0105dzenia ONVIF" From ed2a43ba5ba5435533f4b71837fe4efe57b72edb Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Wed, 20 May 2020 20:25:00 -0400 Subject: [PATCH 114/406] Fix zha climate tests (#35893) --- tests/components/zha/test_climate.py | 35 +++++++++++++--------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/tests/components/zha/test_climate.py b/tests/components/zha/test_climate.py index 81050dc63fb..c8cffef3fb9 100644 --- a/tests/components/zha/test_climate.py +++ b/tests/components/zha/test_climate.py @@ -1,5 +1,4 @@ """Test zha climate.""" -import logging import pytest import zigpy.zcl.clusters @@ -1001,34 +1000,32 @@ async def test_fan_mode(hass, device_climate_fan): assert state.attributes[ATTR_FAN_MODE] == FAN_ON -async def test_set_fan_mode_no_fan(hass, device_climate, caplog): +async def test_set_fan_mode_no_fan(hass, device_climate): """Test setting fan mode on fun less climate.""" entity_id = await find_entity_id(DOMAIN, device_climate, hass) - with caplog.at_level(logging.DEBUG): - await hass.services.async_call( - DOMAIN, - SERVICE_SET_FAN_MODE, - {ATTR_ENTITY_ID: entity_id, ATTR_FAN_MODE: FAN_ON}, - blocking=True, - ) - assert "Fan is not supported" in caplog.text + await hass.services.async_call( + DOMAIN, + SERVICE_SET_FAN_MODE, + {ATTR_ENTITY_ID: entity_id, ATTR_FAN_MODE: FAN_ON}, + blocking=True, + ) -async def test_set_fan_mode_not_supported(hass, device_climate_fan, caplog): +async def test_set_fan_mode_not_supported(hass, device_climate_fan): """Test fan setting unsupported mode.""" entity_id = await find_entity_id(DOMAIN, device_climate_fan, hass) + fan_cluster = device_climate_fan.device.endpoints[1].fan - with caplog.at_level(logging.DEBUG): - await hass.services.async_call( - DOMAIN, - SERVICE_SET_FAN_MODE, - {ATTR_ENTITY_ID: entity_id, ATTR_FAN_MODE: FAN_LOW}, - blocking=True, - ) - assert "Unsupported 'low' fan mode" in caplog.text + await hass.services.async_call( + DOMAIN, + SERVICE_SET_FAN_MODE, + {ATTR_ENTITY_ID: entity_id, ATTR_FAN_MODE: FAN_LOW}, + blocking=True, + ) + assert fan_cluster.write_attributes.await_count == 0 async def test_set_fan_mode(hass, device_climate_fan): From a82900ae275560acf1093a5cdd288be6683979e4 Mon Sep 17 00:00:00 2001 From: Jason Hunter Date: Wed, 20 May 2020 21:26:27 -0400 Subject: [PATCH 115/406] fix mjpeg issue along with some cameras not returning event capabilities properly (#35885) --- homeassistant/components/onvif/camera.py | 20 ++++++++++---------- homeassistant/components/onvif/device.py | 4 ++-- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/onvif/camera.py b/homeassistant/components/onvif/camera.py index 4d39c95c3cd..570b99bfe3a 100644 --- a/homeassistant/components/onvif/camera.py +++ b/homeassistant/components/onvif/camera.py @@ -106,11 +106,6 @@ class ONVIFCameraEntity(ONVIFBaseEntity, Camera): async def stream_source(self): """Return the stream source.""" - if self._stream_uri is None: - uri_no_auth = await self.device.async_get_stream_uri(self.profile) - self._stream_uri = uri_no_auth.replace( - "rtsp://", f"rtsp://{self.device.username}:{self.device.password}@", 1 - ) return self._stream_uri async def async_camera_image(self): @@ -118,11 +113,6 @@ class ONVIFCameraEntity(ONVIFBaseEntity, Camera): image = None if self.device.capabilities.snapshot: - if self._snapshot_uri is None: - self._snapshot_uri = await self.device.async_get_snapshot_uri( - self.profile - ) - auth = None if self.device.username and self.device.password: auth = HTTPDigestAuth(self.device.username, self.device.password) @@ -181,6 +171,16 @@ class ONVIFCameraEntity(ONVIFBaseEntity, Camera): finally: await stream.close() + async def async_added_to_hass(self): + """Run when entity about to be added to hass.""" + uri_no_auth = await self.device.async_get_stream_uri(self.profile) + self._stream_uri = uri_no_auth.replace( + "rtsp://", f"rtsp://{self.device.username}:{self.device.password}@", 1 + ) + + if self.device.capabilities.snapshot: + self._snapshot_uri = await self.device.async_get_snapshot_uri(self.profile) + async def async_perform_ptz( self, distance, diff --git a/homeassistant/components/onvif/device.py b/homeassistant/components/onvif/device.py index 8e69e148da3..0a35dadec26 100644 --- a/homeassistant/components/onvif/device.py +++ b/homeassistant/components/onvif/device.py @@ -223,7 +223,7 @@ class ONVIFDevice: try: media_service = self.device.create_media_service() media_capabilities = await media_service.GetServiceCapabilities() - snapshot = media_capabilities.SnapshotUri + snapshot = media_capabilities and media_capabilities.SnapshotUri except (ONVIFError, Fault): pass @@ -231,7 +231,7 @@ class ONVIFDevice: try: event_service = self.device.create_events_service() event_capabilities = await event_service.GetServiceCapabilities() - pullpoint = event_capabilities.WSPullPointSupport + pullpoint = event_capabilities and event_capabilities.WSPullPointSupport except (ONVIFError, Fault): pass From c2f8e0bf66e4a33a52477c07cba8be5dbbd9027f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 21 May 2020 01:48:01 -0500 Subject: [PATCH 116/406] Ensure http can startup if homekit fails to load (#35888) * Ensure HomeAssistant can startup if homekit fails to load * Update homeassistant/components/logbook/manifest.json Co-authored-by: Martin Hjelmare Co-authored-by: Martin Hjelmare --- homeassistant/components/logbook/manifest.json | 1 - 1 file changed, 1 deletion(-) diff --git a/homeassistant/components/logbook/manifest.json b/homeassistant/components/logbook/manifest.json index 3980469c9c5..26586013108 100644 --- a/homeassistant/components/logbook/manifest.json +++ b/homeassistant/components/logbook/manifest.json @@ -3,6 +3,5 @@ "name": "Logbook", "documentation": "https://www.home-assistant.io/integrations/logbook", "dependencies": ["frontend", "http", "recorder"], - "after_dependencies": ["homekit"], "codeowners": [] } From b19223cb3790842fe4de2feec59f67286332f580 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 21 May 2020 02:18:45 -0500 Subject: [PATCH 117/406] Fix legacy Hunter Douglas PowerView devices (#35895) These devices are missing firmware information as the 1.0 firmware did not provide it. --- .../hunterdouglas_powerview/__init__.py | 22 ++++++++++-- .../hunterdouglas_powerview/const.py | 5 +++ .../hunterdouglas_powerview/userdata_v1.json | 34 +++++++++++++++++++ 3 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 tests/fixtures/hunterdouglas_powerview/userdata_v1.json diff --git a/homeassistant/components/hunterdouglas_powerview/__init__.py b/homeassistant/components/hunterdouglas_powerview/__init__.py index 7001425f306..935410d9351 100644 --- a/homeassistant/components/hunterdouglas_powerview/__init__.py +++ b/homeassistant/components/hunterdouglas_powerview/__init__.py @@ -31,9 +31,15 @@ from .const import ( DEVICE_REVISION, DEVICE_SERIAL_NUMBER, DOMAIN, + FIRMWARE_BUILD, FIRMWARE_IN_USERDATA, + FIRMWARE_SUB_REVISION, HUB_EXCEPTIONS, HUB_NAME, + LEGACY_DEVICE_BUILD, + LEGACY_DEVICE_MODEL, + LEGACY_DEVICE_REVISION, + LEGACY_DEVICE_SUB_REVISION, MAC_ADDRESS_IN_USERDATA, MAINPROCESSOR_IN_USERDATA_FIRMWARE, MODEL_IN_MAINPROCESSOR, @@ -159,9 +165,19 @@ async def async_get_device_info(pv_request): resources = await userdata.get_resources() userdata_data = resources[USER_DATA] - main_processor_info = userdata_data[FIRMWARE_IN_USERDATA][ - MAINPROCESSOR_IN_USERDATA_FIRMWARE - ] + if FIRMWARE_IN_USERDATA in userdata_data: + main_processor_info = userdata_data[FIRMWARE_IN_USERDATA][ + MAINPROCESSOR_IN_USERDATA_FIRMWARE + ] + else: + # Legacy devices + main_processor_info = { + REVISION_IN_MAINPROCESSOR: LEGACY_DEVICE_REVISION, + FIRMWARE_SUB_REVISION: LEGACY_DEVICE_SUB_REVISION, + FIRMWARE_BUILD: LEGACY_DEVICE_BUILD, + MODEL_IN_MAINPROCESSOR: LEGACY_DEVICE_MODEL, + } + return { DEVICE_NAME: base64_to_unicode(userdata_data[HUB_NAME]), DEVICE_MAC_ADDRESS: userdata_data[MAC_ADDRESS_IN_USERDATA], diff --git a/homeassistant/components/hunterdouglas_powerview/const.py b/homeassistant/components/hunterdouglas_powerview/const.py index 17ff3821a7a..e69fe319c0f 100644 --- a/homeassistant/components/hunterdouglas_powerview/const.py +++ b/homeassistant/components/hunterdouglas_powerview/const.py @@ -65,3 +65,8 @@ PV_ROOM_DATA = "pv_room_data" COORDINATOR = "coordinator" HUB_EXCEPTIONS = (asyncio.TimeoutError, PvApiConnectionError) + +LEGACY_DEVICE_SUB_REVISION = 1 +LEGACY_DEVICE_REVISION = 0 +LEGACY_DEVICE_BUILD = 0 +LEGACY_DEVICE_MODEL = "PV Hub1.0" diff --git a/tests/fixtures/hunterdouglas_powerview/userdata_v1.json b/tests/fixtures/hunterdouglas_powerview/userdata_v1.json new file mode 100644 index 00000000000..d97aca162f8 --- /dev/null +++ b/tests/fixtures/hunterdouglas_powerview/userdata_v1.json @@ -0,0 +1,34 @@ +{ + "userData" : { + "enableScheduledEvents" : true, + "staticIp" : false, + "sceneControllerCount" : 0, + "accessPointCount" : 0, + "shadeCount" : 5, + "ip" : "192.168.20.9", + "groupCount" : 9, + "scheduledEventCount" : 0, + "editingEnabled" : true, + "roomCount" : 5, + "setupCompleted" : false, + "sceneCount" : 18, + "sceneControllerMemberCount" : 0, + "mask" : "255.255.255.0", + "hubName" : "UG93ZXJWaWV3IEh1YiBHZW4gMQ==", + "rfID" : "0x8B2A", + "remoteConnectEnabled" : false, + "multiSceneMemberCount" : 0, + "rfStatus" : 0, + "serialNumber" : "REMOVED", + "undefinedShadeCount" : 0, + "sceneMemberCount" : 18, + "unassignedShadeCount" : 0, + "multiSceneCount" : 0, + "addressKind" : "newPrimary", + "gateway" : "192.168.20.1", + "localTimeDataSet" : true, + "dns" : "192.168.20.1", + "macAddress" : "00:00:00:00:00:eb", + "rfIDInt" : 35626 + } +} From 78b57678d840b90dcc0f12791dd25a1fea7303b2 Mon Sep 17 00:00:00 2001 From: Chris Talkington Date: Thu, 21 May 2020 02:22:49 -0500 Subject: [PATCH 118/406] Update rokuecp to 0.4.1 (#35899) * update rokuecp to 0.4.1 * Update requirements_all.txt * Update requirements_test_all.txt --- homeassistant/components/roku/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/roku/manifest.json b/homeassistant/components/roku/manifest.json index 62b3cc58fc8..57c64f4c64a 100644 --- a/homeassistant/components/roku/manifest.json +++ b/homeassistant/components/roku/manifest.json @@ -2,7 +2,7 @@ "domain": "roku", "name": "Roku", "documentation": "https://www.home-assistant.io/integrations/roku", - "requirements": ["rokuecp==0.4.0"], + "requirements": ["rokuecp==0.4.1"], "ssdp": [ { "st": "roku:ecp", diff --git a/requirements_all.txt b/requirements_all.txt index 3d5189a5bae..4fada9a8889 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1871,7 +1871,7 @@ rjpl==0.3.5 rocketchat-API==0.6.1 # homeassistant.components.roku -rokuecp==0.4.0 +rokuecp==0.4.1 # homeassistant.components.roomba roombapy==1.6.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 65725b04173..1ec3193eae1 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -768,7 +768,7 @@ rflink==0.0.52 ring_doorbell==0.6.0 # homeassistant.components.roku -rokuecp==0.4.0 +rokuecp==0.4.1 # homeassistant.components.roomba roombapy==1.6.1 From 4805723a3fdfc470a8d96939d979024311ef08ad Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 21 May 2020 10:25:28 +0200 Subject: [PATCH 119/406] Updated frontend to 20200519.1 (#35877) --- 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 563e71c2eec..01d1b6eb88f 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==20200519.0"], + "requirements": ["home-assistant-frontend==20200519.1"], "dependencies": [ "api", "auth", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index a58ee6b7cdf..6410e3b3800 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -12,7 +12,7 @@ cryptography==2.9.2 defusedxml==0.6.0 distro==1.5.0 hass-nabucasa==0.34.2 -home-assistant-frontend==20200519.0 +home-assistant-frontend==20200519.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 4fada9a8889..e01380c591b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -734,7 +734,7 @@ hole==0.5.1 holidays==0.10.2 # homeassistant.components.frontend -home-assistant-frontend==20200519.0 +home-assistant-frontend==20200519.1 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 1ec3193eae1..50882ae20a9 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -318,7 +318,7 @@ hole==0.5.1 holidays==0.10.2 # homeassistant.components.frontend -home-assistant-frontend==20200519.0 +home-assistant-frontend==20200519.1 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From 6d0349637290600b2b64a99ea183667106f3dd27 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 21 May 2020 03:27:40 -0500 Subject: [PATCH 120/406] Ensure storage write consume the data under the lock (#35889) If two writes trigger at the same time the data would already be consumed. --- homeassistant/helpers/storage.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/homeassistant/helpers/storage.py b/homeassistant/helpers/storage.py index b5ac942bf2f..d2b4c334937 100644 --- a/homeassistant/helpers/storage.py +++ b/homeassistant/helpers/storage.py @@ -200,14 +200,19 @@ class Store: async def _async_handle_write_data(self, *_args): """Handle writing the config.""" - data = self._data - - if "data_func" in data: - data["data"] = data.pop("data_func")() - - self._data = None async with self._write_lock: + if self._data is None: + # Another write already consumed the data + return + + data = self._data + + if "data_func" in data: + data["data"] = data.pop("data_func")() + + self._data = None + try: await self.hass.async_add_executor_job( self._write_data, self.path, data From b57cabfce758aed0c76fe61340dca1483ef20893 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 21 May 2020 03:30:18 -0500 Subject: [PATCH 121/406] Homekit should skip devices that are missing in device registry (#35857) * Homekit should skip devices that are missing in device registry * Add test for this failure state --- homeassistant/components/homekit/__init__.py | 14 ++-- tests/components/homekit/test_homekit.py | 80 ++++++++++++++++++++ 2 files changed, 88 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index 0a208e012fb..adbf79128e3 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -627,12 +627,14 @@ class HomeKit: ent_cfg = self._config.setdefault(entity_id, {}) if ent_reg_ent.device_id: dev_reg_ent = dev_reg.async_get(ent_reg_ent.device_id) - if dev_reg_ent.manufacturer: - ent_cfg[ATTR_MANUFACTURER] = dev_reg_ent.manufacturer - if dev_reg_ent.model: - ent_cfg[ATTR_MODEL] = dev_reg_ent.model - if dev_reg_ent.sw_version: - ent_cfg[ATTR_SOFTWARE_VERSION] = dev_reg_ent.sw_version + if dev_reg_ent is not None: + # Handle missing devices + if dev_reg_ent.manufacturer: + ent_cfg[ATTR_MANUFACTURER] = dev_reg_ent.manufacturer + if dev_reg_ent.model: + ent_cfg[ATTR_MODEL] = dev_reg_ent.model + if dev_reg_ent.sw_version: + ent_cfg[ATTR_SOFTWARE_VERSION] = dev_reg_ent.sw_version if ATTR_MANUFACTURER not in ent_cfg: integration = await async_get_integration(self.hass, ent_reg_ent.platform) ent_cfg[ATTR_INTERGRATION] = integration.name diff --git a/tests/components/homekit/test_homekit.py b/tests/components/homekit/test_homekit.py index 8a4ac87f21b..c0e2ea90fba 100644 --- a/tests/components/homekit/test_homekit.py +++ b/tests/components/homekit/test_homekit.py @@ -913,3 +913,83 @@ def _write_data(path: str, data: Dict) -> None: if not os.path.isdir(os.path.dirname(path)): os.makedirs(os.path.dirname(path)) json_util.save_json(path, data) + + +async def test_homekit_ignored_missing_devices( + hass, hk_driver, debounce_patcher, device_reg, entity_reg +): + """Test HomeKit handles a device in the entity registry but missing from the device registry.""" + entry = await async_init_integration(hass) + + 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") + + config_entry = MockConfigEntry(domain="test", data={}) + config_entry.add_to_hass(hass) + device_entry = device_reg.async_get_or_create( + config_entry_id=config_entry.entry_id, + sw_version="0.16.0", + model="Powerwall 2", + manufacturer="Tesla", + connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, + ) + + entity_reg.async_get_or_create( + "binary_sensor", + "powerwall", + "battery_charging", + device_id=device_entry.id, + device_class=DEVICE_CLASS_BATTERY_CHARGING, + ) + entity_reg.async_get_or_create( + "sensor", + "powerwall", + "battery", + device_id=device_entry.id, + device_class=DEVICE_CLASS_BATTERY, + ) + light = entity_reg.async_get_or_create( + "light", "powerwall", "demo", device_id=device_entry.id + ) + + # Delete the device to make sure we fallback + # to using the platform + device_reg.async_remove_device(device_entry.id) + + hass.states.async_set(light.entity_id, STATE_ON) + + def _mock_get_accessory(*args, **kwargs): + return [None, "acc", None] + + with patch.object(homekit.bridge, "add_accessory"), patch( + f"{PATH_HOMEKIT}.show_setup_message" + ), patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc, patch( + "pyhap.accessory_driver.AccessoryDriver.start" + ): + await homekit.async_start() + await hass.async_block_till_done() + + mock_get_acc.assert_called_with( + hass, + hk_driver, + ANY, + ANY, + { + "platform": "Tesla Powerwall", + "linked_battery_charging_sensor": "binary_sensor.powerwall_battery_charging", + "linked_battery_sensor": "sensor.powerwall_battery", + }, + ) From 1958d132a9830fd6b1b0127c72f7435bc287be6b Mon Sep 17 00:00:00 2001 From: Daniel Perna Date: Thu, 21 May 2020 10:35:04 +0200 Subject: [PATCH 122/406] Fix light profiles for HomeMatic lights (#35882) --- homeassistant/components/homematic/light.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/homematic/light.py b/homeassistant/components/homematic/light.py index c7cfcc2ac8c..7ef632dae81 100644 --- a/homeassistant/components/homematic/light.py +++ b/homeassistant/components/homematic/light.py @@ -111,7 +111,7 @@ class HMLight(HMDevice, LightEntity): ): self._hmdevice.on(self._channel) - if ATTR_HS_COLOR in kwargs: + if ATTR_HS_COLOR in kwargs and self.supported_features & SUPPORT_COLOR: self._hmdevice.set_hs_color( hue=kwargs[ATTR_HS_COLOR][0] / 360.0, saturation=kwargs[ATTR_HS_COLOR][1] / 100.0, From f5a326c51e50b5d30f78ca2e4745d0c83da7e429 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Thu, 21 May 2020 02:49:18 -0600 Subject: [PATCH 123/406] Auto-level AirVisual API calls (#34903) --- .../components/airvisual/__init__.py | 43 ++++++++++++++++++- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/airvisual/__init__.py b/homeassistant/components/airvisual/__init__.py index 4c46e7b3e7d..e5d8b03f316 100644 --- a/homeassistant/components/airvisual/__init__.py +++ b/homeassistant/components/airvisual/__init__.py @@ -1,6 +1,7 @@ """The airvisual component.""" import asyncio from datetime import timedelta +from math import ceil from pyairvisual import Client from pyairvisual.errors import AirVisualError, NodeProError @@ -37,7 +38,6 @@ from .const import ( PLATFORMS = ["air_quality", "sensor"] 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} @@ -88,6 +88,37 @@ def async_get_geography_id(geography_dict): ) +@callback +def async_get_cloud_api_update_interval(hass, api_key): + """Get a leveled scan interval for a particular cloud API key. + + This will shift based on the number of active consumers, thus keeping the user + under the monthly API limit. + """ + num_consumers = len( + { + config_entry + for config_entry in hass.config_entries.async_entries(DOMAIN) + if config_entry.data.get(CONF_API_KEY) == api_key + } + ) + + # Assuming 10,000 calls per month and a "smallest possible month" of 28 days; note + # that we give a buffer of 1500 API calls for any drift, restarts, etc.: + minutes_between_api_calls = ceil(1 / (8500 / 28 / 24 / 60 / num_consumers)) + return timedelta(minutes=minutes_between_api_calls) + + +@callback +def async_reset_coordinator_update_intervals(hass, update_interval): + """Update any existing data coordinators with a new update interval.""" + if not hass.data[DOMAIN][DATA_COORDINATOR]: + return + + for coordinator in hass.data[DOMAIN][DATA_COORDINATOR].values(): + coordinator.update_interval = update_interval + + async def async_setup(hass, config): """Set up the AirVisual component.""" hass.data[DOMAIN] = {DATA_COORDINATOR: {}} @@ -163,6 +194,10 @@ async def async_setup_entry(hass, config_entry): client = Client(api_key=config_entry.data[CONF_API_KEY], session=websession) + update_interval = async_get_cloud_api_update_interval( + hass, config_entry.data[CONF_API_KEY] + ) + async def async_update_data(): """Get new data from the API.""" if CONF_CITY in config_entry.data: @@ -185,10 +220,14 @@ async def async_setup_entry(hass, config_entry): hass, LOGGER, name="geography data", - update_interval=DEFAULT_GEOGRAPHY_SCAN_INTERVAL, + update_interval=update_interval, update_method=async_update_data, ) + # Ensure any other, existing config entries that use this API key are updated + # with the new scan interval: + async_reset_coordinator_update_intervals(hass, update_interval) + # Only geography-based entries have options: config_entry.add_update_listener(async_update_options) else: From 5bef1c223d57b8f28d824d0d3c9fd11617c14d91 Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Thu, 21 May 2020 11:19:20 +0200 Subject: [PATCH 124/406] Add climate platform to ozw (#35566) --- homeassistant/components/ozw/climate.py | 339 ++++++++++++++++++++ homeassistant/components/ozw/const.py | 9 +- homeassistant/components/ozw/discovery.py | 101 ++++++ tests/components/ozw/conftest.py | 17 + tests/components/ozw/test_climate.py | 220 +++++++++++++ tests/fixtures/ozw/climate.json | 54 ++++ tests/fixtures/ozw/climate_network_dump.csv | 140 ++++++++ 7 files changed, 879 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/ozw/climate.py create mode 100644 tests/components/ozw/test_climate.py create mode 100644 tests/fixtures/ozw/climate.json create mode 100644 tests/fixtures/ozw/climate_network_dump.csv diff --git a/homeassistant/components/ozw/climate.py b/homeassistant/components/ozw/climate.py new file mode 100644 index 00000000000..92887069a84 --- /dev/null +++ b/homeassistant/components/ozw/climate.py @@ -0,0 +1,339 @@ +"""Support for Z-Wave climate devices.""" +from enum import IntEnum +import logging +from typing import Optional, Tuple + +from homeassistant.components.climate import DOMAIN as CLIMATE_DOMAIN, ClimateEntity +from homeassistant.components.climate.const import ( + ATTR_TARGET_TEMP_HIGH, + ATTR_TARGET_TEMP_LOW, + CURRENT_HVAC_COOL, + CURRENT_HVAC_FAN, + CURRENT_HVAC_HEAT, + CURRENT_HVAC_IDLE, + CURRENT_HVAC_OFF, + HVAC_MODE_AUTO, + HVAC_MODE_COOL, + HVAC_MODE_DRY, + HVAC_MODE_FAN_ONLY, + HVAC_MODE_HEAT, + HVAC_MODE_HEAT_COOL, + HVAC_MODE_OFF, + PRESET_NONE, + SUPPORT_FAN_MODE, + SUPPORT_PRESET_MODE, + SUPPORT_TARGET_TEMPERATURE, + SUPPORT_TARGET_TEMPERATURE_RANGE, +) +from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT +from homeassistant.core import callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect + +from .const import DATA_UNSUBSCRIBE, DOMAIN +from .entity import ZWaveDeviceEntity + +VALUE_LIST = "List" +VALUE_ID = "Value" +VALUE_LABEL = "Label" +VALUE_SELECTED_ID = "Selected_id" +VALUE_SELECTED_LABEL = "Selected" + +ATTR_FAN_ACTION = "fan_action" +ATTR_VALVE_POSITION = "valve_position" +_LOGGER = logging.getLogger(__name__) + + +class ThermostatMode(IntEnum): + """Enum with all (known/used) Z-Wave ThermostatModes.""" + + # https://github.com/OpenZWave/open-zwave/blob/master/cpp/src/command_classes/ThermostatMode.cpp + OFF = 0 + HEAT = 1 + COOL = 2 + AUTO = 3 + AUXILIARY = 4 + RESUME_ON = 5 + FAN = 6 + FURNANCE = 7 + DRY = 8 + MOIST = 9 + AUTO_CHANGE_OVER = 10 + HEATING_ECON = 11 + COOLING_ECON = 12 + AWAY = 13 + FULL_POWER = 15 + MANUFACTURER_SPECIFIC = 31 + + +MODE_SETPOINT_MAPPINGS = { + ThermostatMode.OFF: (), + ThermostatMode.HEAT: ("setpoint_heating",), + ThermostatMode.COOL: ("setpoint_cooling",), + ThermostatMode.AUTO: ("setpoint_heating", "setpoint_cooling"), + ThermostatMode.AUXILIARY: ("setpoint_heating",), + ThermostatMode.FURNANCE: ("setpoint_furnace",), + ThermostatMode.DRY: ("setpoint_dry_air",), + ThermostatMode.MOIST: ("setpoint_moist_air",), + ThermostatMode.AUTO_CHANGE_OVER: ("setpoint_auto_changeover",), + ThermostatMode.HEATING_ECON: ("setpoint_eco_heating",), + ThermostatMode.COOLING_ECON: ("setpoint_eco_cooling",), + ThermostatMode.AWAY: ("setpoint_away_heating", "setpoint_away_cooling"), + ThermostatMode.FULL_POWER: ("setpoint_full_power",), +} + + +# strings, OZW and/or qt-ozw does not send numeric values +# https://github.com/OpenZWave/open-zwave/blob/master/cpp/src/command_classes/ThermostatOperatingState.cpp +HVAC_CURRENT_MAPPINGS = { + "idle": CURRENT_HVAC_IDLE, + "heat": CURRENT_HVAC_HEAT, + "pending heat": CURRENT_HVAC_IDLE, + "heating": CURRENT_HVAC_HEAT, + "cool": CURRENT_HVAC_COOL, + "pending cool": CURRENT_HVAC_IDLE, + "cooling": CURRENT_HVAC_COOL, + "fan only": CURRENT_HVAC_FAN, + "vent / economiser": CURRENT_HVAC_FAN, + "off": CURRENT_HVAC_OFF, +} + + +# Map Z-Wave HVAC Mode to Home Assistant value +ZW_HVAC_MODE_MAPPINGS = { + ThermostatMode.OFF: HVAC_MODE_OFF, + ThermostatMode.HEAT: HVAC_MODE_HEAT, + ThermostatMode.COOL: HVAC_MODE_COOL, + ThermostatMode.AUTO: HVAC_MODE_AUTO, + ThermostatMode.AUXILIARY: HVAC_MODE_HEAT, + ThermostatMode.FAN: HVAC_MODE_FAN_ONLY, + ThermostatMode.FURNANCE: HVAC_MODE_HEAT, + ThermostatMode.DRY: HVAC_MODE_DRY, + ThermostatMode.AUTO_CHANGE_OVER: HVAC_MODE_HEAT_COOL, + ThermostatMode.HEATING_ECON: HVAC_MODE_HEAT, + ThermostatMode.COOLING_ECON: HVAC_MODE_COOL, + ThermostatMode.AWAY: HVAC_MODE_HEAT_COOL, + ThermostatMode.FULL_POWER: HVAC_MODE_HEAT, +} + +# Map Home Assistant HVAC Mode to Z-Wave value +HVAC_MODE_ZW_MAPPINGS = { + HVAC_MODE_OFF: ThermostatMode.OFF, + HVAC_MODE_HEAT: ThermostatMode.HEAT, + HVAC_MODE_COOL: ThermostatMode.COOL, + HVAC_MODE_AUTO: ThermostatMode.AUTO, + HVAC_MODE_FAN_ONLY: ThermostatMode.FAN, + HVAC_MODE_DRY: ThermostatMode.DRY, + HVAC_MODE_HEAT_COOL: ThermostatMode.AUTO_CHANGE_OVER, +} + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up Z-Wave Climate from Config Entry.""" + + @callback + def async_add_climate(values): + """Add Z-Wave Climate.""" + async_add_entities([ZWaveClimateEntity(values)]) + + hass.data[DOMAIN][config_entry.entry_id][DATA_UNSUBSCRIBE].append( + async_dispatcher_connect( + hass, f"{DOMAIN}_new_{CLIMATE_DOMAIN}", async_add_climate + ) + ) + + +class ZWaveClimateEntity(ZWaveDeviceEntity, ClimateEntity): + """Representation of a Z-Wave Climate device.""" + + def __init__(self, values): + """Initialize the entity.""" + super().__init__(values) + self._current_mode_setpoint_values = self._get_current_mode_setpoint_values() + + @callback + def on_value_update(self): + """Call when the underlying value(s) is added or updated.""" + self._current_mode_setpoint_values = self._get_current_mode_setpoint_values() + + @property + def hvac_mode(self): + """Return hvac operation ie. heat, cool mode.""" + if not self.values.mode: + return None + return ZW_HVAC_MODE_MAPPINGS.get( + self.values.mode.value[VALUE_SELECTED_ID], HVAC_MODE_AUTO + ) + + @property + def hvac_modes(self): + """Return the list of available hvac operation modes.""" + if not self.values.mode: + return [] + # Z-Wave uses one list for both modes and presets. Extract the unique modes + all_modes = [] + for val in self.values.mode.value[VALUE_LIST]: + hass_mode = ZW_HVAC_MODE_MAPPINGS.get(val[VALUE_ID]) + if hass_mode and hass_mode not in all_modes: + all_modes.append(hass_mode) + return all_modes + + @property + def fan_mode(self): + """Return the fan speed set.""" + return self.values.fan_mode.value[VALUE_SELECTED_LABEL] + + @property + def fan_modes(self): + """Return a list of available fan modes.""" + return [entry[VALUE_LABEL] for entry in self.values.fan_mode.value[VALUE_LIST]] + + @property + def temperature_unit(self): + """Return the unit of measurement.""" + if self.values.temperature and self.values.temperature.units == "F": + return TEMP_FAHRENHEIT + return TEMP_CELSIUS + + @property + def current_temperature(self): + """Return the current temperature.""" + if not self.values.temperature: + return None + return self.values.temperature.value + + @property + def hvac_action(self): + """Return the current running hvac operation if supported.""" + if not self.values.operating_state: + return None + cur_state = self.values.operating_state.value.lower() + return HVAC_CURRENT_MAPPINGS.get(cur_state) + + @property + def preset_mode(self): + """Return preset operation ie. eco, away.""" + # Z-Wave uses mode-values > 10 for presets + if self.values.mode.value[VALUE_SELECTED_ID] > 10: + return self.values.mode.value[VALUE_SELECTED_LABEL] + return PRESET_NONE + + @property + def preset_modes(self): + """Return the list of available preset operation modes.""" + # Z-Wave uses mode-values > 10 for presets + return [PRESET_NONE] + [ + val[VALUE_LABEL] + for val in self.values.mode.value[VALUE_LIST] + if val[VALUE_ID] > 10 + ] + + @property + def target_temperature(self): + """Return the temperature we try to reach.""" + return self._current_mode_setpoint_values[0].value + + @property + def target_temperature_low(self) -> Optional[float]: + """Return the lowbound target temperature we try to reach.""" + return self._current_mode_setpoint_values[0].value + + @property + def target_temperature_high(self) -> Optional[float]: + """Return the highbound target temperature we try to reach.""" + return self._current_mode_setpoint_values[1].value + + async def async_set_temperature(self, **kwargs): + """Set new target temperature. + + Must know if single or double setpoint. + """ + if len(self._current_mode_setpoint_values) == 1: + setpoint = self._current_mode_setpoint_values[0] + target_temp = kwargs.get(ATTR_TEMPERATURE) + if setpoint is not None and target_temp is not None: + setpoint.send_value(target_temp) + elif len(self._current_mode_setpoint_values) == 2: + (setpoint_low, setpoint_high) = self._current_mode_setpoint_values + target_temp_low = kwargs.get(ATTR_TARGET_TEMP_LOW) + target_temp_high = kwargs.get(ATTR_TARGET_TEMP_HIGH) + if setpoint_low is not None and target_temp_low is not None: + setpoint_low.send_value(target_temp_low) + if setpoint_high is not None and target_temp_high is not None: + setpoint_high.send_value(target_temp_high) + + async def async_set_fan_mode(self, fan_mode): + """Set new target fan mode.""" + # get id for this fan_mode + fan_mode_value = _get_list_id(self.values.fan_mode.value[VALUE_LIST], fan_mode) + if fan_mode_value is None: + _LOGGER.warning("Received an invalid fan mode: %s", fan_mode) + return + self.values.fan_mode.send_value(fan_mode_value) + + async def async_set_hvac_mode(self, hvac_mode): + """Set new target hvac mode.""" + if not self.values.mode: + return + if hvac_mode not in self.hvac_modes: + _LOGGER.warning("Received an invalid hvac mode: %s", hvac_mode) + return + hvac_mode_value = HVAC_MODE_ZW_MAPPINGS.get(hvac_mode) + self.values.mode.send_value(hvac_mode_value) + + async def async_set_preset_mode(self, preset_mode): + """Set new target preset mode.""" + if preset_mode == PRESET_NONE: + # try to restore to the (translated) main hvac mode + await self.async_set_hvac_mode(self.hvac_mode) + return + preset_mode_value = _get_list_id( + self.values.mode.value[VALUE_LIST], preset_mode + ) + if preset_mode_value is None: + _LOGGER.warning("Received an invalid preset mode: %s", preset_mode) + return + self.values.mode.send_value(preset_mode_value) + + @property + def device_state_attributes(self): + """Return the optional state attributes.""" + data = super().device_state_attributes + if self.values.fan_action: + data[ATTR_FAN_ACTION] = self.values.fan_action.value + if self.values.valve_position: + data[ + ATTR_VALVE_POSITION + ] = f"{self.values.valve_position.value} {self.values.valve_position.units}" + return data + + @property + def supported_features(self): + """Return the list of supported features.""" + support = 0 + if len(self._current_mode_setpoint_values) == 1: + support |= SUPPORT_TARGET_TEMPERATURE + if len(self._current_mode_setpoint_values) > 1: + support |= SUPPORT_TARGET_TEMPERATURE_RANGE + if self.values.fan_mode: + support |= SUPPORT_FAN_MODE + if self.values.mode: + support |= SUPPORT_PRESET_MODE + return support + + def _get_current_mode_setpoint_values(self) -> Tuple: + """Return a tuple of current setpoint Z-Wave value(s).""" + current_mode = self.values.mode.value[VALUE_SELECTED_ID] + setpoint_names = MODE_SETPOINT_MAPPINGS.get(current_mode, ()) + # we do not want None values in our tuple so check if the value exists + return tuple( + getattr(self.values, value_name) + for value_name in setpoint_names + if getattr(self.values, value_name, None) + ) + + +def _get_list_id(value_lst, value_lbl): + """Return the id for the value in the list.""" + return next( + (val[VALUE_ID] for val in value_lst if val[VALUE_LABEL] == value_lbl), None + ) diff --git a/homeassistant/components/ozw/const.py b/homeassistant/components/ozw/const.py index 59f189d124d..aaf67479ba5 100644 --- a/homeassistant/components/ozw/const.py +++ b/homeassistant/components/ozw/const.py @@ -1,12 +1,19 @@ """Constants for the ozw integration.""" from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN +from homeassistant.components.climate import DOMAIN as CLIMATE_DOMAIN from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN DOMAIN = "ozw" DATA_UNSUBSCRIBE = "unsubscribe" -PLATFORMS = [BINARY_SENSOR_DOMAIN, LIGHT_DOMAIN, SENSOR_DOMAIN, SWITCH_DOMAIN] +PLATFORMS = [ + BINARY_SENSOR_DOMAIN, + CLIMATE_DOMAIN, + LIGHT_DOMAIN, + SENSOR_DOMAIN, + SWITCH_DOMAIN, +] # MQTT Topics TOPIC_OPENZWAVE = "OpenZWave" diff --git a/homeassistant/components/ozw/discovery.py b/homeassistant/components/ozw/discovery.py index 38940c9ed6e..297b00e4a88 100644 --- a/homeassistant/components/ozw/discovery.py +++ b/homeassistant/components/ozw/discovery.py @@ -30,6 +30,107 @@ DISCOVERY_SCHEMAS = ( } }, }, + { # Z-Wave Thermostat device translates to Climate entity + const.DISC_COMPONENT: "climate", + const.DISC_GENERIC_DEVICE_CLASS: ( + const_ozw.GENERIC_TYPE_THERMOSTAT, + const_ozw.GENERIC_TYPE_SENSOR_MULTILEVEL, + ), + const.DISC_SPECIFIC_DEVICE_CLASS: ( + const_ozw.SPECIFIC_TYPE_THERMOSTAT_GENERAL, + const_ozw.SPECIFIC_TYPE_THERMOSTAT_GENERAL_V2, + const_ozw.SPECIFIC_TYPE_SETBACK_THERMOSTAT, + const_ozw.SPECIFIC_TYPE_THERMOSTAT_HEATING, + const_ozw.SPECIFIC_TYPE_SETPOINT_THERMOSTAT, + const_ozw.SPECIFIC_TYPE_NOT_USED, + ), + const.DISC_VALUES: { + const.DISC_PRIMARY: { + const.DISC_COMMAND_CLASS: (CommandClass.THERMOSTAT_MODE,) + }, + "mode": { + const.DISC_COMMAND_CLASS: (CommandClass.THERMOSTAT_MODE,), + const.DISC_OPTIONAL: True, + }, + "temperature": { + const.DISC_COMMAND_CLASS: (CommandClass.SENSOR_MULTILEVEL,), + const.DISC_INDEX: (1,), + const.DISC_OPTIONAL: True, + }, + "fan_mode": { + const.DISC_COMMAND_CLASS: (CommandClass.THERMOSTAT_FAN_MODE,), + const.DISC_OPTIONAL: True, + }, + "operating_state": { + const.DISC_COMMAND_CLASS: (CommandClass.THERMOSTAT_OPERATING_STATE,), + const.DISC_OPTIONAL: True, + }, + "fan_action": { + const.DISC_COMMAND_CLASS: (CommandClass.THERMOSTAT_FAN_STATE,), + const.DISC_OPTIONAL: True, + }, + "valve_position": { + const.DISC_COMMAND_CLASS: (CommandClass.SWITCH_MULTILEVEL,), + const.DISC_INDEX: (0,), + const.DISC_OPTIONAL: True, + }, + "setpoint_heating": { + const.DISC_COMMAND_CLASS: (CommandClass.THERMOSTAT_SETPOINT,), + const.DISC_INDEX: (1,), + const.DISC_OPTIONAL: True, + }, + "setpoint_cooling": { + const.DISC_COMMAND_CLASS: (CommandClass.THERMOSTAT_SETPOINT,), + const.DISC_INDEX: (2,), + const.DISC_OPTIONAL: True, + }, + "setpoint_furnace": { + const.DISC_COMMAND_CLASS: (CommandClass.THERMOSTAT_SETPOINT,), + const.DISC_INDEX: (7,), + const.DISC_OPTIONAL: True, + }, + "setpoint_dry_air": { + const.DISC_COMMAND_CLASS: (CommandClass.THERMOSTAT_SETPOINT,), + const.DISC_INDEX: (8,), + const.DISC_OPTIONAL: True, + }, + "setpoint_moist_air": { + const.DISC_COMMAND_CLASS: (CommandClass.THERMOSTAT_SETPOINT,), + const.DISC_INDEX: (9,), + const.DISC_OPTIONAL: True, + }, + "setpoint_auto_changeover": { + const.DISC_COMMAND_CLASS: (CommandClass.THERMOSTAT_SETPOINT,), + const.DISC_INDEX: (10,), + const.DISC_OPTIONAL: True, + }, + "setpoint_eco_heating": { + const.DISC_COMMAND_CLASS: (CommandClass.THERMOSTAT_SETPOINT,), + const.DISC_INDEX: (11,), + const.DISC_OPTIONAL: True, + }, + "setpoint_eco_cooling": { + const.DISC_COMMAND_CLASS: (CommandClass.THERMOSTAT_SETPOINT,), + const.DISC_INDEX: (12,), + const.DISC_OPTIONAL: True, + }, + "setpoint_away_heating": { + const.DISC_COMMAND_CLASS: (CommandClass.THERMOSTAT_SETPOINT,), + const.DISC_INDEX: (13,), + const.DISC_OPTIONAL: True, + }, + "setpoint_away_cooling": { + const.DISC_COMMAND_CLASS: (CommandClass.THERMOSTAT_SETPOINT,), + const.DISC_INDEX: (14,), + const.DISC_OPTIONAL: True, + }, + "setpoint_full_power": { + const.DISC_COMMAND_CLASS: (CommandClass.THERMOSTAT_SETPOINT,), + const.DISC_INDEX: (15,), + const.DISC_OPTIONAL: True, + }, + }, + }, { # Light const.DISC_COMPONENT: "light", const.DISC_GENERIC_DEVICE_CLASS: ( diff --git a/tests/components/ozw/conftest.py b/tests/components/ozw/conftest.py index b984172d355..568b2137d9a 100644 --- a/tests/components/ozw/conftest.py +++ b/tests/components/ozw/conftest.py @@ -21,6 +21,12 @@ def light_data_fixture(): return load_fixture("ozw/light_network_dump.csv") +@pytest.fixture(name="climate_data", scope="session") +def climate_data_fixture(): + """Load climate MQTT data and return it.""" + return load_fixture("ozw/climate_network_dump.csv") + + @pytest.fixture(name="sent_messages") def sent_messages_fixture(): """Fixture to capture sent messages.""" @@ -88,3 +94,14 @@ async def binary_sensor_alt_msg_fixture(hass): message = MQTTMessage(topic=sensor_json["topic"], payload=sensor_json["payload"]) message.encode() return message + + +@pytest.fixture(name="climate_msg") +async def climate_msg_fixture(hass): + """Return a mock MQTT msg with a climate mode change message.""" + sensor_json = json.loads( + await hass.async_add_executor_job(load_fixture, "ozw/climate.json") + ) + message = MQTTMessage(topic=sensor_json["topic"], payload=sensor_json["payload"]) + message.encode() + return message diff --git a/tests/components/ozw/test_climate.py b/tests/components/ozw/test_climate.py new file mode 100644 index 00000000000..c0f4e362735 --- /dev/null +++ b/tests/components/ozw/test_climate.py @@ -0,0 +1,220 @@ +"""Test Z-Wave Multi-setpoint Climate entities.""" +from homeassistant.components.climate import ATTR_TEMPERATURE +from homeassistant.components.climate.const import ( + ATTR_CURRENT_TEMPERATURE, + ATTR_FAN_MODE, + ATTR_FAN_MODES, + ATTR_HVAC_ACTION, + ATTR_HVAC_MODES, + ATTR_PRESET_MODES, + ATTR_TARGET_TEMP_HIGH, + ATTR_TARGET_TEMP_LOW, + CURRENT_HVAC_IDLE, + HVAC_MODE_AUTO, + HVAC_MODE_COOL, + HVAC_MODE_HEAT, + HVAC_MODE_OFF, +) + +from .common import setup_ozw + + +async def test_climate(hass, climate_data, sent_messages, climate_msg, caplog): + """Test setting up config entry.""" + receive_message = await setup_ozw(hass, fixture=climate_data) + + # Test multi-setpoint thermostat (node 7 in dump) + # mode is heat, this should be single setpoint + state = hass.states.get("climate.ct32_thermostat_mode") + assert state is not None + assert state.state == HVAC_MODE_HEAT + assert state.attributes[ATTR_HVAC_MODES] == [ + HVAC_MODE_OFF, + HVAC_MODE_HEAT, + HVAC_MODE_COOL, + HVAC_MODE_AUTO, + ] + assert state.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_IDLE + assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 23.1 + assert state.attributes[ATTR_TEMPERATURE] == 21.1 + assert state.attributes.get(ATTR_TARGET_TEMP_LOW) is None + assert state.attributes.get(ATTR_TARGET_TEMP_HIGH) is None + assert state.attributes[ATTR_FAN_MODE] == "Auto Low" + assert state.attributes[ATTR_FAN_MODES] == ["Auto Low", "On Low"] + + # Test set target temperature + await hass.services.async_call( + "climate", + "set_temperature", + {"entity_id": "climate.ct32_thermostat_mode", "temperature": 26.1}, + blocking=True, + ) + assert len(sent_messages) == 1 + msg = sent_messages[-1] + assert msg["topic"] == "OpenZWave/1/command/setvalue/" + # Celsius is converted to Fahrenheit here! + assert round(msg["payload"]["Value"], 2) == 78.98 + assert msg["payload"]["ValueIDKey"] == 281475099443218 + + # Test set mode + await hass.services.async_call( + "climate", + "set_hvac_mode", + {"entity_id": "climate.ct32_thermostat_mode", "hvac_mode": HVAC_MODE_AUTO}, + blocking=True, + ) + assert len(sent_messages) == 2 + msg = sent_messages[-1] + assert msg["topic"] == "OpenZWave/1/command/setvalue/" + assert msg["payload"] == {"Value": 3, "ValueIDKey": 122683412} + + # Test set missing mode + await hass.services.async_call( + "climate", + "set_hvac_mode", + {"entity_id": "climate.ct32_thermostat_mode", "hvac_mode": "fan_only"}, + blocking=True, + ) + assert len(sent_messages) == 2 + assert "Received an invalid hvac mode: fan_only" in caplog.text + + # Test set fan mode + await hass.services.async_call( + "climate", + "set_fan_mode", + {"entity_id": "climate.ct32_thermostat_mode", "fan_mode": "On Low"}, + blocking=True, + ) + assert len(sent_messages) == 3 + msg = sent_messages[-1] + assert msg["topic"] == "OpenZWave/1/command/setvalue/" + assert msg["payload"] == {"Value": 1, "ValueIDKey": 122748948} + + # Test set invalid fan mode + await hass.services.async_call( + "climate", + "set_fan_mode", + {"entity_id": "climate.ct32_thermostat_mode", "fan_mode": "invalid fan mode"}, + blocking=True, + ) + assert len(sent_messages) == 3 + assert "Received an invalid fan mode: invalid fan mode" in caplog.text + + # Test incoming mode change to auto, + # resulting in multiple setpoints + receive_message(climate_msg) + await hass.async_block_till_done() + state = hass.states.get("climate.ct32_thermostat_mode") + assert state is not None + assert state.state == HVAC_MODE_AUTO + assert state.attributes.get(ATTR_TEMPERATURE) is None + assert state.attributes[ATTR_TARGET_TEMP_LOW] == 21.1 + assert state.attributes[ATTR_TARGET_TEMP_HIGH] == 25.6 + + # Test setting high/low temp on multiple setpoints + await hass.services.async_call( + "climate", + "set_temperature", + { + "entity_id": "climate.ct32_thermostat_mode", + "target_temp_low": 20, + "target_temp_high": 25, + }, + blocking=True, + ) + assert len(sent_messages) == 5 # 2 messages ! + msg = sent_messages[-2] # low setpoint + assert msg["topic"] == "OpenZWave/1/command/setvalue/" + assert round(msg["payload"]["Value"], 2) == 68.0 + assert msg["payload"]["ValueIDKey"] == 281475099443218 + msg = sent_messages[-1] # high setpoint + assert msg["topic"] == "OpenZWave/1/command/setvalue/" + assert round(msg["payload"]["Value"], 2) == 77.0 + assert msg["payload"]["ValueIDKey"] == 562950076153874 + + # Test basic/single-setpoint thermostat (node 16 in dump) + state = hass.states.get("climate.komforthaus_spirit_z_wave_plus_mode") + assert state is not None + assert state.state == HVAC_MODE_HEAT + assert state.attributes[ATTR_HVAC_MODES] == [ + HVAC_MODE_OFF, + HVAC_MODE_HEAT, + ] + assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 17.3 + assert round(state.attributes[ATTR_TEMPERATURE], 0) == 19 + assert state.attributes.get(ATTR_TARGET_TEMP_LOW) is None + assert state.attributes.get(ATTR_TARGET_TEMP_HIGH) is None + assert state.attributes[ATTR_PRESET_MODES] == [ + "none", + "Heat Eco", + "Full Power", + "Manufacturer Specific", + ] + + # Test set target temperature + await hass.services.async_call( + "climate", + "set_temperature", + { + "entity_id": "climate.komforthaus_spirit_z_wave_plus_mode", + "temperature": 28.0, + }, + blocking=True, + ) + assert len(sent_messages) == 6 + msg = sent_messages[-1] + assert msg["topic"] == "OpenZWave/1/command/setvalue/" + assert msg["payload"] == { + "Value": 28.0, + "ValueIDKey": 281475250438162, + } + + # Test set preset mode + await hass.services.async_call( + "climate", + "set_preset_mode", + { + "entity_id": "climate.komforthaus_spirit_z_wave_plus_mode", + "preset_mode": "Heat Eco", + }, + blocking=True, + ) + assert len(sent_messages) == 7 + msg = sent_messages[-1] + assert msg["topic"] == "OpenZWave/1/command/setvalue/" + assert msg["payload"] == { + "Value": 11, + "ValueIDKey": 273678356, + } + + # Test set preset mode None + # This preset should set and return to current hvac mode + await hass.services.async_call( + "climate", + "set_preset_mode", + { + "entity_id": "climate.komforthaus_spirit_z_wave_plus_mode", + "preset_mode": "none", + }, + blocking=True, + ) + assert len(sent_messages) == 8 + msg = sent_messages[-1] + assert msg["topic"] == "OpenZWave/1/command/setvalue/" + assert msg["payload"] == { + "Value": 1, + "ValueIDKey": 273678356, + } + + # Test set invalid preset mode + await hass.services.async_call( + "climate", + "set_preset_mode", + { + "entity_id": "climate.komforthaus_spirit_z_wave_plus_mode", + "preset_mode": "invalid preset mode", + }, + blocking=True, + ) + assert len(sent_messages) == 8 + assert "Received an invalid preset mode: invalid preset mode" in caplog.text diff --git a/tests/fixtures/ozw/climate.json b/tests/fixtures/ozw/climate.json new file mode 100644 index 00000000000..652dc9aef26 --- /dev/null +++ b/tests/fixtures/ozw/climate.json @@ -0,0 +1,54 @@ +{ + "topic": "OpenZWave/1/node/7/instance/1/commandclass/64/value/122683412/", + "payload": { + "Label": "Mode", + "Value": { + "List": [ + { + "Value": 0, + "Label": "Off" + }, + { + "Value": 1, + "Label": "Heat" + }, + { + "Value": 2, + "Label": "Cool" + }, + { + "Value": 3, + "Label": "Auto" + }, + { + "Value": 11, + "Label": "Heat Econ" + }, + { + "Value": 12, + "Label": "Cool Econ" + } + ], + "Selected": "Auto", + "Selected_id": 3 + }, + "Units": "", + "Min": 0, + "Max": 0, + "Type": "List", + "Instance": 1, + "CommandClass": "COMMAND_CLASS_THERMOSTAT_MODE", + "Index": 0, + "Node": 7, + "Genre": "User", + "Help": "Set the Thermostat Mode", + "ValueIDKey": 122683412, + "ReadOnly": false, + "WriteOnly": false, + "ValueSet": false, + "ValuePolled": false, + "ChangeVerified": false, + "Event": "valueAdded", + "TimeStamp": 1588264894 + } +} \ No newline at end of file diff --git a/tests/fixtures/ozw/climate_network_dump.csv b/tests/fixtures/ozw/climate_network_dump.csv new file mode 100644 index 00000000000..c865e6438de --- /dev/null +++ b/tests/fixtures/ozw/climate_network_dump.csv @@ -0,0 +1,140 @@ +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/7/,{ "NodeID": 7, "NodeQueryStage": "Complete", "isListening": true, "isFlirs": false, "isBeaming": false, "isRouting": false, "isSecurityv1": false, "isZWavePlus": true, "isNIFRecieved": true, "isAwake": true, "isFailed": false, "MetaData": { "OZWInfoURL": "", "ZWAProductURL": "", "ProductPic": "", "Description": "", "ProductManualURL": "", "ProductPageURL": "", "InclusionHelp": "", "ExclusionHelp": "", "ResetHelp": "", "WakeupHelp": "", "ProductSupportURL": "", "Frequency": "", "Name": "", "ProductPicBase64": "" }, "Event": "nodeQueriesComplete", "TimeStamp": 1588264908, "NodeManufacturerName": "2GIG Technologies", "NodeProductName": "CT32 Thermostat", "NodeBasicString": "Routing Slave", "NodeBasic": 4, "NodeGenericString": "Thermostat", "NodeGeneric": 8, "NodeSpecificString": "General Thermostat V2", "NodeSpecific": 6, "NodeManufacturerID": "0x0098", "NodeProductType": "0x2002", "NodeProductID": "0x0100", "NodeBaudRate": 100000, "NodeVersion": 4, "NodeName": "", "NodeLocation": "", "NodeDeviceTypeString": "Thermostat HVAC", "NodeDeviceType": 4608, "NodeRole": 7, "NodeRoleString": "Listening Sleeping Slave", "NodePlusType": 0, "NodePlusTypeString": "Z-Wave+ node", "Neighbors": [ 1, 2 ]} +OpenZWave/1/node/7/instance/1/,{ "Instance": 1, "TimeStamp": 1588264894} +OpenZWave/1/node/7/instance/1/commandclass/112/,{ "Instance": 1, "CommandClassId": 112, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "TimeStamp": 1588264893} +OpenZWave/1/node/7/instance/1/commandclass/112/value/281475104374804/,{ "Label": "Temperature Reporting Threshold", "Value": { "List": [ { "Value": 0, "Label": "Disabled" }, { "Value": 1, "Label": "0.5F" }, { "Value": 2, "Label": "1.0F" }, { "Value": 3, "Label": "1.5F" }, { "Value": 4, "Label": "2.0F" } ], "Selected": "1.0F", "Selected_id": 2 }, "Units": "", "Min": 0, "Max": 4, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 1, "Node": 7, "Genre": "Config", "Help": "The Temperature Reporting Threshold Configuration Set Command sets the reporting threshold for changes in the ambient temperature as detected by the thermostat.", "ValueIDKey": 281475104374804, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588264893} +OpenZWave/1/node/7/instance/1/commandclass/112/value/562950081085460/,{ "Label": "HVAC Settings", "Value": { "List": [ { "Value": 17891585, "Label": "HVAC: Normal, Aux Stages: 1, Aux Setup: Gas, Heat Pump Stages: 1, Cool Stages: 1" }, { "Value": 34668801, "Label": "HVAC: Heat Pump, Aux Stages: 1, Aux Setup: Gas, Heat Pump Stages: 1, Cool Stages: 1" }, { "Value": 18940161, "Label": "HVAC: Normal, Aux Stages: 2, Aux Setup: Gas, Heat Pump Stages: 1, Cool Stages: 1" }, { "Value": 35717377, "Label": "HVAC: Heat Pump, Aux Stages: 2, Aux Setup: Gas, Heat Pump Stages: 1, Cool Stages: 1" }, { "Value": 17957121, "Label": "HVAC: Normal, Aux Stages: 1, Aux Setup: Elec, Heat Pump Stages: 1, Cool Stages: 1" }, { "Value": 34734337, "Label": "HVAC: Heat Pump, Aux Stages: 1, Aux Setup: Elec, Heat Pump Stages: 1, Cool Stages: 1" }, { "Value": 19005697, "Label": "HVAC: Normal, Aux Stages: 2, Aux Setup: Elec, Heat Pump Stages: 1, Cool Stages: 1" }, { "Value": 35782913, "Label": "HVAC: Heat Pump, Aux Stages: 2, Aux Setup: Elec, Heat Pump Stages: 1, Cool Stages: 1" }, { "Value": 17891841, "Label": "HVAC: Normal, Aux Stages: 1, Aux Setup: Gas, Heat Pump Stages: 2, Cool Stages: 1" }, { "Value": 34669057, "Label": "HVAC: Heat Pump, Aux Stages: 1, Aux Setup: Gas, Heat Pump Stages: 2, Cool Stages: 1" }, { "Value": 18940417, "Label": "HVAC: Normal, Aux Stages: 2, Aux Setup: Gas, Heat Pump Stages: 2, Cool Stages: 1" }, { "Value": 35717633, "Label": "HVAC: Heat Pump, Aux Stages: 2, Aux Setup: Gas, Heat Pump Stages: 2, Cool Stages: 1" }, { "Value": 17957377, "Label": "HVAC: Normal, Aux Stages: 1, Aux Setup: Elec, Heat Pump Stages: 2, Cool Stages: 1" }, { "Value": 34734593, "Label": "HVAC: Heat Pump, Aux Stages: 1, Aux Setup: Elec, Heat Pump Stages: 2, Cool Stages: 1" }, { "Value": 19005953, "Label": "HVAC: Normal, Aux Stages: 2, Aux Setup: Elec, Heat Pump Stages: 2, Cool Stages: 1" }, { "Value": 35783169, "Label": "HVAC: Heat Pump, Aux Stages: 2, Aux Setup: Elec, Heat Pump Stages: 2, Cool Stages: 1" }, { "Value": 17891586, "Label": "HVAC: Normal, Aux Stages: 1, Aux Setup: Gas, Heat Pump Stages: 1, Cool Stages: 2" }, { "Value": 34668802, "Label": "HVAC: Heat Pump, Aux Stages: 1, Aux Setup: Gas, Heat Pump Stages: 1, Cool Stages: 2" }, { "Value": 18940162, "Label": "HVAC: Normal, Aux Stages: 2, Aux Setup: Gas, Heat Pump Stages: 1, Cool Stages: 2" }, { "Value": 35717378, "Label": "HVAC: Heat Pump, Aux Stages: 2, Aux Setup: Gas, Heat Pump Stages: 1, Cool Stages: 2" }, { "Value": 17957122, "Label": "HVAC: Normal, Aux Stages: 1, Aux Setup: Elec, Heat Pump Stages: 1, Cool Stages: 2" }, { "Value": 34734338, "Label": "HVAC: Heat Pump, Aux Stages: 1, Aux Setup: Elec, Heat Pump Stages: 1, Cool Stages: 2" }, { "Value": 19005698, "Label": "HVAC: Normal, Aux Stages: 2, Aux Setup: Elec, Heat Pump Stages: 1, Cool Stages: 2" }, { "Value": 35782914, "Label": "HVAC: Heat Pump, Aux Stages: 2, Aux Setup: Elec, Heat Pump Stages: 1, Cool Stages: 2" }, { "Value": 17891842, "Label": "HVAC: Normal, Aux Stages: 1, Aux Setup: Gas, Heat Pump Stages: 2, Cool Stages: 2" }, { "Value": 34669058, "Label": "HVAC: Heat Pump, Aux Stages: 1, Aux Setup: Gas, Heat Pump Stages: 2, Cool Stages: 2" }, { "Value": 18940418, "Label": "HVAC: Normal, Aux Stages: 2, Aux Setup: Gas, Heat Pump Stages: 2, Cool Stages: 2" }, { "Value": 35717634, "Label": "HVAC: Heat Pump, Aux Stages: 2, Aux Setup: Gas, Heat Pump Stages: 2, Cool Stages: 2" }, { "Value": 17957378, "Label": "HVAC: Normal, Aux Stages: 1, Aux Setup: Elec, Heat Pump Stages: 2, Cool Stages: 2" }, { "Value": 34734594, "Label": "HVAC: Heat Pump, Aux Stages: 1, Aux Setup: Elec, Heat Pump Stages: 2, Cool Stages: 2" }, { "Value": 19005954, "Label": "HVAC: Normal, Aux Stages: 2, Aux Setup: Elec, Heat Pump Stages: 2, Cool Stages: 2" }, { "Value": 35783170, "Label": "HVAC: Heat Pump, Aux Stages: 2, Aux Setup: Elec, Heat Pump Stages: 2, Cool Stages: 2" } ], "Selected": "HVAC: Normal, Aux Stages: 1, Aux Setup: Gas, Heat Pump Stages: 1, Cool Stages: 1", "Selected_id": 17891585 }, "Units": "", "Min": 0, "Max": 2147483647, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 2, "Node": 7, "Genre": "Config", "Help": "Bits 0 - 7 -> HVAC Setup: Normal (0x01) or Heat Pump (0x02) Bits 8 - 11 -> Number of Auxiliary Stages (Heat Pump) / Number of Heat Stages (Normal) Bits 12 - 15 -> Aux Setup: Gas (0x01) or Electric (0x02) Bits 16 - 23 -> Number of Heat Pump Stages Bits 24 - 31 -> Number of Cool Stages", "ValueIDKey": 562950081085460, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588264893} +OpenZWave/1/node/7/instance/1/commandclass/112/value/844425057796116/,{ "Label": "Utility Lock", "Value": { "List": [ { "Value": 0, "Label": "Disable" }, { "Value": 1, "Label": "Enable" } ], "Selected": "Disable", "Selected_id": 0 }, "Units": "", "Min": 0, "Max": 1, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 3, "Node": 7, "Genre": "Config", "Help": "The Utility Lock Configuration Set command enables or disables the utility lock. If the utility lock is enabled, the setpoint cannot be modified directly via the thermostat screen.", "ValueIDKey": 844425057796116, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588264893} +OpenZWave/1/node/7/instance/1/commandclass/112/value/1125900034506772/,{ "Label": "C-Wire/Battery Status", "Value": { "List": [ { "Value": 1, "Label": "C-Wire" }, { "Value": 2, "Label": "Battery" } ], "Selected": "C-Wire", "Selected_id": 1 }, "Units": "", "Min": 1, "Max": 2, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 4, "Node": 7, "Genre": "Config", "Help": "1 -> C-Wire 2 -> Battery", "ValueIDKey": 1125900034506772, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588264893} +OpenZWave/1/node/7/instance/1/commandclass/112/value/1407375011217428/,{ "Label": "Humidity Reporting Threshold", "Value": { "List": [ { "Value": 0, "Label": "Disabled" }, { "Value": 1, "Label": "3% RH" }, { "Value": 2, "Label": "5% RH" }, { "Value": 3, "Label": "10% RH" } ], "Selected": "5% RH", "Selected_id": 2 }, "Units": "", "Min": 0, "Max": 3, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 5, "Node": 7, "Genre": "Config", "Help": "The Humidity Reporting Threshold Configuration Set Command sets the reporting threshold for changes in the ambient humidity as detected by the thermostat.", "ValueIDKey": 1407375011217428, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588264893} +OpenZWave/1/node/7/instance/1/commandclass/112/value/1688849987928084/,{ "Label": "Auxiliary/Emergency heat", "Value": { "List": [ { "Value": 0, "Label": "Disabled" }, { "Value": 1, "Label": "Enabled" } ], "Selected": "Disabled", "Selected_id": 0 }, "Units": "", "Min": 0, "Max": 1, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 6, "Node": 7, "Genre": "Config", "Help": "The Auxiliary/Emergency configuration command enables or disables auxiliary / emergency heating in the thermostat. Auxiliary / emergency heating is only available if the thermostat is configured in heat pump mode and with at least one stage of auxiliary heating. This command enables auxiliary / emergency heating when the thermostat is in Auto mode. The Thermostat Set Mode command with mode Auxiliary/Emergency Heat will enable emergency heating but only if the thermostat is in Heat mode. This command should only be used on thermsotats that support Auxiliary/Emergency Heat thermostat mode.", "ValueIDKey": 1688849987928084, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588264893} +OpenZWave/1/node/7/instance/1/commandclass/112/value/1970324964638740/,{ "Label": "Thermostat Swing Temperature", "Value": { "List": [ { "Value": 1, "Label": "0.5F" }, { "Value": 2, "Label": "1.0F" }, { "Value": 3, "Label": "1.5F" }, { "Value": 4, "Label": "2.0F" }, { "Value": 5, "Label": "2.5F" }, { "Value": 6, "Label": "3.0F" }, { "Value": 7, "Label": "3.5F" }, { "Value": 8, "Label": "4.0F" } ], "Selected": "1.0F", "Selected_id": 2 }, "Units": "", "Min": 1, "Max": 8, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 7, "Node": 7, "Genre": "Config", "Help": "The Temperate Swing (HVAC cycling rate) is the desired variance in temperature between the thermostat setting and the room temperature required before the heating or cooling system will turn on.", "ValueIDKey": 1970324964638740, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588264893} +OpenZWave/1/node/7/instance/1/commandclass/112/value/2251799941349396/,{ "Label": "Thermostat Differential Temperature", "Value": { "List": [ { "Value": 4, "Label": "2.0F Heat" }, { "Value": 6, "Label": "3.0F Heat" }, { "Value": 8, "Label": "4.0F Heat" }, { "Value": 10, "Label": "5.0F Heat" }, { "Value": 12, "Label": "6.0F Heat" }, { "Value": 260, "Label": "2.0F Cool" }, { "Value": 262, "Label": "3.0F Cool" }, { "Value": 264, "Label": "4.0F Cool" }, { "Value": 266, "Label": "5.0F Cool" }, { "Value": 268, "Label": "6.0F Cool" } ], "Selected": "2.0F Heat", "Selected_id": 4 }, "Units": "F", "Min": 2, "Max": 32767, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 8, "Node": 7, "Genre": "Config", "Help": "(Set Only) The Thermostat Differential Temperature configuration command sets the differential temperature for multi-stage HVAC systems. The differential temperature delta defines when the thermostat will turn on additional stages. There are two differential temperatures, one for multistage cool systems and one for multistage heat systems. If the thermostat is not configured for multistage HVAC systems then these parameters have no effect.", "ValueIDKey": 2251799941349396, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588264893} +OpenZWave/1/node/7/instance/1/commandclass/112/value/2533274918060052/,{ "Label": "Thermostat Recovery Mode", "Value": { "List": [ { "Value": 1, "Label": "Fast" }, { "Value": 2, "Label": "Economy" } ], "Selected": "Economy", "Selected_id": 2 }, "Units": "", "Min": 1, "Max": 2, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 9, "Node": 7, "Genre": "Config", "Help": "The Thermostat Recovery Mode configuration command sets the HVAC recovery mode type. The recovery mode determines when additional HVAC stages are turned off as the ambient temperature returns to the target temperature. If the recovery mode is set to economy, the thermostat will turn off additional HVAC stages when the ambient temperature reaches the target temperature plus/minus the differential temperature. If the recovery mode is set to fast, the thermostat will leave all stages on (assuming they were already on) until the ambient temperature reaches the target temperature.", "ValueIDKey": 2533274918060052, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588264893} +OpenZWave/1/node/7/instance/1/commandclass/112/value/2814749894770710/,{ "Label": "Temperature Reporting Filter", "Value": 124, "Units": "F", "Min": 0, "Max": 124, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 10, "Node": 7, "Genre": "Config", "Help": "The Temperature Reporting Filter configuration command sets upper and lower bounds of the ambient temperature reporting. The thermostat won't report ambient temperature changes if the ambient temperature falls between these bounds. For example, if the upper bound is 80F and the lower bound is 60F, the thermostat will not send SENSOR_MULTI_LEVEL_REPORTS for ambient temperature values between 60F and 80F. The thermostat will only send ambient temperature changes if the thermostat has been added to an association group (see Command Class Association) and the temperature reporting threshold is non-zero (see Temperature Reporting Threshold). Input in hexadecimal only like so: 0x09 0x05 0x09 0x0A. It must always have four 1 byte sized numbers. The first two bytes control the lower temperature bound for the Temperature Reporting Filter the last two control the upper temperature bound. The first byte in the byte pair always refers to temperature scale (Celsius 0x01 or Fahrenheit 0x09). While the second byte in each byte pair is the bound temperature. The max/min temp you can use is 127 degrees. To convert decimal to hex goto: https://www.binaryhexconverter.com/decimal-to-hex-converter or you can use the built in Windows calculator program in Programmer mode. If you mess up your thermostat copy and paste 0x09 0x00 0x09 0x00 (for a F Thermostat) or 0x01 0x00 0x01 0x00 (for a C Thermostat). This will remove any bounds.", "ValueIDKey": 2814749894770710, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588264893} +OpenZWave/1/node/7/instance/1/commandclass/112/value/3096224871481364/,{ "Label": "Simple UI Mode", "Value": { "List": [ { "Value": 0, "Label": "Enable" }, { "Value": 1, "Label": "Disable" } ], "Selected": "Disable", "Selected_id": 1 }, "Units": "", "Min": 0, "Max": 1, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 11, "Node": 7, "Genre": "Config", "Help": "If the value is set to Disable then Normal Mode is enabled. If the value is set to Enable then Simple Mode is enabled.", "ValueIDKey": 3096224871481364, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588264893} +OpenZWave/1/node/7/instance/1/commandclass/112/value/3377699848192017/,{ "Label": "Multicast", "Value": 0, "Units": "", "Min": 0, "Max": 1, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 12, "Node": 7, "Genre": "Config", "Help": "If set to 0, multicast is disabled, if set to 1, will enable the multicast.", "ValueIDKey": 3377699848192017, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588264893} +OpenZWave/1/node/7/instance/1/commandclass/66/,{ "Instance": 1, "CommandClassId": 66, "CommandClass": "COMMAND_CLASS_THERMOSTAT_OPERATING_STATE", "TimeStamp": 1588264893} +OpenZWave/1/node/7/instance/1/commandclass/66/value/122716183/,{ "Label": "Operating State", "Value": "Idle", "Units": "", "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_THERMOSTAT_OPERATING_STATE", "Index": 0, "Node": 7, "Genre": "User", "Help": "Set the Thermostat Operating State", "ValueIDKey": 122716183, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588264893} +OpenZWave/1/node/7/instance/1/commandclass/69/,{ "Instance": 1, "CommandClassId": 69, "CommandClass": "COMMAND_CLASS_THERMOSTAT_FAN_STATE", "TimeStamp": 1588264893} +OpenZWave/1/node/7/instance/1/commandclass/69/value/122765335/,{ "Label": "Fan State", "Value": "Idle", "Units": "", "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_THERMOSTAT_FAN_STATE", "Index": 0, "Node": 7, "Genre": "User", "Help": "Set the Fan State", "ValueIDKey": 122765335, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588264893} +OpenZWave/1/node/7/instance/1/commandclass/94/,{ "Instance": 1, "CommandClassId": 94, "CommandClass": "COMMAND_CLASS_ZWAVEPLUS_INFO", "TimeStamp": 1588264893} +OpenZWave/1/node/7/instance/1/commandclass/94/value/131563537/,{ "Label": "ZWave+ Version", "Value": 1, "Units": "", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_ZWAVEPLUS_INFO", "Index": 0, "Node": 7, "Genre": "System", "Help": "ZWave+ Version Supported on the Device", "ValueIDKey": 131563537, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueChanged", "TimeStamp": 1588264894} +OpenZWave/1/node/7/instance/1/commandclass/94/value/281475108274198/,{ "Label": "InstallerIcon", "Value": 4608, "Units": "", "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_ZWAVEPLUS_INFO", "Index": 1, "Node": 7, "Genre": "System", "Help": "Icon File to use for the Installer Application", "ValueIDKey": 281475108274198, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueChanged", "TimeStamp": 1588264894} +OpenZWave/1/node/7/instance/1/commandclass/94/value/562950084984854/,{ "Label": "UserIcon", "Value": 4608, "Units": "", "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_ZWAVEPLUS_INFO", "Index": 2, "Node": 7, "Genre": "System", "Help": "Icon File to use for the User Application", "ValueIDKey": 562950084984854, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueChanged", "TimeStamp": 1588264894} +OpenZWave/1/node/7/instance/1/commandclass/114/,{ "Instance": 1, "CommandClassId": 114, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "TimeStamp": 1588264893} +OpenZWave/1/node/7/instance/1/commandclass/114/value/131891219/,{ "Label": "Loaded Config Revision", "Value": 0, "Units": "", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 0, "Node": 7, "Genre": "System", "Help": "Revision of the Config file currently loaded", "ValueIDKey": 131891219, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588264893} +OpenZWave/1/node/7/instance/1/commandclass/114/value/281475108601875/,{ "Label": "Config File Revision", "Value": 6, "Units": "", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 1, "Node": 7, "Genre": "System", "Help": "Revision of the Config file on the File System", "ValueIDKey": 281475108601875, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588264893} +OpenZWave/1/node/7/instance/1/commandclass/114/value/562950085312531/,{ "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": 7, "Genre": "System", "Help": "Latest Revision of the Config file available for download", "ValueIDKey": 562950085312531, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588264893} +OpenZWave/1/node/7/instance/1/commandclass/114/value/844425062023191/,{ "Label": "Device ID", "Value": "", "Units": "", "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 3, "Node": 7, "Genre": "System", "Help": "Manufacturer Specific Device ID/Model", "ValueIDKey": 844425062023191, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588264893} +OpenZWave/1/node/7/instance/1/commandclass/114/value/1125900038733847/,{ "Label": "Serial Number", "Value": "", "Units": "", "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 4, "Node": 7, "Genre": "System", "Help": "Device Serial Number", "ValueIDKey": 1125900038733847, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588264893} +OpenZWave/1/node/7/instance/1/commandclass/115/,{ "Instance": 1, "CommandClassId": 115, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "TimeStamp": 1588264893} +OpenZWave/1/node/7/instance/1/commandclass/115/value/131907604/,{ "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", "Selected_id": 0 }, "Units": "dB", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 0, "Node": 7, "Genre": "System", "Help": "Output RF PowerLevel", "ValueIDKey": 131907604, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588264893} +OpenZWave/1/node/7/instance/1/commandclass/115/value/281475108618257/,{ "Label": "Timeout", "Value": 0, "Units": "seconds", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 1, "Node": 7, "Genre": "System", "Help": "Timeout till the PowerLevel is reset to Normal", "ValueIDKey": 281475108618257, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588264893} +OpenZWave/1/node/7/instance/1/commandclass/115/value/562950085328920/,{ "Label": "Set Powerlevel", "Value": false, "Units": "", "Min": 0, "Max": 0, "Type": "Button", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 2, "Node": 7, "Genre": "System", "Help": "Apply the Output PowerLevel and Timeout Values", "ValueIDKey": 562950085328920, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588264893} +OpenZWave/1/node/7/instance/1/commandclass/115/value/844425062039569/,{ "Label": "Test Node", "Value": 0, "Units": "", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 3, "Node": 7, "Genre": "System", "Help": "Node to Perform a test against", "ValueIDKey": 844425062039569, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588264893} +OpenZWave/1/node/7/instance/1/commandclass/115/value/1125900038750228/,{ "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", "Selected_id": 0 }, "Units": "dB", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 4, "Node": 7, "Genre": "System", "Help": "PowerLevel to use for the Test", "ValueIDKey": 1125900038750228, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588264893} +OpenZWave/1/node/7/instance/1/commandclass/115/value/1407375015460886/,{ "Label": "Frame Count", "Value": 0, "Units": "", "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 5, "Node": 7, "Genre": "System", "Help": "How Many Messages to send to the Node for the Test", "ValueIDKey": 1407375015460886, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588264893} +OpenZWave/1/node/7/instance/1/commandclass/115/value/1688849992171544/,{ "Label": "Test", "Value": false, "Units": "", "Min": 0, "Max": 0, "Type": "Button", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 6, "Node": 7, "Genre": "System", "Help": "Perform a PowerLevel Test against the a Node", "ValueIDKey": 1688849992171544, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588264893} +OpenZWave/1/node/7/instance/1/commandclass/115/value/1970324968882200/,{ "Label": "Report", "Value": false, "Units": "", "Min": 0, "Max": 0, "Type": "Button", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 7, "Node": 7, "Genre": "System", "Help": "Get the results of the latest PowerLevel Test against a Node", "ValueIDKey": 1970324968882200, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588264893} +OpenZWave/1/node/7/instance/1/commandclass/115/value/2251799945592852/,{ "Label": "Test Status", "Value": { "List": [ { "Value": 0, "Label": "Failed" }, { "Value": 1, "Label": "Success" }, { "Value": 2, "Label": "In Progress" } ], "Selected": "Failed", "Selected_id": 0 }, "Units": "", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 8, "Node": 7, "Genre": "System", "Help": "The Current Status of the last PowerNode Test Executed", "ValueIDKey": 2251799945592852, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588264893} +OpenZWave/1/node/7/instance/1/commandclass/115/value/2533274922303510/,{ "Label": "Acked Frames", "Value": 0, "Units": "", "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 9, "Node": 7, "Genre": "System", "Help": "Number of Messages successfully Acked by the Target Node", "ValueIDKey": 2533274922303510, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588264893} +OpenZWave/1/node/7/instance/1/commandclass/128/,{ "Instance": 1, "CommandClassId": 128, "CommandClass": "COMMAND_CLASS_BATTERY", "TimeStamp": 1588264893} +OpenZWave/1/node/7/instance/1/commandclass/128/value/123731985/,{ "Label": "Battery Level", "Value": 65, "Units": "%", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_BATTERY", "Index": 0, "Node": 7, "Genre": "User", "Help": "Current Battery Level", "ValueIDKey": 123731985, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueChanged", "TimeStamp": 1588264908} +OpenZWave/1/node/7/instance/1/commandclass/129/,{ "Instance": 1, "CommandClassId": 129, "CommandClass": "COMMAND_CLASS_CLOCK", "TimeStamp": 1588264893} +OpenZWave/1/node/7/instance/1/commandclass/129/value/123748372/,{ "Label": "Day", "Value": { "List": [ { "Value": 1, "Label": "Monday" }, { "Value": 2, "Label": "Tuesday" }, { "Value": 3, "Label": "Wednesday" }, { "Value": 4, "Label": "Thursday" }, { "Value": 5, "Label": "Friday" }, { "Value": 6, "Label": "Saturday" }, { "Value": 7, "Label": "Sunday" } ], "Selected": "Thursday", "Selected_id": 4 }, "Units": "", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CLOCK", "Index": 0, "Node": 7, "Genre": "User", "Help": "Day of Week", "ValueIDKey": 123748372, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueChanged", "TimeStamp": 1588264908} +OpenZWave/1/node/7/instance/1/commandclass/129/value/281475100459025/,{ "Label": "Hour", "Value": 2, "Units": "", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_CLOCK", "Index": 1, "Node": 7, "Genre": "User", "Help": "Hour", "ValueIDKey": 281475100459025, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueChanged", "TimeStamp": 1588264908} +OpenZWave/1/node/7/instance/1/commandclass/129/value/562950077169681/,{ "Label": "Minute", "Value": 17, "Units": "", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_CLOCK", "Index": 2, "Node": 7, "Genre": "User", "Help": "Minute", "ValueIDKey": 562950077169681, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueChanged", "TimeStamp": 1588264908} +OpenZWave/1/node/7/instance/1/commandclass/134/,{ "Instance": 1, "CommandClassId": 134, "CommandClass": "COMMAND_CLASS_VERSION", "TimeStamp": 1588264893} +OpenZWave/1/node/7/instance/1/commandclass/134/value/132218903/,{ "Label": "Library Version", "Value": "3", "Units": "", "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_VERSION", "Index": 0, "Node": 7, "Genre": "System", "Help": "Z-Wave Library Version", "ValueIDKey": 132218903, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueChanged", "TimeStamp": 1588264905} +OpenZWave/1/node/7/instance/1/commandclass/134/value/281475108929559/,{ "Label": "Protocol Version", "Value": "3.83", "Units": "", "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_VERSION", "Index": 1, "Node": 7, "Genre": "System", "Help": "Z-Wave Protocol Version", "ValueIDKey": 281475108929559, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueChanged", "TimeStamp": 1588264905} +OpenZWave/1/node/7/instance/1/commandclass/134/value/562950085640215/,{ "Label": "Application Version", "Value": "10.00", "Units": "", "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_VERSION", "Index": 2, "Node": 7, "Genre": "System", "Help": "Application Version", "ValueIDKey": 562950085640215, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueChanged", "TimeStamp": 1588264905} +OpenZWave/1/node/7/instance/1/commandclass/135/,{ "Instance": 1, "CommandClassId": 135, "CommandClass": "COMMAND_CLASS_INDICATOR", "TimeStamp": 1588264893} +OpenZWave/1/node/7/instance/1/commandclass/135/value/123846673/,{ "Label": "Indicator", "Value": 0, "Units": "", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_INDICATOR", "Index": 0, "Node": 7, "Genre": "User", "Help": "Current Indicator State", "ValueIDKey": 123846673, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588264893} +OpenZWave/1/node/7/instance/1/commandclass/49/,{ "Instance": 1, "CommandClassId": 49, "CommandClass": "COMMAND_CLASS_SENSOR_MULTILEVEL", "TimeStamp": 1588264893} +OpenZWave/1/node/7/instance/1/commandclass/49/value/281475099148306/,{ "Label": "Instance 1: Air Temperature", "Value": 73.5, "Units": "F", "Min": 0, "Max": 0, "Type": "Decimal", "Instance": 1, "CommandClass": "COMMAND_CLASS_SENSOR_MULTILEVEL", "Index": 1, "Node": 7, "Genre": "User", "Help": "Air Temperature Sensor Value", "ValueIDKey": 281475099148306, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueChanged", "TimeStamp": 1588266231} +OpenZWave/1/node/7/instance/1/commandclass/49/value/72057594168754196/,{ "Label": "Instance 1: Air Temperature Units", "Value": { "List": [ { "Value": 1, "Label": "Fahrenheit" } ], "Selected": "Fahrenheit", "Selected_id": 1 }, "Units": "", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_SENSOR_MULTILEVEL", "Index": 256, "Node": 7, "Genre": "System", "Help": "Air Temperature Sensor Available Units", "ValueIDKey": 72057594168754196, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588264893} +OpenZWave/1/node/7/instance/1/commandclass/49/value/1407375005990930/,{ "Label": "Instance 1: Humidity", "Value": 55.0, "Units": "%", "Min": 0, "Max": 0, "Type": "Decimal", "Instance": 1, "CommandClass": "COMMAND_CLASS_SENSOR_MULTILEVEL", "Index": 5, "Node": 7, "Genre": "User", "Help": "Humidity Sensor Value", "ValueIDKey": 1407375005990930, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueChanged", "TimeStamp": 1588266231} +OpenZWave/1/node/7/instance/1/commandclass/49/value/73183494075596820/,{ "Label": "Instance 1: Humidity Units", "Value": { "List": [ { "Value": 0, "Label": "Percent" } ], "Selected": "Percent", "Selected_id": 0 }, "Units": "", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_SENSOR_MULTILEVEL", "Index": 260, "Node": 7, "Genre": "System", "Help": "Humidity Sensor Available Units", "ValueIDKey": 73183494075596820, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588264893} +OpenZWave/1/node/7/instance/1/commandclass/64/,{ "Instance": 1, "CommandClassId": 64, "CommandClass": "COMMAND_CLASS_THERMOSTAT_MODE", "TimeStamp": 1588264894} +OpenZWave/1/node/7/instance/1/commandclass/64/value/122683412/,{ "Label": "Mode", "Value": { "List": [ { "Value": 0, "Label": "Off" }, { "Value": 1, "Label": "Heat" }, { "Value": 2, "Label": "Cool" }, { "Value": 3, "Label": "Auto" }, { "Value": 11, "Label": "Heat Econ" }, { "Value": 12, "Label": "Cool Econ" } ], "Selected": "Heat", "Selected_id": 1 }, "Units": "", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_THERMOSTAT_MODE", "Index": 0, "Node": 7, "Genre": "User", "Help": "Set the Thermostat Mode", "ValueIDKey": 122683412, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588264894} +OpenZWave/1/node/7/instance/1/commandclass/67/,{ "Instance": 1, "CommandClassId": 67, "CommandClass": "COMMAND_CLASS_THERMOSTAT_SETPOINT", "TimeStamp": 1588264894} +OpenZWave/1/node/7/instance/1/commandclass/67/value/281475099443218/,{ "Label": "Heating 1", "Value": 70.0, "Units": "C", "Min": 0, "Max": 0, "Type": "Decimal", "Instance": 1, "CommandClass": "COMMAND_CLASS_THERMOSTAT_SETPOINT", "Index": 1, "Node": 7, "Genre": "User", "Help": "Set the Thermostat Setpoint Heating 1", "ValueIDKey": 281475099443218, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueChanged", "TimeStamp": 1588264906} +OpenZWave/1/node/7/instance/1/commandclass/67/value/562950076153874/,{ "Label": "Cooling 1", "Value": 78.0, "Units": "C", "Min": 0, "Max": 0, "Type": "Decimal", "Instance": 1, "CommandClass": "COMMAND_CLASS_THERMOSTAT_SETPOINT", "Index": 2, "Node": 7, "Genre": "User", "Help": "Set the Thermostat Setpoint Cooling 1", "ValueIDKey": 562950076153874, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueChanged", "TimeStamp": 1588264906} +OpenZWave/1/node/7/instance/1/commandclass/67/value/3096224866549778/,{ "Label": "Heating Econ", "Value": 62.0, "Units": "C", "Min": 0, "Max": 0, "Type": "Decimal", "Instance": 1, "CommandClass": "COMMAND_CLASS_THERMOSTAT_SETPOINT", "Index": 11, "Node": 7, "Genre": "User", "Help": "Set the Thermostat Setpoint Heating Econ", "ValueIDKey": 3096224866549778, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueChanged", "TimeStamp": 1588264906} +OpenZWave/1/node/7/instance/1/commandclass/67/value/3377699843260434/,{ "Label": "Cooling Econ", "Value": 85.0, "Units": "C", "Min": 0, "Max": 0, "Type": "Decimal", "Instance": 1, "CommandClass": "COMMAND_CLASS_THERMOSTAT_SETPOINT", "Index": 12, "Node": 7, "Genre": "User", "Help": "Set the Thermostat Setpoint Cooling Econ", "ValueIDKey": 3377699843260434, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueChanged", "TimeStamp": 1588264906} +OpenZWave/1/node/7/instance/1/commandclass/68/,{ "Instance": 1, "CommandClassId": 68, "CommandClass": "COMMAND_CLASS_THERMOSTAT_FAN_MODE", "TimeStamp": 1588264894} +OpenZWave/1/node/7/instance/1/commandclass/68/value/122748948/,{ "Label": "Fan Mode", "Value": { "List": [ { "Value": 0, "Label": "Auto Low" }, { "Value": 1, "Label": "On Low" } ], "Selected": "Auto Low", "Selected_id": 0 }, "Units": "", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_THERMOSTAT_FAN_MODE", "Index": 0, "Node": 7, "Genre": "User", "Help": "Set the Fan Mode", "ValueIDKey": 122748948, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588264894} +OpenZWave/1/node/7/instance/2/,{ "Instance": 2, "TimeStamp": 1588264894} +OpenZWave/1/node/7/instance/2/commandclass/49/,{ "Instance": 2, "CommandClassId": 49, "CommandClass": "COMMAND_CLASS_SENSOR_MULTILEVEL", "TimeStamp": 1588264894} +OpenZWave/1/node/7/instance/2/commandclass/49/value/281475099148322/,{ "Label": "Instance 2: Air Temperature", "Value": 72.5, "Units": "F", "Min": 0, "Max": 0, "Type": "Decimal", "Instance": 2, "CommandClass": "COMMAND_CLASS_SENSOR_MULTILEVEL", "Index": 1, "Node": 7, "Genre": "User", "Help": "Air Temperature Sensor Value", "ValueIDKey": 281475099148322, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueChanged", "TimeStamp": 1588264906} +OpenZWave/1/node/7/instance/2/commandclass/49/value/72057594168754212/,{ "Label": "Instance 2: Air Temperature Units", "Value": { "List": [ { "Value": 1, "Label": "Fahrenheit" } ], "Selected": "Fahrenheit", "Selected_id": 1 }, "Units": "", "Min": 0, "Max": 0, "Type": "List", "Instance": 2, "CommandClass": "COMMAND_CLASS_SENSOR_MULTILEVEL", "Index": 256, "Node": 7, "Genre": "System", "Help": "Air Temperature Sensor Available Units", "ValueIDKey": 72057594168754212, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588264894} +OpenZWave/1/node/7/instance/2/commandclass/49/value/1407375005990946/,{ "Label": "Instance 2: Humidity", "Value": 56.0, "Units": "%", "Min": 0, "Max": 0, "Type": "Decimal", "Instance": 2, "CommandClass": "COMMAND_CLASS_SENSOR_MULTILEVEL", "Index": 5, "Node": 7, "Genre": "User", "Help": "Humidity Sensor Value", "ValueIDKey": 1407375005990946, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueChanged", "TimeStamp": 1588264907} +OpenZWave/1/node/7/instance/2/commandclass/49/value/73183494075596836/,{ "Label": "Instance 2: Humidity Units", "Value": { "List": [ { "Value": 0, "Label": "Percent" } ], "Selected": "Percent", "Selected_id": 0 }, "Units": "", "Min": 0, "Max": 0, "Type": "List", "Instance": 2, "CommandClass": "COMMAND_CLASS_SENSOR_MULTILEVEL", "Index": 260, "Node": 7, "Genre": "System", "Help": "Humidity Sensor Available Units", "ValueIDKey": 73183494075596836, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588264894} +OpenZWave/1/node/7/association/1/,{ "Name": "Reporting", "Help": "", "MaxAssociations": 2, "Members": [ "1.0" ], "TimeStamp": 1588264906} +OpenZWave/1/node/16/,{ "NodeID": 16, "NodeQueryStage": "Complete", "isListening": false, "isFlirs": true, "isBeaming": true, "isRouting": true, "isSecurityv1": false, "isZWavePlus": true, "isNIFRecieved": true, "isAwake": true, "isFailed": false, "MetaData": { "OZWInfoURL": "http://www.openzwave.com/device-database/0148:0001:0003", "ZWAProductURL": "https://products.z-wavealliance.org/products/2543/", "ProductPic": "images/eurotronic/eur_spiritz.png", "Description": "• Easy control for water radiators from any Z-Wave Controller • Fits most European water radiators (wide range of additional adaptors for different manufacturers available) • FLiRS for quick response time • LED Backlit LCD • Metal nut for reliable connection to the radiator • 2 buttons for easy temperature regulation • Battery level indicator • Child Lock • Over the Air update • UK-Mode for upside down installation • Open Window detection • Automatic frost protection", "ProductManualURL": "https://Products.Z-WaveAlliance.org/ProductManual/File?folder=&filename=Manuals/2650/Spirit_Z-Wave_BAL_web_EN_view_05.pdf", "ProductPageURL": "", "InclusionHelp": "Start Inclusion mode of your primary Z-Wave Controller. Press the Boost-Button.", "ExclusionHelp": "Start Exclusion mode of your primary Z-Wave Controller. Now press and hold the boost button of the Spirit Z-Wave Plus for at least 5 seconds.", "ResetHelp": "Please use this procedure only when the network primary controller is missing or otherwise inoperable. Remove batteries. Press and hold boost button. While still holding boost button insert batteries. The LCD shows RES. Release boost button. To perform the factory reset press boost button.", "WakeupHelp": "", "ProductSupportURL": "", "Frequency": "UAE", "Name": "KOMFORTHAUS Spirit Z-Wave Plus", "ProductPicBase64": "iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAIAAAAiOjnJAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAgAElEQVR4nO29e5Mlx3UfeLKq7vveft1+9zQwGAzxIIknSQMgCVJeSSF6Hd6QvevY2JXlpVa78kdx7LfYcGysvQ5rHRLXBGGBpClCMiXRAiECBvEYDOY909Pd93bfvrfvoypz/8i52afOOZm3unsAQxHOiOmpm3Xy5Hn88pysrKwqpbWGWUUpZYyZSVakiKwC/PEpQna2VmcTowiBjycAkHqxskg5syOKm+5hyXDqDowx1i5nLufn8KmW/yLizez0/ASfhlSBolzEOi2KeZckSLhx6X66v4TM1vjCjOvIkeFjMRgAMgqXBDcsEqUC3HxsRT5i1wGDFJQqYArR+D6jBbwwkwnnkHDb2SJyJ8pwu9hKVyOqSuQ4lTKuU1vD3YyLo+SSOP68YXiYivggBCIrLCqwAcBHEVaZt+JdcAKiiD3mbEmPXAyuHXcKJ044fklnoj6YjJ8VsQh+IDr3E7m5y7nOBD2inoGGXK+ApoTzTK+L/hMpiY7h5pgAj3AiMHc8793HlrDilAFg2HrFJ+8car7KMxfiknN2Icb2h8XqPNweFudARHeV9mBmJhVZiZL4+IRbucpIDAxiyCU6+KIaDz+kCzXNQWQ0iHFF7IIQcN34uPdxILK5sS4KJo51XE8qCRNiEBxRuA0xHx6Vxd6dbX2mUyyT8C58VhUF4NnmpBK3F+dSWAgsPfETHivElD5WhIwUcf4kxvzAnIwjTMw1YW5cU9EUPkqiu5IiDZchIIDPVjMpw72LvguXQI9CKqQUp7lUCUdsTOkk8w2Rgh2JEvp6D2jhGz+8vrhgAbZFuBURsrgAPkfwGOYjCFAK/RJgnVbtgOf4mBPVK+7785eZvZ9Whpn+KCLD56ScUzDi34icLhKW8N9AiiQZkBAQPpB3UoCAy4P5i5Q8KDpKIh6udKw4gc9c+KwoFbeATxdc42MV7lfk7NMF1/uahAnM9PrUnk1wM9+MBIPRl30J1AjIMJmY43FzUSRRycBkRewxQCP+5AeALBPWxYc55Y/QZF7MKzl/IqFvuinO9rjiWC/ehCsFzC+uVWRQUahw3UQDETxxKQMTLE5DdCDjlYwJDvEwgHyuDYsXIHPyEE84AgIILqEPmk738CSPKILtw+HL+XOpXKeEkvgxoJc7dbLyjvvjUQe7E/JjAvLO9tFgKYl1uKrcNIAGB+mO8OFMeBcBkHFnY7FF2HGvzxxOIFlJDD++Vpyta+VrSOzAXQN5iPiyU0Br1yo37EByMOn4VKdmnj0P8eehnE1gHphFVg/XGp+xbb3rWKcSSzwrmu+/ls9P+VSdEsE0ePqyXpEiyifmlOKShYnFOMwHyWnL2VqJYpBJwhn6mqmjSHker51NWlGqCGfcmRxPa3eetmc2ITke8h7CA4D3xSccpy1hIwT+YsGwPQNi4HAuGnmmjtxWhK0of7hSrA/HNj6XBwCltQ774PwBM5BhCzYnxjobK6LIOaWayf/MTT6H04YziJTwxuGgx32MD8jQESdw4fxI+HAawpZc94k0RAbI44kLxtmKZ8lBwCxckplTjoBZsLIFO+JWFeXnHEje8NlHsIy9pUMsWPA6RRz03NCiUYpcIhQhmylkkRJgeNrA9hBlI0bDzQNScXoCnYIiic7iIvlY0eWGsJJFzvr65oKKMCqopwhfQi8SzAR3QMHA8Cie1IoD5VROOYM6wGzuk6c4c0cZWoMurljYguecNJyz+cMqxYfWGXgWx+UZmD9EyuJFuCvEs6lPgsDkA4Lpn5BBgekRlyTAiosxc5Ixs3A5sSRhsX0mCuglahcQ1RcFcVuiNY9VYuoUmfh8epIK7VVhONT7Qi4P4KKPIe9ssbkvQRRMdqJU4jDgpieUxeccYr4InPVJReqLSBXwkc8sPj6Q9yChLJh2+Nmzp8KZUPBJedrAG4BjuNXMPC7ymZmVfBAHz3B6KKxm6jhzPHzahXTk3UHKUwxuXzB9BAqPtMXP+iTk9L4MGO5UJCuesknXkIeaKNhMHcWc6BMpnEAD0VGUE/KGDUviaiIRzq4PEicoKqcEalpcHwoVTm9ZOZk4JT7r/mJVeeERiLfl8hMOAWc7kbit+HEAN9wmuB7/BORREUlOYK4d6YXwJL3g5lxgMb5gVriVq4kg71oOJgIX3LGYTTjqeeEYwtJzo2CUE8SLGvLmkPcKPxb5k45Ef/iMrjxJnxsQH/uGLpGTiydqjWXAfyE/TrD1OLZ86nDVsCKUS8AHPkY+7gH68ISgyByFUIpseXMx4BfRa6b6xe0jxpiApqfqNGzYmYJBMMCfqhSaY7kaLMF/wcJl+zyw/ZSkOmcpLlVByoJkJw+sBuK8LS5nhSMwEYIc88DuaxWQissQZlXkmKc8X334mHAgmau4tJg5YRhmRU758ho/wFNeYJbnUVbU0dWfPKVD5hZEMWJBLrqINp5YxXkb+cltwaXic3/RCr6+xCTC9RK5YQmJamTWgtly9wArvkjgxnNgUuuKOAclfnR8RK25uQiNKAOhKfRcIbFRgKxgJVeDz5yKB3DI+4/4gJwKHweEnClGwY7sX6211jpN0yzL7Bp1HMdxHCdJEkWRj3PxfgP0pIYPfi5tuBfZ7yKwiqDhbJXFeZ6tPERWYW5n6MgYk2WZRZILb1EUWRdkWWYtn0xLAGGfhpo4lp9hhFOe4uRdjNikY7GJmCNEjEM+H3G2POrwUYUpxQwVTj1cTpAsGxaVm4V3aoyZTCZpmnLVXAADAIski7Aoihy8cAojUvnswMUWJecAEvFUxAj0rJsfkDZEGtErAUoRCqK2hFhsHgC62HymbBysYo7w+Y/0G0Cwy3eErW9wZllmkWSjmgtgOHoV9M7Ms0UK8R0EoZLTxcbkQDNxNIMEmiIA4h2Jg4bXC2OiQFwJm0yECDCMBtryse5otNaTyaTgu4Mhj0g763JgskAslUpOsMB4KKKvWBMgEJ2FaTjl7LfN+JTnBOFkXJxATE8zG5JWPsTz4zB8w1qISllIuSjlEhmm4ZGA96i1ttN5F73sBJ/IE7bPTLN/SiXU6zkD6fnj8MPl8ykVbMAsy1ziE+OfGADCzLXWpVIJEEDjOBbB6pp89ubinT58OBe32uehPJRZiHX/NPEZpSDLzHA4StMsiiCKonhacIIrzhwA7Fze5kTIr0udSqPPLFjkHqbAjcloC2TZIn37ck1YBxAvN4pduxUUSWQbbkuGYpZlk8nEVWbZZDgcHh+P0lRHkYqik1mRzWvVajVJTl6ZgVNVYNJjEVYul+M4dpbxYctnH1FTn6NFVtwOPoHp5N1XZvqGd3aGwTFzOlnkVMFeTjXTJ83t3zRN0zR1sEjTdDgcDgbHaZpqnRmjoyjGYcaWKIpKpVK1Wi2Xy4GM5us0juNyuXwyR86jsyCrgtCZySFgQyEVFsxlRTQ5bVosDqzTsjpPIYPbGD2ZpFmWGQNKgTFmMkkHg/5oNJpMJlkGOks73b333nu33mg8++Vn642GUtGJHQCUiiy8avVaqVQiydEXS2zRWkdR5EAJ0hzu4ZazjcBcxDLSFRPuAFBuwhkTQLg8CUR1KoSUzsTJn/Jc4hEZfGKLGsl2kRYRjDGTyUTr1BiwJ8fjSb/fH45GWZoak6Vpur/Xef317//ox2/s7u6Bgd/49W/9k//lu+3ltXJSAQ0QARhQAAoipUAlUbVardfrNsHxTn2Z3RhTqVTsilcuAUleED3lc4QzYMA4RcrJAinpFTxQJclOlGlmk0CCEynF+VZASH7MJRTjv09+e7k3fR2B0dqMRqPBYDAcjrLM3vWb7Ozc/cEPfvDDn/x4NDqu16tJkmSZPup1V5dW/tkf/LPnnnuhXK0pZa/vAAwAgIpAKVUqlRqNRrlcFtwjAQKmV4uVSsVO17gXfE7hBifI8xnNZ3+RIeBU6EtbZ5h/FJmNfR6KTyo8YBykYLoyPhwOB4PBaDSyd/0mk8mNmzdee+37P/uLP890Vpmr1evVSqVULpezTB/3B0d73clg9Jvf+c4/+h/+8eLCShwnAMrdzoHpvL7RaFSrVTwh86Ecw71ardqLTWz5wNgL2yFgkFNNaQBfFfLI/4BCSkxiQpnZVuyFVPIRUzA5+qI3MbovypL476KUq9FaW0iNx5MsS7XW4/H4o48+/Hff///eevuvVazqrVqtUas2Ss1WvV6rRnGcZXrYHx52DnudXnfvYGt987u/93tffubZUqkWRSUrmpMkiqJqtdpoNOyUS5we4GITosWWvUQIqBxIF4HIxP+ShoF5kfBcIe/PpxsUQ5Wve0wmqi2GaJ/+pJWv60BUdoLZRU5jjM1YaZodHw8Hg/54MtFam0yPx6O/+eUvX3v9++/853fictxqNWuNcrlaHo0n24+s1xtJXFJGgzFKT3R/MDzoHPU7R93dzmh0/A/+2//uH/72P2q22nGpAkoDKAADxtgZfaVSaTabZHk9XNI0rVardhGVmzRsAZ/7+HDlyCNOJG56MHkX/TQzs3LH+/I0jyU++IM0zsJhyWdHzop3R6KUWze3lZPJ5Ph4cHw8Go8nADpLs0H/6K//+q++/9prH1+/VmlUanPVer1aq1ZbrVqqzV/+1S/rtfIzz31hbX1BRaCzyOhMaz0epYeHxwfd/uH+YW9398Lqxne/+wdPf+mZcrWqohgAQIECFalIKVUul1utlktwYnIgPyeTSb1etzcTMVZE3UlzHlBEe4ZZceJPceVd7A8YYgJ8oNiMeybPQO+2F6213Sk1pdGTyWQwOB4Ox+kkTXWmdXZ40P2Ln/3HN/796zd2btdb9XqzVqkljWatVa9XayWV6MEo/fl/+mAwHEdR9siFlWeffSKJjTZ2g4POUhj0R4fd/kGnd7B3qAfjv/ebv/Xbv/3fLy4tqaRiVAQKInggW6VSabVa9qKPGJNnFZgOiWazSebyBX30aZTZ9wrJCHCngIUK3lYkDpTw8Ark++IMiVJ4552tH4/H/f7RaGQv90yaZoedvR/+6Ic//slP9nv71VatVi/XGpVGozrXqpcrSSUpxaVEx2YwSq9cubfbOQSTJVH69ZefKZU0qNgYrY3RmckmZnycHhwNu7uH/f2DQae3vrrxu7/zT59/8WulSi2KY5gCSynFcyKP1gZd0duxMTc3Z7GIE9FJevJfuc/0QqCV6JTc5J1kHF/exSWQCgO5GVcW5AwMWz56sSPSI0z3ILgarbVdQRiNRlrrLEvTNL1//96fvPEnP/nTHw/H41qjWWtVa42k2ao1mrVKJS4lcZIkKorGafTRtZsff/LJaKiN0QsL9ccvbW1vrcTKKIiMMnaFQqcmS81kMjk+nhzsHx3sH/X2Dyb90a//2q//T//zP2ktLJbKVSezm29hZHCtsfz2uNVq2Zp79+4ppVZWVvjdyfDUAvIACNg8wEoGFq/hUCCKcRQSQ4iTMMzEN0o4KMOZMSA2TFeALKQsWZZl4/FoMBiMx6nW2mTZeDK5dv36G2+89uc/+zMDptxopJk52O/Esbn8xMVRNp6br29eaJfLpePj9Mq1a1c/uTccQxSZhcXWpYvbq8tz5ZKJYzVNbpkCYwxobbQBnWWTiR4Nda83PNw/7O/1jjq99mL7f/3933/2uRcr9UYUxQDKpkG7fCqGZ5wHYZoltdblcrlWqwFAmqZvvfVWvV6/dOlSrVbLhZPg5CQwewn7JYcHnLYJx5lzoCJkpyUuEqiBhaJwX870bgVBKWUMaJ2NRqPj4+F4PMqyNEshnYyvXPng3732vb9++60oiRuN5nic7d7vDAfDqBSVG8mjj29/fO1mFMHCUm19Y+XD96+PMh1F8cry4qVLW0tLjVipWJlIGaUiUAqMBgBlABQYUJkxoCFLTTbRaZoN+6PD/aNe5+iw2xsdD37t23/3d37nuwuL7aRcBniwLaJWq1UqFaw7MQVWEADwZKvf73/wwQdKqUceeWRpaQnvHOS2cmzDCYcjT3TZSTI+Q/GFxyJnz198wYxTmunlnlM7TdPhcDwcDieTcZZlaZZOJpP3fvk3r//gtXfee1dVVK1ZPx6Yvfvd4+FxOVHVRmXr8oWLly+Uy+rW7XvdznGappVK6fbte+2lpUuPX1harJZiEykDBkApUJGBGJTa3985OhglcalcjtvLCxAnoCdgtMmUzmAyGacj0zsYdPcPD7qHh53e2sLK//77f/DcV18sV2tJUra7ZUjQwgGDYAum42d+ft7S3Lt3b3d3FwA2Njba7TZeJAuYFNcAA3QRvxTdQUp6FZMm5KFdUIHwxK64VKIA7nIvD6nhcDhMU7ukng5Hx2+//fYff++Pr175sFotVxutXu949353PMmSUtRaKD36xIWtxzbLtaScmDKYVKdpGisoa5MZA0mioiiLlI4gBhNrlRoVZTrZ2e18+OGVzu4hmDiJ1daF5S888SgopVRaLccKIqMhyzKdwXikjwfjvb3uoNvv7/aG/dG3fv2/+d1/+nsrK2uVSqVWq9nwE5hg4QNbkiRpNpsWZB999FGn01lcXFxZWWm3287OgQkDd4dvmuSbFOVWcUSI8GM+3xIrCTFRg9uoCIF4IJrbPcXgaLIsG41Gw+HQ7htO00nv6PDnP//LH/7wjWvXrldq5aSRlCvVo+5xd69vQJVr8cUnNje32+WaShKVKAMalIoNZACxAgVKg4kV2EUKpQ0AqLHWN27fv3L15kG3B0aVk9LGxkZ7ceH+7v27d2+tr63u7uy2mrVnnnlyYbGptdE6yyY6m8B4OO52Dnvd415ncNjtXbz4+D//5//H9va2bzJAoEAqJ5PJwsKC3dbc7XY7nc7h4aFS6uLFiwsLC2S+xY3M/eLLmL6GiYhTLi7+6asUp+TiqAKpiASiPHwwYD74cg+PrdFoOBgMs1RPJpPDg/0/ffMnb/zwjd3OXq1aWViaK9fL1YVqrdEYT+4c3e3FpaqBuN4q15pRrEAZAB0bBcYuaBoFShkTAQCYRAEYpUHBJzfvvfvex4PjkTFQq1W3trbm5xt3bt/7xTt/A1kEOq6UG+PJ/n5n9Oaf/eLy5e3Ll7ejODLGRFGkVHkpXqhVm0sL6qWvvvyd3/r7GxuboiOI1uJBHMdHR0c2Ic7NzX388cedTieKomazGUWRXZUIuBhYxBLjpSjGg+YFX7wmYvYMRcziASQFMiM/RTZzQn5UKaVHI727u/ev/59/+eP/8CeDYT+pJNV6tVop1RqVWqtcaZSjJAEd376995/fu5pNJk8/vf3UU4+oSCsVKYiUMgA6MpEyoJUxCkBBZiDSMRiTGfUX/+ndW3d36o369vZ2o1G/cePm7u6+McaYdKW9cPmxRxcXljrdw9t37uo0AzO5sL2+staOIqUyo7K4pOrPPfO1V1/59lxzAQBAAQQHZBhbWZbV6/VqtWqM2dvbu3PnztLSUqlUStN0aWmp0WjMNPhMZ3GPuN5z9wqdZOJUMXD9RVQtft13tsIHWX5by4NipxGYEEBpo3765p/+i//r/9w/uF+uJY1mpdWqVitRVFIqibWK0olO4tLh4fG1T25sX1hZWV2MY1CglNEKAECDSQBAq0yraHCcXbl57dqV21EGrbnaM88+O5wcj8bmwyvX9vf2lYEojldWlx6/dGFhoVZO4tiUJzrTWoM2kVIQxTdv3FqaX1prr3312a+99NVXG/U5oyP14BNHGuvolAI0lwpEdGOM1tomvizL9vf3LY1d8VpfX8c7pIvY/FReEwBIZmTicZGeONiL8HTEBKMcTJbG7Q/mM0oCLKXAGDCgDOhev/fav//jn7/9l3E5jeM0iZXWar971D8exVF04cKqMRmYKIqyB6uVBpQ2oIxWClSkAXr98Xvvf3Lr1u5xlsaQLM03nri8sbnR1gaufHLn2vV7WaZXlxcuPrI6P9csJQkoUEopnWkw2kAEERhldDQ4nPzmr/29r7z48uLcvNLGQAQKALR5sJfQa1XRcfisBVa9XrdbmY+OjobDoTVIr9dbXFxcXl7mWwUx8yJI8sEgIX6aGbemTlLiRRzxLp7oEKOIGPVpQnhaPO3u7na7Xa11kiTlcrnRaDQaDQKmPIhBWXQZmK/P/Y//8Hf+zksvv/bGH93buwF6DCZLkrJS6Z07d+bn6uVSkpTKSRIbo8GAMfZflEK8f9j74INrN2/eTzMTxWpjef6xRx7ZWF2slHRkYAzRZJT1eoM4hka9vLzQSiJlTAYQGQNGawOgIFKmVKu0vvb817/6/NeatXkND+IPGAXGgN3yINmBxyfyE/9VSvX7/VKpZIypVCr9fn8wGHS7XbvNplartVotMbWJ45lfBhIyORXixuDPtQR5PmxBsXwc0MrXNsuy+/fv37592wYq/NaDUqnUbrcXFhbELeEndjFgjDFglIrHevjj//jGz37+p4eHu/vdTr3RGg8ng/6gWks2NlaTREVKZRqU1pHRmVYH/fEP3ngzU7GKko2VhScvbbcXa+VSKQbLGNIsvrd/+Fdvv3s8PE4i81vffnm+XgcwWplMaWOU0uV6ZeGrz730d77ySrVcB5iuJwLYVdSp7qFLP/wTu4BELDtJqNVq5XJZa72zs3N4eNhqtYbDYb/fX15efvTRR+3tSHyhTZwr1gccbX8m3AHumNfwn2KkEXmKJUDAU6SdSNm51OLi4sHBweHhodba3lCzcWtnZ0drvbGx4TS04Ov1etVq1c5kAQCUDRumrEq/8epvXXzk0v/7R/+y0ZjTajI6HrWXWqVSFMUwjSORicBo0AoOj/pJJd5YXr702PbyQrMaQ6RiBQBKa6W0UhqyqJLMLyyqw6RRLcUAqclMpDITmSyZry999YWXv/r8S/VyEwCM0WAz3hQP4EcSCVeYAF+BEYRFUXR8fGzfaTM3N9ftdm/fvm232O/s7MzNzS0vLxObi2MyUM/9qMJBxQl6zrl2mMnMU24i5Ral8Eja39+/cePG0dFRu92u1+ubm5vuBggATCaTbrd7dHRkvdJsNh/clFWRglhBCqCNibVSB/3733vtDz++/p6JRgAGzINkpFRswBjQYEyqo09u7ux3Dp956guV2CRKqyhWOtKQmshkkBwejd778MMbN3eyTCnIVpYa3/jKC0mtpKJSs7z0yovffPHZr9WqdWOmt3o8izuBHAdB5Il/bdCKoijLsmvXru3v79vMWKvVarXaU089FUXRqR6jLVK8D6wSujPAy9eET7CcABzlaZryFQSSl2H6Fg08wRoOh91u9+7du7du3RqNRnEcl0qlra2t+fn51dXVer2hVAyQKjBgSgBRakYaJm/+xY9++rM3MhgZo0EZAGP3vUQRGANaxaORHo30tavXIhVVK+Vmq7m20gIFe93+rz68fuv2fQMZGNNemrt8aXttdakcN1ut+ZdeePnl575ejav6wQzKPLiQMDE2AnhA4zvApsDxDB9Y/9pZfJqm/X7/xo0bpVLJPkamlLp06ZKdxfsyoOgysd7VGMNeY+SbnYnFx9dHif/6CNxPDKlwqiVTjcFgcHBwsLu7++abbw4Gg6effrrdbgPArVu3Pv7448uXL7/wwgutVmtpsR3FkZ3PA4DRBiDSkL7/8bvf+8EfHo32jcqMMgYg0ipSAAq0NmDUJDW/unLznfevpplOIHv2i5f3O72bd/c0mDjSq4vzly9tL7cXy3F1cX7l5Re/8ZVnX6okDa21iTKlDJhIPZhTGQO5yS9W3+VxbCJyTJDkw581Zr1etzvPrl+/Ph6P7cu6kiRpNBrPP/+8jVg+AM30Ly+53Q0PvYiwEGlcsfuihsNhmqY2ApEXKJKZH27e6/W63e5oNAKA119/HQC+853v2LZ2gN6/f/8nP/nJSy+99Oijj8ZRtLK6On0MwUxNAaD0zv69f/v9f3Vn92NtJqBjAwoirUArpYyOtI5Gabrb6f3il+/1Dofbm+t37u6YSC2vzF28uL622C4n5ZWF9Zee/8YLz32tmpRBK1CRMcYorcx02YOpT8DBT3EyHwE3qb12juPYXk3b29KlUimO49Fo9JWvfMXu4jrDdMVXTpbISCTE6YYfixcLXEleL150wHRU2YeJLYENzuPxeDKZAEClUnFvPQApdfZ6vQ8//HB5eTmOYzsb29rasrNUM73nOjc3Nzc39yA7GHPnzp12u91oNACc1QwYtbq4/rv/+H/73uv/5r0P3wIYaQMmi0DFGgBUBEqXSvHq0sKrL3/l9u27lVql2Uray8uL8/OVOFld3Pz6S9985unny1EtghhMatellFtA8INgZu4jB754JtaMRqN6vQ4A9Xq9Vqs5y8RxfOfOHbc9kEwzeBAlzuVoeUBMUiHmAqwQKADDOEcPbuWD7GQyGQ6H4/EYv1PKFjsPSNP06OjIPqJpV/xw6DbTK6O333779u3bTz75ZLlcvn379vvvv7+xsXHhwgW78a3b7b7//vsA8Morr9RqNWPM3t5eHMdPP/00lR8ADGSQ/vjPXv/zv/phmg0AwECsFRiVxVEEJjYmytJURWZilAKITbKytPHNl3/t2adfiFWsjFImAgCjNEiwwD2SSl/oEmNbkVxpuxiNRo1Gw96b/+CDD+yTjDY5lMvlb37zm3jdYWaZmSW9V4W+LGakedLMUGnycyxXbyFlH/7EbPFM3F6zKKVGo1G/39da2+fv8ANPruzu7v70pz/d2tra2trq9XofffTR3bt37RvMSqXSxYsXL1++HEXR7u7ucDjc3t5eX1/nVxIAGowBiLXKfvHOz1//4b8djg80aA1aKQUqAvuUvIl1BgrK66ub33jp1S898UwclcFMF6PAgAEDnHnRiz7fwamyp6Oxl9W1Wi1N006ns7u7a0dylmVxHH/rW9+am5sDKVKcKgmeeLDg67hJ1vPFHjEyicXui7KQwnhyDAHBS6HXSg0Gg8PDQzvg7L16DtY333xzNBo988wztq0NhHYPyf3799M03d7eXllZIW9McKYBpQCUMp+sGxIAACAASURBVEZDBpG5euOjf/NH/3evvwMmNTrWSkGkAQxAdWPt8W+9/OpTj3+xpKrGaFCZnV0YY8A+5WymPEE+wD85GsQpfEFumI8t/X5/bm4uTdNut/urX/3KvrLL0jz//POXLl0i7sYMpeEn3Ghxf3PA4hS4nnQpcodZxWpo71s5nra4JQPHFvIIc+8PtrdUx+NxrVabm5uzC1fOB5bm3Xfffffdd1955ZVKpWKj3b1797TWly5dsheJgYHo3DL9z+zs3flXf/gvdvZvGpVpnUWqemH90re/+RtfePzJ2NiN7WpKfDI3N1MmM5Mdrw8nvoKQcgf2xnO3211eXs6ybDwe/+IXv3DTCQC4ePHiiy++aO0s+pHMalxD78RLVM+XAfkBhhcwtAGLYaPR6PDw0F7x4c0I4tvuHKrMgx1LJ/nRjj87SVpYWLBP4RHJ79y586Mf/ehLX/rSeDw2xnzhC19ot9s+7bioJ2KA0pAdHu394R/966vXr25feOTVb/zdy489pUxJAYChb0QO5yb+kx/zKBWoFPngny52dLvdZrNp3z7yy1/+0o5tY4zWut1uf/vb33YvoSQAIhEH8qgilA+Mhkc5OS1yDJeA2wAgTdP9/f3p0+snwmHouBmVrXFMcOhylZPJ5Pbt23a3ZLvddu/qdD0eHR3t7u4uLCzY3SMYQFYAO5l1LySW1TQKAEDpSTbudLvtdhs0AMTKgDbakReBThhPkIeLWOk7wFoTzs7Fk8nk+Ph4fn7eGPPxxx93u11jTJqm9jmzr3/96/atJJgnNUXhQodXINCJjvH1zcFqjOl0OqPRyI08HkJdsrMvN8eYxlDDiNRaX7lyZTAYLC8vr62t2eFIJCTR0ZYsy/r9vkX5aDRaX18nwpw0MfatVtoAGIjAGAXmwW0+Y+yHrkRU8Uoe0jgxjkw+Pr6zvpDm6geDwdzc3GQy2d/f/+STTxqNxsrKytLS0u7u7pNPPtlqtYo4tEisSbCqIozwT4IVLDFJhUQCALALCi4sE0qYzgOUUnYhyr4tmIhOwpjt9/Lly9evX9/b2wOA5eVlu7hA5CTqjEajnZ2dpaUll3/v3bu3vLxsF2aHw2GpVFpcXJw2yeDB1hkAZcBExihj9QWlZk19wtGI14TBFG4rysADWBRFCwsLTz/9tN1HarfTjEYjvIuGoIJEHFLPjZxbb4T8WAeSNYOXCQFK+/f4+NiiCkcgLJOazr6t5lEUjcdjG7ocWx41bc2jjz7a6XT29vZ2d3cXFxfr9TpZqSfGjeP4xo0b9nFQAKjValrrvb09u0hWLpf39vbs+9DgpNjoBQAaFCgAM11LEF2IS9jxkMdHmNtMSjEouhqbMQDAZr2Dg4ODgwO7BG1v8vgiSIA5sNshQD4rRzwNDGeksUjv8OEobVqx0tt6hzDeO6Bntuw+GRznRBPbrhcXF9fX14fD4f7+fr/fFzOLaxVF0QsvvPDWW29NJhPbvF6vz83N2UVCY8zi4uLdu3fd128wK16IPD4CyEOBEIQpuYPIT1cjOt4Vu3HIGJOm6a1bt3Z2dobDoZ2B4O+yYN+BNAtyBEQG5/0ImIO5MhwBTgexnrOyL0RwTQhqnUz2wF7fOXiRb4eQWTZm0mq1Njc3x+Nxp9Pp9XrudQbEQ7aLarX6pS99qdPpQB7odhjYKUi/34dpjuaY4D9FQ4EfbYEa7gVgYMJ+JWYnxVUmSTKZTNwnx5RS9t1a9qlr4jXcHYkXYX0BIHENfMmSBCd+9REeIra4COwo+Wxa5GaBlSSJTYt2+zaX0AlZq9W2trZu3rzZ6XSMMeTeqpPf7tBaWlpyHMw0QR8dHb3zzjtpmj7xxBPNZtPdEuCyYTtgH/Nx7PNEuEbESvgskYcoDgBRFE0mE7vDttls9vt9O4xNfiZjWOQTuwu4PnIGdaRcEwIyNS1ijaN0P23soR1LO8tEQY0xdheyfeKUDFkisDGmUqlsb2/bLX69Xo8MPjtS7c0y3IWTtlQqJUny6quvbm5uYguQ8UpCBflLhrvvrA9VvG2gUjQjYYv963zh9n3YXTTiQzu4oehrX2UkGk7k6zMo15b8tMAirGDqTh8oDQq/drXJ3u2yadF4hpStLJVKFy5cGI1GdvsouRQ1xjz22GNvv/227TRN06tXr169etV2lCTJ448/bh+WwlYjenFzic4OQI0jTDQgb8h75DYXizWaW9OJosiuVC8sLNTrdfLSNtejcwfuJSy/sXveiawzkx2XPqyPWxEFdjVgAef8Z/K5HD/oYaabIZVSdklT7NRVlsvl7e3t69evdzodpRR+j48xZn5+/uLFi7/61a/m5uY+/PDDvb29paWl1dXVarUKAK1Wa29vbzQa2ZtFHDGiNc52IB4XRO3MGp9frGGTJJmfn69UKvaxMKKsj5uYVXjlg3Ussb1PMpy/MRB9GdfOkzBzjDPMh8Rhoowzt7116r7pYPKXEe64XC5fuHDBzreUUhY0TqTV1dU0Te/cufPUU09tbm5mWXb9+vW1tTU3sb1y5coXv/jFgGoiIMgpHorEyoK4CQxgJU3scEMypO2nMfr9/sHBgd24THaLEP8qNonkAMANEx8jI829AvoE1MavixX7Aj8oCWdjjI1/pVJpMplUKhUjTVGd8NVqdWNj486dO91ud3Fx0b2k36JnfX3dLbhHUbSysmLvpl29evXo6OjJJ5+c6W/i9ZkhDcdjflZk5WMophEyxkT+eDTapRz7AA95DbhBSTDw1/Lk4Ms9sAqeaES4QB4ZuF6Mdu77fTYCu4zmegQ/KAlozDSBAkCpVBqNRu4eDoGp42nvWtj3Ji4tLYmvBHKUx8fH169fn5ub+/KXv+wuL3yyiVHKF7TcMa4nw52c8nXqG+SQ94urIazcBMvOXO1Zu+ODkGH/4r+EAPLesSURG+ADQkCE9mlIVFJKfkkE75FsnhHtaMnshcx4PBaxhQWYn58fDocHBwd2VoFvOBIDLS8vYxOL+CNQmBmlZp4Sz/qKaD38qBXvkXCOoshuU7Y23N3d3drawvcYuH8DYYXLBu6q8FQlDCaQMEqs4EzJWRl0KxBHUM7QGDOZTOwGefz9CFEAAFhdXa1UKgcHB3ZRXvQfqcfzQneKHHBwBChdiAIGL1EA0pYLLHLwkeHY425mDAaDKIouX74s+iKM8jASTg2ssBpigncr4Di68jzos7gYimxxqw/uvSCkdyyhnaG7x3iIkBwEEEQMd7kPUlwkH6qIAcW2cI5i0LTJ1sRxvLm5aW/G8/BTMDWJBF5g+caKGA+4pUSTYWRA/sFwyCOJSjmdV3Ji9wIjO6n3IUMpVSqVNjc37VOHnJgILDLh9ZgeJMzxU+4GUbgQC2BTG4ZaAhdSMKXJ3wpbXl5+/PHH8VDHqvGOAoXY5GS5gRfiyIJzRsM2L5AuHaV7+paw4pWG4RVPjNwTiG7pjwtmfzabzYWFhYODg3K57F7/inshP7nYXKriTYo0V/nrJGxG3IrggJuLnFXossYYY69g4ji2m7G4xcjsiuOSF2JM6gnSwakKkcl3gOdJYmp3B9wT4sTLGOMeOPENd+cJ+1jOwcHBcDh0OZpHi5nHpFIUvmBznwGJ9Xz8OQ2GkRiK7MwhiqL19XU8Grk7fD4lhcAd7L1CLjcwv4rjg7sQm49375YbyAjgYvGfZjqvF4cUANgHftxGF+5CW5RSm5ub/X6/2+26BTZjjF3O4fL7AEFQRaAj2pC3mtlQPMsL8ZTxXEq7A7cDIBw+OCJJj04qElmNMTRi+aQX2/M5Vlgrnih9KOGFPCthWPQyxtiLRLKn3tG7ylar1Wq1Dg8PB4OBb8YDCAeYhltWrBH5hI0c8LFBVzzhmBFghVFiny7hNuTcfB5xDcV0pJSKiECB+MHb+0zA6THyCD3WASSjEye5XpxpXI+TyQS/jY0YAoNyY2NjPB4fHByQpQpgOPDJw+k5LkFCHmGFmYhpgRtNJBMNy3u3ZG7lb6abXH04tvFCl6ExIycKIcDdY5qAcCp/GchpCE+iiZKmtI4Stx2Px/wSkmgEAOVyeXV1dWdnp16vz8/Pc5Uh73XMR7SjSCMiSTRggLlIzFGLbYJrRBl8N9cBYcjHnHTEMWB/njysR8IV7hUHIZOPnyIQuUXIAf4SHw8YkIcF7w5XAsOQm8gTniQ8rK2tAcDBwYHNC5jMzbdw+OGhiIco0oUogOhFXjgmuEf4WZOfLXAafGODH0B+YBPzEubY9QTQQNaxCPS4to4F+YsJyAGgPX1q+kgMH3m+SAB5QBOvkFZOPLdlHjzAtcQbGxu9Xs9uYnZFxA3HEKEhczUiIZFclIfodaocxPHhI8NhnnwgmMd4yBufo0d0vT2mj1KR8CDK6gOyGE4AmRWbzGcRDlbSCntCxDEAuOV48SkMx7PdbidJcnh4aGf9BO6YmByLI4GjCteLYpCGTlPFsh4uxMGij3HB9ZiSf8GVuAP3gnGGBVMs+T5g7pMePC4kBEQyPKZP+kArJUQ+0T2BYWfyOZdv83IMLaocYkSvK6VWV1ftU3WOA0xThi/8kBqROafxaSTqyJGN+czEtCits9twOLQ1eHc4F554QcQfpiStIhEKPtsRZchfrABPhY4DeXhGTBM+T4ihEdhFH0xfY4eDFlbK2cLuZeh2u+7y0MrpUgYwnIkFm0uUPGDbU+nuoydhhhMbY8bj8Xg8rlQq9u3cbgeRYZlXBACXmYcr9zMB5loRsD5VObEINR8N78XXO67n/YqssiyLomg4HNqHM7nJACCKouXl5fv37w8GA/dWOyjgfvAgwAcLwhM7kviGpyHiM9Es5Cz5aecG9uEcd8q9aY1zJvGJezMgxgPD+niRNhiw2DTOCsTTOISQkOva8teKkmmBr54PA/B41MYt+6ysb2K+srKSpil+DlEcnUSFAIACheMb2PAgQyisY8DNttg3BsRxjN9Sblu5yTsJV0qapCs0UQaPx/HfBJOSxlhQN5gwtgL6QH7MAQI1t6arwQ7DP51ievoSaawbFwMPfbsQb19iyyU3xthH6Y+OjkajkX1QWIwQYSTNxBkJToDMzvn4kCQGEuwa3JG9wZUkiftsgtNLoVvRXCTso4J6cT7CckO4fZFZDin2rUu+wUdCFGdia/CD1JCPfCSekYsdpZQdtbx3y1Nrvba2dnx8bJ9iBQmyuCGJZ76xK2oBHiMTxPCMIbb19WgfFQSAarXq3tmHJVfTd1GLnfqMAPkYIUruWJ34gDgp4G8+VrhdcN/2DR/E+gFjYRTivhxnzEqxuA1oGDgCu5cB0NP0ePvh/Px8HMe9Xs8tq2LQiPcKeRHHLj5LQOkzAjkrKkgkBIQGN50izwkSR7tn6iHvUOICXMNZkRrsr4hLRoyo8ukPnxKbcKtF00JkJWblbwoBhC2CJ9ElgLDOu8DYctzc8cLCwmAwcC+5I86DfPEhDFuSUPqCUKC43sUQSMgAwL663L693fVCYgRMb+fjBzN9DiWiKmk6hI8xgXBbLaw/F7RI8aVz8hNDx+SjDu804HLy1+bi4+NjjFFSlpaWxuOxm8LjAsiCvk5xPRkJvBUh4JbBrJQ0MyNN7DpCkiTk2UACDowA/JQl+DEgSstbGTZBp8+riM18HuX+9h3j1/OZfPZ0rPBzbeAPbASCOPtgzvjtNLa4mYcLjbaVvfljX7pydHRkF7REO2K9MG6wHXzGwWdJk4DlIT/AuFMmkwl5ixjhI6IKANyX94gjsNFEeUDyskKTIltzcq2E4xj3FuHC1cZNFLuacBdlOJBi5Uk9l4R36nO8qLwt9gNrBu2rwTo2Go1erzcej907XsVCBsZMwbBqIlsysEXj8+Z27de+UptIEsCEmcZvF9uMZ+ZAEAPIrYEY4SgjyMOTw5ZLxrsnTYiSSilyAcKNi0Unkcl4ZmDYhUR+7iErwHA4tM/7GzQld7ef7TvQ7e4//m4tYgQfrEkrMlpw4XzIWVyJI6J9n7a1qg+pvC+FAnm5XOZwBAZxfMzBxyGFz57cK+TnsINJN2Lh1nfHcRy79zBxMvGpCkAjAzw3tgD5xpnPnXWPcTpsJUlip/CkWAL7alf3dhosqspPIEQTBdQXgWhQ7giMYVtvplnbXvSJz3PjJk5skFxmX5DJe+RKcV1EhrzmZO2bSGM8C1ciO5GG8OSfanGW5Zo4Mvf8ND9Lhhdm9UC3KBeP7V87zcKByh3bz6LYqIaFJAKTUyKqCpaA4pjGFrtvzL7UikcBEqTJAZGfzNydGMTjxL9FdHFlxusJzlMIT/cKZK4zGWeQBw3Bny1uvdRxw+DDoc5V2iUPe4fH7WB2odqisNlsjsdj/Ho3kk3EiOuLWGKEBgZWXENA5iBlcY+t4WjEwMORZ4wZjUZ2Id430+eVxQtpnlu3FLXyjUge6okdSU/k7f6YP0YYBopC83qdf7UkJnM1AdOY6XJzqVQ6Pj42aIEUi91oNNI0PT4+5s1FiGBb+XrnBgyEdlLsbRn3tTMCo4C+vlNRFDUaDR7ysZA+3ItexmS4JNxb5GfAmpjpTLzbiCW+0oiDW6GLDo48Mhw1ej8bVxXzx8FSo3eDOz72Red2/o5vDRHtRDQHxp7PhuJZM51O2bmUyl9/OacWTE+OLMsy+y1MmwfFFEmOCVlg/PDms9/dIFqWEBRR0l4Yix9KBb9XsG98sVBsSH7aJlYA+9ltgmCLM7sLwK3RQ36Yin2FIxmwEUh0IaxgGqWiKMI3zh2qipiajFVb7GsQG40G2ZGMyXyuLBIjCUFk8qPKqUcMSsJjwNCc1YOeosjeRhDDAA453II+z/FQR1g5SVwSsffI8KOqKj9zqtVqdtWR8wEGDtEUAdP5dLGVdgnX3cXDsdlZLIxO8IxYd4FsP0p42sLV9HnEldynaUQpRRCQFEAs6Btb+IYDFtQxFP3ELUW6szV4zYKD0s2oMLAwgWtiX8Ft5++KzdZ5DZbB52yiDk7KML1EtbuoyRN/JNo5qxIXcC2IfeyOIPtdY7Eh7gJbmOguHnNW4B7/ImqDPyr6LCV2TDjgHYzcuGb6ATCfgcRi0ORDsZmWKBvkn6smTeznT+wEn6hTBDe8hH1gpg9wu5vHAflVfpbt64VgRWtdKpXm5+ft8hVviEFJAqQIhiIyJCTM+Hp1x9gZYhNff0op92JxhSakuIlmXxXATOyEGj94w4n56/CJPDhcafQKAzWdzttvPPFtDoFOse64X3fMgy5MIaWmd1eITTATkZUoEjapO5hMJqurqwsLC/jll4QtCfYcFZietCKjWpHdDSafzklLUWiiGFZeBHgURS5ocdST3sWDAPIARRQyHsgAMNO86cTGYLUrPfjL52IvvBAyX9C1x/Z1hPZjBbg+rB35y0UCtgN7NBqtrKwsLi7yLzYQf4mQ5UAXpeUHkUIzRF+XuBL7w6A0BHn8khHgCt55jXmSgrdMcZV80YiMNtGIMJ1m4S2pCr3GU2ttv8iNJ/icoVgC4dapZj87hbe4+CCIawKo4sTuOI7j7e3tdrvNA4HIioABu974g7Hoa+ErFxwuYiABCafWdr5wbYyxa332ZUMm/8Q3llLUnDiAZD2TX1AFT7HmNtOJPCdQSpVKJTt/d8MgHE7wWBJjuZmunI3HYxylzlYMyxhYSJgap91u25fLBd6Gz/kQn/p+8lOEifyIfVjowFlSyaWxa3QnARM9csh7J3GUzK7wT1+AJAQYdnaLGG7lTjUaDa21u7ET0BEXIq1rZW9E2u+fVatVNx6Kc+YdkS5Mfv90q9V69NFHbfrDY9XH0CeMkxMfiKw4POQvrPrimzsVIBC7wcfVatVeB/EkK8ZYnODEfkkvYWyBZ6g5bKnp91HsUhZRnFjAl3xxxLXhuVKpWIRxebgwoti+gn1fq9XsN2bxkOaYEOM6187XkSgw8cLJe95xA186A+Q5kq04CFwhUlar1aOjI5W/qYKx6GqI51ylu7QMyAMSFh29DRtZlvGHNo0x9grD3vrlHiJWJgkaU9rnGe1cCgtM7Cw6GFuPt8K2sqVUKrXbbfeBCXGABfIaiS8iPSbzhRtXIyw3+AALEoaIcAT1oh1LpZLdncKVtAsKPpsaacnKFzOAjU6iFAmN+K99NEp8xTdW3zdkzfQrnvib1jw6kkrChHdnphd9+JT92W63FxcXfbspiadEI3ABxMDsQyTkna7sA6u8P9I3iQS+StIZiRxY4kqlQmYwtuDNgOKYEMVQ00KMQhCJPYQfRyPS2n4rlYqNWDgAcKAD8429eWwXVkSz8MqAdiD5xZnI7npdXV0lz1CQHnkIJCpwLSDvNWJJrjsPuidXhURD0oHYNylioMI83UGtVuv1emIvxPqEocMc5unc7yh9AYyAiUQp15ddcbA7/vhX/BxqMQc1/QqLjXairQh68EDnvifHWH0ASNO0Vqutra3V6/WwX0hcx51ywU5VePQC5Ef6MAVXDJA1MUID+ojBAPPHmwhwMND511k5hzkdNHt1DEy/Moeh6dY/yXDHmHD8zXQtwIUxY4z9RrA41zb5jGBbjUYju9mLW89nMV848dnTSm6/8LixsYFX0vmQxkhyzbEvCNSAgY8LyVmRJrg+wfYif0UriJDnVuCsMH0URfV6fTAYEKOLsZMPL25xXMl30JNQ4Qjw+juOeW6vKfmKPS9KKTsV44nPZxkyPgOUWGwLKa318vLyysoKWYEjgZAMM04AeTQQYTAZrxfdyn8mzkA+JUXIn79UKhX7zUFg9sXIcPGMYALyyMPRkdiFjwQOO8zKVtoMiO/q8PRkPU2ilDgmSXfheA/I97bYdYpms7m5uSl+bh1zJoOQE/tGHRc7ICSO/WIgyK28i30QRxbBVhGaOI4bjUa32/WNDCIAATdednf0Gn25TrS+QVkSj1cMSnvKfmKT3y50KdtOp+x1n2sIEo4L5juuu50nZFlWqVQ2NjbsdIoMOSI85+aTpEj+IRgVO+XE9iD3IUyTz3ckchJ9xG54wuYGdZX1ev3w8JDsueO94LbAQMAV45Uk4HF7YUpLbFMbB5aZTqdc7iOhjgjDjcCLOJDcHlcyneKmIJb3+Z77l8vPAUCGnyiw2CT3IUz8lxiLnOIKcOG4CQjbOI7r9bp7eRCeO/vMjV+67AMQERXfjsRn7QZl0VswffWSzj+nPxqNlFK1Wg2jk4wWEeuiWQLD3c7t2u326uqq6A5XE/YCoEEFkhP5T4wtH2XgwJWid0MDIUSs5wlCNEGz2ez1emZaAv2SIGqmL2HDxHr6rR4cmchc3qHWTC8GiVR22m45u7P2HpR77z63xsxCzEJ0dGztyur8/Pza2poLimJf2PdivORjDPJjUoQU7pEEQneqiPpFgXVaO0J+rIAHiOVyuV6v9/t9kYOzHXE/XnQIh1UlzR5cpUMeX+aA6Vef7EJupVKxO224CpAHfcDo/BQOVPbdJPV6fX19vVar8U/kcS1mngqEgyIMw7EjzERY/fP9LFJIkgqMCVfm5+ctsAgKeboBhAkLOD70Mf58Y8sXSt1P+9yVfaDF7Rgm4OPFlwRJ1+TYRakkSTY3N+fm5orYXMRoEWvPZHgGp4utEm7WMN+ZEpDhQnI2nx7ZoHV0dER6IayweDh3AAoYLjW4XXuAfEm4cd9jyKrpcjwJVDOjkQ9SvCP3dD8ArK6uLi8vi/vDxC64C3wRgRvcJw8w3Kv8BCbAh8dI+eZDoPiiEa8UE7xic0ljzOLi4mAwIJdgriHZjixOUNwxDwmExoGDgBXyAHU5twiqRMG41lgeF6gWFhbW1tbsXaCCuS/sAqJOEf/yhMCbF8SJnAqLtz9b8TFPkmR+fn5/f58EOXtWvDNN5pJ4I03YTGTk2XciAPMHf5hspteJ2JBHNo6ydhmsXq9vbm6KT86cp5zTgw8LAIUm7zMnpAGXq/xMmYxLVzk3N9fr9ew7esibcF1zcT7k0gqBjk9acuDbveR7aqPIFIRbA6PKbqfZ3Ny0+4bFiE66EGvcsWjPgGy4lagO8SA+FiczopyJyQcJX5rwiSiiisRPX/rHJY7jxcXFe/fuGfb+Dw4yYADyzW9mggxnKOtmDD6enoitiAzYJq4LnX+j7srKip1O8czrwxN3xGlBT1iJp8Tu+FRBBCI+ZX8KC6TA4MzRI/qAxCdRSTII8M9ms9nv9+2yFqckJhDtOxOOnNigtS4SC12WJJx9OmIwwRSyLkoZYxYXF+3eKdKRyD+gmq/4XE6ihg+g4eMAakVRE5MHLHGeD92YgNOT0cwhwuOiPWi324PBwH0vCRuFwJFbkJjAHYvGxeq4rTtujd5FF7dJiw9r3js5dhd9WZY1Go2trS27vsoHtziQyKAlxzxac3tCfgyIVuI8fSF5ppzESicPyWD5CJ5IJCPjjNNjcXns5fZypVQqra6uEjFcX7w70jV/5RXXi8iDCfDVPv5QCiYG5FQfwsx0P4K9Uf3II49cunSJvDyIaOeK6GNReD7CA0bm4xn3yAcGiRp8TBJQcl8nxFsEqoEILEKbIz2gD1beNW82m/Pz83bXA5eEWxyYp01+5zHuFD/gz02Gedo3StpHxPita9wvMaA9sB8eW19fX1paIu/Z4tLykAwISdjaXF/Rqtwyoqm5KwOUxJLc6YAGv7Ib/XAwELvhheOURDVSOAHuF/K4WVlZOT4+tq/Otqd0/kvG3L64Bsd/bg5y7KtxrxPyRVzSxBUbq5aWllZXV+2mLm4ZPvT5UJkJGm5M3pZ3xDmIfES7cRfwA8dBeKMfjkOiKXk9iUlcW/yTQJkLEEXR1tbWtWvX7JN95KnLQCjlXcyMu9hwTgZjzGQysU/BB+wAeUgZY9I0tdvx7BcSxb58NvHRBNQkMvPRHuhR/OmDqXgqLB59YBVLKQZ/PjLEcO1o+E+RP6FMkmRjY+PGjRsG7WIgz+Nzq/mchE2MZea3/9y9oPF4bN8GQJoTmV2UyrKsXC5ftgwsxQAAC69JREFUvHix2WyGNSVycs/hjriReUIkdiBNsGd9wUwckASg3Fk+gNomCfnti0Y+mnArUROONhzAXHN7n//mzZtuH8vMIBrIfaR3EqL4qX6/T74caTHkKF2x0/yNjY12u8234/HIISLGHfNRijslkZ4bFnMgGIUg0Hnv5C8xqeh33JGw8s6FJvGMmMkHcB5diNG50KRmfn5+PB7fu3fPocoGrcBTgfaAoxDLGba1bTUcDhcXF4EBEe/YsRtdFhYWNjc33c0+HiFEx/tcAmx4OEyTSmI3Hsk4RrnZi4xDn4+4PLgm95QOhwhXmIdlYnqCMGduHj/DqLdleXk5TdP9/X3MnI9FwpAIiV3O4yUwv7o9WD5iu5TQaDQ2Nzfti9p8qOJKkfgqkokDgHPgP4llSIwIVELeg0RgbmSREosh3ysUBw3k3SACGWOR24WIiPviARKm3lpbW9NadzodkmjEmCyqygePSOwS7tHRUalUIu+Igymk7M2+Rx55ZGFhgTiYWM8nCTGgzyz4gCQNDgvOXywE09gO3PiYkg9pbmpcmbse5mPIN2gK1oR/krGLD7AaURRtbGxorQ8ODgB9r9X3dR3SBeQHBnEnfogeppOno6Ojer1OnkJzqNrY2FhaWuLfiSDWE+NxwAIQdAQf6uRYlATTEIByOX3xggtAjEkQYlnl3lMIDPK4Sy4uKeGzREoiLgn+xChxHF+4cMEYc3BwYKZ3qfk+GV+osLBw/EmWIc9AZ1k2GAzW1tYALeXbRwjtdAq/KEGMxMRoon1EAn7sC64+Y5JKX1DEwOUe98kwUyT8M3cTmo8wx0I8DlP6SngcixxskwsXLkRR1Ol0VH5xC7flw06ssQUYMrTWw+FQTR/FsafcdMqtTvEQRWKtT5GApjyWhI1GKnnUJx356n09BrQQzxI75J4rJBRcLFF6PpKIuYnJeJQuwhAA4jje3NxUSu3t7dl634PRLkRBftT6UIvHYqfTqdfrdkeynU5dvHix1WqRjMmzwMyIDh4villGlJPbRFQf0xORuJC+QUjk5KNIHFeu0A+dgzSqfNDhknECoh6PH6JWPp5xHG9tbcVxvLOzQ2Qmutka/DlgMfiTjuyHMNfX1+0+6fX19Xa7TTbP+OIBsYYYTvBPMciFrcoNgjnzLiBvf94jb8i5cYBiJr44ffK24LA5uGl4Kz58CT2WL2w432hwmNjd3b1z5w5MtyS4G71OTxwgidVMfi+h46m13tnZsXf6lpaW1tfX3XccxLAt6gUeB4MUD0T3iwFAZCjWi4HK1xchIDJgaUXxQn35QndAtyKUxZsX4Q/SZKjX6127dg2mqIL8ly9djYhj8n4iWwaDQafTeeyxx7a2tvCnIgM+Dpw9c3mIpvv0OM9klXuKHCORIBckLItZ33UsdCbBHwsamKCQs/Z4OBx+8skn9vsOajqjx3ELS4Uv/TBA7U/7/dLLly/bz/mRvfCBaOSTmYzvIqqFoz7RHTcHKXiIDgqHW5/MPmxwO5wkioDCYglD9TxnTyWDO8iy7ObNmwcHBxYNODNaGhy0FHqjrhXG3pZJkmR9fX1+ft731bvPRqPzsPr04tzZOhJeXgCeMVGEexj4IGEfMxRnJL5pCpZzf3//1q1bIKVFogUOWvatySsrK3Y7Hld2pgxiSCASEmW51iDFGyKMrxUPbIFK8BSfI0RWIkNOf4olzc/heMKhazwe3759+/Dw0G37JDkRS2K3JKysrLgtCZzyzCIVYVKEjNOc34wiQM/GOSweXVUnYYYPMrEDYC4JjJiCEauIYpAf5caYo6Ojmzdv2o/VAItbrsnS0hJeRwAWZQsKcAY3B2Yn5+zroYze8ytliyJ7wHkDCF4WBfAkQvNUGD2tkm7+dHR01Ol07JNkLhQppSqVyvz8vPsWyAMTzFq0LCgtbyjmQbFHX048LdrI3MM38eAqiKwCAsy0WC5iiY3D4zhgoJkHM89yMWb2js9mWTYej+3biCqVSrlcFl9Be4bMVUSYQGXBMrPT8OwqMLADUXOmQwsKEAJWgIV46qFPpD6lmdlD6egzk+1UZaabfH/F5gUDuchBeGCVNCPgE2M1T2GuLf5JeBYJdSIrHu1571zUIqV4W3IqrKlPZrFhuFMsp8jNF4E4kkQ8hUMaoX8QnMQAKTb4r+WhlHPa81TNC0aXh9tvgPLkSegiXAixqxHJZvLhlQH64qdmCsDbEo2KFG4Hfsznf8X5n7YEUMW79rmSsJrZUAyuD07p/NclAzE5nCh9BD7iM1TOLGI+OjOrcAo7J6szF5LsivMX869vSlPQj4HpGt3dcM5CpjsziR9ivzOvLc7PihCEO7UHIsEZ0lZBLc5sf1EkyEOwoBiWJlH5Uc4HhKvnHfjMN3Nchi8IYFbACAwpMpJEeXyBDVCoxxy4AAE7+IZyWCrfTNlnB5/8WEJRDHHSzVN2wEfgxxxM1wuNm7yL4D1DJZeS6yOi3sdKdKEYe2ey4haZyYqYmyNDZOXjcFqpgI3YgmY/QxGHwXk4zH7QxZ1yBIGfJJbQvMuuWjElMPQ4Mo7+sMCYJ7DhjscWNoqoOBHGNsciceGJZXzCiMSYbKaaoq3EA94RriEqE/Wxvtw42DXYfSdvmxGVdBb32UWUCXvRZxouilgf4IAbcmRwFUhfGF5Wdz4qeC9i15ge2FgnwnMQcG5c5iIYcrqIXWNLitDnFiDd8VFE7JyzqghGn1bA8ERAIJ7irIh1xF5IKywe74XbjhOQaMeDnxgkeNecj8jNZwdgviGsuKiQL4SMuJYYgftOdJlvWHJWPjBQ7/tuQgd6JSYW0cD58LzDOyUEgWNfTbjfE7UlyWfyJJx5Q5BsxfWdKZVINtNWvibiqJ4Z4wOtxChAOs2tY0Ee+HysBOrDp4pQimHvbPxn9ivGfF8gdDXgiQThjoCh52xazJS5oBZhI5+Wlbf5OV0FpxlYgeEVbv5ZlocoQ3FWgczwt65YrXPPF7hjUkOakQMODswK0+Phy/siHMiBjz9vLsrpE8mnpk9ZkbMoOWfFuRFin/2LsA24L9BdcQLuNd+BKriO5drwGQAgVBkpGhmW0UkrQs/5YLmLsCKUhIAzn9lvWOywhD7+oq3OwEqUGZjjRNOF58dOhjOzigi1Y0QAx6FGpp+uXjzrmAe8wqUkTQhn7gxSlFJhIbFg5BTvC7MKUJJeRHNxW3HPiaxIJbEhdq2ZriJhyX0Dg9fjVu4nqSRSEVYnSzgi3dnKw+LzqZa/FUL+7So59Iu7G8QYSMjIZJwMPhIeCX+Rkicd3jsXLJwycBMsp6hyQUU487B2oqiEoS+WE/kDbX0WE4UJ2yrMjdgBk+HmIZUeYiT77IPiQ+kozIQnwYdSzqnaQ4zE52EVORa+v7zytPR8qsEnT76pjzsgx24iSPoF5G8fB8yfjKuZlfwsEZtLHubP7YZHO+8LpHAiih2WkPwVC+loJhJwzUNYxypSTIG1rs++GJbQHwrD89N8fspppXX0uYjFidxBYAyJo4EUHD/ClD5x+RAJcxOHIxnZZ0OVjzN41j5msgJPMDgVK96c8ORd+HxaRAyROaBplvwODGcm3ECkBBYw8c9wiMaswoZzF7o4XwS4BfiANHWdWUiqAqYmSN4SmYvzWl5Jfp4Bsny9APJjSfQpOetdTWDMiX1yn033Kczn/+ERhkHmeiXS8+DB2XL+jiHk8cF7D1wGisecmPcrNiEE4YJlC/geGNTCOOOV5CoSWxKDADxO9BGIvrbFedngOVZg7J4hWYSZnCpOnFMqTmbOPa9ybYk1zzYXeehSnYEgBwj/VpdT9XLyZQreAeFoaXDHZATweM7jkBh1RYlJdCQ8CwospmluCNGmHECiKXlC5ww5f8WmGZxSJAvYioMVC8mF4bYNd8Sh4jOOMf5XRZ6tPJTw9qky/NyWz7mmp42p9NuhOB+LOT581nccmC5wBYgyAVZhPjMpi/P8DFidQVORAOeHIjQFCXxTTB+r/x/orEKbtlVUngAAAABJRU5ErkJggg==" }, "Event": "nodeQueriesComplete", "TimeStamp": 1588422766, "NodeManufacturerName": "EUROtronic", "NodeProductName": "EUR_SPIRITZ Wall Radiator Thermostat", "NodeBasicString": "Routing Slave", "NodeBasic": 4, "NodeGenericString": "Thermostat", "NodeGeneric": 8, "NodeSpecificString": "General Thermostat V2", "NodeSpecific": 6, "NodeManufacturerID": "0x0148", "NodeProductType": "0x0003", "NodeProductID": "0x0001", "NodeBaudRate": 100000, "NodeVersion": 4, "NodeGroups": 1, "NodeName": "", "NodeLocation": "", "NodeDeviceTypeString": "Thermostat HVAC", "NodeDeviceType": 4608, "NodeRole": 7, "NodeRoleString": "Listening Sleeping Slave", "NodePlusType": 0, "NodePlusTypeString": "Z-Wave+ node", "Neighbors": [ 1, 3, 7, 8, 9, 10, 12, 13, 14 ]} +OpenZWave/1/node/16/instance/1/,{ "Instance": 1, "TimeStamp": 1588422682} +OpenZWave/1/node/16/instance/1/commandclass/38/,{ "Instance": 1, "CommandClassId": 38, "CommandClass": "COMMAND_CLASS_SWITCH_MULTILEVEL", "TimeStamp": 1588422682} +OpenZWave/1/node/16/instance/1/commandclass/38/value/273252369/,{ "Label": "Level", "Value": 94, "Units": "%", "Min": 0, "Max": 100, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_SWITCH_MULTILEVEL", "Index": 0, "Node": 16, "Genre": "User", "Help": "The Current Level of the Device", "ValueIDKey": 273252369, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueChanged", "TimeStamp": 1588422759} +OpenZWave/1/node/16/instance/1/commandclass/38/value/281475249963032/,{ "Label": "Up", "Value": false, "Units": "", "Min": 0, "Max": 0, "Type": "Button", "Instance": 1, "CommandClass": "COMMAND_CLASS_SWITCH_MULTILEVEL", "Index": 1, "Node": 16, "Genre": "User", "Help": "Increase the Brightness of the Device", "ValueIDKey": 281475249963032, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588422682} +OpenZWave/1/node/16/instance/1/commandclass/38/value/562950226673688/,{ "Label": "Down", "Value": false, "Units": "", "Min": 0, "Max": 0, "Type": "Button", "Instance": 1, "CommandClass": "COMMAND_CLASS_SWITCH_MULTILEVEL", "Index": 2, "Node": 16, "Genre": "User", "Help": "Decrease the Brightness of the Device", "ValueIDKey": 562950226673688, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588422682} +OpenZWave/1/node/16/instance/1/commandclass/38/value/844425211772944/,{ "Label": "Ignore Start Level", "Value": true, "Units": "", "Min": 0, "Max": 0, "Type": "Bool", "Instance": 1, "CommandClass": "COMMAND_CLASS_SWITCH_MULTILEVEL", "Index": 3, "Node": 16, "Genre": "System", "Help": "Ignore the Start Level of the Device when increasing/decreasing brightness", "ValueIDKey": 844425211772944, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588422682} +OpenZWave/1/node/16/instance/1/commandclass/38/value/1125900188483601/,{ "Label": "Start Level", "Value": 0, "Units": "", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_SWITCH_MULTILEVEL", "Index": 4, "Node": 16, "Genre": "System", "Help": "Start Level when Changing the Brightness of a Device", "ValueIDKey": 1125900188483601, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588422682} +OpenZWave/1/node/16/instance/1/commandclass/64/,{ "Instance": 1, "CommandClassId": 64, "CommandClass": "COMMAND_CLASS_THERMOSTAT_MODE", "TimeStamp": 1588422682} +OpenZWave/1/node/16/instance/1/commandclass/64/value/273678356/,{ "Label": "Mode", "Value": { "List": [ { "Value": 0, "Label": "Off" }, { "Value": 1, "Label": "Heat" }, { "Value": 11, "Label": "Heat Eco" }, { "Value": 15, "Label": "Full Power" }, { "Value": 31, "Label": "Manufacturer Specific" } ], "Selected": "Heat", "Selected_id": 1 }, "Units": "", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_THERMOSTAT_MODE", "Index": 0, "Node": 16, "Genre": "User", "Help": "Off: No heating, only frost protection. Heat: Room temperature will be kept at the configured setpoint. Heat Eco: Energy save heating mode. Room temperature will be lowered to the configured eco setpoint in order to save energy. Full Power: Full power heating. This mode is left automatically after 5 minutes. Manufacturer Specific: Direct valve control mode. The valve opening percentage can be controlled using the switch multilevel command class.", "ValueIDKey": 273678356, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588422682} +OpenZWave/1/node/16/instance/1/commandclass/67/,{ "Instance": 1, "CommandClassId": 67, "CommandClass": "COMMAND_CLASS_THERMOSTAT_SETPOINT", "TimeStamp": 1588422682} +OpenZWave/1/node/16/instance/1/commandclass/67/value/281475250438162/,{ "Label": "Heating 1", "Value": 19.0, "Units": "C", "Min": 8, "Max": 28, "Type": "Decimal", "Instance": 1, "CommandClass": "COMMAND_CLASS_THERMOSTAT_SETPOINT", "Index": 1, "Node": 16, "Genre": "User", "Help": "Set the Thermostat Setpoint Heating 1", "ValueIDKey": 281475250438162, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588422682} +OpenZWave/1/node/16/instance/1/commandclass/67/value/3096225017544722/,{ "Label": "Heating Econ", "Value": 18.0, "Units": "C", "Min": 8, "Max": 28, "Type": "Decimal", "Instance": 1, "CommandClass": "COMMAND_CLASS_THERMOSTAT_SETPOINT", "Index": 11, "Node": 16, "Genre": "User", "Help": "Set the Thermostat Setpoint Heating Econ", "ValueIDKey": 3096225017544722, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588422682} +OpenZWave/1/node/16/instance/1/commandclass/67/value/28428972921503762/,{ "Label": "Heating 1_minimum", "Value": 8.0, "Units": "C", "Min": 0, "Max": 0, "Type": "Decimal", "Instance": 1, "CommandClass": "COMMAND_CLASS_THERMOSTAT_SETPOINT", "Index": 101, "Node": 16, "Genre": "User", "Help": "", "ValueIDKey": 28428972921503762, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588422682} +OpenZWave/1/node/16/instance/1/commandclass/67/value/56576470592569362/,{ "Label": "Heating 1_maximum", "Value": 28.0, "Units": "C", "Min": 0, "Max": 0, "Type": "Decimal", "Instance": 1, "CommandClass": "COMMAND_CLASS_THERMOSTAT_SETPOINT", "Index": 201, "Node": 16, "Genre": "User", "Help": "", "ValueIDKey": 56576470592569362, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588422682} +OpenZWave/1/node/16/instance/1/commandclass/67/value/31243722688610322/,{ "Label": "Heating Econ_minimum", "Value": 8.0, "Units": "C", "Min": 0, "Max": 0, "Type": "Decimal", "Instance": 1, "CommandClass": "COMMAND_CLASS_THERMOSTAT_SETPOINT", "Index": 111, "Node": 16, "Genre": "User", "Help": "", "ValueIDKey": 31243722688610322, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588422682} +OpenZWave/1/node/16/instance/1/commandclass/67/value/59391220359675922/,{ "Label": "Heating Econ_maximum", "Value": 28.0, "Units": "C", "Min": 0, "Max": 0, "Type": "Decimal", "Instance": 1, "CommandClass": "COMMAND_CLASS_THERMOSTAT_SETPOINT", "Index": 211, "Node": 16, "Genre": "User", "Help": "", "ValueIDKey": 59391220359675922, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588422682} +OpenZWave/1/node/16/instance/1/commandclass/112/,{ "Instance": 1, "CommandClassId": 112, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "TimeStamp": 1588422682} +OpenZWave/1/node/16/instance/1/commandclass/112/value/281475255369748/,{ "Label": "LCD Invert", "Value": { "List": [ { "Value": 0, "Label": "Normal" }, { "Value": 1, "Label": "Upside Down" } ], "Selected": "Normal", "Selected_id": 0 }, "Units": "", "Min": 0, "Max": 1, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 1, "Node": 16, "Genre": "Config", "Help": "Allows rotating the LCD contents by 180 degrees. Default: Normal", "ValueIDKey": 281475255369748, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588422682} +OpenZWave/1/node/16/instance/1/commandclass/112/value/562950232080401/,{ "Label": "LCD Timeout", "Value": 0, "Units": "sec", "Min": 0, "Max": 30, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 2, "Node": 16, "Genre": "Config", "Help": "0: No Timeout, LCD always on. 5-30: Timeout after 5-30s. Default: 0 (LCD always on)", "ValueIDKey": 562950232080401, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588422682} +OpenZWave/1/node/16/instance/1/commandclass/112/value/844425208791060/,{ "Label": "Backlight", "Value": { "List": [ { "Value": 0, "Label": "Backlight disabled" }, { "Value": 1, "Label": "Backlight enabled" } ], "Selected": "Backlight enabled", "Selected_id": 1 }, "Units": "", "Min": 0, "Max": 1, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 3, "Node": 16, "Genre": "Config", "Help": "Default: Backlight enabled", "ValueIDKey": 844425208791060, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588422682} +OpenZWave/1/node/16/instance/1/commandclass/112/value/1125900185501716/,{ "Label": "Battery Report", "Value": { "List": [ { "Value": 0, "Label": "Only send battery status as notification" }, { "Value": 1, "Label": "Send once a day" } ], "Selected": "Send once a day", "Selected_id": 1 }, "Units": "", "Min": 0, "Max": 1, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 4, "Node": 16, "Genre": "Config", "Help": "Default: Send once a day", "ValueIDKey": 1125900185501716, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588422682} +OpenZWave/1/node/16/instance/1/commandclass/112/value/1407375162212369/,{ "Label": "Temperature Report Threshold", "Value": 1, "Units": "0.1°C", "Min": 0, "Max": 50, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 5, "Node": 16, "Genre": "Config", "Help": "0: Don't send temperature automatically. 1-50: Report temperature at 0.1-5.0°C temperature difference. Default: 5 (Delta = 0.5°C)", "ValueIDKey": 1407375162212369, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueChanged", "TimeStamp": 1588422810} +OpenZWave/1/node/16/instance/1/commandclass/112/value/1688850138923025/,{ "Label": "Valve Opening Percentage Report", "Value": 5, "Units": "", "Min": 0, "Max": 100, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 6, "Node": 16, "Genre": "Config", "Help": "0: Don't send Valve opening percentage automatically. 1-100: Report valve opening percentage at a delta of 1-100%. Default: 0", "ValueIDKey": 1688850138923025, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueChanged", "TimeStamp": 1588422811} +OpenZWave/1/node/16/instance/1/commandclass/112/value/1970325115633684/,{ "Label": "Open Window Detection", "Value": { "List": [ { "Value": 0, "Label": "Disabled" }, { "Value": 1, "Label": "Low sensibility" }, { "Value": 2, "Label": "Medium sensibility" }, { "Value": 3, "Label": "High sensibility" } ], "Selected": "Medium sensibility", "Selected_id": 2 }, "Units": "", "Min": 0, "Max": 3, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 7, "Node": 16, "Genre": "Config", "Help": "Default: Medium sensibility", "ValueIDKey": 1970325115633684, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588422682} +OpenZWave/1/node/16/instance/1/commandclass/112/value/2251800092344337/,{ "Label": "Measured Temperature Offset", "Value": 0, "Units": "", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 8, "Node": 16, "Genre": "Config", "Help": "206-255: -5.0 to -0.1°C. 0-50: 0°C-5°C. 128: External Temperature Sensor. Default: 0 (0.0°C Offset)", "ValueIDKey": 2251800092344337, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588422682} +OpenZWave/1/node/16/instance/1/commandclass/94/,{ "Instance": 1, "CommandClassId": 94, "CommandClass": "COMMAND_CLASS_ZWAVEPLUS_INFO", "TimeStamp": 1588422682} +OpenZWave/1/node/16/instance/1/commandclass/94/value/282558481/,{ "Label": "ZWave+ Version", "Value": 1, "Units": "", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_ZWAVEPLUS_INFO", "Index": 0, "Node": 16, "Genre": "System", "Help": "ZWave+ Version Supported on the Device", "ValueIDKey": 282558481, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588422682} +OpenZWave/1/node/16/instance/1/commandclass/94/value/281475259269142/,{ "Label": "InstallerIcon", "Value": 4608, "Units": "", "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_ZWAVEPLUS_INFO", "Index": 1, "Node": 16, "Genre": "System", "Help": "Icon File to use for the Installer Application", "ValueIDKey": 281475259269142, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588422682} +OpenZWave/1/node/16/instance/1/commandclass/94/value/562950235979798/,{ "Label": "UserIcon", "Value": 4608, "Units": "", "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_ZWAVEPLUS_INFO", "Index": 2, "Node": 16, "Genre": "System", "Help": "Icon File to use for the User Application", "ValueIDKey": 562950235979798, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588422682} +OpenZWave/1/node/16/instance/1/commandclass/114/,{ "Instance": 1, "CommandClassId": 114, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "TimeStamp": 1588422682} +OpenZWave/1/node/16/instance/1/commandclass/114/value/282886163/,{ "Label": "Loaded Config Revision", "Value": 0, "Units": "", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 0, "Node": 16, "Genre": "System", "Help": "Revision of the Config file currently loaded", "ValueIDKey": 282886163, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588422682} +OpenZWave/1/node/16/instance/1/commandclass/114/value/281475259596819/,{ "Label": "Config File Revision", "Value": 5, "Units": "", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 1, "Node": 16, "Genre": "System", "Help": "Revision of the Config file on the File System", "ValueIDKey": 281475259596819, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588422682} +OpenZWave/1/node/16/instance/1/commandclass/114/value/562950236307475/,{ "Label": "Latest Available Config File Revision", "Value": 5, "Units": "", "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 2, "Node": 16, "Genre": "System", "Help": "Latest Revision of the Config file available for download", "ValueIDKey": 562950236307475, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588422682} +OpenZWave/1/node/16/instance/1/commandclass/114/value/844425213018135/,{ "Label": "Device ID", "Value": "", "Units": "", "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 3, "Node": 16, "Genre": "System", "Help": "Manufacturer Specific Device ID/Model", "ValueIDKey": 844425213018135, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588422682} +OpenZWave/1/node/16/instance/1/commandclass/114/value/1125900189728791/,{ "Label": "Serial Number", "Value": "", "Units": "", "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 4, "Node": 16, "Genre": "System", "Help": "Device Serial Number", "ValueIDKey": 1125900189728791, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588422682} +OpenZWave/1/node/16/instance/1/commandclass/115/,{ "Instance": 1, "CommandClassId": 115, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "TimeStamp": 1588422682} +OpenZWave/1/node/16/instance/1/commandclass/115/value/282902548/,{ "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", "Selected_id": 0 }, "Units": "dB", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 0, "Node": 16, "Genre": "System", "Help": "Output RF PowerLevel", "ValueIDKey": 282902548, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588422682} +OpenZWave/1/node/16/instance/1/commandclass/115/value/281475259613201/,{ "Label": "Timeout", "Value": 0, "Units": "seconds", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 1, "Node": 16, "Genre": "System", "Help": "Timeout till the PowerLevel is reset to Normal", "ValueIDKey": 281475259613201, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588422682} +OpenZWave/1/node/16/instance/1/commandclass/115/value/562950236323864/,{ "Label": "Set Powerlevel", "Value": false, "Units": "", "Min": 0, "Max": 0, "Type": "Button", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 2, "Node": 16, "Genre": "System", "Help": "Apply the Output PowerLevel and Timeout Values", "ValueIDKey": 562950236323864, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588422682} +OpenZWave/1/node/16/instance/1/commandclass/115/value/844425213034513/,{ "Label": "Test Node", "Value": 0, "Units": "", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 3, "Node": 16, "Genre": "System", "Help": "Node to Perform a test against", "ValueIDKey": 844425213034513, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588422682} +OpenZWave/1/node/16/instance/1/commandclass/115/value/1125900189745172/,{ "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", "Selected_id": 0 }, "Units": "dB", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 4, "Node": 16, "Genre": "System", "Help": "PowerLevel to use for the Test", "ValueIDKey": 1125900189745172, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588422682} +OpenZWave/1/node/16/instance/1/commandclass/115/value/1407375166455830/,{ "Label": "Frame Count", "Value": 0, "Units": "", "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 5, "Node": 16, "Genre": "System", "Help": "How Many Messages to send to the Node for the Test", "ValueIDKey": 1407375166455830, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588422682} +OpenZWave/1/node/16/instance/1/commandclass/115/value/1688850143166488/,{ "Label": "Test", "Value": false, "Units": "", "Min": 0, "Max": 0, "Type": "Button", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 6, "Node": 16, "Genre": "System", "Help": "Perform a PowerLevel Test against the a Node", "ValueIDKey": 1688850143166488, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588422682} +OpenZWave/1/node/16/instance/1/commandclass/115/value/1970325119877144/,{ "Label": "Report", "Value": false, "Units": "", "Min": 0, "Max": 0, "Type": "Button", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 7, "Node": 16, "Genre": "System", "Help": "Get the results of the latest PowerLevel Test against a Node", "ValueIDKey": 1970325119877144, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588422682} +OpenZWave/1/node/16/instance/1/commandclass/115/value/2251800096587796/,{ "Label": "Test Status", "Value": { "List": [ { "Value": 0, "Label": "Failed" }, { "Value": 1, "Label": "Success" }, { "Value": 2, "Label": "In Progress" } ], "Selected": "Failed", "Selected_id": 0 }, "Units": "", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 8, "Node": 16, "Genre": "System", "Help": "The Current Status of the last PowerNode Test Executed", "ValueIDKey": 2251800096587796, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588422682} +OpenZWave/1/node/16/instance/1/commandclass/115/value/2533275073298454/,{ "Label": "Acked Frames", "Value": 0, "Units": "", "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 9, "Node": 16, "Genre": "System", "Help": "Number of Messages successfully Acked by the Target Node", "ValueIDKey": 2533275073298454, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588422682} +OpenZWave/1/node/16/instance/1/commandclass/117/,{ "Instance": 1, "CommandClassId": 117, "CommandClass": "COMMAND_CLASS_PROTECTION", "TimeStamp": 1588422682} +OpenZWave/1/node/16/instance/1/commandclass/117/value/282935316/,{ "Label": "Protection", "Value": { "List": [ { "Value": 0, "Label": "Unprotected" }, { "Value": 1, "Label": "Protection by Sequence" }, { "Value": 2, "Label": "No Operation Possible" } ], "Selected": "Protection by Sequence", "Selected_id": 1 }, "Units": "", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_PROTECTION", "Index": 0, "Node": 16, "Genre": "System", "Help": "Protect a device against unintentional control", "ValueIDKey": 282935316, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588422682} +OpenZWave/1/node/16/instance/1/commandclass/128/,{ "Instance": 1, "CommandClassId": 128, "CommandClass": "COMMAND_CLASS_BATTERY", "TimeStamp": 1588422682} +OpenZWave/1/node/16/instance/1/commandclass/128/value/274726929/,{ "Label": "Battery Level", "Value": 90, "Units": "%", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_BATTERY", "Index": 0, "Node": 16, "Genre": "User", "Help": "Current Battery Level", "ValueIDKey": 274726929, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588422682} +OpenZWave/1/node/16/instance/1/commandclass/134/,{ "Instance": 1, "CommandClassId": 134, "CommandClass": "COMMAND_CLASS_VERSION", "TimeStamp": 1588422682} +OpenZWave/1/node/16/instance/1/commandclass/134/value/283213847/,{ "Label": "Library Version", "Value": "3", "Units": "", "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_VERSION", "Index": 0, "Node": 16, "Genre": "System", "Help": "Z-Wave Library Version", "ValueIDKey": 283213847, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588422682} +OpenZWave/1/node/16/instance/1/commandclass/134/value/281475259924503/,{ "Label": "Protocol Version", "Value": "4.61", "Units": "", "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_VERSION", "Index": 1, "Node": 16, "Genre": "System", "Help": "Z-Wave Protocol Version", "ValueIDKey": 281475259924503, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588422682} +OpenZWave/1/node/16/instance/1/commandclass/134/value/562950236635159/,{ "Label": "Application Version", "Value": "0.15", "Units": "", "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_VERSION", "Index": 2, "Node": 16, "Genre": "System", "Help": "Application Version", "ValueIDKey": 562950236635159, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588422682} +OpenZWave/1/node/16/instance/1/commandclass/152/,{ "Instance": 1, "CommandClassId": 152, "CommandClass": "COMMAND_CLASS_SECURITY", "TimeStamp": 1588422682} +OpenZWave/1/node/16/instance/1/commandclass/152/value/283508752/,{ "Label": "Secured", "Value": true, "Units": "", "Min": 0, "Max": 0, "Type": "Bool", "Instance": 1, "CommandClass": "COMMAND_CLASS_SECURITY", "Index": 0, "Node": 16, "Genre": "System", "Help": "Is Communication with Device Encrypted", "ValueIDKey": 283508752, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588422682} +OpenZWave/1/node/16/instance/1/commandclass/49/,{ "Instance": 1, "CommandClassId": 49, "CommandClass": "COMMAND_CLASS_SENSOR_MULTILEVEL", "TimeStamp": 1588422682} +OpenZWave/1/node/16/instance/1/commandclass/49/value/281475250143250/,{ "Label": "Air Temperature", "Value": 17.260000228881837, "Units": "C", "Min": 0, "Max": 0, "Type": "Decimal", "Instance": 1, "CommandClass": "COMMAND_CLASS_SENSOR_MULTILEVEL", "Index": 1, "Node": 16, "Genre": "User", "Help": "Air Temperature Sensor Value", "ValueIDKey": 281475250143250, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueChanged", "TimeStamp": 1588422760} +OpenZWave/1/node/16/instance/1/commandclass/49/value/72057594319749140/,{ "Label": "Air Temperature Units", "Value": { "List": [ { "Value": 0, "Label": "Celsius" } ], "Selected": "Celsius", "Selected_id": 0 }, "Units": "", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_SENSOR_MULTILEVEL", "Index": 256, "Node": 16, "Genre": "System", "Help": "Air Temperature Sensor Available Units", "ValueIDKey": 72057594319749140, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588422682} +OpenZWave/1/node/16/instance/1/commandclass/113/,{ "Instance": 1, "CommandClassId": 113, "CommandClass": "COMMAND_CLASS_NOTIFICATION", "TimeStamp": 1588422682} +OpenZWave/1/node/16/instance/1/commandclass/113/value/72057594312409105/,{ "Label": "Previous Event Cleared", "Value": 0, "Units": "", "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_NOTIFICATION", "Index": 256, "Node": 16, "Genre": "User", "Help": "Previous Event that was sent", "ValueIDKey": 72057594312409105, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588422682} +OpenZWave/1/node/16/instance/1/commandclass/113/value/2251800088166420/,{ "Label": "Power Management", "Value": { "List": [ { "Value": 0, "Label": "Clear" }, { "Value": 10, "Label": "Replace Battery Soon" }, { "Value": 11, "Label": "Replace Battery Now" } ], "Selected": "Clear", "Selected_id": 0 }, "Units": "", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_NOTIFICATION", "Index": 8, "Node": 16, "Genre": "User", "Help": "Power Management Alerts", "ValueIDKey": 2251800088166420, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588422682} +OpenZWave/1/node/16/instance/1/commandclass/113/value/74872344079515671/,{ "Label": "Error Code", "Value": "", "Units": "", "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_NOTIFICATION", "Index": 266, "Node": 16, "Genre": "User", "Help": "The Error Code returned by the device", "ValueIDKey": 74872344079515671, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588422682} +OpenZWave/1/node/16/instance/1/commandclass/113/value/2533275064877076/,{ "Label": "System", "Value": { "List": [ { "Value": 0, "Label": "Clear" }, { "Value": 3, "Label": "Hardware Failure Code" } ], "Selected": "Clear", "Selected_id": 0 }, "Units": "", "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_NOTIFICATION", "Index": 9, "Node": 16, "Genre": "User", "Help": "System Alerts", "ValueIDKey": 2533275064877076, "ReadOnly": false, "WriteOnly": false, "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Event": "valueAdded", "TimeStamp": 1588422682} +OpenZWave/1/node/16/association/1/,{ "Name": "Group 1", "Help": "", "MaxAssociations": 1, "Members": [ "1.0" ], "TimeStamp": 1588422682} \ No newline at end of file From 0dea4f2ad1ce7b864657dc114698e1bcce49cf71 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 21 May 2020 17:43:30 +0200 Subject: [PATCH 125/406] Update frontend to 20200519.3 (#35925) * Updated frontend to 20200519.2 * Updated frontend to 20200519.3 --- 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 01d1b6eb88f..042ae78535d 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==20200519.1"], + "requirements": ["home-assistant-frontend==20200519.3"], "dependencies": [ "api", "auth", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 6410e3b3800..9e635fbde9d 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -12,7 +12,7 @@ cryptography==2.9.2 defusedxml==0.6.0 distro==1.5.0 hass-nabucasa==0.34.2 -home-assistant-frontend==20200519.1 +home-assistant-frontend==20200519.3 importlib-metadata==1.6.0 jinja2>=2.11.1 netdisco==2.6.0 diff --git a/requirements_all.txt b/requirements_all.txt index e01380c591b..315a9a477ff 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -734,7 +734,7 @@ hole==0.5.1 holidays==0.10.2 # homeassistant.components.frontend -home-assistant-frontend==20200519.1 +home-assistant-frontend==20200519.3 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 50882ae20a9..7a032df47a6 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -318,7 +318,7 @@ hole==0.5.1 holidays==0.10.2 # homeassistant.components.frontend -home-assistant-frontend==20200519.1 +home-assistant-frontend==20200519.3 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From 973f66a974712c4e706a2444bf2aff8b3cb9187e Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Fri, 22 May 2020 00:05:00 +0000 Subject: [PATCH 126/406] [ci skip] Translation update --- .../components/abode/translations/it.json | 2 +- .../components/acmeda/translations/fr.json | 12 ++++++ .../components/acmeda/translations/it.json | 16 ++++++++ .../components/acmeda/translations/nl.json | 16 ++++++++ .../components/agent_dvr/translations/fr.json | 3 +- .../components/airly/translations/it.json | 2 +- .../components/airvisual/translations/it.json | 2 +- .../ambiclimate/translations/it.json | 2 +- .../ambient_station/translations/it.json | 2 +- .../components/arcam_fmj/translations/it.json | 5 +++ .../components/arcam_fmj/translations/nl.json | 5 +++ .../components/atag/translations/it.json | 3 +- .../components/blebox/translations/it.json | 1 + .../components/blink/translations/fr.json | 10 +++++ .../components/blink/translations/it.json | 27 ++++++++++++ .../components/blink/translations/nl.json | 23 +++++++++++ .../components/blink/translations/pl.json | 10 ++++- .../components/braviatv/translations/it.json | 2 +- .../components/brother/translations/it.json | 2 +- .../components/bsblan/translations/it.json | 4 +- .../cert_expiry/translations/it.json | 4 +- .../components/daikin/translations/it.json | 2 +- .../components/doorbird/translations/it.json | 2 +- .../components/ecobee/translations/it.json | 2 +- .../components/elgato/translations/it.json | 4 +- .../components/elkm1/translations/it.json | 4 +- .../forked_daapd/translations/it.json | 41 +++++++++++++++++++ .../forked_daapd/translations/nl.json | 41 +++++++++++++++++++ .../components/fritzbox/translations/it.json | 2 +- .../components/gogogate2/translations/it.json | 22 ++++++++++ .../components/gogogate2/translations/nl.json | 22 ++++++++++ .../components/hangouts/translations/it.json | 2 +- .../components/harmony/translations/it.json | 2 +- .../components/iaqualink/translations/it.json | 2 +- .../components/icloud/translations/it.json | 1 + .../components/ipp/translations/it.json | 13 +++--- .../components/ipp/translations/nl.json | 3 +- .../components/isy994/translations/it.json | 1 + .../components/isy994/translations/nl.json | 5 +++ .../components/konnected/translations/it.json | 4 +- .../logi_circle/translations/it.json | 2 +- .../components/melcloud/translations/it.json | 4 +- .../components/monoprice/translations/it.json | 2 +- .../components/notion/translations/it.json | 2 +- .../components/nws/translations/it.json | 2 +- .../components/onvif/translations/it.json | 1 + .../components/onvif/translations/nl.json | 1 + .../components/openuv/translations/it.json | 2 +- .../components/pi_hole/translations/it.json | 23 +++++++++++ .../components/pi_hole/translations/nl.json | 11 +++++ .../components/pi_hole/translations/pl.json | 8 +++- .../components/plex/translations/it.json | 3 +- .../components/plex/translations/nl.json | 1 + .../components/plex/translations/pl.json | 2 + .../components/point/translations/it.json | 2 +- .../components/rachio/translations/it.json | 2 +- .../components/roku/translations/it.json | 6 +-- .../components/roomba/translations/it.json | 2 +- .../components/samsungtv/translations/it.json | 2 +- .../components/sense/translations/it.json | 2 +- .../simplisafe/translations/it.json | 2 +- .../components/solaredge/translations/it.json | 2 +- .../components/solarlog/translations/it.json | 2 +- .../components/songpal/translations/it.json | 1 + .../components/starline/translations/it.json | 2 +- .../synology_dsm/translations/it.json | 4 +- .../components/tesla/translations/it.json | 2 +- .../components/tuya/translations/pl.json | 7 +++- .../components/upnp/translations/it.json | 5 +++ .../components/upnp/translations/nl.json | 1 + .../components/vesync/translations/it.json | 2 +- .../components/vilfo/translations/it.json | 4 +- .../components/vizio/translations/it.json | 2 +- .../components/wiffi/translations/it.json | 25 +++++++++++ .../components/wiffi/translations/nl.json | 25 +++++++++++ .../components/wled/translations/it.json | 2 +- .../xiaomi_miio/translations/it.json | 3 +- .../xiaomi_miio/translations/nl.json | 3 ++ .../components/zerproc/translations/it.json | 14 +++++++ .../components/zerproc/translations/nl.json | 3 ++ .../components/zerproc/translations/pl.json | 8 +++- .../components/zha/translations/fr.json | 8 +++- .../components/zwave/translations/it.json | 2 +- 83 files changed, 468 insertions(+), 69 deletions(-) create mode 100644 homeassistant/components/acmeda/translations/fr.json create mode 100644 homeassistant/components/acmeda/translations/it.json create mode 100644 homeassistant/components/acmeda/translations/nl.json create mode 100644 homeassistant/components/blink/translations/it.json create mode 100644 homeassistant/components/blink/translations/nl.json create mode 100644 homeassistant/components/forked_daapd/translations/it.json create mode 100644 homeassistant/components/forked_daapd/translations/nl.json create mode 100644 homeassistant/components/gogogate2/translations/it.json create mode 100644 homeassistant/components/gogogate2/translations/nl.json create mode 100644 homeassistant/components/isy994/translations/nl.json create mode 100644 homeassistant/components/pi_hole/translations/it.json create mode 100644 homeassistant/components/pi_hole/translations/nl.json create mode 100644 homeassistant/components/wiffi/translations/it.json create mode 100644 homeassistant/components/wiffi/translations/nl.json create mode 100644 homeassistant/components/zerproc/translations/it.json create mode 100644 homeassistant/components/zerproc/translations/nl.json diff --git a/homeassistant/components/abode/translations/it.json b/homeassistant/components/abode/translations/it.json index 414ffb92ef4..97b5d562283 100644 --- a/homeassistant/components/abode/translations/it.json +++ b/homeassistant/components/abode/translations/it.json @@ -12,7 +12,7 @@ "user": { "data": { "password": "Password", - "username": "Indirizzo email" + "username": "E-mail" }, "title": "Inserisci le tue informazioni di accesso Abode" } diff --git a/homeassistant/components/acmeda/translations/fr.json b/homeassistant/components/acmeda/translations/fr.json new file mode 100644 index 00000000000..03798dc33b7 --- /dev/null +++ b/homeassistant/components/acmeda/translations/fr.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "id": "ID de l'h\u00f4te" + }, + "title": "Choisissez un hub \u00e0 ajouter" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/acmeda/translations/it.json b/homeassistant/components/acmeda/translations/it.json new file mode 100644 index 00000000000..a9349d06923 --- /dev/null +++ b/homeassistant/components/acmeda/translations/it.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "all_configured": "Non sono stati scoperti nuovi hub Pulse." + }, + "step": { + "user": { + "data": { + "id": "ID host" + }, + "title": "Scegliere un hub da aggiungere" + } + } + }, + "title": "Rollease Acmeda Automate" +} \ No newline at end of file diff --git a/homeassistant/components/acmeda/translations/nl.json b/homeassistant/components/acmeda/translations/nl.json new file mode 100644 index 00000000000..76b680a5f8c --- /dev/null +++ b/homeassistant/components/acmeda/translations/nl.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "all_configured": "Geen nieuwe Pulse hubs ontdekt." + }, + "step": { + "user": { + "data": { + "id": "Host ID" + }, + "title": "Kies een hub om toe te voegen" + } + } + }, + "title": "Rollease Acmeda Automate" +} \ No newline at end of file diff --git a/homeassistant/components/agent_dvr/translations/fr.json b/homeassistant/components/agent_dvr/translations/fr.json index dc9a372a0c0..89f70da0af1 100644 --- a/homeassistant/components/agent_dvr/translations/fr.json +++ b/homeassistant/components/agent_dvr/translations/fr.json @@ -12,7 +12,8 @@ "data": { "host": "H\u00f4te", "port": "Port" - } + }, + "title": "Configurer l'agent DVR" } } }, diff --git a/homeassistant/components/airly/translations/it.json b/homeassistant/components/airly/translations/it.json index c42af14f030..e394b7af8d3 100644 --- a/homeassistant/components/airly/translations/it.json +++ b/homeassistant/components/airly/translations/it.json @@ -10,7 +10,7 @@ "step": { "user": { "data": { - "api_key": "Chiave API Airly", + "api_key": "Chiave API", "latitude": "Latitudine", "longitude": "Logitudine", "name": "Nome dell'integrazione" diff --git a/homeassistant/components/airvisual/translations/it.json b/homeassistant/components/airvisual/translations/it.json index 9831011c7b2..334900cd731 100644 --- a/homeassistant/components/airvisual/translations/it.json +++ b/homeassistant/components/airvisual/translations/it.json @@ -21,7 +21,7 @@ "node_pro": { "data": { "ip_address": "Indirizzo IP/Nome host dell'unit\u00e0", - "password": "Password dell'unit\u00e0" + "password": "Password" }, "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" diff --git a/homeassistant/components/ambiclimate/translations/it.json b/homeassistant/components/ambiclimate/translations/it.json index 2da7a0ee4c8..2bda20def83 100644 --- a/homeassistant/components/ambiclimate/translations/it.json +++ b/homeassistant/components/ambiclimate/translations/it.json @@ -14,7 +14,7 @@ }, "step": { "auth": { - "description": "Segui questo [link]({authorization_url}) e Consenti accesso al tuo account Ambiclimate, quindi torna indietro e premi Invia qui sotto. \n (Assicurati che l'URL di richiamata specificato sia {cb_url})", + "description": "Segui questo [link]({authorization_url}) e **Consenti** l'accesso al tuo account Ambiclimate, quindi torna indietro e premi **Invia** qui sotto. \n(Assicurati che l'URL di richiamata specificato sia {cb_url})", "title": "Autenticare Ambiclimate" } } diff --git a/homeassistant/components/ambient_station/translations/it.json b/homeassistant/components/ambient_station/translations/it.json index 1991d053f6c..ceecb5363c8 100644 --- a/homeassistant/components/ambient_station/translations/it.json +++ b/homeassistant/components/ambient_station/translations/it.json @@ -10,7 +10,7 @@ "step": { "user": { "data": { - "api_key": "API Key", + "api_key": "Chiave API", "app_key": "Application Key" }, "title": "Inserisci i tuoi dati" diff --git a/homeassistant/components/arcam_fmj/translations/it.json b/homeassistant/components/arcam_fmj/translations/it.json index b78b8cbaa7b..3f0165cbb4a 100644 --- a/homeassistant/components/arcam_fmj/translations/it.json +++ b/homeassistant/components/arcam_fmj/translations/it.json @@ -1,3 +1,8 @@ { + "device_automation": { + "trigger_type": { + "turn_on": "\u00c8 stato richiesto di attivare {entity_name}" + } + }, "title": "Arcam FMJ" } \ No newline at end of file diff --git a/homeassistant/components/arcam_fmj/translations/nl.json b/homeassistant/components/arcam_fmj/translations/nl.json index b78b8cbaa7b..d9781c001b9 100644 --- a/homeassistant/components/arcam_fmj/translations/nl.json +++ b/homeassistant/components/arcam_fmj/translations/nl.json @@ -1,3 +1,8 @@ { + "device_automation": { + "trigger_type": { + "turn_on": "{entity_name} is gevraagd in te schakelen" + } + }, "title": "Arcam FMJ" } \ No newline at end of file diff --git a/homeassistant/components/atag/translations/it.json b/homeassistant/components/atag/translations/it.json index 190da0f14d7..84d07585e73 100644 --- a/homeassistant/components/atag/translations/it.json +++ b/homeassistant/components/atag/translations/it.json @@ -9,8 +9,9 @@ "step": { "user": { "data": { + "email": "E-mail (Opzionale)", "host": "Host", - "port": "Porta (10000)" + "port": "Porta" }, "title": "Connettersi al dispositivo" } diff --git a/homeassistant/components/blebox/translations/it.json b/homeassistant/components/blebox/translations/it.json index 6b36ef97a51..73f7b9276e9 100644 --- a/homeassistant/components/blebox/translations/it.json +++ b/homeassistant/components/blebox/translations/it.json @@ -13,6 +13,7 @@ "step": { "user": { "data": { + "host": "Indirizzo IP", "port": "Porta" }, "description": "Configura BleBox per l'integrazione con Home Assistant.", diff --git a/homeassistant/components/blink/translations/fr.json b/homeassistant/components/blink/translations/fr.json index 0e46e20a75e..c0283817e60 100644 --- a/homeassistant/components/blink/translations/fr.json +++ b/homeassistant/components/blink/translations/fr.json @@ -1,8 +1,18 @@ { "config": { + "error": { + "invalid_auth": "Authentification invalide", + "unknown": "Erreur inattendue" + }, "step": { "2fa": { "title": "Authentification \u00e0 deux facteurs" + }, + "user": { + "data": { + "password": "Mot de passe", + "username": "Identifiant" + } } } } diff --git a/homeassistant/components/blink/translations/it.json b/homeassistant/components/blink/translations/it.json new file mode 100644 index 00000000000..47099ed8611 --- /dev/null +++ b/homeassistant/components/blink/translations/it.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato" + }, + "error": { + "invalid_auth": "Autenticazione non valida", + "unknown": "Errore imprevisto" + }, + "step": { + "2fa": { + "data": { + "2fa": "Codice a due fattori" + }, + "description": "Inserisci il PIN inviato alla tua e-mail. Se l'e-mail non contiene un PIN, lasciare vuoto", + "title": "Autenticazione a due fattori" + }, + "user": { + "data": { + "password": "Password", + "username": "Nome utente" + }, + "title": "Accedi con l'account Blink" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/blink/translations/nl.json b/homeassistant/components/blink/translations/nl.json new file mode 100644 index 00000000000..2af76f4f414 --- /dev/null +++ b/homeassistant/components/blink/translations/nl.json @@ -0,0 +1,23 @@ +{ + "config": { + "error": { + "unknown": "Onverwachte fout" + }, + "step": { + "2fa": { + "data": { + "2fa": "Twee-factor code" + }, + "description": "Voer de pincode in die naar uw e-mail is gestuurd. Als de e-mail geen pincode bevat, laat u dit leeg", + "title": "Tweestapsverificatie" + }, + "user": { + "data": { + "password": "Wachtwoord", + "username": "Gebruikersnaam" + }, + "title": "Aanmelden met Blink account" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/blink/translations/pl.json b/homeassistant/components/blink/translations/pl.json index 1bbec96e5b5..99e3f9aebd9 100644 --- a/homeassistant/components/blink/translations/pl.json +++ b/homeassistant/components/blink/translations/pl.json @@ -8,11 +8,19 @@ "unknown": "Nieoczekiwany b\u0142\u0105d." }, "step": { + "2fa": { + "data": { + "2fa": "Kod uwierzytelniania dwusk\u0142adnikowego" + }, + "description": "Wpisz kod PIN wys\u0142any na Tw\u00f3j adres e-mail. Je\u015bli wiadomo\u015b\u0107 e-mail nie zawiera kodu PIN, pozostaw pole puste.", + "title": "Uwierzytelnianie dwusk\u0142adnikowe" + }, "user": { "data": { "password": "Has\u0142o", "username": "Nazwa u\u017cytkownika" - } + }, + "title": "Zaloguj si\u0119 za pomoc\u0105 konta Blink" } } } diff --git a/homeassistant/components/braviatv/translations/it.json b/homeassistant/components/braviatv/translations/it.json index c6fe7db4439..1b20c51009b 100644 --- a/homeassistant/components/braviatv/translations/it.json +++ b/homeassistant/components/braviatv/translations/it.json @@ -18,7 +18,7 @@ }, "user": { "data": { - "host": "Nome host TV o indirizzo IP" + "host": "Host" }, "description": "Configurare l'integrazione TV di Sony Bravia. In caso di problemi con la configurazione visitare: https://www.home-assistant.io/integrations/braviatv\n\nAssicurarsi che il televisore sia acceso.", "title": "Sony Bravia TV" diff --git a/homeassistant/components/brother/translations/it.json b/homeassistant/components/brother/translations/it.json index 7631709d0b0..d007b8d0394 100644 --- a/homeassistant/components/brother/translations/it.json +++ b/homeassistant/components/brother/translations/it.json @@ -13,7 +13,7 @@ "step": { "user": { "data": { - "host": "Nome host o indirizzo IP della stampante", + "host": "Host", "type": "Tipo di stampante" }, "description": "Configurare l'integrazione della stampante Brother. In caso di problemi con la configurazione, visitare: https://www.home-assistant.io/integrations/brother", diff --git a/homeassistant/components/bsblan/translations/it.json b/homeassistant/components/bsblan/translations/it.json index 176d582fed1..421a5ad975a 100644 --- a/homeassistant/components/bsblan/translations/it.json +++ b/homeassistant/components/bsblan/translations/it.json @@ -10,9 +10,9 @@ "step": { "user": { "data": { - "host": "Host o indirizzo IP", + "host": "Host", "passkey": "Stringa passkey", - "port": "Numero porta" + "port": "Porta" }, "description": "Configura il tuo dispositivo BSB-Lan per l'integrazione con Home Assistant.", "title": "Collegamento al dispositivo BSB-Lan" diff --git a/homeassistant/components/cert_expiry/translations/it.json b/homeassistant/components/cert_expiry/translations/it.json index c5e56bc95a2..29b26710b62 100644 --- a/homeassistant/components/cert_expiry/translations/it.json +++ b/homeassistant/components/cert_expiry/translations/it.json @@ -12,9 +12,9 @@ "step": { "user": { "data": { - "host": "L'hostname del certificato", + "host": "Host", "name": "Il nome del certificato", - "port": "La porta del certificato" + "port": "Porta" }, "title": "Definire il certificato da testare" } diff --git a/homeassistant/components/daikin/translations/it.json b/homeassistant/components/daikin/translations/it.json index 634d500ef1e..08715eaec4c 100644 --- a/homeassistant/components/daikin/translations/it.json +++ b/homeassistant/components/daikin/translations/it.json @@ -17,7 +17,7 @@ "key": "Chiave API", "password": "Password" }, - "description": "Inserisci l'indirizzo IP del tuo Daikin AC.", + "description": "Inserisci l'indirizzo IP del tuo condizionatore d'aria Daikin. \n\nSi noti che Chiave API e Password sono usati rispettivamente dai dispositivi BRP072Cxx e SKYFi.", "title": "Configura Daikin AC" } } diff --git a/homeassistant/components/doorbird/translations/it.json b/homeassistant/components/doorbird/translations/it.json index 6bfbdbf8401..ee9c603fb13 100644 --- a/homeassistant/components/doorbird/translations/it.json +++ b/homeassistant/components/doorbird/translations/it.json @@ -14,7 +14,7 @@ "step": { "user": { "data": { - "host": "Host (indirizzo IP)", + "host": "Host", "name": "Nome del dispositivo", "password": "Password", "username": "Nome utente" diff --git a/homeassistant/components/ecobee/translations/it.json b/homeassistant/components/ecobee/translations/it.json index 428ce782291..dce66271b9a 100644 --- a/homeassistant/components/ecobee/translations/it.json +++ b/homeassistant/components/ecobee/translations/it.json @@ -14,7 +14,7 @@ }, "user": { "data": { - "api_key": "API Key" + "api_key": "Chiave API" }, "description": "Inserisci la chiave API ottenuta da ecobee.com.", "title": "chiave API ecobee" diff --git a/homeassistant/components/elgato/translations/it.json b/homeassistant/components/elgato/translations/it.json index 6d9e8607676..d354c97b8c1 100644 --- a/homeassistant/components/elgato/translations/it.json +++ b/homeassistant/components/elgato/translations/it.json @@ -11,8 +11,8 @@ "step": { "user": { "data": { - "host": "Host o indirizzo IP", - "port": "Numero porta" + "host": "Host", + "port": "Porta" }, "description": "Configura Elgato Key Light per l'integrazione con Home Assistant.", "title": "Collega il tuo Elgato Key Light" diff --git a/homeassistant/components/elkm1/translations/it.json b/homeassistant/components/elkm1/translations/it.json index c9f3f0e1876..e6eeacaf661 100644 --- a/homeassistant/components/elkm1/translations/it.json +++ b/homeassistant/components/elkm1/translations/it.json @@ -13,11 +13,11 @@ "user": { "data": { "address": "L'indirizzo IP o il dominio o la porta seriale se ci si connette tramite seriale.", - "password": "Password (solo sicura).", + "password": "Password", "prefix": "Un prefisso univoco (lasciare vuoto se si dispone di un solo ElkM1).", "protocol": "Protocollo", "temperature_unit": "L'unit\u00e0 di temperatura utilizzata da ElkM1.", - "username": "Nome utente (solo sicuro)." + "username": "Nome utente" }, "description": "La stringa di indirizzi deve essere nella forma \"address[:port]\" per \"secure\" e \"non secure\". Esempio: '192.168.1.1.1'. La porta \u00e8 facoltativa e il valore predefinito \u00e8 2101 per 'non sicuro' e 2601 per 'sicuro'. Per il protocollo seriale, l'indirizzo deve essere nella forma 'tty[:baud]'. Esempio: '/dev/ttyS1'. Il baud \u00e8 opzionale e il valore predefinito \u00e8 115200.", "title": "Collegamento al controllo Elk-M1" diff --git a/homeassistant/components/forked_daapd/translations/it.json b/homeassistant/components/forked_daapd/translations/it.json new file mode 100644 index 00000000000..f6d4517c7e7 --- /dev/null +++ b/homeassistant/components/forked_daapd/translations/it.json @@ -0,0 +1,41 @@ +{ + "config": { + "abort": { + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato.", + "not_forked_daapd": "Il dispositivo non \u00e8 un server forked-daapd." + }, + "error": { + "unknown_error": "Errore sconosciuto.", + "websocket_not_enabled": "websocket del server forked-daapd non abilitato.", + "wrong_host_or_port": "Impossibile connettersi. Si prega di controllare host e porta.", + "wrong_password": "Password errata", + "wrong_server_type": "L'integrazione forked-daapd richiede un server forked-daapd con versione >= 27.0." + }, + "flow_title": "server forked-daapd: {name} ({host})", + "step": { + "user": { + "data": { + "host": "Host", + "name": "Nome descrittivo", + "password": "Password API (lasciare vuota se non c'\u00e8 password)", + "port": "Porta API" + }, + "title": "Configurare il dispositivo forked-daapd" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "librespot_java_port": "Porta per il controllo pipe librespot-java (se utilizzata)", + "max_playlists": "Numero massimo di playlist utilizzate come origini", + "tts_pause_time": "Secondi di pausa prima e dopo il TTS", + "tts_volume": "Volume TTS (variabile nell'intervallo [0,1])" + }, + "description": "Impostare le varie opzioni per l'integrazione forked-daapd.", + "title": "Configura le opzioni forked-daapd" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/forked_daapd/translations/nl.json b/homeassistant/components/forked_daapd/translations/nl.json new file mode 100644 index 00000000000..73dd47b2498 --- /dev/null +++ b/homeassistant/components/forked_daapd/translations/nl.json @@ -0,0 +1,41 @@ +{ + "config": { + "abort": { + "already_configured": "Apparaat is al geconfigureerd.", + "not_forked_daapd": "Apparaat is geen forked-daapd-server." + }, + "error": { + "unknown_error": "Onbekende fout.", + "websocket_not_enabled": "forked-daapd server websocket niet ingeschakeld.", + "wrong_host_or_port": "Verbinding mislukt, controleer het host-adres en poort.", + "wrong_password": "Onjuist wachtwoord.", + "wrong_server_type": "De forked-daapd-integratie vereist een forked-daapd-server met versie >= 27.0." + }, + "flow_title": "forked-daapd server: {naam} ({host})", + "step": { + "user": { + "data": { + "host": "Host", + "name": "Vriendelijke naam", + "password": "API-wachtwoord (leeg laten als er geen wachtwoord is)", + "port": "API-poort" + }, + "title": "Stel een forked-daapd apparaat in" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "librespot_java_port": "Poort voor librespot-java pipe control (indien gebruikt)", + "max_playlists": "Maximum aantal afspeellijsten dat als bronnen wordt gebruikt", + "tts_pause_time": "Seconden om te pauzeren voor en na TTS", + "tts_volume": "TTS-volume (float in het bereik [0,1])" + }, + "description": "Stel verschillende opties in voor de fork-daapd integratie.", + "title": "Configureer forked-daapd opties" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/fritzbox/translations/it.json b/homeassistant/components/fritzbox/translations/it.json index f51868956bc..8e1a1fe9b4d 100644 --- a/homeassistant/components/fritzbox/translations/it.json +++ b/homeassistant/components/fritzbox/translations/it.json @@ -21,7 +21,7 @@ }, "user": { "data": { - "host": "Host o indirizzo IP", + "host": "Host", "password": "Password", "username": "Nome utente" }, diff --git a/homeassistant/components/gogogate2/translations/it.json b/homeassistant/components/gogogate2/translations/it.json new file mode 100644 index 00000000000..378d55630a4 --- /dev/null +++ b/homeassistant/components/gogogate2/translations/it.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "cannot_connect": "Impossibile connettersi" + }, + "error": { + "cannot_connect": "Impossibile connettersi", + "invalid_auth": "Autenticazione non valida" + }, + "step": { + "user": { + "data": { + "ip_address": "Indirizzo IP", + "password": "Password", + "username": "Nome utente" + }, + "description": "Fornire le informazioni richieste di seguito.", + "title": "Configurazione GogoGate2" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gogogate2/translations/nl.json b/homeassistant/components/gogogate2/translations/nl.json new file mode 100644 index 00000000000..ad8e894d093 --- /dev/null +++ b/homeassistant/components/gogogate2/translations/nl.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "cannot_connect": "Kon niet verbinden" + }, + "error": { + "cannot_connect": "Kon niet verbinden", + "invalid_auth": "Ongeldige authenticatie" + }, + "step": { + "user": { + "data": { + "ip_address": "IP-adres", + "password": "Wachtwoord", + "username": "Gebruikersnaam" + }, + "description": "Geef hieronder de vereiste informatie op.", + "title": "Stel GogoGate2 in" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hangouts/translations/it.json b/homeassistant/components/hangouts/translations/it.json index 094280da4ef..29ddab24913 100644 --- a/homeassistant/components/hangouts/translations/it.json +++ b/homeassistant/components/hangouts/translations/it.json @@ -20,7 +20,7 @@ "user": { "data": { "authorization_code": "Codice di autorizzazione (necessario per l'autenticazione manuale)", - "email": "Indirizzo E-mail", + "email": "E-mail", "password": "Password" }, "description": "Vuoto", diff --git a/homeassistant/components/harmony/translations/it.json b/homeassistant/components/harmony/translations/it.json index 8095fa05156..c658e69e0c0 100644 --- a/homeassistant/components/harmony/translations/it.json +++ b/homeassistant/components/harmony/translations/it.json @@ -15,7 +15,7 @@ }, "user": { "data": { - "host": "Nome dell'host o indirizzo IP", + "host": "Host", "name": "Nome Hub" }, "title": "Configurare Logitech Harmony Hub" diff --git a/homeassistant/components/iaqualink/translations/it.json b/homeassistant/components/iaqualink/translations/it.json index 471b21cdc9e..568f91961f2 100644 --- a/homeassistant/components/iaqualink/translations/it.json +++ b/homeassistant/components/iaqualink/translations/it.json @@ -10,7 +10,7 @@ "user": { "data": { "password": "Password", - "username": "Nome Utente / Indirizzo E-mail" + "username": "Nome utente" }, "description": "Inserisci il nome utente e la password del tuo account iAqualink.", "title": "Collegati a iAqualink" diff --git a/homeassistant/components/icloud/translations/it.json b/homeassistant/components/icloud/translations/it.json index 80cdfed7ace..27429081e1e 100644 --- a/homeassistant/components/icloud/translations/it.json +++ b/homeassistant/components/icloud/translations/it.json @@ -20,6 +20,7 @@ "user": { "data": { "password": "Password", + "username": "E-mail", "with_family": "Con la famiglia" }, "description": "Inserisci le tue credenziali", diff --git a/homeassistant/components/ipp/translations/it.json b/homeassistant/components/ipp/translations/it.json index d2f53fec24b..c1eacf00c28 100644 --- a/homeassistant/components/ipp/translations/it.json +++ b/homeassistant/components/ipp/translations/it.json @@ -1,15 +1,16 @@ { "config": { "abort": { - "already_configured": "Questa stampante \u00e8 gi\u00e0 configurata.", - "connection_error": "Impossibile connettersi alla stampante.", + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", + "connection_error": "Impossibile connettersi", "connection_upgrade": "Impossibile connettersi alla stampante a causa della necessit\u00e0 dell'aggiornamento della connessione.", "ipp_error": "Si \u00e8 verificato un errore IPP.", "ipp_version_error": "Versione IPP non supportata dalla stampante.", - "parse_error": "Impossibile analizzare la risposta dalla stampante." + "parse_error": "Impossibile analizzare la risposta dalla stampante.", + "unique_id_required": "Identificazione univoca del dispositivo mancante necessaria per l'individuazione." }, "error": { - "connection_error": "Impossibile connettersi alla stampante.", + "connection_error": "Impossibile connettersi", "connection_upgrade": "Impossibile connettersi alla stampante. Riprovare selezionando l'opzione SSL/TLS." }, "flow_title": "Stampante: {name}", @@ -17,7 +18,7 @@ "user": { "data": { "base_path": "Percorso relativo alla stampante", - "host": "Host o indirizzo IP", + "host": "Host", "port": "Porta", "ssl": "La stampante supporta la comunicazione su SSL/TLS", "verify_ssl": "La stampante utilizza un certificato SSL adeguato" @@ -26,7 +27,7 @@ "title": "Collegare la stampante" }, "zeroconf_confirm": { - "description": "Vuoi aggiungere la stampante denominata `{name}` a Home Assistant?", + "description": "Vuoi configurare {name}?", "title": "Stampante rilevata" } } diff --git a/homeassistant/components/ipp/translations/nl.json b/homeassistant/components/ipp/translations/nl.json index 12f9d37ec7a..e021c8a0010 100644 --- a/homeassistant/components/ipp/translations/nl.json +++ b/homeassistant/components/ipp/translations/nl.json @@ -6,7 +6,8 @@ "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." + "parse_error": "Ongeldige reactie van de printer.", + "unique_id_required": "Apparaat ontbreekt een unieke identificatie die nodig is voor de ontdekking." }, "error": { "connection_error": "Kan geen verbinding maken met de printer.", diff --git a/homeassistant/components/isy994/translations/it.json b/homeassistant/components/isy994/translations/it.json index ccb5ff85b87..9b35a464c1b 100644 --- a/homeassistant/components/isy994/translations/it.json +++ b/homeassistant/components/isy994/translations/it.json @@ -9,6 +9,7 @@ "invalid_host": "La voce host non era nel formato URL completo, ad esempio http://192.168.10.100:80", "unknown": "Errore imprevisto" }, + "flow_title": "Universal Devices ISY994 {name} ({host})", "step": { "user": { "data": { diff --git a/homeassistant/components/isy994/translations/nl.json b/homeassistant/components/isy994/translations/nl.json new file mode 100644 index 00000000000..35e52de882f --- /dev/null +++ b/homeassistant/components/isy994/translations/nl.json @@ -0,0 +1,5 @@ +{ + "config": { + "flow_title": "Universele apparaten ISY994 {naam} ({host})" + } +} \ No newline at end of file diff --git a/homeassistant/components/konnected/translations/it.json b/homeassistant/components/konnected/translations/it.json index 84fdcac4880..684e561156b 100644 --- a/homeassistant/components/konnected/translations/it.json +++ b/homeassistant/components/konnected/translations/it.json @@ -20,8 +20,8 @@ }, "user": { "data": { - "host": "Indirizzo IP del dispositivo Konnected", - "port": "Porta del dispositivo Konnected" + "host": "Indirizzo IP", + "port": "Porta" }, "description": "Si prega di inserire le informazioni dell'host per il tuo Pannello Konnected.", "title": "Rileva il dispositivo Konnected" diff --git a/homeassistant/components/logi_circle/translations/it.json b/homeassistant/components/logi_circle/translations/it.json index 6ce7948472e..2d6e7621281 100644 --- a/homeassistant/components/logi_circle/translations/it.json +++ b/homeassistant/components/logi_circle/translations/it.json @@ -16,7 +16,7 @@ }, "step": { "auth": { - "description": "Segui il link qui sotto e Accetta l'accesso al tuo account Logi Circle, quindi torna indietro e premi Invia qui sotto. \n\n [Link]({authorization_url})", + "description": "Segui il link qui sotto e **Accetta** l'accesso al tuo account Logi Circle, quindi torna indietro e premi **Invia** qui sotto. \n\n [Link]({authorization_url})", "title": "Autenticarsi con Logi Circle" }, "user": { diff --git a/homeassistant/components/melcloud/translations/it.json b/homeassistant/components/melcloud/translations/it.json index c027473ad14..2a312561932 100644 --- a/homeassistant/components/melcloud/translations/it.json +++ b/homeassistant/components/melcloud/translations/it.json @@ -11,8 +11,8 @@ "step": { "user": { "data": { - "password": "Password MELCloud.", - "username": "Email utilizzata per accedere a MELCloud." + "password": "Password", + "username": "E-mail" }, "description": "Connettiti utilizzando il tuo account MELCloud.", "title": "Connettersi a MELCloud" diff --git a/homeassistant/components/monoprice/translations/it.json b/homeassistant/components/monoprice/translations/it.json index d16a249d1a5..b89758a9da3 100644 --- a/homeassistant/components/monoprice/translations/it.json +++ b/homeassistant/components/monoprice/translations/it.json @@ -10,7 +10,7 @@ "step": { "user": { "data": { - "port": "Porta seriale", + "port": "Porta", "source_1": "Nome della fonte n. 1", "source_2": "Nome della fonte n. 2", "source_3": "Nome della fonte n. 3", diff --git a/homeassistant/components/notion/translations/it.json b/homeassistant/components/notion/translations/it.json index b60cdeb60f0..c7626f59202 100644 --- a/homeassistant/components/notion/translations/it.json +++ b/homeassistant/components/notion/translations/it.json @@ -11,7 +11,7 @@ "user": { "data": { "password": "Password", - "username": "Nome utente / indirizzo E-mail" + "username": "Nome utente" }, "title": "Inserisci le tue informazioni" } diff --git a/homeassistant/components/nws/translations/it.json b/homeassistant/components/nws/translations/it.json index f4b110f857f..92c519513b4 100644 --- a/homeassistant/components/nws/translations/it.json +++ b/homeassistant/components/nws/translations/it.json @@ -10,7 +10,7 @@ "step": { "user": { "data": { - "api_key": "Chiave API (email)", + "api_key": "Chiave API", "latitude": "Latitudine", "longitude": "Logitudine", "station": "Codice stazione METAR" diff --git a/homeassistant/components/onvif/translations/it.json b/homeassistant/components/onvif/translations/it.json index 689babb357c..374c65b99a1 100644 --- a/homeassistant/components/onvif/translations/it.json +++ b/homeassistant/components/onvif/translations/it.json @@ -34,6 +34,7 @@ "manual_input": { "data": { "host": "Host", + "name": "Nome", "port": "Porta" }, "title": "Configurare il dispositivo ONVIF" diff --git a/homeassistant/components/onvif/translations/nl.json b/homeassistant/components/onvif/translations/nl.json index 90a8c4d4993..71ba0d115be 100644 --- a/homeassistant/components/onvif/translations/nl.json +++ b/homeassistant/components/onvif/translations/nl.json @@ -17,6 +17,7 @@ "manual_input": { "data": { "host": "Host", + "name": "Naam", "port": "Poort" } } diff --git a/homeassistant/components/openuv/translations/it.json b/homeassistant/components/openuv/translations/it.json index 89d80216fd5..8a07b3c3175 100644 --- a/homeassistant/components/openuv/translations/it.json +++ b/homeassistant/components/openuv/translations/it.json @@ -7,7 +7,7 @@ "step": { "user": { "data": { - "api_key": "API Key di OpenUV", + "api_key": "Chiave API", "elevation": "Altitudine", "latitude": "Latitudine", "longitude": "Longitudine" diff --git a/homeassistant/components/pi_hole/translations/it.json b/homeassistant/components/pi_hole/translations/it.json new file mode 100644 index 00000000000..d8ee9d3c6b7 --- /dev/null +++ b/homeassistant/components/pi_hole/translations/it.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Il servizio \u00e8 gi\u00e0 configurato", + "duplicated_name": "Il nome \u00e8 gi\u00e0 esistente" + }, + "error": { + "cannot_connect": "Impossibile connettersi" + }, + "step": { + "user": { + "data": { + "api_key": "Chiave API (opzionale)", + "host": "Host", + "name": "Nome", + "port": "Porta", + "ssl": "Utilizzare SSL", + "verify_ssl": "Verificare il certificato SSL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/pi_hole/translations/nl.json b/homeassistant/components/pi_hole/translations/nl.json new file mode 100644 index 00000000000..16ef25a15fa --- /dev/null +++ b/homeassistant/components/pi_hole/translations/nl.json @@ -0,0 +1,11 @@ +{ + "config": { + "abort": { + "already_configured": "Service al geconfigureerd", + "duplicated_name": "Naam bestond al" + }, + "error": { + "cannot_connect": "Kon niet verbinden" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/pi_hole/translations/pl.json b/homeassistant/components/pi_hole/translations/pl.json index cc7013e98bd..c4986e71aa7 100644 --- a/homeassistant/components/pi_hole/translations/pl.json +++ b/homeassistant/components/pi_hole/translations/pl.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Us\u0142uga jest ju\u017c skonfigurowana." + "already_configured": "Us\u0142uga jest ju\u017c skonfigurowana.", + "duplicated_name": "Nazwa ju\u017c istnieje." }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia." @@ -11,7 +12,10 @@ "data": { "api_key": "Klucz API (opcjonalnie)", "host": "Nazwa hosta lub adres IP", - "port": "Port" + "name": "Nazwa", + "port": "Port", + "ssl": "U\u017cyj SSL", + "verify_ssl": "Weryfikacja certyfikatu SSL" } } } diff --git a/homeassistant/components/plex/translations/it.json b/homeassistant/components/plex/translations/it.json index d7bd1060985..8a95984fd9a 100644 --- a/homeassistant/components/plex/translations/it.json +++ b/homeassistant/components/plex/translations/it.json @@ -16,10 +16,11 @@ "not_found": "Server Plex non trovato", "ssl_error": "Problema con il certificato SSL" }, + "flow_title": "{name} ({host})", "step": { "manual_setup": { "data": { - "host": "Host (opzionale se si \u00e8 fornito un Token)", + "host": "Host", "port": "Porta", "ssl": "Utilizzare SSL", "token": "Token (opzionale)", diff --git a/homeassistant/components/plex/translations/nl.json b/homeassistant/components/plex/translations/nl.json index 6938bdea5f5..5a3372fa902 100644 --- a/homeassistant/components/plex/translations/nl.json +++ b/homeassistant/components/plex/translations/nl.json @@ -14,6 +14,7 @@ "no_servers": "Geen servers gekoppeld aan account", "not_found": "Plex-server niet gevonden" }, + "flow_title": "{naam} ({host})", "step": { "select_server": { "data": { diff --git a/homeassistant/components/plex/translations/pl.json b/homeassistant/components/plex/translations/pl.json index 2c002d77bbd..15d272f3d93 100644 --- a/homeassistant/components/plex/translations/pl.json +++ b/homeassistant/components/plex/translations/pl.json @@ -16,6 +16,7 @@ "not_found": "Nie znaleziono serwera Plex", "ssl_error": "Problem z certyfikatem SSL." }, + "flow_title": "{name} ({host})", "step": { "manual_setup": { "data": { @@ -55,6 +56,7 @@ "plex_mp_settings": { "data": { "ignore_new_shared_users": "Ignoruj nowych zarz\u0105dzanych/wsp\u00f3\u0142dzielonych u\u017cytkownik\u00f3w", + "ignore_plex_web_clients": "Zignoruj klient\u00f3w Plex Web", "monitored_users": "Monitorowani u\u017cytkownicy", "use_episode_art": "U\u017cyj grafiki odcinka" }, diff --git a/homeassistant/components/point/translations/it.json b/homeassistant/components/point/translations/it.json index 90f9c1b73cd..ec38d1a16f3 100644 --- a/homeassistant/components/point/translations/it.json +++ b/homeassistant/components/point/translations/it.json @@ -16,7 +16,7 @@ }, "step": { "auth": { - "description": "Segui il link qui sotto e Accetta l'accesso al tuo account Minut, quindi torna indietro e premi Invia qui sotto. \n\n [Link] ( {authorization_url} )", + "description": "Segui il link qui sotto e ** Accetta** l'accesso al tuo account Minut, quindi torna indietro e premi **Invia** qui sotto. \n\n [Link]({authorization_url})", "title": "Autenticare Point" }, "user": { diff --git a/homeassistant/components/rachio/translations/it.json b/homeassistant/components/rachio/translations/it.json index 5a9c7618b00..f8013eb3551 100644 --- a/homeassistant/components/rachio/translations/it.json +++ b/homeassistant/components/rachio/translations/it.json @@ -11,7 +11,7 @@ "step": { "user": { "data": { - "api_key": "Chiave API per l'account Rachio." + "api_key": "Chiave API" }, "description": "\u00c8 necessaria la chiave API di https://app.rach.io/. Selezionare 'Impostazioni Account', quindi fare clic su 'GET API KEY'.", "title": "Connettiti al tuo dispositivo Rachio" diff --git a/homeassistant/components/roku/translations/it.json b/homeassistant/components/roku/translations/it.json index 0192c848e09..3116497faf1 100644 --- a/homeassistant/components/roku/translations/it.json +++ b/homeassistant/components/roku/translations/it.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "Il dispositivo Roku \u00e8 gi\u00e0 configurato", + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", "unknown": "Errore imprevisto" }, "error": { - "cannot_connect": "Impossibile connettersi, si prega di riprovare" + "cannot_connect": "Impossibile connettersi" }, "flow_title": "Roku: {name}", "step": { @@ -19,7 +19,7 @@ }, "user": { "data": { - "host": "Host o indirizzo IP" + "host": "Host" }, "description": "Inserisci le tue informazioni Roku.", "title": "Roku" diff --git a/homeassistant/components/roomba/translations/it.json b/homeassistant/components/roomba/translations/it.json index 042959a67c4..cea9d1d0f77 100644 --- a/homeassistant/components/roomba/translations/it.json +++ b/homeassistant/components/roomba/translations/it.json @@ -11,7 +11,7 @@ "certificate": "Certificato", "continuous": "Continuo", "delay": "Ritardo", - "host": "Nome dell'host o indirizzo IP", + "host": "Host", "password": "Password" }, "description": "Attualmente il recupero del BLID e della password \u00e8 un processo manuale. Si prega di seguire i passi descritti nella documentazione all'indirizzo: https://www.home-assistant.io/integrations/roomba/#retrieving-your-credentials", diff --git a/homeassistant/components/samsungtv/translations/it.json b/homeassistant/components/samsungtv/translations/it.json index 7b62a48e62f..244f8079fc5 100644 --- a/homeassistant/components/samsungtv/translations/it.json +++ b/homeassistant/components/samsungtv/translations/it.json @@ -15,7 +15,7 @@ }, "user": { "data": { - "host": "Host o indirizzo IP", + "host": "Host", "name": "Nome" }, "description": "Inserisci le informazioni del tuo Samsung TV. Se non hai mai connesso Home Assistant in precedenza, dovresti vedere un messaggio sul TV in cui \u00e8 richiesta l'autorizzazione.", diff --git a/homeassistant/components/sense/translations/it.json b/homeassistant/components/sense/translations/it.json index 2320eef1a9b..4e1ddd01b42 100644 --- a/homeassistant/components/sense/translations/it.json +++ b/homeassistant/components/sense/translations/it.json @@ -11,7 +11,7 @@ "step": { "user": { "data": { - "email": "Indirizzo E-Mail", + "email": "E-mail", "password": "Password" }, "title": "Connettiti al tuo Sense Energy Monitor" diff --git a/homeassistant/components/simplisafe/translations/it.json b/homeassistant/components/simplisafe/translations/it.json index c63894ceaf2..c970cd6d48b 100644 --- a/homeassistant/components/simplisafe/translations/it.json +++ b/homeassistant/components/simplisafe/translations/it.json @@ -12,7 +12,7 @@ "data": { "code": "Codice (utilizzato nell'Interfaccia Utente di Home Assistant)", "password": "Password", - "username": "Indirizzo E-mail" + "username": "E-mail" }, "title": "Inserisci le tue informazioni." } diff --git a/homeassistant/components/solaredge/translations/it.json b/homeassistant/components/solaredge/translations/it.json index eeac06cee2d..28b34bdbd3c 100644 --- a/homeassistant/components/solaredge/translations/it.json +++ b/homeassistant/components/solaredge/translations/it.json @@ -9,7 +9,7 @@ "step": { "user": { "data": { - "api_key": "La chiave API per questo sito", + "api_key": "Chiave API", "name": "Il nome di questa installazione", "site_id": "Il sito-id di SolarEdge" }, diff --git a/homeassistant/components/solarlog/translations/it.json b/homeassistant/components/solarlog/translations/it.json index 046faae52f7..c227ba7a16c 100644 --- a/homeassistant/components/solarlog/translations/it.json +++ b/homeassistant/components/solarlog/translations/it.json @@ -10,7 +10,7 @@ "step": { "user": { "data": { - "host": "Il nome host o l'indirizzo IP del dispositivo Solar-Log", + "host": "Host", "name": "Il prefisso da utilizzare per i sensori Solar-Log" }, "title": "Definire la connessione Solar-Log" diff --git a/homeassistant/components/songpal/translations/it.json b/homeassistant/components/songpal/translations/it.json index 6e0eb1fdc94..884f468d356 100644 --- a/homeassistant/components/songpal/translations/it.json +++ b/homeassistant/components/songpal/translations/it.json @@ -5,6 +5,7 @@ "not_songpal_device": "Non \u00e8 un dispositivo Songpal" }, "error": { + "cannot_connect": "Impossibile connettersi", "connection": "Errore di connessione: controlla il tuo endpoint" }, "flow_title": "Sony Songpal {name} ({host})", diff --git a/homeassistant/components/starline/translations/it.json b/homeassistant/components/starline/translations/it.json index 01364b9c3ce..57bc2213103 100644 --- a/homeassistant/components/starline/translations/it.json +++ b/homeassistant/components/starline/translations/it.json @@ -11,7 +11,7 @@ "app_id": "ID applicazione", "app_secret": "Segreto" }, - "description": "ID applicazione e codice segreto da Account sviluppatore StarLine ", + "description": "ID applicazione e codice segreto dall'[account sviluppatore StarLine](https://my.starline.ru/developer)", "title": "Credenziali dell'applicazione" }, "auth_captcha": { diff --git a/homeassistant/components/synology_dsm/translations/it.json b/homeassistant/components/synology_dsm/translations/it.json index 3bdd7ab0faf..ef02d9fe977 100644 --- a/homeassistant/components/synology_dsm/translations/it.json +++ b/homeassistant/components/synology_dsm/translations/it.json @@ -22,7 +22,7 @@ "data": { "api_version": "Versione DSM", "password": "Password", - "port": "Porta (opzionale)", + "port": "Porta", "ssl": "Utilizzare SSL/TLS per connettersi al NAS", "username": "Nome utente" }, @@ -34,7 +34,7 @@ "api_version": "Versione DSM", "host": "Host", "password": "Password", - "port": "Porta (opzionale)", + "port": "Porta", "ssl": "Utilizzare SSL/TLS per connettersi al NAS", "username": "Nome utente" }, diff --git a/homeassistant/components/tesla/translations/it.json b/homeassistant/components/tesla/translations/it.json index 3a77ce669eb..0c4ae68fe0f 100644 --- a/homeassistant/components/tesla/translations/it.json +++ b/homeassistant/components/tesla/translations/it.json @@ -10,7 +10,7 @@ "user": { "data": { "password": "Password", - "username": "Indirizzo E-Mail" + "username": "E-mail" }, "description": "Si prega di inserire le tue informazioni.", "title": "Tesla - Configurazione" diff --git a/homeassistant/components/tuya/translations/pl.json b/homeassistant/components/tuya/translations/pl.json index d79f81e6850..0291bef6217 100644 --- a/homeassistant/components/tuya/translations/pl.json +++ b/homeassistant/components/tuya/translations/pl.json @@ -9,12 +9,17 @@ "error": { "auth_failed": "Niepoprawne uwierzytelnienie." }, + "flow_title": "Konfiguracja integracji Tuya", "step": { "user": { "data": { + "country_code": "Kod kraju twojego konta (np. 1 dla USA lub 86 dla Chin)", "password": "Has\u0142o", + "platform": "Aplikacja, w kt\u00f3rej zarejestrowane jest Twoje konto", "username": "Nazwa u\u017cytkownika" - } + }, + "description": "Wprowad\u017a dane uwierzytelniaj\u0105ce", + "title": "Tuya" } } } diff --git a/homeassistant/components/upnp/translations/it.json b/homeassistant/components/upnp/translations/it.json index df13cc79d6f..064f448aef7 100644 --- a/homeassistant/components/upnp/translations/it.json +++ b/homeassistant/components/upnp/translations/it.json @@ -3,6 +3,7 @@ "abort": { "already_configured": "UPnP/IGD \u00e8 gi\u00e0 configurato", "incomplete_device": "Ignorare il dispositivo UPnP incompleto", + "incomplete_discovery": "Individuazione incompleta", "no_devices_discovered": "Nessun UPnP/IGD trovato", "no_devices_found": "Nessun dispositivo UPnP/IGD trovato in rete.", "no_sensors_or_port_mapping": "Abilita almeno i sensori o la mappatura delle porte", @@ -18,6 +19,10 @@ "description": "Vuoi configurare UPnP/IGD?", "title": "UPnP/IGD" }, + "init": { + "one": "uno", + "other": "altro" + }, "ssdp_confirm": { "description": "Vuoi configurare questo dispositivo UPnP/IGD?" }, diff --git a/homeassistant/components/upnp/translations/nl.json b/homeassistant/components/upnp/translations/nl.json index c36e6252867..932d13434da 100644 --- a/homeassistant/components/upnp/translations/nl.json +++ b/homeassistant/components/upnp/translations/nl.json @@ -3,6 +3,7 @@ "abort": { "already_configured": "UPnP/IGD is al geconfigureerd", "incomplete_device": "Onvolledig UPnP-apparaat negeren", + "incomplete_discovery": "Onvolledige ontdekking", "no_devices_discovered": "Geen UPnP'/IGD's ontdekt", "no_devices_found": "Geen UPnP/IGD apparaten gevonden op het netwerk.", "no_sensors_or_port_mapping": "Schakel ten minste sensoren of poorttoewijzing in", diff --git a/homeassistant/components/vesync/translations/it.json b/homeassistant/components/vesync/translations/it.json index 42e5b1bb1e4..f18dd28c996 100644 --- a/homeassistant/components/vesync/translations/it.json +++ b/homeassistant/components/vesync/translations/it.json @@ -10,7 +10,7 @@ "user": { "data": { "password": "Password", - "username": "Indirizzo E-mail" + "username": "E-mail" }, "title": "Immettere nome utente e password" } diff --git a/homeassistant/components/vilfo/translations/it.json b/homeassistant/components/vilfo/translations/it.json index 2f39499260e..8d41d74c79d 100644 --- a/homeassistant/components/vilfo/translations/it.json +++ b/homeassistant/components/vilfo/translations/it.json @@ -11,8 +11,8 @@ "step": { "user": { "data": { - "access_token": "Token di accesso per il Vilfo Router API", - "host": "Nome host o IP del router" + "access_token": "Token di accesso", + "host": "Host" }, "description": "Configurare l'integrazione del Vilfo Router. \u00c8 necessario il vostro hostname/IP del Vilfo Router e un token di accesso API. Per ulteriori informazioni su questa integrazione e su come ottenere tali dettagli, visitare il sito: https://www.home-assistant.io/integrations/vilfo", "title": "Collegamento al Vilfo Router" diff --git a/homeassistant/components/vizio/translations/it.json b/homeassistant/components/vizio/translations/it.json index 0212b8a1281..6ec2a7d1b84 100644 --- a/homeassistant/components/vizio/translations/it.json +++ b/homeassistant/components/vizio/translations/it.json @@ -31,7 +31,7 @@ "data": { "access_token": "Token di accesso", "device_class": "Tipo di dispositivo", - "host": "< Host / IP >: ", + "host": "Host", "name": "Nome" }, "description": "Un Token di accesso \u00e8 necessario solo per i televisori. Se si sta configurando un televisore e non si dispone ancora di un Token di accesso, lasciarlo vuoto per passare attraverso un processo di associazione.", diff --git a/homeassistant/components/wiffi/translations/it.json b/homeassistant/components/wiffi/translations/it.json new file mode 100644 index 00000000000..0884f31cbd7 --- /dev/null +++ b/homeassistant/components/wiffi/translations/it.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "addr_in_use": "Porta del server gi\u00e0 in uso.", + "start_server_failed": "Avvio del server non riuscito." + }, + "step": { + "user": { + "data": { + "port": "Porta del server" + }, + "title": "Configurare il server TCP per i dispositivi WIFFI" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "timeout": "Timeout (minuti)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wiffi/translations/nl.json b/homeassistant/components/wiffi/translations/nl.json new file mode 100644 index 00000000000..af14d1942a7 --- /dev/null +++ b/homeassistant/components/wiffi/translations/nl.json @@ -0,0 +1,25 @@ +{ + "config": { + "abort": { + "addr_in_use": "Serverpoort al in gebruik.", + "start_server_failed": "Start server is mislukt." + }, + "step": { + "user": { + "data": { + "port": "Server poort" + }, + "title": "TCP-server instellen voor WIFFI-apparaten" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "timeout": "Time-out (minuten)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wled/translations/it.json b/homeassistant/components/wled/translations/it.json index 60a896c34d1..0c1d505dc6d 100644 --- a/homeassistant/components/wled/translations/it.json +++ b/homeassistant/components/wled/translations/it.json @@ -11,7 +11,7 @@ "step": { "user": { "data": { - "host": "Host o indirizzo IP" + "host": "Host" }, "description": "Configura WLED per l'integrazione con Home Assistant.", "title": "Collega il tuo WLED" diff --git a/homeassistant/components/xiaomi_miio/translations/it.json b/homeassistant/components/xiaomi_miio/translations/it.json index 911e16a3dc5..dae9a8dbbc1 100644 --- a/homeassistant/components/xiaomi_miio/translations/it.json +++ b/homeassistant/components/xiaomi_miio/translations/it.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato" + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", + "already_in_progress": "Il flusso di configurazione per questo dispositivo Xiaomi Miio \u00e8 gi\u00e0 in corso." }, "error": { "connect_error": "Impossibile connettersi", diff --git a/homeassistant/components/xiaomi_miio/translations/nl.json b/homeassistant/components/xiaomi_miio/translations/nl.json index cb4aa077ba0..5daf94a5e0d 100644 --- a/homeassistant/components/xiaomi_miio/translations/nl.json +++ b/homeassistant/components/xiaomi_miio/translations/nl.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_in_progress": "De configuratiestroom voor dit Xiaomi Miio-apparaat is al bezig." + }, "error": { "connect_error": "Verbinding mislukt, probeer het opnieuw", "no_device_selected": "Geen apparaat geselecteerd, selecteer 1 apparaat alstublieft" diff --git a/homeassistant/components/zerproc/translations/it.json b/homeassistant/components/zerproc/translations/it.json new file mode 100644 index 00000000000..91f3a3a984b --- /dev/null +++ b/homeassistant/components/zerproc/translations/it.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "no_devices_found": "Nessun dispositivo trovato sulla rete", + "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." + }, + "step": { + "confirm": { + "description": "Vuoi iniziare la configurazione?" + } + } + }, + "title": "Zerproc" +} \ No newline at end of file diff --git a/homeassistant/components/zerproc/translations/nl.json b/homeassistant/components/zerproc/translations/nl.json new file mode 100644 index 00000000000..cdfd3890fb8 --- /dev/null +++ b/homeassistant/components/zerproc/translations/nl.json @@ -0,0 +1,3 @@ +{ + "title": "Zerproc" +} \ No newline at end of file diff --git a/homeassistant/components/zerproc/translations/pl.json b/homeassistant/components/zerproc/translations/pl.json index 0bd69f84746..429b6f37e65 100644 --- a/homeassistant/components/zerproc/translations/pl.json +++ b/homeassistant/components/zerproc/translations/pl.json @@ -3,6 +3,12 @@ "abort": { "no_devices_found": "Nie znaleziono urz\u0105dze\u0144 w sieci.", "single_instance_allowed": "Ju\u017c skonfigurowano. Mo\u017cliwa jest tylko jedna konfiguracja." + }, + "step": { + "confirm": { + "description": "Czy chcesz rozpocz\u0105\u0107 konfiguracj\u0119?" + } } - } + }, + "title": "Zerproc" } \ No newline at end of file diff --git a/homeassistant/components/zha/translations/fr.json b/homeassistant/components/zha/translations/fr.json index 035ca744dda..41cff30b9e5 100644 --- a/homeassistant/components/zha/translations/fr.json +++ b/homeassistant/components/zha/translations/fr.json @@ -16,14 +16,19 @@ }, "port_config": { "data": { - "baudrate": "vitesse du port" + "baudrate": "vitesse du port", + "flow_control": "contr\u00f4le du flux de donn\u00e9es", + "path": "Chemin du p\u00e9riph\u00e9rique s\u00e9rie" }, + "description": "Saisir les param\u00e8tres sp\u00e9cifiques au port", "title": "R\u00e9glages" }, "user": { "data": { + "path": "Chemin du p\u00e9riph\u00e9rique s\u00e9rie", "radio_type": "Type de radio" }, + "description": "S\u00e9lectionnez le port s\u00e9rie de la radio Zigbee", "title": "ZHA" } } @@ -65,6 +70,7 @@ "device_shaken": "Appareil secou\u00e9", "device_slid": "Appareil gliss\u00e9 \"{subtype}\"", "device_tilted": "Dispositif inclin\u00e9", + "remote_button_alt_triple_press": "\"{subtype}\" bouton triple-cliqu\u00e9 (mode alternatif)", "remote_button_double_press": "Double clic sur le bouton \" {subtype} \"", "remote_button_long_press": "Bouton \"{subtype}\" appuy\u00e9 continuellement", "remote_button_long_release": "Bouton \" {subtype} \" rel\u00e2ch\u00e9 apr\u00e8s un appui long", diff --git a/homeassistant/components/zwave/translations/it.json b/homeassistant/components/zwave/translations/it.json index 8b8ffb732fc..2b1b248e92c 100644 --- a/homeassistant/components/zwave/translations/it.json +++ b/homeassistant/components/zwave/translations/it.json @@ -11,7 +11,7 @@ "user": { "data": { "network_key": "Chiave di rete (lascia vuoto per generare automaticamente)", - "usb_path": "Percorso USB" + "usb_path": "Percorso del dispositivo USB" }, "description": "Vai su https://www.home-assistant.io/docs/z-wave/installation/ per le informazioni sulle variabili di configurazione", "title": "Configura Z-Wave" From 8d5cf1a72c318cb4bfd9bfd1a58715287fc15749 Mon Sep 17 00:00:00 2001 From: Fredrik Erlandsson Date: Fri, 22 May 2020 12:07:01 +0200 Subject: [PATCH 127/406] Fix Daikin AC integration for AirBase units (#35952) --- homeassistant/components/daikin/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/daikin/manifest.json b/homeassistant/components/daikin/manifest.json index 9732962de5a..9b4e76e5eb1 100644 --- a/homeassistant/components/daikin/manifest.json +++ b/homeassistant/components/daikin/manifest.json @@ -3,7 +3,7 @@ "name": "Daikin AC", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/daikin", - "requirements": ["pydaikin==2.0.2"], + "requirements": ["pydaikin==2.0.4"], "codeowners": ["@fredrike"], "quality_scale": "platinum" } diff --git a/requirements_all.txt b/requirements_all.txt index 315a9a477ff..aca5464632b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1263,7 +1263,7 @@ pycsspeechtts==1.0.3 # pycups==1.9.73 # homeassistant.components.daikin -pydaikin==2.0.2 +pydaikin==2.0.4 # homeassistant.components.danfoss_air pydanfossair==0.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 7a032df47a6..9b345ae89fb 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -539,7 +539,7 @@ pychromecast==5.2.0 pycoolmasternet==0.0.4 # homeassistant.components.daikin -pydaikin==2.0.2 +pydaikin==2.0.4 # homeassistant.components.deconz pydeconz==70 From 7035a6da9fb40f9a0233bb6b35bdf61b74c8d5b7 Mon Sep 17 00:00:00 2001 From: Hugues Granger Date: Fri, 22 May 2020 13:08:53 +0200 Subject: [PATCH 128/406] Fix typo in conf[CONF_SSL] (#35946) --- homeassistant/components/zabbix/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/zabbix/__init__.py b/homeassistant/components/zabbix/__init__.py index 644d35da728..a1b4327470f 100644 --- a/homeassistant/components/zabbix/__init__.py +++ b/homeassistant/components/zabbix/__init__.py @@ -40,7 +40,7 @@ def setup(hass, config): """Set up the Zabbix component.""" conf = config[DOMAIN] - protocol = "https" if config[CONF_SSL] else "http" + protocol = "https" if conf[CONF_SSL] else "http" url = urljoin(f"{protocol}://{conf[CONF_HOST]}", conf[CONF_PATH]) username = conf.get(CONF_USERNAME) From be416d9fc66306027991afbc2d9f22557bde794c Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Fri, 22 May 2020 09:24:42 -0400 Subject: [PATCH 129/406] No side effects in zha climate properties (#35942) * No side effects in properties * Remove logging --- homeassistant/components/zha/climate.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/homeassistant/components/zha/climate.py b/homeassistant/components/zha/climate.py index 92f17e70af0..20b52baf72e 100644 --- a/homeassistant/components/zha/climate.py +++ b/homeassistant/components/zha/climate.py @@ -234,8 +234,6 @@ class Thermostat(ZhaEntity, ClimateEntity): self._thrm.pi_heating_demand is None and self._thrm.pi_cooling_demand is None ): - self.debug("Running mode: %s", self._thrm.running_mode) - self.debug("Running state: %s", self._thrm.running_state) running_state = self._thrm.running_state if running_state is None: return None @@ -261,13 +259,7 @@ class Thermostat(ZhaEntity, ClimateEntity): @property def hvac_mode(self) -> Optional[str]: """Return HVAC operation mode.""" - try: - return SYSTEM_MODE_2_HVAC[self._thrm.system_mode] - except KeyError: - self.error( - "can't map 'system_mode: %s' to a HVAC mode", self._thrm.system_mode - ) - return None + return SYSTEM_MODE_2_HVAC.get(self._thrm.system_mode) @property def hvac_modes(self) -> Tuple[str, ...]: From 8cd905487e482e874a384c2e8a02070a53adfb95 Mon Sep 17 00:00:00 2001 From: Jason Hunter Date: Fri, 22 May 2020 12:13:37 -0400 Subject: [PATCH 130/406] Bump pyAV and close unclosed outputs (#35960) * bump pyAV and close unclosed outputs * skip stream from coverage for now * fix divide by zero error --- .coveragerc | 1 + homeassistant/components/stream/core.py | 5 ++++- homeassistant/components/stream/manifest.json | 2 +- homeassistant/components/stream/worker.py | 4 ++++ requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 6 files changed, 12 insertions(+), 4 deletions(-) diff --git a/.coveragerc b/.coveragerc index c49073f28f0..03c1f9f088e 100644 --- a/.coveragerc +++ b/.coveragerc @@ -733,6 +733,7 @@ omit = homeassistant/components/steam_online/sensor.py homeassistant/components/stiebel_eltron/* homeassistant/components/stookalert/* + homeassistant/components/stream/* homeassistant/components/streamlabswater/* homeassistant/components/suez_water/* homeassistant/components/supervisord/sensor.py diff --git a/homeassistant/components/stream/core.py b/homeassistant/components/stream/core.py index 9282c2cb855..153c006ccb2 100644 --- a/homeassistant/components/stream/core.py +++ b/homeassistant/components/stream/core.py @@ -79,8 +79,11 @@ class StreamOutput: @property def target_duration(self) -> int: """Return the average duration of the segments in seconds.""" + segment_length = len(self._segments) + if not segment_length: + return 0 durations = [s.duration for s in self._segments] - return round(sum(durations) // len(self._segments)) or 1 + return round(sum(durations) // segment_length) or 1 def get_segment(self, sequence: int = None) -> Any: """Retrieve a specific segment, or the whole list.""" diff --git a/homeassistant/components/stream/manifest.json b/homeassistant/components/stream/manifest.json index 2cc60938a8d..e90d93cbfe3 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==7.0.1"], + "requirements": ["av==8.0.1"], "dependencies": ["http"], "codeowners": ["@hunterjm"], "quality_scale": "internal" diff --git a/homeassistant/components/stream/worker.py b/homeassistant/components/stream/worker.py index 6cd07c7f926..15c1c3c02ff 100644 --- a/homeassistant/components/stream/worker.py +++ b/homeassistant/components/stream/worker.py @@ -164,3 +164,7 @@ def stream_worker(hass, stream, quit_event): # Assign the video packet to the new stream & mux packet.stream = buffer.vstream buffer.output.mux(packet) + + # Close stream + buffer.output.close() + container.close() diff --git a/requirements_all.txt b/requirements_all.txt index aca5464632b..c1f5956e737 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -291,7 +291,7 @@ atenpdu==0.3.0 aurorapy==0.2.6 # homeassistant.components.stream -av==7.0.1 +av==8.0.1 # homeassistant.components.avea avea==1.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9b345ae89fb..96f65820985 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -141,7 +141,7 @@ arcam-fmj==0.4.6 async-upnp-client==0.14.13 # homeassistant.components.stream -av==7.0.1 +av==8.0.1 # homeassistant.components.axis axis==26 From 7043d1e163509c090867df9f781b7b9d32ca38a9 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Fri, 22 May 2020 22:50:03 +0200 Subject: [PATCH 131/406] Update frontend to 20200519.4 (#35987) --- 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 042ae78535d..89f83d8fa65 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==20200519.3"], + "requirements": ["home-assistant-frontend==20200519.4"], "dependencies": [ "api", "auth", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 9e635fbde9d..0784fb39052 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -12,7 +12,7 @@ cryptography==2.9.2 defusedxml==0.6.0 distro==1.5.0 hass-nabucasa==0.34.2 -home-assistant-frontend==20200519.3 +home-assistant-frontend==20200519.4 importlib-metadata==1.6.0 jinja2>=2.11.1 netdisco==2.6.0 diff --git a/requirements_all.txt b/requirements_all.txt index c1f5956e737..fc3645a3880 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -734,7 +734,7 @@ hole==0.5.1 holidays==0.10.2 # homeassistant.components.frontend -home-assistant-frontend==20200519.3 +home-assistant-frontend==20200519.4 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 96f65820985..4e8fc1f4b07 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -318,7 +318,7 @@ hole==0.5.1 holidays==0.10.2 # homeassistant.components.frontend -home-assistant-frontend==20200519.3 +home-assistant-frontend==20200519.4 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From f74e2f8abbc55ed693de43dedef602ecc565cdd7 Mon Sep 17 00:00:00 2001 From: John Hollowell Date: Fri, 22 May 2020 16:53:17 -0400 Subject: [PATCH 132/406] Update proxmoxer to 1.1.0 (#35951) --- CODEOWNERS | 2 +- .../components/proxmoxve/__init__.py | 21 +++++++------------ .../components/proxmoxve/manifest.json | 4 ++-- requirements_all.txt | 2 +- 4 files changed, 12 insertions(+), 17 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 20336249168..fcc8edf7710 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -312,7 +312,7 @@ homeassistant/components/plugwise/* @laetificat @CoMPaTech @bouwew homeassistant/components/plum_lightpad/* @ColinHarrington homeassistant/components/point/* @fredrike homeassistant/components/powerwall/* @bdraco @jrester -homeassistant/components/proxmoxve/* @k4ds3 +homeassistant/components/proxmoxve/* @k4ds3 @jhollowe homeassistant/components/ps4/* @ktnrg45 homeassistant/components/ptvsd/* @swamp-ig homeassistant/components/push/* @dgomes diff --git a/homeassistant/components/proxmoxve/__init__.py b/homeassistant/components/proxmoxve/__init__.py index 315fb8b1c91..0919feb15e3 100644 --- a/homeassistant/components/proxmoxve/__init__.py +++ b/homeassistant/components/proxmoxve/__init__.py @@ -1,7 +1,6 @@ """Support for Proxmox VE.""" from enum import Enum import logging -import time from proxmoxer import ProxmoxAPI from proxmoxer.backends.https import AuthenticationError @@ -137,25 +136,21 @@ class ProxmoxClient: self._connection_start_time = None def build_client(self): - """Construct the ProxmoxAPI client.""" + """Construct the ProxmoxAPI client. Allows inserting the realm within the `user` value.""" + + if "@" in self._user: + user_id = self._user + else: + user_id = f"{self._user}@{self._realm}" self._proxmox = ProxmoxAPI( self._host, port=self._port, - user=f"{self._user}@{self._realm}", + user=user_id, password=self._password, verify_ssl=self._verify_ssl, ) - self._connection_start_time = time.monotonic() - def get_api_client(self): - """Return the ProxmoxAPI client and rebuild it if necessary.""" - - connection_age = time.monotonic() - self._connection_start_time - - # Workaround for the Proxmoxer bug where the connection stops working after some time - if connection_age > 30 * 60: - self.build_client() - + """Return the ProxmoxAPI client.""" return self._proxmox diff --git a/homeassistant/components/proxmoxve/manifest.json b/homeassistant/components/proxmoxve/manifest.json index 2735bab1b04..4040ca7c469 100644 --- a/homeassistant/components/proxmoxve/manifest.json +++ b/homeassistant/components/proxmoxve/manifest.json @@ -2,6 +2,6 @@ "domain": "proxmoxve", "name": "Proxmox VE", "documentation": "https://www.home-assistant.io/integrations/proxmoxve", - "codeowners": ["@k4ds3"], - "requirements": ["proxmoxer==1.0.4"] + "codeowners": ["@k4ds3", "@jhollowe"], + "requirements": ["proxmoxer==1.1.0"] } diff --git a/requirements_all.txt b/requirements_all.txt index fc3645a3880..d2f63a96407 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1115,7 +1115,7 @@ prometheus_client==0.7.1 protobuf==3.6.1 # homeassistant.components.proxmoxve -proxmoxer==1.0.4 +proxmoxer==1.1.0 # homeassistant.components.systemmonitor psutil==5.7.0 From 6dfc362f98e80673eb40da45f8ce61c0ea9c31d1 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Fri, 22 May 2020 23:01:05 +0200 Subject: [PATCH 133/406] Bump python-openzwave-mqtt to 1.0.2 (#35980) --- homeassistant/components/ozw/manifest.json | 14 +++++++++++--- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/ozw/manifest.json b/homeassistant/components/ozw/manifest.json index 3b828845852..c6c96ed15a2 100644 --- a/homeassistant/components/ozw/manifest.json +++ b/homeassistant/components/ozw/manifest.json @@ -3,7 +3,15 @@ "name": "OpenZWave (beta)", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/ozw", - "requirements": ["python-openzwave-mqtt==1.0.1"], - "after_dependencies": ["mqtt"], - "codeowners": ["@cgarwood", "@marcelveldt", "@MartinHjelmare"] + "requirements": [ + "python-openzwave-mqtt==1.0.2" + ], + "after_dependencies": [ + "mqtt" + ], + "codeowners": [ + "@cgarwood", + "@marcelveldt", + "@MartinHjelmare" + ] } diff --git a/requirements_all.txt b/requirements_all.txt index d2f63a96407..8dde73b786f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1711,7 +1711,7 @@ python-nest==4.1.0 python-nmap==0.6.1 # homeassistant.components.ozw -python-openzwave-mqtt==1.0.1 +python-openzwave-mqtt==1.0.2 # homeassistant.components.qbittorrent python-qbittorrent==0.4.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 4e8fc1f4b07..9b32018ecb7 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -711,7 +711,7 @@ python-miio==0.5.0.1 python-nest==4.1.0 # homeassistant.components.ozw -python-openzwave-mqtt==1.0.1 +python-openzwave-mqtt==1.0.2 # homeassistant.components.songpal python-songpal==0.12 From cc369cd461cf91cbad64435f0e210e229ff224de Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Fri, 22 May 2020 23:01:48 +0200 Subject: [PATCH 134/406] Fix lutron_caseta setup options (#35974) --- .coveragerc | 8 ++- .../components/lutron_caseta/__init__.py | 6 +-- .../components/lutron_caseta/config_flow.py | 5 -- .../components/lutron_caseta/manifest.json | 11 +++-- homeassistant/generated/config_flows.py | 1 - .../lutron_caseta/test_config_flow.py | 49 ++++++++++++++++--- 6 files changed, 57 insertions(+), 23 deletions(-) diff --git a/.coveragerc b/.coveragerc index 03c1f9f088e..7f0f519133f 100644 --- a/.coveragerc +++ b/.coveragerc @@ -440,7 +440,13 @@ omit = homeassistant/components/luftdaten/* homeassistant/components/lupusec/* homeassistant/components/lutron/* - homeassistant/components/lutron_caseta/* + homeassistant/components/lutron_caseta/__init__.py + homeassistant/components/lutron_caseta/binary_sensor.py + homeassistant/components/lutron_caseta/cover.py + homeassistant/components/lutron_caseta/fan.py + homeassistant/components/lutron_caseta/light.py + homeassistant/components/lutron_caseta/scene.py + homeassistant/components/lutron_caseta/switch.py homeassistant/components/lw12wifi/light.py homeassistant/components/lyft/sensor.py homeassistant/components/magicseaweed/sensor.py diff --git a/homeassistant/components/lutron_caseta/__init__.py b/homeassistant/components/lutron_caseta/__init__.py index 59fd81e650e..ff7ec61ecc8 100644 --- a/homeassistant/components/lutron_caseta/__init__.py +++ b/homeassistant/components/lutron_caseta/__init__.py @@ -39,11 +39,7 @@ LUTRON_CASETA_COMPONENTS = ["light", "switch", "cover", "scene", "fan", "binary_ async def async_setup(hass, base_config): """Set up the Lutron component.""" - bridge_configs = base_config.get(DOMAIN) - - if not bridge_configs: - return True - + bridge_configs = base_config[DOMAIN] hass.data.setdefault(DOMAIN, {}) for config in bridge_configs: diff --git a/homeassistant/components/lutron_caseta/config_flow.py b/homeassistant/components/lutron_caseta/config_flow.py index 45a7f10fbf0..3a5a4a151a1 100644 --- a/homeassistant/components/lutron_caseta/config_flow.py +++ b/homeassistant/components/lutron_caseta/config_flow.py @@ -94,11 +94,6 @@ class LutronCasetaFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): await bridge.close() return True - except (KeyError, ValueError): - _LOGGER.error( - "Error while checking connectivity to bridge %s", self.data[CONF_HOST], - ) - return False except Exception: # pylint: disable=broad-except _LOGGER.exception( "Unknown exception while checking connectivity to bridge %s", diff --git a/homeassistant/components/lutron_caseta/manifest.json b/homeassistant/components/lutron_caseta/manifest.json index 7b55dfd9c87..34fc326425c 100644 --- a/homeassistant/components/lutron_caseta/manifest.json +++ b/homeassistant/components/lutron_caseta/manifest.json @@ -2,7 +2,10 @@ "domain": "lutron_caseta", "name": "Lutron Caséta", "documentation": "https://www.home-assistant.io/integrations/lutron_caseta", - "requirements": ["pylutron-caseta==0.6.1"], - "codeowners": ["@swails"], - "config_flow": true -} \ No newline at end of file + "requirements": [ + "pylutron-caseta==0.6.1" + ], + "codeowners": [ + "@swails" + ] +} diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 2918f09d626..208ea3ef07f 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -85,7 +85,6 @@ FLOWS = [ "locative", "logi_circle", "luftdaten", - "lutron_caseta", "mailgun", "melcloud", "met", diff --git a/tests/components/lutron_caseta/test_config_flow.py b/tests/components/lutron_caseta/test_config_flow.py index a528e223e44..fc9c5fe279d 100644 --- a/tests/components/lutron_caseta/test_config_flow.py +++ b/tests/components/lutron_caseta/test_config_flow.py @@ -1,5 +1,4 @@ """Test the Lutron Caseta config flow.""" -from asynctest import patch from pylutron_caseta.smartbridge import Smartbridge from homeassistant import config_entries, data_entry_flow @@ -14,6 +13,7 @@ from homeassistant.components.lutron_caseta.const import ( ) from homeassistant.const import CONF_HOST +from tests.async_mock import AsyncMock, patch from tests.common import MockConfigEntry @@ -51,7 +51,11 @@ async def test_bridge_import_flow(hass): with patch( "homeassistant.components.lutron_caseta.async_setup_entry", return_value=True, - ) as mock_setup_entry, patch.object(Smartbridge, "create_tls") as create_tls: + ) as mock_setup_entry, patch( + "homeassistant.components.lutron_caseta.async_setup", return_value=True + ), patch.object( + Smartbridge, "create_tls" + ) as create_tls: create_tls.return_value = MockBridge(can_connect=True) result = await hass.config_entries.flow.async_init( @@ -77,9 +81,7 @@ async def test_bridge_cannot_connect(hass): CONF_CA_CERTS: "", } - with patch( - "homeassistant.components.lutron_caseta.async_setup_entry", return_value=True, - ) as mock_setup_entry, patch.object(Smartbridge, "create_tls") as create_tls: + with patch.object(Smartbridge, "create_tls") as create_tls: create_tls.return_value = MockBridge(can_connect=False) result = await hass.config_entries.flow.async_init( @@ -91,8 +93,41 @@ async def test_bridge_cannot_connect(hass): assert result["type"] == "form" assert result["step_id"] == STEP_IMPORT_FAILED assert result["errors"] == {"base": ERROR_CANNOT_CONNECT} - # validate setup_entry was not called - assert len(mock_setup_entry.mock_calls) == 0 + + result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) + + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == CasetaConfigFlow.ABORT_REASON_CANNOT_CONNECT + + +async def test_bridge_cannot_connect_unknown_error(hass): + """Test checking for connection and encountering an unknown error.""" + + entry_mock_data = { + CONF_HOST: "", + CONF_KEYFILE: "", + CONF_CERTFILE: "", + CONF_CA_CERTS: "", + } + + with patch.object(Smartbridge, "create_tls") as create_tls: + mock_bridge = MockBridge() + mock_bridge.connect = AsyncMock(side_effect=Exception()) + create_tls.return_value = mock_bridge + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data=entry_mock_data, + ) + + assert result["type"] == "form" + assert result["step_id"] == STEP_IMPORT_FAILED + assert result["errors"] == {"base": ERROR_CANNOT_CONNECT} + + result = await hass.config_entries.flow.async_configure(result["flow_id"], {}) + + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == CasetaConfigFlow.ABORT_REASON_CANNOT_CONNECT async def test_duplicate_bridge_import(hass): From 0e83cfade1b2f89e7ddc3498283245658af722b1 Mon Sep 17 00:00:00 2001 From: Jason Hunter Date: Fri, 22 May 2020 19:46:11 -0400 Subject: [PATCH 135/406] Fix ONVIF Transport (#35932) * allow lib to create AsyncTransport * fix transport close issue --- homeassistant/components/onvif/__init__.py | 3 ++- homeassistant/components/onvif/device.py | 16 +++++++++------- homeassistant/components/onvif/event.py | 4 ++-- homeassistant/components/onvif/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 6 files changed, 16 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/onvif/__init__.py b/homeassistant/components/onvif/__init__.py index 6d90c5828f9..bb8008e1fef 100644 --- a/homeassistant/components/onvif/__init__.py +++ b/homeassistant/components/onvif/__init__.py @@ -82,13 +82,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): if device.capabilities.events and await device.events.async_start(): platforms += ["binary_sensor", "sensor"] - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, device.events.async_stop) for component in platforms: hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, component) ) + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, device.async_stop) + return True diff --git a/homeassistant/components/onvif/device.py b/homeassistant/components/onvif/device.py index 0a35dadec26..d6f407f016d 100644 --- a/homeassistant/components/onvif/device.py +++ b/homeassistant/components/onvif/device.py @@ -8,7 +8,6 @@ from aiohttp.client_exceptions import ClientConnectionError, ServerDisconnectedE import onvif from onvif import ONVIFCamera from onvif.exceptions import ONVIFError -from zeep.asyncio import AsyncTransport from zeep.exceptions import Fault from homeassistant.config_entries import ConfigEntry @@ -20,7 +19,6 @@ from homeassistant.const import ( CONF_USERNAME, ) from homeassistant.core import HomeAssistant -from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.util.dt as dt_util from .const import ( @@ -141,6 +139,12 @@ class ONVIFDevice: return True + async def async_stop(self, event=None): + """Shut it all down.""" + if self.events: + await self.events.async_stop() + await self.device.close() + async def async_check_date_and_time(self) -> None: """Warns if device and system date not synced.""" LOGGER.debug("Setting up the ONVIF device management service") @@ -278,7 +282,7 @@ class ONVIFDevice: is not None, ) - ptz_service = self.device.get_service("ptz") + ptz_service = self.device.create_ptz_service() presets = await ptz_service.GetPresets(profile.token) profile.ptz.presets = [preset.token for preset in presets] @@ -326,7 +330,7 @@ class ONVIFDevice: LOGGER.warning("PTZ actions are not supported on device '%s'", self.name) return - ptz_service = self.device.get_service("ptz") + ptz_service = self.device.create_ptz_service() pan_val = distance * PAN_FACTOR.get(pan, 0) tilt_val = distance * TILT_FACTOR.get(tilt, 0) @@ -423,13 +427,11 @@ class ONVIFDevice: def get_device(hass, host, port, username, password) -> ONVIFCamera: """Get ONVIFCamera instance.""" - session = async_get_clientsession(hass) - transport = AsyncTransport(None, session=session) return ONVIFCamera( host, port, username, password, f"{os.path.dirname(onvif.__file__)}/wsdl/", - transport=transport, + no_cache=True, ) diff --git a/homeassistant/components/onvif/event.py b/homeassistant/components/onvif/event.py index 888fe5bd92b..183ad0ab532 100644 --- a/homeassistant/components/onvif/event.py +++ b/homeassistant/components/onvif/event.py @@ -91,7 +91,7 @@ class EventManager: return self.started - async def async_stop(self, event=None) -> None: + async def async_stop(self) -> None: """Unsubscribe from events.""" if not self._subscription: return @@ -110,7 +110,7 @@ class EventManager: async def async_pull_messages(self, _now: dt = None) -> None: """Pull messages from device.""" try: - pullpoint = self.device.get_service("pullpoint") + pullpoint = self.device.create_pullpoint_service() req = pullpoint.create_type("PullMessages") req.MessageLimit = 100 req.Timeout = dt.timedelta(seconds=60) diff --git a/homeassistant/components/onvif/manifest.json b/homeassistant/components/onvif/manifest.json index f291f9c6613..4214cf3ab5c 100644 --- a/homeassistant/components/onvif/manifest.json +++ b/homeassistant/components/onvif/manifest.json @@ -2,7 +2,7 @@ "domain": "onvif", "name": "ONVIF", "documentation": "https://www.home-assistant.io/integrations/onvif", - "requirements": ["onvif-zeep-async==0.3.0", "WSDiscovery==2.0.0"], + "requirements": ["onvif-zeep-async==0.4.0", "WSDiscovery==2.0.0"], "dependencies": ["ffmpeg"], "codeowners": ["@hunterjm"], "config_flow": true diff --git a/requirements_all.txt b/requirements_all.txt index 8dde73b786f..580b9efa6e1 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -994,7 +994,7 @@ oemthermostat==1.1 onkyo-eiscp==1.2.7 # homeassistant.components.onvif -onvif-zeep-async==0.3.0 +onvif-zeep-async==0.4.0 # homeassistant.components.opengarage open-garage==0.1.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9b32018ecb7..4e50ba04bf4 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -420,7 +420,7 @@ numpy==1.18.4 oauth2client==4.0.0 # homeassistant.components.onvif -onvif-zeep-async==0.3.0 +onvif-zeep-async==0.4.0 # homeassistant.components.openerz openerz-api==0.1.0 From 56efc341dd0cb97d147eefabef55d7e29d123366 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Sat, 23 May 2020 00:02:41 +0000 Subject: [PATCH 136/406] [ci skip] Translation update --- homeassistant/components/almond/translations/pl.json | 2 +- homeassistant/components/auth/translations/fi.json | 3 +++ homeassistant/components/braviatv/translations/pl.json | 2 +- homeassistant/components/brother/translations/pl.json | 2 +- homeassistant/components/bsblan/translations/pl.json | 7 +++++-- homeassistant/components/dialogflow/translations/pl.json | 2 +- homeassistant/components/elgato/translations/pl.json | 4 ++-- homeassistant/components/freebox/translations/pl.json | 2 +- homeassistant/components/geofency/translations/pl.json | 2 +- homeassistant/components/gpslogger/translations/pl.json | 2 +- homeassistant/components/hangouts/translations/fi.json | 3 +++ homeassistant/components/huawei_lte/translations/pl.json | 2 +- homeassistant/components/ifttt/translations/pl.json | 2 +- homeassistant/components/ipp/translations/pl.json | 2 +- homeassistant/components/konnected/translations/pl.json | 2 +- homeassistant/components/locative/translations/pl.json | 2 +- homeassistant/components/mailgun/translations/pl.json | 2 +- homeassistant/components/mqtt/translations/pl.json | 2 +- homeassistant/components/nest/translations/fi.json | 3 ++- homeassistant/components/openuv/translations/fi.json | 3 ++- homeassistant/components/plaato/translations/pl.json | 2 +- homeassistant/components/samsungtv/translations/pl.json | 4 ++-- homeassistant/components/simplisafe/translations/pl.json | 4 ++-- homeassistant/components/smartthings/translations/pl.json | 4 ++-- homeassistant/components/traccar/translations/pl.json | 2 +- homeassistant/components/twilio/translations/pl.json | 2 +- homeassistant/components/upnp/translations/pl.json | 2 +- homeassistant/components/vizio/translations/pl.json | 5 +++-- homeassistant/components/wled/translations/pl.json | 4 ++-- 29 files changed, 46 insertions(+), 34 deletions(-) diff --git a/homeassistant/components/almond/translations/pl.json b/homeassistant/components/almond/translations/pl.json index 6b3feb4bd0b..b9fc05c1b42 100644 --- a/homeassistant/components/almond/translations/pl.json +++ b/homeassistant/components/almond/translations/pl.json @@ -7,7 +7,7 @@ }, "step": { "hassio_confirm": { - "description": "Czy chcesz skonfigurowa\u0107 Home Assistant'a, aby \u0142\u0105czy\u0142 si\u0119 z Almond dostarczonym przez dodatek Hass.io: {addon}?", + "description": "Czy chcesz skonfigurowa\u0107 Home Assistanta, aby \u0142\u0105czy\u0142 si\u0119 z Almond dostarczonym przez dodatek Hass.io: {addon}?", "title": "Almond poprzez dodatek Hass.io" }, "pick_implementation": { diff --git a/homeassistant/components/auth/translations/fi.json b/homeassistant/components/auth/translations/fi.json index b73c7e194f9..92e4f03c0f9 100644 --- a/homeassistant/components/auth/translations/fi.json +++ b/homeassistant/components/auth/translations/fi.json @@ -1,6 +1,9 @@ { "mfa_setup": { "notify": { + "error": { + "invalid_code": "Virheellinen koodi. Yrit\u00e4 uudelleen." + }, "step": { "setup": { "title": "Varmista asetukset" diff --git a/homeassistant/components/braviatv/translations/pl.json b/homeassistant/components/braviatv/translations/pl.json index 337532165f3..3b8262a5559 100644 --- a/homeassistant/components/braviatv/translations/pl.json +++ b/homeassistant/components/braviatv/translations/pl.json @@ -13,7 +13,7 @@ "data": { "pin": "Kod PIN" }, - "description": "Wprowad\u017a kod PIN wy\u015bwietlany na telewizorze Sony Bravia. \n\nJe\u015bli kod PIN nie jest wy\u015bwietlany, musisz wyrejestrowa\u0107 Home Assistant'a na swoim telewizorze, przejd\u017a do Ustawienia -> Sie\u0107 -> Ustawienia urz\u0105dzenia zdalnego -> Wyrejestruj urz\u0105dzenie zdalne.", + "description": "Wprowad\u017a kod PIN wy\u015bwietlany na telewizorze Sony Bravia. \n\nJe\u015bli kod PIN nie jest wy\u015bwietlany, musisz wyrejestrowa\u0107 Home Assistanta na swoim telewizorze, przejd\u017a do Ustawienia -> Sie\u0107 -> Ustawienia urz\u0105dzenia zdalnego -> Wyrejestruj urz\u0105dzenie zdalne.", "title": "Autoryzacja Sony Bravia TV" }, "user": { diff --git a/homeassistant/components/brother/translations/pl.json b/homeassistant/components/brother/translations/pl.json index 37bbf8bc749..28b475013f3 100644 --- a/homeassistant/components/brother/translations/pl.json +++ b/homeassistant/components/brother/translations/pl.json @@ -23,7 +23,7 @@ "data": { "type": "Typ drukarki" }, - "description": "Czy chcesz doda\u0107 drukark\u0119 Brother {model} o numerze seryjnym `{serial_number}` do Home Assistant'a?", + "description": "Czy chcesz doda\u0107 drukark\u0119 Brother {model} o numerze seryjnym `{serial_number}` do Home Assistanta?", "title": "Wykryto drukark\u0119 Brother" } } diff --git a/homeassistant/components/bsblan/translations/pl.json b/homeassistant/components/bsblan/translations/pl.json index e70e415d73f..a510785f52f 100644 --- a/homeassistant/components/bsblan/translations/pl.json +++ b/homeassistant/components/bsblan/translations/pl.json @@ -5,8 +5,11 @@ "data": { "host": "Nazwa hosta lub adres IP", "port": "Port" - } + }, + "description": "Konfiguracja urz\u0105dzenia BSB-LAN w celu integracji z Home Assistantem.", + "title": "Po\u0142\u0105czenie z urz\u0105dzeniem BSB-Lan" } } - } + }, + "title": "BSB-Lan" } \ No newline at end of file diff --git a/homeassistant/components/dialogflow/translations/pl.json b/homeassistant/components/dialogflow/translations/pl.json index 3b939a9f369..ee3f65ef0b1 100644 --- a/homeassistant/components/dialogflow/translations/pl.json +++ b/homeassistant/components/dialogflow/translations/pl.json @@ -5,7 +5,7 @@ "one_instance_allowed": "Wymagana jest tylko jedna instancja." }, "create_entry": { - "default": "Aby wysy\u0142a\u0107 zdarzenia do Home Assistant'a, musisz skonfigurowa\u0107 [Dialogflow Webhook]({dialogflow_url}). \n\n Wprowad\u017a nast\u0119puj\u0105ce dane:\n\n - URL: `{webhook_url}` \n - Metoda: POST \n - Typ zawarto\u015bci: application/json\n\nZapoznaj si\u0119 z [dokumentacj\u0105]({docs_url}), by pozna\u0107 szczeg\u00f3\u0142y." + "default": "Aby wysy\u0142a\u0107 zdarzenia do Home Assistanta, musisz skonfigurowa\u0107 [Dialogflow Webhook]({dialogflow_url}). \n\n Wprowad\u017a nast\u0119puj\u0105ce dane:\n\n - URL: `{webhook_url}` \n - Metoda: POST \n - Typ zawarto\u015bci: application/json\n\nZapoznaj si\u0119 z [dokumentacj\u0105]({docs_url}), by pozna\u0107 szczeg\u00f3\u0142y." }, "step": { "user": { diff --git a/homeassistant/components/elgato/translations/pl.json b/homeassistant/components/elgato/translations/pl.json index 3e3aa41a0ae..300ae515230 100644 --- a/homeassistant/components/elgato/translations/pl.json +++ b/homeassistant/components/elgato/translations/pl.json @@ -14,11 +14,11 @@ "host": "Nazwa hosta lub adres IP", "port": "Port" }, - "description": "Konfiguracja Elgato Key Light w celu integracji z Home Assistant'em.", + "description": "Konfiguracja Elgato Key Light w celu integracji z Home Assistantem.", "title": "Po\u0142\u0105cz swoje Elgato Key Light" }, "zeroconf_confirm": { - "description": "Czy chcesz doda\u0107 urz\u0105dzenie Elgato Key Light o numerze seryjnym `{serial_number}` do Home Assistant'a?", + "description": "Czy chcesz doda\u0107 urz\u0105dzenie Elgato Key Light o numerze seryjnym `{serial_number}` do Home Assistanta?", "title": "Wykryto urz\u0105dzenie Elgato Key Light" } } diff --git a/homeassistant/components/freebox/translations/pl.json b/homeassistant/components/freebox/translations/pl.json index 2cb9a840544..c465e16fcb6 100644 --- a/homeassistant/components/freebox/translations/pl.json +++ b/homeassistant/components/freebox/translations/pl.json @@ -10,7 +10,7 @@ }, "step": { "link": { - "description": "Kliknij \"Zatwierd\u017a\", a nast\u0119pnie naci\u015bnij przycisk strza\u0142ki w prawo na routerze, aby zarejestrowa\u0107 Freebox w Home Assistan'cie. \n\n ![Lokalizacja przycisku na routerze] (/static/images/config_freebox.png)", + "description": "Kliknij \"Zatwierd\u017a\", a nast\u0119pnie naci\u015bnij przycisk strza\u0142ki w prawo na routerze, aby zarejestrowa\u0107 Freebox w Home Assistancie. \n\n ![Lokalizacja przycisku na routerze] (/static/images/config_freebox.png)", "title": "Po\u0142\u0105czenie z routerem Freebox" }, "user": { diff --git a/homeassistant/components/geofency/translations/pl.json b/homeassistant/components/geofency/translations/pl.json index 9180d3483ef..698aa515b9a 100644 --- a/homeassistant/components/geofency/translations/pl.json +++ b/homeassistant/components/geofency/translations/pl.json @@ -5,7 +5,7 @@ "one_instance_allowed": "Wymagana jest tylko jedna instancja." }, "create_entry": { - "default": "Aby wysy\u0142a\u0107 zdarzenia do Home Assistant'a, musisz skonfigurowa\u0107 webhook w Geofency. \n\n Wprowad\u017a nast\u0119puj\u0105ce dane:\n\n - URL: `{webhook_url}` \n - Metoda: POST \n\nZapoznaj si\u0119 z [dokumentacj\u0105]({docs_url}), by pozna\u0107 szczeg\u00f3\u0142y." + "default": "Aby wysy\u0142a\u0107 zdarzenia do Home Assistanta, musisz skonfigurowa\u0107 webhook w Geofency. \n\n Wprowad\u017a nast\u0119puj\u0105ce dane:\n\n - URL: `{webhook_url}` \n - Metoda: POST \n\nZapoznaj si\u0119 z [dokumentacj\u0105]({docs_url}), by pozna\u0107 szczeg\u00f3\u0142y." }, "step": { "user": { diff --git a/homeassistant/components/gpslogger/translations/pl.json b/homeassistant/components/gpslogger/translations/pl.json index 76cac00025f..8b3486496a0 100644 --- a/homeassistant/components/gpslogger/translations/pl.json +++ b/homeassistant/components/gpslogger/translations/pl.json @@ -5,7 +5,7 @@ "one_instance_allowed": "Wymagana jest tylko jedna instancja." }, "create_entry": { - "default": "Aby wysy\u0142a\u0107 lokalizacje do Home Assistant'a, musisz skonfigurowa\u0107 webhook w aplikacji GPSLogger. \n\n Wprowad\u017a nast\u0119puj\u0105ce dane:\n\n - URL: `{webhook_url}` \n - Metoda: POST \n\nZapoznaj si\u0119 z [dokumentacj\u0105]({docs_url}), by pozna\u0107 szczeg\u00f3\u0142y." + "default": "Aby wysy\u0142a\u0107 lokalizacje do Home Assistanta, musisz skonfigurowa\u0107 webhook w aplikacji GPSLogger. \n\n Wprowad\u017a nast\u0119puj\u0105ce dane:\n\n - URL: `{webhook_url}` \n - Metoda: POST \n\nZapoznaj si\u0119 z [dokumentacj\u0105]({docs_url}), by pozna\u0107 szczeg\u00f3\u0142y." }, "step": { "user": { diff --git a/homeassistant/components/hangouts/translations/fi.json b/homeassistant/components/hangouts/translations/fi.json index 68bc9eb0389..959a2c06a63 100644 --- a/homeassistant/components/hangouts/translations/fi.json +++ b/homeassistant/components/hangouts/translations/fi.json @@ -5,6 +5,9 @@ }, "step": { "2fa": { + "data": { + "2fa": "2FA-pin" + }, "title": "Kaksivaiheinen tunnistus" }, "user": { diff --git a/homeassistant/components/huawei_lte/translations/pl.json b/homeassistant/components/huawei_lte/translations/pl.json index 86e6e4e5853..c36b37b5252 100644 --- a/homeassistant/components/huawei_lte/translations/pl.json +++ b/homeassistant/components/huawei_lte/translations/pl.json @@ -23,7 +23,7 @@ "url": "URL", "username": "Nazwa u\u017cytkownika" }, - "description": "Wprowad\u017a szczeg\u00f3\u0142y dost\u0119pu do urz\u0105dzenia. Okre\u015blenie nazwy u\u017cytkownika i has\u0142a jest opcjonalne, ale umo\u017cliwia obs\u0142ug\u0119 wi\u0119kszej liczby funkcji integracji. Z drugiej strony u\u017cycie autoryzowanego po\u0142\u0105czenia mo\u017ce powodowa\u0107 problemy z dost\u0119pem do interfejsu internetowego urz\u0105dzenia z zewn\u0105trz Home Assistant'a gdy integracja jest aktywna.", + "description": "Wprowad\u017a szczeg\u00f3\u0142y dost\u0119pu do urz\u0105dzenia. Okre\u015blenie nazwy u\u017cytkownika i has\u0142a jest opcjonalne, ale umo\u017cliwia obs\u0142ug\u0119 wi\u0119kszej liczby funkcji integracji. Z drugiej strony u\u017cycie autoryzowanego po\u0142\u0105czenia mo\u017ce powodowa\u0107 problemy z dost\u0119pem do interfejsu internetowego urz\u0105dzenia z zewn\u0105trz Home Assistanta gdy integracja jest aktywna.", "title": "Konfiguracja Huawei LTE" } } diff --git a/homeassistant/components/ifttt/translations/pl.json b/homeassistant/components/ifttt/translations/pl.json index 47bf2c29a16..d35b30cbf8f 100644 --- a/homeassistant/components/ifttt/translations/pl.json +++ b/homeassistant/components/ifttt/translations/pl.json @@ -5,7 +5,7 @@ "one_instance_allowed": "Wymagana jest tylko jedna instancja." }, "create_entry": { - "default": "Aby wysy\u0142a\u0107 zdarzenia do Home Assistant'a, musisz u\u017cy\u0107 akcji \"Make a web request\" z [IFTTT Webhook apletu]({applet_url}). \n\n Wprowad\u017a nast\u0119puj\u0105ce dane:\n\n - URL: `{webhook_url}`\n - Metoda: POST\n - Typ zawarto\u015bci: application/json\n\nZapoznaj si\u0119 z [dokumentacj\u0105]({docs_url}) na temat konfiguracji automatyzacji, by obs\u0142u\u017cy\u0107 przychodz\u0105ce dane." + "default": "Aby wysy\u0142a\u0107 zdarzenia do Home Assistanta, musisz u\u017cy\u0107 akcji \"Make a web request\" z [IFTTT Webhook apletu]({applet_url}). \n\n Wprowad\u017a nast\u0119puj\u0105ce dane:\n\n - URL: `{webhook_url}`\n - Metoda: POST\n - Typ zawarto\u015bci: application/json\n\nZapoznaj si\u0119 z [dokumentacj\u0105]({docs_url}) na temat konfiguracji automatyzacji, by obs\u0142u\u017cy\u0107 przychodz\u0105ce dane." }, "step": { "user": { diff --git a/homeassistant/components/ipp/translations/pl.json b/homeassistant/components/ipp/translations/pl.json index 877bdb0cd0d..4e5af33041c 100644 --- a/homeassistant/components/ipp/translations/pl.json +++ b/homeassistant/components/ipp/translations/pl.json @@ -27,7 +27,7 @@ "title": "Po\u0142\u0105cz swoj\u0105 drukark\u0119" }, "zeroconf_confirm": { - "description": "Czy chcesz doda\u0107 drukark\u0119 o nazwie `{name}` do Home Assistant'a?", + "description": "Czy chcesz doda\u0107 drukark\u0119 o nazwie `{name}` do Home Assistanta?", "title": "Wykryto drukark\u0119" } } diff --git a/homeassistant/components/konnected/translations/pl.json b/homeassistant/components/konnected/translations/pl.json index 29a2155c8fc..f4a80b60bdf 100644 --- a/homeassistant/components/konnected/translations/pl.json +++ b/homeassistant/components/konnected/translations/pl.json @@ -90,7 +90,7 @@ "data": { "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" + "override_api_host": "Zast\u0105p domy\u015blny adres URL API Home Assistanta" }, "description": "Wybierz po\u017c\u0105dane zachowanie dla swojego panelu", "title": "R\u00f3\u017cne opcje" diff --git a/homeassistant/components/locative/translations/pl.json b/homeassistant/components/locative/translations/pl.json index 7294b31ef74..1323b1e284b 100644 --- a/homeassistant/components/locative/translations/pl.json +++ b/homeassistant/components/locative/translations/pl.json @@ -5,7 +5,7 @@ "one_instance_allowed": "Wymagana jest tylko jedna instancja." }, "create_entry": { - "default": "Aby wysy\u0142a\u0107 lokalizacje do Home Assistant'a, musisz skonfigurowa\u0107 webhook w aplikacji Locative. \n\n Wprowad\u017a nast\u0119puj\u0105ce dane:\n\n - URL: `{webhook_url}` \n - Metoda: POST \n\nZapoznaj si\u0119 z [dokumentacj\u0105]({docs_url}), by pozna\u0107 szczeg\u00f3\u0142y." + "default": "Aby wysy\u0142a\u0107 lokalizacje do Home Assistanta, musisz skonfigurowa\u0107 webhook w aplikacji Locative. \n\n Wprowad\u017a nast\u0119puj\u0105ce dane:\n\n - URL: `{webhook_url}` \n - Metoda: POST \n\nZapoznaj si\u0119 z [dokumentacj\u0105]({docs_url}), by pozna\u0107 szczeg\u00f3\u0142y." }, "step": { "user": { diff --git a/homeassistant/components/mailgun/translations/pl.json b/homeassistant/components/mailgun/translations/pl.json index e24f2d7ec8a..5a6b92505b7 100644 --- a/homeassistant/components/mailgun/translations/pl.json +++ b/homeassistant/components/mailgun/translations/pl.json @@ -5,7 +5,7 @@ "one_instance_allowed": "Wymagana jest tylko jedna instancja." }, "create_entry": { - "default": "Aby wysy\u0142a\u0107 zdarzenia do Home Assistant'a, musisz skonfigurowa\u0107 [Mailgun Webhook]({mailgun_url}). \n\n Wprowad\u017a nast\u0119puj\u0105ce dane:\n\n - URL: `{webhook_url}` \n - Metoda: POST \n - Typ zawarto\u015bci: application/json\n\nZapoznaj si\u0119 z [dokumentacj\u0105]({docs_url}) na temat konfiguracji automatyzacji, by obs\u0142u\u017cy\u0107 przychodz\u0105ce dane." + "default": "Aby wysy\u0142a\u0107 zdarzenia do Home Assistanta, musisz skonfigurowa\u0107 [Mailgun Webhook]({mailgun_url}). \n\n Wprowad\u017a nast\u0119puj\u0105ce dane:\n\n - URL: `{webhook_url}` \n - Metoda: POST \n - Typ zawarto\u015bci: application/json\n\nZapoznaj si\u0119 z [dokumentacj\u0105]({docs_url}) na temat konfiguracji automatyzacji, by obs\u0142u\u017cy\u0107 przychodz\u0105ce dane." }, "step": { "user": { diff --git a/homeassistant/components/mqtt/translations/pl.json b/homeassistant/components/mqtt/translations/pl.json index 73a29caced6..bc847771908 100644 --- a/homeassistant/components/mqtt/translations/pl.json +++ b/homeassistant/components/mqtt/translations/pl.json @@ -22,7 +22,7 @@ "data": { "discovery": "W\u0142\u0105cz wykrywanie" }, - "description": "Czy chcesz skonfigurowa\u0107 Home Assistant'a, aby po\u0142\u0105czy\u0142 si\u0119 z po\u015brednikiem MQTT dostarczonym przez dodatek Hass.io {addon}?", + "description": "Czy chcesz skonfigurowa\u0107 Home Assistanta, aby po\u0142\u0105czy\u0142 si\u0119 z po\u015brednikiem MQTT dostarczonym przez dodatek Hass.io {addon}?", "title": "Po\u015brednik MQTT za po\u015brednictwem dodatku Hass.io" } } diff --git a/homeassistant/components/nest/translations/fi.json b/homeassistant/components/nest/translations/fi.json index 561b03f9286..d810bd0657e 100644 --- a/homeassistant/components/nest/translations/fi.json +++ b/homeassistant/components/nest/translations/fi.json @@ -11,7 +11,8 @@ "init": { "data": { "flow_impl": "Tarjoaja" - } + }, + "title": "Todentamisen tarjoaja" }, "link": { "data": { diff --git a/homeassistant/components/openuv/translations/fi.json b/homeassistant/components/openuv/translations/fi.json index 65b313a059b..4b023bff905 100644 --- a/homeassistant/components/openuv/translations/fi.json +++ b/homeassistant/components/openuv/translations/fi.json @@ -11,7 +11,8 @@ "elevation": "Korkeus merenpinnasta", "latitude": "Leveysaste", "longitude": "Pituusaste" - } + }, + "title": "T\u00e4yt\u00e4 tietosi" } } } diff --git a/homeassistant/components/plaato/translations/pl.json b/homeassistant/components/plaato/translations/pl.json index 4edc54e357e..6db0a100e4a 100644 --- a/homeassistant/components/plaato/translations/pl.json +++ b/homeassistant/components/plaato/translations/pl.json @@ -5,7 +5,7 @@ "one_instance_allowed": "Wymagana jest tylko jedna instancja." }, "create_entry": { - "default": "Aby wysy\u0142a\u0107 zdarzenia do Home Assistant'a, musisz skonfigurowa\u0107 webhook w Plaato Airlock. \n\n Wprowad\u017a nast\u0119puj\u0105ce dane:\n\n - URL: `{webhook_url}` \n - Metoda: POST \n\nZapoznaj si\u0119 z [dokumentacj\u0105]({docs_url}), by pozna\u0107 szczeg\u00f3\u0142y." + "default": "Aby wysy\u0142a\u0107 zdarzenia do Home Assistanta, musisz skonfigurowa\u0107 webhook w Plaato Airlock. \n\n Wprowad\u017a nast\u0119puj\u0105ce dane:\n\n - URL: `{webhook_url}` \n - Metoda: POST \n\nZapoznaj si\u0119 z [dokumentacj\u0105]({docs_url}), by pozna\u0107 szczeg\u00f3\u0142y." }, "step": { "user": { diff --git a/homeassistant/components/samsungtv/translations/pl.json b/homeassistant/components/samsungtv/translations/pl.json index 1124b5335a8..c82a7c96e66 100644 --- a/homeassistant/components/samsungtv/translations/pl.json +++ b/homeassistant/components/samsungtv/translations/pl.json @@ -10,7 +10,7 @@ "flow_title": "Samsung TV: {model}", "step": { "confirm": { - "description": "Czy chcesz skonfigurowa\u0107 telewizor Samsung {model}? Je\u015bli nigdy wcze\u015bniej ten telewizor nie by\u0142 \u0142\u0105czony z Home Assistant'em na jego ekranie powinna pojawi\u0107 si\u0119 pro\u015bba o uwierzytelnienie. R\u0119czne konfiguracje tego telewizora zostan\u0105 zast\u0105pione.", + "description": "Czy chcesz skonfigurowa\u0107 telewizor Samsung {model}? Je\u015bli nigdy wcze\u015bniej ten telewizor nie by\u0142 \u0142\u0105czony z Home Assistantem, na jego ekranie powinna pojawi\u0107 si\u0119 pro\u015bba o uwierzytelnienie. R\u0119czne konfiguracje tego telewizora zostan\u0105 zast\u0105pione.", "title": "Samsung TV" }, "user": { @@ -18,7 +18,7 @@ "host": "Nazwa hosta lub adres IP", "name": "Nazwa" }, - "description": "Wprowad\u017a informacje o telewizorze Samsung. Je\u015bli nigdy wcze\u015bniej ten telewizor nie by\u0142 \u0142\u0105czony z Home Assistant'em na jego ekranie powinna pojawi\u0107 si\u0119 pro\u015bba o uwierzytelnienie.", + "description": "Wprowad\u017a informacje o telewizorze Samsung. Je\u015bli nigdy wcze\u015bniej ten telewizor nie by\u0142 \u0142\u0105czony z Home Assistantem.", "title": "Samsung TV" } } diff --git a/homeassistant/components/simplisafe/translations/pl.json b/homeassistant/components/simplisafe/translations/pl.json index 4b7b3c8b2f1..0562222eea3 100644 --- a/homeassistant/components/simplisafe/translations/pl.json +++ b/homeassistant/components/simplisafe/translations/pl.json @@ -10,7 +10,7 @@ "step": { "user": { "data": { - "code": "Kod (u\u017cywany w interfejsie Home Assistant'a)", + "code": "Kod (u\u017cywany w interfejsie Home Assistanta)", "password": "Has\u0142o", "username": "Adres e-mail" }, @@ -22,7 +22,7 @@ "step": { "init": { "data": { - "code": "Kod (u\u017cywany w interfejsie u\u017cytkownika Home Assistant'a)" + "code": "Kod (u\u017cywany w interfejsie u\u017cytkownika Home Assistanta)" }, "title": "Konfiguracja SimpliSafe" } diff --git a/homeassistant/components/smartthings/translations/pl.json b/homeassistant/components/smartthings/translations/pl.json index 7738d79c3e8..f43a6f267b3 100644 --- a/homeassistant/components/smartthings/translations/pl.json +++ b/homeassistant/components/smartthings/translations/pl.json @@ -13,7 +13,7 @@ }, "step": { "authorize": { - "title": "Autoryzuj Home Assistant'a" + "title": "Autoryzuj Home Assistanta" }, "pat": { "data": { @@ -26,7 +26,7 @@ "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.", + "description": "Wybierz lokalizacj\u0119 SmartThings, kt\u00f3r\u0105 chcesz doda\u0107 do Home Assistanta. 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/traccar/translations/pl.json b/homeassistant/components/traccar/translations/pl.json index 5b1ad10c74d..fecfc94c36c 100644 --- a/homeassistant/components/traccar/translations/pl.json +++ b/homeassistant/components/traccar/translations/pl.json @@ -5,7 +5,7 @@ "one_instance_allowed": "Niezb\u0119dna jest tylko jedna instancja." }, "create_entry": { - "default": "Aby wysy\u0142a\u0107 zdarzenia do Home Assistant'a, musisz skonfigurowa\u0107 webhook w Traccar. \n\n U\u017cyj nast\u0119puj\u0105cego URL: `{webhook_url}` \n\nZapoznaj si\u0119 z [dokumentacj\u0105]({docs_url}), by pozna\u0107 szczeg\u00f3\u0142y." + "default": "Aby wysy\u0142a\u0107 zdarzenia do Home Assistanta, musisz skonfigurowa\u0107 webhook w Traccar. \n\n U\u017cyj nast\u0119puj\u0105cego URL: `{webhook_url}` \n\nZapoznaj si\u0119 z [dokumentacj\u0105]({docs_url}), by pozna\u0107 szczeg\u00f3\u0142y." }, "step": { "user": { diff --git a/homeassistant/components/twilio/translations/pl.json b/homeassistant/components/twilio/translations/pl.json index 8d2f02ade2a..8c7a4da4cdd 100644 --- a/homeassistant/components/twilio/translations/pl.json +++ b/homeassistant/components/twilio/translations/pl.json @@ -5,7 +5,7 @@ "one_instance_allowed": "Wymagana jest tylko jedna instancja." }, "create_entry": { - "default": "Aby wysy\u0142a\u0107 zdarzenia do Home Assistant'a, musisz skonfigurowa\u0107 [Twilio Webhook]({twilio_url}). \n\n Wprowad\u017a nast\u0119puj\u0105ce dane:\n\n - URL: `{webhook_url}` \n - Metoda: POST \n - Typ zawarto\u015bci: application/x-www-form-urlencoded \n\nZapoznaj si\u0119 z [dokumentacj\u0105]({docs_url}) na temat konfiguracji automatyzacji, by obs\u0142u\u017cy\u0107 przychodz\u0105ce dane." + "default": "Aby wysy\u0142a\u0107 zdarzenia do Home Assistanta, musisz skonfigurowa\u0107 [Twilio Webhook]({twilio_url}). \n\n Wprowad\u017a nast\u0119puj\u0105ce dane:\n\n - URL: `{webhook_url}` \n - Metoda: POST \n - Typ zawarto\u015bci: application/x-www-form-urlencoded \n\nZapoznaj si\u0119 z [dokumentacj\u0105]({docs_url}) na temat konfiguracji automatyzacji, by obs\u0142u\u017cy\u0107 przychodz\u0105ce dane." }, "step": { "user": { diff --git a/homeassistant/components/upnp/translations/pl.json b/homeassistant/components/upnp/translations/pl.json index f30dab02a4b..63af10a93c7 100644 --- a/homeassistant/components/upnp/translations/pl.json +++ b/homeassistant/components/upnp/translations/pl.json @@ -20,7 +20,7 @@ }, "user": { "data": { - "enable_port_mapping": "W\u0142\u0105cz mapowanie port\u00f3w dla Home Assistant'a", + "enable_port_mapping": "W\u0142\u0105cz mapowanie port\u00f3w dla Home Assistanta", "enable_sensors": "Dodaj sensor ruchu sieciowego", "igd": "UPnP/IGD", "scan_interval": "Cz\u0119stotliwo\u015b\u0107 aktualizacji (sekundy, minimum 30)", diff --git a/homeassistant/components/vizio/translations/pl.json b/homeassistant/components/vizio/translations/pl.json index 6b8d1202299..7e150f234ce 100644 --- a/homeassistant/components/vizio/translations/pl.json +++ b/homeassistant/components/vizio/translations/pl.json @@ -7,6 +7,7 @@ "error": { "cant_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia.", "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.", + "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." }, @@ -19,11 +20,11 @@ "title": "Ko\u0144czenie procesu parowania" }, "pairing_complete": { - "description": "Twoje urz\u0105dzenie VIZIO SmartCast jest teraz po\u0142\u0105czone z Home Assistant'em.", + "description": "Twoje urz\u0105dzenie VIZIO SmartCast jest teraz po\u0142\u0105czone z Home Assistantem.", "title": "Parowanie zako\u0144czone" }, "pairing_complete_import": { - "description": "Twoje urz\u0105dzenie VIZIO SmartCast jest teraz po\u0142\u0105czone z Home Assistant'em.\n\nToken dost\u0119pu to '**{access_token}**'.", + "description": "Twoje urz\u0105dzenie VIZIO SmartCast jest teraz po\u0142\u0105czone z Home Assistantem.\n\nToken dost\u0119pu to '**{access_token}**'.", "title": "Parowanie zako\u0144czone" }, "user": { diff --git a/homeassistant/components/wled/translations/pl.json b/homeassistant/components/wled/translations/pl.json index c5551b0927d..8a912fb6a19 100644 --- a/homeassistant/components/wled/translations/pl.json +++ b/homeassistant/components/wled/translations/pl.json @@ -13,11 +13,11 @@ "data": { "host": "Nazwa hosta lub adres IP" }, - "description": "Konfiguracja WLED w celu integracji z Home Assistant'em.", + "description": "Konfiguracja WLED w celu integracji z Home Assistantem.", "title": "Po\u0142\u0105czenie z WLED" }, "zeroconf_confirm": { - "description": "Czy chcesz doda\u0107 WLED o nazwie `{name}` do Home Assistant'a?", + "description": "Czy chcesz doda\u0107 WLED o nazwie `{name}` do Home Assistanta?", "title": "Wykryto urz\u0105dzenie WLED" } } From 80de233276406eb8c97967c2da89e8eafc823abc Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 22 May 2020 19:52:33 -0500 Subject: [PATCH 137/406] Ensure homekit functions if numpy is unavailable (#35931) --- homeassistant/components/homekit/img_util.py | 8 ++++++-- tests/components/homekit/test_img_util.py | 18 +++++------------- tests/components/homekit/test_type_cameras.py | 4 +--- 3 files changed, 12 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/homekit/img_util.py b/homeassistant/components/homekit/img_util.py index 835b04558e6..88217bf776d 100644 --- a/homeassistant/components/homekit/img_util.py +++ b/homeassistant/components/homekit/img_util.py @@ -2,8 +2,6 @@ import logging -from turbojpeg import TurboJPEG - SUPPORTED_SCALING_FACTORS = [(7, 8), (3, 4), (5, 8), (1, 2), (3, 8), (1, 4), (1, 8)] _LOGGER = logging.getLogger(__name__) @@ -54,6 +52,12 @@ class TurboJPEGSingleton: def __init__(self): """Try to create TurboJPEG only once.""" try: + # TurboJPEG checks for libturbojpeg + # when its created, but it imports + # numpy which may or may not work so + # we have to guard the import here. + from turbojpeg import TurboJPEG # pylint: disable=import-outside-toplevel + TurboJPEGSingleton.__instance = TurboJPEG() except Exception: # pylint: disable=broad-except _LOGGER.exception( diff --git a/tests/components/homekit/test_img_util.py b/tests/components/homekit/test_img_util.py index 4ada89b3acd..728bb8847ff 100644 --- a/tests/components/homekit/test_img_util.py +++ b/tests/components/homekit/test_img_util.py @@ -23,25 +23,19 @@ def test_scale_jpeg_camera_image(): camera_image = Image("image/jpeg", EMPTY_16_12_JPEG) turbo_jpeg = mock_turbo_jpeg(first_width=16, first_height=12) - with patch( - "homeassistant.components.homekit.img_util.TurboJPEG", return_value=False - ): + with patch("turbojpeg.TurboJPEG", return_value=False): TurboJPEGSingleton() assert scale_jpeg_camera_image(camera_image, 16, 12) == camera_image.content turbo_jpeg = mock_turbo_jpeg(first_width=16, first_height=12) - with patch( - "homeassistant.components.homekit.img_util.TurboJPEG", return_value=turbo_jpeg - ): + with patch("turbojpeg.TurboJPEG", return_value=turbo_jpeg): TurboJPEGSingleton() assert scale_jpeg_camera_image(camera_image, 16, 12) == EMPTY_16_12_JPEG turbo_jpeg = mock_turbo_jpeg( first_width=16, first_height=12, second_width=8, second_height=6 ) - with patch( - "homeassistant.components.homekit.img_util.TurboJPEG", return_value=turbo_jpeg - ): + with patch("turbojpeg.TurboJPEG", return_value=turbo_jpeg): TurboJPEGSingleton() jpeg_bytes = scale_jpeg_camera_image(camera_image, 8, 6) @@ -51,12 +45,10 @@ def test_scale_jpeg_camera_image(): def test_turbojpeg_load_failure(): """Handle libjpegturbo not being installed.""" - with patch( - "homeassistant.components.homekit.img_util.TurboJPEG", side_effect=Exception - ): + with patch("turbojpeg.TurboJPEG", side_effect=Exception): TurboJPEGSingleton() assert TurboJPEGSingleton.instance() is False - with patch("homeassistant.components.homekit.img_util.TurboJPEG"): + with patch("turbojpeg.TurboJPEG"): TurboJPEGSingleton() assert TurboJPEGSingleton.instance() diff --git a/tests/components/homekit/test_type_cameras.py b/tests/components/homekit/test_type_cameras.py index e3444ca23e4..0c002fa7213 100644 --- a/tests/components/homekit/test_type_cameras.py +++ b/tests/components/homekit/test_type_cameras.py @@ -193,9 +193,7 @@ async def test_camera_stream_source_configured(hass, run_driver, events): turbo_jpeg = mock_turbo_jpeg( first_width=16, first_height=12, second_width=300, second_height=200 ) - with patch( - "homeassistant.components.homekit.img_util.TurboJPEG", return_value=turbo_jpeg - ): + with patch("turbojpeg.TurboJPEG", return_value=turbo_jpeg): TurboJPEGSingleton() assert await hass.async_add_executor_job( acc.get_snapshot, {"aid": 2, "image-width": 300, "image-height": 200} From 0514960bda07b9d503efaf3d0482ece66b849342 Mon Sep 17 00:00:00 2001 From: Jason Hunter Date: Fri, 22 May 2020 21:11:30 -0400 Subject: [PATCH 138/406] Fix ONVIF PTZ and profile encoding issues (#36006) * allow lib to create AsyncTransport * fix transport close issue * fix zoom only cameras without PTZ presets * catch profiles without encoding configuration * also catch ServerDisconnectedError for ptz --- homeassistant/components/onvif/config_flow.py | 3 ++- homeassistant/components/onvif/device.py | 15 +++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/onvif/config_flow.py b/homeassistant/components/onvif/config_flow.py index ceb861fc7dd..1dba697380d 100644 --- a/homeassistant/components/onvif/config_flow.py +++ b/homeassistant/components/onvif/config_flow.py @@ -219,7 +219,8 @@ class OnvifFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): media_service = device.create_media_service() profiles = await media_service.GetProfiles() h264 = any( - profile.VideoEncoderConfiguration.Encoding == "H264" + profile.VideoEncoderConfiguration + and profile.VideoEncoderConfiguration.Encoding == "H264" for profile in profiles ) diff --git a/homeassistant/components/onvif/device.py b/homeassistant/components/onvif/device.py index d6f407f016d..938c960080f 100644 --- a/homeassistant/components/onvif/device.py +++ b/homeassistant/components/onvif/device.py @@ -255,7 +255,10 @@ class ONVIFDevice: profiles = [] for key, onvif_profile in enumerate(result): # Only add H264 profiles - if onvif_profile.VideoEncoderConfiguration.Encoding != "H264": + if ( + not onvif_profile.VideoEncoderConfiguration + or onvif_profile.VideoEncoderConfiguration.Encoding != "H264" + ): continue profile = Profile( @@ -282,9 +285,13 @@ class ONVIFDevice: is not None, ) - ptz_service = self.device.create_ptz_service() - presets = await ptz_service.GetPresets(profile.token) - profile.ptz.presets = [preset.token for preset in presets] + try: + ptz_service = self.device.create_ptz_service() + presets = await ptz_service.GetPresets(profile.token) + profile.ptz.presets = [preset.token for preset in presets] + except (Fault, ServerDisconnectedError): + # It's OK if Presets aren't supported + profile.ptz.presets = [] profiles.append(profile) From 8b8aa198faeff3060d13f7d851b8b920557ec48e Mon Sep 17 00:00:00 2001 From: Florent Thoumie Date: Fri, 22 May 2020 23:53:10 -0700 Subject: [PATCH 139/406] Fix iaqualink sensors (#36000) * iaqualink: small sensor fixes * Re-add device_class, fix type hints. --- homeassistant/components/iaqualink/sensor.py | 21 ++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/iaqualink/sensor.py b/homeassistant/components/iaqualink/sensor.py index 81021d0b447..80f18dc8191 100644 --- a/homeassistant/components/iaqualink/sensor.py +++ b/homeassistant/components/iaqualink/sensor.py @@ -34,16 +34,25 @@ class HassAqualinkSensor(AqualinkEntity): return self.dev.label @property - def unit_of_measurement(self) -> str: + def unit_of_measurement(self) -> Optional[str]: """Return the measurement unit for the sensor.""" - if self.dev.system.temp_unit == "F": - return TEMP_FAHRENHEIT - return TEMP_CELSIUS + if self.dev.name.endswith("_temp"): + if self.dev.system.temp_unit == "F": + return TEMP_FAHRENHEIT + return TEMP_CELSIUS + return None @property - def state(self) -> str: + def state(self) -> Optional[str]: """Return the state of the sensor.""" - return int(self.dev.state) if self.dev.state != "" else None + if self.dev.state == "": + return None + + try: + state = int(self.dev.state) + except ValueError: + state = float(self.dev.state) + return state @property def device_class(self) -> Optional[str]: From f1b91b050cf8698b2297024e0b465e667ac53f14 Mon Sep 17 00:00:00 2001 From: Florent Thoumie Date: Fri, 22 May 2020 23:55:53 -0700 Subject: [PATCH 140/406] Bump iaqualink to 0.3.3 (#35999) --- homeassistant/components/iaqualink/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/iaqualink/manifest.json b/homeassistant/components/iaqualink/manifest.json index d9d16038d19..68b4554f73a 100644 --- a/homeassistant/components/iaqualink/manifest.json +++ b/homeassistant/components/iaqualink/manifest.json @@ -4,5 +4,5 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/iaqualink/", "codeowners": ["@flz"], - "requirements": ["iaqualink==0.3.1"] + "requirements": ["iaqualink==0.3.3"] } diff --git a/requirements_all.txt b/requirements_all.txt index 580b9efa6e1..4466a8a543d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -767,7 +767,7 @@ hydrawiser==0.1.1 iammeter==0.1.7 # homeassistant.components.iaqualink -iaqualink==0.3.1 +iaqualink==0.3.3 # homeassistant.components.watson_tts ibm-watson==4.0.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 4e50ba04bf4..d0f8ae47b04 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -337,7 +337,7 @@ httplib2==0.10.3 huawei-lte-api==1.4.12 # homeassistant.components.iaqualink -iaqualink==0.3.1 +iaqualink==0.3.3 # homeassistant.components.influxdb influxdb==5.2.3 From 919f3243de8fe4c8ec7085c56f6a06f2f811e8d3 Mon Sep 17 00:00:00 2001 From: Andre Lengwenus Date: Sat, 23 May 2020 09:46:03 +0200 Subject: [PATCH 141/406] Fix device_registry cleanup behavior (#35977) * Fix: Only decives which are not referenced by an entity or a config_entry are removed * Adapted test for async_cleanup * Changed variable names --- homeassistant/helpers/device_registry.py | 18 +++++++++++++----- tests/helpers/test_device_registry.py | 2 +- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/homeassistant/helpers/device_registry.py b/homeassistant/helpers/device_registry.py index 8fbb81962ff..2e91d0d6622 100644 --- a/homeassistant/helpers/device_registry.py +++ b/homeassistant/helpers/device_registry.py @@ -399,17 +399,25 @@ def async_cleanup( ent_reg: "entity_registry.EntityRegistry", ) -> None: """Clean up device registry.""" - # Find all devices that are no longer referenced in the entity registry. - referenced = {entry.device_id for entry in ent_reg.entities.values()} - orphan = set(dev_reg.devices) - referenced + # Find all devices that are referenced by a config_entry. + config_entry_ids = {entry.entry_id for entry in hass.config_entries.async_entries()} + references_config_entries = { + device.id + for device in dev_reg.devices.values() + for config_entry_id in device.config_entries + if config_entry_id in config_entry_ids + } + + # Find all devices that are referenced in the entity registry. + references_entities = {entry.device_id for entry in ent_reg.entities.values()} + + orphan = set(dev_reg.devices) - references_entities - references_config_entries for dev_id in orphan: dev_reg.async_remove_device(dev_id) # Find all referenced config entries that no longer exist # This shouldn't happen but have not been able to track down the bug :( - config_entry_ids = {entry.entry_id for entry in hass.config_entries.async_entries()} - for device in list(dev_reg.devices.values()): for config_entry_id in device.config_entries: if config_entry_id not in config_entry_ids: diff --git a/tests/helpers/test_device_registry.py b/tests/helpers/test_device_registry.py index ef5f92de79c..3fbb73a2aa8 100644 --- a/tests/helpers/test_device_registry.py +++ b/tests/helpers/test_device_registry.py @@ -539,7 +539,7 @@ async def test_cleanup_device_registry(hass, registry): device_registry.async_cleanup(hass, registry, ent_reg) assert registry.async_get_device({("hue", "d1")}, set()) is not None - assert registry.async_get_device({("hue", "d2")}, set()) is None + assert registry.async_get_device({("hue", "d2")}, set()) is not None assert registry.async_get_device({("hue", "d3")}, set()) is not None assert registry.async_get_device({("something", "d4")}, set()) is None From 765bf760b4a788e38e1327bef6cf55fa8d4a5445 Mon Sep 17 00:00:00 2001 From: Chris Talkington Date: Sat, 23 May 2020 04:06:48 -0500 Subject: [PATCH 142/406] Fix roku play/pause support (#35991) --- homeassistant/components/roku/media_player.py | 10 ++++++++ tests/components/roku/test_media_player.py | 25 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/homeassistant/components/roku/media_player.py b/homeassistant/components/roku/media_player.py index 7b64888bbd1..8c92eff3687 100644 --- a/homeassistant/components/roku/media_player.py +++ b/homeassistant/components/roku/media_player.py @@ -7,6 +7,7 @@ from homeassistant.components.media_player.const import ( MEDIA_TYPE_APP, MEDIA_TYPE_CHANNEL, SUPPORT_NEXT_TRACK, + SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, @@ -29,6 +30,7 @@ SUPPORT_ROKU = ( | SUPPORT_VOLUME_STEP | SUPPORT_VOLUME_MUTE | SUPPORT_SELECT_SOURCE + | SUPPORT_PAUSE | SUPPORT_PLAY | SUPPORT_PLAY_MEDIA | SUPPORT_TURN_ON @@ -167,6 +169,14 @@ class RokuMediaPlayer(RokuEntity, MediaPlayerEntity): """Turn off the Roku.""" await self.coordinator.roku.remote("poweroff") + async def async_media_pause(self) -> None: + """Send pause command.""" + await self.coordinator.roku.remote("play") + + async def async_media_play(self) -> None: + """Send play command.""" + await self.coordinator.roku.remote("play") + async def async_media_play_pause(self) -> None: """Send play/pause command.""" await self.coordinator.roku.remote("play") diff --git a/tests/components/roku/test_media_player.py b/tests/components/roku/test_media_player.py index f91a8b286b3..9d809cae433 100644 --- a/tests/components/roku/test_media_player.py +++ b/tests/components/roku/test_media_player.py @@ -18,6 +18,7 @@ from homeassistant.components.media_player.const import ( SERVICE_PLAY_MEDIA, SERVICE_SELECT_SOURCE, SUPPORT_NEXT_TRACK, + SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, @@ -30,6 +31,8 @@ from homeassistant.components.media_player.const import ( from homeassistant.const import ( ATTR_ENTITY_ID, SERVICE_MEDIA_NEXT_TRACK, + SERVICE_MEDIA_PAUSE, + SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PLAY_PAUSE, SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_TURN_OFF, @@ -142,6 +145,7 @@ async def test_supported_features( | SUPPORT_VOLUME_STEP | SUPPORT_VOLUME_MUTE | SUPPORT_SELECT_SOURCE + | SUPPORT_PAUSE | SUPPORT_PLAY | SUPPORT_PLAY_MEDIA | SUPPORT_TURN_ON @@ -170,6 +174,7 @@ async def test_tv_supported_features( | SUPPORT_VOLUME_STEP | SUPPORT_VOLUME_MUTE | SUPPORT_SELECT_SOURCE + | SUPPORT_PAUSE | SUPPORT_PLAY | SUPPORT_PLAY_MEDIA | SUPPORT_TURN_ON @@ -267,6 +272,26 @@ async def test_services( remote_mock.assert_called_once_with("poweron") + with patch("homeassistant.components.roku.Roku.remote") as remote_mock: + await hass.services.async_call( + MP_DOMAIN, + SERVICE_MEDIA_PAUSE, + {ATTR_ENTITY_ID: MAIN_ENTITY_ID}, + blocking=True, + ) + + remote_mock.assert_called_once_with("play") + + with patch("homeassistant.components.roku.Roku.remote") as remote_mock: + await hass.services.async_call( + MP_DOMAIN, + SERVICE_MEDIA_PLAY, + {ATTR_ENTITY_ID: MAIN_ENTITY_ID}, + blocking=True, + ) + + remote_mock.assert_called_once_with("play") + with patch("homeassistant.components.roku.Roku.remote") as remote_mock: await hass.services.async_call( MP_DOMAIN, From 275c8b6982e952aa24855599f0732c7a4a84b9db Mon Sep 17 00:00:00 2001 From: Anders Melchiorsen Date: Sat, 23 May 2020 11:08:01 +0200 Subject: [PATCH 143/406] Update sonos codeowners (#36016) --- CODEOWNERS | 1 - homeassistant/components/sonos/manifest.json | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index fcc8edf7710..2d7805d676a 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -372,7 +372,6 @@ homeassistant/components/soma/* @ratsept homeassistant/components/somfy/* @tetienne homeassistant/components/sonarr/* @ctalkington homeassistant/components/songpal/* @rytilahti @shenxn -homeassistant/components/sonos/* @amelchio homeassistant/components/spaceapi/* @fabaff homeassistant/components/speedtestdotnet/* @rohankapoorcom homeassistant/components/spider/* @peternijssen diff --git a/homeassistant/components/sonos/manifest.json b/homeassistant/components/sonos/manifest.json index e5ce9ede290..020bfbade56 100644 --- a/homeassistant/components/sonos/manifest.json +++ b/homeassistant/components/sonos/manifest.json @@ -9,5 +9,5 @@ "st": "urn:schemas-upnp-org:device:ZonePlayer:1" } ], - "codeowners": ["@amelchio"] + "codeowners": [] } From 88ebdf3a534375632bb6e6fd2ad03336326feccf Mon Sep 17 00:00:00 2001 From: Fredrik Erlandsson Date: Sat, 23 May 2020 11:08:49 +0200 Subject: [PATCH 144/406] Deprecate Daikin yaml-support (#35768) --- homeassistant/components/daikin/__init__.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/daikin/__init__.py b/homeassistant/components/daikin/__init__.py index 06735d7e3b8..c51b30054df 100644 --- a/homeassistant/components/daikin/__init__.py +++ b/homeassistant/components/daikin/__init__.py @@ -28,11 +28,18 @@ MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60) COMPONENT_TYPES = ["climate", "sensor", "switch"] CONFIG_SCHEMA = vol.Schema( - { - DOMAIN: vol.Schema( - {vol.Optional(CONF_HOSTS, default=[]): vol.All(cv.ensure_list, [cv.string])} - ) - }, + vol.All( + cv.deprecated(DOMAIN, invalidation_version="0.113.0"), + { + DOMAIN: vol.Schema( + { + vol.Optional(CONF_HOSTS, default=[]): vol.All( + cv.ensure_list, [cv.string] + ) + } + ) + }, + ), extra=vol.ALLOW_EXTRA, ) From 3d253fa52ad4a9a953984b6b3ca68a6e7ddcee31 Mon Sep 17 00:00:00 2001 From: Felipe Martins Diel <41558831+felipediel@users.noreply.github.com> Date: Sat, 23 May 2020 06:10:06 -0300 Subject: [PATCH 145/406] Handle StorageError in the Broadlink integration (#35986) --- homeassistant/components/broadlink/__init__.py | 7 ++++--- homeassistant/components/broadlink/remote.py | 6 ++++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/broadlink/__init__.py b/homeassistant/components/broadlink/__init__.py index a729ac0ff4a..d8b7f60b5b4 100644 --- a/homeassistant/components/broadlink/__init__.py +++ b/homeassistant/components/broadlink/__init__.py @@ -6,7 +6,7 @@ from datetime import timedelta import logging import re -from broadlink.exceptions import BroadlinkException, ReadError +from broadlink.exceptions import BroadlinkException, ReadError, StorageError import voluptuous as vol from homeassistant.const import CONF_HOST @@ -85,10 +85,11 @@ async def async_setup_service(hass, host, device): _LOGGER.info("Press the key you want Home Assistant to learn") start_time = utcnow() while (utcnow() - start_time) < timedelta(seconds=20): + await asyncio.sleep(1) try: packet = await device.async_request(device.api.check_data) - except ReadError: - await asyncio.sleep(1) + except (ReadError, StorageError): + continue except BroadlinkException as err_msg: _LOGGER.error("Failed to learn: %s", err_msg) return diff --git a/homeassistant/components/broadlink/remote.py b/homeassistant/components/broadlink/remote.py index b03bf7a4a04..03ecb9b7634 100644 --- a/homeassistant/components/broadlink/remote.py +++ b/homeassistant/components/broadlink/remote.py @@ -14,6 +14,7 @@ from broadlink.exceptions import ( BroadlinkException, DeviceOfflineError, ReadError, + StorageError, ) import voluptuous as vol @@ -321,10 +322,11 @@ class BroadlinkRemote(RemoteEntity): code = None start_time = utcnow() while (utcnow() - start_time) < timedelta(seconds=timeout): + await asyncio.sleep(1) try: code = await self.device.async_request(self.device.api.check_data) - except ReadError: - await asyncio.sleep(1) + except (ReadError, StorageError): + continue else: break From 04cfd36d06ce4becf8567ca27ea589f0550037f5 Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Sat, 23 May 2020 06:22:36 -0400 Subject: [PATCH 146/406] Fix ZHA climate hvac_action for Centralite thermostat (#35993) * Centralite specific control seq of operation * Remove Fan safeguards * Split hvac_action property. * Refactor hvac_action property. Current hvac_action logic is Zen Within thermostat specific and differs a bit from ZCL specs. Implement it as a separate class. * Refactor hvac_action property for default thermostat Follow more closely ZCL specs in parsing hvac state of the thermostat. --- homeassistant/components/zha/climate.py | 87 ++++++++++++++----- tests/components/zha/test_climate.py | 101 +++++++++++++++++------ tests/components/zha/zha_devices_list.py | 2 +- 3 files changed, 144 insertions(+), 46 deletions(-) diff --git a/homeassistant/components/zha/climate.py b/homeassistant/components/zha/climate.py index 20b52baf72e..3a0ff6455d2 100644 --- a/homeassistant/components/zha/climate.py +++ b/homeassistant/components/zha/climate.py @@ -103,6 +103,8 @@ SEQ_OF_OPERATION = { 0x04: (HVAC_MODE_OFF, HVAC_MODE_HEAT_COOL, HVAC_MODE_COOL, HVAC_MODE_HEAT), # cooling and heating 4-pipes 0x05: (HVAC_MODE_OFF, HVAC_MODE_HEAT_COOL, HVAC_MODE_COOL, HVAC_MODE_HEAT), + 0x06: (HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_OFF), # centralite specific + 0x07: (HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF), # centralite specific } @@ -234,24 +236,39 @@ class Thermostat(ZhaEntity, ClimateEntity): self._thrm.pi_heating_demand is None and self._thrm.pi_cooling_demand is None ): - running_state = self._thrm.running_state - if running_state is None: - return None - if running_state & (RunningState.HEAT | RunningState.HEAT_STAGE_2): - return CURRENT_HVAC_HEAT - if running_state & (RunningState.COOL | RunningState.COOL_STAGE_2): - return CURRENT_HVAC_COOL - if running_state & ( - RunningState.FAN | RunningState.FAN_STAGE_2 | RunningState.FAN_STAGE_3 - ): - return CURRENT_HVAC_FAN - else: - heating_demand = self._thrm.pi_heating_demand - if heating_demand is not None and heating_demand > 0: - return CURRENT_HVAC_HEAT - cooling_demand = self._thrm.pi_cooling_demand - if cooling_demand is not None and cooling_demand > 0: - return CURRENT_HVAC_COOL + return self._rm_rs_action + return self._pi_demand_action + + @property + def _rm_rs_action(self) -> Optional[str]: + """Return the current HVAC action based on running mode and running state.""" + + running_mode = self._thrm.running_mode + if running_mode == SystemMode.HEAT: + return CURRENT_HVAC_HEAT + if running_mode == SystemMode.COOL: + return CURRENT_HVAC_COOL + + running_state = self._thrm.running_state + if running_state and running_state & ( + RunningState.FAN | RunningState.FAN_STAGE_2 | RunningState.FAN_STAGE_3 + ): + return CURRENT_HVAC_FAN + if self.hvac_mode != HVAC_MODE_OFF and running_mode == SystemMode.OFF: + return CURRENT_HVAC_IDLE + return CURRENT_HVAC_OFF + + @property + def _pi_demand_action(self) -> Optional[str]: + """Return the current HVAC action based on pi_demands.""" + + heating_demand = self._thrm.pi_heating_demand + if heating_demand is not None and heating_demand > 0: + return CURRENT_HVAC_HEAT + cooling_demand = self._thrm.pi_cooling_demand + if cooling_demand is not None and cooling_demand > 0: + return CURRENT_HVAC_COOL + if self.hvac_mode != HVAC_MODE_OFF: return CURRENT_HVAC_IDLE return CURRENT_HVAC_OFF @@ -389,14 +406,11 @@ class Thermostat(ZhaEntity, ClimateEntity): if occupancy is True: self._preset = PRESET_NONE + self.debug("Attribute '%s' = %s update", record.attr_name, record.value) self.async_write_ha_state() async def async_set_fan_mode(self, fan_mode: str) -> None: """Set fan mode.""" - if self.fan_modes is None: - self.warning("Fan is not supported") - return - if fan_mode not in self.fan_modes: self.warning("Unsupported '%s' fan mode", fan_mode) return @@ -540,3 +554,32 @@ class SinopeTechnologiesThermostat(Thermostat): self.debug("set occupancy to %s. Status: %s", 0 if is_away else 1, res) return res + + +@STRICT_MATCH( + channel_names=CHANNEL_THERMOSTAT, + aux_channels=CHANNEL_FAN, + manufacturers="Zen Within", +) +class ZenWithinThermostat(Thermostat): + """Zen Within Thermostat implementation.""" + + @property + def _rm_rs_action(self) -> Optional[str]: + """Return the current HVAC action based on running mode and running state.""" + + running_state = self._thrm.running_state + if running_state is None: + return None + if running_state & (RunningState.HEAT | RunningState.HEAT_STAGE_2): + return CURRENT_HVAC_HEAT + if running_state & (RunningState.COOL | RunningState.COOL_STAGE_2): + return CURRENT_HVAC_COOL + if running_state & ( + RunningState.FAN | RunningState.FAN_STAGE_2 | RunningState.FAN_STAGE_3 + ): + return CURRENT_HVAC_FAN + + if self.hvac_mode != HVAC_MODE_OFF: + return CURRENT_HVAC_IDLE + return CURRENT_HVAC_OFF diff --git a/tests/components/zha/test_climate.py b/tests/components/zha/test_climate.py index c8cffef3fb9..8ca949cd44e 100644 --- a/tests/components/zha/test_climate.py +++ b/tests/components/zha/test_climate.py @@ -89,7 +89,22 @@ CLIMATE_SINOPE = { "profile_id": 260, }, } -SINOPE = "Sinope Technologies" + +CLIMATE_ZEN = { + 1: { + "device_type": zigpy.profiles.zha.DeviceType.THERMOSTAT, + "in_clusters": [ + zigpy.zcl.clusters.general.Basic.cluster_id, + zigpy.zcl.clusters.general.Identify.cluster_id, + zigpy.zcl.clusters.hvac.Fan.cluster_id, + zigpy.zcl.clusters.hvac.Thermostat.cluster_id, + zigpy.zcl.clusters.hvac.UserInterface.cluster_id, + ], + "out_clusters": [zigpy.zcl.clusters.general.Ota.cluster_id], + } +} +MANUF_SINOPE = "Sinope Technologies" +MANUF_ZEN = "Zen Within" ZCL_ATTR_PLUG = { "abs_min_heat_setpoint_limit": 800, @@ -169,7 +184,14 @@ async def device_climate_fan(device_climate_mock): async def device_climate_sinope(device_climate_mock): """Sinope thermostat.""" - return await device_climate_mock(CLIMATE_SINOPE, manuf=SINOPE) + return await device_climate_mock(CLIMATE_SINOPE, manuf=MANUF_SINOPE) + + +@pytest.fixture +async def device_climate_zen(device_climate_mock): + """Zen Within thermostat.""" + + return await device_climate_mock(CLIMATE_ZEN, manuf=MANUF_ZEN) def test_sequence_mappings(): @@ -201,6 +223,52 @@ async def test_climate_hvac_action_running_state(hass, device_climate): thrm_cluster = device_climate.device.endpoints[1].thermostat entity_id = await find_entity_id(DOMAIN, device_climate, hass) + state = hass.states.get(entity_id) + assert state.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_OFF + + await send_attributes_report( + hass, thrm_cluster, {0x001E: Thermostat.RunningMode.Off} + ) + state = hass.states.get(entity_id) + assert state.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_OFF + + await send_attributes_report( + hass, thrm_cluster, {0x001C: Thermostat.SystemMode.Auto} + ) + state = hass.states.get(entity_id) + assert state.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_IDLE + + await send_attributes_report( + hass, thrm_cluster, {0x001E: Thermostat.RunningMode.Cool} + ) + state = hass.states.get(entity_id) + assert state.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_COOL + + await send_attributes_report( + hass, thrm_cluster, {0x001E: Thermostat.RunningMode.Heat} + ) + state = hass.states.get(entity_id) + assert state.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_HEAT + + await send_attributes_report( + hass, thrm_cluster, {0x001E: Thermostat.RunningMode.Off} + ) + state = hass.states.get(entity_id) + assert state.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_IDLE + + await send_attributes_report( + hass, thrm_cluster, {0x0029: Thermostat.RunningState.Fan_State_On} + ) + state = hass.states.get(entity_id) + assert state.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_FAN + + +async def test_climate_hvac_action_running_state_zen(hass, device_climate_zen): + """Test Zen hvac action via running state.""" + + thrm_cluster = device_climate_zen.device.endpoints[1].thermostat + entity_id = await find_entity_id(DOMAIN, device_climate_zen, hass) + state = hass.states.get(entity_id) assert ATTR_HVAC_ACTION not in state.attributes @@ -266,7 +334,7 @@ async def test_climate_hvac_action_pi_demand(hass, device_climate): entity_id = await find_entity_id(DOMAIN, device_climate, hass) state = hass.states.get(entity_id) - assert ATTR_HVAC_ACTION not in state.attributes + assert state.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_OFF await send_attributes_report(hass, thrm_cluster, {0x0007: 10}) state = hass.states.get(entity_id) @@ -381,7 +449,7 @@ async def test_target_temperature( "unoccupied_heating_setpoint": 1600, "unoccupied_cooling_setpoint": 2700, }, - manuf=SINOPE, + manuf=MANUF_SINOPE, ) entity_id = await find_entity_id(DOMAIN, device_climate, hass) if preset: @@ -417,7 +485,7 @@ async def test_target_temperature_high( "system_mode": Thermostat.SystemMode.Auto, "unoccupied_cooling_setpoint": unoccupied, }, - manuf=SINOPE, + manuf=MANUF_SINOPE, ) entity_id = await find_entity_id(DOMAIN, device_climate, hass) if preset: @@ -453,7 +521,7 @@ async def test_target_temperature_low( "system_mode": Thermostat.SystemMode.Auto, "unoccupied_heating_setpoint": unoccupied, }, - manuf=SINOPE, + manuf=MANUF_SINOPE, ) entity_id = await find_entity_id(DOMAIN, device_climate, hass) if preset: @@ -665,7 +733,7 @@ async def test_set_temperature_heat_cool(hass, device_climate_mock): "unoccupied_heating_setpoint": 1600, "unoccupied_cooling_setpoint": 2700, }, - manuf=SINOPE, + manuf=MANUF_SINOPE, ) entity_id = await find_entity_id(DOMAIN, device_climate, hass) thrm_cluster = device_climate.device.endpoints[1].thermostat @@ -755,7 +823,7 @@ async def test_set_temperature_heat(hass, device_climate_mock): "unoccupied_heating_setpoint": 1600, "unoccupied_cooling_setpoint": 2700, }, - manuf=SINOPE, + manuf=MANUF_SINOPE, ) entity_id = await find_entity_id(DOMAIN, device_climate, hass) thrm_cluster = device_climate.device.endpoints[1].thermostat @@ -838,7 +906,7 @@ async def test_set_temperature_cool(hass, device_climate_mock): "unoccupied_cooling_setpoint": 1600, "unoccupied_heating_setpoint": 2700, }, - manuf=SINOPE, + manuf=MANUF_SINOPE, ) entity_id = await find_entity_id(DOMAIN, device_climate, hass) thrm_cluster = device_climate.device.endpoints[1].thermostat @@ -921,7 +989,7 @@ async def test_set_temperature_wrong_mode(hass, device_climate_mock): "unoccupied_cooling_setpoint": 1600, "unoccupied_heating_setpoint": 2700, }, - manuf=SINOPE, + manuf=MANUF_SINOPE, ) entity_id = await find_entity_id(DOMAIN, device_climate, hass) thrm_cluster = device_climate.device.endpoints[1].thermostat @@ -1000,19 +1068,6 @@ async def test_fan_mode(hass, device_climate_fan): assert state.attributes[ATTR_FAN_MODE] == FAN_ON -async def test_set_fan_mode_no_fan(hass, device_climate): - """Test setting fan mode on fun less climate.""" - - entity_id = await find_entity_id(DOMAIN, device_climate, hass) - - await hass.services.async_call( - DOMAIN, - SERVICE_SET_FAN_MODE, - {ATTR_ENTITY_ID: entity_id, ATTR_FAN_MODE: FAN_ON}, - blocking=True, - ) - - async def test_set_fan_mode_not_supported(hass, device_climate_fan): """Test fan setting unsupported mode.""" diff --git a/tests/components/zha/zha_devices_list.py b/tests/components/zha/zha_devices_list.py index 01144cde694..230cc5d2377 100644 --- a/tests/components/zha/zha_devices_list.py +++ b/tests/components/zha/zha_devices_list.py @@ -3349,7 +3349,7 @@ DEVICES = [ }, ("climate", "00:11:22:33:44:55:66:77-1"): { "channels": ["thermostat", "fan"], - "entity_class": "Thermostat", + "entity_class": "ZenWithinThermostat", "entity_id": "climate.zen_within_zen_01_77665544_fan_thermostat", }, }, From cda2da62cfece023c7121e7d86b8ae80ea672a2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren=20Oldag?= Date: Sat, 23 May 2020 14:09:59 +0200 Subject: [PATCH 147/406] Migrate rpi_gpio_pwm to extend LightEntity instead of Light (#36028) --- homeassistant/components/rpi_gpio_pwm/light.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/rpi_gpio_pwm/light.py b/homeassistant/components/rpi_gpio_pwm/light.py index 96ac3c6f2ed..f86d1f27832 100644 --- a/homeassistant/components/rpi_gpio_pwm/light.py +++ b/homeassistant/components/rpi_gpio_pwm/light.py @@ -17,7 +17,7 @@ from homeassistant.components.light import ( SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_TRANSITION, - Light, + LightEntity, ) from homeassistant.const import CONF_ADDRESS, CONF_HOST, CONF_NAME, CONF_TYPE, STATE_ON import homeassistant.helpers.config_validation as cv @@ -104,7 +104,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities(leds) -class PwmSimpleLed(Light, RestoreEntity): +class PwmSimpleLed(LightEntity, RestoreEntity): """Representation of a simple one-color PWM LED.""" def __init__(self, led, name): From b0578018cb13cc24ef04eee3520605fbc31fa793 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 23 May 2020 11:05:41 -0500 Subject: [PATCH 148/406] Use a single service browser for zeroconf discovery (#35997) --- homeassistant/components/zeroconf/__init__.py | 9 +++++---- homeassistant/components/zeroconf/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/zeroconf/test_init.py | 7 ++++--- 6 files changed, 13 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/zeroconf/__init__.py b/homeassistant/components/zeroconf/__init__.py index 6c7e0ff8103..b4d1359df9d 100644 --- a/homeassistant/components/zeroconf/__init__.py +++ b/homeassistant/components/zeroconf/__init__.py @@ -98,7 +98,7 @@ class HaServiceBrowser(ServiceBrowser): # To avoid overwhemling the system we pre-filter here and only process # DNSPointers for the configured record name (type) # - if record.name != self.type or not isinstance(record, DNSPointer): + if record.name not in self.types or not isinstance(record, DNSPointer): return super().update_record(zc, now, record) @@ -224,11 +224,12 @@ def setup(hass, config): ) ) - for service in ZEROCONF: - HaServiceBrowser(zeroconf, service, handlers=[service_update]) + types = list(ZEROCONF) if HOMEKIT_TYPE not in ZEROCONF: - HaServiceBrowser(zeroconf, HOMEKIT_TYPE, handlers=[service_update]) + types.append(HOMEKIT_TYPE) + + HaServiceBrowser(zeroconf, types, handlers=[service_update]) return True diff --git a/homeassistant/components/zeroconf/manifest.json b/homeassistant/components/zeroconf/manifest.json index a3d4d1d8399..9d4398f3b56 100644 --- a/homeassistant/components/zeroconf/manifest.json +++ b/homeassistant/components/zeroconf/manifest.json @@ -2,7 +2,7 @@ "domain": "zeroconf", "name": "Zero-configuration networking (zeroconf)", "documentation": "https://www.home-assistant.io/integrations/zeroconf", - "requirements": ["zeroconf==0.26.1"], + "requirements": ["zeroconf==0.26.2"], "dependencies": ["api"], "codeowners": ["@robbiet480", "@Kane610"], "quality_scale": "internal" diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 0784fb39052..7a1543690e2 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -25,7 +25,7 @@ ruamel.yaml==0.15.100 sqlalchemy==1.3.17 voluptuous-serialize==2.3.0 voluptuous==0.11.7 -zeroconf==0.26.1 +zeroconf==0.26.2 pycryptodome>=3.6.6 diff --git a/requirements_all.txt b/requirements_all.txt index 4466a8a543d..d19f01f6c59 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2239,7 +2239,7 @@ youtube_dl==2020.05.08 zengge==0.2 # homeassistant.components.zeroconf -zeroconf==0.26.1 +zeroconf==0.26.2 # homeassistant.components.zha zha-quirks==0.0.39 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index d0f8ae47b04..8e79c1f30c2 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -906,7 +906,7 @@ xmltodict==0.12.0 ya_ma==0.3.8 # homeassistant.components.zeroconf -zeroconf==0.26.1 +zeroconf==0.26.2 # homeassistant.components.zha zha-quirks==0.0.39 diff --git a/tests/components/zeroconf/test_init.py b/tests/components/zeroconf/test_init.py index 89c9d0c2643..74069fa5faa 100644 --- a/tests/components/zeroconf/test_init.py +++ b/tests/components/zeroconf/test_init.py @@ -29,9 +29,10 @@ def mock_zeroconf(): yield mock_zc.return_value -def service_update_mock(zeroconf, service, handlers): +def service_update_mock(zeroconf, services, handlers): """Call service update handler.""" - handlers[0](zeroconf, service, f"name.{service}", ServiceStateChange.Added) + for service in services: + handlers[0](zeroconf, service, f"name.{service}", ServiceStateChange.Added) def get_service_info_mock(service_type, name): @@ -76,7 +77,7 @@ async def test_setup(hass, mock_zeroconf): mock_zeroconf.get_service_info.side_effect = get_service_info_mock assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) - assert len(mock_service_browser.mock_calls) == len(zc_gen.ZEROCONF) + assert len(mock_service_browser.mock_calls) == 1 expected_flow_calls = 0 for matching_components in zc_gen.ZEROCONF.values(): expected_flow_calls += len(matching_components) From d21cfd869e8519c8eb409d767cef2dcf2c4c594f Mon Sep 17 00:00:00 2001 From: Robert Chmielowiec Date: Sat, 23 May 2020 18:11:51 +0200 Subject: [PATCH 149/406] Fix service registration supported features check (#35718) --- homeassistant/helpers/service.py | 3 +- tests/helpers/test_service.py | 77 ++++++++++++++++++++++++++++---- 2 files changed, 71 insertions(+), 9 deletions(-) diff --git a/homeassistant/helpers/service.py b/homeassistant/helpers/service.py index ce52d188540..af4bdb50fa4 100644 --- a/homeassistant/helpers/service.py +++ b/homeassistant/helpers/service.py @@ -431,7 +431,8 @@ async def entity_service_call(hass, platforms, func, call, required_features=Non # Skip entities that don't have the required feature. if required_features is not None and not any( - entity.supported_features & feature_set for feature_set in required_features + entity.supported_features & feature_set == feature_set + for feature_set in required_features ): continue diff --git a/tests/helpers/test_service.py b/tests/helpers/test_service.py index e87fd2646dd..ba72cbc83ca 100644 --- a/tests/helpers/test_service.py +++ b/tests/helpers/test_service.py @@ -35,6 +35,10 @@ from tests.common import ( mock_service, ) +SUPPORT_A = 1 +SUPPORT_B = 2 +SUPPORT_C = 4 + @pytest.fixture def mock_handle_entity_call(): @@ -52,17 +56,31 @@ def mock_entities(hass): entity_id="light.kitchen", available=True, should_poll=False, - supported_features=1, + supported_features=SUPPORT_A, ) living_room = MockEntity( entity_id="light.living_room", available=True, should_poll=False, - supported_features=0, + supported_features=SUPPORT_B, + ) + bedroom = MockEntity( + entity_id="light.bedroom", + available=True, + should_poll=False, + supported_features=(SUPPORT_A | SUPPORT_B), + ) + bathroom = MockEntity( + entity_id="light.bathroom", + available=True, + should_poll=False, + supported_features=(SUPPORT_B | SUPPORT_C), ) entities = OrderedDict() entities[kitchen.entity_id] = kitchen entities[living_room.entity_id] = living_room + entities[bedroom.entity_id] = bedroom + entities[bathroom.entity_id] = bathroom return entities @@ -307,18 +325,61 @@ 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 calls invoked only if entity has required features.""" test_service_mock = AsyncMock(return_value=None) await service.entity_service_call( hass, [Mock(entities=mock_entities)], test_service_mock, ha.ServiceCall("test_domain", "test_service", {"entity_id": "all"}), - required_features=[1], + required_features=[SUPPORT_A], ) - assert len(mock_entities) == 2 - # Called once because only one of the entities had the required features + + assert test_service_mock.call_count == 2 + expected = [ + mock_entities["light.kitchen"], + mock_entities["light.bedroom"], + ] + actual = [call[0][0] for call in test_service_mock.call_args_list] + assert all(entity in actual for entity in expected) + + +async def test_call_with_both_required_features(hass, mock_entities): + """Test service calls invoked only if entity has both features.""" + test_service_mock = AsyncMock(return_value=None) + await service.entity_service_call( + hass, + [Mock(entities=mock_entities)], + test_service_mock, + ha.ServiceCall("test_domain", "test_service", {"entity_id": "all"}), + required_features=[SUPPORT_A | SUPPORT_B], + ) + assert test_service_mock.call_count == 1 + assert [call[0][0] for call in test_service_mock.call_args_list] == [ + mock_entities["light.bedroom"] + ] + + +async def test_call_with_one_of_required_features(hass, mock_entities): + """Test service calls invoked with one entity having the required features.""" + test_service_mock = AsyncMock(return_value=None) + await service.entity_service_call( + hass, + [Mock(entities=mock_entities)], + test_service_mock, + ha.ServiceCall("test_domain", "test_service", {"entity_id": "all"}), + required_features=[SUPPORT_A, SUPPORT_C], + ) + + assert test_service_mock.call_count == 3 + expected = [ + mock_entities["light.kitchen"], + mock_entities["light.bedroom"], + mock_entities["light.bathroom"], + ] + actual = [call[0][0] for call in test_service_mock.call_args_list] + assert all(entity in actual for entity in expected) async def test_call_with_sync_func(hass, mock_entities): @@ -458,7 +519,7 @@ async def test_call_no_context_target_all(hass, mock_handle_entity_call, mock_en ), ) - assert len(mock_handle_entity_call.mock_calls) == 2 + assert len(mock_handle_entity_call.mock_calls) == 4 assert [call[1][1] for call in mock_handle_entity_call.mock_calls] == list( mock_entities.values() ) @@ -494,7 +555,7 @@ async def test_call_with_match_all( ha.ServiceCall("test_domain", "test_service", {"entity_id": "all"}), ) - assert len(mock_handle_entity_call.mock_calls) == 2 + assert len(mock_handle_entity_call.mock_calls) == 4 assert [call[1][1] for call in mock_handle_entity_call.mock_calls] == list( mock_entities.values() ) From 2c7eee672273450bbffb99c6f1627721bed6a5d4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 23 May 2020 11:12:55 -0500 Subject: [PATCH 150/406] Ensure configured logger severity is respected (#35749) --- homeassistant/components/logger/__init__.py | 49 ++++-- tests/components/logger/test_init.py | 159 ++++++++++---------- 2 files changed, 113 insertions(+), 95 deletions(-) diff --git a/homeassistant/components/logger/__init__.py b/homeassistant/components/logger/__init__.py index 961718a30ee..ddf41640e6f 100644 --- a/homeassistant/components/logger/__init__.py +++ b/homeassistant/components/logger/__init__.py @@ -1,6 +1,6 @@ """Support for setting the level of logging for components.""" -from collections import OrderedDict import logging +import re import voluptuous as vol @@ -50,33 +50,54 @@ CONFIG_SCHEMA = vol.Schema( class HomeAssistantLogFilter(logging.Filter): """A log filter.""" - def __init__(self, logfilter): + def __init__(self): """Initialize the filter.""" super().__init__() - self.logfilter = logfilter + self._default = None + self._logs = None + self._log_rx = None + + def update_default_level(self, default_level): + """Update the default logger level.""" + self._default = default_level + + def update_log_filter(self, logs): + """Rebuild the internal filter from new config.""" + # + # A precompiled regex is used to avoid + # the overhead of a list transversal + # + # Sort to make sure the longer + # names are always matched first + # so they take precedence of the shorter names + # to allow for more granular settings. + # + names_by_len = sorted(list(logs), key=len, reverse=True) + self._log_rx = re.compile("".join(["^(?:", "|".join(names_by_len), ")"])) + self._logs = logs def filter(self, record): """Filter the log entries.""" # Log with filtered severity - if LOGGER_LOGS in self.logfilter: - for filtername in self.logfilter[LOGGER_LOGS]: - logseverity = self.logfilter[LOGGER_LOGS][filtername] - if record.name.startswith(filtername): - return record.levelno >= logseverity + if self._log_rx: + match = self._log_rx.match(record.name) + if match: + return record.levelno >= self._logs[match.group(0)] # Log with default severity - default = self.logfilter[LOGGER_DEFAULT] - return record.levelno >= default + return record.levelno >= self._default async def async_setup(hass, config): """Set up the logger component.""" logfilter = {} + hass_filter = HomeAssistantLogFilter() def set_default_log_level(level): """Set the default log level for components.""" logfilter[LOGGER_DEFAULT] = LOGSEVERITY[level] + hass_filter.update_default_level(LOGSEVERITY[level]) def set_log_levels(logpoints): """Set the specified log levels.""" @@ -90,9 +111,9 @@ async def async_setup(hass, config): for key, value in logpoints.items(): logs[key] = LOGSEVERITY[value] - logfilter[LOGGER_LOGS] = OrderedDict( - sorted(logs.items(), key=lambda t: len(t[0]), reverse=True) - ) + logfilter[LOGGER_LOGS] = logs + + hass_filter.update_log_filter(logs) # Set default log severity if LOGGER_DEFAULT in config.get(DOMAIN): @@ -106,7 +127,7 @@ async def async_setup(hass, config): # Set log filter for all log handler for handler in logging.root.handlers: handler.setLevel(logging.NOTSET) - handler.addFilter(HomeAssistantLogFilter(logfilter)) + handler.addFilter(hass_filter) if LOGGER_LOGS in config.get(DOMAIN): set_log_levels(config.get(DOMAIN)[LOGGER_LOGS]) diff --git a/tests/components/logger/test_init.py b/tests/components/logger/test_init.py index 00fa5aa3558..ccbc476c204 100644 --- a/tests/components/logger/test_init.py +++ b/tests/components/logger/test_init.py @@ -1,123 +1,120 @@ """The tests for the Logger component.""" from collections import namedtuple import logging -import unittest from homeassistant.components import logger -from homeassistant.setup import setup_component - -from tests.common import get_test_home_assistant +from homeassistant.setup import async_setup_component RECORD = namedtuple("record", ("name", "levelno")) NO_DEFAULT_CONFIG = {"logger": {}} NO_LOGS_CONFIG = {"logger": {"default": "info"}} -TEST_CONFIG = {"logger": {"default": "warning", "logs": {"test": "info"}}} +TEST_CONFIG = { + "logger": { + "default": "warning", + "logs": {"test": "info", "test.child": "debug", "test.child.child": "warning"}, + } +} -class TestUpdater(unittest.TestCase): - """Test logger component.""" +async def async_setup_logger(hass, config): + """Set up logger and save log filter.""" + await async_setup_component(hass, logger.DOMAIN, config) + return logging.root.handlers[-1].filters[0] - def setUp(self): - """Set up things to be run when tests are started.""" - self.hass = get_test_home_assistant() - self.log_filter = None - def tearDown(self): - """Stop everything that was started.""" - del logging.root.handlers[-1] - self.hass.stop() +async def test_logger_setup(hass): + """Use logger to create a logging filter.""" + await async_setup_logger(hass, TEST_CONFIG) - def setup_logger(self, config): - """Set up logger and save log filter.""" - setup_component(self.hass, logger.DOMAIN, config) - self.log_filter = logging.root.handlers[-1].filters[0] + assert len(logging.root.handlers) > 0 + handler = logging.root.handlers[-1] - def assert_logged(self, name, level): - """Assert that a certain record was logged.""" - assert self.log_filter.filter(RECORD(name, level)) + assert len(handler.filters) == 1 - def assert_not_logged(self, name, level): - """Assert that a certain record was not logged.""" - assert not self.log_filter.filter(RECORD(name, level)) - def test_logger_setup(self): - """Use logger to create a logging filter.""" - self.setup_logger(TEST_CONFIG) +async def test_logger_test_filters(hass): + """Test resulting filter operation.""" + log_filter = await async_setup_logger(hass, TEST_CONFIG) - assert len(logging.root.handlers) > 0 - handler = logging.root.handlers[-1] + # Blocked default record + assert not log_filter.filter(RECORD("asdf", logging.DEBUG)) - assert len(handler.filters) == 1 - log_filter = handler.filters[0].logfilter + # Allowed default record + assert log_filter.filter(RECORD("asdf", logging.WARNING)) - assert log_filter["default"] == logging.WARNING - assert log_filter["logs"]["test"] == logging.INFO + # Blocked named record + assert not log_filter.filter(RECORD("test", logging.DEBUG)) - def test_logger_test_filters(self): - """Test resulting filter operation.""" - self.setup_logger(TEST_CONFIG) + # Allowed named record + assert log_filter.filter(RECORD("test", logging.INFO)) - # Blocked default record - self.assert_not_logged("asdf", logging.DEBUG) + # Allowed named record child + assert log_filter.filter(RECORD("test.child", logging.INFO)) - # Allowed default record - self.assert_logged("asdf", logging.WARNING) + # Allowed named record child + assert log_filter.filter(RECORD("test.child", logging.DEBUG)) - # Blocked named record - self.assert_not_logged("test", logging.DEBUG) + # Blocked named record child of child + assert not log_filter.filter(RECORD("test.child.child", logging.DEBUG)) - # Allowed named record - self.assert_logged("test", logging.INFO) + # Allowed named record child of child + assert log_filter.filter(RECORD("test.child.child", logging.WARNING)) - def test_set_filter_empty_config(self): - """Test change log level from empty configuration.""" - self.setup_logger(NO_LOGS_CONFIG) - self.assert_not_logged("test", logging.DEBUG) +async def test_set_filter_empty_config(hass): + """Test change log level from empty configuration.""" + log_filter = await async_setup_logger(hass, NO_LOGS_CONFIG) - self.hass.services.call(logger.DOMAIN, "set_level", {"test": "debug"}) - self.hass.block_till_done() + assert not log_filter.filter(RECORD("test", logging.DEBUG)) - self.assert_logged("test", logging.DEBUG) + await hass.services.async_call(logger.DOMAIN, "set_level", {"test": "debug"}) + await hass.async_block_till_done() - def test_set_filter(self): - """Test change log level of existing filter.""" - self.setup_logger(TEST_CONFIG) + assert log_filter.filter(RECORD("test", logging.DEBUG)) - self.assert_not_logged("asdf", logging.DEBUG) - self.assert_logged("dummy", logging.WARNING) - self.hass.services.call( - logger.DOMAIN, "set_level", {"asdf": "debug", "dummy": "info"} - ) - self.hass.block_till_done() +async def test_set_filter(hass): + """Test change log level of existing filter.""" + log_filter = await async_setup_logger(hass, TEST_CONFIG) - self.assert_logged("asdf", logging.DEBUG) - self.assert_logged("dummy", logging.WARNING) + assert not log_filter.filter(RECORD("asdf", logging.DEBUG)) + assert log_filter.filter(RECORD("dummy", logging.WARNING)) - def test_set_default_filter_empty_config(self): - """Test change default log level from empty configuration.""" - self.setup_logger(NO_DEFAULT_CONFIG) + await hass.services.async_call( + logger.DOMAIN, "set_level", {"asdf": "debug", "dummy": "info"} + ) + await hass.async_block_till_done() - self.assert_logged("test", logging.DEBUG) + assert log_filter.filter(RECORD("asdf", logging.DEBUG)) + assert log_filter.filter(RECORD("dummy", logging.WARNING)) - self.hass.services.call( - logger.DOMAIN, "set_default_level", {"level": "warning"} - ) - self.hass.block_till_done() - self.assert_not_logged("test", logging.DEBUG) +async def test_set_default_filter_empty_config(hass): + """Test change default log level from empty configuration.""" + log_filter = await async_setup_logger(hass, NO_DEFAULT_CONFIG) - def test_set_default_filter(self): - """Test change default log level with existing default.""" - self.setup_logger(TEST_CONFIG) + assert log_filter.filter(RECORD("test", logging.DEBUG)) - self.assert_not_logged("asdf", logging.DEBUG) - self.assert_logged("dummy", logging.WARNING) + await hass.services.async_call( + logger.DOMAIN, "set_default_level", {"level": "warning"} + ) + await hass.async_block_till_done() - self.hass.services.call(logger.DOMAIN, "set_default_level", {"level": "debug"}) - self.hass.block_till_done() + assert not log_filter.filter(RECORD("test", logging.DEBUG)) - self.assert_logged("asdf", logging.DEBUG) - self.assert_logged("dummy", logging.WARNING) + +async def test_set_default_filter(hass): + """Test change default log level with existing default.""" + log_filter = await async_setup_logger(hass, TEST_CONFIG) + + assert not log_filter.filter(RECORD("asdf", logging.DEBUG)) + assert log_filter.filter(RECORD("dummy", logging.WARNING)) + + await hass.services.async_call( + logger.DOMAIN, "set_default_level", {"level": "debug"} + ) + await hass.async_block_till_done() + + assert log_filter.filter(RECORD("asdf", logging.DEBUG)) + assert log_filter.filter(RECORD("dummy", logging.WARNING)) From b1363906474ae06aa2a8f5458916263a823c85c7 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Sat, 23 May 2020 18:29:13 +0200 Subject: [PATCH 151/406] Reset zeroconf log level (#36002) --- homeassistant/components/zeroconf/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/homeassistant/components/zeroconf/__init__.py b/homeassistant/components/zeroconf/__init__.py index b4d1359df9d..5b5275f1d1b 100644 --- a/homeassistant/components/zeroconf/__init__.py +++ b/homeassistant/components/zeroconf/__init__.py @@ -14,6 +14,7 @@ from zeroconf import ( ServiceInfo, ServiceStateChange, Zeroconf, + log as zeroconf_log, ) from homeassistant import util @@ -114,6 +115,8 @@ class HaZeroconf(Zeroconf): def setup(hass, config): """Set up Zeroconf and make Home Assistant discoverable.""" + # Zeroconf sets its log level to WARNING, reset it to allow filtering by the logger component. + zeroconf_log.setLevel(logging.NOTSET) zeroconf = hass.data[DOMAIN] = _get_instance( hass, config.get(DOMAIN, {}).get(CONF_DEFAULT_INTERFACE) ) From e9a729a46cf38f2059da485ca7a4453f0ac6a337 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 23 May 2020 12:56:27 -0500 Subject: [PATCH 152/406] Fix shade compatibility with hunter douglas powerview 1.0 hubs (#36040) --- .../hunterdouglas_powerview/cover.py | 6 +++++ .../hunterdouglas_powerview/entity.py | 22 ++++++++++++------- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/hunterdouglas_powerview/cover.py b/homeassistant/components/hunterdouglas_powerview/cover.py index e14142677e3..8135b4a8c77 100644 --- a/homeassistant/components/hunterdouglas_powerview/cover.py +++ b/homeassistant/components/hunterdouglas_powerview/cover.py @@ -68,6 +68,12 @@ async def async_setup_entry(hass, entry, async_add_entities): except asyncio.TimeoutError: # Forced refresh is not required for setup pass + if ATTR_POSITION_DATA not in shade.raw_data: + _LOGGER.info( + "The %s shade was skipped because it is missing position data", + name_before_refresh, + ) + continue entities.append( PowerViewShade( shade, name_before_refresh, room_data, coordinator, device_info diff --git a/homeassistant/components/hunterdouglas_powerview/entity.py b/homeassistant/components/hunterdouglas_powerview/entity.py index 3c98eeaf615..f89ca28023b 100644 --- a/homeassistant/components/hunterdouglas_powerview/entity.py +++ b/homeassistant/components/hunterdouglas_powerview/entity.py @@ -74,6 +74,17 @@ class ShadeEntity(HDEntity): @property def device_info(self): """Return the device_info of the device.""" + + device_info = { + "identifiers": {(DOMAIN, self._shade.id)}, + "name": self._shade_name, + "manufacturer": MANUFACTURER, + "via_device": (DOMAIN, self._device_info[DEVICE_SERIAL_NUMBER]), + } + + if FIRMWARE_IN_SHADE not in self._shade.raw_data: + return device_info + 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] @@ -82,11 +93,6 @@ class ShadeEntity(HDEntity): 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]), - } + device_info["sw_version"] = sw_version + device_info["model"] = model + return device_info From 99e345d6e4e8f6f24b77b6e3271584d69e127c02 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 23 May 2020 19:57:39 +0200 Subject: [PATCH 153/406] Upgrade hass-nabucasa to 0.34.3 (#36025) --- 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 de5496cfd99..fcd6738b77c 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.2"], + "requirements": ["hass-nabucasa==0.34.3"], "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 7a1543690e2..5786ea0085e 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -11,7 +11,7 @@ ciso8601==2.1.3 cryptography==2.9.2 defusedxml==0.6.0 distro==1.5.0 -hass-nabucasa==0.34.2 +hass-nabucasa==0.34.3 home-assistant-frontend==20200519.4 importlib-metadata==1.6.0 jinja2>=2.11.1 diff --git a/requirements_all.txt b/requirements_all.txt index d19f01f6c59..3944338e913 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -704,7 +704,7 @@ habitipy==0.2.0 hangups==0.4.9 # homeassistant.components.cloud -hass-nabucasa==0.34.2 +hass-nabucasa==0.34.3 # homeassistant.components.mqtt hbmqtt==0.9.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 8e79c1f30c2..fc25c794562 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -300,7 +300,7 @@ ha-ffmpeg==2.0 hangups==0.4.9 # homeassistant.components.cloud -hass-nabucasa==0.34.2 +hass-nabucasa==0.34.3 # homeassistant.components.mqtt hbmqtt==0.9.5 From 6e3bba07dad3716f6b3dc459e5bcc5445fb7d1cf Mon Sep 17 00:00:00 2001 From: Jc2k Date: Sat, 23 May 2020 18:59:32 +0100 Subject: [PATCH 154/406] Bump aiohomekit (#36041) --- .../components/homekit_controller/connection.py | 10 ++-------- .../components/homekit_controller/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 5 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/homekit_controller/connection.py b/homeassistant/components/homekit_controller/connection.py index 605253e6235..d910de34321 100644 --- a/homeassistant/components/homekit_controller/connection.py +++ b/homeassistant/components/homekit_controller/connection.py @@ -89,10 +89,6 @@ class HKDevice: # mapped to a HA entity. self.entities = [] - # There are multiple entities sharing a single connection - only - # allow one entity to use pairing at once. - self.pairing_lock = asyncio.Lock() - self.available = True self.signal_state_updated = "_".join((DOMAIN, self.unique_id, "state_updated")) @@ -333,13 +329,11 @@ class HKDevice: async def get_characteristics(self, *args, **kwargs): """Read latest state from homekit accessory.""" - async with self.pairing_lock: - return await self.pairing.get_characteristics(*args, **kwargs) + return await self.pairing.get_characteristics(*args, **kwargs) async def put_characteristics(self, characteristics): """Control a HomeKit device state from Home Assistant.""" - async with self.pairing_lock: - results = await self.pairing.put_characteristics(characteristics) + results = await self.pairing.put_characteristics(characteristics) # Feed characteristics back into HA and update the current state # results will only contain failures, so anythin in characteristics diff --git a/homeassistant/components/homekit_controller/manifest.json b/homeassistant/components/homekit_controller/manifest.json index 07736f61c8e..961dd380ac1 100644 --- a/homeassistant/components/homekit_controller/manifest.json +++ b/homeassistant/components/homekit_controller/manifest.json @@ -3,7 +3,7 @@ "name": "HomeKit Controller", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/homekit_controller", - "requirements": ["aiohomekit[IP]==0.2.37"], + "requirements": ["aiohomekit[IP]==0.2.38"], "zeroconf": ["_hap._tcp.local."], "codeowners": ["@Jc2k"] } diff --git a/requirements_all.txt b/requirements_all.txt index 3944338e913..6d783de4b9d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -175,7 +175,7 @@ aioftp==0.12.0 aioharmony==0.1.13 # homeassistant.components.homekit_controller -aiohomekit[IP]==0.2.37 +aiohomekit[IP]==0.2.38 # homeassistant.components.emulated_hue # homeassistant.components.http diff --git a/requirements_test_all.txt b/requirements_test_all.txt index fc25c794562..38ec9efb4b0 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -82,7 +82,7 @@ aiofreepybox==0.0.8 aioharmony==0.1.13 # homeassistant.components.homekit_controller -aiohomekit[IP]==0.2.37 +aiohomekit[IP]==0.2.38 # homeassistant.components.emulated_hue # homeassistant.components.http From be854b73632fea4ff740f99f2fa8e4978a74f9fd Mon Sep 17 00:00:00 2001 From: Chris Talkington Date: Sat, 23 May 2020 13:02:49 -0500 Subject: [PATCH 155/406] Improve ipp unique id parsing (#35959) --- homeassistant/components/ipp/config_flow.py | 10 +++++----- tests/components/ipp/test_config_flow.py | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/ipp/config_flow.py b/homeassistant/components/ipp/config_flow.py index 3128583f218..ba12d7ec8e2 100644 --- a/homeassistant/components/ipp/config_flow.py +++ b/homeassistant/components/ipp/config_flow.py @@ -85,12 +85,12 @@ class IPPFlowHandler(ConfigFlow, domain=DOMAIN): unique_id = user_input[CONF_UUID] = info[CONF_UUID] - if unique_id is None and info[CONF_SERIAL] is not None: + if not unique_id and info[CONF_SERIAL]: _LOGGER.debug( "Printer UUID is missing from IPP response. Falling back to IPP serial number" ) unique_id = info[CONF_SERIAL] - elif unique_id is None: + elif not unique_id: _LOGGER.debug("Unable to determine unique id from IPP response") await self.async_set_unique_id(unique_id) @@ -138,17 +138,17 @@ class IPPFlowHandler(ConfigFlow, domain=DOMAIN): return self.async_abort(reason="ipp_error") unique_id = self.discovery_info[CONF_UUID] - if unique_id is None and info[CONF_UUID] is not None: + if not unique_id and info[CONF_UUID]: _LOGGER.debug( "Printer UUID is missing from discovery info. Falling back to IPP UUID" ) unique_id = self.discovery_info[CONF_UUID] = info[CONF_UUID] - elif unique_id is None and info[CONF_SERIAL] is not None: + elif not unique_id and info[CONF_SERIAL]: _LOGGER.debug( "Printer UUID is missing from discovery info and IPP response. Falling back to IPP serial number" ) unique_id = info[CONF_SERIAL] - elif unique_id is None: + elif not unique_id: _LOGGER.debug( "Unable to determine unique id from discovery info and IPP response" ) diff --git a/tests/components/ipp/test_config_flow.py b/tests/components/ipp/test_config_flow.py index 0093ba57e5b..a468115f239 100644 --- a/tests/components/ipp/test_config_flow.py +++ b/tests/components/ipp/test_config_flow.py @@ -264,6 +264,24 @@ async def test_zeroconf_with_uuid_device_exists_abort( assert result["reason"] == "already_configured" +async def test_zeroconf_empty_unique_id_required_abort( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: + """Test we abort zeroconf flow if printer lacks (empty) unique identification.""" + mock_connection(aioclient_mock, no_unique_id=True) + + discovery_info = { + **MOCK_ZEROCONF_IPP_SERVICE_INFO, + "properties": {**MOCK_ZEROCONF_IPP_SERVICE_INFO["properties"], "UUID": ""}, + } + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_ZEROCONF}, data=discovery_info, + ) + + assert result["type"] == RESULT_TYPE_ABORT + assert result["reason"] == "unique_id_required" + + async def test_zeroconf_unique_id_required_abort( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker ) -> None: From a5caf8576bc71ec14abaf8c8ea7f6fc589c9dfa9 Mon Sep 17 00:00:00 2001 From: mnaggatz <62095545+mnaggatz@users.noreply.github.com> Date: Sat, 23 May 2020 20:08:02 +0200 Subject: [PATCH 156/406] Add support for Velux Gates (#34774) --- homeassistant/components/velux/cover.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/velux/cover.py b/homeassistant/components/velux/cover.py index 901946b3245..1decffeb744 100644 --- a/homeassistant/components/velux/cover.py +++ b/homeassistant/components/velux/cover.py @@ -1,6 +1,6 @@ """Support for Velux covers.""" from pyvlx import OpeningDevice, Position -from pyvlx.opening_device import Awning, Blind, GarageDoor, RollerShutter, Window +from pyvlx.opening_device import Awning, Blind, GarageDoor, Gate, RollerShutter, Window from homeassistant.components.cover import ( ATTR_POSITION, @@ -73,17 +73,19 @@ class VeluxCover(CoverEntity): @property def device_class(self): - """Define this cover as either window/blind/awning/shutter.""" - if isinstance(self.node, Window): - return "window" - if isinstance(self.node, Blind): - return "blind" - if isinstance(self.node, RollerShutter): - return "shutter" + """Define this cover as either awning, blind, garage, gate, shutter or window.""" if isinstance(self.node, Awning): return "awning" + if isinstance(self.node, Blind): + return "blind" if isinstance(self.node, GarageDoor): return "garage" + if isinstance(self.node, Gate): + return "gate" + if isinstance(self.node, RollerShutter): + return "shutter" + if isinstance(self.node, Window): + return "window" return "window" @property From f3411fee41e1798c2110d21eb2dd19f07afd8124 Mon Sep 17 00:00:00 2001 From: Fredrik Erlandsson Date: Sat, 23 May 2020 21:47:24 +0200 Subject: [PATCH 157/406] Bump tellduslive version (#36048) --- homeassistant/components/tellduslive/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/tellduslive/manifest.json b/homeassistant/components/tellduslive/manifest.json index 55149369427..7ad65b4abd4 100644 --- a/homeassistant/components/tellduslive/manifest.json +++ b/homeassistant/components/tellduslive/manifest.json @@ -3,7 +3,7 @@ "name": "Telldus Live", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/tellduslive", - "requirements": ["tellduslive==0.10.10"], + "requirements": ["tellduslive==0.10.11"], "codeowners": ["@fredrike"], "quality_scale": "gold" } diff --git a/requirements_all.txt b/requirements_all.txt index 6d783de4b9d..84988162762 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2063,7 +2063,7 @@ tellcore-net==0.4 tellcore-py==1.1.2 # homeassistant.components.tellduslive -tellduslive==0.10.10 +tellduslive==0.10.11 # homeassistant.components.lg_soundbar temescal==0.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 38ec9efb4b0..b45b4b3f261 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -832,7 +832,7 @@ stringcase==1.2.0 sunwatcher==0.2.1 # homeassistant.components.tellduslive -tellduslive==0.10.10 +tellduslive==0.10.11 # homeassistant.components.powerwall tesla-powerwall==0.2.8 From 4dcff294c9494b7c01a7446711da1c37bfc770e6 Mon Sep 17 00:00:00 2001 From: michaeldavie Date: Sat, 23 May 2020 16:25:00 -0400 Subject: [PATCH 158/406] Update environment_canada camera (#36010) * Bump env_canada to 0.0.38 * Fix timestamp type --- .../components/environment_canada/camera.py | 17 ++++++----------- .../components/environment_canada/manifest.json | 2 +- requirements_all.txt | 2 +- 3 files changed, 8 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/environment_canada/camera.py b/homeassistant/components/environment_canada/camera.py index d51b69f5713..c0565988c65 100644 --- a/homeassistant/components/environment_canada/camera.py +++ b/homeassistant/components/environment_canada/camera.py @@ -17,8 +17,6 @@ from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) -ATTR_STATION = "station" -ATTR_LOCATION = "location" ATTR_UPDATED = "updated" CONF_ATTRIBUTION = "Data provided by Environment Canada" @@ -50,7 +48,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None): else: lat = config.get(CONF_LATITUDE, hass.config.latitude) lon = config.get(CONF_LONGITUDE, hass.config.longitude) - radar_object = ECRadar(coordinates=(lat, lon)) + radar_object = ECRadar( + coordinates=(lat, lon), precip_type=config.get(CONF_PRECIP_TYPE) + ) add_devices([ECCamera(radar_object, config.get(CONF_NAME))], True) @@ -78,17 +78,12 @@ class ECCamera(Camera): """Return the name of the camera.""" if self.camera_name is not None: return self.camera_name - return " ".join([self.radar_object.station_name, "Radar"]) + return "Environment Canada Radar" @property def device_state_attributes(self): """Return the state attributes of the device.""" - attr = { - ATTR_ATTRIBUTION: CONF_ATTRIBUTION, - ATTR_LOCATION: self.radar_object.station_name, - ATTR_STATION: self.radar_object.station_code, - ATTR_UPDATED: self.timestamp, - } + attr = {ATTR_ATTRIBUTION: CONF_ATTRIBUTION, ATTR_UPDATED: self.timestamp} return attr @@ -99,4 +94,4 @@ class ECCamera(Camera): self.image = self.radar_object.get_loop() else: self.image = self.radar_object.get_latest_frame() - self.timestamp = self.radar_object.timestamp.isoformat() + self.timestamp = self.radar_object.timestamp diff --git a/homeassistant/components/environment_canada/manifest.json b/homeassistant/components/environment_canada/manifest.json index bdc38e90c0c..1fd4d19e370 100644 --- a/homeassistant/components/environment_canada/manifest.json +++ b/homeassistant/components/environment_canada/manifest.json @@ -2,6 +2,6 @@ "domain": "environment_canada", "name": "Environment Canada", "documentation": "https://www.home-assistant.io/integrations/environment_canada", - "requirements": ["env_canada==0.0.35"], + "requirements": ["env_canada==0.0.38"], "codeowners": ["@michaeldavie"] } diff --git a/requirements_all.txt b/requirements_all.txt index 84988162762..a00aeeaac73 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -532,7 +532,7 @@ enocean==0.50 enturclient==0.2.1 # homeassistant.components.environment_canada -env_canada==0.0.35 +env_canada==0.0.38 # homeassistant.components.envirophat # envirophat==0.0.6 From eaa16fa818d5127b65f7bc4fe7ccf9cc36a6a9de Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Sun, 24 May 2020 00:03:05 +0000 Subject: [PATCH 159/406] [ci skip] Translation update --- homeassistant/components/acmeda/translations/no.json | 2 +- homeassistant/components/auth/translations/ko.json | 4 ++-- homeassistant/components/bsblan/translations/pl.json | 8 ++++++++ homeassistant/components/gogogate2/translations/no.json | 4 +++- 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/acmeda/translations/no.json b/homeassistant/components/acmeda/translations/no.json index 79f618f2b99..66335077cfb 100644 --- a/homeassistant/components/acmeda/translations/no.json +++ b/homeassistant/components/acmeda/translations/no.json @@ -12,5 +12,5 @@ } } }, - "title": "Rollease Acmeda Automate" + "title": "" } \ No newline at end of file diff --git a/homeassistant/components/auth/translations/ko.json b/homeassistant/components/auth/translations/ko.json index 563c141587f..80850bb58b4 100644 --- a/homeassistant/components/auth/translations/ko.json +++ b/homeassistant/components/auth/translations/ko.json @@ -21,11 +21,11 @@ }, "totp": { "error": { - "invalid_code": "\uc798\ubabb\ub41c \ucf54\ub4dc \uc785\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694. \uc774 \uc624\ub958\uac00 \uc9c0\uc18d\uc801\uc73c\ub85c \ubc1c\uc0dd\ud55c\ub2e4\uba74 Home Assistant \uc758 \uc2dc\uac04\uc124\uc815\uc774 \uc62c\ubc14\ub978\uc9c0 \ud655\uc778\ud574\ubcf4\uc138\uc694." + "invalid_code": "\uc798\ubabb\ub41c \ucf54\ub4dc\uc785\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694. \uc774 \uc624\ub958\uac00 \uc9c0\uc18d\uc801\uc73c\ub85c \ubc1c\uc0dd\ud55c\ub2e4\uba74 Home Assistant \uc758 \uc2dc\uac04\uc124\uc815\uc774 \uc62c\ubc14\ub978\uc9c0 \ud655\uc778\ud574\uc8fc\uc138\uc694." }, "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.", + "description": "\uc2dc\uac04 \uae30\ubc18\uc758 \uc77c\ud68c\uc6a9 \ube44\ubc00\ubc88\ud638\ub97c \uc0ac\uc6a9\ud558\ub294 2\ub2e8\uacc4 \uc778\uc99d\uc744 \uad6c\uc131\ud558\ub824\uba74 \uc778\uc99d\uc6a9 \uc571\uc744 \uc774\uc6a9\ud574\uc11c QR \ucf54\ub4dc\ub97c \uc2a4\uce94\ud574\uc8fc\uc138\uc694. \uc778\uc99d\uc6a9 \uc571\uc740 [Google OTP](https://support.google.com/accounts/answer/1066447) \ub610\ub294 [Authy](https://authy.com/) \ub97c \ucd94\ucc9c\ub4dc\ub9bd\ub2c8\ub2e4.\n\n{qr_code}\n\n\uc2a4\uce94 \ud6c4\uc5d0 \uc0dd\uc131\ub41c 6\uc790\ub9ac \ucf54\ub4dc\ub97c \uc785\ub825\ud558\uc5ec \uc124\uc815\uc744 \ud655\uc778\ud574\uc8fc\uc138\uc694. QR \ucf54\ub4dc \uc2a4\uce94\uc5d0 \ubb38\uc81c\uac00 \uc788\ub2e4\uba74, **`{code}`** \ucf54\ub4dc\ub85c \uc9c1\uc811 \uc124\uc815\ud574\uc8fc\uc138\uc694.", "title": "TOTP 2\ub2e8\uacc4 \uc778\uc99d \uad6c\uc131\ud558\uae30" } }, diff --git a/homeassistant/components/bsblan/translations/pl.json b/homeassistant/components/bsblan/translations/pl.json index a510785f52f..81ed1374f7f 100644 --- a/homeassistant/components/bsblan/translations/pl.json +++ b/homeassistant/components/bsblan/translations/pl.json @@ -1,9 +1,17 @@ { "config": { + "abort": { + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane." + }, + "error": { + "connection_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia." + }, + "flow_title": "BSB-Lan: {name}", "step": { "user": { "data": { "host": "Nazwa hosta lub adres IP", + "passkey": "Ci\u0105g klucza dost\u0119pu", "port": "Port" }, "description": "Konfiguracja urz\u0105dzenia BSB-LAN w celu integracji z Home Assistantem.", diff --git a/homeassistant/components/gogogate2/translations/no.json b/homeassistant/components/gogogate2/translations/no.json index 1f67db7bfd2..794c72e9aeb 100644 --- a/homeassistant/components/gogogate2/translations/no.json +++ b/homeassistant/components/gogogate2/translations/no.json @@ -3,7 +3,9 @@ "step": { "user": { "data": { - "ip_address": "IP adresse" + "ip_address": "IP adresse", + "password": "Passord", + "username": "Brukernavn" }, "description": "Oppgi n\u00f8dvendig informasjon nedenfor.", "title": "Konfigurer GogoGate2" From f4c5b9f8f8f30fb905651383d2248fae37529878 Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Sat, 23 May 2020 22:37:49 -0400 Subject: [PATCH 160/406] Add ZHA roller shadows as cover instead of switch (#36059) * Implement cover for "Shade" ZHA device type. * Update ZHA cover tests. * Add stop command * Coverage. --- homeassistant/components/zha/core/const.py | 1 + .../components/zha/core/registries.py | 1 + homeassistant/components/zha/cover.py | 126 +++++++++++- homeassistant/components/zha/entity.py | 4 +- tests/components/zha/test_cover.py | 191 +++++++++++++++++- tests/components/zha/zha_devices_list.py | 24 +++ 6 files changed, 335 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/zha/core/const.py b/homeassistant/components/zha/core/const.py index 6bc93354af7..8a99a8a1b11 100644 --- a/homeassistant/components/zha/core/const.py +++ b/homeassistant/components/zha/core/const.py @@ -86,6 +86,7 @@ CHANNEL_OCCUPANCY = "occupancy" CHANNEL_ON_OFF = "on_off" CHANNEL_POWER_CONFIGURATION = "power" CHANNEL_PRESSURE = "pressure" +CHANNEL_SHADE = "shade" CHANNEL_SMARTENERGY_METERING = "smartenergy_metering" CHANNEL_TEMPERATURE = "temperature" CHANNEL_THERMOSTAT = "thermostat" diff --git a/homeassistant/components/zha/core/registries.py b/homeassistant/components/zha/core/registries.py index 4b68b9675a9..3a8bcaa148a 100644 --- a/homeassistant/components/zha/core/registries.py +++ b/homeassistant/components/zha/core/registries.py @@ -102,6 +102,7 @@ DEVICE_CLASS = { zigpy.profiles.zha.DeviceType.ON_OFF_BALLAST: SWITCH, zigpy.profiles.zha.DeviceType.ON_OFF_LIGHT: LIGHT, zigpy.profiles.zha.DeviceType.ON_OFF_PLUG_IN_UNIT: SWITCH, + zigpy.profiles.zha.DeviceType.SHADE: COVER, zigpy.profiles.zha.DeviceType.SMART_PLUG: SWITCH, }, zigpy.profiles.zll.PROFILE_ID: { diff --git a/homeassistant/components/zha/cover.py b/homeassistant/components/zha/cover.py index 0feaf14b3c5..35d488f2c35 100644 --- a/homeassistant/components/zha/cover.py +++ b/homeassistant/components/zha/cover.py @@ -1,11 +1,17 @@ """Support for ZHA covers.""" -from datetime import timedelta import functools import logging +from typing import List, Optional from zigpy.zcl.foundation import Status -from homeassistant.components.cover import ATTR_POSITION, DOMAIN, CoverEntity +from homeassistant.components.cover import ( + ATTR_CURRENT_POSITION, + ATTR_POSITION, + DEVICE_CLASS_SHADE, + 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 @@ -13,17 +19,21 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect from .core import discovery from .core.const import ( CHANNEL_COVER, + CHANNEL_LEVEL, + CHANNEL_ON_OFF, + CHANNEL_SHADE, DATA_ZHA, DATA_ZHA_DISPATCHERS, SIGNAL_ADD_ENTITIES, SIGNAL_ATTR_UPDATED, + SIGNAL_SET_LEVEL, ) from .core.registries import ZHA_ENTITIES +from .core.typing import ChannelType, ZhaDeviceType from .entity import ZhaEntity _LOGGER = logging.getLogger(__name__) -SCAN_INTERVAL = timedelta(minutes=60) STRICT_MATCH = functools.partial(ZHA_ENTITIES.strict_match, DOMAIN) @@ -158,3 +168,113 @@ class ZhaCover(ZhaEntity, CoverEntity): else: self._current_position = None self._state = None + + +@STRICT_MATCH(channel_names={CHANNEL_LEVEL, CHANNEL_ON_OFF, CHANNEL_SHADE}) +class Shade(ZhaEntity, CoverEntity): + """ZHA Shade.""" + + def __init__( + self, + unique_id: str, + zha_device: ZhaDeviceType, + channels: List[ChannelType], + **kwargs, + ): + """Initialize the ZHA light.""" + super().__init__(unique_id, zha_device, channels, **kwargs) + self._on_off_channel = self.cluster_channels[CHANNEL_ON_OFF] + self._level_channel = self.cluster_channels[CHANNEL_LEVEL] + self._position = None + self._is_open = None + + @property + def current_cover_position(self): + """Return current position of cover. + + None is unknown, 0 is closed, 100 is fully open. + """ + return self._position + + @property + def device_class(self) -> Optional[str]: + """Return the class of this device, from component DEVICE_CLASSES.""" + return DEVICE_CLASS_SHADE + + @property + def is_closed(self) -> Optional[bool]: + """Return True if shade is closed.""" + if self._is_open is None: + return None + return not self._is_open + + async def async_added_to_hass(self): + """Run when about to be added to hass.""" + await super().async_added_to_hass() + await self.async_accept_signal( + self._on_off_channel, SIGNAL_ATTR_UPDATED, self.async_set_open_closed + ) + await self.async_accept_signal( + self._level_channel, SIGNAL_SET_LEVEL, self.async_set_level + ) + + @callback + def async_restore_last_state(self, last_state): + """Restore previous state.""" + self._is_open = last_state.state == STATE_OPEN + if ATTR_CURRENT_POSITION in last_state.attributes: + self._position = last_state.attributes[ATTR_CURRENT_POSITION] + + @callback + def async_set_open_closed(self, attr_id: int, attr_name: str, value: bool) -> None: + """Set open/closed state.""" + self._is_open = bool(value) + self.async_write_ha_state() + + @callback + def async_set_level(self, value: int) -> None: + """Set the reported position.""" + value = max(0, min(255, value)) + self._position = int(value * 100 / 255) + self.async_write_ha_state() + + async def async_open_cover(self, **kwargs): + """Open the window cover.""" + res = await self._on_off_channel.on() + if not isinstance(res, list) or res[1] != Status.SUCCESS: + self.debug("couldn't open cover: %s", res) + return + + self._is_open = True + self.async_write_ha_state() + + async def async_close_cover(self, **kwargs): + """Close the window cover.""" + res = await self._on_off_channel.off() + if not isinstance(res, list) or res[1] != Status.SUCCESS: + self.debug("couldn't open cover: %s", res) + return + + self._is_open = False + self.async_write_ha_state() + + async def async_set_cover_position(self, **kwargs): + """Move the roller shutter to a specific position.""" + new_pos = kwargs[ATTR_POSITION] + res = await self._level_channel.move_to_level_with_on_off( + new_pos * 255 / 100, 1 + ) + + if not isinstance(res, list) or res[1] != Status.SUCCESS: + self.debug("couldn't set cover's position: %s", res) + return + + self._position = new_pos + self.async_write_ha_state() + + async def async_stop_cover(self, **kwargs) -> None: + """Stop the cover.""" + res = await self._level_channel.stop() + if not isinstance(res, list) or res[1] != Status.SUCCESS: + self.debug("couldn't stop cover: %s", res) + return diff --git a/homeassistant/components/zha/entity.py b/homeassistant/components/zha/entity.py index fe213f7920b..8629fc50075 100644 --- a/homeassistant/components/zha/entity.py +++ b/homeassistant/components/zha/entity.py @@ -28,7 +28,7 @@ from .core.const import ( SIGNAL_REMOVE_GROUP, ) from .core.helpers import LogMixin -from .core.typing import CALLABLE_T, ChannelsType, ChannelType, ZhaDeviceType +from .core.typing import CALLABLE_T, ChannelType, ZhaDeviceType _LOGGER = logging.getLogger(__name__) @@ -150,7 +150,7 @@ class ZhaEntity(BaseZhaEntity, RestoreEntity): self, unique_id: str, zha_device: ZhaDeviceType, - channels: ChannelsType, + channels: List[ChannelType], **kwargs, ): """Init ZHA entity.""" diff --git a/tests/components/zha/test_cover.py b/tests/components/zha/test_cover.py index b4c72fd82d4..63623a5ce9e 100644 --- a/tests/components/zha/test_cover.py +++ b/tests/components/zha/test_cover.py @@ -1,11 +1,22 @@ """Test zha cover.""" +import asyncio + import pytest import zigpy.types import zigpy.zcl.clusters.closures as closures +import zigpy.zcl.clusters.general as general import zigpy.zcl.foundation as zcl_f -from homeassistant.components.cover import DOMAIN +from homeassistant.components.cover import ( + ATTR_CURRENT_POSITION, + DOMAIN, + SERVICE_CLOSE_COVER, + SERVICE_OPEN_COVER, + SERVICE_SET_COVER_POSITION, + SERVICE_STOP_COVER, +) from homeassistant.const import STATE_CLOSED, STATE_OPEN, STATE_UNAVAILABLE +from homeassistant.core import CoreState, State from .common import ( async_enable_traffic, @@ -14,8 +25,8 @@ from .common import ( send_attributes_report, ) -from tests.async_mock import MagicMock, call, patch -from tests.common import mock_coro +from tests.async_mock import AsyncMock, MagicMock, call, patch +from tests.common import mock_coro, mock_restore_cache @pytest.fixture @@ -32,6 +43,24 @@ def zigpy_cover_device(zigpy_device_mock): return zigpy_device_mock(endpoints) +@pytest.fixture +def zigpy_shade_device(zigpy_device_mock): + """Zigpy shade device.""" + + endpoints = { + 1: { + "device_type": 512, + "in_clusters": [ + closures.Shade.cluster_id, + general.LevelControl.cluster_id, + general.OnOff.cluster_id, + ], + "out_clusters": [], + } + } + return zigpy_device_mock(endpoints) + + @patch( "homeassistant.components.zha.core.channels.closures.WindowCovering.async_initialize" ) @@ -74,7 +103,7 @@ async def test_cover(m1, hass, zha_device_joined_restored, zigpy_cover_device): "zigpy.zcl.Cluster.request", return_value=mock_coro([0x1, zcl_f.Status.SUCCESS]) ): await hass.services.async_call( - DOMAIN, "close_cover", {"entity_id": entity_id}, blocking=True + DOMAIN, SERVICE_CLOSE_COVER, {"entity_id": entity_id}, blocking=True ) assert cluster.request.call_count == 1 assert cluster.request.call_args == call( @@ -86,7 +115,7 @@ async def test_cover(m1, hass, zha_device_joined_restored, zigpy_cover_device): "zigpy.zcl.Cluster.request", return_value=mock_coro([0x0, zcl_f.Status.SUCCESS]) ): await hass.services.async_call( - DOMAIN, "open_cover", {"entity_id": entity_id}, blocking=True + DOMAIN, SERVICE_OPEN_COVER, {"entity_id": entity_id}, blocking=True ) assert cluster.request.call_count == 1 assert cluster.request.call_args == call( @@ -99,7 +128,7 @@ async def test_cover(m1, hass, zha_device_joined_restored, zigpy_cover_device): ): await hass.services.async_call( DOMAIN, - "set_cover_position", + SERVICE_SET_COVER_POSITION, {"entity_id": entity_id, "position": 47}, blocking=True, ) @@ -119,7 +148,7 @@ async def test_cover(m1, hass, zha_device_joined_restored, zigpy_cover_device): "zigpy.zcl.Cluster.request", return_value=mock_coro([0x2, zcl_f.Status.SUCCESS]) ): await hass.services.async_call( - DOMAIN, "stop_cover", {"entity_id": entity_id}, blocking=True + DOMAIN, SERVICE_STOP_COVER, {"entity_id": entity_id}, blocking=True ) assert cluster.request.call_count == 1 assert cluster.request.call_args == call( @@ -129,3 +158,151 @@ async def test_cover(m1, hass, zha_device_joined_restored, zigpy_cover_device): # test rejoin await async_test_rejoin(hass, zigpy_cover_device, [cluster], (1,)) assert hass.states.get(entity_id).state == STATE_OPEN + + +async def test_shade(hass, zha_device_joined_restored, zigpy_shade_device): + """Test zha cover platform for shade device type.""" + + # load up cover domain + zha_device = await zha_device_joined_restored(zigpy_shade_device) + + cluster_on_off = zigpy_shade_device.endpoints.get(1).on_off + cluster_level = zigpy_shade_device.endpoints.get(1).level + entity_id = await find_entity_id(DOMAIN, zha_device, hass) + assert entity_id is not None + + # test that the cover was created and that it is unavailable + assert hass.states.get(entity_id).state == STATE_UNAVAILABLE + + # allow traffic to flow through the gateway and device + await async_enable_traffic(hass, [zha_device]) + await hass.async_block_till_done() + + # test that the state has changed from unavailable to off + await send_attributes_report(hass, cluster_on_off, {8: 0, 0: False, 1: 1}) + assert hass.states.get(entity_id).state == STATE_CLOSED + + # test to see if it opens + await send_attributes_report(hass, cluster_on_off, {8: 0, 0: True, 1: 1}) + assert hass.states.get(entity_id).state == STATE_OPEN + + # close from UI command fails + with patch("zigpy.zcl.Cluster.request", side_effect=asyncio.TimeoutError): + await hass.services.async_call( + DOMAIN, SERVICE_CLOSE_COVER, {"entity_id": entity_id}, blocking=True + ) + assert cluster_on_off.request.call_count == 1 + assert cluster_on_off.request.call_args[0][0] is False + assert cluster_on_off.request.call_args[0][1] == 0x0000 + assert hass.states.get(entity_id).state == STATE_OPEN + + with patch( + "zigpy.zcl.Cluster.request", AsyncMock(return_value=[0x1, zcl_f.Status.SUCCESS]) + ): + await hass.services.async_call( + DOMAIN, SERVICE_CLOSE_COVER, {"entity_id": entity_id}, blocking=True + ) + assert cluster_on_off.request.call_count == 1 + assert cluster_on_off.request.call_args[0][0] is False + assert cluster_on_off.request.call_args[0][1] == 0x0000 + assert hass.states.get(entity_id).state == STATE_CLOSED + + # open from UI command fails + assert ATTR_CURRENT_POSITION not in hass.states.get(entity_id).attributes + await send_attributes_report(hass, cluster_level, {0: 0}) + with patch("zigpy.zcl.Cluster.request", side_effect=asyncio.TimeoutError): + await hass.services.async_call( + DOMAIN, SERVICE_OPEN_COVER, {"entity_id": entity_id}, blocking=True + ) + assert cluster_on_off.request.call_count == 1 + assert cluster_on_off.request.call_args[0][0] is False + assert cluster_on_off.request.call_args[0][1] == 0x0001 + assert hass.states.get(entity_id).state == STATE_CLOSED + + # open from UI succeeds + with patch( + "zigpy.zcl.Cluster.request", AsyncMock(return_value=[0x0, zcl_f.Status.SUCCESS]) + ): + await hass.services.async_call( + DOMAIN, SERVICE_OPEN_COVER, {"entity_id": entity_id}, blocking=True + ) + assert cluster_on_off.request.call_count == 1 + assert cluster_on_off.request.call_args[0][0] is False + assert cluster_on_off.request.call_args[0][1] == 0x0001 + assert hass.states.get(entity_id).state == STATE_OPEN + + # set position UI command fails + with patch("zigpy.zcl.Cluster.request", side_effect=asyncio.TimeoutError): + await hass.services.async_call( + DOMAIN, + SERVICE_SET_COVER_POSITION, + {"entity_id": entity_id, "position": 47}, + blocking=True, + ) + assert cluster_level.request.call_count == 1 + assert cluster_level.request.call_args[0][0] is False + assert cluster_level.request.call_args[0][1] == 0x0004 + assert int(cluster_level.request.call_args[0][3] * 100 / 255) == 47 + assert hass.states.get(entity_id).attributes[ATTR_CURRENT_POSITION] == 0 + + # set position UI success + with patch( + "zigpy.zcl.Cluster.request", AsyncMock(return_value=[0x5, zcl_f.Status.SUCCESS]) + ): + await hass.services.async_call( + DOMAIN, + SERVICE_SET_COVER_POSITION, + {"entity_id": entity_id, "position": 47}, + blocking=True, + ) + assert cluster_level.request.call_count == 1 + assert cluster_level.request.call_args[0][0] is False + assert cluster_level.request.call_args[0][1] == 0x0004 + assert int(cluster_level.request.call_args[0][3] * 100 / 255) == 47 + assert hass.states.get(entity_id).attributes[ATTR_CURRENT_POSITION] == 47 + + # report position change + await send_attributes_report(hass, cluster_level, {8: 0, 0: 100, 1: 1}) + assert hass.states.get(entity_id).attributes[ATTR_CURRENT_POSITION] == int( + 100 * 100 / 255 + ) + + # test rejoin + await async_test_rejoin( + hass, zigpy_shade_device, [cluster_level, cluster_on_off], (1,) + ) + assert hass.states.get(entity_id).state == STATE_OPEN + + # test cover stop + with patch("zigpy.zcl.Cluster.request", side_effect=asyncio.TimeoutError): + await hass.services.async_call( + DOMAIN, SERVICE_STOP_COVER, {"entity_id": entity_id}, blocking=True, + ) + assert cluster_level.request.call_count == 1 + assert cluster_level.request.call_args[0][0] is False + assert cluster_level.request.call_args[0][1] in (0x0003, 0x0007) + + +async def test_restore_state(hass, zha_device_restored, zigpy_shade_device): + """Ensure states are restored on startup.""" + + mock_restore_cache( + hass, + ( + State( + "cover.fakemanufacturer_fakemodel_e769900a_level_on_off_shade", + STATE_OPEN, + {ATTR_CURRENT_POSITION: 50}, + ), + ), + ) + + hass.state = CoreState.starting + + zha_device = await zha_device_restored(zigpy_shade_device) + entity_id = await find_entity_id(DOMAIN, zha_device, hass) + assert entity_id is not None + + # test that the cover was created and that it is unavailable + assert hass.states.get(entity_id).state == STATE_OPEN + assert hass.states.get(entity_id).attributes[ATTR_CURRENT_POSITION] == 50 diff --git a/tests/components/zha/zha_devices_list.py b/tests/components/zha/zha_devices_list.py index 230cc5d2377..53e1e845d5d 100644 --- a/tests/components/zha/zha_devices_list.py +++ b/tests/components/zha/zha_devices_list.py @@ -3552,4 +3552,28 @@ DEVICES = [ "model": "Z01-A19NAE26", "node_descriptor": b"\x02@\x8c`\x11RR\x00\x00\x00R\x00\x00", }, + { + "device_no": 97, + "endpoints": { + 1: { + "device_type": 512, + "endpoint_id": 1, + "in_clusters": [0, 3, 4, 5, 6, 8, 10, 21, 256, 64544, 64545], + "out_clusters": [3, 64544], + "profile_id": 260, + } + }, + "entities": ["cover.unk_manufacturer_unk_model_77665544_level_on_off_shade"], + "entity_map": { + ("cover", "00:11:22:33:44:55:66:77-1"): { + "channels": ["level", "on_off", "shade"], + "entity_class": "Shade", + "entity_id": "cover.unk_manufacturer_unk_model_77665544_level_on_off_shade", + } + }, + "event_channels": [], + "manufacturer": "unk_manufacturer", + "model": "unk_model", + "node_descriptor": b"\x01@\x8e\x10\x11RR\x00\x00\x00R\x00\x00", + }, ] From e6065569ae1e3817b6e539c4a72be71acc92380b Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Sun, 24 May 2020 12:10:15 +0200 Subject: [PATCH 161/406] Use Coerce(float) on service options for kef (#35659) * use Coerce(float) on service options * defer the type from options list --- homeassistant/components/kef/media_player.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/kef/media_player.py b/homeassistant/components/kef/media_player.py index cf87a7dd447..1ba4d63ae4f 100644 --- a/homeassistant/components/kef/media_player.py +++ b/homeassistant/components/kef/media_player.py @@ -160,9 +160,11 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= platform.async_register_entity_service(SERVICE_UPDATE_DSP, {}, "update_dsp") def add_service(name, which, option): + options = DSP_OPTION_MAPPING[which] + dtype = type(options[0]) # int or float platform.async_register_entity_service( name, - {vol.Required(option): vol.In(DSP_OPTION_MAPPING[which])}, + {vol.Required(option): vol.All(vol.Coerce(dtype), vol.In(options))}, f"set_{which}", ) From 9212d1c2dcda64b7608fafb7fd576ca23aa5f516 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20H=C3=B8yer=20Iversen?= Date: Sun, 24 May 2020 14:28:54 +0200 Subject: [PATCH 162/406] Fix opengarage async_setup_platform (#36075) * opengarage async_setup_platform * async_add_entities --- homeassistant/components/opengarage/cover.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/opengarage/cover.py b/homeassistant/components/opengarage/cover.py index 239697a229c..70b4d8c98ee 100644 --- a/homeassistant/components/opengarage/cover.py +++ b/homeassistant/components/opengarage/cover.py @@ -55,7 +55,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( ) -def setup_platform(hass, config, add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the OpenGarage covers.""" covers = [] devices = config.get(CONF_COVERS) @@ -75,7 +75,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): covers.append(OpenGarageCover(device_config.get(CONF_NAME), open_garage)) - add_entities(covers, True) + async_add_entities(covers, True) class OpenGarageCover(CoverEntity): From fe45935f38274a3c34d6dbf5f9ab06e1d368031d Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Sun, 24 May 2020 11:10:16 -0400 Subject: [PATCH 163/406] Implement Keen vents as zha cover devices (#36080) * Implement Keen vents as cover devices * Update homeassistant/components/zha/cover.py --- .../components/zha/core/registries.py | 2 +- homeassistant/components/zha/cover.py | 30 ++++++++ tests/components/zha/test_cover.py | 69 +++++++++++++++++++ tests/components/zha/zha_devices_list.py | 24 +++---- 4 files changed, 112 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/zha/core/registries.py b/homeassistant/components/zha/core/registries.py index 3a8bcaa148a..c9b3435482b 100644 --- a/homeassistant/components/zha/core/registries.py +++ b/homeassistant/components/zha/core/registries.py @@ -98,7 +98,7 @@ DEVICE_CLASS = { zigpy.profiles.zha.DeviceType.DIMMABLE_LIGHT: LIGHT, zigpy.profiles.zha.DeviceType.DIMMABLE_PLUG_IN_UNIT: LIGHT, zigpy.profiles.zha.DeviceType.EXTENDED_COLOR_LIGHT: LIGHT, - zigpy.profiles.zha.DeviceType.LEVEL_CONTROLLABLE_OUTPUT: LIGHT, + zigpy.profiles.zha.DeviceType.LEVEL_CONTROLLABLE_OUTPUT: COVER, zigpy.profiles.zha.DeviceType.ON_OFF_BALLAST: SWITCH, zigpy.profiles.zha.DeviceType.ON_OFF_LIGHT: LIGHT, zigpy.profiles.zha.DeviceType.ON_OFF_PLUG_IN_UNIT: SWITCH, diff --git a/homeassistant/components/zha/cover.py b/homeassistant/components/zha/cover.py index 35d488f2c35..235368080f0 100644 --- a/homeassistant/components/zha/cover.py +++ b/homeassistant/components/zha/cover.py @@ -1,4 +1,5 @@ """Support for ZHA covers.""" +import asyncio import functools import logging from typing import List, Optional @@ -8,6 +9,7 @@ from zigpy.zcl.foundation import Status from homeassistant.components.cover import ( ATTR_CURRENT_POSITION, ATTR_POSITION, + DEVICE_CLASS_DAMPER, DEVICE_CLASS_SHADE, DOMAIN, CoverEntity, @@ -278,3 +280,31 @@ class Shade(ZhaEntity, CoverEntity): if not isinstance(res, list) or res[1] != Status.SUCCESS: self.debug("couldn't stop cover: %s", res) return + + +@STRICT_MATCH( + channel_names={CHANNEL_LEVEL, CHANNEL_ON_OFF}, manufacturers="Keen Home Inc" +) +class KeenVent(Shade): + """Keen vent cover.""" + + @property + def device_class(self) -> Optional[str]: + """Return the class of this device, from component DEVICE_CLASSES.""" + return DEVICE_CLASS_DAMPER + + async def async_open_cover(self, **kwargs): + """Open the cover.""" + position = self._position or 100 + tasks = [ + self._level_channel.move_to_level_with_on_off(position * 255 / 100, 1), + self._on_off_channel.on(), + ] + results = await asyncio.gather(*tasks, return_exceptions=True) + if any([isinstance(result, Exception) for result in results]): + self.debug("couldn't open cover") + return + + self._is_open = True + self._position = position + self.async_write_ha_state() diff --git a/tests/components/zha/test_cover.py b/tests/components/zha/test_cover.py index 63623a5ce9e..c3404a2bb83 100644 --- a/tests/components/zha/test_cover.py +++ b/tests/components/zha/test_cover.py @@ -61,6 +61,22 @@ def zigpy_shade_device(zigpy_device_mock): return zigpy_device_mock(endpoints) +@pytest.fixture +def zigpy_keen_vent(zigpy_device_mock): + """Zigpy Keen Vent device.""" + + endpoints = { + 1: { + "device_type": 3, + "in_clusters": [general.LevelControl.cluster_id, general.OnOff.cluster_id], + "out_clusters": [], + } + } + return zigpy_device_mock( + endpoints, manufacturer="Keen Home Inc", model="SV02-612-MP-1.3" + ) + + @patch( "homeassistant.components.zha.core.channels.closures.WindowCovering.async_initialize" ) @@ -306,3 +322,56 @@ async def test_restore_state(hass, zha_device_restored, zigpy_shade_device): # test that the cover was created and that it is unavailable assert hass.states.get(entity_id).state == STATE_OPEN assert hass.states.get(entity_id).attributes[ATTR_CURRENT_POSITION] == 50 + + +async def test_keen_vent(hass, zha_device_joined_restored, zigpy_keen_vent): + """Test keen vent.""" + + # load up cover domain + zha_device = await zha_device_joined_restored(zigpy_keen_vent) + + cluster_on_off = zigpy_keen_vent.endpoints.get(1).on_off + cluster_level = zigpy_keen_vent.endpoints.get(1).level + entity_id = await find_entity_id(DOMAIN, zha_device, hass) + assert entity_id is not None + + # test that the cover was created and that it is unavailable + assert hass.states.get(entity_id).state == STATE_UNAVAILABLE + + # allow traffic to flow through the gateway and device + await async_enable_traffic(hass, [zha_device]) + await hass.async_block_till_done() + + # test that the state has changed from unavailable to off + await send_attributes_report(hass, cluster_on_off, {8: 0, 0: False, 1: 1}) + assert hass.states.get(entity_id).state == STATE_CLOSED + + # open from UI command fails + p1 = patch.object(cluster_on_off, "request", side_effect=asyncio.TimeoutError) + p2 = patch.object(cluster_level, "request", AsyncMock(return_value=[4, 0])) + + with p1, p2: + await hass.services.async_call( + DOMAIN, SERVICE_OPEN_COVER, {"entity_id": entity_id}, blocking=True + ) + assert cluster_on_off.request.call_count == 1 + assert cluster_on_off.request.call_args[0][0] is False + assert cluster_on_off.request.call_args[0][1] == 0x0001 + assert cluster_level.request.call_count == 1 + assert hass.states.get(entity_id).state == STATE_CLOSED + + # open from UI command success + p1 = patch.object(cluster_on_off, "request", AsyncMock(return_value=[1, 0])) + p2 = patch.object(cluster_level, "request", AsyncMock(return_value=[4, 0])) + + with p1, p2: + await hass.services.async_call( + DOMAIN, SERVICE_OPEN_COVER, {"entity_id": entity_id}, blocking=True + ) + await asyncio.sleep(0) + assert cluster_on_off.request.call_count == 1 + assert cluster_on_off.request.call_args[0][0] is False + assert cluster_on_off.request.call_args[0][1] == 0x0001 + assert cluster_level.request.call_count == 1 + assert hass.states.get(entity_id).state == STATE_OPEN + assert hass.states.get(entity_id).attributes[ATTR_CURRENT_POSITION] == 100 diff --git a/tests/components/zha/zha_devices_list.py b/tests/components/zha/zha_devices_list.py index 53e1e845d5d..0b1ec9ae2c6 100644 --- a/tests/components/zha/zha_devices_list.py +++ b/tests/components/zha/zha_devices_list.py @@ -1036,16 +1036,16 @@ DEVICES = [ } }, "entities": [ - "light.keen_home_inc_sv02_610_mp_1_3_77665544_level_on_off", + "cover.keen_home_inc_sv02_610_mp_1_3_77665544_level_on_off", "sensor.keen_home_inc_sv02_610_mp_1_3_77665544_power", "sensor.keen_home_inc_sv02_610_mp_1_3_77665544_pressure", "sensor.keen_home_inc_sv02_610_mp_1_3_77665544_temperature", ], "entity_map": { - ("light", "00:11:22:33:44:55:66:77-1"): { + ("cover", "00:11:22:33:44:55:66:77-1"): { "channels": ["level", "on_off"], - "entity_class": "Light", - "entity_id": "light.keen_home_inc_sv02_610_mp_1_3_77665544_level_on_off", + "entity_class": "KeenVent", + "entity_id": "cover.keen_home_inc_sv02_610_mp_1_3_77665544_level_on_off", }, ("sensor", "00:11:22:33:44:55:66:77-1-1"): { "channels": ["power"], @@ -1094,16 +1094,16 @@ DEVICES = [ } }, "entities": [ - "light.keen_home_inc_sv02_612_mp_1_2_77665544_level_on_off", + "cover.keen_home_inc_sv02_612_mp_1_2_77665544_level_on_off", "sensor.keen_home_inc_sv02_612_mp_1_2_77665544_power", "sensor.keen_home_inc_sv02_612_mp_1_2_77665544_pressure", "sensor.keen_home_inc_sv02_612_mp_1_2_77665544_temperature", ], "entity_map": { - ("light", "00:11:22:33:44:55:66:77-1"): { + ("cover", "00:11:22:33:44:55:66:77-1"): { "channels": ["level", "on_off"], - "entity_class": "Light", - "entity_id": "light.keen_home_inc_sv02_612_mp_1_2_77665544_level_on_off", + "entity_class": "KeenVent", + "entity_id": "cover.keen_home_inc_sv02_612_mp_1_2_77665544_level_on_off", }, ("sensor", "00:11:22:33:44:55:66:77-1-1"): { "channels": ["power"], @@ -1152,16 +1152,16 @@ DEVICES = [ } }, "entities": [ - "light.keen_home_inc_sv02_612_mp_1_3_77665544_level_on_off", + "cover.keen_home_inc_sv02_612_mp_1_3_77665544_level_on_off", "sensor.keen_home_inc_sv02_612_mp_1_3_77665544_power", "sensor.keen_home_inc_sv02_612_mp_1_3_77665544_pressure", "sensor.keen_home_inc_sv02_612_mp_1_3_77665544_temperature", ], "entity_map": { - ("light", "00:11:22:33:44:55:66:77-1"): { + ("cover", "00:11:22:33:44:55:66:77-1"): { "channels": ["level", "on_off"], - "entity_class": "Light", - "entity_id": "light.keen_home_inc_sv02_612_mp_1_3_77665544_level_on_off", + "entity_class": "KeenVent", + "entity_id": "cover.keen_home_inc_sv02_612_mp_1_3_77665544_level_on_off", }, ("sensor", "00:11:22:33:44:55:66:77-1-1"): { "channels": ["power"], From ed7ac3c1f78ab17094c4228f1da1302097a56dbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20H=C3=B8yer=20Iversen?= Date: Sun, 24 May 2020 17:21:29 +0200 Subject: [PATCH 164/406] Add Open garage unique_id (#36074) --- homeassistant/components/opengarage/cover.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/opengarage/cover.py b/homeassistant/components/opengarage/cover.py index 70b4d8c98ee..cf6825c867b 100644 --- a/homeassistant/components/opengarage/cover.py +++ b/homeassistant/components/opengarage/cover.py @@ -25,6 +25,7 @@ from homeassistant.const import ( ) from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.device_registry import format_mac _LOGGER = logging.getLogger(__name__) @@ -72,8 +73,12 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= device_config[CONF_VERIFY_SSL], async_get_clientsession(hass), ) - - covers.append(OpenGarageCover(device_config.get(CONF_NAME), open_garage)) + status = await open_garage.update_state() + covers.append( + OpenGarageCover( + device_config.get(CONF_NAME), open_garage, format_mac(status["mac"]) + ) + ) async_add_entities(covers, True) @@ -81,7 +86,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= class OpenGarageCover(CoverEntity): """Representation of a OpenGarage cover.""" - def __init__(self, name, open_garage): + def __init__(self, name, open_garage, device_id): """Initialize the cover.""" self._name = name self._open_garage = open_garage @@ -89,6 +94,7 @@ class OpenGarageCover(CoverEntity): self._state_before_move = None self._device_state_attributes = {} self._available = True + self._device_id = device_id @property def name(self): @@ -181,3 +187,8 @@ class OpenGarageCover(CoverEntity): def supported_features(self): """Flag supported features.""" return SUPPORT_OPEN | SUPPORT_CLOSE + + @property + def unique_id(self): + """Return a unique ID.""" + return self._device_id From bd8848e57acd8ff6fe9016a8ebcae1366394ef7e Mon Sep 17 00:00:00 2001 From: Jason Hunter Date: Sun, 24 May 2020 15:50:50 -0400 Subject: [PATCH 165/406] Fix ONVIF config entry unique ID (#36008) * fallback to device serial number if no mac available * make password optional to fix #35904 * update tests to reflect new flow * fix snake case and AsyncMock * add comments around why weird things are being done --- homeassistant/components/onvif/base.py | 24 +++++-- homeassistant/components/onvif/camera.py | 4 +- homeassistant/components/onvif/config_flow.py | 20 ++++-- homeassistant/components/onvif/device.py | 19 +++-- homeassistant/components/onvif/event.py | 5 +- homeassistant/components/onvif/models.py | 1 + tests/components/onvif/test_config_flow.py | 70 +++++++++++++++---- 7 files changed, 112 insertions(+), 31 deletions(-) diff --git a/homeassistant/components/onvif/base.py b/homeassistant/components/onvif/base.py index 43a846cac37..cca84c4562a 100644 --- a/homeassistant/components/onvif/base.py +++ b/homeassistant/components/onvif/base.py @@ -2,6 +2,7 @@ from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from homeassistant.helpers.entity import Entity +from .const import DOMAIN from .device import ONVIFDevice from .models import Profile @@ -11,8 +12,8 @@ class ONVIFBaseEntity(Entity): def __init__(self, device: ONVIFDevice, profile: Profile = None) -> None: """Initialize the ONVIF entity.""" - self.device = device - self.profile = profile + self.device: ONVIFDevice = device + self.profile: Profile = profile @property def available(self): @@ -22,10 +23,25 @@ class ONVIFBaseEntity(Entity): @property def device_info(self): """Return a device description for device registry.""" - return { - "connections": {(CONNECTION_NETWORK_MAC, self.device.info.mac)}, + device_info = { "manufacturer": self.device.info.manufacturer, "model": self.device.info.model, "name": self.device.name, "sw_version": self.device.info.fw_version, } + + # MAC address is not always available, and given the number + # of non-conformant ONVIF devices we have historically supported, + # we can not guarantee serial number either. Due to this, we have + # adopted an either/or approach in the config entry setup, and can + # guarantee that one or the other will be populated. + # See: https://github.com/home-assistant/core/issues/35883 + if self.device.info.serial_number: + device_info["identifiers"] = {(DOMAIN, self.device.info.serial_number)} + + if self.device.info.mac: + device_info["connections"] = { + (CONNECTION_NETWORK_MAC, self.device.info.mac) + } + + return device_info diff --git a/homeassistant/components/onvif/camera.py b/homeassistant/components/onvif/camera.py index 570b99bfe3a..7f97e0fbea4 100644 --- a/homeassistant/components/onvif/camera.py +++ b/homeassistant/components/onvif/camera.py @@ -96,8 +96,8 @@ class ONVIFCameraEntity(ONVIFBaseEntity, Camera): def unique_id(self) -> str: """Return a unique ID.""" if self.profile.index: - return f"{self.device.info.mac}_{self.profile.index}" - return self.device.info.mac + return f"{self.device.info.mac or self.device.info.serial_number}_{self.profile.index}" + return self.device.info.mac or self.device.info.serial_number @property def entity_registry_enabled_default(self) -> bool: diff --git a/homeassistant/components/onvif/config_flow.py b/homeassistant/components/onvif/config_flow.py index 1dba697380d..29784b25429 100644 --- a/homeassistant/components/onvif/config_flow.py +++ b/homeassistant/components/onvif/config_flow.py @@ -169,10 +169,16 @@ class OnvifFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): self.onvif_config[CONF_PASSWORD] = user_input[CONF_PASSWORD] return await self.async_step_profiles() + # Password is optional and default empty due to some cameras not + # allowing you to change ONVIF user settings. + # See https://github.com/home-assistant/core/issues/35904 return self.async_show_form( step_id="auth", data_schema=vol.Schema( - {vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str} + { + vol.Required(CONF_USERNAME): str, + vol.Optional(CONF_PASSWORD, default=""): str, + } ), ) @@ -195,15 +201,21 @@ class OnvifFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): await device.update_xaddrs() try: + device_mgmt = device.create_devicemgmt_service() + # 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() + network_interfaces = await device_mgmt.GetNetworkInterfaces() for interface in network_interfaces: if interface.Enabled: self.device_id = interface.Info.HwAddress - if self.device_id is None: + # If no network interfaces are exposed, fallback to serial number + if not self.device_id: + device_info = await device_mgmt.GetDeviceInformation() + self.device_id = device_info.SerialNumber + + if not self.device_id: return self.async_abort(reason="no_mac") await self.async_set_unique_id(self.device_id, raise_on_progress=False) diff --git a/homeassistant/components/onvif/device.py b/homeassistant/components/onvif/device.py index 938c960080f..a28e9c4d6c2 100644 --- a/homeassistant/components/onvif/device.py +++ b/homeassistant/components/onvif/device.py @@ -148,12 +148,12 @@ class ONVIFDevice: async def async_check_date_and_time(self) -> None: """Warns if device and system date not synced.""" LOGGER.debug("Setting up the ONVIF device management service") - devicemgmt = self.device.create_devicemgmt_service() + device_mgmt = self.device.create_devicemgmt_service() LOGGER.debug("Retrieving current device date/time") try: system_date = dt_util.utcnow() - device_time = await devicemgmt.GetSystemDateAndTime() + device_time = await device_mgmt.GetSystemDateAndTime() if not device_time: LOGGER.debug( """Couldn't get device '%s' date/time. @@ -212,13 +212,22 @@ class ONVIFDevice: async def async_get_device_info(self) -> DeviceInfo: """Obtain information about this device.""" - devicemgmt = self.device.create_devicemgmt_service() - device_info = await devicemgmt.GetDeviceInformation() + device_mgmt = self.device.create_devicemgmt_service() + device_info = await device_mgmt.GetDeviceInformation() + + # Grab the last MAC address for backwards compatibility + mac = None + network_interfaces = await device_mgmt.GetNetworkInterfaces() + for interface in network_interfaces: + if interface.Enabled: + mac = interface.Info.HwAddress + return DeviceInfo( device_info.Manufacturer, device_info.Model, device_info.FirmwareVersion, - self.config_entry.unique_id, + device_info.SerialNumber, + mac, ) async def async_get_capabilities(self): diff --git a/homeassistant/components/onvif/event.py b/homeassistant/components/onvif/event.py index 183ad0ab532..7a0113177c4 100644 --- a/homeassistant/components/onvif/event.py +++ b/homeassistant/components/onvif/event.py @@ -62,7 +62,8 @@ class EventManager: @callback def async_remove_listener(self, update_callback: CALLBACK_TYPE) -> None: """Remove data update.""" - self._listeners.remove(update_callback) + if update_callback in self._listeners: + self._listeners.remove(update_callback) if not self._listeners and self._unsub_refresh: self._unsub_refresh() @@ -93,6 +94,8 @@ class EventManager: async def async_stop(self) -> None: """Unsubscribe from events.""" + self._listeners = [] + if not self._subscription: return diff --git a/homeassistant/components/onvif/models.py b/homeassistant/components/onvif/models.py index 686d9fecbda..2a129d3bc44 100644 --- a/homeassistant/components/onvif/models.py +++ b/homeassistant/components/onvif/models.py @@ -10,6 +10,7 @@ class DeviceInfo: manufacturer: str = None model: str = None fw_version: str = None + serial_number: str = None mac: str = None diff --git a/tests/components/onvif/test_config_flow.py b/tests/components/onvif/test_config_flow.py index c709c5e6f67..5c41349bbbe 100644 --- a/tests/components/onvif/test_config_flow.py +++ b/tests/components/onvif/test_config_flow.py @@ -1,13 +1,11 @@ """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.async_mock import AsyncMock, MagicMock, patch from tests.common import MockConfigEntry URN = "urn:uuid:123456789" @@ -17,6 +15,7 @@ PORT = 80 USERNAME = "admin" PASSWORD = "12345" MAC = "aa:bb:cc:dd:ee" +SERIAL_NUMBER = "ABCDEFGHIJK" DISCOVERY = [ { @@ -37,18 +36,25 @@ DISCOVERY = [ def setup_mock_onvif_camera( - mock_onvif_camera, with_h264=True, two_profiles=False, with_interfaces=True + mock_onvif_camera, + with_h264=True, + two_profiles=False, + with_interfaces=True, + with_serial=True, ): """Prepare mock onvif.ONVIFCamera.""" devicemgmt = MagicMock() + device_info = MagicMock() + device_info.SerialNumber = SERIAL_NUMBER if with_serial else None + devicemgmt.GetDeviceInformation = AsyncMock(return_value=device_info) + 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 [] + devicemgmt.GetNetworkInterfaces = AsyncMock( + return_value=[interface] if with_interfaces else [] ) media_service = MagicMock() @@ -58,11 +64,9 @@ def setup_mock_onvif_camera( 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]) + media_service.GetProfiles = AsyncMock(return_value=[profile1, profile2]) - mock_onvif_camera.update_xaddrs.return_value = Future() - mock_onvif_camera.update_xaddrs.return_value.set_result(True) + mock_onvif_camera.update_xaddrs = AsyncMock(return_value=True) mock_onvif_camera.create_devicemgmt_service = MagicMock(return_value=devicemgmt) mock_onvif_camera.create_media_service = MagicMock(return_value=media_service) @@ -116,8 +120,7 @@ def setup_mock_discovery( def setup_mock_device(mock_device): """Prepare mock ONVIFDevice.""" - mock_device.async_setup.return_value = Future() - mock_device.async_setup.return_value.set_result(True) + mock_device.async_setup = AsyncMock(return_value=True) def mock_constructor(hass, config): """Fake the controller constructor.""" @@ -390,11 +393,48 @@ async def test_flow_manual_entry(hass): async def test_flow_import_no_mac(hass): - """Test that config flow fails when no MAC available.""" + """Test that config flow uses Serial Number when no MAC available.""" + with patch( + "homeassistant.components.onvif.config_flow.get_device" + ) as mock_onvif_camera, patch( + "homeassistant.components.onvif.ONVIFDevice" + ) as mock_device: + setup_mock_onvif_camera(mock_onvif_camera, with_interfaces=False) + setup_mock_device(mock_device) + + 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, + }, + ) + + await hass.async_block_till_done() + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["title"] == f"{NAME} - {SERIAL_NUMBER}" + 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, + } + + +async def test_flow_import_no_mac_or_serial(hass): + """Test that config flow fails when no MAC or Serial Number available.""" with patch( "homeassistant.components.onvif.config_flow.get_device" ) as mock_onvif_camera: - setup_mock_onvif_camera(mock_onvif_camera, with_interfaces=False) + setup_mock_onvif_camera( + mock_onvif_camera, with_interfaces=False, with_serial=False + ) result = await hass.config_entries.flow.async_init( config_flow.DOMAIN, From cc1951c13db0eec7b4b561841f6386a806435092 Mon Sep 17 00:00:00 2001 From: mnaggatz <62095545+mnaggatz@users.noreply.github.com> Date: Mon, 25 May 2020 00:46:31 +0200 Subject: [PATCH 166/406] Use device class constants for velux cover entity (#36078) * Add support for Velux Gates * Comment expanded and listing sorted alphabetically. * There are constants in the cover entity integration for the valid device classes. Import and use those here. * Blank line deleted * Reformate code because of "CheckFormat" error * Import only the names we need instead of the whole module * Reformate code because of "CheckFormat" error * isort error --- homeassistant/components/velux/cover.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/velux/cover.py b/homeassistant/components/velux/cover.py index 1decffeb744..e8e210c1e53 100644 --- a/homeassistant/components/velux/cover.py +++ b/homeassistant/components/velux/cover.py @@ -4,6 +4,12 @@ from pyvlx.opening_device import Awning, Blind, GarageDoor, Gate, RollerShutter, from homeassistant.components.cover import ( ATTR_POSITION, + DEVICE_CLASS_AWNING, + DEVICE_CLASS_BLIND, + DEVICE_CLASS_GARAGE, + DEVICE_CLASS_GATE, + DEVICE_CLASS_SHUTTER, + DEVICE_CLASS_WINDOW, SUPPORT_CLOSE, SUPPORT_OPEN, SUPPORT_SET_POSITION, @@ -19,7 +25,6 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= """Set up cover(s) for Velux platform.""" entities = [] for node in hass.data[DATA_VELUX].pyvlx.nodes: - if isinstance(node, OpeningDevice): entities.append(VeluxCover(node)) async_add_entities(entities) @@ -75,18 +80,18 @@ class VeluxCover(CoverEntity): def device_class(self): """Define this cover as either awning, blind, garage, gate, shutter or window.""" if isinstance(self.node, Awning): - return "awning" + return DEVICE_CLASS_AWNING if isinstance(self.node, Blind): - return "blind" + return DEVICE_CLASS_BLIND if isinstance(self.node, GarageDoor): - return "garage" + return DEVICE_CLASS_GARAGE if isinstance(self.node, Gate): - return "gate" + return DEVICE_CLASS_GATE if isinstance(self.node, RollerShutter): - return "shutter" + return DEVICE_CLASS_SHUTTER if isinstance(self.node, Window): - return "window" - return "window" + return DEVICE_CLASS_WINDOW + return DEVICE_CLASS_WINDOW @property def is_closed(self): From d4f3bb8ce050b2823997996bc735bb77a4bb455d Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Mon, 25 May 2020 00:02:49 +0000 Subject: [PATCH 167/406] [ci skip] Translation update --- .../components/acmeda/translations/de.json | 11 +++++++ .../components/atag/translations/de.json | 1 + .../components/august/translations/fr.json | 2 ++ .../components/blebox/translations/de.json | 1 + .../components/blink/translations/de.json | 4 +++ .../components/bsblan/translations/de.json | 2 +- .../components/daikin/translations/de.json | 3 ++ .../forked_daapd/translations/de.json | 31 +++++++++++++++++++ .../components/gogogate2/translations/de.json | 20 ++++++++++++ .../components/icloud/translations/de.json | 1 + .../components/ipp/translations/de.json | 3 +- .../components/konnected/translations/fr.json | 10 +++++- .../components/songpal/translations/de.json | 1 + .../components/unifi/translations/fr.json | 1 + .../components/vilfo/translations/fr.json | 9 ++++++ .../components/wiffi/translations/de.json | 24 ++++++++++++++ .../components/zerproc/translations/de.json | 12 +++++++ 17 files changed, 133 insertions(+), 3 deletions(-) create mode 100644 homeassistant/components/acmeda/translations/de.json create mode 100644 homeassistant/components/forked_daapd/translations/de.json create mode 100644 homeassistant/components/gogogate2/translations/de.json create mode 100644 homeassistant/components/wiffi/translations/de.json create mode 100644 homeassistant/components/zerproc/translations/de.json diff --git a/homeassistant/components/acmeda/translations/de.json b/homeassistant/components/acmeda/translations/de.json new file mode 100644 index 00000000000..bef1c5e9e99 --- /dev/null +++ b/homeassistant/components/acmeda/translations/de.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "id": "Host-ID" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/atag/translations/de.json b/homeassistant/components/atag/translations/de.json index f9d40a035a3..cf133aef758 100644 --- a/homeassistant/components/atag/translations/de.json +++ b/homeassistant/components/atag/translations/de.json @@ -9,6 +9,7 @@ "step": { "user": { "data": { + "email": "Email (Optional)", "host": "Host", "port": "Port (10000)" }, diff --git a/homeassistant/components/august/translations/fr.json b/homeassistant/components/august/translations/fr.json index da2df2461a1..752b7dc3712 100644 --- a/homeassistant/components/august/translations/fr.json +++ b/homeassistant/components/august/translations/fr.json @@ -16,12 +16,14 @@ "timeout": "D\u00e9lai d'expiration (secondes)", "username": "Nom d'utilisateur" }, + "description": "Si la m\u00e9thode de connexion est \u00abe-mail\u00bb, le nom d'utilisateur est l'adresse e-mail. Si la m\u00e9thode de connexion est \u00abt\u00e9l\u00e9phone\u00bb, le nom d'utilisateur est le num\u00e9ro de t\u00e9l\u00e9phone au format \u00ab+ NNNNNNNNN\u00bb.", "title": "Configurer un compte August" }, "validation": { "data": { "code": "Code de v\u00e9rification" }, + "description": "Veuillez v\u00e9rifier votre {login_method} ( {username} ) et entrez le code de v\u00e9rification ci-dessous", "title": "Authentification \u00e0 deux facteurs" } } diff --git a/homeassistant/components/blebox/translations/de.json b/homeassistant/components/blebox/translations/de.json index 501b1335244..baf14ba4897 100644 --- a/homeassistant/components/blebox/translations/de.json +++ b/homeassistant/components/blebox/translations/de.json @@ -13,6 +13,7 @@ "step": { "user": { "data": { + "host": "IP Adresse", "port": "Port" }, "description": "Richten Sie Ihre BleBox f\u00fcr die Integration mit dem Home Assistant ein.", diff --git a/homeassistant/components/blink/translations/de.json b/homeassistant/components/blink/translations/de.json index 751d015ffdf..ec5ad6c53ca 100644 --- a/homeassistant/components/blink/translations/de.json +++ b/homeassistant/components/blink/translations/de.json @@ -3,6 +3,10 @@ "abort": { "already_configured": "Ger\u00e4t ist bereits konfiguriert" }, + "error": { + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "unknown": "Unerwarteter Fehler" + }, "step": { "2fa": { "data": { diff --git a/homeassistant/components/bsblan/translations/de.json b/homeassistant/components/bsblan/translations/de.json index 4515aa5f74d..39be96b84d5 100644 --- a/homeassistant/components/bsblan/translations/de.json +++ b/homeassistant/components/bsblan/translations/de.json @@ -6,7 +6,7 @@ "step": { "user": { "data": { - "host": "Server oder IP-Adresse", + "host": "Host", "port": "Port Nummer" } } diff --git a/homeassistant/components/daikin/translations/de.json b/homeassistant/components/daikin/translations/de.json index f3f55ea2ecb..98ccb6433a4 100644 --- a/homeassistant/components/daikin/translations/de.json +++ b/homeassistant/components/daikin/translations/de.json @@ -5,6 +5,9 @@ "device_fail": "Unerwarteter Fehler beim Erstellen des Ger\u00e4ts.", "device_timeout": "Zeit\u00fcberschreitung beim Verbinden mit dem Ger\u00e4t." }, + "error": { + "device_timeout": "Verbindung fehlgeschlagen" + }, "step": { "user": { "data": { diff --git a/homeassistant/components/forked_daapd/translations/de.json b/homeassistant/components/forked_daapd/translations/de.json new file mode 100644 index 00000000000..4a82bf666cd --- /dev/null +++ b/homeassistant/components/forked_daapd/translations/de.json @@ -0,0 +1,31 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, + "error": { + "unknown_error": "Unbekannter Fehler", + "wrong_host_or_port": "Verbindung konnte nicht hergestellt werden. Bitte \u00fcberpr\u00fcfen Sie Host und Port.", + "wrong_password": "Ung\u00fcltiges Passwort" + }, + "step": { + "user": { + "data": { + "host": "Host", + "password": "API-Passwort (leer lassen, wenn kein Passwort vorhanden ist)", + "port": "API Port" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "max_playlists": "Maximale Anzahl der als Quellen verwendeten Wiedergabelisten", + "tts_pause_time": "Sekunden bis zur Pause vor und nach der TTS" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gogogate2/translations/de.json b/homeassistant/components/gogogate2/translations/de.json new file mode 100644 index 00000000000..119d198615c --- /dev/null +++ b/homeassistant/components/gogogate2/translations/de.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "cannot_connect": "Verbindung fehlgeschlagen" + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen", + "invalid_auth": "Ung\u00fcltige Authentifizierung" + }, + "step": { + "user": { + "data": { + "ip_address": "IP-Adresse", + "password": "Passwort", + "username": "Benutzername" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/icloud/translations/de.json b/homeassistant/components/icloud/translations/de.json index d09889453e0..5857c3b34f0 100644 --- a/homeassistant/components/icloud/translations/de.json +++ b/homeassistant/components/icloud/translations/de.json @@ -20,6 +20,7 @@ "user": { "data": { "password": "Passwort", + "username": "Email", "with_family": "Mit Familie" }, "description": "Gib deine Zugangsdaten ein", diff --git a/homeassistant/components/ipp/translations/de.json b/homeassistant/components/ipp/translations/de.json index 3c43cd08cc5..3ee2f2159b9 100644 --- a/homeassistant/components/ipp/translations/de.json +++ b/homeassistant/components/ipp/translations/de.json @@ -6,7 +6,8 @@ "connection_upgrade": "Verbindung zum Drucker fehlgeschlagen, da ein Verbindungsupgrade erforderlich ist.", "ipp_error": "IPP-Fehler festgestellt.", "ipp_version_error": "IPP-Version wird vom Drucker nicht unterst\u00fctzt.", - "parse_error": "Antwort vom Drucker konnte nicht analysiert werden." + "parse_error": "Antwort vom Drucker konnte nicht analysiert werden.", + "unique_id_required": "Ger\u00e4t fehlt die f\u00fcr die Entdeckung erforderliche eindeutige Identifizierung." }, "error": { "connection_error": "Verbindung zum Drucker fehlgeschlagen.", diff --git a/homeassistant/components/konnected/translations/fr.json b/homeassistant/components/konnected/translations/fr.json index a463803a8fe..23330311f74 100644 --- a/homeassistant/components/konnected/translations/fr.json +++ b/homeassistant/components/konnected/translations/fr.json @@ -15,6 +15,7 @@ "title": "Appareil Konnected pr\u00eat" }, "import_confirm": { + "description": "Un panneau d'alarme Konnected avec l'ID {id} a \u00e9t\u00e9 d\u00e9couvert dans configuration.yaml. Ce flux vous permettra de l'importer dans une entr\u00e9e de configuration.", "title": "Importer un appareil connect\u00e9" }, "user": { @@ -78,9 +79,14 @@ "alarm2_out2": "OUT2/ALARM2", "out1": "OUT1" }, + "description": "S\u00e9lectionnez la configuration des E / S restantes ci-dessous. Vous pourrez configurer des options d\u00e9taill\u00e9es dans les \u00e9tapes suivantes.", "title": "Configurer les E/S \u00e9tendues" }, "options_misc": { + "data": { + "blink": "Voyant du panneau clignotant lors de l'envoi d'un changement d'\u00e9tat" + }, + "description": "Veuillez s\u00e9lectionner le comportement souhat\u00e9 de votre panneau", "title": "Configurer divers" }, "options_switch": { @@ -91,7 +97,9 @@ "name": "Nom (facultatif)", "pause": "Pause entre les impulsions (ms) (facultatif)", "repeat": "Nombre de r\u00e9p\u00e9tition (-1=infini) (facultatif)" - } + }, + "description": "Veuillez s\u00e9lectionner les options de sortie pour {zone} : \u00e9tat {state}", + "title": "Configurer la sortie commutable" } } } diff --git a/homeassistant/components/songpal/translations/de.json b/homeassistant/components/songpal/translations/de.json index b351dab97db..4786dde4ca0 100644 --- a/homeassistant/components/songpal/translations/de.json +++ b/homeassistant/components/songpal/translations/de.json @@ -5,6 +5,7 @@ "not_songpal_device": "Kein Songpal-Ger\u00e4t" }, "error": { + "cannot_connect": "Verbindung fehlgeschlagen", "connection": "Verbindungsfehler: Bitte \u00fcberpr\u00fcfen Sie Ihren Endpunkt" }, "flow_title": "Sony Songpal {name} ({host})", diff --git a/homeassistant/components/unifi/translations/fr.json b/homeassistant/components/unifi/translations/fr.json index 8e2a8be41b1..a655ed46e4d 100644 --- a/homeassistant/components/unifi/translations/fr.json +++ b/homeassistant/components/unifi/translations/fr.json @@ -36,6 +36,7 @@ "device_tracker": { "data": { "detection_time": "Temps en secondes depuis la derni\u00e8re vue avant de consid\u00e9rer comme absent", + "ssid_filter": "S\u00e9lectionnez les SSID pour suivre les clients sans fil", "track_clients": "Suivre les clients du r\u00e9seau", "track_devices": "Suivre les p\u00e9riph\u00e9riques r\u00e9seau (p\u00e9riph\u00e9riques Ubiquiti)", "track_wired_clients": "Inclure les clients du r\u00e9seau filaire" diff --git a/homeassistant/components/vilfo/translations/fr.json b/homeassistant/components/vilfo/translations/fr.json index 867b78ac411..1a587121b55 100644 --- a/homeassistant/components/vilfo/translations/fr.json +++ b/homeassistant/components/vilfo/translations/fr.json @@ -1,8 +1,17 @@ { "config": { + "abort": { + "already_configured": "Ce routeur Vilfo est d\u00e9j\u00e0 configur\u00e9." + }, + "error": { + "cannot_connect": "\u00c9chec de connexion. Veuillez v\u00e9rifier les informations que vous avez fournies et r\u00e9essayer.", + "invalid_auth": "Authentification non valide. Veuillez v\u00e9rifier le jeton d'acc\u00e8s et r\u00e9essayer.", + "unknown": "Une erreur inattendue s'est produite lors de la configuration de l'int\u00e9gration." + }, "step": { "user": { "data": { + "access_token": "Jeton d'Acc\u00e8s", "host": "Nom d'h\u00f4te ou IP du routeur" }, "title": "Connectez-vous au routeur Vilfo" diff --git a/homeassistant/components/wiffi/translations/de.json b/homeassistant/components/wiffi/translations/de.json new file mode 100644 index 00000000000..79bf8168a14 --- /dev/null +++ b/homeassistant/components/wiffi/translations/de.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "addr_in_use": "Server Port wird bereits genutzt", + "start_server_failed": "Server starten fehlgeschlagen" + }, + "step": { + "user": { + "data": { + "port": "Server Port" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "timeout": "Zeit\u00fcberschreitung (Minuten)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zerproc/translations/de.json b/homeassistant/components/zerproc/translations/de.json new file mode 100644 index 00000000000..fdbf8971238 --- /dev/null +++ b/homeassistant/components/zerproc/translations/de.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "no_devices_found": "Keine Ger\u00e4te im Netzwerk gefunden" + }, + "step": { + "confirm": { + "description": "M\u00f6chtest du die Installation starten?" + } + } + } +} \ No newline at end of file From 59fe5458d0466c4d6de8ea7f94e6a668690c8f8f Mon Sep 17 00:00:00 2001 From: Julius Mittenzwei Date: Mon, 25 May 2020 08:31:49 +0200 Subject: [PATCH 168/406] Bump pyvlx to 0.2.16 (#35971) --- homeassistant/components/velux/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/velux/manifest.json b/homeassistant/components/velux/manifest.json index e6747060da6..73306bca7b5 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.14"], + "requirements": ["pyvlx==0.2.16"], "codeowners": ["@Julius2342"] } diff --git a/requirements_all.txt b/requirements_all.txt index a00aeeaac73..b39e79475a4 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1802,7 +1802,7 @@ pyvesync==1.1.0 pyvizio==0.1.47 # homeassistant.components.velux -pyvlx==0.2.14 +pyvlx==0.2.16 # homeassistant.components.html5 pywebpush==1.9.2 From 12fb6a85d51f32123bc53ad0f329e191f8c14ede Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20H=C3=B8yer=20Iversen?= Date: Mon, 25 May 2020 12:26:03 +0200 Subject: [PATCH 169/406] Set PARALLEL_UPDATES for Tibber (#35915) --- homeassistant/components/tibber/sensor.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/tibber/sensor.py b/homeassistant/components/tibber/sensor.py index 7fc8820e92d..8dd5c507d7d 100644 --- a/homeassistant/components/tibber/sensor.py +++ b/homeassistant/components/tibber/sensor.py @@ -18,6 +18,7 @@ ICON = "mdi:currency-usd" ICON_RT = "mdi:power-plug" SCAN_INTERVAL = timedelta(minutes=1) MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=5) +PARALLEL_UPDATES = 0 async def async_setup_entry(hass, entry, async_add_entities): From b0012bd5a6317280a6a09a99b7a95263db0e5838 Mon Sep 17 00:00:00 2001 From: Jason Hunter Date: Mon, 25 May 2020 07:37:47 -0400 Subject: [PATCH 170/406] guard against missing topic (#36108) --- homeassistant/components/onvif/event.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/homeassistant/components/onvif/event.py b/homeassistant/components/onvif/event.py index 7a0113177c4..3888db4fa8e 100644 --- a/homeassistant/components/onvif/event.py +++ b/homeassistant/components/onvif/event.py @@ -147,6 +147,10 @@ class EventManager: async def async_parse_messages(self, messages) -> None: """Parse notification message.""" for msg in messages: + # Guard against empty message + if not msg.Topic: + continue + topic = msg.Topic._value_1 parser = PARSERS.get(topic) if not parser: From 5dfae0eb7c2f5c2729cff41d07e5ff634e32a6f2 Mon Sep 17 00:00:00 2001 From: Jason Hunter Date: Mon, 25 May 2020 07:38:57 -0400 Subject: [PATCH 171/406] fix preset warning (#36110) --- homeassistant/components/onvif/device.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/onvif/device.py b/homeassistant/components/onvif/device.py index a28e9c4d6c2..e430eed9f73 100644 --- a/homeassistant/components/onvif/device.py +++ b/homeassistant/components/onvif/device.py @@ -424,7 +424,7 @@ class ONVIFDevice: "PTZ preset '%s' does not exist on device '%s'. Available Presets: %s", preset_val, self.name, - profile.ptz.presets.join(", "), + ", ".join(profile.ptz.presets), ) return From 22a2c386e9552271e11e467c1aedcc288ef64670 Mon Sep 17 00:00:00 2001 From: Chris Talkington Date: Mon, 25 May 2020 06:40:16 -0500 Subject: [PATCH 172/406] Update rokuecp to 0.4.2 (#36102) * update rokuecp to 0.4.2 * Update requirements_all.txt * Update requirements_test_all.txt --- homeassistant/components/roku/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/roku/manifest.json b/homeassistant/components/roku/manifest.json index 57c64f4c64a..276fe2332f5 100644 --- a/homeassistant/components/roku/manifest.json +++ b/homeassistant/components/roku/manifest.json @@ -2,7 +2,7 @@ "domain": "roku", "name": "Roku", "documentation": "https://www.home-assistant.io/integrations/roku", - "requirements": ["rokuecp==0.4.1"], + "requirements": ["rokuecp==0.4.2"], "ssdp": [ { "st": "roku:ecp", diff --git a/requirements_all.txt b/requirements_all.txt index b39e79475a4..00b38b49cf2 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1871,7 +1871,7 @@ rjpl==0.3.5 rocketchat-API==0.6.1 # homeassistant.components.roku -rokuecp==0.4.1 +rokuecp==0.4.2 # homeassistant.components.roomba roombapy==1.6.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index b45b4b3f261..eb0f25d0a37 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -768,7 +768,7 @@ rflink==0.0.52 ring_doorbell==0.6.0 # homeassistant.components.roku -rokuecp==0.4.1 +rokuecp==0.4.2 # homeassistant.components.roomba roombapy==1.6.1 From 77eab66e0f32a3466ded33f079a476033d105cc7 Mon Sep 17 00:00:00 2001 From: Chris Talkington Date: Mon, 25 May 2020 06:55:25 -0500 Subject: [PATCH 173/406] Fix roku play/pause during standby (#36096) --- homeassistant/components/roku/media_player.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/roku/media_player.py b/homeassistant/components/roku/media_player.py index 8c92eff3687..0deeb44dbc2 100644 --- a/homeassistant/components/roku/media_player.py +++ b/homeassistant/components/roku/media_player.py @@ -171,15 +171,18 @@ class RokuMediaPlayer(RokuEntity, MediaPlayerEntity): async def async_media_pause(self) -> None: """Send pause command.""" - await self.coordinator.roku.remote("play") + if self.state != STATE_STANDBY: + await self.coordinator.roku.remote("play") async def async_media_play(self) -> None: """Send play command.""" - await self.coordinator.roku.remote("play") + if self.state != STATE_STANDBY: + await self.coordinator.roku.remote("play") async def async_media_play_pause(self) -> None: """Send play/pause command.""" - await self.coordinator.roku.remote("play") + if self.state != STATE_STANDBY: + await self.coordinator.roku.remote("play") async def async_media_previous_track(self) -> None: """Send previous track command.""" From 2375e0002913d10dea9ba465ea035bd16c45cd60 Mon Sep 17 00:00:00 2001 From: Minims Date: Mon, 25 May 2020 14:02:21 +0200 Subject: [PATCH 174/406] Fix onvif snapshot for Sricam SP009 (#36095) --- homeassistant/components/onvif/device.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/onvif/device.py b/homeassistant/components/onvif/device.py index e430eed9f73..0e9d3ddca98 100644 --- a/homeassistant/components/onvif/device.py +++ b/homeassistant/components/onvif/device.py @@ -237,7 +237,7 @@ class ONVIFDevice: media_service = self.device.create_media_service() media_capabilities = await media_service.GetServiceCapabilities() snapshot = media_capabilities and media_capabilities.SnapshotUri - except (ONVIFError, Fault): + except (ONVIFError, Fault, ServerDisconnectedError): pass pullpoint = False From 71ebf8738f2da9fd27ddd3661815066b778f6260 Mon Sep 17 00:00:00 2001 From: isk0001y Date: Mon, 25 May 2020 14:29:26 +0200 Subject: [PATCH 175/406] Re-read last imap_email_content email when no change (#36065) --- homeassistant/components/imap_email_content/sensor.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/homeassistant/components/imap_email_content/sensor.py b/homeassistant/components/imap_email_content/sensor.py index ea93c2bb975..21b535450a1 100644 --- a/homeassistant/components/imap_email_content/sensor.py +++ b/homeassistant/components/imap_email_content/sensor.py @@ -103,6 +103,8 @@ class EmailReader: if message_data is None: return None + if message_data[0] is None: + return None raw_email = message_data[0][1] email_message = email.message_from_bytes(raw_email) return email_message @@ -126,13 +128,22 @@ class EmailReader: self._last_id = int(message_uid) return self._fetch_message(message_uid) + return self._fetch_message(str(self._last_id)) + except imaplib.IMAP4.error: _LOGGER.info("Connection to %s lost, attempting to reconnect", self._server) try: self.connect() + _LOGGER.info( + "Reconnect to %s succeeded, trying last message", self._server + ) + if self._last_id is not None: + return self._fetch_message(str(self._last_id)) except imaplib.IMAP4.error: _LOGGER.error("Failed to reconnect") + return None + class EmailContentSensor(Entity): """Representation of an EMail sensor.""" From 1f3d3c3e5bd948fa19ba32d3294c57e65e8eaf69 Mon Sep 17 00:00:00 2001 From: braam <44501894+braam@users.noreply.github.com> Date: Mon, 25 May 2020 15:51:41 +0200 Subject: [PATCH 176/406] Add Unify Circuit (#35756) --- .coveragerc | 1 + CODEOWNERS | 1 + homeassistant/components/circuit/__init__.py | 38 +++++++++++++++++++ .../components/circuit/manifest.json | 7 ++++ homeassistant/components/circuit/notify.py | 37 ++++++++++++++++++ requirements_all.txt | 3 ++ 6 files changed, 87 insertions(+) create mode 100644 homeassistant/components/circuit/__init__.py create mode 100644 homeassistant/components/circuit/manifest.json create mode 100644 homeassistant/components/circuit/notify.py diff --git a/.coveragerc b/.coveragerc index 7f0f519133f..92c6cdbcfbc 100644 --- a/.coveragerc +++ b/.coveragerc @@ -122,6 +122,7 @@ omit = homeassistant/components/cast/* homeassistant/components/cert_expiry/helper.py homeassistant/components/channels/* + homeassistant/components/circuit/* homeassistant/components/cisco_ios/device_tracker.py homeassistant/components/cisco_mobility_express/device_tracker.py homeassistant/components/cisco_webex_teams/notify.py diff --git a/CODEOWNERS b/CODEOWNERS index 2d7805d676a..a8aea5f4727 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -68,6 +68,7 @@ homeassistant/components/bt_smarthub/* @jxwolstenholme homeassistant/components/buienradar/* @mjj4791 @ties homeassistant/components/cast/* @emontnemery homeassistant/components/cert_expiry/* @Cereal2nd @jjlawren +homeassistant/components/circuit/* @braam homeassistant/components/cisco_ios/* @fbradyirl homeassistant/components/cisco_mobility_express/* @fbradyirl homeassistant/components/cisco_webex_teams/* @fbradyirl diff --git a/homeassistant/components/circuit/__init__.py b/homeassistant/components/circuit/__init__.py new file mode 100644 index 00000000000..3d97e110475 --- /dev/null +++ b/homeassistant/components/circuit/__init__.py @@ -0,0 +1,38 @@ +"""The Unify Circuit component.""" + +import logging + +import voluptuous as vol + +from homeassistant.const import CONF_NAME, CONF_URL +from homeassistant.helpers import config_validation as cv, discovery + +_LOGGER = logging.getLogger(__name__) + +DOMAIN = "circuit" +CONF_WEBHOOK = "webhook" + +WEBHOOK_SCHEMA = vol.Schema( + {vol.Optional(CONF_NAME): cv.string, vol.Required(CONF_URL): cv.string} +) + +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + {vol.Required(CONF_WEBHOOK): vol.All(cv.ensure_list, [WEBHOOK_SCHEMA])} + ) + }, + extra=vol.ALLOW_EXTRA, +) + + +async def async_setup(hass, config): + """Set up the Unify Circuit component.""" + webhooks = config[DOMAIN][CONF_WEBHOOK] + + for webhook_conf in webhooks: + hass.async_create_task( + discovery.async_load_platform(hass, "notify", DOMAIN, webhook_conf, config) + ) + + return True diff --git a/homeassistant/components/circuit/manifest.json b/homeassistant/components/circuit/manifest.json new file mode 100644 index 00000000000..d6c43e18677 --- /dev/null +++ b/homeassistant/components/circuit/manifest.json @@ -0,0 +1,7 @@ +{ + "domain": "circuit", + "name": "Unify Circuit", + "documentation": "https://www.home-assistant.io/integrations/circuit", + "codeowners": ["@braam"], + "requirements": ["circuit-webhook==1.0.1"] +} diff --git a/homeassistant/components/circuit/notify.py b/homeassistant/components/circuit/notify.py new file mode 100644 index 00000000000..634ecb4f859 --- /dev/null +++ b/homeassistant/components/circuit/notify.py @@ -0,0 +1,37 @@ +"""Unify Circuit platform for notify component.""" +import logging + +from circuit_webhook import Circuit + +from homeassistant.components.notify import BaseNotificationService +from homeassistant.const import CONF_URL + +_LOGGER = logging.getLogger(__name__) + + +def get_service(hass, config, discovery_info=None): + """Get the Unify Circuit notification service.""" + if discovery_info is None: + return None + + return CircuitNotificationService(discovery_info) + + +class CircuitNotificationService(BaseNotificationService): + """Implement the notification service for Unify Circuit.""" + + def __init__(self, config): + """Initialize the service.""" + self.webhook_url = config[CONF_URL] + + def send_message(self, message=None, **kwargs): + """Send a message to the webhook.""" + + webhook_url = self.webhook_url + + if webhook_url and message: + try: + circuit_message = Circuit(url=webhook_url) + circuit_message.post(text=message) + except RuntimeError as err: + _LOGGER.error("Could not send notification. Error: %s", err) diff --git a/requirements_all.txt b/requirements_all.txt index 00b38b49cf2..95a649b2d85 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -403,6 +403,9 @@ buienradar==1.0.4 # homeassistant.components.caldav caldav==0.6.1 +# homeassistant.components.circuit +circuit-webhook==1.0.1 + # homeassistant.components.cisco_mobility_express ciscomobilityexpress==0.3.3 From 73fa617c1daeaaec5a0dcc1c8b8a974f03ce1a19 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 25 May 2020 10:51:46 -0500 Subject: [PATCH 177/406] Remove unsupported stop feature with Hunter Douglas Powerview 1.0 Hubs (#36129) --- homeassistant/components/hunterdouglas_powerview/cover.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/hunterdouglas_powerview/cover.py b/homeassistant/components/hunterdouglas_powerview/cover.py index 8135b4a8c77..ea56d56352a 100644 --- a/homeassistant/components/hunterdouglas_powerview/cover.py +++ b/homeassistant/components/hunterdouglas_powerview/cover.py @@ -28,6 +28,7 @@ from .const import ( DEVICE_INFO, DEVICE_MODEL, DOMAIN, + LEGACY_DEVICE_MODEL, PV_API, PV_ROOM_DATA, PV_SHADE_DATA, @@ -118,7 +119,7 @@ class PowerViewShade(ShadeEntity, CoverEntity): def supported_features(self): """Flag supported features.""" supported_features = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION - if self._device_info[DEVICE_MODEL] != "1": + if self._device_info[DEVICE_MODEL] != LEGACY_DEVICE_MODEL: supported_features |= SUPPORT_STOP return supported_features From 4313d4b26ba64ffd5f15e3e2a85d38c0b9b4e551 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 25 May 2020 11:17:30 -0500 Subject: [PATCH 178/406] Ensure homekit bridge state is restored before creating devices (#36098) * Ensure homekit bridge state is restored before creating devices * Tests to ensure homekit device registry entry is stable * remove stray continue --- homeassistant/components/homekit/__init__.py | 44 ++++++++++++++++++-- tests/components/homekit/test_homekit.py | 41 +++++++++++++++++- 2 files changed, 81 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index adbf79128e3..428f8e30abf 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -2,6 +2,7 @@ import asyncio import ipaddress import logging +import os from aiohttp import web import voluptuous as vol @@ -49,6 +50,7 @@ from .const import ( ATTR_SOFTWARE_VERSION, ATTR_VALUE, BRIDGE_NAME, + BRIDGE_SERIAL_NUMBER, CONF_ADVERTISE_IP, CONF_AUTO_START, CONF_ENTITY_CONFIG, @@ -434,6 +436,13 @@ class HomeKit: interface_choice=self._interface_choice, ) + # If we do not load the mac address will be wrong + # as pyhap uses a random one until state is restored + if os.path.exists(persist_file): + self.driver.load() + else: + self.driver.persist() + self.bridge = HomeBridge(self.hass, self.driver, self._name) if self._safe_mode: _LOGGER.debug("Safe_mode selected for %s", self._name) @@ -540,16 +549,45 @@ class HomeKit: @callback def _async_register_bridge(self, dev_reg): """Register the bridge as a device so homekit_controller and exclude it from discovery.""" + formatted_mac = device_registry.format_mac(self.driver.state.mac) + # Connections and identifiers are both used here. + # + # connections exists so homekit_controller can know the + # virtual mac address of the bridge and know to not offer + # it via discovery. + # + # identifiers is used as well since the virtual mac may change + # because it will not survive manual pairing resets (deleting state file) + # which we have trained users to do over the past few years + # because this was the way you had to fix homekit when pairing + # failed. + # + connection = (device_registry.CONNECTION_NETWORK_MAC, formatted_mac) + identifier = (DOMAIN, self._entry_id, BRIDGE_SERIAL_NUMBER) + self._async_purge_old_bridges(dev_reg, identifier, connection) dev_reg.async_get_or_create( config_entry_id=self._entry_id, - connections={ - (device_registry.CONNECTION_NETWORK_MAC, self.driver.state.mac) - }, + identifiers={identifier}, + connections={connection}, manufacturer=MANUFACTURER, name=self._name, model="Home Assistant HomeKit Bridge", ) + @callback + def _async_purge_old_bridges(self, dev_reg, identifier, connection): + """Purge bridges that exist from failed pairing or manual resets.""" + devices_to_purge = [] + for entry in dev_reg.devices.values(): + if self._entry_id in entry.config_entries and ( + identifier not in entry.identifiers + or connection not in entry.connections + ): + devices_to_purge.append(entry.id) + + for device_id in devices_to_purge: + dev_reg.async_remove_device(device_id) + def _start(self, bridged_states): from . import ( # noqa: F401 pylint: disable=unused-import, import-outside-toplevel type_cameras, diff --git a/tests/components/homekit/test_homekit.py b/tests/components/homekit/test_homekit.py index c0e2ea90fba..b016997b7c9 100644 --- a/tests/components/homekit/test_homekit.py +++ b/tests/components/homekit/test_homekit.py @@ -19,6 +19,7 @@ from homeassistant.components.homekit.accessories import HomeBridge from homeassistant.components.homekit.const import ( AID_STORAGE, BRIDGE_NAME, + BRIDGE_SERIAL_NUMBER, CONF_AUTO_START, CONF_ENTRY_INDEX, CONF_SAFE_MODE, @@ -458,7 +459,7 @@ async def test_homekit_entity_filter(hass): assert mock_get_acc.called is False -async def test_homekit_start(hass, hk_driver, debounce_patcher): +async def test_homekit_start(hass, hk_driver, device_reg, debounce_patcher): """Test HomeKit start method.""" entry = await async_init_integration(hass) @@ -480,6 +481,15 @@ async def test_homekit_start(hass, hk_driver, debounce_patcher): homekit.driver = hk_driver homekit._filter = Mock(return_value=True) + connection = (device_registry.CONNECTION_NETWORK_MAC, "AA:BB:CC:DD:EE:FF") + bridge_with_wrong_mac = device_reg.async_get_or_create( + config_entry_id=entry.entry_id, + connections={connection}, + manufacturer="Any", + name="Any", + model="Home Assistant HomeKit Bridge", + ) + hass.states.async_set("light.demo", "on") state = hass.states.async_all()[0] @@ -505,6 +515,35 @@ async def test_homekit_start(hass, hk_driver, debounce_patcher): await hass.async_block_till_done() assert not hk_driver_start.called + assert device_reg.async_get(bridge_with_wrong_mac.id) is None + + device = device_reg.async_get_device( + {(DOMAIN, entry.entry_id, BRIDGE_SERIAL_NUMBER)}, {} + ) + assert device + formatted_mac = device_registry.format_mac(homekit.driver.state.mac) + assert (device_registry.CONNECTION_NETWORK_MAC, formatted_mac) in device.connections + + # Start again to make sure the registry entry is kept + homekit.status = STATUS_READY + with patch(f"{PATH_HOMEKIT}.HomeKit.add_bridge_accessory") as mock_add_acc, patch( + f"{PATH_HOMEKIT}.show_setup_message" + ) as mock_setup_msg, patch( + "pyhap.accessory_driver.AccessoryDriver.add_accessory" + ) as hk_driver_add_acc, patch( + "pyhap.accessory_driver.AccessoryDriver.start" + ) as hk_driver_start: + await homekit.async_start() + + device = device_reg.async_get_device( + {(DOMAIN, entry.entry_id, BRIDGE_SERIAL_NUMBER)}, {} + ) + assert device + formatted_mac = device_registry.format_mac(homekit.driver.state.mac) + assert (device_registry.CONNECTION_NETWORK_MAC, formatted_mac) in device.connections + + assert len(device_reg.devices) == 1 + async def test_homekit_start_with_a_broken_accessory(hass, hk_driver, debounce_patcher): """Test HomeKit start method.""" From ba120d422097ed9ff9d65fbfb74f604acf0177c4 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 25 May 2020 12:39:24 -0700 Subject: [PATCH 179/406] Fix client ID lookup for official apps (#36131) --- homeassistant/components/auth/indieauth.py | 4 ++-- tests/components/auth/test_indieauth.py | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/auth/indieauth.py b/homeassistant/components/auth/indieauth.py index cd8e797876f..0d942bd358d 100644 --- a/homeassistant/components/auth/indieauth.py +++ b/homeassistant/components/auth/indieauth.py @@ -33,8 +33,8 @@ async def verify_redirect_uri(hass, client_id, redirect_uri): # Whitelist the iOS and Android callbacks so that people can link apps # without being connected to the internet. if redirect_uri == "homeassistant://auth-callback" and client_id in ( - "https://www.home-assistant.io/android", - "https://www.home-assistant.io/iOS", + "https://home-assistant.io/android", + "https://home-assistant.io/iOS", ): return True diff --git a/tests/components/auth/test_indieauth.py b/tests/components/auth/test_indieauth.py index 3d1ba068c85..8a02502e16c 100644 --- a/tests/components/auth/test_indieauth.py +++ b/tests/components/auth/test_indieauth.py @@ -166,8 +166,7 @@ async def test_find_link_tag_max_size(hass, mock_session): @pytest.mark.parametrize( - "client_id", - ["https://www.home-assistant.io/android", "https://www.home-assistant.io/iOS"], + "client_id", ["https://home-assistant.io/android", "https://home-assistant.io/iOS"], ) async def test_verify_redirect_uri_android_ios(client_id): """Test that we verify redirect uri correctly for Android/iOS.""" From d2a92ce4f36f1803f597cd684dbc1fa4e1cc5a80 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 25 May 2020 14:40:06 -0500 Subject: [PATCH 180/406] Ensure a deleted integration can be removed (#36130) * Ensure a deleted intergration can be removed * Update homeassistant/config_entries.py Co-authored-by: Paulus Schoutsen Co-authored-by: Paulus Schoutsen --- homeassistant/config_entries.py | 20 ++++++++++++++++++-- tests/test_config_entries.py | 22 ++++++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index 6dc259d6515..8dc88aa4da9 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -268,7 +268,15 @@ class ConfigEntry: return True if integration is None: - integration = await loader.async_get_integration(hass, self.domain) + try: + integration = await loader.async_get_integration(hass, self.domain) + except loader.IntegrationNotFound: + # The integration was likely a custom_component + # that was uninstalled, or an integration + # that has been renamed without removing the config + # entry. + self.state = ENTRY_STATE_NOT_LOADED + return True component = integration.get_component() @@ -316,7 +324,15 @@ class ConfigEntry: if self.source == SOURCE_IGNORE: return - integration = await loader.async_get_integration(hass, self.domain) + try: + integration = await loader.async_get_integration(hass, self.domain) + except loader.IntegrationNotFound: + # The integration was likely a custom_component + # that was uninstalled, or an integration + # that has been renamed without removing the config + # entry. + return + component = integration.get_component() if not hasattr(component, "async_remove_entry"): return diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py index 592bc1d4656..12b9c7308aa 100644 --- a/tests/test_config_entries.py +++ b/tests/test_config_entries.py @@ -365,6 +365,28 @@ async def test_remove_entry_if_not_loaded(hass, manager): assert len(mock_unload_entry.mock_calls) == 0 +async def test_remove_entry_if_integration_deleted(hass, manager): + """Test that we can remove an entry when the integration is deleted.""" + mock_unload_entry = AsyncMock(return_value=True) + + MockConfigEntry(domain="test", entry_id="test1").add_to_manager(manager) + MockConfigEntry(domain="comp", entry_id="test2").add_to_manager(manager) + MockConfigEntry(domain="test", entry_id="test3").add_to_manager(manager) + + assert [item.entry_id for item in manager.async_entries()] == [ + "test1", + "test2", + "test3", + ] + + result = await manager.async_remove("test2") + + assert result == {"require_restart": False} + assert [item.entry_id for item in manager.async_entries()] == ["test1", "test3"] + + assert len(mock_unload_entry.mock_calls) == 0 + + async def test_add_entry_calls_setup_entry(hass, manager): """Test we call setup_config_entry.""" mock_setup_entry = AsyncMock(return_value=True) From a22a86e4d2f36c5225e46b907070bec5c9eeecd9 Mon Sep 17 00:00:00 2001 From: gadgetmobile <57815233+gadgetmobile@users.noreply.github.com> Date: Mon, 25 May 2020 21:45:01 +0200 Subject: [PATCH 181/406] Add Blebox climate support (#35373) * support BleBox climate * refactor entity async_setup_entry functions * use constants and simplify hvac mode setting * apply fixes from review requests in #35370 * remove unneeded const mappings --- homeassistant/components/blebox/__init__.py | 2 +- homeassistant/components/blebox/climate.py | 91 +++++++ tests/components/blebox/test_climate.py | 257 ++++++++++++++++++++ 3 files changed, 349 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/blebox/climate.py create mode 100644 tests/components/blebox/test_climate.py diff --git a/homeassistant/components/blebox/__init__.py b/homeassistant/components/blebox/__init__.py index 9245392f89a..3d3c997596a 100644 --- a/homeassistant/components/blebox/__init__.py +++ b/homeassistant/components/blebox/__init__.py @@ -17,7 +17,7 @@ from .const import DEFAULT_SETUP_TIMEOUT, DOMAIN, PRODUCT _LOGGER = logging.getLogger(__name__) -PLATFORMS = ["cover", "sensor", "switch", "air_quality", "light"] +PLATFORMS = ["cover", "sensor", "switch", "air_quality", "light", "climate"] PARALLEL_UPDATES = 0 diff --git a/homeassistant/components/blebox/climate.py b/homeassistant/components/blebox/climate.py new file mode 100644 index 00000000000..3f1b21ff687 --- /dev/null +++ b/homeassistant/components/blebox/climate.py @@ -0,0 +1,91 @@ +"""BleBox climate entity.""" + +from homeassistant.components.climate import ClimateDevice +from homeassistant.components.climate.const import ( + CURRENT_HVAC_HEAT, + CURRENT_HVAC_IDLE, + CURRENT_HVAC_OFF, + HVAC_MODE_HEAT, + HVAC_MODE_OFF, + SUPPORT_TARGET_TEMPERATURE, +) +from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS + +from . import BleBoxEntity, create_blebox_entities + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up a BleBox climate entity.""" + + create_blebox_entities( + hass, config_entry, async_add_entities, BleBoxClimateEntity, "climates" + ) + + +class BleBoxClimateEntity(BleBoxEntity, ClimateDevice): + """Representation of a BleBox climate feature (saunaBox).""" + + @property + def supported_features(self): + """Return the supported climate features.""" + return SUPPORT_TARGET_TEMPERATURE + + @property + def hvac_mode(self): + """Return the desired HVAC mode.""" + if self._feature.is_on is None: + return None + + return HVAC_MODE_HEAT if self._feature.is_on else HVAC_MODE_OFF + + @property + def hvac_action(self): + """Return the actual current HVAC action.""" + is_on = self._feature.is_on + if not is_on: + return None if is_on is None else CURRENT_HVAC_OFF + + # NOTE: In practice, there's no need to handle case when is_heating is None + return CURRENT_HVAC_HEAT if self._feature.is_heating else CURRENT_HVAC_IDLE + + @property + def hvac_modes(self): + """Return a list of possible HVAC modes.""" + return [HVAC_MODE_OFF, HVAC_MODE_HEAT] + + @property + def temperature_unit(self): + """Return the temperature unit.""" + return TEMP_CELSIUS + + @property + def max_temp(self): + """Return the maximum temperature supported.""" + return self._feature.max_temp + + @property + def min_temp(self): + """Return the maximum temperature supported.""" + return self._feature.min_temp + + @property + def current_temperature(self): + """Return the current temperature.""" + return self._feature.current + + @property + def target_temperature(self): + """Return the desired thermostat temperature.""" + return self._feature.desired + + async def async_set_hvac_mode(self, hvac_mode): + """Set the climate entity mode.""" + if hvac_mode == HVAC_MODE_HEAT: + return await self._feature.async_on() + + return await self._feature.async_off() + + async def async_set_temperature(self, **kwargs): + """Set the thermostat temperature.""" + value = kwargs[ATTR_TEMPERATURE] + await self._feature.async_set_temperature(value) diff --git a/tests/components/blebox/test_climate.py b/tests/components/blebox/test_climate.py new file mode 100644 index 00000000000..f7b58cf55c8 --- /dev/null +++ b/tests/components/blebox/test_climate.py @@ -0,0 +1,257 @@ +"""BleBox climate entities tests.""" + +import logging + +import blebox_uniapi +import pytest + +from homeassistant.components.climate.const import ( + ATTR_CURRENT_TEMPERATURE, + ATTR_HVAC_ACTION, + ATTR_HVAC_MODE, + ATTR_HVAC_MODES, + ATTR_MAX_TEMP, + ATTR_MIN_TEMP, + CURRENT_HVAC_HEAT, + CURRENT_HVAC_IDLE, + CURRENT_HVAC_OFF, + HVAC_MODE_HEAT, + HVAC_MODE_OFF, + SERVICE_SET_HVAC_MODE, + SERVICE_SET_TEMPERATURE, + SUPPORT_TARGET_TEMPERATURE, +) +from homeassistant.const import ( + ATTR_DEVICE_CLASS, + ATTR_SUPPORTED_FEATURES, + ATTR_TEMPERATURE, + STATE_UNKNOWN, +) + +from .conftest import async_setup_entity, mock_feature + +from tests.async_mock import AsyncMock, PropertyMock + + +@pytest.fixture(name="saunabox") +def saunabox_fixture(): + """Return a default climate entity mock.""" + feature = mock_feature( + "climates", + blebox_uniapi.climate.Climate, + unique_id="BleBox-saunaBox-1afe34db9437-thermostat", + full_name="saunaBox-thermostat", + device_class=None, + is_on=None, + desired=None, + current=None, + min_temp=-54.3, + max_temp=124.3, + ) + product = feature.product + type(product).name = PropertyMock(return_value="My sauna") + type(product).model = PropertyMock(return_value="saunaBox") + return (feature, "climate.saunabox_thermostat") + + +async def test_init(saunabox, hass, config): + """Test default state.""" + + _, entity_id = saunabox + entry = await async_setup_entity(hass, config, entity_id) + assert entry.unique_id == "BleBox-saunaBox-1afe34db9437-thermostat" + + state = hass.states.get(entity_id) + assert state.name == "saunaBox-thermostat" + + supported_features = state.attributes[ATTR_SUPPORTED_FEATURES] + assert supported_features & SUPPORT_TARGET_TEMPERATURE + + assert state.attributes[ATTR_HVAC_MODES] == [HVAC_MODE_OFF, HVAC_MODE_HEAT] + + assert ATTR_DEVICE_CLASS not in state.attributes + assert ATTR_HVAC_MODE not in state.attributes + assert ATTR_HVAC_ACTION not in state.attributes + + assert state.attributes[ATTR_MIN_TEMP] == -54.3 + assert state.attributes[ATTR_MAX_TEMP] == 124.3 + assert state.attributes[ATTR_TEMPERATURE] is None + assert state.attributes[ATTR_CURRENT_TEMPERATURE] is None + + assert state.state == STATE_UNKNOWN + + device_registry = await hass.helpers.device_registry.async_get_registry() + device = device_registry.async_get(entry.device_id) + + assert device.name == "My sauna" + assert device.identifiers == {("blebox", "abcd0123ef5678")} + assert device.manufacturer == "BleBox" + assert device.model == "saunaBox" + assert device.sw_version == "1.23" + + +async def test_update(saunabox, hass, config): + """Test updating.""" + + feature_mock, entity_id = saunabox + + def initial_update(): + feature_mock.is_on = False + feature_mock.desired = 64.3 + feature_mock.current = 40.9 + + feature_mock.async_update = AsyncMock(side_effect=initial_update) + await async_setup_entity(hass, config, entity_id) + + state = hass.states.get(entity_id) + assert state.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_OFF + assert state.attributes[ATTR_TEMPERATURE] == 64.3 + assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 40.9 + assert state.state == HVAC_MODE_OFF + + +async def test_on_when_below_desired(saunabox, hass, config): + """Test when temperature is below desired.""" + + feature_mock, entity_id = saunabox + + def initial_update(): + feature_mock.is_on = False + + feature_mock.async_update = AsyncMock(side_effect=initial_update) + await async_setup_entity(hass, config, entity_id) + feature_mock.async_update = AsyncMock() + + def turn_on(): + feature_mock.is_on = True + feature_mock.is_heating = True + feature_mock.desired = 64.8 + feature_mock.current = 25.7 + + feature_mock.async_on = AsyncMock(side_effect=turn_on) + await hass.services.async_call( + "climate", + SERVICE_SET_HVAC_MODE, + {"entity_id": entity_id, ATTR_HVAC_MODE: HVAC_MODE_HEAT}, + blocking=True, + ) + state = hass.states.get(entity_id) + + assert state.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_HEAT + assert state.attributes[ATTR_TEMPERATURE] == 64.8 + assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 25.7 + assert state.state == HVAC_MODE_HEAT + + +async def test_on_when_above_desired(saunabox, hass, config): + """Test when temperature is below desired.""" + + feature_mock, entity_id = saunabox + + def initial_update(): + feature_mock.is_on = False + + feature_mock.async_update = AsyncMock(side_effect=initial_update) + await async_setup_entity(hass, config, entity_id) + feature_mock.async_update = AsyncMock() + + def turn_on(): + feature_mock.is_on = True + feature_mock.is_heating = False + feature_mock.desired = 23.4 + feature_mock.current = 28.7 + + feature_mock.async_on = AsyncMock(side_effect=turn_on) + + await hass.services.async_call( + "climate", + SERVICE_SET_HVAC_MODE, + {"entity_id": entity_id, ATTR_HVAC_MODE: HVAC_MODE_HEAT}, + blocking=True, + ) + state = hass.states.get(entity_id) + + assert state.attributes[ATTR_TEMPERATURE] == 23.4 + assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 28.7 + assert state.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_IDLE + assert state.state == HVAC_MODE_HEAT + + +async def test_off(saunabox, hass, config): + """Test turning off.""" + + feature_mock, entity_id = saunabox + + def initial_update(): + feature_mock.is_on = True + feature_mock.is_heating = False + + feature_mock.async_update = AsyncMock(side_effect=initial_update) + await async_setup_entity(hass, config, entity_id) + feature_mock.async_update = AsyncMock() + + def turn_off(): + feature_mock.is_on = False + feature_mock.is_heating = False + feature_mock.desired = 29.8 + feature_mock.current = 22.7 + + feature_mock.async_off = AsyncMock(side_effect=turn_off) + await hass.services.async_call( + "climate", + SERVICE_SET_HVAC_MODE, + {"entity_id": entity_id, ATTR_HVAC_MODE: HVAC_MODE_OFF}, + blocking=True, + ) + state = hass.states.get(entity_id) + + assert state.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_OFF + assert state.attributes[ATTR_TEMPERATURE] == 29.8 + assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 22.7 + assert state.state == HVAC_MODE_OFF + + +async def test_set_thermo(saunabox, hass, config): + """Test setting thermostat.""" + + feature_mock, entity_id = saunabox + + def update(): + feature_mock.is_on = False + feature_mock.is_heating = False + + feature_mock.async_update = AsyncMock(side_effect=update) + await async_setup_entity(hass, config, entity_id) + feature_mock.async_update = AsyncMock() + + def set_temp(temp): + feature_mock.is_on = True + feature_mock.is_heating = True + feature_mock.desired = 29.2 + feature_mock.current = 29.1 + + feature_mock.async_set_temperature = AsyncMock(side_effect=set_temp) + await hass.services.async_call( + "climate", + SERVICE_SET_TEMPERATURE, + {"entity_id": entity_id, ATTR_TEMPERATURE: 43.21}, + blocking=True, + ) + state = hass.states.get(entity_id) + + assert state.attributes[ATTR_TEMPERATURE] == 29.2 + assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 29.1 + assert state.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_HEAT + assert state.state == HVAC_MODE_HEAT + + +async def test_update_failure(saunabox, hass, config, caplog): + """Test that update failures are logged.""" + + caplog.set_level(logging.ERROR) + + feature_mock, entity_id = saunabox + feature_mock.async_update = AsyncMock(side_effect=blebox_uniapi.error.ClientError) + await async_setup_entity(hass, config, entity_id) + + assert f"Updating '{feature_mock.full_name}' failed: " in caplog.text From ed62fe03b0044129aa1a32dd75f2642e0cb63f34 Mon Sep 17 00:00:00 2001 From: Thomas Hollstegge Date: Mon, 25 May 2020 21:55:23 +0200 Subject: [PATCH 182/406] Fix emulated_hue compatibility with older devices (#36090) * Fix emulated_hue compatibility with older devices * Fix test ugliness * Fix pylint errors --- .../components/emulated_hue/const.py | 4 ++ homeassistant/components/emulated_hue/upnp.py | 55 +++++++++++------ tests/components/emulated_hue/test_upnp.py | 60 +++++++++++++++++++ 3 files changed, 100 insertions(+), 19 deletions(-) create mode 100644 homeassistant/components/emulated_hue/const.py diff --git a/homeassistant/components/emulated_hue/const.py b/homeassistant/components/emulated_hue/const.py new file mode 100644 index 00000000000..bfd58c5a0e1 --- /dev/null +++ b/homeassistant/components/emulated_hue/const.py @@ -0,0 +1,4 @@ +"""Constants for emulated_hue.""" + +HUE_SERIAL_NUMBER = "001788FFFE23BFC2" +HUE_UUID = "2f402f80-da50-11e1-9b23-001788255acc" diff --git a/homeassistant/components/emulated_hue/upnp.py b/homeassistant/components/emulated_hue/upnp.py index f0fe392f865..14e3cf11ca2 100644 --- a/homeassistant/components/emulated_hue/upnp.py +++ b/homeassistant/components/emulated_hue/upnp.py @@ -9,6 +9,8 @@ from aiohttp import web from homeassistant import core from homeassistant.components.http import HomeAssistantView +from .const import HUE_SERIAL_NUMBER, HUE_UUID + _LOGGER = logging.getLogger(__name__) @@ -42,8 +44,8 @@ class DescriptionXmlView(HomeAssistantView): Philips hue bridge 2015 BSB002 http://www.meethue.com -001788FFFE23BFC2 -uuid:2f402f80-da50-11e1-9b23-001788255acc +{HUE_SERIAL_NUMBER} +uuid:{HUE_UUID} """ @@ -70,21 +72,8 @@ class UPNPResponderThread(threading.Thread): self.host_ip_addr = host_ip_addr self.listen_port = listen_port self.upnp_bind_multicast = upnp_bind_multicast - - # Note that the double newline at the end of - # this string is required per the SSDP spec - resp_template = f"""HTTP/1.1 200 OK -CACHE-CONTROL: max-age=60 -EXT: -LOCATION: http://{advertise_ip}:{advertise_port}/description.xml -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 - -""" - - self.upnp_response = resp_template.replace("\n", "\r\n").encode("utf-8") + self.advertise_ip = advertise_ip + self.advertise_port = advertise_port def run(self): """Run the server.""" @@ -136,10 +125,13 @@ USN: uuid:2f402f80-da50-11e1-9b23-00178829d301::upnp:rootdevice continue if "M-SEARCH" in data.decode("utf-8", errors="ignore"): + _LOGGER.debug("UPNP Responder M-SEARCH method received: %s", data) # SSDP M-SEARCH method received, respond to it with our info - resp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + response = self._handle_request(data) - resp_socket.sendto(self.upnp_response, addr) + resp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + resp_socket.sendto(response, addr) + _LOGGER.debug("UPNP Responder responding with: %s", response) resp_socket.close() def stop(self): @@ -148,6 +140,31 @@ USN: uuid:2f402f80-da50-11e1-9b23-00178829d301::upnp:rootdevice self._interrupted = True self.join() + def _handle_request(self, data): + if "upnp:rootdevice" in data.decode("utf-8", errors="ignore"): + return self._prepare_response( + "upnp:rootdevice", f"uuid:{HUE_UUID}::upnp:rootdevice" + ) + + return self._prepare_response( + "urn:schemas-upnp-org:device:basic:1", f"uuid:{HUE_UUID}" + ) + + def _prepare_response(self, search_target, unique_service_name): + # Note that the double newline at the end of + # this string is required per the SSDP spec + response = f"""HTTP/1.1 200 OK +CACHE-CONTROL: max-age=60 +EXT: +LOCATION: http://{self.advertise_ip}:{self.advertise_port}/description.xml +SERVER: FreeRTOS/6.0.5, UPnP/1.0, IpBridge/1.16.0 +hue-bridgeid: {HUE_SERIAL_NUMBER} +ST: {search_target} +USN: {unique_service_name} + +""" + return response.replace("\n", "\r\n").encode("utf-8") + def clean_socket_close(sock): """Close a socket connection and logs its closure.""" diff --git a/tests/components/emulated_hue/test_upnp.py b/tests/components/emulated_hue/test_upnp.py index 32859ca00c1..a6040e8db68 100644 --- a/tests/components/emulated_hue/test_upnp.py +++ b/tests/components/emulated_hue/test_upnp.py @@ -48,6 +48,66 @@ class TestEmulatedHue(unittest.TestCase): """Stop the class.""" cls.hass.stop() + def test_upnp_discovery_basic(self): + """Tests the UPnP basic discovery response.""" + with patch("threading.Thread.__init__"): + upnp_responder_thread = emulated_hue.UPNPResponderThread( + "0.0.0.0", 80, True, "192.0.2.42", 8080 + ) + + """Original request emitted by the Hue Bridge v1 app.""" + request = """M-SEARCH * HTTP/1.1 +HOST:239.255.255.250:1900 +ST:ssdp:all +Man:"ssdp:discover" +MX:3 + +""" + encoded_request = request.replace("\n", "\r\n").encode("utf-8") + + response = upnp_responder_thread._handle_request(encoded_request) + expected_response = """HTTP/1.1 200 OK +CACHE-CONTROL: max-age=60 +EXT: +LOCATION: http://192.0.2.42:8080/description.xml +SERVER: FreeRTOS/6.0.5, UPnP/1.0, IpBridge/1.16.0 +hue-bridgeid: 001788FFFE23BFC2 +ST: urn:schemas-upnp-org:device:basic:1 +USN: uuid:2f402f80-da50-11e1-9b23-001788255acc + +""" + assert expected_response.replace("\n", "\r\n").encode("utf-8") == response + + def test_upnp_discovery_rootdevice(self): + """Tests the UPnP rootdevice discovery response.""" + with patch("threading.Thread.__init__"): + upnp_responder_thread = emulated_hue.UPNPResponderThread( + "0.0.0.0", 80, True, "192.0.2.42", 8080 + ) + + """Original request emitted by Busch-Jaeger free@home SysAP.""" + request = """M-SEARCH * HTTP/1.1 +HOST: 239.255.255.250:1900 +MAN: "ssdp:discover" +MX: 40 +ST: upnp:rootdevice + +""" + encoded_request = request.replace("\n", "\r\n").encode("utf-8") + + response = upnp_responder_thread._handle_request(encoded_request) + expected_response = """HTTP/1.1 200 OK +CACHE-CONTROL: max-age=60 +EXT: +LOCATION: http://192.0.2.42:8080/description.xml +SERVER: FreeRTOS/6.0.5, UPnP/1.0, IpBridge/1.16.0 +hue-bridgeid: 001788FFFE23BFC2 +ST: upnp:rootdevice +USN: uuid:2f402f80-da50-11e1-9b23-001788255acc::upnp:rootdevice + +""" + assert expected_response.replace("\n", "\r\n").encode("utf-8") == response + def test_description_xml(self): """Test the description.""" result = requests.get(BRIDGE_URL_BASE.format("/description.xml"), timeout=5) From 751428fe2be8ce10a688f4425982d29e07b4c077 Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Mon, 25 May 2020 22:05:52 +0200 Subject: [PATCH 183/406] Catch NoIPControl exception (#36088) --- .../components/braviatv/config_flow.py | 17 +++++-- .../components/braviatv/manifest.json | 2 +- .../components/braviatv/media_player.py | 10 ++-- .../components/braviatv/strings.json | 3 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/braviatv/test_config_flow.py | 48 +++++++++++++------ 7 files changed, 57 insertions(+), 27 deletions(-) diff --git a/homeassistant/components/braviatv/config_flow.py b/homeassistant/components/braviatv/config_flow.py index 660e2e83ea1..d7db38c5c2a 100644 --- a/homeassistant/components/braviatv/config_flow.py +++ b/homeassistant/components/braviatv/config_flow.py @@ -4,6 +4,7 @@ import logging import re from bravia_tv import BraviaRC +from bravia_tv.braviarc import NoIPControl import voluptuous as vol from homeassistant import config_entries, exceptions @@ -51,7 +52,7 @@ class BraviaTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def init_device(self, pin): """Initialize Bravia TV device.""" await self.hass.async_add_executor_job( - self.braviarc.connect, pin, CLIENTID_PREFIX, NICKNAME, + self.braviarc.connect, pin, CLIENTID_PREFIX, NICKNAME ) if not self.braviarc.is_connected(): @@ -85,6 +86,9 @@ class BraviaTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): except CannotConnect: _LOGGER.error("Import aborted, cannot connect to %s", self.host) return self.async_abort(reason="cannot_connect") + except NoIPControl: + _LOGGER.error("IP Control is disabled in the TV settings") + return self.async_abort(reason="no_ip_control") except ModelNotSupported: _LOGGER.error("Import aborted, your TV is not supported") return self.async_abort(reason="unsupported_model") @@ -129,9 +133,12 @@ class BraviaTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return self.async_create_entry(title=self.title, data=user_input) # Connecting with th PIN "0000" to start the pairing process on the TV. - await self.hass.async_add_executor_job( - self.braviarc.connect, "0000", CLIENTID_PREFIX, NICKNAME, - ) + try: + await self.hass.async_add_executor_job( + self.braviarc.connect, "0000", CLIENTID_PREFIX, NICKNAME + ) + except NoIPControl: + return self.async_abort(reason="no_ip_control") return self.async_show_form( step_id="authorize", @@ -156,7 +163,7 @@ class BraviaTVOptionsFlowHandler(config_entries.OptionsFlow): 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, + self.braviarc.connect, self.pin, CLIENTID_PREFIX, NICKNAME ) content_mapping = await self.hass.async_add_executor_job( diff --git a/homeassistant/components/braviatv/manifest.json b/homeassistant/components/braviatv/manifest.json index 0936c1f9088..7ed09ee018d 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.4"], + "requirements": ["bravia-tv==1.0.5"], "codeowners": ["@robbiet480", "@bieniu"], "config_flow": true } diff --git a/homeassistant/components/braviatv/media_player.py b/homeassistant/components/braviatv/media_player.py index eb75542460f..f6c023481c0 100644 --- a/homeassistant/components/braviatv/media_player.py +++ b/homeassistant/components/braviatv/media_player.py @@ -2,6 +2,7 @@ import asyncio import logging +from bravia_tv.braviarc import NoIPControl import voluptuous as vol from homeassistant.components.media_player import ( @@ -162,9 +163,12 @@ class BraviaTVDevice(MediaPlayerEntity): ) if power_status == "active": if self._need_refresh: - connected = await self.hass.async_add_executor_job( - self._braviarc.connect, self._pin, CLIENTID_PREFIX, NICKNAME - ) + try: + connected = await self.hass.async_add_executor_job( + self._braviarc.connect, self._pin, CLIENTID_PREFIX, NICKNAME + ) + except NoIPControl: + _LOGGER.error("IP Control is disabled in the TV settings") self._need_refresh = False else: connected = self._braviarc.is_connected() diff --git a/homeassistant/components/braviatv/strings.json b/homeassistant/components/braviatv/strings.json index ca432270cbb..c066f91d395 100644 --- a/homeassistant/components/braviatv/strings.json +++ b/homeassistant/components/braviatv/strings.json @@ -22,7 +22,8 @@ "unsupported_model": "Your TV model is not supported." }, "abort": { - "already_configured": "This TV is already configured." + "already_configured": "This TV is already configured.", + "no_ip_control": "IP Control is disabled on your TV or the TV is not supported." } }, "options": { diff --git a/requirements_all.txt b/requirements_all.txt index 95a649b2d85..ecd24e3ced4 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -371,7 +371,7 @@ bomradarloop==0.1.4 boto3==1.9.252 # homeassistant.components.braviatv -bravia-tv==1.0.4 +bravia-tv==1.0.5 # homeassistant.components.broadlink broadlink==0.14.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index eb0f25d0a37..693c6bdb060 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -159,7 +159,7 @@ blebox_uniapi==1.3.2 bomradarloop==0.1.4 # homeassistant.components.braviatv -bravia-tv==1.0.4 +bravia-tv==1.0.5 # homeassistant.components.broadlink broadlink==0.14.0 diff --git a/tests/components/braviatv/test_config_flow.py b/tests/components/braviatv/test_config_flow.py index 87fa26c242a..c6f76105cfc 100644 --- a/tests/components/braviatv/test_config_flow.py +++ b/tests/components/braviatv/test_config_flow.py @@ -1,4 +1,6 @@ """Define tests for the Bravia TV config flow.""" +from bravia_tv.braviarc import NoIPControl + 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 @@ -28,14 +30,8 @@ BRAVIA_SOURCE_LIST = { "AV/Component": "extInput:component?port=1", } -IMPORT_CONFIG_HOSTNAME = { - CONF_HOST: "bravia-host", - CONF_PIN: "1234", -} -IMPORT_CONFIG_IP = { - CONF_HOST: "10.10.10.12", - CONF_PIN: "1234", -} +IMPORT_CONFIG_HOSTNAME = {CONF_HOST: "bravia-host", CONF_PIN: "1234"} +IMPORT_CONFIG_IP = {CONF_HOST: "10.10.10.12", CONF_PIN: "1234"} async def test_show_form(hass): @@ -58,7 +54,7 @@ async def test_import(hass): "homeassistant.components.braviatv.async_setup_entry", return_value=True ): result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_IMPORT}, data=IMPORT_CONFIG_HOSTNAME, + DOMAIN, context={"source": SOURCE_IMPORT}, data=IMPORT_CONFIG_HOSTNAME ) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY @@ -77,7 +73,7 @@ async def test_import_cannot_connect(hass): "bravia_tv.BraviaRC.is_connected", return_value=False ): result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_IMPORT}, data=IMPORT_CONFIG_HOSTNAME, + DOMAIN, context={"source": SOURCE_IMPORT}, data=IMPORT_CONFIG_HOSTNAME ) assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT @@ -90,13 +86,24 @@ async def test_import_model_unsupported(hass): "bravia_tv.BraviaRC.is_connected", return_value=True ), patch("bravia_tv.BraviaRC.get_system_info", return_value={}): result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_IMPORT}, data=IMPORT_CONFIG_IP, + DOMAIN, context={"source": SOURCE_IMPORT}, data=IMPORT_CONFIG_IP ) assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result["reason"] == "unsupported_model" +async def test_import_no_ip_control(hass): + """Test that errors are shown when IP Control is disabled on the TV during import.""" + with patch("bravia_tv.BraviaRC.connect", side_effect=NoIPControl("No IP Control")): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_IMPORT}, data=IMPORT_CONFIG_IP + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "no_ip_control" + + async def test_import_duplicate_error(hass): """Test that errors are shown when duplicates are added during import.""" config_entry = MockConfigEntry( @@ -116,7 +123,7 @@ async def test_import_duplicate_error(hass): ), patch("bravia_tv.BraviaRC.get_system_info", return_value=BRAVIA_SYSTEM_INFO): result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_IMPORT}, data=IMPORT_CONFIG_HOSTNAME, + DOMAIN, context={"source": SOURCE_IMPORT}, data=IMPORT_CONFIG_HOSTNAME ) assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT @@ -136,7 +143,7 @@ async def test_authorize_cannot_connect(hass): """Test that errors are shown when cannot connect to host at the authorize step.""" with patch("bravia_tv.BraviaRC.connect", return_value=True): result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_USER}, data={CONF_HOST: "bravia-host"}, + DOMAIN, context={"source": SOURCE_USER}, data={CONF_HOST: "bravia-host"} ) result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={CONF_PIN: "1234"} @@ -151,7 +158,7 @@ async def test_authorize_model_unsupported(hass): "bravia_tv.BraviaRC.is_connected", return_value=True ), patch("bravia_tv.BraviaRC.get_system_info", return_value={}): result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_USER}, data={CONF_HOST: "10.10.10.12"}, + DOMAIN, context={"source": SOURCE_USER}, data={CONF_HOST: "10.10.10.12"} ) result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={CONF_PIN: "1234"} @@ -160,6 +167,17 @@ async def test_authorize_model_unsupported(hass): assert result["errors"] == {"base": "unsupported_model"} +async def test_authorize_no_ip_control(hass): + """Test that errors are shown when IP Control is disabled on the TV.""" + with patch("bravia_tv.BraviaRC.connect", side_effect=NoIPControl("No IP Control")): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER}, data={CONF_HOST: "bravia-host"} + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "no_ip_control" + + async def test_duplicate_error(hass): """Test that errors are shown when duplicates are added.""" config_entry = MockConfigEntry( @@ -179,7 +197,7 @@ async def test_duplicate_error(hass): ), patch("bravia_tv.BraviaRC.get_system_info", return_value=BRAVIA_SYSTEM_INFO): result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_USER}, data={CONF_HOST: "bravia-host"}, + DOMAIN, context={"source": SOURCE_USER}, data={CONF_HOST: "bravia-host"} ) result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={CONF_PIN: "1234"} From 654195454b4495f01fa978b684d23ab82cd0c74a Mon Sep 17 00:00:00 2001 From: Chris Talkington Date: Mon, 25 May 2020 15:06:38 -0500 Subject: [PATCH 184/406] Add roku tv input names to alexa inputs (#36089) * add roku tv input names to alexa inputs * Update const.py --- homeassistant/components/alexa/const.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/homeassistant/components/alexa/const.py b/homeassistant/components/alexa/const.py index ca1c6236fe6..3dd50ce3b3e 100644 --- a/homeassistant/components/alexa/const.py +++ b/homeassistant/components/alexa/const.py @@ -126,6 +126,8 @@ class Inputs: """ VALID_SOURCE_NAME_MAP = { + "antenna": "TUNER", + "antennatv": "TUNER", "aux": "AUX 1", "aux1": "AUX 1", "aux2": "AUX 2", @@ -135,6 +137,7 @@ class Inputs: "aux6": "AUX 6", "aux7": "AUX 7", "bluray": "BLURAY", + "blurayplayer": "BLURAY", "cable": "CABLE", "cd": "CD", "coax": "COAX 1", @@ -186,6 +189,7 @@ class Inputs: "playstation": "PLAYSTATION", "playstation3": "PLAYSTATION 3", "playstation4": "PLAYSTATION 4", + "rokumediaplayer": "MEDIA PLAYER", "satellite": "SATELLITE", "satellitetv": "SATELLITE", "smartcast": "SMARTCAST", From 2793e6cdd878ef09a95aac515114715d58e68c74 Mon Sep 17 00:00:00 2001 From: teldri Date: Mon, 25 May 2020 22:07:58 +0200 Subject: [PATCH 185/406] Fallback lg_soundbar sound mode on unknown value (#35892) * fallback if equaliser setting unknown to temescal * fallback if equaliser setting unknown to temescal Change Author * fix return values, moved fallback code * add comment to temporary fix --- homeassistant/components/lg_soundbar/media_player.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/lg_soundbar/media_player.py b/homeassistant/components/lg_soundbar/media_player.py index f3c89d6138e..a10b46f89ce 100644 --- a/homeassistant/components/lg_soundbar/media_player.py +++ b/homeassistant/components/lg_soundbar/media_player.py @@ -114,6 +114,11 @@ class LGDevice(MediaPlayerEntity): self._device.get_settings() self._device.get_product_info() + # Temporary fix until handling of unknown equaliser settings is integrated in the temescal library + for equaliser in self._equalisers: + if equaliser >= len(temescal.equalisers): + temescal.equalisers.append("unknown " + str(equaliser)) + @property def name(self): """Return the name of the device.""" @@ -139,9 +144,8 @@ class LGDevice(MediaPlayerEntity): @property def sound_mode(self): """Return the current sound mode.""" - - if self._equaliser == -1: - return "" + if self._equaliser == -1 or self._equaliser >= len(temescal.equalisers): + return None return temescal.equalisers[self._equaliser] @property @@ -156,7 +160,7 @@ class LGDevice(MediaPlayerEntity): def source(self): """Return the current input source.""" if self._function == -1: - return "" + return None return temescal.functions[self._function] @property From fa55f01c8c421dc0d1e856232c90ec7abded9809 Mon Sep 17 00:00:00 2001 From: Thomas Hollstegge Date: Mon, 25 May 2020 22:08:49 +0200 Subject: [PATCH 186/406] Report entity IDs for min/max sensor platform (#33806) * Report entity ids for min/max sensor platform * Use better variable names --- homeassistant/components/min_max/sensor.py | 50 ++++++++++++++-------- tests/components/min_max/test_sensor.py | 27 ++++++++++++ 2 files changed, 59 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/min_max/sensor.py b/homeassistant/components/min_max/sensor.py index aa58cc0be21..a9032787420 100644 --- a/homeassistant/components/min_max/sensor.py +++ b/homeassistant/components/min_max/sensor.py @@ -19,17 +19,23 @@ from homeassistant.helpers.event import async_track_state_change _LOGGER = logging.getLogger(__name__) ATTR_MIN_VALUE = "min_value" +ATTR_MIN_ENTITY_ID = "min_entity_id" ATTR_MAX_VALUE = "max_value" +ATTR_MAX_ENTITY_ID = "max_entity_id" ATTR_COUNT_SENSORS = "count_sensors" ATTR_MEAN = "mean" ATTR_LAST = "last" +ATTR_LAST_ENTITY_ID = "last_entity_id" ATTR_TO_PROPERTY = [ ATTR_COUNT_SENSORS, ATTR_MAX_VALUE, + ATTR_MAX_ENTITY_ID, ATTR_MEAN, ATTR_MIN_VALUE, + ATTR_MIN_ENTITY_ID, ATTR_LAST, + ATTR_LAST_ENTITY_ID, ] CONF_ENTITY_IDS = "entity_ids" @@ -72,34 +78,36 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= def calc_min(sensor_values): """Calculate min value, honoring unknown states.""" val = None - for sval in sensor_values: - if sval != STATE_UNKNOWN: - if val is None or val > sval: - val = sval - return val + entity_id = None + for sensor_id, sensor_value in sensor_values: + if sensor_value != STATE_UNKNOWN: + if val is None or val > sensor_value: + entity_id, val = sensor_id, sensor_value + return entity_id, val def calc_max(sensor_values): """Calculate max value, honoring unknown states.""" val = None - for sval in sensor_values: - if sval != STATE_UNKNOWN: - if val is None or val < sval: - val = sval - return val + entity_id = None + for sensor_id, sensor_value in sensor_values: + if sensor_value != STATE_UNKNOWN: + if val is None or val < sensor_value: + entity_id, val = sensor_id, sensor_value + return entity_id, val def calc_mean(sensor_values, round_digits): """Calculate mean value, honoring unknown states.""" - val = 0 + sensor_value_sum = 0 count = 0 - for sval in sensor_values: - if sval != STATE_UNKNOWN: - val += sval + for _, sensor_value in sensor_values: + if sensor_value != STATE_UNKNOWN: + sensor_value_sum += sensor_value count += 1 if count == 0: return None - return round(val / count, round_digits) + return round(sensor_value_sum / count, round_digits) class MinMaxSensor(Entity): @@ -119,6 +127,7 @@ class MinMaxSensor(Entity): self._unit_of_measurement = None self._unit_of_measurement_mismatch = False self.min_value = self.max_value = self.mean = self.last = None + self.min_entity_id = self.max_entity_id = self.last_entity_id = None self.count_sensors = len(self._entity_ids) self.states = {} @@ -149,6 +158,7 @@ class MinMaxSensor(Entity): try: self.states[entity] = float(new_state.state) self.last = float(new_state.state) + self.last_entity_id = entity except ValueError: _LOGGER.warning( "Unable to store state. Only numerical states are supported" @@ -201,7 +211,11 @@ class MinMaxSensor(Entity): async def async_update(self): """Get the latest data and updates the states.""" - sensor_values = [self.states[k] for k in self._entity_ids if k in self.states] - self.min_value = calc_min(sensor_values) - self.max_value = calc_max(sensor_values) + sensor_values = [ + (entity_id, self.states[entity_id]) + for entity_id in self._entity_ids + if entity_id in self.states + ] + self.min_entity_id, self.min_value = calc_min(sensor_values) + self.max_entity_id, self.max_value = calc_max(sensor_values) self.mean = calc_mean(sensor_values, self._round_digits) diff --git a/tests/components/min_max/test_sensor.py b/tests/components/min_max/test_sensor.py index 10860d0fbe4..57ac39f8ee4 100644 --- a/tests/components/min_max/test_sensor.py +++ b/tests/components/min_max/test_sensor.py @@ -54,7 +54,9 @@ class TestMinMaxSensor(unittest.TestCase): state = self.hass.states.get("sensor.test_min") assert str(float(self.min)) == state.state + assert entity_ids[2] == state.attributes.get("min_entity_id") assert self.max == state.attributes.get("max_value") + assert entity_ids[1] == state.attributes.get("max_entity_id") assert self.mean == state.attributes.get("mean") def test_max_sensor(self): @@ -79,7 +81,9 @@ class TestMinMaxSensor(unittest.TestCase): state = self.hass.states.get("sensor.test_max") assert str(float(self.max)) == state.state + assert entity_ids[2] == state.attributes.get("min_entity_id") assert self.min == state.attributes.get("min_value") + assert entity_ids[1] == state.attributes.get("max_entity_id") assert self.mean == state.attributes.get("mean") def test_mean_sensor(self): @@ -105,7 +109,9 @@ class TestMinMaxSensor(unittest.TestCase): assert str(float(self.mean)) == state.state assert self.min == state.attributes.get("min_value") + assert entity_ids[2] == state.attributes.get("min_entity_id") assert self.max == state.attributes.get("max_value") + assert entity_ids[1] == state.attributes.get("max_entity_id") def test_mean_1_digit_sensor(self): """Test the mean with 1-digit precision sensor.""" @@ -131,7 +137,9 @@ class TestMinMaxSensor(unittest.TestCase): assert str(float(self.mean_1_digit)) == state.state assert self.min == state.attributes.get("min_value") + assert entity_ids[2] == state.attributes.get("min_entity_id") assert self.max == state.attributes.get("max_value") + assert entity_ids[1] == state.attributes.get("max_entity_id") def test_mean_4_digit_sensor(self): """Test the mean with 1-digit precision sensor.""" @@ -157,7 +165,9 @@ class TestMinMaxSensor(unittest.TestCase): assert str(float(self.mean_4_digits)) == state.state assert self.min == state.attributes.get("min_value") + assert entity_ids[2] == state.attributes.get("min_entity_id") assert self.max == state.attributes.get("max_value") + assert entity_ids[1] == state.attributes.get("max_entity_id") def test_not_enough_sensor_value(self): """Test that there is nothing done if not enough values available.""" @@ -179,24 +189,40 @@ class TestMinMaxSensor(unittest.TestCase): state = self.hass.states.get("sensor.test_max") assert STATE_UNKNOWN == state.state + assert state.attributes.get("min_entity_id") is None + assert state.attributes.get("min_value") is None + assert state.attributes.get("max_entity_id") is None + assert state.attributes.get("max_value") is None self.hass.states.set(entity_ids[1], self.values[1]) self.hass.block_till_done() state = self.hass.states.get("sensor.test_max") assert STATE_UNKNOWN != state.state + assert entity_ids[1] == state.attributes.get("min_entity_id") + assert self.values[1] == state.attributes.get("min_value") + assert entity_ids[1] == state.attributes.get("max_entity_id") + assert self.values[1] == state.attributes.get("max_value") self.hass.states.set(entity_ids[2], STATE_UNKNOWN) self.hass.block_till_done() state = self.hass.states.get("sensor.test_max") assert STATE_UNKNOWN != state.state + assert entity_ids[1] == state.attributes.get("min_entity_id") + assert self.values[1] == state.attributes.get("min_value") + assert entity_ids[1] == state.attributes.get("max_entity_id") + assert self.values[1] == state.attributes.get("max_value") self.hass.states.set(entity_ids[1], STATE_UNAVAILABLE) self.hass.block_till_done() state = self.hass.states.get("sensor.test_max") assert STATE_UNKNOWN == state.state + assert state.attributes.get("min_entity_id") is None + assert state.attributes.get("min_value") is None + assert state.attributes.get("max_entity_id") is None + assert state.attributes.get("max_value") is None def test_different_unit_of_measurement(self): """Test for different unit of measurement.""" @@ -264,6 +290,7 @@ class TestMinMaxSensor(unittest.TestCase): self.hass.block_till_done() state = self.hass.states.get("sensor.test_last") assert str(float(value)) == state.state + assert entity_id == state.attributes.get("last_entity_id") assert self.min == state.attributes.get("min_value") assert self.max == state.attributes.get("max_value") From 05cbb3f0e4343a3d70bd713c49cb39ffff5d52b3 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Mon, 25 May 2020 14:34:51 -0600 Subject: [PATCH 187/406] Remove WWLLN integration (#35926) --- .coveragerc | 2 - CODEOWNERS | 1 - homeassistant/components/wwlln/__init__.py | 102 -------- homeassistant/components/wwlln/config_flow.py | 70 ------ homeassistant/components/wwlln/const.py | 11 - .../components/wwlln/geo_location.py | 228 ------------------ homeassistant/components/wwlln/manifest.json | 8 - homeassistant/components/wwlln/strings.json | 15 -- .../components/wwlln/translations/bg.json | 14 -- .../components/wwlln/translations/ca.json | 17 -- .../components/wwlln/translations/cy.json | 14 -- .../components/wwlln/translations/da.json | 14 -- .../components/wwlln/translations/de.json | 17 -- .../components/wwlln/translations/en.json | 17 -- .../components/wwlln/translations/es-419.json | 17 -- .../components/wwlln/translations/es.json | 17 -- .../components/wwlln/translations/fi.json | 12 - .../components/wwlln/translations/fr.json | 17 -- .../components/wwlln/translations/hr.json | 14 -- .../components/wwlln/translations/hu.json | 12 - .../components/wwlln/translations/it.json | 17 -- .../components/wwlln/translations/ko.json | 17 -- .../components/wwlln/translations/lb.json | 17 -- .../components/wwlln/translations/nl.json | 17 -- .../components/wwlln/translations/no.json | 17 -- .../components/wwlln/translations/pl.json | 17 -- .../components/wwlln/translations/pt-BR.json | 17 -- .../components/wwlln/translations/pt.json | 12 - .../components/wwlln/translations/ru.json | 17 -- .../components/wwlln/translations/sl.json | 17 -- .../components/wwlln/translations/sv.json | 14 -- .../wwlln/translations/zh-Hans.json | 14 -- .../wwlln/translations/zh-Hant.json | 17 -- homeassistant/generated/config_flows.py | 1 - requirements_all.txt | 3 - requirements_test_all.txt | 3 - tests/components/wwlln/__init__.py | 1 - tests/components/wwlln/conftest.py | 28 --- tests/components/wwlln/test_config_flow.py | 135 ----------- 39 files changed, 1000 deletions(-) delete mode 100644 homeassistant/components/wwlln/__init__.py delete mode 100644 homeassistant/components/wwlln/config_flow.py delete mode 100644 homeassistant/components/wwlln/const.py delete mode 100644 homeassistant/components/wwlln/geo_location.py delete mode 100644 homeassistant/components/wwlln/manifest.json delete mode 100644 homeassistant/components/wwlln/strings.json delete mode 100644 homeassistant/components/wwlln/translations/bg.json delete mode 100644 homeassistant/components/wwlln/translations/ca.json delete mode 100644 homeassistant/components/wwlln/translations/cy.json delete mode 100644 homeassistant/components/wwlln/translations/da.json delete mode 100644 homeassistant/components/wwlln/translations/de.json delete mode 100644 homeassistant/components/wwlln/translations/en.json delete mode 100644 homeassistant/components/wwlln/translations/es-419.json delete mode 100644 homeassistant/components/wwlln/translations/es.json delete mode 100644 homeassistant/components/wwlln/translations/fi.json delete mode 100644 homeassistant/components/wwlln/translations/fr.json delete mode 100644 homeassistant/components/wwlln/translations/hr.json delete mode 100644 homeassistant/components/wwlln/translations/hu.json delete mode 100644 homeassistant/components/wwlln/translations/it.json delete mode 100644 homeassistant/components/wwlln/translations/ko.json delete mode 100644 homeassistant/components/wwlln/translations/lb.json delete mode 100644 homeassistant/components/wwlln/translations/nl.json delete mode 100644 homeassistant/components/wwlln/translations/no.json delete mode 100644 homeassistant/components/wwlln/translations/pl.json delete mode 100644 homeassistant/components/wwlln/translations/pt-BR.json delete mode 100644 homeassistant/components/wwlln/translations/pt.json delete mode 100644 homeassistant/components/wwlln/translations/ru.json delete mode 100644 homeassistant/components/wwlln/translations/sl.json delete mode 100644 homeassistant/components/wwlln/translations/sv.json delete mode 100644 homeassistant/components/wwlln/translations/zh-Hans.json delete mode 100644 homeassistant/components/wwlln/translations/zh-Hant.json delete mode 100644 tests/components/wwlln/__init__.py delete mode 100644 tests/components/wwlln/conftest.py delete mode 100644 tests/components/wwlln/test_config_flow.py diff --git a/.coveragerc b/.coveragerc index 92c6cdbcfbc..c99b29cd719 100644 --- a/.coveragerc +++ b/.coveragerc @@ -880,8 +880,6 @@ omit = homeassistant/components/worldtidesinfo/sensor.py homeassistant/components/worxlandroid/sensor.py homeassistant/components/wunderlist/* - homeassistant/components/wwlln/__init__.py - homeassistant/components/wwlln/geo_location.py homeassistant/components/x10/light.py homeassistant/components/xbox_live/sensor.py homeassistant/components/xeoma/camera.py diff --git a/CODEOWNERS b/CODEOWNERS index a8aea5f4727..a0ccec81579 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -458,7 +458,6 @@ homeassistant/components/withings/* @vangorra homeassistant/components/wled/* @frenck homeassistant/components/workday/* @fabaff homeassistant/components/worldclock/* @fabaff -homeassistant/components/wwlln/* @bachya homeassistant/components/xbox_live/* @MartinHjelmare homeassistant/components/xfinity/* @cisasteelersfan homeassistant/components/xiaomi_aqara/* @danielhiversen @syssi diff --git a/homeassistant/components/wwlln/__init__.py b/homeassistant/components/wwlln/__init__.py deleted file mode 100644 index d83e19bd391..00000000000 --- a/homeassistant/components/wwlln/__init__.py +++ /dev/null @@ -1,102 +0,0 @@ -"""Support for World Wide Lightning Location Network.""" -import logging - -from aiowwlln import Client -import voluptuous as vol - -from homeassistant.config_entries import SOURCE_IMPORT -from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_RADIUS -from homeassistant.helpers import aiohttp_client, config_validation as cv - -from .const import CONF_WINDOW, DATA_CLIENT, DEFAULT_RADIUS, DEFAULT_WINDOW, DOMAIN - -_LOGGER = logging.getLogger(__name__) - -CONFIG_SCHEMA = vol.Schema( - { - DOMAIN: vol.Schema( - { - vol.Optional(CONF_LATITUDE): cv.latitude, - vol.Optional(CONF_LONGITUDE): cv.longitude, - vol.Optional(CONF_RADIUS, default=DEFAULT_RADIUS): cv.positive_int, - vol.Optional(CONF_WINDOW, default=DEFAULT_WINDOW): vol.All( - cv.time_period, - cv.positive_timedelta, - lambda value: value.total_seconds(), - vol.Range(min=DEFAULT_WINDOW.total_seconds()), - ), - } - ) - }, - extra=vol.ALLOW_EXTRA, -) - - -async def async_setup(hass, config): - """Set up the WWLLN component.""" - if DOMAIN not in config: - return True - - conf = 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, config_entry): - """Set up the WWLLN as config entry.""" - if not config_entry.unique_id: - hass.config_entries.async_update_entry( - config_entry, - unique_id=( - f"{config_entry.data[CONF_LATITUDE]}, " - f"{config_entry.data[CONF_LONGITUDE]}" - ), - ) - - hass.data[DOMAIN] = {} - hass.data[DOMAIN][DATA_CLIENT] = {} - - websession = aiohttp_client.async_get_clientsession(hass) - - hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] = Client(websession) - - hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, "geo_location") - ) - - return True - - -async def async_unload_entry(hass, config_entry): - """Unload an WWLLN config entry.""" - hass.data[DOMAIN][DATA_CLIENT].pop(config_entry.entry_id) - - await hass.config_entries.async_forward_entry_unload(config_entry, "geo_location") - - return True - - -async def async_migrate_entry(hass, config_entry): - """Migrate the config entry upon new versions.""" - version = config_entry.version - data = config_entry.data - - default_total_seconds = DEFAULT_WINDOW.total_seconds() - - _LOGGER.debug("Migrating from version %s", version) - - # 1 -> 2: Expanding the default window to 1 hour (if needed): - if version == 1: - if data[CONF_WINDOW] < default_total_seconds: - data[CONF_WINDOW] = default_total_seconds - version = config_entry.version = 2 - hass.config_entries.async_update_entry(config_entry, data=data) - _LOGGER.info("Migration to version %s successful", version) - - return True diff --git a/homeassistant/components/wwlln/config_flow.py b/homeassistant/components/wwlln/config_flow.py deleted file mode 100644 index 4ec7c2a9a0c..00000000000 --- a/homeassistant/components/wwlln/config_flow.py +++ /dev/null @@ -1,70 +0,0 @@ -"""Config flow to configure the WWLLN integration.""" -import voluptuous as vol - -from homeassistant import config_entries -from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_RADIUS -from homeassistant.helpers import config_validation as cv - -from .const import ( # pylint: disable=unused-import - CONF_WINDOW, - DEFAULT_RADIUS, - DEFAULT_WINDOW, - DOMAIN, -) - - -class WWLLNFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): - """Handle a WWLLN config flow.""" - - VERSION = 2 - CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL - - @property - def data_schema(self): - """Return the data schema for the user form.""" - return vol.Schema( - { - vol.Optional( - CONF_LATITUDE, default=self.hass.config.latitude - ): cv.latitude, - vol.Optional( - CONF_LONGITUDE, default=self.hass.config.longitude - ): cv.longitude, - vol.Optional(CONF_RADIUS, default=DEFAULT_RADIUS): cv.positive_int, - } - ) - - async def _show_form(self, errors=None): - """Show the form to the user.""" - return self.async_show_form( - step_id="user", data_schema=self.data_schema, errors=errors or {} - ) - - 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.""" - if not user_input: - return await self._show_form() - - latitude = user_input.get(CONF_LATITUDE, self.hass.config.latitude) - longitude = user_input.get(CONF_LONGITUDE, self.hass.config.longitude) - - identifier = f"{latitude}, {longitude}" - - await self.async_set_unique_id(identifier) - self._abort_if_unique_id_configured() - - return self.async_create_entry( - title=identifier, - data={ - CONF_LATITUDE: latitude, - CONF_LONGITUDE: longitude, - CONF_RADIUS: user_input.get(CONF_RADIUS, DEFAULT_RADIUS), - CONF_WINDOW: user_input.get( - CONF_WINDOW, DEFAULT_WINDOW.total_seconds() - ), - }, - ) diff --git a/homeassistant/components/wwlln/const.py b/homeassistant/components/wwlln/const.py deleted file mode 100644 index 141baf38cda..00000000000 --- a/homeassistant/components/wwlln/const.py +++ /dev/null @@ -1,11 +0,0 @@ -"""Define constants for the WWLLN integration.""" -from datetime import timedelta - -DOMAIN = "wwlln" - -CONF_WINDOW = "window" - -DATA_CLIENT = "client" - -DEFAULT_RADIUS = 25 -DEFAULT_WINDOW = timedelta(hours=1) diff --git a/homeassistant/components/wwlln/geo_location.py b/homeassistant/components/wwlln/geo_location.py deleted file mode 100644 index ed4d4fcd6b8..00000000000 --- a/homeassistant/components/wwlln/geo_location.py +++ /dev/null @@ -1,228 +0,0 @@ -"""Support for WWLLN geo location events.""" -from datetime import timedelta -import logging - -from aiowwlln.errors import WWLLNError - -from homeassistant.components.geo_location import GeolocationEvent -from homeassistant.const import ( - ATTR_ATTRIBUTION, - CONF_LATITUDE, - CONF_LONGITUDE, - CONF_RADIUS, - CONF_UNIT_SYSTEM_IMPERIAL, - LENGTH_KILOMETERS, - LENGTH_MILES, -) -from homeassistant.core import callback -from homeassistant.helpers.dispatcher import ( - async_dispatcher_connect, - async_dispatcher_send, -) -from homeassistant.helpers.event import async_track_time_interval -from homeassistant.util.dt import utc_from_timestamp - -from .const import CONF_WINDOW, DATA_CLIENT, DOMAIN - -_LOGGER = logging.getLogger(__name__) - -ATTR_EXTERNAL_ID = "external_id" -ATTR_PUBLICATION_DATE = "publication_date" - -DEFAULT_ATTRIBUTION = "Data provided by the WWLLN" -DEFAULT_EVENT_NAME = "Lightning Strike: {0}" -DEFAULT_ICON = "mdi:flash" -DEFAULT_UPDATE_INTERVAL = timedelta(minutes=10) - -SIGNAL_DELETE_ENTITY = "wwlln_delete_entity_{0}" - - -async def async_setup_entry(hass, entry, async_add_entities): - """Set up WWLLN based on a config entry.""" - client = hass.data[DOMAIN][DATA_CLIENT][entry.entry_id] - manager = WWLLNEventManager( - hass, - async_add_entities, - client, - entry.data[CONF_LATITUDE], - entry.data[CONF_LONGITUDE], - entry.data[CONF_RADIUS], - entry.data[CONF_WINDOW], - ) - await manager.async_init() - - -class WWLLNEventManager: - """Define a class to handle WWLLN events.""" - - def __init__( - self, - hass, - async_add_entities, - client, - latitude, - longitude, - radius, - window_seconds, - ): - """Initialize.""" - self._async_add_entities = async_add_entities - self._client = client - self._hass = hass - self._latitude = latitude - self._longitude = longitude - self._managed_strike_ids = set() - self._radius = radius - self._strikes = {} - self._window = timedelta(seconds=window_seconds) - - if hass.config.units.name == CONF_UNIT_SYSTEM_IMPERIAL: - self._unit = LENGTH_MILES - else: - self._unit = LENGTH_KILOMETERS - - @callback - def _create_events(self, ids_to_create): - """Create new geo location events.""" - _LOGGER.debug("Going to create %s", ids_to_create) - events = [] - for strike_id in ids_to_create: - strike = self._strikes[strike_id] - event = WWLLNEvent( - strike["distance"], - strike["lat"], - strike["long"], - self._unit, - strike_id, - strike["unixTime"], - ) - events.append(event) - - self._async_add_entities(events) - - @callback - def _remove_events(self, ids_to_remove): - """Remove old geo location events.""" - _LOGGER.debug("Going to remove %s", ids_to_remove) - for strike_id in ids_to_remove: - async_dispatcher_send(self._hass, SIGNAL_DELETE_ENTITY.format(strike_id)) - - async def async_init(self): - """Schedule regular updates based on configured time interval.""" - - async def update(event_time): - """Update.""" - await self.async_update() - - await self.async_update() - async_track_time_interval(self._hass, update, DEFAULT_UPDATE_INTERVAL) - - async def async_update(self): - """Refresh data.""" - _LOGGER.debug("Refreshing WWLLN data") - - try: - self._strikes = await self._client.within_radius( - self._latitude, - self._longitude, - self._radius, - unit=self._hass.config.units.name, - window=self._window, - ) - except WWLLNError as err: - _LOGGER.error("Error while updating WWLLN data: %s", err) - return - - new_strike_ids = set(self._strikes) - # Remove all managed entities that are not in the latest update anymore. - ids_to_remove = self._managed_strike_ids.difference(new_strike_ids) - self._remove_events(ids_to_remove) - - # Create new entities for all strikes that are not managed entities yet. - ids_to_create = new_strike_ids.difference(self._managed_strike_ids) - self._create_events(ids_to_create) - - # Store all external IDs of all managed strikes. - self._managed_strike_ids = new_strike_ids - - -class WWLLNEvent(GeolocationEvent): - """Define a lightning strike event.""" - - def __init__( - self, distance, latitude, longitude, unit, strike_id, publication_date - ): - """Initialize entity with data provided.""" - self._distance = distance - self._latitude = latitude - self._longitude = longitude - self._publication_date = publication_date - self._remove_signal_delete = None - self._strike_id = strike_id - self._unit_of_measurement = unit - - @property - def device_state_attributes(self): - """Return the device state attributes.""" - attributes = {} - for key, value in ( - (ATTR_EXTERNAL_ID, self._strike_id), - (ATTR_ATTRIBUTION, DEFAULT_ATTRIBUTION), - (ATTR_PUBLICATION_DATE, utc_from_timestamp(self._publication_date)), - ): - attributes[key] = value - return attributes - - @property - def distance(self): - """Return distance value of this external event.""" - return self._distance - - @property - def icon(self): - """Return the icon to use in the front-end.""" - return DEFAULT_ICON - - @property - def latitude(self): - """Return latitude value of this external event.""" - return self._latitude - - @property - def longitude(self): - """Return longitude value of this external event.""" - return self._longitude - - @property - def name(self): - """Return the name of the event.""" - return DEFAULT_EVENT_NAME.format(self._strike_id) - - @property - def source(self) -> str: - """Return source value of this external event.""" - return DOMAIN - - @property - def should_poll(self): - """Disable polling.""" - return False - - @property - def unit_of_measurement(self): - """Return the unit of measurement.""" - return self._unit_of_measurement - - @callback - def _delete_callback(self): - """Remove this entity.""" - self._remove_signal_delete() - self.hass.async_create_task(self.async_remove()) - - async def async_added_to_hass(self): - """Call when entity is added to hass.""" - self._remove_signal_delete = async_dispatcher_connect( - self.hass, - SIGNAL_DELETE_ENTITY.format(self._strike_id), - self._delete_callback, - ) diff --git a/homeassistant/components/wwlln/manifest.json b/homeassistant/components/wwlln/manifest.json deleted file mode 100644 index 19406ac4b7a..00000000000 --- a/homeassistant/components/wwlln/manifest.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "domain": "wwlln", - "name": "World Wide Lightning Location Network (WWLLN)", - "config_flow": true, - "documentation": "https://www.home-assistant.io/integrations/wwlln", - "requirements": ["aiowwlln==2.0.2"], - "codeowners": ["@bachya"] -} diff --git a/homeassistant/components/wwlln/strings.json b/homeassistant/components/wwlln/strings.json deleted file mode 100644 index c3c9193df33..00000000000 --- a/homeassistant/components/wwlln/strings.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "config": { - "step": { - "user": { - "title": "Fill in your location information.", - "data": { - "latitude": "Latitude", - "longitude": "Longitude", - "radius": "Radius (using your base unit system)" - } - } - }, - "abort": { "already_configured": "This location is already registered." } - } -} diff --git a/homeassistant/components/wwlln/translations/bg.json b/homeassistant/components/wwlln/translations/bg.json deleted file mode 100644 index cd39935f171..00000000000 --- a/homeassistant/components/wwlln/translations/bg.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "config": { - "step": { - "user": { - "data": { - "latitude": "\u0413\u0435\u043e\u0433\u0440\u0430\u0444\u0441\u043a\u0430 \u0448\u0438\u0440\u0438\u043d\u0430", - "longitude": "\u0413\u0435\u043e\u0433\u0440\u0430\u0444\u0441\u043a\u0430 \u0434\u044a\u043b\u0436\u0438\u043d\u0430", - "radius": "\u0420\u0430\u0434\u0438\u0443\u0441 (\u0438\u0437\u043f\u043e\u043b\u0437\u0432\u0430\u0439\u043a\u0438 \u0438\u0437\u0431\u0440\u0430\u043d\u0430\u0442\u0430 \u043e\u0442 \u0412\u0430\u0441 \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u043e\u0442 \u043c\u0435\u0440\u043d\u0438 \u0435\u0434\u0438\u043d\u0438\u0446\u0438)" - }, - "title": "\u041f\u043e\u043f\u044a\u043b\u043d\u0435\u0442\u0435 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f\u0442\u0430 \u0437\u0430 \u043c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u0442\u043e \u0441\u0438." - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/wwlln/translations/ca.json b/homeassistant/components/wwlln/translations/ca.json deleted file mode 100644 index b6e915aa35e..00000000000 --- a/homeassistant/components/wwlln/translations/ca.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "Aquesta ubicaci\u00f3 ja est\u00e0 registrada." - }, - "step": { - "user": { - "data": { - "latitude": "Latitud", - "longitude": "Longitud", - "radius": "Radi (utilitzant el sistema d'unitats establert)" - }, - "title": "Introdueix la teva informaci\u00f3 d'ubicaci\u00f3." - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/wwlln/translations/cy.json b/homeassistant/components/wwlln/translations/cy.json deleted file mode 100644 index f9c36f4e72a..00000000000 --- a/homeassistant/components/wwlln/translations/cy.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "config": { - "step": { - "user": { - "data": { - "latitude": "Lledred", - "longitude": "Hydred", - "radius": "Radiws (gan ddefnyddio'ch system uned sylfaenol)" - }, - "title": "Cwblhewch gwybodaeth eich lleoliad" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/wwlln/translations/da.json b/homeassistant/components/wwlln/translations/da.json deleted file mode 100644 index b87d9af14f3..00000000000 --- a/homeassistant/components/wwlln/translations/da.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "config": { - "step": { - "user": { - "data": { - "latitude": "Breddegrad", - "longitude": "L\u00e6ngdegrad", - "radius": "Radius (ved hj\u00e6lp af dit basisenhedssystem)" - }, - "title": "Udfyld dine lokalitetsoplysninger." - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/wwlln/translations/de.json b/homeassistant/components/wwlln/translations/de.json deleted file mode 100644 index 2f59ea2d38c..00000000000 --- a/homeassistant/components/wwlln/translations/de.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "Dieser Standort ist bereits registriert." - }, - "step": { - "user": { - "data": { - "latitude": "Breitengrad", - "longitude": "L\u00e4ngengrad", - "radius": "Radius (mit Ma\u00dfeinheit)" - }, - "title": "Gib deine Standortinformationen ein." - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/wwlln/translations/en.json b/homeassistant/components/wwlln/translations/en.json deleted file mode 100644 index 936c64c2a77..00000000000 --- a/homeassistant/components/wwlln/translations/en.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "This location is already registered." - }, - "step": { - "user": { - "data": { - "latitude": "Latitude", - "longitude": "Longitude", - "radius": "Radius (using your base unit system)" - }, - "title": "Fill in your location information." - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/wwlln/translations/es-419.json b/homeassistant/components/wwlln/translations/es-419.json deleted file mode 100644 index 11ae8c64359..00000000000 --- a/homeassistant/components/wwlln/translations/es-419.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "Esta ubicaci\u00f3n ya est\u00e1 registrada." - }, - "step": { - "user": { - "data": { - "latitude": "Latitud", - "longitude": "Longitud", - "radius": "Radio (usando su sistema de unidad base)" - }, - "title": "Complete su informaci\u00f3n de ubicaci\u00f3n." - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/wwlln/translations/es.json b/homeassistant/components/wwlln/translations/es.json deleted file mode 100644 index 16b3b461ad1..00000000000 --- a/homeassistant/components/wwlln/translations/es.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "Esta ubicaci\u00f3n ya est\u00e1 registrada." - }, - "step": { - "user": { - "data": { - "latitude": "Latitud", - "longitude": "Longitud", - "radius": "Radio (usando la unidad base del sistema)" - }, - "title": "Completa la informaci\u00f3n de tu ubicaci\u00f3n." - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/wwlln/translations/fi.json b/homeassistant/components/wwlln/translations/fi.json deleted file mode 100644 index 1b1b454585f..00000000000 --- a/homeassistant/components/wwlln/translations/fi.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "config": { - "step": { - "user": { - "data": { - "latitude": "Leveysaste", - "longitude": "Pituusaste" - } - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/wwlln/translations/fr.json b/homeassistant/components/wwlln/translations/fr.json deleted file mode 100644 index d4fad7f0160..00000000000 --- a/homeassistant/components/wwlln/translations/fr.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "Cet emplacement est d\u00e9j\u00e0 enregistr\u00e9." - }, - "step": { - "user": { - "data": { - "latitude": "Latitude", - "longitude": "Longitude", - "radius": "Rayon (en utilisant votre syst\u00e8me d'unit\u00e9 de base)" - }, - "title": "Veuillez saisir vos informations d'emplacement." - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/wwlln/translations/hr.json b/homeassistant/components/wwlln/translations/hr.json deleted file mode 100644 index 43af25b9047..00000000000 --- a/homeassistant/components/wwlln/translations/hr.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "config": { - "step": { - "user": { - "data": { - "latitude": "Zemljopisna \u0161irina", - "longitude": "Zemljopisna du\u017eina", - "radius": "Radius (koriste\u0107i sustav osnovne jedinice)" - }, - "title": "Ispunite podatke o lokaciji." - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/wwlln/translations/hu.json b/homeassistant/components/wwlln/translations/hu.json deleted file mode 100644 index 740fc1a8179..00000000000 --- a/homeassistant/components/wwlln/translations/hu.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "config": { - "step": { - "user": { - "data": { - "latitude": "Sz\u00e9less\u00e9g", - "longitude": "Hossz\u00fas\u00e1g" - } - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/wwlln/translations/it.json b/homeassistant/components/wwlln/translations/it.json deleted file mode 100644 index 4193ccab890..00000000000 --- a/homeassistant/components/wwlln/translations/it.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "Questa posizione \u00e8 gi\u00e0 registrata." - }, - "step": { - "user": { - "data": { - "latitude": "Latitudine", - "longitude": "Longitudine", - "radius": "Raggio (utilizzando il tuo sistema di unit\u00e0 di misura di base)" - }, - "title": "Inserisci le informazioni sulla tua posizione." - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/wwlln/translations/ko.json b/homeassistant/components/wwlln/translations/ko.json deleted file mode 100644 index 5ddf4f05184..00000000000 --- a/homeassistant/components/wwlln/translations/ko.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "\uc774 \uc704\uce58\ub294 \uc774\ubbf8 \ub4f1\ub85d\ub418\uc5c8\uc2b5\ub2c8\ub2e4" - }, - "step": { - "user": { - "data": { - "latitude": "\uc704\ub3c4", - "longitude": "\uacbd\ub3c4", - "radius": "\ubc18\uacbd (\uae30\ubcf8 \ub2e8\uc704 \uc2dc\uc2a4\ud15c \uc0ac\uc6a9)" - }, - "title": "\uc704\uce58 \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694." - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/wwlln/translations/lb.json b/homeassistant/components/wwlln/translations/lb.json deleted file mode 100644 index dcdc2becf06..00000000000 --- a/homeassistant/components/wwlln/translations/lb.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "D\u00ebse Standuert ass scho registr\u00e9iert" - }, - "step": { - "user": { - "data": { - "latitude": "Breedegrad", - "longitude": "L\u00e4ngegrad", - "radius": "Radius (mat \u00e4ren Basis Unit\u00e9ite System)" - }, - "title": "F\u00ebllt \u00e4r Informatiounen aus." - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/wwlln/translations/nl.json b/homeassistant/components/wwlln/translations/nl.json deleted file mode 100644 index 34388295976..00000000000 --- a/homeassistant/components/wwlln/translations/nl.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "Deze locatie is al geregistreerd." - }, - "step": { - "user": { - "data": { - "latitude": "Breedtegraad", - "longitude": "Lengtegraad", - "radius": "Radius (met behulp van uw basisstation systeem)" - }, - "title": "Vul uw locatiegegevens in." - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/wwlln/translations/no.json b/homeassistant/components/wwlln/translations/no.json deleted file mode 100644 index 4ce9b99b738..00000000000 --- a/homeassistant/components/wwlln/translations/no.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "Denne plasseringen er allerede registrert." - }, - "step": { - "user": { - "data": { - "latitude": "Breddegrad", - "longitude": "Lengdegrad", - "radius": "Radius (ved hjelp av ditt basenhetssystem)" - }, - "title": "Fyll ut posisjonsinformasjonen din." - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/wwlln/translations/pl.json b/homeassistant/components/wwlln/translations/pl.json deleted file mode 100644 index 04071c31cb1..00000000000 --- a/homeassistant/components/wwlln/translations/pl.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "Ta lokalizacja jest ju\u017c zarejestrowana." - }, - "step": { - "user": { - "data": { - "latitude": "Szeroko\u015b\u0107 geograficzna", - "longitude": "D\u0142ugo\u015b\u0107 geograficzna", - "radius": "Promie\u0144 (przy u\u017cyciu systemu jednostki bazowej)" - }, - "title": "Wprowad\u017a informacje o lokalizacji." - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/wwlln/translations/pt-BR.json b/homeassistant/components/wwlln/translations/pt-BR.json deleted file mode 100644 index 3c64c1534a1..00000000000 --- a/homeassistant/components/wwlln/translations/pt-BR.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "Este local j\u00e1 est\u00e1 registrado." - }, - "step": { - "user": { - "data": { - "latitude": "Latitude", - "longitude": "Longitude", - "radius": "Raio (usando seu sistema de unidade base)" - }, - "title": "Preencha suas informa\u00e7\u00f5es de localiza\u00e7\u00e3o." - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/wwlln/translations/pt.json b/homeassistant/components/wwlln/translations/pt.json deleted file mode 100644 index c7081cd694a..00000000000 --- a/homeassistant/components/wwlln/translations/pt.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "config": { - "step": { - "user": { - "data": { - "latitude": "Latitude", - "longitude": "Longitude" - } - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/wwlln/translations/ru.json b/homeassistant/components/wwlln/translations/ru.json deleted file mode 100644 index 007704bf406..00000000000 --- a/homeassistant/components/wwlln/translations/ru.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." - }, - "step": { - "user": { - "data": { - "latitude": "\u0428\u0438\u0440\u043e\u0442\u0430", - "longitude": "\u0414\u043e\u043b\u0433\u043e\u0442\u0430", - "radius": "\u0420\u0430\u0434\u0438\u0443\u0441 (\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f \u0412\u0430\u0448\u0443 \u0431\u0430\u0437\u043e\u0432\u0443\u044e \u0441\u0438\u0441\u0442\u0435\u043c\u0443 \u0435\u0434\u0438\u043d\u0438\u0446)" - }, - "title": "\u041c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/wwlln/translations/sl.json b/homeassistant/components/wwlln/translations/sl.json deleted file mode 100644 index 55712fd8590..00000000000 --- a/homeassistant/components/wwlln/translations/sl.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "Ta lokacija je \u017ee registrirana." - }, - "step": { - "user": { - "data": { - "latitude": "Zemljepisna \u0161irina", - "longitude": "Zemljepisna dol\u017eina", - "radius": "Obmo\u010dje (z uporabo va\u0161ih osnovnih enot)" - }, - "title": "Izpolnite podatke o va\u0161i lokaciji." - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/wwlln/translations/sv.json b/homeassistant/components/wwlln/translations/sv.json deleted file mode 100644 index 22a91d7dfcc..00000000000 --- a/homeassistant/components/wwlln/translations/sv.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "config": { - "step": { - "user": { - "data": { - "latitude": "Latitud", - "longitude": "Longitud", - "radius": "Radie (i basinst\u00e4llningarnas enheter)" - }, - "title": "Fyll i platsinformation." - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/wwlln/translations/zh-Hans.json b/homeassistant/components/wwlln/translations/zh-Hans.json deleted file mode 100644 index 5346a777cd6..00000000000 --- a/homeassistant/components/wwlln/translations/zh-Hans.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "config": { - "step": { - "user": { - "data": { - "latitude": "\u7eac\u5ea6", - "longitude": "\u7ecf\u5ea6", - "radius": "\u534a\u5f84\uff08\u4f7f\u7528\u57fa\u672c\u5355\u4f4d\u7cfb\u7edf\uff09" - }, - "title": "\u586b\u5199\u60a8\u7684\u4f4d\u7f6e\u4fe1\u606f\u3002" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/components/wwlln/translations/zh-Hant.json b/homeassistant/components/wwlln/translations/zh-Hant.json deleted file mode 100644 index 7b4bfaa1c08..00000000000 --- a/homeassistant/components/wwlln/translations/zh-Hant.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "config": { - "abort": { - "already_configured": "\u6b64\u4f4d\u7f6e\u5df2\u8a3b\u518a\u3002" - }, - "step": { - "user": { - "data": { - "latitude": "\u7def\u5ea6", - "longitude": "\u7d93\u5ea6", - "radius": "\u534a\u5f91\uff08\u4f7f\u7528\u57fa\u672c\u55ae\u4f4d\u7cfb\u7d71\uff09" - }, - "title": "\u586b\u5beb\u5ea7\u6a19\u8cc7\u8a0a\u3002" - } - } - } -} \ No newline at end of file diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 208ea3ef07f..468fb5c200c 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -163,7 +163,6 @@ FLOWS = [ "wiffi", "withings", "wled", - "wwlln", "xiaomi_miio", "zerproc", "zha", diff --git a/requirements_all.txt b/requirements_all.txt index ecd24e3ced4..ffe18f964f5 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -223,9 +223,6 @@ aioswitcher==1.2.0 # homeassistant.components.unifi aiounifi==22 -# homeassistant.components.wwlln -aiowwlln==2.0.2 - # homeassistant.components.airly airly==0.0.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 693c6bdb060..b4b4146511a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -112,9 +112,6 @@ aioswitcher==1.2.0 # homeassistant.components.unifi aiounifi==22 -# homeassistant.components.wwlln -aiowwlln==2.0.2 - # homeassistant.components.airly airly==0.0.2 diff --git a/tests/components/wwlln/__init__.py b/tests/components/wwlln/__init__.py deleted file mode 100644 index c44245e5988..00000000000 --- a/tests/components/wwlln/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Define tests for the WWLLN component.""" diff --git a/tests/components/wwlln/conftest.py b/tests/components/wwlln/conftest.py deleted file mode 100644 index 787b68aebcc..00000000000 --- a/tests/components/wwlln/conftest.py +++ /dev/null @@ -1,28 +0,0 @@ -"""Define various utilities for WWLLN tests.""" -import pytest - -from homeassistant.components.wwlln import CONF_WINDOW, DOMAIN -from homeassistant.const import ( - CONF_LATITUDE, - CONF_LONGITUDE, - CONF_RADIUS, - CONF_UNIT_SYSTEM, -) - -from tests.common import MockConfigEntry - - -@pytest.fixture -def config_entry(): - """Create a mock WWLLN config entry.""" - return MockConfigEntry( - domain=DOMAIN, - data={ - CONF_LATITUDE: 39.128712, - CONF_LONGITUDE: -104.9812612, - CONF_RADIUS: 25, - CONF_UNIT_SYSTEM: "metric", - CONF_WINDOW: 3600, - }, - title="39.128712, -104.9812612", - ) diff --git a/tests/components/wwlln/test_config_flow.py b/tests/components/wwlln/test_config_flow.py deleted file mode 100644 index b5d34f542e3..00000000000 --- a/tests/components/wwlln/test_config_flow.py +++ /dev/null @@ -1,135 +0,0 @@ -"""Define tests for the WWLLN config flow.""" -from homeassistant import data_entry_flow -from homeassistant.components.wwlln import ( - CONF_WINDOW, - DATA_CLIENT, - DOMAIN, - async_setup_entry, -) -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 - - -async def test_duplicate_error(hass, config_entry): - """Test that errors are shown when duplicates are added.""" - conf = {CONF_LATITUDE: 39.128712, CONF_LONGITUDE: -104.9812612, CONF_RADIUS: 25} - - MockConfigEntry( - domain=DOMAIN, unique_id="39.128712, -104.9812612", data=conf - ).add_to_hass(hass) - - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_USER}, data=conf - ) - - assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT - assert result["reason"] == "already_configured" - - -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.""" - conf = { - CONF_LATITUDE: 39.128712, - CONF_LONGITUDE: -104.9812612, - CONF_RADIUS: 25, - } - - 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"] == "39.128712, -104.9812612" - assert result["data"] == { - CONF_LATITUDE: 39.128712, - CONF_LONGITUDE: -104.9812612, - CONF_RADIUS: 25, - CONF_WINDOW: 3600.0, - } - - -async def test_step_user(hass): - """Test that the user step works.""" - conf = {CONF_LATITUDE: 39.128712, CONF_LONGITUDE: -104.9812612, CONF_RADIUS: 25} - - 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"] == "39.128712, -104.9812612" - assert result["data"] == { - CONF_LATITUDE: 39.128712, - CONF_LONGITUDE: -104.9812612, - CONF_RADIUS: 25, - CONF_WINDOW: 3600.0, - } - - -async def test_different_unit_system(hass): - """Test that the config flow picks up the HASS unit system.""" - conf = { - CONF_LATITUDE: 39.128712, - CONF_LONGITUDE: -104.9812612, - CONF_RADIUS: 25, - } - - 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"] == "39.128712, -104.9812612" - assert result["data"] == { - CONF_LATITUDE: 39.128712, - CONF_LONGITUDE: -104.9812612, - CONF_RADIUS: 25, - CONF_WINDOW: 3600.0, - } - - -async def test_custom_window(hass): - """Test that a custom window is stored correctly.""" - conf = { - CONF_LATITUDE: 39.128712, - CONF_LONGITUDE: -104.9812612, - CONF_RADIUS: 25, - CONF_WINDOW: 7200, - } - - 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"] == "39.128712, -104.9812612" - assert result["data"] == { - CONF_LATITUDE: 39.128712, - CONF_LONGITUDE: -104.9812612, - CONF_RADIUS: 25, - CONF_WINDOW: 7200, - } - - -async def test_component_load_config_entry(hass, config_entry): - """Test that loading an existing config entry yields a client.""" - config_entry.add_to_hass(hass) - with patch.object(hass.config_entries, "async_forward_entry_setup") as forward_mock: - assert await async_setup_entry(hass, config_entry) - - await hass.async_block_till_done() - assert forward_mock.call_count == 1 - assert len(hass.data[DOMAIN][DATA_CLIENT]) == 1 From db92ffdf89a0b9a0d2607f5d34c19c32a65e518a Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Mon, 25 May 2020 16:36:49 -0400 Subject: [PATCH 188/406] Clean up vizio translation strings (#35725) * further vizio string updates * fix errant find/replace --- homeassistant/components/vizio/config_flow.py | 8 ++++---- homeassistant/components/vizio/strings.json | 16 ++++++++-------- tests/components/vizio/test_config_flow.py | 8 ++++---- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/vizio/config_flow.py b/homeassistant/components/vizio/config_flow.py index 379f6c48ace..da4c7cc8b13 100644 --- a/homeassistant/components/vizio/config_flow.py +++ b/homeassistant/components/vizio/config_flow.py @@ -229,7 +229,7 @@ class VizioConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): user_input[CONF_DEVICE_CLASS], session=async_get_clientsession(self.hass, False), ): - errors["base"] = "cant_connect" + errors["base"] = "cannot_connect" if not errors: unique_id = await VizioAsync.get_unique_id( @@ -323,7 +323,7 @@ class VizioConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): ) return self.async_abort(reason="updated_entry") - return self.async_abort(reason="already_setup") + return self.async_abort(reason="already_configured_device") self._must_show_form = True # Store config key/value pairs that are not configurable in user step so they @@ -354,7 +354,7 @@ class VizioConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): continue if _host_is_same(entry.data[CONF_HOST], discovery_info[CONF_HOST]): - return self.async_abort(reason="already_setup") + return self.async_abort(reason="already_configured_device") # Set default name to discovered device name by stripping zeroconf service # (`type`) from `name` @@ -400,7 +400,7 @@ class VizioConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return self.async_show_form( step_id="user", data_schema=_get_config_schema(self._data), - errors={"base": "cant_connect"}, + errors={"base": "cannot_connect"}, ) # Complete pairing process if PIN has been provided diff --git a/homeassistant/components/vizio/strings.json b/homeassistant/components/vizio/strings.json index 9cceb6c35a2..0de1a380d12 100644 --- a/homeassistant/components/vizio/strings.json +++ b/homeassistant/components/vizio/strings.json @@ -2,7 +2,7 @@ "config": { "step": { "user": { - "title": "Setup VIZIO SmartCast Device", + "title": "VIZIO SmartCast Device", "description": "An [%key:common::config_flow::data::access_token%] is only needed for TVs. If you are configuring a TV and do not have an [%key:common::config_flow::data::access_token%] yet, leave it blank to go through a pairing process.", "data": { "name": "Name", @@ -20,28 +20,28 @@ }, "pairing_complete": { "title": "Pairing Complete", - "description": "Your VIZIO SmartCast device is now connected to Home Assistant." + "description": "Your [%key:component::vizio::config::step::user::title%] is now connected to Home Assistant." }, "pairing_complete_import": { "title": "Pairing Complete", - "description": "Your VIZIO SmartCast TV is now connected to Home Assistant.\n\nYour [%key:common::config_flow::data::access_token%] is '**{access_token}**'." + "description": "Your [%key:component::vizio::config::step::user::title%] is now connected to Home Assistant.\n\nYour [%key:common::config_flow::data::access_token%] is '**{access_token}**'." } }, "error": { - "host_exists": "VIZIO device with specified host already configured.", - "name_exists": "VIZIO device with specified name already configured.", + "host_exists": "[%key:component::vizio::config::step::user::title%] with specified host already configured.", + "name_exists": "[%key:component::vizio::config::step::user::title%] with specified name already configured.", "complete_pairing_failed": "Unable to complete pairing. Ensure the PIN you provided is correct and the TV is still powered and connected to the network before resubmitting.", - "cant_connect": "[%key:common::config_flow::error::cannot_connect%]" + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" }, "abort": { - "already_setup": "This entry has already been setup.", + "already_configured_device": "[%key:common::config_flow::abort::already_configured_device%]", "updated_entry": "This entry has already been setup but the name, apps, and/or options defined in the configuration do not match the previously imported configuration, so the configuration entry has been updated accordingly." } }, "options": { "step": { "init": { - "title": "Update VIZIO SmartCast Options", + "title": "Update [%key:component::vizio::config::step::user::title%] Options", "description": "If you have a Smart TV, you can optionally filter your source list by choosing which apps to include or exclude in your source list.", "data": { "volume_step": "Volume Step Size", diff --git a/tests/components/vizio/test_config_flow.py b/tests/components/vizio/test_config_flow.py index 0c057ba1a71..2b1908dc770 100644 --- a/tests/components/vizio/test_config_flow.py +++ b/tests/components/vizio/test_config_flow.py @@ -316,7 +316,7 @@ async def test_user_error_on_could_not_connect( ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["errors"] == {"base": "cant_connect"} + assert result["errors"] == {"base": "cannot_connect"} async def test_user_tv_pairing_no_apps( @@ -363,7 +363,7 @@ async def test_user_start_pairing_failure( assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["step_id"] == "user" - assert result["errors"] == {"base": "cant_connect"} + assert result["errors"] == {"base": "cannot_connect"} async def test_user_invalid_pin( @@ -471,7 +471,7 @@ async def test_import_entity_already_configured( ) assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT - assert result["reason"] == "already_setup" + assert result["reason"] == "already_configured_device" async def test_import_flow_update_options( @@ -778,7 +778,7 @@ async def test_zeroconf_flow_already_configured( # Flow should abort because device is already setup assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT - assert result["reason"] == "already_setup" + assert result["reason"] == "already_configured_device" async def test_zeroconf_dupe_fail( From 376e0e0e938440cc8758599cb5ee724f153ec812 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Mon, 25 May 2020 23:13:34 +0200 Subject: [PATCH 189/406] Add Axis MQTT support (#36015) * Working PoC * Store * Handle subscribing to MQTT and stopping stream when first telegram arrives * Improve naming * Now with test * Better strings * Fix Martins comments * Improve mock device patching * Bump dependency to v27 Add MQTT as after dependency --- homeassistant/components/axis/device.py | 61 ++++++++++++++---- homeassistant/components/axis/manifest.json | 3 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/axis/test_device.py | 69 ++++++++++++++++++--- 5 files changed, 115 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/axis/device.py b/homeassistant/components/axis/device.py index 3483bfbea2e..cc327cb3b65 100644 --- a/homeassistant/components/axis/device.py +++ b/homeassistant/components/axis/device.py @@ -5,8 +5,12 @@ import asyncio import async_timeout import axis from axis.event_stream import OPERATION_INITIALIZED -from axis.streammanager import SIGNAL_PLAYING +from axis.mqtt import mqtt_json_to_event +from axis.streammanager import SIGNAL_PLAYING, STATE_STOPPED +from homeassistant.components import mqtt +from homeassistant.components.mqtt import DOMAIN as MQTT_DOMAIN +from homeassistant.components.mqtt.models import Message from homeassistant.const import ( CONF_HOST, CONF_NAME, @@ -15,10 +19,11 @@ from homeassistant.const import ( CONF_TRIGGER_TIME, CONF_USERNAME, ) -from homeassistant.core import callback +from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from homeassistant.helpers.dispatcher import async_dispatcher_send +from homeassistant.setup import async_when_setup from .const import ( ATTR_MANUFACTURER, @@ -122,6 +127,7 @@ class AxisNetworkDevice: async def async_new_address_callback(hass, entry): """Handle signals of device getting new address. + Called when config entry is updated. This is a static method because a class method (bound method), can not be used with weak references. """ @@ -142,6 +148,25 @@ class AxisNetworkDevice: sw_version=self.fw_version, ) + async def use_mqtt(self, hass: HomeAssistant, component: str) -> None: + """Set up to use MQTT.""" + status = await hass.async_add_executor_job( + self.api.vapix.mqtt.get_client_status + ) + + if status.get("data", {}).get("status", {}).get("state") == "active": + self.listeners.append( + await mqtt.async_subscribe(hass, f"{self.serial}/#", self.mqtt_message) + ) + + @callback + def mqtt_message(self, message: Message) -> None: + """Receive Axis MQTT message.""" + self.disconnect_from_stream() + + event = mqtt_json_to_event(message.payload) + self.api.event.process_event(event) + async def async_setup(self): """Set up the device.""" try: @@ -173,11 +198,14 @@ class AxisNetworkDevice: ] ) if self.option_events: - self.api.stream.connection_status_callback = ( + self.api.stream.connection_status_callback.append( self.async_connection_status_callback ) self.api.enable_events(event_callback=self.async_event_callback) - self.api.start() + self.api.stream.start() + + if self.api.vapix.mqtt: + async_when_setup(self.hass, MQTT_DOMAIN, self.use_mqtt) self.hass.async_create_task(start_platforms()) @@ -185,14 +213,23 @@ class AxisNetworkDevice: return True + @callback + def disconnect_from_stream(self): + """Stop stream.""" + if self.api.stream.state != STATE_STOPPED: + self.api.stream.connection_status_callback.remove( + self.async_connection_status_callback + ) + self.api.stream.stop() + @callback def shutdown(self, event): """Stop the event stream.""" - self.api.stop() + self.disconnect_from_stream() async def async_reset(self): """Reset this device to default state.""" - self.api.stop() + self.disconnect_from_stream() unload_ok = all( await asyncio.gather( @@ -226,11 +263,13 @@ async def get_device(hass, host, port, username, password): try: with async_timeout.timeout(15): - await asyncio.gather( - hass.async_add_executor_job(device.vapix.params.update_brand), - hass.async_add_executor_job(device.vapix.params.update_properties), - hass.async_add_executor_job(device.vapix.ports.update), - ) + for vapix_call in ( + device.vapix.initialize_api_discovery, + device.vapix.params.update_brand, + device.vapix.params.update_properties, + device.vapix.ports.update, + ): + await hass.async_add_executor_job(vapix_call) return device diff --git a/homeassistant/components/axis/manifest.json b/homeassistant/components/axis/manifest.json index d532ac4c9e4..dd33cdbf1ea 100644 --- a/homeassistant/components/axis/manifest.json +++ b/homeassistant/components/axis/manifest.json @@ -3,7 +3,8 @@ "name": "Axis", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/axis", - "requirements": ["axis==26"], + "requirements": ["axis==27"], "zeroconf": ["_axis-video._tcp.local."], + "after_dependencies": ["mqtt"], "codeowners": ["@Kane610"] } diff --git a/requirements_all.txt b/requirements_all.txt index ffe18f964f5..4613422e270 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -300,7 +300,7 @@ avea==1.4 avri-api==0.1.7 # homeassistant.components.axis -axis==26 +axis==27 # homeassistant.components.azure_event_hub azure-eventhub==1.3.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index b4b4146511a..d95db048d98 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -141,7 +141,7 @@ async-upnp-client==0.14.13 av==8.0.1 # homeassistant.components.axis -axis==26 +axis==27 # homeassistant.components.homekit base36==0.1.1 diff --git a/tests/components/axis/test_device.py b/tests/components/axis/test_device.py index facb6f7de42..a61176d84a7 100644 --- a/tests/components/axis/test_device.py +++ b/tests/components/axis/test_device.py @@ -1,5 +1,6 @@ """Test Axis device.""" from copy import deepcopy +from unittest import mock import axis as axislib from axis.event_stream import OPERATION_INITIALIZED @@ -13,6 +14,7 @@ from homeassistant.components.axis.const import ( CONF_MODEL, DOMAIN as AXIS_DOMAIN, ) +from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN from homeassistant.const import ( CONF_HOST, CONF_MAC, @@ -23,7 +25,11 @@ from homeassistant.const import ( ) from tests.async_mock import Mock, patch -from tests.common import MockConfigEntry +from tests.common import ( + MockConfigEntry, + async_fire_mqtt_message, + async_mock_mqtt_component, +) MAC = "00408C12345" MODEL = "model" @@ -41,6 +47,17 @@ ENTRY_CONFIG = { CONF_NAME: NAME, } +DEFAULT_API_DISCOVERY = { + "method": "getApiList", + "apiVersion": "1.0", + "data": { + "apiList": [ + {"id": "api-discovery", "version": "1.0", "name": "API Discovery Service"}, + {"id": "param-cgi", "version": "1.0", "name": "Legacy Parameter Handling"}, + ] + }, +} + DEFAULT_BRAND = """root.Brand.Brand=AXIS root.Brand.ProdFullName=AXIS M1065-LW Network Camera root.Brand.ProdNbr=M1065-LW @@ -76,6 +93,7 @@ async def setup_axis_integration( hass, config=ENTRY_CONFIG, options=ENTRY_OPTIONS, + api_discovery=DEFAULT_API_DISCOVERY, brand=DEFAULT_BRAND, ports=DEFAULT_PORTS, properties=DEFAULT_PROPERTIES, @@ -91,6 +109,9 @@ async def setup_axis_integration( ) config_entry.add_to_hass(hass) + def mock_update_api_discovery(self): + self.process_raw(api_discovery) + def mock_update_brand(self): self.process_raw(brand) @@ -100,15 +121,17 @@ async def setup_axis_integration( def mock_update_properties(self): self.process_raw(properties) - with patch("axis.param_cgi.Brand.update_brand", new=mock_update_brand), patch( + with patch( + "axis.api_discovery.ApiDiscovery.update", new=mock_update_api_discovery + ), patch("axis.param_cgi.Brand.update_brand", new=mock_update_brand), patch( "axis.param_cgi.Ports.update_ports", new=mock_update_ports ), patch( "axis.param_cgi.Properties.update_properties", new=mock_update_properties ), patch( - "axis.AxisDevice.start", return_value=True + "axis.rtsp.RTSPClient.start", return_value=True, ): await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + await hass.async_block_till_done() return hass.data[AXIS_DOMAIN].get(config_entry.unique_id) @@ -134,6 +157,36 @@ async def test_device_setup(hass): assert device.serial == ENTRY_CONFIG[CONF_MAC] +async def test_device_support_mqtt(hass): + """Successful setup.""" + api_discovery = deepcopy(DEFAULT_API_DISCOVERY) + api_discovery["data"]["apiList"].append( + {"id": "mqtt-client", "version": "1.0", "name": "MQTT Client API"} + ) + get_client_status = {"data": {"status": {"state": "active"}}} + + mock_mqtt = await async_mock_mqtt_component(hass) + + with patch( + "axis.mqtt.MqttClient.get_client_status", return_value=get_client_status + ): + await setup_axis_integration(hass, api_discovery=api_discovery) + + mock_mqtt.async_subscribe.assert_called_with(f"{MAC}/#", mock.ANY, 0, "utf-8") + + topic = f"{MAC}/event/tns:onvif/Device/tns:axis/Sensor/PIR/$source/sensor/0" + message = b'{"timestamp": 1590258472044, "topic": "onvif:Device/axis:Sensor/PIR", "message": {"source": {"sensor": "0"}, "key": {}, "data": {"state": "1"}}}' + + assert len(hass.states.async_entity_ids(BINARY_SENSOR_DOMAIN)) == 0 + async_fire_mqtt_message(hass, topic, message) + await hass.async_block_till_done() + assert len(hass.states.async_entity_ids(BINARY_SENSOR_DOMAIN)) == 1 + + pir = hass.states.get(f"binary_sensor.{NAME}_pir_0") + assert pir.state == "on" + assert pir.name == f"{NAME} PIR 0" + + async def test_update_address(hass): """Test update address works.""" device = await setup_axis_integration(hass) @@ -208,13 +261,13 @@ async def test_shutdown(): axis_device.shutdown(None) - assert len(axis_device.api.stop.mock_calls) == 1 + assert len(axis_device.api.stream.stop.mock_calls) == 1 async def test_get_device_fails(hass): """Device unauthorized yields authentication required error.""" with patch( - "axis.param_cgi.Params.update_brand", side_effect=axislib.Unauthorized + "axis.api_discovery.ApiDiscovery.update", side_effect=axislib.Unauthorized ), pytest.raises(axis.errors.AuthenticationRequired): await axis.device.get_device(hass, host="", port="", username="", password="") @@ -222,7 +275,7 @@ async def test_get_device_fails(hass): async def test_get_device_device_unavailable(hass): """Device unavailable yields cannot connect error.""" with patch( - "axis.param_cgi.Params.update_brand", side_effect=axislib.RequestError + "axis.api_discovery.ApiDiscovery.update", side_effect=axislib.RequestError ), pytest.raises(axis.errors.CannotConnect): await axis.device.get_device(hass, host="", port="", username="", password="") @@ -230,6 +283,6 @@ async def test_get_device_device_unavailable(hass): async def test_get_device_unknown_error(hass): """Device yield unknown error.""" with patch( - "axis.param_cgi.Params.update_brand", side_effect=axislib.AxisException + "axis.api_discovery.ApiDiscovery.update", side_effect=axislib.AxisException ), pytest.raises(axis.errors.AuthenticationRequired): await axis.device.get_device(hass, host="", port="", username="", password="") From 6fbc3c4a517271f88316c3d4d7f4f315c88a7d9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mariusz=20Kry=C5=84ski?= Date: Mon, 25 May 2020 23:51:09 +0200 Subject: [PATCH 190/406] Add lock platform to ozw component (#36103) Co-authored-by: Martin Hjelmare --- homeassistant/components/ozw/const.py | 2 + homeassistant/components/ozw/discovery.py | 10 +++ homeassistant/components/ozw/lock.py | 39 +++++++++++ tests/components/ozw/conftest.py | 17 +++++ tests/components/ozw/test_lock.py | 41 ++++++++++++ tests/fixtures/ozw/lock.json | 25 +++++++ tests/fixtures/ozw/lock_network_dump.csv | 79 +++++++++++++++++++++++ 7 files changed, 213 insertions(+) create mode 100644 homeassistant/components/ozw/lock.py create mode 100644 tests/components/ozw/test_lock.py create mode 100644 tests/fixtures/ozw/lock.json create mode 100644 tests/fixtures/ozw/lock_network_dump.csv diff --git a/homeassistant/components/ozw/const.py b/homeassistant/components/ozw/const.py index aaf67479ba5..cbd85af4b42 100644 --- a/homeassistant/components/ozw/const.py +++ b/homeassistant/components/ozw/const.py @@ -2,6 +2,7 @@ from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN from homeassistant.components.climate import DOMAIN as CLIMATE_DOMAIN from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN +from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN @@ -11,6 +12,7 @@ PLATFORMS = [ BINARY_SENSOR_DOMAIN, CLIMATE_DOMAIN, LIGHT_DOMAIN, + LOCK_DOMAIN, SENSOR_DOMAIN, SWITCH_DOMAIN, ] diff --git a/homeassistant/components/ozw/discovery.py b/homeassistant/components/ozw/discovery.py index 297b00e4a88..2a403964370 100644 --- a/homeassistant/components/ozw/discovery.py +++ b/homeassistant/components/ozw/discovery.py @@ -199,6 +199,16 @@ DISCOVERY_SCHEMAS = ( } }, }, + { # Lock platform + const.DISC_COMPONENT: "lock", + const.DISC_VALUES: { + const.DISC_PRIMARY: { + const.DISC_COMMAND_CLASS: (CommandClass.DOOR_LOCK,), + const.DISC_TYPE: ValueType.BOOL, + const.DISC_GENRE: ValueGenre.USER, + } + }, + }, ) diff --git a/homeassistant/components/ozw/lock.py b/homeassistant/components/ozw/lock.py new file mode 100644 index 00000000000..60e0ee7ffd3 --- /dev/null +++ b/homeassistant/components/ozw/lock.py @@ -0,0 +1,39 @@ +"""Representation of Z-Wave locks.""" +from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN, LockEntity +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 lock from config entry.""" + + @callback + def async_add_lock(value): + """Add Z-Wave Lock.""" + lock = ZWaveLock(value) + + async_add_entities([lock]) + + hass.data[DOMAIN][config_entry.entry_id][DATA_UNSUBSCRIBE].append( + async_dispatcher_connect(hass, f"{DOMAIN}_new_{LOCK_DOMAIN}", async_add_lock) + ) + + +class ZWaveLock(ZWaveDeviceEntity, LockEntity): + """Representation of a Z-Wave lock.""" + + @property + def is_locked(self): + """Return a boolean for the state of the lock.""" + return bool(self.values.primary.value) + + async def async_lock(self, **kwargs): + """Lock the lock.""" + self.values.primary.send_value(True) + + async def async_unlock(self, **kwargs): + """Unlock the lock.""" + self.values.primary.send_value(False) diff --git a/tests/components/ozw/conftest.py b/tests/components/ozw/conftest.py index 568b2137d9a..b9c7e17ba62 100644 --- a/tests/components/ozw/conftest.py +++ b/tests/components/ozw/conftest.py @@ -27,6 +27,12 @@ def climate_data_fixture(): return load_fixture("ozw/climate_network_dump.csv") +@pytest.fixture(name="lock_data", scope="session") +def lock_data_fixture(): + """Load lock MQTT data and return it.""" + return load_fixture("ozw/lock_network_dump.csv") + + @pytest.fixture(name="sent_messages") def sent_messages_fixture(): """Fixture to capture sent messages.""" @@ -105,3 +111,14 @@ async def climate_msg_fixture(hass): message = MQTTMessage(topic=sensor_json["topic"], payload=sensor_json["payload"]) message.encode() return message + + +@pytest.fixture(name="lock_msg") +async def lock_msg_fixture(hass): + """Return a mock MQTT msg with a lock actuator message.""" + lock_json = json.loads( + await hass.async_add_executor_job(load_fixture, "ozw/lock.json") + ) + message = MQTTMessage(topic=lock_json["topic"], payload=lock_json["payload"]) + message.encode() + return message diff --git a/tests/components/ozw/test_lock.py b/tests/components/ozw/test_lock.py new file mode 100644 index 00000000000..d36c3d4bbbf --- /dev/null +++ b/tests/components/ozw/test_lock.py @@ -0,0 +1,41 @@ +"""Test Z-Wave Locks.""" +from .common import setup_ozw + + +async def test_lock(hass, lock_data, sent_messages, lock_msg): + """Test lock.""" + receive_message = await setup_ozw(hass, fixture=lock_data) + + # Test loaded + state = hass.states.get("lock.danalock_v3_btze_locked") + assert state is not None + assert state.state == "unlocked" + + # Test locking + await hass.services.async_call( + "lock", "lock", {"entity_id": "lock.danalock_v3_btze_locked"}, blocking=True + ) + assert len(sent_messages) == 1 + msg = sent_messages[0] + assert msg["topic"] == "OpenZWave/1/command/setvalue/" + assert msg["payload"] == {"Value": True, "ValueIDKey": 173572112} + + # Feedback on state + lock_msg.decode() + lock_msg.payload["Value"] = True + lock_msg.encode() + receive_message(lock_msg) + await hass.async_block_till_done() + + state = hass.states.get("lock.danalock_v3_btze_locked") + assert state is not None + assert state.state == "locked" + + # Test unlocking + await hass.services.async_call( + "lock", "unlock", {"entity_id": "lock.danalock_v3_btze_locked"}, blocking=True + ) + assert len(sent_messages) == 2 + msg = sent_messages[1] + assert msg["topic"] == "OpenZWave/1/command/setvalue/" + assert msg["payload"] == {"Value": False, "ValueIDKey": 173572112} diff --git a/tests/fixtures/ozw/lock.json b/tests/fixtures/ozw/lock.json new file mode 100644 index 00000000000..1ec2187abcb --- /dev/null +++ b/tests/fixtures/ozw/lock.json @@ -0,0 +1,25 @@ +{ + "topic": "OpenZWave/1/node/10/instance/1/commandclass/98/value/173572112/", + "payload": { + "Label": "Lock", + "Value": false, + "Units": "", + "Min": 0, + "Max": 0, + "Type": "Bool", + "Instance": 1, + "CommandClass": "COMMAND_CLASS_DOOR_LOCK", + "Index": 0, + "Node": 10, + "Genre": "User", + "Help": "Lock / Unlock Device", + "ValueIDKey": 173572112, + "ReadOnly": false, + "WriteOnly": false, + "ValueSet": false, + "ValuePolled": false, + "ChangeVerified": false, + "Event": "valueAdded", + "TimeStamp": 1579566891 + } +} diff --git a/tests/fixtures/ozw/lock_network_dump.csv b/tests/fixtures/ozw/lock_network_dump.csv new file mode 100644 index 00000000000..fdb4ce7353e --- /dev/null +++ b/tests/fixtures/ozw/lock_network_dump.csv @@ -0,0 +1,79 @@ +OpenZWave/1/status/,{ "OpenZWave_Version": "1.6.1131", "OZWDaemon_Version": "0.1.101", "QTOpenZWave_Version": "1.0.0", "QT_Version": "5.12.5", "Status": "driverAllNodesQueriedSomeDead", "TimeStamp": 1590178891, "ManufacturerSpecificDBReady": true, "homeID": 4075923038, "getControllerNodeId": 1, "getSUCNodeId": 0, "isPrimaryController": false, "isBridgeController": false, "hasExtendedTXStatistics": true, "getControllerLibraryVersion": "Z-Wave 4.05", "getControllerLibraryType": "Static Controller", "getControllerPath": "/dev/zwave"} +OpenZWave/1/node/10/,{ "NodeID": 10, "NodeQueryStage": "Complete", "isListening": false, "isFlirs": true, "isBeaming": true, "isRouting": true, "isSecurityv1": false, "isZWavePlus": true, "isNIFRecieved": true, "isAwake": true, "isFailed": false, "MetaData": { "OZWInfoURL": "", "ZWAProductURL": "", "ProductPic": "", "Description": "", "ProductManualURL": "", "ProductPageURL": "", "InclusionHelp": "", "ExclusionHelp": "", "ResetHelp": "", "WakeupHelp": "", "ProductSupportURL": "", "Frequency": "", "Name": "", "ProductPicBase64": "" }, "Event": "nodeQueriesComplete", "TimeStamp": 1590178891, "NodeManufacturerName": "Poly-control", "NodeProductName": "Danalock V3 BTZE", "NodeBasicString": "Routing Slave", "NodeBasic": 4, "NodeGenericString": "Entry Control", "NodeGeneric": 64, "NodeSpecificString": "Secure Keypad Door Lock", "NodeSpecific": 3, "NodeManufacturerID": "0x010e", "NodeProductType": "0x0009", "NodeProductID": "0x0001", "NodeBaudRate": 100000, "NodeVersion": 4, "NodeName": "", "NodeLocation": "", "NodeGroups": 1, "NodeDeviceTypeString": "Door Lock Keypad", "NodeDeviceType": 768, "NodeRole": 7, "NodeRoleString": "Listening Sleeping Slave", "NodePlusType": 0, "NodePlusTypeString": "Z-Wave+ node", "Neighbors": [ 1, 5, 9 ]} +OpenZWave/1/node/10/instance/1/,{ "Instance": 1, "TimeStamp": 1590178857} +OpenZWave/1/node/10/instance/1/commandclass/112/,{ "Instance": 1, "CommandClassId": 112, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "CommandClassVersion": 1, "TimeStamp": 1590178855} +OpenZWave/1/node/10/instance/1/commandclass/112/value/281475154706452/,{ "Label": "Twist Assist", "Value": { "List": [ { "Value": 0, "Label": "Disabled" }, { "Value": 1, "Label": "Enabled" } ], "Selected": "Disabled", "Selected_id": 0 }, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 1, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 1, "Node": 10, "Genre": "Config", "Help": "", "ValueIDKey": 281475154706452, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1590178855} +OpenZWave/1/node/10/instance/1/commandclass/112/value/562950131417107/,{ "Label": "Hold and Release", "Value": 0, "Units": "seconds", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 2, "Node": 10, "Genre": "Config", "Help": "0 Disable. 1 to 2147483647 Enable, time in seconds.", "ValueIDKey": 562950131417107, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1590178855} +OpenZWave/1/node/10/instance/1/commandclass/112/value/844425108127764/,{ "Label": "Block to Block", "Value": { "List": [ { "Value": 0, "Label": "Disable" }, { "Value": 1, "Label": "Enable" } ], "Selected": "Disable", "Selected_id": 0 }, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 1, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 3, "Node": 10, "Genre": "Config", "Help": "", "ValueIDKey": 844425108127764, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1590178855} +OpenZWave/1/node/10/instance/1/commandclass/112/value/1125900084838419/,{ "Label": "BLE Temporary Allowed", "Value": 0, "Units": "seconds", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 4, "Node": 10, "Genre": "Config", "Help": "0 Disable. 1 to 2147483647 Enable, time in seconds.", "ValueIDKey": 1125900084838419, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1590178855} +OpenZWave/1/node/10/instance/1/commandclass/112/value/1407375061549076/,{ "Label": "BLE Always Allowed", "Value": { "List": [ { "Value": 0, "Label": "Disable" }, { "Value": 1, "Label": "Enable" } ], "Selected": "Disable", "Selected_id": 0 }, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 1, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 5, "Node": 10, "Genre": "Config", "Help": "", "ValueIDKey": 1407375061549076, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1590178855} +OpenZWave/1/node/10/instance/1/commandclass/112/value/1688850038259731/,{ "Label": "Autolock", "Value": 0, "Units": "seconds", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 6, "Node": 10, "Genre": "Config", "Help": "0 Disable. 1 to 2147483647 Enable, time in seconds.", "ValueIDKey": 1688850038259731, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1590178856} +OpenZWave/1/node/10/instance/1/commandclass/94/,{ "Instance": 1, "CommandClassId": 94, "CommandClass": "COMMAND_CLASS_ZWAVEPLUS_INFO", "CommandClassVersion": 1, "TimeStamp": 1590178856} +OpenZWave/1/node/10/instance/1/commandclass/94/value/181895185/,{ "Label": "ZWave+ Version", "Value": 1, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_ZWAVEPLUS_INFO", "Index": 0, "Node": 10, "Genre": "System", "Help": "ZWave+ Version Supported on the Device", "ValueIDKey": 181895185, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1590178856} +OpenZWave/1/node/10/instance/1/commandclass/94/value/281475158605846/,{ "Label": "InstallerIcon", "Value": 768, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_ZWAVEPLUS_INFO", "Index": 1, "Node": 10, "Genre": "System", "Help": "Icon File to use for the Installer Application", "ValueIDKey": 281475158605846, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1590178856} +OpenZWave/1/node/10/instance/1/commandclass/94/value/562950135316502/,{ "Label": "UserIcon", "Value": 768, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_ZWAVEPLUS_INFO", "Index": 2, "Node": 10, "Genre": "System", "Help": "Icon File to use for the User Application", "ValueIDKey": 562950135316502, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1590178856} +OpenZWave/1/node/10/instance/1/commandclass/98/,{ "Instance": 1, "CommandClassId": 98, "CommandClass": "COMMAND_CLASS_DOOR_LOCK", "CommandClassVersion": 1, "TimeStamp": 1590178856} +OpenZWave/1/node/10/instance/1/commandclass/98/value/173572112/,{ "Label": "Locked", "Value": false, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "Bool", "Instance": 1, "CommandClass": "COMMAND_CLASS_DOOR_LOCK", "Index": 0, "Node": 10, "Genre": "User", "Help": "State of the Lock", "ValueIDKey": 173572112, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1590345913} +OpenZWave/1/node/10/instance/1/commandclass/98/value/281475150282772/,{ "Label": "Locked (Advanced)", "Value": { "List": [ { "Value": 0, "Label": "Unsecure" }, { "Value": 1, "Label": "Unsecured with Timeout" }, { "Value": 2, "Label": "Inside Handle Unsecured" }, { "Value": 3, "Label": "Inside Handle Unsecured with Timeout" }, { "Value": 4, "Label": "Outside Handle Unsecured" }, { "Value": 5, "Label": "Outside Handle Unsecured with Timeout" }, { "Value": 6, "Label": "Secured" }, { "Value": 255, "Label": "Invalid" } ], "Selected": "Unsecure", "Selected_id": 0 }, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_DOOR_LOCK", "Index": 1, "Node": 10, "Genre": "User", "Help": "State of the Lock (Advanced)", "ValueIDKey": 281475150282772, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1590345913} +OpenZWave/1/node/10/instance/1/commandclass/98/value/562950135382036/,{ "Label": "Timeout Mode", "Value": { "List": [ { "Value": 1, "Label": "No Timeout" }, { "Value": 2, "Label": "Secure Lock after Timeout" } ], "Selected": "No Timeout", "Selected_id": 1 }, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_DOOR_LOCK", "Index": 2, "Node": 10, "Genre": "System", "Help": "Timeout Mode for Reverting Lock State", "ValueIDKey": 562950135382036, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1590178856} +OpenZWave/1/node/10/instance/1/commandclass/98/value/1407375065514001/,{ "Label": "Outside Handle Control", "Value": 1, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_DOOR_LOCK", "Index": 5, "Node": 10, "Genre": "System", "Help": "State of the Exterior Handle Control", "ValueIDKey": 1407375065514001, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1590178856} +OpenZWave/1/node/10/instance/1/commandclass/98/value/1688850042224657/,{ "Label": "Inside Handle Control", "Value": 1, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_DOOR_LOCK", "Index": 6, "Node": 10, "Genre": "System", "Help": "State of the Interior Handle Control", "ValueIDKey": 1688850042224657, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1590178856} +OpenZWave/1/node/10/instance/1/commandclass/99/,{ "Instance": 1, "CommandClassId": 99, "CommandClass": "COMMAND_CLASS_USER_CODE", "CommandClassVersion": 1, "TimeStamp": 1590178856} +OpenZWave/1/node/10/instance/1/commandclass/99/value/72339069196615702/,{ "Label": "Code Count", "Value": 20, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_USER_CODE", "Index": 257, "Node": 10, "Genre": "System", "Help": "Number of User Codes supported by the Device", "ValueIDKey": 72339069196615702, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1590178856} +OpenZWave/1/node/10/instance/1/commandclass/99/value/71776119243194392/,{ "Label": "Refresh All UserCodes", "Value": false, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "Button", "Instance": 1, "CommandClass": "COMMAND_CLASS_USER_CODE", "Index": 255, "Node": 10, "Genre": "System", "Help": "Refresh All UserCodes Stored on Device", "ValueIDKey": 71776119243194392, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1590178856} +OpenZWave/1/node/10/instance/1/commandclass/99/value/72057594219905046/,{ "Label": "Remove User Code", "Value": 0, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_USER_CODE", "Index": 256, "Node": 10, "Genre": "System", "Help": "Remove A UserCode at the Specified Index", "ValueIDKey": 72057594219905046, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1590178856} +OpenZWave/1/node/10/instance/1/commandclass/99/value/173588503/,{ "Label": "Enrollment Code", "Value": "", "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_USER_CODE", "Index": 0, "Node": 10, "Genre": "User", "Help": "Enrollment Code", "ValueIDKey": 173588503, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1590178856} +OpenZWave/1/node/10/instance/1/commandclass/99/value/281475150299159/,{ "Label": "Code 1:", "Value": "", "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_USER_CODE", "Index": 1, "Node": 10, "Genre": "User", "Help": "UserCode 1", "ValueIDKey": 281475150299159, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1590178856} +OpenZWave/1/node/10/instance/1/commandclass/99/value/562950127009815/,{ "Label": "Code 2:", "Value": "", "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_USER_CODE", "Index": 2, "Node": 10, "Genre": "User", "Help": "UserCode 2", "ValueIDKey": 562950127009815, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1590178856} +OpenZWave/1/node/10/instance/1/commandclass/99/value/844425103720471/,{ "Label": "Code 3:", "Value": "", "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_USER_CODE", "Index": 3, "Node": 10, "Genre": "User", "Help": "UserCode 3", "ValueIDKey": 844425103720471, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1590178856} +OpenZWave/1/node/10/instance/1/commandclass/99/value/1125900080431127/,{ "Label": "Code 4:", "Value": "", "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_USER_CODE", "Index": 4, "Node": 10, "Genre": "User", "Help": "UserCode 4", "ValueIDKey": 1125900080431127, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1590178856} +OpenZWave/1/node/10/instance/1/commandclass/99/value/1407375057141783/,{ "Label": "Code 5:", "Value": "", "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_USER_CODE", "Index": 5, "Node": 10, "Genre": "User", "Help": "UserCode 5", "ValueIDKey": 1407375057141783, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1590178856} +OpenZWave/1/node/10/instance/1/commandclass/99/value/1688850033852439/,{ "Label": "Code 6:", "Value": "", "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_USER_CODE", "Index": 6, "Node": 10, "Genre": "User", "Help": "UserCode 6", "ValueIDKey": 1688850033852439, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1590178856} +OpenZWave/1/node/10/instance/1/commandclass/99/value/1970325010563095/,{ "Label": "Code 7:", "Value": "", "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_USER_CODE", "Index": 7, "Node": 10, "Genre": "User", "Help": "UserCode 7", "ValueIDKey": 1970325010563095, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1590178856} +OpenZWave/1/node/10/instance/1/commandclass/99/value/2251799987273751/,{ "Label": "Code 8:", "Value": "", "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_USER_CODE", "Index": 8, "Node": 10, "Genre": "User", "Help": "UserCode 8", "ValueIDKey": 2251799987273751, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1590178856} +OpenZWave/1/node/10/instance/1/commandclass/99/value/2533274963984407/,{ "Label": "Code 9:", "Value": "", "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_USER_CODE", "Index": 9, "Node": 10, "Genre": "User", "Help": "UserCode 9", "ValueIDKey": 2533274963984407, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1590178856} +OpenZWave/1/node/10/instance/1/commandclass/99/value/2814749940695063/,{ "Label": "Code 10:", "Value": "", "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_USER_CODE", "Index": 10, "Node": 10, "Genre": "User", "Help": "UserCode 10", "ValueIDKey": 2814749940695063, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1590178856} +OpenZWave/1/node/10/instance/1/commandclass/99/value/3096224917405719/,{ "Label": "Code 11:", "Value": "", "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_USER_CODE", "Index": 11, "Node": 10, "Genre": "User", "Help": "UserCode 11", "ValueIDKey": 3096224917405719, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1590178856} +OpenZWave/1/node/10/instance/1/commandclass/99/value/3377699894116375/,{ "Label": "Code 12:", "Value": "", "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_USER_CODE", "Index": 12, "Node": 10, "Genre": "User", "Help": "UserCode 12", "ValueIDKey": 3377699894116375, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1590178856} +OpenZWave/1/node/10/instance/1/commandclass/99/value/3659174870827031/,{ "Label": "Code 13:", "Value": "", "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_USER_CODE", "Index": 13, "Node": 10, "Genre": "User", "Help": "UserCode 13", "ValueIDKey": 3659174870827031, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1590178856} +OpenZWave/1/node/10/instance/1/commandclass/99/value/3940649847537687/,{ "Label": "Code 14:", "Value": "", "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_USER_CODE", "Index": 14, "Node": 10, "Genre": "User", "Help": "UserCode 14", "ValueIDKey": 3940649847537687, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1590178856} +OpenZWave/1/node/10/instance/1/commandclass/99/value/4222124824248343/,{ "Label": "Code 15:", "Value": "", "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_USER_CODE", "Index": 15, "Node": 10, "Genre": "User", "Help": "UserCode 15", "ValueIDKey": 4222124824248343, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1590178856} +OpenZWave/1/node/10/instance/1/commandclass/99/value/4503599800958999/,{ "Label": "Code 16:", "Value": "", "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_USER_CODE", "Index": 16, "Node": 10, "Genre": "User", "Help": "UserCode 16", "ValueIDKey": 4503599800958999, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1590178856} +OpenZWave/1/node/10/instance/1/commandclass/99/value/4785074777669655/,{ "Label": "Code 17:", "Value": "", "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_USER_CODE", "Index": 17, "Node": 10, "Genre": "User", "Help": "UserCode 17", "ValueIDKey": 4785074777669655, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1590178856} +OpenZWave/1/node/10/instance/1/commandclass/99/value/5066549754380311/,{ "Label": "Code 18:", "Value": "", "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_USER_CODE", "Index": 18, "Node": 10, "Genre": "User", "Help": "UserCode 18", "ValueIDKey": 5066549754380311, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1590178856} +OpenZWave/1/node/10/instance/1/commandclass/99/value/5348024731090967/,{ "Label": "Code 19:", "Value": "", "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_USER_CODE", "Index": 19, "Node": 10, "Genre": "User", "Help": "UserCode 19", "ValueIDKey": 5348024731090967, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1590178856} +OpenZWave/1/node/10/instance/1/commandclass/99/value/5629499707801623/,{ "Label": "Code 20:", "Value": "", "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_USER_CODE", "Index": 20, "Node": 10, "Genre": "User", "Help": "UserCode 20", "ValueIDKey": 5629499707801623, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1590178856} +OpenZWave/1/node/10/instance/1/commandclass/114/,{ "Instance": 1, "CommandClassId": 114, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "CommandClassVersion": 2, "TimeStamp": 1590178856} +OpenZWave/1/node/10/instance/1/commandclass/114/value/182222867/,{ "Label": "Loaded Config Revision", "Value": 0, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 0, "Node": 10, "Genre": "System", "Help": "Revision of the Config file currently loaded", "ValueIDKey": 182222867, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1590178856} +OpenZWave/1/node/10/instance/1/commandclass/114/value/281475158933523/,{ "Label": "Config File Revision", "Value": 15, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 1, "Node": 10, "Genre": "System", "Help": "Revision of the Config file on the File System", "ValueIDKey": 281475158933523, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1590178856} +OpenZWave/1/node/10/instance/1/commandclass/114/value/562950135644179/,{ "Label": "Latest Available Config File Revision", "Value": 15, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 2, "Node": 10, "Genre": "System", "Help": "Latest Revision of the Config file available for download", "ValueIDKey": 562950135644179, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1590178856} +OpenZWave/1/node/10/instance/1/commandclass/114/value/844425112354839/,{ "Label": "Device ID", "Value": "", "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 3, "Node": 10, "Genre": "System", "Help": "Manufacturer Specific Device ID/Model", "ValueIDKey": 844425112354839, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1590178856} +OpenZWave/1/node/10/instance/1/commandclass/114/value/1125900089065495/,{ "Label": "Serial Number", "Value": "3b548b972bf8", "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 4, "Node": 10, "Genre": "System", "Help": "Device Serial Number", "ValueIDKey": 1125900089065495, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1590178857} +OpenZWave/1/node/10/instance/1/commandclass/115/,{ "Instance": 1, "CommandClassId": 115, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "CommandClassVersion": 1, "TimeStamp": 1590178856} +OpenZWave/1/node/10/instance/1/commandclass/115/value/182239252/,{ "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", "Selected_id": 0 }, "Units": "dB", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 0, "Node": 10, "Genre": "System", "Help": "Output RF PowerLevel", "ValueIDKey": 182239252, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1590178856} +OpenZWave/1/node/10/instance/1/commandclass/115/value/281475158949905/,{ "Label": "Timeout", "Value": 0, "Units": "seconds", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 1, "Node": 10, "Genre": "System", "Help": "Timeout till the PowerLevel is reset to Normal", "ValueIDKey": 281475158949905, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1590178856} +OpenZWave/1/node/10/instance/1/commandclass/115/value/562950135660568/,{ "Label": "Set Powerlevel", "Value": false, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "Button", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 2, "Node": 10, "Genre": "System", "Help": "Apply the Output PowerLevel and Timeout Values", "ValueIDKey": 562950135660568, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1590178856} +OpenZWave/1/node/10/instance/1/commandclass/115/value/844425112371217/,{ "Label": "Test Node", "Value": 0, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 3, "Node": 10, "Genre": "System", "Help": "Node to Perform a test against", "ValueIDKey": 844425112371217, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1590178856} +OpenZWave/1/node/10/instance/1/commandclass/115/value/1125900089081876/,{ "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", "Selected_id": 0 }, "Units": "dB", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 4, "Node": 10, "Genre": "System", "Help": "PowerLevel to use for the Test", "ValueIDKey": 1125900089081876, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1590178856} +OpenZWave/1/node/10/instance/1/commandclass/115/value/1407375065792534/,{ "Label": "Frame Count", "Value": 0, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 5, "Node": 10, "Genre": "System", "Help": "How Many Messages to send to the Node for the Test", "ValueIDKey": 1407375065792534, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1590178856} +OpenZWave/1/node/10/instance/1/commandclass/115/value/1688850042503192/,{ "Label": "Test", "Value": false, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "Button", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 6, "Node": 10, "Genre": "System", "Help": "Perform a PowerLevel Test against the a Node", "ValueIDKey": 1688850042503192, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1590178856} +OpenZWave/1/node/10/instance/1/commandclass/115/value/1970325019213848/,{ "Label": "Report", "Value": false, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "Button", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 7, "Node": 10, "Genre": "System", "Help": "Get the results of the latest PowerLevel Test against a Node", "ValueIDKey": 1970325019213848, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1590178856} +OpenZWave/1/node/10/instance/1/commandclass/115/value/2251799995924500/,{ "Label": "Test Status", "Value": { "List": [ { "Value": 0, "Label": "Failed" }, { "Value": 1, "Label": "Success" }, { "Value": 2, "Label": "In Progress" } ], "Selected": "Failed", "Selected_id": 0 }, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 8, "Node": 10, "Genre": "System", "Help": "The Current Status of the last PowerNode Test Executed", "ValueIDKey": 2251799995924500, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1590178856} +OpenZWave/1/node/10/instance/1/commandclass/115/value/2533274972635158/,{ "Label": "Acked Frames", "Value": 0, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 9, "Node": 10, "Genre": "System", "Help": "Number of Messages successfully Acked by the Target Node", "ValueIDKey": 2533274972635158, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1590178856} +OpenZWave/1/node/10/instance/1/commandclass/128/,{ "Instance": 1, "CommandClassId": 128, "CommandClass": "COMMAND_CLASS_BATTERY", "CommandClassVersion": 1, "TimeStamp": 1590178856} +OpenZWave/1/node/10/instance/1/commandclass/128/value/174063633/,{ "Label": "Battery Level", "Value": 94, "Units": "%", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_BATTERY", "Index": 0, "Node": 10, "Genre": "User", "Help": "Current Battery Level", "ValueIDKey": 174063633, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1590178891} +OpenZWave/1/node/10/instance/1/commandclass/134/,{ "Instance": 1, "CommandClassId": 134, "CommandClass": "COMMAND_CLASS_VERSION", "CommandClassVersion": 1, "TimeStamp": 1590178856} +OpenZWave/1/node/10/instance/1/commandclass/134/value/182550551/,{ "Label": "Library Version", "Value": "3", "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_VERSION", "Index": 0, "Node": 10, "Genre": "System", "Help": "Z-Wave Library Version", "ValueIDKey": 182550551, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1590178857} +OpenZWave/1/node/10/instance/1/commandclass/134/value/281475159261207/,{ "Label": "Protocol Version", "Value": "4.61", "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_VERSION", "Index": 1, "Node": 10, "Genre": "System", "Help": "Z-Wave Protocol Version", "ValueIDKey": 281475159261207, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1590178857} +OpenZWave/1/node/10/instance/1/commandclass/134/value/562950135971863/,{ "Label": "Application Version", "Value": "1.02", "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_VERSION", "Index": 2, "Node": 10, "Genre": "System", "Help": "Application Version", "ValueIDKey": 562950135971863, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1590178857} +OpenZWave/1/node/10/instance/1/commandclass/139/,{ "Instance": 1, "CommandClassId": 139, "CommandClass": "COMMAND_CLASS_TIME_PARAMETERS", "CommandClassVersion": 1, "TimeStamp": 1590178856} +OpenZWave/1/node/10/instance/1/commandclass/139/value/182632471/,{ "Label": "Date", "Value": "22/05/2020", "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_TIME_PARAMETERS", "Index": 0, "Node": 10, "Genre": "System", "Help": "Current Date", "ValueIDKey": 182632471, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1590178858} +OpenZWave/1/node/10/instance/1/commandclass/139/value/281475159343127/,{ "Label": "Time", "Value": "20:20:57", "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_TIME_PARAMETERS", "Index": 1, "Node": 10, "Genre": "System", "Help": "Current Time", "ValueIDKey": 281475159343127, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1590178858} +OpenZWave/1/node/10/instance/1/commandclass/139/value/562950136053784/,{ "Label": "Set Date/Time", "Value": false, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "Button", "Instance": 1, "CommandClass": "COMMAND_CLASS_TIME_PARAMETERS", "Index": 2, "Node": 10, "Genre": "System", "Help": "Set the Date/Time", "ValueIDKey": 562950136053784, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1590178856} +OpenZWave/1/node/10/instance/1/commandclass/139/value/844425112764440/,{ "Label": "Refresh Date/Time", "Value": false, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "Button", "Instance": 1, "CommandClass": "COMMAND_CLASS_TIME_PARAMETERS", "Index": 3, "Node": 10, "Genre": "System", "Help": "Refresh the Date/Time", "ValueIDKey": 844425112764440, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1590178856} +OpenZWave/1/node/10/instance/1/commandclass/152/,{ "Instance": 1, "CommandClassId": 152, "CommandClass": "COMMAND_CLASS_SECURITY", "CommandClassVersion": 1, "TimeStamp": 1590178856} +OpenZWave/1/node/10/instance/1/commandclass/152/value/182845456/,{ "Label": "Secured", "Value": true, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "Bool", "Instance": 1, "CommandClass": "COMMAND_CLASS_SECURITY", "Index": 0, "Node": 10, "Genre": "System", "Help": "Is Communication with Device Encrypted", "ValueIDKey": 182845456, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1590178856} +OpenZWave/1/node/10/instance/1/commandclass/113/,{ "Instance": 1, "CommandClassId": 113, "CommandClass": "COMMAND_CLASS_NOTIFICATION", "CommandClassVersion": 8, "TimeStamp": 1590178857} +OpenZWave/1/node/10/instance/1/commandclass/113/value/72057594211745809/,{ "Label": "Previous Event Cleared", "Value": 0, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_NOTIFICATION", "Index": 256, "Node": 10, "Genre": "User", "Help": "Previous Event that was sent", "ValueIDKey": 72057594211745809, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1590178857} +OpenZWave/1/node/10/instance/1/commandclass/113/value/1688850034081812/,{ "Label": "Access Control", "Value": { "List": [ { "Value": 0, "Label": "Clear" }, { "Value": 1, "Label": "Manual Lock Operation" }, { "Value": 2, "Label": "Manual Unlock Operation" }, { "Value": 3, "Label": "Wireless Lock Operation" }, { "Value": 4, "Label": "Wireless Unlock Operation" }, { "Value": 9, "Label": "Auto Lock" }, { "Value": 11, "Label": "Lock Jammed" } ], "Selected": "Wireless Unlock Operation", "Selected_id": 4 }, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_NOTIFICATION", "Index": 6, "Node": 10, "Genre": "User", "Help": "Access Control Alerts", "ValueIDKey": 1688850034081812, "ReadOnly": false, "WriteOnly": false, "Event": "valueChanged", "TimeStamp": 1590345911} +OpenZWave/1/node/10/association/1/,{ "Name": "Lifeline", "Help": "", "MaxAssociations": 1, "Members": [ "1.0" ], "TimeStamp": 1590178858} \ No newline at end of file From 3dfeec5033ac4f703e657b0b405ad7d6015ab455 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 25 May 2020 17:41:50 -0500 Subject: [PATCH 191/406] Implement async_get_stream_source in the camera integration (#35704) --- homeassistant/components/camera/__init__.py | 8 ++++++++ homeassistant/components/homekit/type_cameras.py | 9 +++------ tests/components/camera/test_init.py | 13 +++++++++++++ 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py index 0b2c1e77d3f..1dc18baf232 100644 --- a/homeassistant/components/camera/__init__.py +++ b/homeassistant/components/camera/__init__.py @@ -161,6 +161,14 @@ async def async_get_image(hass, entity_id, timeout=10): raise HomeAssistantError("Unable to get image") +@bind_hass +async def async_get_stream_source(hass, entity_id): + """Fetch the stream source for a camera entity.""" + camera = _get_camera_from_entity_id(hass, entity_id) + + return await camera.stream_source() + + @bind_hass async def async_get_mjpeg_stream(hass, request, entity_id): """Fetch an mjpeg stream from a camera entity.""" diff --git a/homeassistant/components/homekit/type_cameras.py b/homeassistant/components/homekit/type_cameras.py index c4e52f07832..c81790a3874 100644 --- a/homeassistant/components/homekit/type_cameras.py +++ b/homeassistant/components/homekit/type_cameras.py @@ -12,7 +12,6 @@ from pyhap.camera import ( ) from pyhap.const import CATEGORY_CAMERA -from homeassistant.components.camera.const import DOMAIN as DOMAIN_CAMERA from homeassistant.components.ffmpeg import DATA_FFMPEG from homeassistant.core import callback from homeassistant.helpers.event import async_track_time_interval @@ -126,7 +125,6 @@ class Camera(HomeAccessory, PyhapCamera): """Initialize a Camera accessory object.""" self._ffmpeg = hass.data[DATA_FFMPEG] self._cur_session = None - self._camera = hass.data[DOMAIN_CAMERA] for config_key in CONFIG_DEFAULTS: if config_key not in config: config[config_key] = CONFIG_DEFAULTS[config_key] @@ -188,14 +186,13 @@ class Camera(HomeAccessory, PyhapCamera): async def _async_get_stream_source(self): """Find the camera stream source url.""" - camera = self._camera.get_entity(self.entity_id) - if not camera or not camera.is_on: - return None stream_source = self.config.get(CONF_STREAM_SOURCE) if stream_source: return stream_source try: - stream_source = await camera.stream_source() + stream_source = await self.hass.components.camera.async_get_stream_source( + self.entity_id + ) except Exception: # pylint: disable=broad-except _LOGGER.exception( "Failed to get stream source - this could be a transient error or your camera might not be compatible with HomeKit yet" diff --git a/tests/components/camera/test_init.py b/tests/components/camera/test_init.py index 36ee9b8aabf..81f215a145d 100644 --- a/tests/components/camera/test_init.py +++ b/tests/components/camera/test_init.py @@ -67,6 +67,19 @@ async def test_get_image_from_camera(hass, image_mock_url): assert image.content == b"Test" +async def test_get_stream_source_from_camera(hass, mock_camera): + """Fetch stream source from camera entity.""" + + with patch( + "homeassistant.components.camera.Camera.stream_source", + return_value="rtsp://127.0.0.1/stream", + ) as mock_camera_stream_source: + stream_source = await camera.async_get_stream_source(hass, "camera.demo_camera") + + assert mock_camera_stream_source.called + assert stream_source == "rtsp://127.0.0.1/stream" + + async def test_get_image_without_exists_camera(hass, image_mock_url): """Try to get image without exists camera.""" with patch( From 15539536ad48d06ef5fe5c226f93b6d97e6885f5 Mon Sep 17 00:00:00 2001 From: Frederik Gladhorn Date: Tue, 26 May 2020 00:50:19 +0200 Subject: [PATCH 192/406] Use speaker-multiple icon for NAD receiver (#34572) By default media players get a chrome cast icon, which feels quite wrong for my old style amplifier that doesn't have any connection to the rest of the word, except for a RS232 port. --- homeassistant/components/nad/media_player.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/homeassistant/components/nad/media_player.py b/homeassistant/components/nad/media_player.py index 2d9afbb7541..066088ab994 100644 --- a/homeassistant/components/nad/media_player.py +++ b/homeassistant/components/nad/media_player.py @@ -129,6 +129,11 @@ class NAD(MediaPlayerEntity): """Return the state of the device.""" return self._state + @property + def icon(self): + """Return the icon for the device.""" + return "mdi:speaker-multiple" + @property def volume_level(self): """Volume level of the media player (0..1).""" From 8cbee7692912fd39f1dbde6be68b1662f5933582 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 25 May 2020 18:05:38 -0500 Subject: [PATCH 193/406] Add support for homekit camera motion notification (#35994) * Add support for homekit camera motion notification A motion sensor can now be linked to the cameras. * Increase coverage --- homeassistant/components/homekit/__init__.py | 35 ++++--- homeassistant/components/homekit/const.py | 1 + .../components/homekit/type_cameras.py | 50 +++++++++- homeassistant/components/homekit/util.py | 8 +- tests/components/homekit/test_homekit.py | 80 +++++++++++++++- tests/components/homekit/test_type_cameras.py | 93 +++++++++++++++++++ 6 files changed, 253 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index 428f8e30abf..5814fdaad2b 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -9,8 +9,14 @@ import voluptuous as vol from zeroconf import InterfaceChoice from homeassistant.components import zeroconf -from homeassistant.components.binary_sensor import DEVICE_CLASS_BATTERY_CHARGING +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_BATTERY_CHARGING, + DEVICE_CLASS_MOTION, + DOMAIN as BINARY_SENSOR_DOMAIN, +) +from homeassistant.components.camera import DOMAIN as CAMERA_DOMAIN from homeassistant.components.http import HomeAssistantView +from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import ( ATTR_BATTERY_CHARGING, @@ -58,6 +64,7 @@ from .const import ( CONF_FILTER, CONF_LINKED_BATTERY_CHARGING_SENSOR, CONF_LINKED_BATTERY_SENSOR, + CONF_LINKED_MOTION_SENSOR, CONF_SAFE_MODE, CONF_ZEROCONF_DEFAULT_INTERFACE, CONFIG_OPTIONS, @@ -522,8 +529,9 @@ class HomeKit: device_lookup = ent_reg.async_get_device_class_lookup( { - ("binary_sensor", DEVICE_CLASS_BATTERY_CHARGING), - ("sensor", DEVICE_CLASS_BATTERY), + (BINARY_SENSOR_DOMAIN, DEVICE_CLASS_BATTERY_CHARGING), + (BINARY_SENSOR_DOMAIN, DEVICE_CLASS_MOTION), + (SENSOR_DOMAIN, DEVICE_CLASS_BATTERY), } ) @@ -537,9 +545,7 @@ class HomeKit: await self._async_set_device_info_attributes( ent_reg_ent, dev_reg, state.entity_id ) - self._async_configure_linked_battery_sensors( - ent_reg_ent, device_lookup, state - ) + self._async_configure_linked_sensors(ent_reg_ent, device_lookup, state) bridged_states.append(state) @@ -629,9 +635,7 @@ class HomeKit: self.hass.add_job(self.driver.stop) @callback - def _async_configure_linked_battery_sensors( - self, ent_reg_ent, device_lookup, state - ): + def _async_configure_linked_sensors(self, ent_reg_ent, device_lookup, state): if ( ent_reg_ent is None or ent_reg_ent.device_id is None @@ -644,7 +648,7 @@ class HomeKit: if ATTR_BATTERY_CHARGING not in state.attributes: battery_charging_binary_sensor_entity_id = device_lookup[ ent_reg_ent.device_id - ].get(("binary_sensor", DEVICE_CLASS_BATTERY_CHARGING)) + ].get((BINARY_SENSOR_DOMAIN, DEVICE_CLASS_BATTERY_CHARGING)) if battery_charging_binary_sensor_entity_id: self._config.setdefault(state.entity_id, {}).setdefault( CONF_LINKED_BATTERY_CHARGING_SENSOR, @@ -653,13 +657,22 @@ class HomeKit: if ATTR_BATTERY_LEVEL not in state.attributes: battery_sensor_entity_id = device_lookup[ent_reg_ent.device_id].get( - ("sensor", DEVICE_CLASS_BATTERY) + (SENSOR_DOMAIN, DEVICE_CLASS_BATTERY) ) if battery_sensor_entity_id: self._config.setdefault(state.entity_id, {}).setdefault( CONF_LINKED_BATTERY_SENSOR, battery_sensor_entity_id ) + if state.entity_id.startswith(f"{CAMERA_DOMAIN}."): + motion_binary_sensor_entity_id = device_lookup[ent_reg_ent.device_id].get( + (BINARY_SENSOR_DOMAIN, DEVICE_CLASS_MOTION) + ) + if motion_binary_sensor_entity_id: + self._config.setdefault(state.entity_id, {}).setdefault( + CONF_LINKED_MOTION_SENSOR, motion_binary_sensor_entity_id, + ) + async def _async_set_device_info_attributes(self, ent_reg_ent, dev_reg, entity_id): """Set attributes that will be used for homekit device info.""" ent_cfg = self._config.setdefault(entity_id, {}) diff --git a/homeassistant/components/homekit/const.py b/homeassistant/components/homekit/const.py index 8c431830589..2a93ae4cf7e 100644 --- a/homeassistant/components/homekit/const.py +++ b/homeassistant/components/homekit/const.py @@ -41,6 +41,7 @@ CONF_FEATURE_LIST = "feature_list" CONF_FILTER = "filter" CONF_LINKED_BATTERY_SENSOR = "linked_battery_sensor" CONF_LINKED_BATTERY_CHARGING_SENSOR = "linked_battery_charging_sensor" +CONF_LINKED_MOTION_SENSOR = "linked_motion_sensor" CONF_LOW_BATTERY_THRESHOLD = "low_battery_threshold" CONF_MAX_FPS = "max_fps" CONF_MAX_HEIGHT = "max_height" diff --git a/homeassistant/components/homekit/type_cameras.py b/homeassistant/components/homekit/type_cameras.py index c81790a3874..e25c5189262 100644 --- a/homeassistant/components/homekit/type_cameras.py +++ b/homeassistant/components/homekit/type_cameras.py @@ -13,16 +13,22 @@ from pyhap.camera import ( from pyhap.const import CATEGORY_CAMERA from homeassistant.components.ffmpeg import DATA_FFMPEG +from homeassistant.const import STATE_ON from homeassistant.core import callback -from homeassistant.helpers.event import async_track_time_interval +from homeassistant.helpers.event import ( + async_track_state_change, + async_track_time_interval, +) from homeassistant.util import get_local_ip from .accessories import TYPES, HomeAccessory from .const import ( + CHAR_MOTION_DETECTED, CHAR_STREAMING_STRATUS, CONF_AUDIO_CODEC, CONF_AUDIO_MAP, CONF_AUDIO_PACKET_SIZE, + CONF_LINKED_MOTION_SENSOR, CONF_MAX_FPS, CONF_MAX_HEIGHT, CONF_MAX_WIDTH, @@ -43,6 +49,7 @@ from .const import ( DEFAULT_VIDEO_MAP, DEFAULT_VIDEO_PACKET_SIZE, SERV_CAMERA_RTP_STREAM_MANAGEMENT, + SERV_MOTION_SENSOR, ) from .img_util import scale_jpeg_camera_image from .util import pid_is_alive @@ -178,6 +185,47 @@ class Camera(HomeAccessory, PyhapCamera): category=CATEGORY_CAMERA, options=options, ) + self._char_motion_detected = None + self.linked_motion_sensor = self.config.get(CONF_LINKED_MOTION_SENSOR) + if not self.linked_motion_sensor: + return + state = self.hass.states.get(self.linked_motion_sensor) + if not state: + return + serv_motion = self.add_preload_service(SERV_MOTION_SENSOR) + self._char_motion_detected = serv_motion.configure_char( + CHAR_MOTION_DETECTED, value=False + ) + self._async_update_motion_state(None, None, state) + + async def run_handler(self): + """Handle accessory driver started event. + + Run inside the Home Assistant event loop. + """ + if self._char_motion_detected: + async_track_state_change( + self.hass, self.linked_motion_sensor, self._async_update_motion_state + ) + + await super().run_handler() + + @callback + def _async_update_motion_state( + self, entity_id=None, old_state=None, new_state=None + ): + """Handle link motion sensor state change to update HomeKit value.""" + detected = new_state.state == STATE_ON + if self._char_motion_detected.value == detected: + return + + self._char_motion_detected.set_value(detected) + _LOGGER.debug( + "%s: Set linked motion %s sensor to %d", + self.entity_id, + self.linked_motion_sensor, + detected, + ) @callback def async_update_state(self, new_state): diff --git a/homeassistant/components/homekit/util.py b/homeassistant/components/homekit/util.py index d35c463ca39..0465e33388d 100644 --- a/homeassistant/components/homekit/util.py +++ b/homeassistant/components/homekit/util.py @@ -11,7 +11,7 @@ import socket import pyqrcode import voluptuous as vol -from homeassistant.components import fan, media_player, sensor +from homeassistant.components import binary_sensor, fan, media_player, sensor from homeassistant.const import ( ATTR_CODE, ATTR_SUPPORTED_FEATURES, @@ -32,7 +32,9 @@ from .const import ( CONF_AUDIO_PACKET_SIZE, CONF_FEATURE, CONF_FEATURE_LIST, + CONF_LINKED_BATTERY_CHARGING_SENSOR, CONF_LINKED_BATTERY_SENSOR, + CONF_LINKED_MOTION_SENSOR, CONF_LOW_BATTERY_THRESHOLD, CONF_MAX_FPS, CONF_MAX_HEIGHT, @@ -83,6 +85,9 @@ BASIC_INFO_SCHEMA = vol.Schema( { vol.Optional(CONF_NAME): cv.string, vol.Optional(CONF_LINKED_BATTERY_SENSOR): cv.entity_domain(sensor.DOMAIN), + vol.Optional(CONF_LINKED_BATTERY_CHARGING_SENSOR): cv.entity_domain( + binary_sensor.DOMAIN + ), vol.Optional( CONF_LOW_BATTERY_THRESHOLD, default=DEFAULT_LOW_BATTERY_THRESHOLD ): cv.positive_int, @@ -115,6 +120,7 @@ CAMERA_SCHEMA = BASIC_INFO_SCHEMA.extend( vol.Optional( CONF_VIDEO_PACKET_SIZE, default=DEFAULT_VIDEO_PACKET_SIZE ): cv.positive_int, + vol.Optional(CONF_LINKED_MOTION_SENSOR): cv.entity_domain(binary_sensor.DOMAIN), } ) diff --git a/tests/components/homekit/test_homekit.py b/tests/components/homekit/test_homekit.py index b016997b7c9..5e756ae51b5 100644 --- a/tests/components/homekit/test_homekit.py +++ b/tests/components/homekit/test_homekit.py @@ -6,7 +6,10 @@ import pytest from zeroconf import InterfaceChoice from homeassistant.components import zeroconf -from homeassistant.components.binary_sensor import DEVICE_CLASS_BATTERY_CHARGING +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_BATTERY_CHARGING, + DEVICE_CLASS_MOTION, +) from homeassistant.components.homekit import ( MAX_DEVICES, STATUS_READY, @@ -1032,3 +1035,78 @@ async def test_homekit_ignored_missing_devices( "linked_battery_sensor": "sensor.powerwall_battery", }, ) + + +async def test_homekit_finds_linked_motion_sensors( + hass, hk_driver, debounce_patcher, device_reg, entity_reg +): + """Test HomeKit start method.""" + entry = await async_init_integration(hass) + + homekit = HomeKit( + hass, + None, + None, + None, + {}, + {"camera.camera_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") + + config_entry = MockConfigEntry(domain="test", data={}) + config_entry.add_to_hass(hass) + device_entry = device_reg.async_get_or_create( + config_entry_id=config_entry.entry_id, + sw_version="0.16.0", + model="Camera Server", + manufacturer="Ubq", + connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, + ) + + binary_motion_sensor = entity_reg.async_get_or_create( + "binary_sensor", + "camera", + "motion_sensor", + device_id=device_entry.id, + device_class=DEVICE_CLASS_MOTION, + ) + camera = entity_reg.async_get_or_create( + "camera", "camera", "demo", device_id=device_entry.id + ) + + hass.states.async_set( + binary_motion_sensor.entity_id, + STATE_ON, + {ATTR_DEVICE_CLASS: DEVICE_CLASS_MOTION}, + ) + hass.states.async_set(camera.entity_id, STATE_ON) + + def _mock_get_accessory(*args, **kwargs): + return [None, "acc", None] + + with patch.object(homekit.bridge, "add_accessory"), patch( + f"{PATH_HOMEKIT}.show_setup_message" + ), patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc, patch( + "pyhap.accessory_driver.AccessoryDriver.start" + ): + await homekit.async_start() + await hass.async_block_till_done() + + mock_get_acc.assert_called_with( + hass, + hk_driver, + ANY, + ANY, + { + "manufacturer": "Ubq", + "model": "Camera Server", + "sw_version": "0.16.0", + "linked_motion_sensor": "binary_sensor.camera_motion_sensor", + }, + ) diff --git a/tests/components/homekit/test_type_cameras.py b/tests/components/homekit/test_type_cameras.py index 0c002fa7213..9de08b8fc22 100644 --- a/tests/components/homekit/test_type_cameras.py +++ b/tests/components/homekit/test_type_cameras.py @@ -9,16 +9,21 @@ from homeassistant.components import camera, ffmpeg from homeassistant.components.homekit.accessories import HomeBridge from homeassistant.components.homekit.const import ( AUDIO_CODEC_COPY, + CHAR_MOTION_DETECTED, CONF_AUDIO_CODEC, + CONF_LINKED_MOTION_SENSOR, CONF_STREAM_SOURCE, CONF_SUPPORT_AUDIO, CONF_VIDEO_CODEC, + DEVICE_CLASS_MOTION, + SERV_MOTION_SENSOR, VIDEO_CODEC_COPY, VIDEO_CODEC_H264_OMX, ) from homeassistant.components.homekit.img_util import TurboJPEGSingleton from homeassistant.components.homekit.type_cameras import Camera from homeassistant.components.homekit.type_switches import Switch +from homeassistant.const import ATTR_DEVICE_CLASS, STATE_OFF, STATE_ON from homeassistant.exceptions import HomeAssistantError from homeassistant.setup import async_setup_component @@ -492,3 +497,91 @@ async def test_camera_streaming_fails_after_starting_ffmpeg(hass, run_driver, ev output=expected_output.format(**session_info), stdout_pipe=False, ) + + +async def test_camera_with_linked_motion_sensor(hass, run_driver, events): + """Test a camera with a linked motion sensor can update.""" + await async_setup_component(hass, ffmpeg.DOMAIN, {ffmpeg.DOMAIN: {}}) + await async_setup_component( + hass, camera.DOMAIN, {camera.DOMAIN: {"platform": "demo"}} + ) + motion_entity_id = "binary_sensor.motion" + + hass.states.async_set( + motion_entity_id, STATE_ON, {ATTR_DEVICE_CLASS: DEVICE_CLASS_MOTION} + ) + await hass.async_block_till_done() + entity_id = "camera.demo_camera" + + hass.states.async_set(entity_id, None) + await hass.async_block_till_done() + acc = Camera( + hass, + run_driver, + "Camera", + entity_id, + 2, + { + CONF_STREAM_SOURCE: "/dev/null", + CONF_SUPPORT_AUDIO: True, + CONF_VIDEO_CODEC: VIDEO_CODEC_H264_OMX, + CONF_AUDIO_CODEC: AUDIO_CODEC_COPY, + CONF_LINKED_MOTION_SENSOR: motion_entity_id, + }, + ) + bridge = HomeBridge("hass", run_driver, "Test Bridge") + bridge.add_accessory(acc) + + await acc.run_handler() + + assert acc.aid == 2 + assert acc.category == 17 # Camera + + service = acc.get_service(SERV_MOTION_SENSOR) + assert service + char = service.get_characteristic(CHAR_MOTION_DETECTED) + assert char + + assert char.value is True + + hass.states.async_set( + motion_entity_id, STATE_OFF, {ATTR_DEVICE_CLASS: DEVICE_CLASS_MOTION} + ) + await hass.async_block_till_done() + assert char.value is False + + char.set_value(True) + hass.states.async_set( + motion_entity_id, STATE_ON, {ATTR_DEVICE_CLASS: DEVICE_CLASS_MOTION} + ) + await hass.async_block_till_done() + assert char.value is True + + +async def test_camera_with_a_missing_linked_motion_sensor(hass, run_driver, events): + """Test a camera with a configured linked motion sensor that is missing.""" + await async_setup_component(hass, ffmpeg.DOMAIN, {ffmpeg.DOMAIN: {}}) + await async_setup_component( + hass, camera.DOMAIN, {camera.DOMAIN: {"platform": "demo"}} + ) + motion_entity_id = "binary_sensor.motion" + entity_id = "camera.demo_camera" + hass.states.async_set(entity_id, None) + await hass.async_block_till_done() + acc = Camera( + hass, + run_driver, + "Camera", + entity_id, + 2, + {CONF_LINKED_MOTION_SENSOR: motion_entity_id}, + ) + bridge = HomeBridge("hass", run_driver, "Test Bridge") + bridge.add_accessory(acc) + + await acc.run_handler() + + assert acc.aid == 2 + assert acc.category == 17 # Camera + + assert not acc.get_service(SERV_MOTION_SENSOR) From 3a97d96dc0bf4b633cfc92a6c8c592f7ea648ca8 Mon Sep 17 00:00:00 2001 From: MatthewFlamm <39341281+MatthewFlamm@users.noreply.github.com> Date: Mon, 25 May 2020 19:39:46 -0400 Subject: [PATCH 194/406] Cache data and update faster after failed updates in NWS (#35722) * add last_update_success_time and a failed update interval * add failed update interval annd valid times to nws * Revert "add last_update_success_time and a failed update interval" This reverts commit 09428c96861aa4601832e6f6392ac3b18591159d. * extend DataUpdateCoordinator --- homeassistant/components/nws/__init__.py | 69 +++++++++++++++-- homeassistant/components/nws/weather.py | 20 ++++- tests/components/nws/test_weather.py | 95 +++++++++++++++++------- 3 files changed, 150 insertions(+), 34 deletions(-) diff --git a/homeassistant/components/nws/__init__.py b/homeassistant/components/nws/__init__.py index 9f0579dc20e..a0958be8d9e 100644 --- a/homeassistant/components/nws/__init__.py +++ b/homeassistant/components/nws/__init__.py @@ -2,15 +2,18 @@ import asyncio import datetime import logging +from typing import Awaitable, Callable, Optional from pynws import SimpleNWS from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import debounce from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.helpers.event import async_track_point_in_utc_time from homeassistant.helpers.update_coordinator import DataUpdateCoordinator +from homeassistant.util.dt import utcnow from .const import ( CONF_STATION, @@ -26,7 +29,7 @@ _LOGGER = logging.getLogger(__name__) PLATFORMS = ["weather"] DEFAULT_SCAN_INTERVAL = datetime.timedelta(minutes=10) - +FAILED_SCAN_INTERVAL = datetime.timedelta(minutes=1) DEBOUNCE_TIME = 60 # in seconds @@ -40,6 +43,59 @@ async def async_setup(hass: HomeAssistant, config: dict): return True +class NwsDataUpdateCoordinator(DataUpdateCoordinator): + """ + NWS data update coordinator. + + Implements faster data update intervals for failed updates and exposes a last successful update time. + """ + + def __init__( + self, + hass: HomeAssistant, + logger: logging.Logger, + *, + name: str, + update_interval: datetime.timedelta, + failed_update_interval: datetime.timedelta, + update_method: Optional[Callable[[], Awaitable]] = None, + request_refresh_debouncer: Optional[debounce.Debouncer] = None, + ): + """Initialize NWS coordinator.""" + super().__init__( + hass, + logger, + name=name, + update_interval=update_interval, + update_method=update_method, + request_refresh_debouncer=request_refresh_debouncer, + ) + self.failed_update_interval = failed_update_interval + self.last_update_success_time = None + + @callback + def _schedule_refresh(self) -> None: + """Schedule a refresh.""" + if self._unsub_refresh: + self._unsub_refresh() + self._unsub_refresh = None + + # We _floor_ utcnow to create a schedule on a rounded second, + # minimizing the time between the point and the real activation. + # That way we obtain a constant update frequency, + # as long as the update process takes less than a second + if self.last_update_success: + update_interval = self.update_interval + self.last_update_success_time = utcnow() + else: + update_interval = self.failed_update_interval + self._unsub_refresh = async_track_point_in_utc_time( + self.hass, + self._handle_refresh_interval, + utcnow().replace(microsecond=0) + update_interval, + ) + + async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): """Set up a National Weather Service entry.""" latitude = entry.data[CONF_LATITUDE] @@ -53,34 +109,37 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): nws_data = SimpleNWS(latitude, longitude, api_key, client_session) await nws_data.set_station(station) - coordinator_observation = DataUpdateCoordinator( + coordinator_observation = NwsDataUpdateCoordinator( hass, _LOGGER, name=f"NWS observation station {station}", update_method=nws_data.update_observation, update_interval=DEFAULT_SCAN_INTERVAL, + failed_update_interval=FAILED_SCAN_INTERVAL, request_refresh_debouncer=debounce.Debouncer( hass, _LOGGER, cooldown=DEBOUNCE_TIME, immediate=True ), ) - coordinator_forecast = DataUpdateCoordinator( + coordinator_forecast = NwsDataUpdateCoordinator( hass, _LOGGER, name=f"NWS forecast station {station}", update_method=nws_data.update_forecast, update_interval=DEFAULT_SCAN_INTERVAL, + failed_update_interval=FAILED_SCAN_INTERVAL, request_refresh_debouncer=debounce.Debouncer( hass, _LOGGER, cooldown=DEBOUNCE_TIME, immediate=True ), ) - coordinator_forecast_hourly = DataUpdateCoordinator( + coordinator_forecast_hourly = NwsDataUpdateCoordinator( hass, _LOGGER, name=f"NWS forecast hourly station {station}", update_method=nws_data.update_forecast_hourly, update_interval=DEFAULT_SCAN_INTERVAL, + failed_update_interval=FAILED_SCAN_INTERVAL, request_refresh_debouncer=debounce.Debouncer( hass, _LOGGER, cooldown=DEBOUNCE_TIME, immediate=True ), diff --git a/homeassistant/components/nws/weather.py b/homeassistant/components/nws/weather.py index 807591e0c2b..7e1ca37ab6b 100644 --- a/homeassistant/components/nws/weather.py +++ b/homeassistant/components/nws/weather.py @@ -1,4 +1,5 @@ """Support for NWS weather service.""" +from datetime import timedelta import logging from homeassistant.components.weather import ( @@ -24,6 +25,7 @@ from homeassistant.const import ( from homeassistant.core import callback from homeassistant.helpers.typing import ConfigType, HomeAssistantType from homeassistant.util.distance import convert as convert_distance +from homeassistant.util.dt import utcnow from homeassistant.util.pressure import convert as convert_pressure from homeassistant.util.temperature import convert as convert_temperature @@ -47,6 +49,9 @@ _LOGGER = logging.getLogger(__name__) PARALLEL_UPDATES = 0 +OBSERVATION_VALID_TIME = timedelta(minutes=20) +FORECAST_VALID_TIME = timedelta(minutes=45) + def convert_condition(time, weather): """ @@ -287,10 +292,23 @@ class NWSWeather(WeatherEntity): @property def available(self): """Return if state is available.""" - return ( + last_success = ( self.coordinator_observation.last_update_success and self.coordinator_forecast.last_update_success ) + if ( + self.coordinator_observation.last_update_success_time + and self.coordinator_forecast.last_update_success_time + ): + last_success_time = ( + utcnow() - self.coordinator_observation.last_update_success_time + < OBSERVATION_VALID_TIME + and utcnow() - self.coordinator_forecast.last_update_success_time + < FORECAST_VALID_TIME + ) + else: + last_success_time = False + return last_success or last_success_time async def async_update(self): """Update the entity. diff --git a/tests/components/nws/test_weather.py b/tests/components/nws/test_weather.py index 667f40db137..1486015d80e 100644 --- a/tests/components/nws/test_weather.py +++ b/tests/components/nws/test_weather.py @@ -10,6 +10,7 @@ from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util from homeassistant.util.unit_system import IMPERIAL_SYSTEM, METRIC_SYSTEM +from tests.async_mock import patch from tests.common import MockConfigEntry, async_fire_time_changed from tests.components.nws.const import ( EXPECTED_FORECAST_IMPERIAL, @@ -154,39 +155,79 @@ async def test_entity_refresh(hass, mock_simple_nws): async def test_error_observation(hass, mock_simple_nws): """Test error during update observation.""" - instance = mock_simple_nws.return_value - instance.update_observation.side_effect = aiohttp.ClientError + utc_time = dt_util.utcnow() + with patch("homeassistant.components.nws.utcnow") as mock_utc, patch( + "homeassistant.components.nws.weather.utcnow" + ) as mock_utc_weather: - entry = MockConfigEntry(domain=nws.DOMAIN, data=NWS_CONFIG,) - entry.add_to_hass(hass) - await hass.config_entries.async_setup(entry.entry_id) - await hass.async_block_till_done() + def increment_time(time): + mock_utc.return_value += time + mock_utc_weather.return_value += time + async_fire_time_changed(hass, mock_utc.return_value) - instance.update_observation.assert_called_once() + mock_utc.return_value = utc_time + mock_utc_weather.return_value = utc_time + instance = mock_simple_nws.return_value + # first update fails + instance.update_observation.side_effect = aiohttp.ClientError - state = hass.states.get("weather.abc_daynight") - assert state - assert state.state == "unavailable" + entry = MockConfigEntry(domain=nws.DOMAIN, data=NWS_CONFIG,) + entry.add_to_hass(hass) + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() - state = hass.states.get("weather.abc_hourly") - assert state - assert state.state == "unavailable" + instance.update_observation.assert_called_once() - instance.update_observation.side_effect = None + state = hass.states.get("weather.abc_daynight") + assert state + assert state.state == "unavailable" - future_time = dt_util.utcnow() + timedelta(minutes=15) - async_fire_time_changed(hass, future_time) - await hass.async_block_till_done() + state = hass.states.get("weather.abc_hourly") + assert state + assert state.state == "unavailable" - assert instance.update_observation.call_count == 2 + # second update happens faster and succeeds + instance.update_observation.side_effect = None + increment_time(timedelta(minutes=1)) + await hass.async_block_till_done() - state = hass.states.get("weather.abc_daynight") - assert state - assert state.state == "sunny" + assert instance.update_observation.call_count == 2 - state = hass.states.get("weather.abc_hourly") - assert state - assert state.state == "sunny" + state = hass.states.get("weather.abc_daynight") + assert state + assert state.state == "sunny" + + state = hass.states.get("weather.abc_hourly") + assert state + assert state.state == "sunny" + + # third udate fails, but data is cached + instance.update_observation.side_effect = aiohttp.ClientError + + increment_time(timedelta(minutes=10)) + await hass.async_block_till_done() + + assert instance.update_observation.call_count == 3 + + state = hass.states.get("weather.abc_daynight") + assert state + assert state.state == "sunny" + + state = hass.states.get("weather.abc_hourly") + assert state + assert state.state == "sunny" + + # after 20 minutes data caching expires, data is no longer shown + increment_time(timedelta(minutes=10)) + await hass.async_block_till_done() + + state = hass.states.get("weather.abc_daynight") + assert state + assert state.state == "unavailable" + + state = hass.states.get("weather.abc_hourly") + assert state + assert state.state == "unavailable" async def test_error_forecast(hass, mock_simple_nws): @@ -207,8 +248,7 @@ async def test_error_forecast(hass, mock_simple_nws): instance.update_forecast.side_effect = None - future_time = dt_util.utcnow() + timedelta(minutes=15) - async_fire_time_changed(hass, future_time) + async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=1)) await hass.async_block_till_done() assert instance.update_forecast.call_count == 2 @@ -236,8 +276,7 @@ async def test_error_forecast_hourly(hass, mock_simple_nws): instance.update_forecast_hourly.side_effect = None - future_time = dt_util.utcnow() + timedelta(minutes=15) - async_fire_time_changed(hass, future_time) + async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=1)) await hass.async_block_till_done() assert instance.update_forecast_hourly.call_count == 2 From b15bac595d7a140a11b25c0cb7f20f14c4615333 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 26 May 2020 01:40:40 +0200 Subject: [PATCH 195/406] Update Code of Conduct to 2.0 (#36142) --- CODE_OF_CONDUCT.md | 156 +++++++++++++++++++++++++++++++-------------- 1 file changed, 108 insertions(+), 48 deletions(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 5d2149dce05..f9b1ea79314 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -2,79 +2,139 @@ ## Our Pledge -In the interest of fostering an open and welcoming environment, we as -contributors and maintainers pledge to making participation in our project and -our community a harassment-free experience for everyone, regardless of age, body -size, disability, ethnicity, gender identity and expression, level of experience, -nationality, personal appearance, race, religion, or sexual identity and -orientation. +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. ## Our Standards -Examples of behavior that contributes to creating a positive environment -include: +Examples of behavior that contributes to a positive environment for our +community include: -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community -Examples of unacceptable behavior by participants include: +Examples of unacceptable behavior include: -* The use of sexualized language or imagery and unwelcome sexual attention or -advances -* Trolling, insulting/derogatory comments, and personal or political attacks +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks * Public or private harassment -* Publishing others' private information, such as a physical or electronic - address, without explicit permission +* Publishing others' private information, such as a physical or email + address, without their explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting -## Our Responsibilities +## Enforcement Responsibilities -Project maintainers are responsible for clarifying the standards of acceptable -behavior and are expected to take appropriate and fair corrective action in -response to any instances of unacceptable behavior. +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. -Project maintainers have the right and responsibility to remove, edit, or -reject comments, commits, code, wiki edits, issues, and other contributions -that are not aligned to this Code of Conduct, or to ban temporarily or -permanently any contributor for other behaviors that they deem inappropriate, -threatening, offensive, or harmful. +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. ## Scope -This Code of Conduct applies both within project spaces and in public spaces -when an individual is representing the project or its community. Examples of -representing a project or community include using an official project e-mail -address, posting via an official social media account, or acting as an appointed -representative at an online or offline event. Representation of a project may be -further defined and clarified by project maintainers. +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team at [safety@home-assistant.io][email]. All -complaints will be reviewed and investigated and will result in a response that -is deemed necessary and appropriate to the circumstances. The project team is -obligated to maintain confidentiality with regard to the reporter of an incident. -Further details of specific enforcement policies may be posted separately. +reported to the community leaders responsible for enforcement at +[safety@home-assistant.io][email] or by using the report/flag feature of +the medium used. All complaints will be reviewed and investigated promptly and +fairly. -Project maintainers who do not follow or enforce the Code of Conduct in good -faith may face temporary or permanent repercussions as determined by other -members of the project's leadership. +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. ## Attribution -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, -available [here][version]. +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available [here][version]. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder][mozilla]. ## Adoption -This Code of Conduct was first adopted January 21st, 2017 and announced in [this][coc-blog] blog post. +This Code of Conduct was first adopted January 21st, 2017 and announced in +[this][coc-blog] blog post and has been updated on May 25th, 2020 to version +2.0 of the [Contributor Covenant][homepage] as announced in [this][coc2-blog] +blog post. -[homepage]: http://contributor-covenant.org -[version]: http://contributor-covenant.org/version/1/4/ +For answers to common questions about this code of conduct, see the FAQ at +. Translations are available at +. + +[coc-blog]: /blog/2017/01/21/home-assistant-governance/ +[coc2-blog]: /blog/2020/05/25/code-of-conduct-updated/ [email]: mailto:safety@home-assistant.io -[coc-blog]: https://home-assistant.io/blog/2017/01/21/home-assistant-governance/ +[homepage]: http://contributor-covenant.org +[mozilla]: https://github.com/mozilla/diversity +[version]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html From e61280095e1e8c70dc296aa13c2c8c0dafa118fe Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 26 May 2020 02:39:09 +0200 Subject: [PATCH 196/406] Add uuid to google assistant (#35811) --- .../components/google_assistant/helpers.py | 1 + .../google_assistant/test_helpers.py | 20 ++++++++++--------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/google_assistant/helpers.py b/homeassistant/components/google_assistant/helpers.py index dd948f1fc51..49e08e90e4b 100644 --- a/homeassistant/components/google_assistant/helpers.py +++ b/homeassistant/components/google_assistant/helpers.py @@ -426,6 +426,7 @@ class GoogleEntity: "webhookId": self.config.local_sdk_webhook_id, "httpPort": self.hass.http.server_port, "httpSSL": self.hass.config.api.use_ssl, + "uuid": await self.hass.helpers.instance_id.async_get(), "baseUrl": get_url(self.hass, prefer_external=True), "proxyDeviceId": agent_user_id, } diff --git a/tests/components/google_assistant/test_helpers.py b/tests/components/google_assistant/test_helpers.py index 05afd29a5bd..c58f44c06c8 100644 --- a/tests/components/google_assistant/test_helpers.py +++ b/tests/components/google_assistant/test_helpers.py @@ -44,15 +44,17 @@ async def test_google_entity_sync_serialize_with_local_sdk(hass): config.async_enable_local_sdk() - serialized = await entity.sync_serialize(None) - assert serialized["otherDeviceIds"] == [{"deviceId": "light.ceiling_lights"}] - assert serialized["customData"] == { - "httpPort": 1234, - "httpSSL": True, - "proxyDeviceId": None, - "webhookId": "mock-webhook-id", - "baseUrl": "https://hostname:1234", - } + with patch("homeassistant.helpers.instance_id.async_get", return_value="abcdef"): + serialized = await entity.sync_serialize(None) + assert serialized["otherDeviceIds"] == [{"deviceId": "light.ceiling_lights"}] + assert serialized["customData"] == { + "httpPort": 1234, + "httpSSL": True, + "proxyDeviceId": None, + "webhookId": "mock-webhook-id", + "baseUrl": "https://hostname:1234", + "uuid": "abcdef", + } for device_type in NOT_EXPOSE_LOCAL: with patch( From 7e67b6b5680298580f87d19fc0e66c19d7d3318a Mon Sep 17 00:00:00 2001 From: Xiaonan Shen Date: Mon, 25 May 2020 17:56:12 -0700 Subject: [PATCH 197/406] Improve Vacuum Entity (#35554) --- homeassistant/components/demo/vacuum.py | 18 ------ .../components/mqtt/vacuum/schema_legacy.py | 14 +---- .../components/mqtt/vacuum/schema_state.py | 10 +-- .../components/roomba/irobot_base.py | 10 --- homeassistant/components/vacuum/__init__.py | 63 ++++++++----------- 5 files changed, 29 insertions(+), 86 deletions(-) diff --git a/homeassistant/components/demo/vacuum.py b/homeassistant/components/demo/vacuum.py index a5d85aa9bd6..528944ae27b 100644 --- a/homeassistant/components/demo/vacuum.py +++ b/homeassistant/components/demo/vacuum.py @@ -126,31 +126,21 @@ class DemoVacuum(VacuumEntity): @property def status(self): """Return the status of the vacuum.""" - if self.supported_features & SUPPORT_STATUS == 0: - return - return self._status @property def fan_speed(self): """Return the status of the vacuum.""" - if self.supported_features & SUPPORT_FAN_SPEED == 0: - return - return self._fan_speed @property def fan_speed_list(self): """Return the status of the vacuum.""" - assert self.supported_features & SUPPORT_FAN_SPEED != 0 return FAN_SPEEDS @property def battery_level(self): """Return the status of the vacuum.""" - if self.supported_features & SUPPORT_BATTERY == 0: - return - return max(0, min(100, self._battery_level)) @property @@ -289,24 +279,16 @@ class StateDemoVacuum(StateVacuumEntity): @property def battery_level(self): """Return the current battery level of the vacuum.""" - if self.supported_features & SUPPORT_BATTERY == 0: - return - return max(0, min(100, self._battery_level)) @property def fan_speed(self): """Return the current fan speed of the vacuum.""" - if self.supported_features & SUPPORT_FAN_SPEED == 0: - return - return self._fan_speed @property def fan_speed_list(self): """Return the list of supported fan speeds.""" - if self.supported_features & SUPPORT_FAN_SPEED == 0: - return return FAN_SPEEDS @property diff --git a/homeassistant/components/mqtt/vacuum/schema_legacy.py b/homeassistant/components/mqtt/vacuum/schema_legacy.py index c4259272bb3..b69565f7114 100644 --- a/homeassistant/components/mqtt/vacuum/schema_legacy.py +++ b/homeassistant/components/mqtt/vacuum/schema_legacy.py @@ -395,33 +395,21 @@ class MqttVacuum( @property def status(self): """Return a status string for the vacuum.""" - if self.supported_features & SUPPORT_STATUS == 0: - return None - return self._status @property def fan_speed(self): """Return the status of the vacuum.""" - if self.supported_features & SUPPORT_FAN_SPEED == 0: - return None - return self._fan_speed @property def fan_speed_list(self): - """Return the status of the vacuum. - - No need to check SUPPORT_FAN_SPEED, this won't be called if fan_speed is None. - """ + """Return the status of the vacuum.""" return self._fan_speed_list @property def battery_level(self): """Return the status of the vacuum.""" - if self.supported_features & SUPPORT_BATTERY == 0: - return None - return max(0, min(100, self._battery_level)) @property diff --git a/homeassistant/components/mqtt/vacuum/schema_state.py b/homeassistant/components/mqtt/vacuum/schema_state.py index 9049df45110..628f85614fe 100644 --- a/homeassistant/components/mqtt/vacuum/schema_state.py +++ b/homeassistant/components/mqtt/vacuum/schema_state.py @@ -275,24 +275,16 @@ class MqttStateVacuum( @property def fan_speed(self): """Return fan speed of the vacuum.""" - if self.supported_features & SUPPORT_FAN_SPEED == 0: - return None - return self._state_attrs.get(FAN_SPEED, 0) @property def fan_speed_list(self): - """Return fan speed list of the vacuum. - - No need to check SUPPORT_FAN_SPEED, this won't be called if fan_speed is None. - """ + """Return fan speed list of the vacuum.""" return self._fan_speed_list @property def battery_level(self): """Return battery level of the vacuum.""" - if self.supported_features & SUPPORT_BATTERY == 0: - return None return max(0, min(100, self._state_attrs.get(BATTERY, 0))) @property diff --git a/homeassistant/components/roomba/irobot_base.py b/homeassistant/components/roomba/irobot_base.py index 035428192c0..8bc1e22547f 100644 --- a/homeassistant/components/roomba/irobot_base.py +++ b/homeassistant/components/roomba/irobot_base.py @@ -146,16 +146,6 @@ class IRobotVacuum(IRobotEntity, StateVacuumEntity): """Flag vacuum cleaner robot features that are supported.""" return SUPPORT_IROBOT - @property - def fan_speed(self): - """Return the fan speed of the vacuum cleaner.""" - return None - - @property - def fan_speed_list(self): - """Get the list of available fan speed steps of the vacuum cleaner.""" - return [] - @property def battery_level(self): """Return the battery level of the vacuum cleaner.""" diff --git a/homeassistant/components/vacuum/__init__.py b/homeassistant/components/vacuum/__init__.py index 111fa64f988..ca5caec7ac1 100644 --- a/homeassistant/components/vacuum/__init__.py +++ b/homeassistant/components/vacuum/__init__.py @@ -147,6 +147,11 @@ class _BaseVacuum(Entity): """Return the battery level of the vacuum cleaner.""" return None + @property + def battery_icon(self): + """Return the battery icon for the vacuum cleaner.""" + raise NotImplementedError() + @property def fan_speed(self): """Return the fan speed of the vacuum cleaner.""" @@ -157,6 +162,26 @@ class _BaseVacuum(Entity): """Get the list of available fan speed steps of the vacuum cleaner.""" raise NotImplementedError() + @property + def capability_attributes(self): + """Return capability attributes.""" + if self.supported_features & SUPPORT_FAN_SPEED: + return {ATTR_FAN_SPEED_LIST: self.fan_speed_list} + + @property + def state_attributes(self): + """Return the state attributes of the vacuum cleaner.""" + data = {} + + if self.supported_features & SUPPORT_BATTERY: + data[ATTR_BATTERY_LEVEL] = self.battery_level + data[ATTR_BATTERY_ICON] = self.battery_icon + + if self.supported_features & SUPPORT_FAN_SPEED: + data[ATTR_FAN_SPEED] = self.fan_speed + + return data + def stop(self, **kwargs): """Stop the vacuum cleaner.""" raise NotImplementedError() @@ -246,27 +271,14 @@ class VacuumEntity(_BaseVacuum, ToggleEntity): battery_level=self.battery_level, charging=charging ) - @property - def capability_attributes(self): - """Return capability attributes.""" - if self.supported_features & SUPPORT_FAN_SPEED: - return {ATTR_FAN_SPEED_LIST: self.fan_speed_list} - @property def state_attributes(self): """Return the state attributes of the vacuum cleaner.""" - data = {} + data = super().state_attributes - if self.status is not None: + if self.supported_features & SUPPORT_STATUS: data[ATTR_STATUS] = self.status - if self.battery_level is not None: - data[ATTR_BATTERY_LEVEL] = self.battery_level - data[ATTR_BATTERY_ICON] = self.battery_icon - - if self.fan_speed is not None: - data[ATTR_FAN_SPEED] = self.fan_speed - return data def turn_on(self, **kwargs): @@ -337,27 +349,6 @@ class StateVacuumEntity(_BaseVacuum): battery_level=self.battery_level, charging=charging ) - @property - def capability_attributes(self): - """Return capability attributes.""" - if self.supported_features & SUPPORT_FAN_SPEED: - return {ATTR_FAN_SPEED_LIST: self.fan_speed_list} - - @property - def state_attributes(self): - """Return the state attributes of the vacuum cleaner.""" - data = {} - - if self.battery_level is not None: - data[ATTR_BATTERY_LEVEL] = self.battery_level - data[ATTR_BATTERY_ICON] = self.battery_icon - - if self.fan_speed is not None: - data[ATTR_FAN_SPEED] = self.fan_speed - data[ATTR_FAN_SPEED_LIST] = self.fan_speed_list - - return data - def start(self): """Start or resume the cleaning task.""" raise NotImplementedError() From 67a96222095e38f5e6259608896b2d1919b91f21 Mon Sep 17 00:00:00 2001 From: MatsNl <37705266+MatsNl@users.noreply.github.com> Date: Tue, 26 May 2020 08:38:02 +0200 Subject: [PATCH 198/406] Add tests to Atag integration (#35944) * add tests * better error handling in dependency * dont suppress errors * add support for multiple devices * add test for Unauthorized status * raise error on service call failure --- .coveragerc | 4 - homeassistant/components/atag/__init__.py | 18 +-- homeassistant/components/atag/climate.py | 6 +- homeassistant/components/atag/config_flow.py | 19 +-- homeassistant/components/atag/manifest.json | 2 +- homeassistant/components/atag/sensor.py | 4 + homeassistant/components/atag/strings.json | 5 +- homeassistant/components/atag/water_heater.py | 5 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/atag/__init__.py | 86 +++++++++++++- tests/components/atag/test_climate.py | 108 ++++++++++++++++++ tests/components/atag/test_config_flow.py | 86 ++++++++------ tests/components/atag/test_init.py | 39 +++++++ tests/components/atag/test_sensors.py | 21 ++++ tests/components/atag/test_water_heater.py | 41 +++++++ 16 files changed, 372 insertions(+), 76 deletions(-) create mode 100644 tests/components/atag/test_climate.py create mode 100644 tests/components/atag/test_init.py create mode 100644 tests/components/atag/test_sensors.py create mode 100644 tests/components/atag/test_water_heater.py diff --git a/.coveragerc b/.coveragerc index c99b29cd719..dfab2c4035a 100644 --- a/.coveragerc +++ b/.coveragerc @@ -63,10 +63,6 @@ omit = homeassistant/components/arwn/sensor.py homeassistant/components/asterisk_cdr/mailbox.py homeassistant/components/asterisk_mbox/* - homeassistant/components/atag/__init__.py - homeassistant/components/atag/climate.py - homeassistant/components/atag/sensor.py - homeassistant/components/atag/water_heater.py homeassistant/components/aten_pe/* homeassistant/components/atome/* homeassistant/components/aurora_abb_powerone/sensor.py diff --git a/homeassistant/components/atag/__init__.py b/homeassistant/components/atag/__init__.py index e80ae0cc79e..237a82f207a 100644 --- a/homeassistant/components/atag/__init__.py +++ b/homeassistant/components/atag/__init__.py @@ -31,16 +31,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): session = async_get_clientsession(hass) coordinator = AtagDataUpdateCoordinator(hass, session, entry) - try: - await coordinator.async_refresh() - except AtagException: - raise ConfigEntryNotReady - + await coordinator.async_refresh() if not coordinator.last_update_success: raise ConfigEntryNotReady hass.data.setdefault(DOMAIN, {}) hass.data[DOMAIN][entry.entry_id] = coordinator + if entry.unique_id is None: + hass.config_entries.async_update_entry(entry, unique_id=coordinator.atag.id) for platform in PLATFORMS: hass.async_create_task( @@ -65,9 +63,8 @@ class AtagDataUpdateCoordinator(DataUpdateCoordinator): """Update data via library.""" with async_timeout.timeout(20): try: - await self.atag.update() - if not self.atag.report: - raise UpdateFailed("No data") + if not await self.atag.update(): + raise UpdateFailed("No data received") except AtagException as error: raise UpdateFailed(error) return self.atag.report @@ -121,11 +118,6 @@ class AtagEntity(Entity): """Return the polling requirement of the entity.""" return False - @property - def unit_of_measurement(self): - """Return the unit of measurement of this entity, if any.""" - return self.coordinator.atag.climate.temp_unit - @property def available(self): """Return True if entity is available.""" diff --git a/homeassistant/components/atag/climate.py b/homeassistant/components/atag/climate.py index 4c39b2ea8f8..ad46fefe8c2 100644 --- a/homeassistant/components/atag/climate.py +++ b/homeassistant/components/atag/climate.py @@ -12,7 +12,7 @@ from homeassistant.components.climate.const import ( SUPPORT_PRESET_MODE, SUPPORT_TARGET_TEMPERATURE, ) -from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT +from homeassistant.const import ATTR_TEMPERATURE from . import CLIMATE, DOMAIN, AtagEntity @@ -66,9 +66,7 @@ class AtagThermostat(AtagEntity, ClimateEntity): @property def temperature_unit(self): """Return the unit of measurement.""" - if self.coordinator.atag.climate.temp_unit in [TEMP_CELSIUS, TEMP_FAHRENHEIT]: - return self.coordinator.atag.climate.temp_unit - return None + return self.coordinator.atag.climate.temp_unit @property def current_temperature(self) -> Optional[float]: diff --git a/homeassistant/components/atag/config_flow.py b/homeassistant/components/atag/config_flow.py index 369f4b98587..e59d9154a4a 100644 --- a/homeassistant/components/atag/config_flow.py +++ b/homeassistant/components/atag/config_flow.py @@ -1,9 +1,9 @@ """Config flow for the Atag component.""" -from pyatag import DEFAULT_PORT, AtagException, AtagOne +import pyatag # from pyatag import DEFAULT_PORT, AtagException, AtagOne import voluptuous as vol from homeassistant import config_entries -from homeassistant.const import CONF_DEVICE, CONF_EMAIL, CONF_HOST, CONF_PORT +from homeassistant.const import CONF_EMAIL, CONF_HOST, CONF_PORT from homeassistant.core import callback from homeassistant.helpers.aiohttp_client import async_get_clientsession @@ -12,7 +12,7 @@ from . import DOMAIN # pylint: disable=unused-import DATA_SCHEMA = { vol.Required(CONF_HOST): str, vol.Optional(CONF_EMAIL): str, - vol.Required(CONF_PORT, default=DEFAULT_PORT): vol.Coerce(int), + vol.Required(CONF_PORT, default=pyatag.const.DEFAULT_PORT): vol.Coerce(int), } @@ -25,21 +25,22 @@ class AtagConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user(self, user_input=None): """Handle a flow initialized by the user.""" - if self._async_current_entries(): - return self.async_abort(reason="already_configured") - if not user_input: return await self._show_form() session = async_get_clientsession(self.hass) try: - atag = AtagOne(session=session, **user_input) + atag = pyatag.AtagOne(session=session, **user_input) await atag.authorize() await atag.update(force=True) - except AtagException: + except pyatag.errors.Unauthorized: + return await self._show_form({"base": "unauthorized"}) + except pyatag.errors.AtagException: return await self._show_form({"base": "connection_error"}) - user_input.update({CONF_DEVICE: atag.id}) + await self.async_set_unique_id(atag.id) + self._abort_if_unique_id_configured(updates=user_input) + return self.async_create_entry(title=atag.id, data=user_input) @callback diff --git a/homeassistant/components/atag/manifest.json b/homeassistant/components/atag/manifest.json index 5fd77ee5155..9f8a5d2c6eb 100644 --- a/homeassistant/components/atag/manifest.json +++ b/homeassistant/components/atag/manifest.json @@ -3,6 +3,6 @@ "name": "Atag", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/atag/", - "requirements": ["pyatag==0.3.1.2"], + "requirements": ["pyatag==0.3.3.4"], "codeowners": ["@MatsNL"] } diff --git a/homeassistant/components/atag/sensor.py b/homeassistant/components/atag/sensor.py index d5ff0b7bbde..1d647eb4764 100644 --- a/homeassistant/components/atag/sensor.py +++ b/homeassistant/components/atag/sensor.py @@ -5,6 +5,8 @@ from homeassistant.const import ( PRESSURE_BAR, TEMP_CELSIUS, TEMP_FAHRENHEIT, + TIME_HOURS, + UNIT_PERCENTAGE, ) from . import DOMAIN, AtagEntity @@ -65,6 +67,8 @@ class AtagSensor(AtagEntity): PRESSURE_BAR, TEMP_CELSIUS, TEMP_FAHRENHEIT, + UNIT_PERCENTAGE, + TIME_HOURS, ]: return self.coordinator.data[self._id].measure return None diff --git a/homeassistant/components/atag/strings.json b/homeassistant/components/atag/strings.json index 859f0531c5a..85e22c10c1b 100644 --- a/homeassistant/components/atag/strings.json +++ b/homeassistant/components/atag/strings.json @@ -12,10 +12,11 @@ } }, "error": { + "unauthorized": "Pairing denied, check device for auth request", "connection_error": "Failed to connect, please try again" }, "abort": { - "already_configured": "Only one Atag device can be added to Home Assistant" + "already_configured": "This device has already been added to HomeAssistant" } } -} \ No newline at end of file +} diff --git a/homeassistant/components/atag/water_heater.py b/homeassistant/components/atag/water_heater.py index 311ff56985b..f9c2a4625bb 100644 --- a/homeassistant/components/atag/water_heater.py +++ b/homeassistant/components/atag/water_heater.py @@ -40,9 +40,8 @@ class AtagWaterHeater(AtagEntity, WaterHeaterEntity): @property def current_operation(self): """Return current operation.""" - if self.coordinator.atag.dhw.status: - return STATE_PERFORMANCE - return STATE_OFF + operation = self.coordinator.atag.dhw.current_operation + return operation if operation in self.operation_list else STATE_OFF @property def operation_list(self): diff --git a/requirements_all.txt b/requirements_all.txt index 4613422e270..91d5d29a02d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1212,7 +1212,7 @@ pyalmond==0.0.2 pyarlo==0.2.3 # homeassistant.components.atag -pyatag==0.3.1.2 +pyatag==0.3.3.4 # homeassistant.components.netatmo pyatmo==3.3.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index d95db048d98..ef0e379b0df 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -518,7 +518,7 @@ pyalmond==0.0.2 pyarlo==0.2.3 # homeassistant.components.atag -pyatag==0.3.1.2 +pyatag==0.3.3.4 # homeassistant.components.netatmo pyatmo==3.3.1 diff --git a/tests/components/atag/__init__.py b/tests/components/atag/__init__.py index b975a8de929..3f2b6468491 100644 --- a/tests/components/atag/__init__.py +++ b/tests/components/atag/__init__.py @@ -1 +1,85 @@ -"""Tests for the Atag component.""" +"""Tests for the Atag integration.""" + +from homeassistant.components.atag import DOMAIN +from homeassistant.const import CONF_EMAIL, CONF_HOST, CONF_PORT +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry +from tests.test_util.aiohttp import AiohttpClientMocker + +USER_INPUT = { + CONF_HOST: "127.0.0.1", + CONF_EMAIL: "atag@domain.com", + CONF_PORT: 10000, +} +UID = "xxxx-xxxx-xxxx_xx-xx-xxx-xxx" +PAIR_REPLY = {"pair_reply": {"status": {"device_id": UID}, "acc_status": 2}} +UPDATE_REPLY = {"update_reply": {"status": {"device_id": UID}, "acc_status": 2}} +RECEIVE_REPLY = { + "retrieve_reply": { + "status": {"device_id": UID}, + "report": { + "burning_hours": 1000, + "room_temp": 20, + "outside_temp": 15, + "dhw_water_temp": 30, + "ch_water_temp": 40, + "ch_water_pres": 1.8, + "ch_return_temp": 35, + "boiler_status": 0, + "tout_avg": 12, + "details": {"rel_mod_level": 0}, + }, + "control": { + "ch_control_mode": 0, + "ch_mode": 1, + "ch_mode_duration": 0, + "ch_mode_temp": 12, + "dhw_temp_setp": 40, + "dhw_mode": 1, + "dhw_mode_temp": 150, + "weather_status": 8, + }, + "configuration": { + "download_url": "http://firmware.atag-one.com:80/R58", + "temp_unit": 0, + "dhw_max_set": 65, + "dhw_min_set": 40, + }, + "acc_status": 2, + } +} + + +async def init_integration( + hass: HomeAssistant, + aioclient_mock: AiohttpClientMocker, + rgbw: bool = False, + skip_setup: bool = False, +) -> MockConfigEntry: + """Set up the Atag integration in Home Assistant.""" + + aioclient_mock.get( + "http://127.0.0.1:10000/retrieve", + json=RECEIVE_REPLY, + headers={"Content-Type": "application/json"}, + ) + aioclient_mock.post( + "http://127.0.0.1:10000/update", + json=UPDATE_REPLY, + headers={"Content-Type": "application/json"}, + ) + aioclient_mock.post( + "http://127.0.0.1:10000/pair", + json=PAIR_REPLY, + headers={"Content-Type": "application/json"}, + ) + + entry = MockConfigEntry(domain=DOMAIN, data=USER_INPUT) + entry.add_to_hass(hass) + + if not skip_setup: + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + return entry diff --git a/tests/components/atag/test_climate.py b/tests/components/atag/test_climate.py new file mode 100644 index 00000000000..0ccb67d0eb5 --- /dev/null +++ b/tests/components/atag/test_climate.py @@ -0,0 +1,108 @@ +"""Tests for the Atag climate platform.""" + +from homeassistant.components.atag import CLIMATE, DOMAIN +from homeassistant.components.climate import ( + ATTR_HVAC_ACTION, + ATTR_HVAC_MODE, + ATTR_PRESET_MODE, + HVAC_MODE_HEAT, + SERVICE_SET_HVAC_MODE, + SERVICE_SET_PRESET_MODE, + SERVICE_SET_TEMPERATURE, +) +from homeassistant.components.climate.const import CURRENT_HVAC_HEAT, PRESET_AWAY +from homeassistant.components.homeassistant import ( + DOMAIN as HA_DOMAIN, + SERVICE_UPDATE_ENTITY, +) +from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE, STATE_UNKNOWN +from homeassistant.core import HomeAssistant +from homeassistant.setup import async_setup_component + +from tests.async_mock import PropertyMock, patch +from tests.components.atag import UID, init_integration +from tests.test_util.aiohttp import AiohttpClientMocker + +CLIMATE_ID = f"{CLIMATE}.{DOMAIN}" + + +async def test_climate( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: + """Test the creation and values of Atag climate device.""" + with patch("pyatag.entities.Climate.status"): + entry = await init_integration(hass, aioclient_mock) + registry = await hass.helpers.entity_registry.async_get_registry() + + assert registry.async_is_registered(CLIMATE_ID) + entry = registry.async_get(CLIMATE_ID) + assert entry.unique_id == f"{UID}-{CLIMATE}" + assert ( + hass.states.get(CLIMATE_ID).attributes[ATTR_HVAC_ACTION] + == CURRENT_HVAC_HEAT + ) + + +async def test_setting_climate( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: + """Test setting the climate device.""" + await init_integration(hass, aioclient_mock) + with patch("pyatag.entities.Climate.set_temp") as mock_set_temp: + await hass.services.async_call( + CLIMATE, + SERVICE_SET_TEMPERATURE, + {ATTR_ENTITY_ID: CLIMATE_ID, ATTR_TEMPERATURE: 15}, + blocking=True, + ) + await hass.async_block_till_done() + mock_set_temp.assert_called_once_with(15) + + with patch("pyatag.entities.Climate.set_preset_mode") as mock_set_preset: + await hass.services.async_call( + CLIMATE, + SERVICE_SET_PRESET_MODE, + {ATTR_ENTITY_ID: CLIMATE_ID, ATTR_PRESET_MODE: PRESET_AWAY}, + blocking=True, + ) + await hass.async_block_till_done() + mock_set_preset.assert_called_once_with(PRESET_AWAY) + + with patch("pyatag.entities.Climate.set_hvac_mode") as mock_set_hvac: + await hass.services.async_call( + CLIMATE, + SERVICE_SET_HVAC_MODE, + {ATTR_ENTITY_ID: CLIMATE_ID, ATTR_HVAC_MODE: HVAC_MODE_HEAT}, + blocking=True, + ) + await hass.async_block_till_done() + mock_set_hvac.assert_called_once_with(HVAC_MODE_HEAT) + + +async def test_incorrect_modes( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, +) -> None: + """Test incorrect values are handled correctly.""" + with patch( + "pyatag.entities.Climate.hvac_mode", + new_callable=PropertyMock(return_value="bug"), + ): + await init_integration(hass, aioclient_mock) + assert hass.states.get(CLIMATE_ID).state == STATE_UNKNOWN + + +async def test_update_service( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: + """Test the updater service is called.""" + await init_integration(hass, aioclient_mock) + await async_setup_component(hass, HA_DOMAIN, {}) + with patch("pyatag.AtagOne.update") as updater: + await hass.services.async_call( + HA_DOMAIN, + SERVICE_UPDATE_ENTITY, + {ATTR_ENTITY_ID: CLIMATE_ID}, + blocking=True, + ) + await hass.async_block_till_done() + updater.assert_called_once() diff --git a/tests/components/atag/test_config_flow.py b/tests/components/atag/test_config_flow.py index 65583340524..dc675e24eba 100644 --- a/tests/components/atag/test_config_flow.py +++ b/tests/components/atag/test_config_flow.py @@ -1,20 +1,19 @@ """Tests for the Atag config flow.""" -from pyatag import AtagException +from pyatag import errors from homeassistant import config_entries, data_entry_flow from homeassistant.components.atag import DOMAIN -from homeassistant.const import CONF_DEVICE, CONF_EMAIL, CONF_HOST, CONF_PORT +from homeassistant.core import HomeAssistant from tests.async_mock import PropertyMock, patch -from tests.common import MockConfigEntry - -FIXTURE_USER_INPUT = { - CONF_HOST: "127.0.0.1", - CONF_EMAIL: "test@domain.com", - CONF_PORT: 10000, -} -FIXTURE_COMPLETE_ENTRY = FIXTURE_USER_INPUT.copy() -FIXTURE_COMPLETE_ENTRY[CONF_DEVICE] = "device_identifier" +from tests.components.atag import ( + PAIR_REPLY, + RECEIVE_REPLY, + UID, + USER_INPUT, + init_integration, +) +from tests.test_util.aiohttp import AiohttpClientMocker async def test_show_form(hass): @@ -27,29 +26,31 @@ async def test_show_form(hass): assert result["step_id"] == "user" -async def test_one_config_allowed(hass): +async def test_adding_second_device( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: """Test that only one Atag configuration is allowed.""" - MockConfigEntry(domain="atag", data=FIXTURE_USER_INPUT).add_to_hass(hass) - + await init_integration(hass, aioclient_mock) result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER} + DOMAIN, context={"source": config_entries.SOURCE_USER}, data=USER_INPUT ) assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result["reason"] == "already_configured" + with patch( + "pyatag.AtagOne.id", new_callable=PropertyMock(return_value="secondary_device"), + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER}, data=USER_INPUT + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY async def test_connection_error(hass): """Test we show user form on Atag connection error.""" - - with patch( - "homeassistant.components.atag.config_flow.AtagOne.authorize", - side_effect=AtagException(), - ): + with patch("pyatag.AtagOne.authorize", side_effect=errors.AtagException()): result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": config_entries.SOURCE_USER}, - data=FIXTURE_USER_INPUT, + DOMAIN, context={"source": config_entries.SOURCE_USER}, data=USER_INPUT, ) assert result["type"] == data_entry_flow.RESULT_TYPE_FORM @@ -57,19 +58,30 @@ async def test_connection_error(hass): assert result["errors"] == {"base": "connection_error"} -async def test_full_flow_implementation(hass): - """Test registering an integration and finishing flow works.""" - with patch("homeassistant.components.atag.AtagOne.authorize",), patch( - "homeassistant.components.atag.AtagOne.update", - ), patch( - "homeassistant.components.atag.AtagOne.id", - new_callable=PropertyMock(return_value="device_identifier"), - ): +async def test_unauthorized(hass): + """Test we show correct form when Unauthorized error is raised.""" + with patch("pyatag.AtagOne.authorize", side_effect=errors.Unauthorized()): result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": config_entries.SOURCE_USER}, - data=FIXTURE_USER_INPUT, + DOMAIN, context={"source": config_entries.SOURCE_USER}, data=USER_INPUT, ) - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result["title"] == FIXTURE_COMPLETE_ENTRY[CONF_DEVICE] - assert result["data"] == FIXTURE_COMPLETE_ENTRY + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "user" + assert result["errors"] == {"base": "unauthorized"} + + +async def test_full_flow_implementation( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: + """Test registering an integration and finishing flow works.""" + aioclient_mock.get( + "http://127.0.0.1:10000/retrieve", json=RECEIVE_REPLY, + ) + aioclient_mock.post( + "http://127.0.0.1:10000/pair", json=PAIR_REPLY, + ) + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER}, data=USER_INPUT, + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["title"] == UID + assert result["result"].unique_id == UID diff --git a/tests/components/atag/test_init.py b/tests/components/atag/test_init.py new file mode 100644 index 00000000000..0fca4b37c46 --- /dev/null +++ b/tests/components/atag/test_init.py @@ -0,0 +1,39 @@ +"""Tests for the ATAG integration.""" +import aiohttp + +from homeassistant.components.atag import DOMAIN +from homeassistant.config_entries import ENTRY_STATE_SETUP_RETRY +from homeassistant.core import HomeAssistant + +from tests.async_mock import patch +from tests.components.atag import init_integration +from tests.test_util.aiohttp import AiohttpClientMocker + + +async def test_config_entry_not_ready( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: + """Test configuration entry not ready on library error.""" + aioclient_mock.get("http://127.0.0.1:10000/retrieve", exc=aiohttp.ClientError) + entry = await init_integration(hass, aioclient_mock) + assert entry.state == ENTRY_STATE_SETUP_RETRY + + +async def test_config_entry_empty_reply( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: + """Test configuration entry not ready when library returns False.""" + with patch("pyatag.AtagOne.update", return_value=False): + entry = await init_integration(hass, aioclient_mock) + assert entry.state == ENTRY_STATE_SETUP_RETRY + + +async def test_unload_config_entry( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: + """Test the ATAG configuration entry unloading.""" + entry = await init_integration(hass, aioclient_mock) + assert hass.data[DOMAIN] + await hass.config_entries.async_unload(entry.entry_id) + await hass.async_block_till_done() + assert not hass.data.get(DOMAIN) diff --git a/tests/components/atag/test_sensors.py b/tests/components/atag/test_sensors.py new file mode 100644 index 00000000000..e7bf4df44e9 --- /dev/null +++ b/tests/components/atag/test_sensors.py @@ -0,0 +1,21 @@ +"""Tests for the Atag sensor platform.""" + +from homeassistant.components.atag.sensor import SENSORS +from homeassistant.core import HomeAssistant + +from tests.components.atag import UID, init_integration +from tests.test_util.aiohttp import AiohttpClientMocker + + +async def test_sensors( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: + """Test the creation of ATAG sensors.""" + entry = await init_integration(hass, aioclient_mock) + registry = await hass.helpers.entity_registry.async_get_registry() + + for item in SENSORS: + sensor_id = "_".join(f"sensor.{item}".lower().split()) + assert registry.async_is_registered(sensor_id) + entry = registry.async_get(sensor_id) + assert entry.unique_id in [f"{UID}-{v}" for v in SENSORS.values()] diff --git a/tests/components/atag/test_water_heater.py b/tests/components/atag/test_water_heater.py new file mode 100644 index 00000000000..0d717db70bc --- /dev/null +++ b/tests/components/atag/test_water_heater.py @@ -0,0 +1,41 @@ +"""Tests for the Atag water heater platform.""" + +from homeassistant.components.atag import DOMAIN, WATER_HEATER +from homeassistant.components.water_heater import SERVICE_SET_TEMPERATURE +from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE +from homeassistant.core import HomeAssistant + +from tests.async_mock import patch +from tests.components.atag import UID, init_integration +from tests.test_util.aiohttp import AiohttpClientMocker + +WATER_HEATER_ID = f"{WATER_HEATER}.{DOMAIN}" + + +async def test_water_heater( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: + """Test the creation of Atag water heater.""" + with patch("pyatag.entities.DHW.status"): + entry = await init_integration(hass, aioclient_mock) + registry = await hass.helpers.entity_registry.async_get_registry() + + assert registry.async_is_registered(WATER_HEATER_ID) + entry = registry.async_get(WATER_HEATER_ID) + assert entry.unique_id == f"{UID}-{WATER_HEATER}" + + +async def test_setting_target_temperature( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: + """Test setting the water heater device.""" + await init_integration(hass, aioclient_mock) + with patch("pyatag.entities.DHW.set_temp") as mock_set_temp: + await hass.services.async_call( + WATER_HEATER, + SERVICE_SET_TEMPERATURE, + {ATTR_ENTITY_ID: WATER_HEATER_ID, ATTR_TEMPERATURE: 50}, + blocking=True, + ) + await hass.async_block_till_done() + mock_set_temp.assert_called_once_with(50) From 599d3ae930c2cb7a9b7e34fa1ec5c66f91ab541d Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Tue, 26 May 2020 01:00:05 -0600 Subject: [PATCH 199/406] Fix bugs with AirVisual auto-leveling API (#36097) * Fix bugs with AirVisual auto-leveling API * Code review * Code review --- .../components/airvisual/__init__.py | 73 ++++++++++++------- .../components/airvisual/air_quality.py | 8 +- 2 files changed, 52 insertions(+), 29 deletions(-) diff --git a/homeassistant/components/airvisual/__init__.py b/homeassistant/components/airvisual/__init__.py index e5d8b03f316..96b3770974a 100644 --- a/homeassistant/components/airvisual/__init__.py +++ b/homeassistant/components/airvisual/__init__.py @@ -38,7 +38,7 @@ from .const import ( PLATFORMS = ["air_quality", "sensor"] DEFAULT_ATTRIBUTION = "Data provided by AirVisual" -DEFAULT_NODE_PRO_SCAN_INTERVAL = timedelta(minutes=1) +DEFAULT_NODE_PRO_UPDATE_INTERVAL = timedelta(minutes=1) DEFAULT_OPTIONS = {CONF_SHOW_ON_MAP: True} GEOGRAPHY_COORDINATES_SCHEMA = vol.Schema( @@ -95,27 +95,43 @@ def async_get_cloud_api_update_interval(hass, api_key): This will shift based on the number of active consumers, thus keeping the user under the monthly API limit. """ - num_consumers = len( - { - config_entry - for config_entry in hass.config_entries.async_entries(DOMAIN) - if config_entry.data.get(CONF_API_KEY) == api_key - } - ) + num_consumers = len(async_get_cloud_coordinators_by_api_key(hass, api_key)) # Assuming 10,000 calls per month and a "smallest possible month" of 28 days; note # that we give a buffer of 1500 API calls for any drift, restarts, etc.: minutes_between_api_calls = ceil(1 / (8500 / 28 / 24 / 60 / num_consumers)) + + LOGGER.debug( + "Leveling API key usage (%s): %s consumers, %s minutes between updates", + api_key, + num_consumers, + minutes_between_api_calls, + ) + return timedelta(minutes=minutes_between_api_calls) @callback -def async_reset_coordinator_update_intervals(hass, update_interval): - """Update any existing data coordinators with a new update interval.""" - if not hass.data[DOMAIN][DATA_COORDINATOR]: - return +def async_get_cloud_coordinators_by_api_key(hass, api_key): + """Get all DataUpdateCoordinator objects related to a particular API key.""" + coordinators = [] + for entry_id, coordinator in hass.data[DOMAIN][DATA_COORDINATOR].items(): + config_entry = hass.config_entries.async_get_entry(entry_id) + if config_entry.data.get(CONF_API_KEY) == api_key: + coordinators.append(coordinator) + return coordinators - for coordinator in hass.data[DOMAIN][DATA_COORDINATOR].values(): + +@callback +def async_sync_geo_coordinator_update_intervals(hass, api_key): + """Sync the update interval for geography-based data coordinators (by API key).""" + update_interval = async_get_cloud_api_update_interval(hass, api_key) + for coordinator in async_get_cloud_coordinators_by_api_key(hass, api_key): + LOGGER.debug( + "Updating interval for coordinator: %s, %s", + coordinator.name, + update_interval, + ) coordinator.update_interval = update_interval @@ -194,10 +210,6 @@ async def async_setup_entry(hass, config_entry): client = Client(api_key=config_entry.data[CONF_API_KEY], session=websession) - update_interval = async_get_cloud_api_update_interval( - hass, config_entry.data[CONF_API_KEY] - ) - async def async_update_data(): """Get new data from the API.""" if CONF_CITY in config_entry.data: @@ -219,14 +231,19 @@ async def async_setup_entry(hass, config_entry): coordinator = DataUpdateCoordinator( hass, LOGGER, - name="geography data", - update_interval=update_interval, + name=async_get_geography_id(config_entry.data), + # We give a placeholder update interval in order to create the coordinator; + # then, below, we use the coordinator's presence (along with any other + # coordinators using the same API key) to calculate an actual, leveled + # update interval: + update_interval=timedelta(minutes=5), update_method=async_update_data, ) - # Ensure any other, existing config entries that use this API key are updated - # with the new scan interval: - async_reset_coordinator_update_intervals(hass, update_interval) + hass.data[DOMAIN][DATA_COORDINATOR][config_entry.entry_id] = coordinator + async_sync_geo_coordinator_update_intervals( + hass, config_entry.data[CONF_API_KEY] + ) # Only geography-based entries have options: config_entry.add_update_listener(async_update_options) @@ -251,13 +268,13 @@ async def async_setup_entry(hass, config_entry): hass, LOGGER, name="Node/Pro data", - update_interval=DEFAULT_NODE_PRO_SCAN_INTERVAL, + update_interval=DEFAULT_NODE_PRO_UPDATE_INTERVAL, update_method=async_update_data, ) - await coordinator.async_refresh() + hass.data[DOMAIN][DATA_COORDINATOR][config_entry.entry_id] = coordinator - hass.data[DOMAIN][DATA_COORDINATOR][config_entry.entry_id] = coordinator + await coordinator.async_refresh() for component in PLATFORMS: hass.async_create_task( @@ -317,6 +334,12 @@ async def async_unload_entry(hass, config_entry): ) if unload_ok: hass.data[DOMAIN][DATA_COORDINATOR].pop(config_entry.entry_id) + if config_entry.data[CONF_INTEGRATION_TYPE] == INTEGRATION_TYPE_GEOGRAPHY: + # Re-calculate the update interval period for any remaining consumes of this + # API key: + async_sync_geo_coordinator_update_intervals( + hass, config_entry.data[CONF_API_KEY] + ) return unload_ok diff --git a/homeassistant/components/airvisual/air_quality.py b/homeassistant/components/airvisual/air_quality.py index bd1c10a9d84..bb2d64a23db 100644 --- a/homeassistant/components/airvisual/air_quality.py +++ b/homeassistant/components/airvisual/air_quality.py @@ -8,7 +8,7 @@ from .const import ( CONF_INTEGRATION_TYPE, DATA_COORDINATOR, DOMAIN, - INTEGRATION_TYPE_GEOGRAPHY, + INTEGRATION_TYPE_NODE_PRO, ) ATTR_HUMIDITY = "humidity" @@ -18,12 +18,12 @@ 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.""" - coordinator = hass.data[DOMAIN][DATA_COORDINATOR][config_entry.entry_id] - # Geography-based AirVisual integrations don't utilize this platform: - if config_entry.data[CONF_INTEGRATION_TYPE] == INTEGRATION_TYPE_GEOGRAPHY: + if config_entry.data[CONF_INTEGRATION_TYPE] != INTEGRATION_TYPE_NODE_PRO: return + coordinator = hass.data[DOMAIN][DATA_COORDINATOR][config_entry.entry_id] + async_add_entities([AirVisualNodeProSensor(coordinator)], True) From 97a523e8541cdce813683857590f556a8a04ecd3 Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Tue, 26 May 2020 03:38:41 -0400 Subject: [PATCH 200/406] Add device classes to Blink sensors (#35620) * Add device classes, move battery sensor to binary_sensor * Remove cast to bool --- .../components/blink/binary_sensor.py | 27 ++++++++++++++----- homeassistant/components/blink/sensor.py | 27 ++++++++++--------- 2 files changed, 35 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/blink/binary_sensor.py b/homeassistant/components/blink/binary_sensor.py index 8c86622b74e..1841dbbc438 100644 --- a/homeassistant/components/blink/binary_sensor.py +++ b/homeassistant/components/blink/binary_sensor.py @@ -1,11 +1,16 @@ """Support for Blink system camera control.""" -from homeassistant.components.binary_sensor import BinarySensorEntity +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_BATTERY, + DEVICE_CLASS_MOTION, + BinarySensorEntity, +) -from .const import DOMAIN, TYPE_CAMERA_ARMED, TYPE_MOTION_DETECTED +from .const import DOMAIN, TYPE_BATTERY, TYPE_CAMERA_ARMED, TYPE_MOTION_DETECTED BINARY_SENSORS = { - TYPE_CAMERA_ARMED: ["Camera Armed", "mdi:verified"], - TYPE_MOTION_DETECTED: ["Motion Detected", "mdi:run-fast"], + TYPE_BATTERY: ["Battery", DEVICE_CLASS_BATTERY], + TYPE_CAMERA_ARMED: ["Camera Armed", None], + TYPE_MOTION_DETECTED: ["Motion Detected", DEVICE_CLASS_MOTION], } @@ -27,9 +32,9 @@ class BlinkBinarySensor(BinarySensorEntity): """Initialize the sensor.""" self.data = data self._type = sensor_type - name, icon = BINARY_SENSORS[sensor_type] + name, device_class = BINARY_SENSORS[sensor_type] self._name = f"{DOMAIN} {camera} {name}" - self._icon = icon + self._device_class = device_class self._camera = data.cameras[camera] self._state = None self._unique_id = f"{self._camera.serial}-{self._type}" @@ -39,6 +44,11 @@ class BlinkBinarySensor(BinarySensorEntity): """Return the name of the blink sensor.""" return self._name + @property + def device_class(self): + """Return the class of this device.""" + return self._device_class + @property def is_on(self): """Return the status of the sensor.""" @@ -47,4 +57,7 @@ class BlinkBinarySensor(BinarySensorEntity): def update(self): """Update sensor state.""" self.data.refresh() - self._state = self._camera.attributes[self._type] + state = self._camera.attributes[self._type] + if self._type == TYPE_BATTERY: + state = state != "ok" + self._state = state diff --git a/homeassistant/components/blink/sensor.py b/homeassistant/components/blink/sensor.py index eb9e309fc65..35cc2d0d5a5 100644 --- a/homeassistant/components/blink/sensor.py +++ b/homeassistant/components/blink/sensor.py @@ -1,17 +1,20 @@ """Support for Blink system camera sensors.""" import logging -from homeassistant.const import TEMP_FAHRENHEIT +from homeassistant.const import ( + DEVICE_CLASS_SIGNAL_STRENGTH, + DEVICE_CLASS_TEMPERATURE, + TEMP_FAHRENHEIT, +) from homeassistant.helpers.entity import Entity -from .const import DOMAIN, TYPE_BATTERY, TYPE_TEMPERATURE, TYPE_WIFI_STRENGTH +from .const import DOMAIN, TYPE_TEMPERATURE, TYPE_WIFI_STRENGTH _LOGGER = logging.getLogger(__name__) SENSORS = { - TYPE_TEMPERATURE: ["Temperature", TEMP_FAHRENHEIT, "mdi:thermometer"], - TYPE_BATTERY: ["Battery", "", "mdi:battery-80"], - TYPE_WIFI_STRENGTH: ["Wifi Signal", "dBm", "mdi:wifi-strength-2"], + TYPE_TEMPERATURE: ["Temperature", TEMP_FAHRENHEIT, DEVICE_CLASS_TEMPERATURE], + TYPE_WIFI_STRENGTH: ["Wifi Signal", "dBm", DEVICE_CLASS_SIGNAL_STRENGTH], } @@ -31,15 +34,15 @@ class BlinkSensor(Entity): def __init__(self, data, camera, sensor_type): """Initialize sensors from Blink camera.""" - name, units, icon = SENSORS[sensor_type] + name, units, device_class = SENSORS[sensor_type] self._name = f"{DOMAIN} {camera} {name}" self._camera_name = name self._type = sensor_type + self._device_class = device_class self.data = data self._camera = data.cameras[camera] self._state = None self._unit_of_measurement = units - self._icon = icon self._unique_id = f"{self._camera.serial}-{self._type}" self._sensor_key = self._type if self._type == "temperature": @@ -55,16 +58,16 @@ class BlinkSensor(Entity): """Return the unique id for the camera sensor.""" return self._unique_id - @property - def icon(self): - """Return the icon of the sensor.""" - return self._icon - @property def state(self): """Return the camera's current state.""" return self._state + @property + def device_class(self): + """Return the device's class.""" + return self._device_class + @property def unit_of_measurement(self): """Return the unit of measurement.""" From 50b7d24e32cf7db278ad52a1b9b0917313fffd66 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 26 May 2020 11:44:07 +0200 Subject: [PATCH 201/406] Update frontend to 20200519.5 (#36154) --- 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 89f83d8fa65..14ae15dd87b 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==20200519.4"], + "requirements": ["home-assistant-frontend==20200519.5"], "dependencies": [ "api", "auth", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 5786ea0085e..278f41b1f45 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -12,7 +12,7 @@ cryptography==2.9.2 defusedxml==0.6.0 distro==1.5.0 hass-nabucasa==0.34.3 -home-assistant-frontend==20200519.4 +home-assistant-frontend==20200519.5 importlib-metadata==1.6.0 jinja2>=2.11.1 netdisco==2.6.0 diff --git a/requirements_all.txt b/requirements_all.txt index 91d5d29a02d..52e6cd81895 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -734,7 +734,7 @@ hole==0.5.1 holidays==0.10.2 # homeassistant.components.frontend -home-assistant-frontend==20200519.4 +home-assistant-frontend==20200519.5 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ef0e379b0df..cc16e1e5f39 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -315,7 +315,7 @@ hole==0.5.1 holidays==0.10.2 # homeassistant.components.frontend -home-assistant-frontend==20200519.4 +home-assistant-frontend==20200519.5 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From 26fdb8eb1e944277d4d9e26d6454b5c342b47682 Mon Sep 17 00:00:00 2001 From: MatsNl <37705266+MatsNl@users.noreply.github.com> Date: Tue, 26 May 2020 12:06:20 +0200 Subject: [PATCH 202/406] Remove commented code in Atag integration (#36153) --- homeassistant/components/atag/config_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/atag/config_flow.py b/homeassistant/components/atag/config_flow.py index e59d9154a4a..7ad05087304 100644 --- a/homeassistant/components/atag/config_flow.py +++ b/homeassistant/components/atag/config_flow.py @@ -1,5 +1,5 @@ """Config flow for the Atag component.""" -import pyatag # from pyatag import DEFAULT_PORT, AtagException, AtagOne +import pyatag import voluptuous as vol from homeassistant import config_entries From dc2fe66f2997a468174aef15f07eb5712197f9f1 Mon Sep 17 00:00:00 2001 From: gadgetmobile <57815233+gadgetmobile@users.noreply.github.com> Date: Tue, 26 May 2020 13:29:19 +0200 Subject: [PATCH 203/406] Clean up blebox climate (#36143) --- homeassistant/components/blebox/climate.py | 9 +++++---- tests/components/blebox/test_climate.py | 3 +++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/blebox/climate.py b/homeassistant/components/blebox/climate.py index 3f1b21ff687..4ee8cf9be76 100644 --- a/homeassistant/components/blebox/climate.py +++ b/homeassistant/components/blebox/climate.py @@ -1,6 +1,6 @@ """BleBox climate entity.""" -from homeassistant.components.climate import ClimateDevice +from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( CURRENT_HVAC_HEAT, CURRENT_HVAC_IDLE, @@ -22,7 +22,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ) -class BleBoxClimateEntity(BleBoxEntity, ClimateDevice): +class BleBoxClimateEntity(BleBoxEntity, ClimateEntity): """Representation of a BleBox climate feature (saunaBox).""" @property @@ -81,9 +81,10 @@ class BleBoxClimateEntity(BleBoxEntity, ClimateDevice): async def async_set_hvac_mode(self, hvac_mode): """Set the climate entity mode.""" if hvac_mode == HVAC_MODE_HEAT: - return await self._feature.async_on() + await self._feature.async_on() + return - return await self._feature.async_off() + await self._feature.async_off() async def async_set_temperature(self, **kwargs): """Set the thermostat temperature.""" diff --git a/tests/components/blebox/test_climate.py b/tests/components/blebox/test_climate.py index f7b58cf55c8..c36c93a7f98 100644 --- a/tests/components/blebox/test_climate.py +++ b/tests/components/blebox/test_climate.py @@ -135,6 +135,7 @@ async def test_on_when_below_desired(saunabox, hass, config): {"entity_id": entity_id, ATTR_HVAC_MODE: HVAC_MODE_HEAT}, blocking=True, ) + feature_mock.async_off.assert_not_called() state = hass.states.get(entity_id) assert state.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_HEAT @@ -169,6 +170,7 @@ async def test_on_when_above_desired(saunabox, hass, config): {"entity_id": entity_id, ATTR_HVAC_MODE: HVAC_MODE_HEAT}, blocking=True, ) + feature_mock.async_off.assert_not_called() state = hass.states.get(entity_id) assert state.attributes[ATTR_TEMPERATURE] == 23.4 @@ -203,6 +205,7 @@ async def test_off(saunabox, hass, config): {"entity_id": entity_id, ATTR_HVAC_MODE: HVAC_MODE_OFF}, blocking=True, ) + feature_mock.async_on.assert_not_called() state = hass.states.get(entity_id) assert state.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_OFF From 369745c4cf161974384308817739dcf642bbf76b Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Tue, 26 May 2020 07:47:25 -0600 Subject: [PATCH 204/406] Add support for Elexa Guardian water valve controllers (#34627) * Add support for Elexa Guardian water valve controllers * Zeroconf + cleanup * Sensors and services * API registration * Service bug fixes * Fix bug in cleanup * Tests and coverage * Fix incorrect service description * Bump aioguardian * Bump aioguardian to 0.2.2 * Bump aioguardian to 0.2.3 * Proper entity inheritance * Give device a proper name * Code review --- .coveragerc | 4 + CODEOWNERS | 1 + homeassistant/components/guardian/__init__.py | 364 ++++++++++++++++++ .../components/guardian/binary_sensor.py | 62 +++ .../components/guardian/config_flow.py | 91 +++++ homeassistant/components/guardian/const.py | 25 ++ .../components/guardian/manifest.json | 18 + homeassistant/components/guardian/sensor.py | 73 ++++ .../components/guardian/services.yaml | 21 + .../components/guardian/strings.json | 22 ++ homeassistant/components/guardian/switch.py | 83 ++++ .../components/guardian/translations/en.json | 22 ++ homeassistant/generated/config_flows.py | 1 + homeassistant/generated/zeroconf.py | 3 + requirements_all.txt | 3 + requirements_test_all.txt | 3 + tests/components/guardian/__init__.py | 1 + tests/components/guardian/conftest.py | 17 + tests/components/guardian/test_config_flow.py | 124 ++++++ 19 files changed, 938 insertions(+) create mode 100644 homeassistant/components/guardian/__init__.py create mode 100644 homeassistant/components/guardian/binary_sensor.py create mode 100644 homeassistant/components/guardian/config_flow.py create mode 100644 homeassistant/components/guardian/const.py create mode 100644 homeassistant/components/guardian/manifest.json create mode 100644 homeassistant/components/guardian/sensor.py create mode 100644 homeassistant/components/guardian/services.yaml create mode 100644 homeassistant/components/guardian/strings.json create mode 100644 homeassistant/components/guardian/switch.py create mode 100644 homeassistant/components/guardian/translations/en.json create mode 100644 tests/components/guardian/__init__.py create mode 100644 tests/components/guardian/conftest.py create mode 100644 tests/components/guardian/test_config_flow.py diff --git a/.coveragerc b/.coveragerc index dfab2c4035a..9f29ab80bb8 100644 --- a/.coveragerc +++ b/.coveragerc @@ -299,6 +299,10 @@ omit = homeassistant/components/growatt_server/sensor.py homeassistant/components/gstreamer/media_player.py homeassistant/components/gtfs/sensor.py + homeassistant/components/guardian/__init__.py + homeassistant/components/guardian/binary_sensor.py + homeassistant/components/guardian/sensor.py + homeassistant/components/guardian/switch.py homeassistant/components/habitica/* homeassistant/components/hangouts/* homeassistant/components/hangouts/__init__.py diff --git a/CODEOWNERS b/CODEOWNERS index a0ccec81579..569d104d0bb 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -161,6 +161,7 @@ homeassistant/components/griddy/* @bdraco homeassistant/components/group/* @home-assistant/core homeassistant/components/growatt_server/* @indykoning homeassistant/components/gtfs/* @robbiet480 +homeassistant/components/guardian/* @bachya homeassistant/components/harmony/* @ehendrix23 @bramkragten @bdraco homeassistant/components/hassio/* @home-assistant/hass-io homeassistant/components/heatmiser/* @andylockran diff --git a/homeassistant/components/guardian/__init__.py b/homeassistant/components/guardian/__init__.py new file mode 100644 index 00000000000..8ccf69c7077 --- /dev/null +++ b/homeassistant/components/guardian/__init__.py @@ -0,0 +1,364 @@ +"""The Elexa Guardian integration.""" +import asyncio +from datetime import timedelta + +from aioguardian import Client +from aioguardian.commands.device import ( + DEFAULT_FIRMWARE_UPGRADE_FILENAME, + DEFAULT_FIRMWARE_UPGRADE_PORT, + DEFAULT_FIRMWARE_UPGRADE_URL, +) +from aioguardian.errors import GuardianError +import voluptuous as vol + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import ( + ATTR_ATTRIBUTION, + CONF_FILENAME, + CONF_IP_ADDRESS, + CONF_PORT, + CONF_URL, +) +from homeassistant.core import HomeAssistant, callback +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.dispatcher import ( + async_dispatcher_connect, + async_dispatcher_send, +) +from homeassistant.helpers.entity import Entity +from homeassistant.helpers.event import async_track_time_interval +from homeassistant.helpers.service import ( + async_register_admin_service, + verify_domain_control, +) + +from .const import ( + CONF_UID, + DATA_CLIENT, + DATA_DIAGNOSTICS, + DATA_PAIR_DUMP, + DATA_PING, + DATA_SENSOR_STATUS, + DATA_VALVE_STATUS, + DATA_WIFI_STATUS, + DOMAIN, + LOGGER, + SENSOR_KIND_AP_INFO, + SENSOR_KIND_LEAK_DETECTED, + SENSOR_KIND_TEMPERATURE, + SWITCH_KIND_VALVE, + TOPIC_UPDATE, +) + +DATA_ENTITY_TYPE_MAP = { + SENSOR_KIND_AP_INFO: DATA_WIFI_STATUS, + SENSOR_KIND_LEAK_DETECTED: DATA_SENSOR_STATUS, + SENSOR_KIND_TEMPERATURE: DATA_SENSOR_STATUS, + SWITCH_KIND_VALVE: DATA_VALVE_STATUS, +} + +DEFAULT_SCAN_INTERVAL = timedelta(seconds=30) + +PLATFORMS = ["binary_sensor", "sensor", "switch"] + +SERVICE_UPGRADE_FIRMWARE_SCHEMA = vol.Schema( + { + vol.Optional(CONF_URL, default=DEFAULT_FIRMWARE_UPGRADE_URL): cv.url, + vol.Optional(CONF_PORT, default=DEFAULT_FIRMWARE_UPGRADE_PORT): cv.port, + vol.Optional( + CONF_FILENAME, default=DEFAULT_FIRMWARE_UPGRADE_FILENAME + ): cv.string, + } +) + + +@callback +def async_get_api_category(entity_kind: str): + """Get the API data category to which an entity belongs.""" + return DATA_ENTITY_TYPE_MAP.get(entity_kind) + + +async def async_setup(hass: HomeAssistant, config: dict): + """Set up the Elexa Guardian component.""" + hass.data[DOMAIN] = {DATA_CLIENT: {}} + return True + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): + """Set up Elexa Guardian from a config entry.""" + _verify_domain_control = verify_domain_control(hass, DOMAIN) + + guardian = Guardian(hass, entry) + await guardian.async_update() + hass.data[DOMAIN][DATA_CLIENT][entry.entry_id] = guardian + + for component in PLATFORMS: + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(entry, component) + ) + + @_verify_domain_control + async def disable_ap(call): + """Disable the device's onboard access point.""" + try: + async with guardian.client: + await guardian.client.device.wifi_disable_ap() + except GuardianError as err: + LOGGER.error("Error during service call: %s", err) + return + + @_verify_domain_control + async def enable_ap(call): + """Enable the device's onboard access point.""" + try: + async with guardian.client: + await guardian.client.device.wifi_enable_ap() + except GuardianError as err: + LOGGER.error("Error during service call: %s", err) + return + + @_verify_domain_control + async def reboot(call): + """Reboot the device.""" + try: + async with guardian.client: + await guardian.client.device.reboot() + except GuardianError as err: + LOGGER.error("Error during service call: %s", err) + return + + @_verify_domain_control + async def reset_valve_diagnostics(call): + """Fully reset system motor diagnostics.""" + try: + async with guardian.client: + await guardian.client.valve.valve_reset() + except GuardianError as err: + LOGGER.error("Error during service call: %s", err) + return + + @_verify_domain_control + async def upgrade_firmware(call): + """Upgrade the device firmware.""" + try: + async with guardian.client: + await guardian.client.device.upgrade_firmware( + url=call.data[CONF_URL], + port=call.data[CONF_PORT], + filename=call.data[CONF_FILENAME], + ) + except GuardianError as err: + LOGGER.error("Error during service call: %s", err) + return + + for service, method, schema in [ + ("disable_ap", disable_ap, None), + ("enable_ap", enable_ap, None), + ("reboot", reboot, None), + ("reset_valve_diagnostics", reset_valve_diagnostics, None), + ("upgrade_firmware", upgrade_firmware, SERVICE_UPGRADE_FIRMWARE_SCHEMA), + ]: + async_register_admin_service(hass, DOMAIN, service, method, schema=schema) + + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): + """Unload a config entry.""" + unload_ok = all( + await asyncio.gather( + *[ + hass.config_entries.async_forward_entry_unload(entry, component) + for component in PLATFORMS + ] + ) + ) + if unload_ok: + hass.data[DOMAIN][DATA_CLIENT].pop(entry.entry_id) + + return unload_ok + + +class Guardian: + """Define a class to communicate with the Guardian device.""" + + def __init__(self, hass: HomeAssistant, entry: ConfigEntry): + """Initialize.""" + self._async_cancel_time_interval_listener = None + self._hass = hass + self.client = Client(entry.data[CONF_IP_ADDRESS]) + self.data = {} + self.uid = entry.data[CONF_UID] + + self._api_coros = { + DATA_DIAGNOSTICS: self.client.device.diagnostics, + DATA_PAIR_DUMP: self.client.sensor.pair_dump, + DATA_PING: self.client.device.ping, + DATA_SENSOR_STATUS: self.client.sensor.sensor_status, + DATA_VALVE_STATUS: self.client.valve.valve_status, + DATA_WIFI_STATUS: self.client.device.wifi_status, + } + + self._api_category_count = { + DATA_SENSOR_STATUS: 0, + DATA_VALVE_STATUS: 0, + DATA_WIFI_STATUS: 0, + } + + self._api_lock = asyncio.Lock() + + async def _async_get_data_from_api(self, api_category: str): + """Update and save data for a particular API category.""" + if self._api_category_count.get(api_category) == 0: + return + + try: + result = await self._api_coros[api_category]() + except GuardianError as err: + LOGGER.error("Error while fetching %s data: %s", api_category, err) + self.data[api_category] = {} + else: + self.data[api_category] = result["data"] + + async def _async_update_listener_action(self, _): + """Define an async_track_time_interval action to update data.""" + await self.async_update() + + @callback + def async_deregister_api_interest(self, sensor_kind: str): + """Decrement the number of entities with data needs from an API category.""" + # If this deregistration should leave us with no registration at all, remove the + # time interval: + if sum(self._api_category_count.values()) == 0: + if self._async_cancel_time_interval_listener: + self._async_cancel_time_interval_listener() + self._async_cancel_time_interval_listener = None + return + + api_category = async_get_api_category(sensor_kind) + if api_category: + self._api_category_count[api_category] -= 1 + + async def async_register_api_interest(self, sensor_kind: str): + """Increment the number of entities with data needs from an API category.""" + # If this is the first registration we have, start a time interval: + if not self._async_cancel_time_interval_listener: + self._async_cancel_time_interval_listener = async_track_time_interval( + self._hass, self._async_update_listener_action, DEFAULT_SCAN_INTERVAL, + ) + + api_category = async_get_api_category(sensor_kind) + + if not api_category: + return + + self._api_category_count[api_category] += 1 + + # If a sensor registers interest in a particular API call and the data doesn't + # exist for it yet, make the API call and grab the data: + async with self._api_lock: + if api_category not in self.data: + async with self.client: + await self._async_get_data_from_api(api_category) + + async def async_update(self): + """Get updated data from the device.""" + async with self.client: + tasks = [ + self._async_get_data_from_api(api_category) + for api_category in self._api_coros + ] + + await asyncio.gather(*tasks) + + LOGGER.debug("Received new data: %s", self.data) + async_dispatcher_send(self._hass, TOPIC_UPDATE.format(self.uid)) + + +class GuardianEntity(Entity): + """Define a base Guardian entity.""" + + def __init__( + self, guardian: Guardian, kind: str, name: str, device_class: str, icon: str + ): + """Initialize.""" + self._attrs = {ATTR_ATTRIBUTION: "Data provided by Elexa"} + self._available = True + self._device_class = device_class + self._guardian = guardian + self._icon = icon + self._kind = kind + self._name = name + + @property + def available(self): + """Return whether the entity is available.""" + return bool(self._guardian.data[DATA_PING]) + + @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._guardian.uid)}, + "manufacturer": "Elexa", + "model": self._guardian.data[DATA_DIAGNOSTICS]["firmware"], + "name": f"Guardian {self._guardian.uid}", + } + + @property + def device_state_attributes(self): + """Return the state attributes.""" + return self._attrs + + @property + def icon(self) -> str: + """Return the icon.""" + return self._icon + + @property + def name(self): + """Return the name of the entity.""" + return f"Guardian {self._guardian.uid}: {self._name}" + + @property + def should_poll(self) -> bool: + """Return True if entity has to be polled for state.""" + return False + + @property + def unique_id(self): + """Return the unique ID of the entity.""" + return f"{self._guardian.uid}_{self._kind}" + + @callback + def _update_from_latest_data(self): + """Update the entity.""" + raise NotImplementedError + + 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, TOPIC_UPDATE.format(self._guardian.uid), update + ) + ) + + await self._guardian.async_register_api_interest(self._kind) + + self._update_from_latest_data() + + async def async_will_remove_from_hass(self) -> None: + """Disconnect dispatcher listener when removed.""" + self._guardian.async_deregister_api_interest(self._kind) diff --git a/homeassistant/components/guardian/binary_sensor.py b/homeassistant/components/guardian/binary_sensor.py new file mode 100644 index 00000000000..f9d70d03d5d --- /dev/null +++ b/homeassistant/components/guardian/binary_sensor.py @@ -0,0 +1,62 @@ +"""Binary sensors for the Elexa Guardian integration.""" +from homeassistant.components.binary_sensor import BinarySensorEntity +from homeassistant.core import callback + +from . import GuardianEntity +from .const import ( + DATA_CLIENT, + DATA_SENSOR_STATUS, + DATA_WIFI_STATUS, + DOMAIN, + SENSOR_KIND_AP_INFO, + SENSOR_KIND_LEAK_DETECTED, +) + +ATTR_CONNECTED_CLIENTS = "connected_clients" + +SENSORS = [ + (SENSOR_KIND_AP_INFO, "Onboard AP Enabled", "connectivity"), + (SENSOR_KIND_LEAK_DETECTED, "Leak Detected", "moisture"), +] + + +async def async_setup_entry(hass, entry, async_add_entities): + """Set up Guardian switches based on a config entry.""" + guardian = hass.data[DOMAIN][DATA_CLIENT][entry.entry_id] + async_add_entities( + [ + GuardianBinarySensor(guardian, kind, name, device_class) + for kind, name, device_class in SENSORS + ], + True, + ) + + +class GuardianBinarySensor(GuardianEntity, BinarySensorEntity): + """Define a generic Guardian sensor.""" + + def __init__(self, guardian, kind, name, device_class): + """Initialize.""" + super().__init__(guardian, kind, name, device_class, None) + + self._is_on = True + + @property + def is_on(self): + """Return True if the binary sensor is on.""" + return self._is_on + + @callback + def _update_from_latest_data(self): + """Update the entity.""" + if self._kind == SENSOR_KIND_AP_INFO: + self._is_on = self._guardian.data[DATA_WIFI_STATUS]["ap_enabled"] + self._attrs.update( + { + ATTR_CONNECTED_CLIENTS: self._guardian.data[DATA_WIFI_STATUS][ + "ap_clients" + ] + } + ) + elif self._kind == SENSOR_KIND_LEAK_DETECTED: + self._is_on = self._guardian.data[DATA_SENSOR_STATUS]["wet"] diff --git a/homeassistant/components/guardian/config_flow.py b/homeassistant/components/guardian/config_flow.py new file mode 100644 index 00000000000..3a7558bb222 --- /dev/null +++ b/homeassistant/components/guardian/config_flow.py @@ -0,0 +1,91 @@ +"""Config flow for Elexa Guardian integration.""" +from aioguardian import Client +from aioguardian.errors import GuardianError +import voluptuous as vol + +from homeassistant import config_entries, core +from homeassistant.const import CONF_IP_ADDRESS, CONF_PORT + +from .const import CONF_UID, DOMAIN, LOGGER # pylint:disable=unused-import + +DATA_SCHEMA = vol.Schema( + {vol.Required(CONF_IP_ADDRESS): str, vol.Required(CONF_PORT, default=7777): int} +) + + +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. + """ + async with Client(data[CONF_IP_ADDRESS]) as client: + ping_data = await client.device.ping() + + return { + "title": f"Elexa Guardian ({data[CONF_IP_ADDRESS]})", + CONF_UID: ping_data["data"]["uid"], + } + + +class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow for Elexa Guardian.""" + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL + + def __init__(self): + """Initialize.""" + self.discovery_info = {} + + async def async_step_user(self, user_input=None): + """Handle configuration via the UI.""" + if user_input is None: + return self.async_show_form( + step_id="user", data_schema=DATA_SCHEMA, errors={} + ) + + await self.async_set_unique_id(user_input[CONF_IP_ADDRESS]) + self._abort_if_unique_id_configured() + + try: + info = await validate_input(self.hass, user_input) + except GuardianError as err: + LOGGER.error("Error while connecting to unit: %s", err) + return self.async_show_form( + step_id="user", + data_schema=DATA_SCHEMA, + errors={CONF_IP_ADDRESS: "cannot_connect"}, + ) + + return self.async_create_entry( + title=info["title"], data={CONF_UID: info["uid"], **user_input} + ) + + async def async_step_zeroconf(self, discovery_info=None): + """Handle the configuration via zeroconf.""" + if discovery_info is None: + return self.async_abort(reason="connection_error") + + ip_address = discovery_info["host"] + + # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 + self.context[CONF_IP_ADDRESS] = ip_address + + if any( + ip_address == flow["context"][CONF_IP_ADDRESS] + for flow in self._async_in_progress() + ): + return self.async_abort(reason="already_in_progress") + + self.discovery_info = { + CONF_IP_ADDRESS: ip_address, + CONF_PORT: discovery_info["port"], + } + + return await self.async_step_zeroconf_confirm() + + async def async_step_zeroconf_confirm(self, user_input=None): + """Finish the configuration via zeroconf.""" + if user_input is None: + return self.async_show_form(step_id="zeroconf_confirm") + return await self.async_step_user(self.discovery_info) diff --git a/homeassistant/components/guardian/const.py b/homeassistant/components/guardian/const.py new file mode 100644 index 00000000000..f1d60fd07da --- /dev/null +++ b/homeassistant/components/guardian/const.py @@ -0,0 +1,25 @@ +"""Constants for the Elexa Guardian integration.""" +import logging + +DOMAIN = "guardian" + +LOGGER = logging.getLogger(__package__) + +CONF_UID = "uid" + +DATA_CLIENT = "client" +DATA_DIAGNOSTICS = "diagnostics" +DATA_PAIR_DUMP = "pair_sensor" +DATA_PING = "ping" +DATA_SENSOR_STATUS = "sensor_status" +DATA_VALVE_STATUS = "valve_status" +DATA_WIFI_STATUS = "wifi_status" + +SENSOR_KIND_AP_INFO = "ap_enabled" +SENSOR_KIND_LEAK_DETECTED = "leak_detected" +SENSOR_KIND_TEMPERATURE = "temperature" +SENSOR_KIND_UPTIME = "uptime" + +SWITCH_KIND_VALVE = "valve" + +TOPIC_UPDATE = "guardian_update_{0}" diff --git a/homeassistant/components/guardian/manifest.json b/homeassistant/components/guardian/manifest.json new file mode 100644 index 00000000000..a3e2d9e66ee --- /dev/null +++ b/homeassistant/components/guardian/manifest.json @@ -0,0 +1,18 @@ +{ + "domain": "guardian", + "name": "Elexa Guardian", + "config_flow": true, + "documentation": "https://www.home-assistant.io/integrations/guardian", + "requirements": [ + "aioguardian==0.2.3" + ], + "ssdp": [], + "zeroconf": [ + "_api._udp.local." + ], + "homekit": {}, + "dependencies": [], + "codeowners": [ + "@bachya" + ] +} diff --git a/homeassistant/components/guardian/sensor.py b/homeassistant/components/guardian/sensor.py new file mode 100644 index 00000000000..4da200224cf --- /dev/null +++ b/homeassistant/components/guardian/sensor.py @@ -0,0 +1,73 @@ +"""Sensors for the Elexa Guardian integration.""" +from homeassistant.const import DEVICE_CLASS_TEMPERATURE, TEMP_FAHRENHEIT, TIME_MINUTES +from homeassistant.core import callback + +from . import Guardian, GuardianEntity +from .const import ( + DATA_CLIENT, + DATA_DIAGNOSTICS, + DATA_SENSOR_STATUS, + DOMAIN, + SENSOR_KIND_TEMPERATURE, + SENSOR_KIND_UPTIME, +) + +SENSORS = [ + ( + SENSOR_KIND_TEMPERATURE, + "Temperature", + DEVICE_CLASS_TEMPERATURE, + None, + TEMP_FAHRENHEIT, + ), + (SENSOR_KIND_UPTIME, "Uptime", None, "mdi:timer", TIME_MINUTES), +] + + +async def async_setup_entry(hass, entry, async_add_entities): + """Set up Guardian switches based on a config entry.""" + guardian = hass.data[DOMAIN][DATA_CLIENT][entry.entry_id] + async_add_entities( + [ + GuardianSensor(guardian, kind, name, device_class, icon, unit) + for kind, name, device_class, icon, unit in SENSORS + ], + True, + ) + + +class GuardianSensor(GuardianEntity): + """Define a generic Guardian sensor.""" + + def __init__( + self, + guardian: Guardian, + kind: str, + name: str, + device_class: str, + icon: str, + unit: str, + ): + """Initialize.""" + super().__init__(guardian, kind, name, device_class, icon) + + self._state = None + self._unit = unit + + @property + def state(self): + """Return the sensor state.""" + return self._state + + @property + def unit_of_measurement(self): + """Return the unit of measurement of this entity, if any.""" + return self._unit + + @callback + def _update_from_latest_data(self): + """Update the entity.""" + if self._kind == SENSOR_KIND_TEMPERATURE: + self._state = self._guardian.data[DATA_SENSOR_STATUS]["temperature"] + elif self._kind == SENSOR_KIND_UPTIME: + self._state = self._guardian.data[DATA_DIAGNOSTICS]["uptime"] diff --git a/homeassistant/components/guardian/services.yaml b/homeassistant/components/guardian/services.yaml new file mode 100644 index 00000000000..42565448451 --- /dev/null +++ b/homeassistant/components/guardian/services.yaml @@ -0,0 +1,21 @@ +# Describes the format for available Elexa Guardians services +disable_ap: + description: Disable the device's onboard access point. +enable_ap: + description: Enable the device's onboard access point. +reboot: + description: Reboot the device. +reset_valve_diagnostics: + description: Fully (and irrecoverably) reset all valve diagnostics. +upgrade_firmware: + description: Upgrade the device firmware. + fields: + url: + description: (optional) The URL of the server hosting the firmware file. + example: https://repo.guardiancloud.services/gvc/fw + port: + description: (optional) The port on which the firmware file is served. + example: 443 + filename: + description: (optional) The firmware filename. + example: latest.bin diff --git a/homeassistant/components/guardian/strings.json b/homeassistant/components/guardian/strings.json new file mode 100644 index 00000000000..3f87d3260f4 --- /dev/null +++ b/homeassistant/components/guardian/strings.json @@ -0,0 +1,22 @@ +{ + "title": "Elexa Guardian", + "config": { + "step": { + "user": { + "description": "Configure a local Elexa Guardian device.", + "data": { + "ip_address": "IP Address", + "port": "Port" + } + }, + "zeroconf_confirm": { + "description": "Do you want to set up this Guardian device?" + } + }, + "abort": { + "already_configured": "This Guardian device has already been configured.", + "already_in_progress": "Guardian device configuration is already in process.", + "connection_error": "Failed to connect to the Guardian device." + } + } +} diff --git a/homeassistant/components/guardian/switch.py b/homeassistant/components/guardian/switch.py new file mode 100644 index 00000000000..9917482b5b6 --- /dev/null +++ b/homeassistant/components/guardian/switch.py @@ -0,0 +1,83 @@ +"""Switches for the Elexa Guardian integration.""" +from aioguardian.errors import GuardianError + +from homeassistant.components.switch import SwitchEntity +from homeassistant.core import callback + +from . import Guardian, GuardianEntity +from .const import DATA_CLIENT, DATA_VALVE_STATUS, DOMAIN, LOGGER, SWITCH_KIND_VALVE + +ATTR_AVG_CURRENT = "average_current" +ATTR_INST_CURRENT = "instantaneous_current" +ATTR_INST_CURRENT_DDT = "instantaneous_current_ddt" +ATTR_TRAVEL_COUNT = "travel_count" + + +async def async_setup_entry(hass, entry, async_add_entities): + """Set up Guardian switches based on a config entry.""" + guardian = hass.data[DOMAIN][DATA_CLIENT][entry.entry_id] + async_add_entities([GuardianSwitch(guardian)], True) + + +class GuardianSwitch(GuardianEntity, SwitchEntity): + """Define a switch to open/close the Guardian valve.""" + + def __init__(self, guardian: Guardian): + """Initialize.""" + super().__init__(guardian, SWITCH_KIND_VALVE, "Valve", None, "mdi:water") + + self._is_on = True + + @property + def is_on(self): + """Return True if the valve is open.""" + return self._is_on + + @callback + def _update_from_latest_data(self): + """Update the entity.""" + self._is_on = self._guardian.data[DATA_VALVE_STATUS]["state"] in ( + "start_opening", + "opening", + "finish_opening", + "opened", + ) + + self._attrs.update( + { + ATTR_AVG_CURRENT: self._guardian.data[DATA_VALVE_STATUS][ + "average_current" + ], + ATTR_INST_CURRENT: self._guardian.data[DATA_VALVE_STATUS][ + "instantaneous_current" + ], + ATTR_INST_CURRENT_DDT: self._guardian.data[DATA_VALVE_STATUS][ + "instantaneous_current_ddt" + ], + ATTR_TRAVEL_COUNT: self._guardian.data[DATA_VALVE_STATUS][ + "travel_count" + ], + } + ) + + async def async_turn_off(self, **kwargs) -> None: + """Turn the valve off (closed).""" + try: + async with self._guardian.client: + await self._guardian.client.valve.valve_close() + except GuardianError as err: + LOGGER.error("Error while closing the valve: %s", err) + return + + self._is_on = False + + async def async_turn_on(self, **kwargs) -> None: + """Turn the valve on (open).""" + try: + async with self._guardian.client: + await self._guardian.client.valve.valve_open() + except GuardianError as err: + LOGGER.error("Error while opening the valve: %s", err) + return + + self._is_on = True diff --git a/homeassistant/components/guardian/translations/en.json b/homeassistant/components/guardian/translations/en.json new file mode 100644 index 00000000000..40a8a003c12 --- /dev/null +++ b/homeassistant/components/guardian/translations/en.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "This Guardian device has already been configured.", + "already_in_progress": "Guardian device configuration is already in process.", + "connection_error": "Failed to connect to the Guardian device." + }, + "step": { + "user": { + "data": { + "ip_address": "IP Address", + "port": "Port" + }, + "description": "Configure a local Elexa Guardian device." + }, + "zeroconf_confirm": { + "description": "Do you want to set up this Guardian device?" + } + } + }, + "title": "Elexa Guardian" +} \ No newline at end of file diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 468fb5c200c..1c5ae1a4d7b 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -55,6 +55,7 @@ FLOWS = [ "gogogate2", "gpslogger", "griddy", + "guardian", "hangouts", "harmony", "heos", diff --git a/homeassistant/generated/zeroconf.py b/homeassistant/generated/zeroconf.py index 02f316c33bc..5982c23b3bd 100644 --- a/homeassistant/generated/zeroconf.py +++ b/homeassistant/generated/zeroconf.py @@ -6,6 +6,9 @@ To update, run python3 -m script.hassfest # fmt: off ZEROCONF = { + "_api._udp.local.": [ + "guardian" + ], "_axis-video._tcp.local.": [ "axis", "doorbird" diff --git a/requirements_all.txt b/requirements_all.txt index 52e6cd81895..bac28658479 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -171,6 +171,9 @@ aiofreepybox==0.0.8 # homeassistant.components.yi aioftp==0.12.0 +# homeassistant.components.guardian +aioguardian==0.2.3 + # homeassistant.components.harmony aioharmony==0.1.13 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index cc16e1e5f39..7a722525be7 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -78,6 +78,9 @@ aioesphomeapi==2.6.1 # homeassistant.components.freebox aiofreepybox==0.0.8 +# homeassistant.components.guardian +aioguardian==0.2.3 + # homeassistant.components.harmony aioharmony==0.1.13 diff --git a/tests/components/guardian/__init__.py b/tests/components/guardian/__init__.py new file mode 100644 index 00000000000..8bbb10defb4 --- /dev/null +++ b/tests/components/guardian/__init__.py @@ -0,0 +1 @@ +"""Tests for the Elexa Guardian integration.""" diff --git a/tests/components/guardian/conftest.py b/tests/components/guardian/conftest.py new file mode 100644 index 00000000000..40df9c3cdb1 --- /dev/null +++ b/tests/components/guardian/conftest.py @@ -0,0 +1,17 @@ +"""Define fixtures for Elexa Guardian tests.""" +from asynctest import patch +import pytest + + +@pytest.fixture() +def ping_client(): + """Define a patched client that returns a successful ping response.""" + with patch( + "homeassistant.components.guardian.async_setup_entry", return_value=True + ), patch("aioguardian.client.Client.connect"), patch( + "aioguardian.commands.device.Device.ping", + return_value={"command": 0, "status": "ok", "data": {"uid": "ABCDEF123456"}}, + ), patch( + "aioguardian.client.Client.disconnect" + ): + yield diff --git a/tests/components/guardian/test_config_flow.py b/tests/components/guardian/test_config_flow.py new file mode 100644 index 00000000000..1625e48f1ba --- /dev/null +++ b/tests/components/guardian/test_config_flow.py @@ -0,0 +1,124 @@ +"""Define tests for the Elexa Guardian config flow.""" +from aioguardian.errors import GuardianError +from asynctest import patch + +from homeassistant import data_entry_flow +from homeassistant.components.guardian import CONF_UID, DOMAIN +from homeassistant.config_entries import SOURCE_USER, SOURCE_ZEROCONF +from homeassistant.const import CONF_IP_ADDRESS, CONF_PORT + +from tests.common import MockConfigEntry + + +async def test_duplicate_error(hass): + """Test that errors are shown when duplicate entries are added.""" + conf = {CONF_IP_ADDRESS: "192.168.1.100", CONF_PORT: 7777} + + MockConfigEntry(domain=DOMAIN, unique_id="192.168.1.100", data=conf).add_to_hass( + hass + ) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER}, data=conf + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "already_configured" + + +async def test_connect_error(hass): + """Test that the config entry errors out if the device cannot connect.""" + conf = {CONF_IP_ADDRESS: "192.168.1.100", CONF_PORT: 7777} + + with patch( + "aioguardian.client.Client.connect", side_effect=GuardianError, + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER}, data=conf + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] == {CONF_IP_ADDRESS: "cannot_connect"} + + +async def test_step_user(hass, ping_client): + """Test the user step.""" + conf = {CONF_IP_ADDRESS: "192.168.1.100", CONF_PORT: 7777} + + 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" + + 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"] == "Elexa Guardian (192.168.1.100)" + assert result["data"] == { + CONF_IP_ADDRESS: "192.168.1.100", + CONF_PORT: 7777, + CONF_UID: "ABCDEF123456", + } + + +async def test_step_zeroconf(hass, ping_client): + """Test the zeroconf step.""" + zeroconf_data = { + "host": "192.168.1.100", + "port": 7777, + "hostname": "GVC1-ABCD.local.", + "type": "_api._udp.local.", + "name": "Guardian Valve Controller API._api._udp.local.", + "properties": {"_raw": {}}, + } + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_ZEROCONF}, data=zeroconf_data + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "zeroconf_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"] == "Elexa Guardian (192.168.1.100)" + assert result["data"] == { + CONF_IP_ADDRESS: "192.168.1.100", + CONF_PORT: 7777, + CONF_UID: "ABCDEF123456", + } + + +async def test_step_zeroconf_already_in_progress(hass): + """Test the zeroconf step aborting because it's already in progress.""" + zeroconf_data = { + "host": "192.168.1.100", + "port": 7777, + "hostname": "GVC1-ABCD.local.", + "type": "_api._udp.local.", + "name": "Guardian Valve Controller API._api._udp.local.", + "properties": {"_raw": {}}, + } + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_ZEROCONF}, data=zeroconf_data + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "zeroconf_confirm" + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_ZEROCONF}, data=zeroconf_data + ) + assert result["type"] == "abort" + assert result["reason"] == "already_in_progress" + + +async def test_step_zeroconf_no_discovery_info(hass): + """Test the zeroconf step aborting because no discovery info came along.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_ZEROCONF} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "connection_error" From bc1dac80b643ee590e6c28a49a4eb790e2dc597e Mon Sep 17 00:00:00 2001 From: Markus Bong Date: Tue, 26 May 2020 15:56:49 +0200 Subject: [PATCH 205/406] Fix cloud connection within API (#36158) --- homeassistant/components/devolo_home_control/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/devolo_home_control/manifest.json b/homeassistant/components/devolo_home_control/manifest.json index e3a4e2f8720..867848f89d1 100644 --- a/homeassistant/components/devolo_home_control/manifest.json +++ b/homeassistant/components/devolo_home_control/manifest.json @@ -2,7 +2,7 @@ "domain": "devolo_home_control", "name": "devolo_home_control", "documentation": "https://www.home-assistant.io/integrations/devolo_home_control", - "requirements": ["devolo-home-control-api==0.10.0"], + "requirements": ["devolo-home-control-api==0.11.0"], "config_flow": true, "codeowners": [ "@2Fake", diff --git a/requirements_all.txt b/requirements_all.txt index bac28658479..be47621cf76 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -472,7 +472,7 @@ deluge-client==1.7.1 denonavr==0.8.1 # homeassistant.components.devolo_home_control -devolo-home-control-api==0.10.0 +devolo-home-control-api==0.11.0 # homeassistant.components.directv directv==0.3.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 7a722525be7..88666ed685f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -206,7 +206,7 @@ defusedxml==0.6.0 denonavr==0.8.1 # homeassistant.components.devolo_home_control -devolo-home-control-api==0.10.0 +devolo-home-control-api==0.11.0 # homeassistant.components.directv directv==0.3.0 From f8416484f86db4031764f25832c6ed38802d693f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Tue, 26 May 2020 17:28:22 +0300 Subject: [PATCH 206/406] More data entry flow and HTTP related type hints (#34430) --- homeassistant/components/calendar/__init__.py | 6 +-- homeassistant/components/camera/__init__.py | 8 ++-- homeassistant/components/history/__init__.py | 33 +++++++++------- .../components/http/data_validator.py | 14 +++++-- homeassistant/components/http/view.py | 38 +++++++++++-------- homeassistant/components/mailbox/__init__.py | 2 +- .../components/media_player/__init__.py | 5 ++- homeassistant/components/tts/__init__.py | 4 +- .../components/websocket_api/http.py | 4 +- homeassistant/helpers/data_entry_flow.py | 21 +++++----- 10 files changed, 79 insertions(+), 56 deletions(-) diff --git a/homeassistant/components/calendar/__init__.py b/homeassistant/components/calendar/__init__.py index 3528608dde3..c0e94defae5 100644 --- a/homeassistant/components/calendar/__init__.py +++ b/homeassistant/components/calendar/__init__.py @@ -172,7 +172,7 @@ class CalendarEventView(http.HomeAssistantView): url = "/api/calendars/{entity_id}" name = "api:calendars:calendar" - def __init__(self, component): + def __init__(self, component: EntityComponent) -> None: """Initialize calendar view.""" self.component = component @@ -200,11 +200,11 @@ class CalendarListView(http.HomeAssistantView): url = "/api/calendars" name = "api:calendars" - def __init__(self, component): + def __init__(self, component: EntityComponent) -> None: """Initialize calendar view.""" self.component = component - async def get(self, request): + async def get(self, request: web.Request) -> web.Response: """Retrieve calendar list.""" hass = request.app["hass"] calendar_list = [] diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py index 1dc18baf232..7681ee67c29 100644 --- a/homeassistant/components/camera/__init__.py +++ b/homeassistant/components/camera/__init__.py @@ -473,11 +473,11 @@ class CameraView(HomeAssistantView): requires_auth = False - def __init__(self, component): + def __init__(self, component: EntityComponent) -> None: """Initialize a basic camera view.""" self.component = component - async def get(self, request, entity_id): + async def get(self, request: web.Request, entity_id: str) -> web.Response: """Start a GET request.""" camera = self.component.get_entity(entity_id) @@ -509,7 +509,7 @@ class CameraImageView(CameraView): url = "/api/camera_proxy/{entity_id}" name = "api:camera:image" - async def handle(self, request, camera): + async def handle(self, request: web.Request, camera: Camera) -> web.Response: """Serve camera image.""" with suppress(asyncio.CancelledError, asyncio.TimeoutError): async with async_timeout.timeout(10): @@ -527,7 +527,7 @@ class CameraMjpegStream(CameraView): url = "/api/camera_proxy_stream/{entity_id}" name = "api:camera:stream" - async def handle(self, request, camera): + async def handle(self, request: web.Request, camera: Camera) -> web.Response: """Serve camera stream, possibly with interval.""" interval = request.query.get("interval") if interval is None: diff --git a/homeassistant/components/history/__init__.py b/homeassistant/components/history/__init__.py index 7538764dcb8..8228e1c67df 100644 --- a/homeassistant/components/history/__init__.py +++ b/homeassistant/components/history/__init__.py @@ -4,7 +4,9 @@ from datetime import timedelta from itertools import groupby import logging import time +from typing import Optional, cast +from aiohttp import web from sqlalchemy import and_, func import voluptuous as vol @@ -337,20 +339,22 @@ class HistoryPeriodView(HomeAssistantView): self.filters = filters self.use_include_order = use_include_order - async def get(self, request, datetime=None): + async def get( + self, request: web.Request, datetime: Optional[str] = None + ) -> web.Response: """Return history over a period of time.""" if datetime: - datetime = dt_util.parse_datetime(datetime) + datetime_ = dt_util.parse_datetime(datetime) - if datetime is None: + if datetime_ is None: return self.json_message("Invalid datetime", HTTP_BAD_REQUEST) now = dt_util.utcnow() one_day = timedelta(days=1) - if datetime: - start_time = dt_util.as_utc(datetime) + if datetime_: + start_time = dt_util.as_utc(datetime_) else: start_time = now - one_day @@ -376,14 +380,17 @@ class HistoryPeriodView(HomeAssistantView): hass = request.app["hass"] - return await hass.async_add_executor_job( - self._sorted_significant_states_json, - hass, - start_time, - end_time, - entity_ids, - include_start_time_state, - significant_changes_only, + return cast( + web.Response, + await hass.async_add_executor_job( + self._sorted_significant_states_json, + hass, + start_time, + end_time, + entity_ids, + include_start_time_state, + significant_changes_only, + ), ) def _sorted_significant_states_json( diff --git a/homeassistant/components/http/data_validator.py b/homeassistant/components/http/data_validator.py index 84bdb4e1c17..1c9e796dc86 100644 --- a/homeassistant/components/http/data_validator.py +++ b/homeassistant/components/http/data_validator.py @@ -1,12 +1,14 @@ """Decorator for view methods to help with data validation.""" from functools import wraps import logging +from typing import Any, Awaitable, Callable +from aiohttp import web import voluptuous as vol from homeassistant.const import HTTP_BAD_REQUEST -# mypy: allow-untyped-defs +from .view import HomeAssistantView _LOGGER = logging.getLogger(__name__) @@ -20,7 +22,7 @@ class RequestDataValidator: Will return a 400 if no JSON provided or doesn't match schema. """ - def __init__(self, schema, allow_empty=False): + def __init__(self, schema: vol.Schema, allow_empty: bool = False) -> None: """Initialize the decorator.""" if isinstance(schema, dict): schema = vol.Schema(schema) @@ -28,11 +30,15 @@ class RequestDataValidator: self._schema = schema self._allow_empty = allow_empty - def __call__(self, method): + def __call__( + self, method: Callable[..., Awaitable[web.StreamResponse]] + ) -> Callable: """Decorate a function.""" @wraps(method) - async def wrapper(view, request, *args, **kwargs): + async def wrapper( + view: HomeAssistantView, request: web.Request, *args: Any, **kwargs: Any + ) -> web.StreamResponse: """Wrap a request handler with data validation.""" data = None try: diff --git a/homeassistant/components/http/view.py b/homeassistant/components/http/view.py index 40ca43ff695..701c497d88c 100644 --- a/homeassistant/components/http/view.py +++ b/homeassistant/components/http/view.py @@ -2,9 +2,10 @@ import asyncio import json import logging -from typing import List, Optional +from typing import Any, Callable, List, Optional from aiohttp import web +from aiohttp.typedefs import LooseHeaders from aiohttp.web_exceptions import ( HTTPBadRequest, HTTPInternalServerError, @@ -22,9 +23,6 @@ from .const import KEY_AUTHENTICATED, KEY_HASS, KEY_REAL_IP _LOGGER = logging.getLogger(__name__) -# mypy: allow-untyped-defs, no-check-untyped-defs - - class HomeAssistantView: """Base view for all views.""" @@ -35,7 +33,7 @@ class HomeAssistantView: cors_allowed = False @staticmethod - def context(request): + def context(request: web.Request) -> Context: """Generate a context from a request.""" user = request.get("hass_user") if user is None: @@ -44,7 +42,9 @@ class HomeAssistantView: return Context(user_id=user.id) @staticmethod - def json(result, status_code=HTTP_OK, headers=None): + def json( + result: Any, status_code: int = HTTP_OK, headers: Optional[LooseHeaders] = None, + ) -> web.Response: """Return a JSON response.""" try: msg = json.dumps( @@ -63,15 +63,19 @@ class HomeAssistantView: return response def json_message( - self, message, status_code=HTTP_OK, message_code=None, headers=None - ): + self, + message: str, + status_code: int = HTTP_OK, + message_code: Optional[str] = None, + headers: Optional[LooseHeaders] = None, + ) -> web.Response: """Return a JSON message response.""" data = {"message": message} if message_code is not None: data["code"] = message_code return self.json(data, status_code, headers=headers) - def register(self, app, router): + def register(self, app: web.Application, router: web.UrlDispatcher) -> None: """Register the view with a router.""" assert self.url is not None, "No url set for view" urls = [self.url] + self.extra_urls @@ -95,13 +99,13 @@ class HomeAssistantView: app["allow_cors"](route) -def request_handler_factory(view, handler): +def request_handler_factory(view: HomeAssistantView, handler: Callable) -> Callable: """Wrap the handler classes.""" assert asyncio.iscoroutinefunction(handler) or is_callback( handler ), "Handler should be a coroutine or a callback." - async def handle(request): + async def handle(request: web.Request) -> web.StreamResponse: """Handle incoming request.""" if not request.app[KEY_HASS].is_running: return web.Response(status=503) @@ -139,15 +143,17 @@ def request_handler_factory(view, handler): if isinstance(result, tuple): result, status_code = result - if isinstance(result, str): - result = result.encode("utf-8") + if isinstance(result, bytes): + bresult = result + elif isinstance(result, str): + bresult = result.encode("utf-8") elif result is None: - result = b"" - elif not isinstance(result, bytes): + bresult = b"" + else: assert ( False ), f"Result should be None, string, bytes or Response. Got: {result}" - return web.Response(body=result, status=status_code) + return web.Response(body=bresult, status=status_code) return handle diff --git a/homeassistant/components/mailbox/__init__.py b/homeassistant/components/mailbox/__init__.py index c3a24fe9b02..e5a0f16863d 100644 --- a/homeassistant/components/mailbox/__init__.py +++ b/homeassistant/components/mailbox/__init__.py @@ -200,7 +200,7 @@ class MailboxPlatformsView(MailboxView): url = "/api/mailbox/platforms" name = "api:mailbox:platforms" - async def get(self, request): + async def get(self, request: web.Request) -> web.Response: """Retrieve list of platforms.""" platforms = [] for mailbox in self.mailboxes: diff --git a/homeassistant/components/media_player/__init__.py b/homeassistant/components/media_player/__init__.py index 0d73c93ec71..9ba37e6c18a 100644 --- a/homeassistant/components/media_player/__init__.py +++ b/homeassistant/components/media_player/__init__.py @@ -12,6 +12,7 @@ from urllib.parse import urlparse from aiohttp import web from aiohttp.hdrs import CACHE_CONTROL, CONTENT_TYPE +from aiohttp.typedefs import LooseHeaders import async_timeout import voluptuous as vol @@ -863,7 +864,7 @@ class MediaPlayerImageView(HomeAssistantView): """Initialize a media player view.""" self.component = component - async def get(self, request, entity_id): + async def get(self, request: web.Request, entity_id: str) -> web.Response: """Start a get request.""" player = self.component.get_entity(entity_id) if player is None: @@ -883,7 +884,7 @@ class MediaPlayerImageView(HomeAssistantView): if data is None: return web.Response(status=HTTP_INTERNAL_SERVER_ERROR) - headers = {CACHE_CONTROL: "max-age=3600"} + headers: LooseHeaders = {CACHE_CONTROL: "max-age=3600"} return web.Response(body=data, content_type=content_type, headers=headers) diff --git a/homeassistant/components/tts/__init__.py b/homeassistant/components/tts/__init__.py index 9a33c0514d5..b7d3b4f1f4c 100644 --- a/homeassistant/components/tts/__init__.py +++ b/homeassistant/components/tts/__init__.py @@ -530,7 +530,7 @@ class TextToSpeechUrlView(HomeAssistantView): """Initialize a tts view.""" self.tts = tts - async def post(self, request): + async def post(self, request: web.Request) -> web.Response: """Generate speech and provide url.""" try: data = await request.json() @@ -570,7 +570,7 @@ class TextToSpeechView(HomeAssistantView): """Initialize a tts view.""" self.tts = tts - async def get(self, request, filename): + async def get(self, request: web.Request, filename: str) -> web.Response: """Start a get request.""" try: content, data = await self.tts.async_read_tts(filename) diff --git a/homeassistant/components/websocket_api/http.py b/homeassistant/components/websocket_api/http.py index b22eff150ba..e20e53d139a 100644 --- a/homeassistant/components/websocket_api/http.py +++ b/homeassistant/components/websocket_api/http.py @@ -42,7 +42,7 @@ class WebsocketAPIView(HomeAssistantView): url = URL requires_auth = False - async def get(self, request): + async def get(self, request: web.Request) -> web.WebSocketResponse: """Handle an incoming websocket connection.""" return await WebSocketHandler(request.app["hass"], request).async_handle() @@ -148,7 +148,7 @@ class WebSocketHandler: self._handle_task.cancel() self._writer_task.cancel() - async def async_handle(self): + async def async_handle(self) -> web.WebSocketResponse: """Handle a websocket response.""" request = self.request wsock = self.wsock = web.WebSocketResponse(heartbeat=55) diff --git a/homeassistant/helpers/data_entry_flow.py b/homeassistant/helpers/data_entry_flow.py index fe6420750c6..1cf1fa4545c 100644 --- a/homeassistant/helpers/data_entry_flow.py +++ b/homeassistant/helpers/data_entry_flow.py @@ -1,5 +1,8 @@ """Helpers for the data entry flow.""" +from typing import Any, Dict + +from aiohttp import web import voluptuous as vol from homeassistant import config_entries, data_entry_flow @@ -8,18 +11,16 @@ from homeassistant.components.http.data_validator import RequestDataValidator from homeassistant.const import HTTP_NOT_FOUND import homeassistant.helpers.config_validation as cv -# mypy: allow-untyped-calls, allow-untyped-defs - class _BaseFlowManagerView(HomeAssistantView): """Foundation for flow manager views.""" - def __init__(self, flow_mgr): + def __init__(self, flow_mgr: data_entry_flow.FlowManager) -> None: """Initialize the flow manager index view.""" self._flow_mgr = flow_mgr # pylint: disable=no-self-use - def _prepare_result_json(self, result): + def _prepare_result_json(self, result: Dict[str, Any]) -> Dict[str, Any]: """Convert result to JSON.""" if result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY: data = result.copy() @@ -57,7 +58,7 @@ class FlowManagerIndexView(_BaseFlowManagerView): extra=vol.ALLOW_EXTRA, ) ) - async def post(self, request, data): + async def post(self, request: web.Request, data: Dict[str, Any]) -> web.Response: """Handle a POST request.""" if isinstance(data["handler"], list): handler = tuple(data["handler"]) @@ -66,7 +67,7 @@ class FlowManagerIndexView(_BaseFlowManagerView): try: result = await self._flow_mgr.async_init( - handler, + handler, # type: ignore context={ "source": config_entries.SOURCE_USER, "show_advanced_options": data["show_advanced_options"], @@ -85,7 +86,7 @@ class FlowManagerIndexView(_BaseFlowManagerView): class FlowManagerResourceView(_BaseFlowManagerView): """View to interact with the flow manager.""" - async def get(self, request, flow_id): + async def get(self, request: web.Request, flow_id: str) -> web.Response: """Get the current state of a data_entry_flow.""" try: result = await self._flow_mgr.async_configure(flow_id) @@ -97,7 +98,9 @@ class FlowManagerResourceView(_BaseFlowManagerView): return self.json(result) @RequestDataValidator(vol.Schema(dict), allow_empty=True) - async def post(self, request, flow_id, data): + async def post( + self, request: web.Request, flow_id: str, data: Dict[str, Any] + ) -> web.Response: """Handle a POST request.""" try: result = await self._flow_mgr.async_configure(flow_id, data) @@ -110,7 +113,7 @@ class FlowManagerResourceView(_BaseFlowManagerView): return self.json(result) - async def delete(self, request, flow_id): + async def delete(self, request: web.Request, flow_id: str) -> web.Response: """Cancel a flow in progress.""" try: self._flow_mgr.async_abort(flow_id) From 514c64619ab096dfdadd2192e565ed0c683e19c2 Mon Sep 17 00:00:00 2001 From: Steven Rollason <2099542+gadgetchnnel@users.noreply.github.com> Date: Tue, 26 May 2020 15:39:53 +0100 Subject: [PATCH 207/406] Check todoist due date is not None in async_get_events (#36140) * Check that due date is not None Check that due date is not None, prevents taks without due dates from breaking Calendar API * Invert None check to reduce indentation --- homeassistant/components/todoist/calendar.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/components/todoist/calendar.py b/homeassistant/components/todoist/calendar.py index fd246520696..0ce8101f49c 100644 --- a/homeassistant/components/todoist/calendar.py +++ b/homeassistant/components/todoist/calendar.py @@ -500,6 +500,8 @@ class TodoistProjectData: events = [] for task in project_task_data: + if task["due"] is None: + continue due_date = _parse_due_date(task["due"]) if start_date < due_date < end_date: event = { From 8de863ecf1214f2fbf30e80b975d5efe7ea13160 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 26 May 2020 17:12:22 +0200 Subject: [PATCH 208/406] Let PAHO MQTT client handle connection to MQTT server (#35983) * Let PAHO client handle connection to MQTT server --- homeassistant/components/mqtt/__init__.py | 28 ++++++++--------------- tests/components/mqtt/test_init.py | 12 +++++----- 2 files changed, 15 insertions(+), 25 deletions(-) diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index 25b2b0381ea..64b25ad9486 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -29,11 +29,7 @@ from homeassistant.const import ( EVENT_HOMEASSISTANT_STOP, ) from homeassistant.core import Event, ServiceCall, callback -from homeassistant.exceptions import ( - ConfigEntryNotReady, - HomeAssistantError, - Unauthorized, -) +from homeassistant.exceptions import HomeAssistantError, Unauthorized from homeassistant.helpers import config_validation as cv, event, template from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import Entity @@ -649,13 +645,7 @@ async def async_setup_entry(hass, entry): tls_version=tls_version, ) - result: str = await hass.data[DATA_MQTT].async_connect() - - if result == CONNECTION_FAILED: - return False - - if result == CONNECTION_FAILED_RECOVERABLE: - raise ConfigEntryNotReady + await hass.data[DATA_MQTT].async_connect() async def async_stop_mqtt(_event: Event): """Stop MQTT component.""" @@ -835,15 +825,14 @@ class MQTT: self._mqttc.connect, self.broker, self.port, self.keepalive ) except OSError as err: - _LOGGER.error("Failed to connect due to exception: %s", err) - return CONNECTION_FAILED_RECOVERABLE + _LOGGER.error("Failed to connect to MQTT server due to exception: %s", err) - if result != 0: - _LOGGER.error("Failed to connect: %s", mqtt.error_string(result)) - return CONNECTION_FAILED + if result is not None and result != 0: + _LOGGER.error( + "Failed to connect to MQTT server: %s", mqtt.error_string(result) + ) self._mqttc.loop_start() - return CONNECTION_SUCCESS async def async_disconnect(self): """Stop the MQTT client.""" @@ -933,6 +922,7 @@ class MQTT: return self.connected = True + _LOGGER.info("Connected to MQTT server (%s)", result_code) # Group subscriptions to only re-subscribe once for each topic. keyfunc = attrgetter("topic") @@ -999,7 +989,7 @@ class MQTT: def _mqtt_on_disconnect(self, _mqttc, _userdata, result_code: int) -> None: """Disconnected callback.""" self.connected = False - _LOGGER.warning("Disconnected from MQTT (%s).", result_code) + _LOGGER.warning("Disconnected from MQTT server (%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 9ec5e09f276..3626c5a746c 100644 --- a/tests/components/mqtt/test_init.py +++ b/tests/components/mqtt/test_init.py @@ -18,7 +18,6 @@ from homeassistant.const import ( TEMP_CELSIUS, ) from homeassistant.core import callback -from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import device_registry from homeassistant.setup import async_setup_component from homeassistant.util.dt import utcnow @@ -678,23 +677,24 @@ async def test_setup_embedded_with_embedded(hass): assert _start.call_count == 1 -async def test_setup_fails_if_no_connect_broker(hass): +async def test_setup_logs_error_if_no_connect_broker(hass, caplog): """Test for setup failure if connection to broker is missing.""" entry = MockConfigEntry(domain=mqtt.DOMAIN, data={mqtt.CONF_BROKER: "test-broker"}) with patch("paho.mqtt.client.Client") as mock_client: mock_client().connect = lambda *args: 1 - assert not await mqtt.async_setup_entry(hass, entry) + assert await mqtt.async_setup_entry(hass, entry) + assert "Failed to connect to MQTT server:" in caplog.text -async def test_setup_raises_ConfigEntryNotReady_if_no_connect_broker(hass): +async def test_setup_raises_ConfigEntryNotReady_if_no_connect_broker(hass, caplog): """Test for setup failure if connection to broker is missing.""" entry = MockConfigEntry(domain=mqtt.DOMAIN, data={mqtt.CONF_BROKER: "test-broker"}) 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) + assert await mqtt.async_setup_entry(hass, entry) + assert "Failed to connect to MQTT server due to exception:" in caplog.text async def test_setup_uses_certificate_on_certificate_set_to_auto(hass, mock_mqtt): From 9a5324075987ed599ae26ef4bfc247d02919ee2f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 26 May 2020 10:51:50 -0500 Subject: [PATCH 209/406] Add ability to ignore heos discovery (#34653) * Add ability to ignore heos discovery * Fetch player_id, update tests * Handle failure state * Update tests as there are two players in the mock now * Adjust and add more tests * Strip out player id lookup * reverts per review * one more revert --- homeassistant/components/heos/__init__.py | 4 ++ homeassistant/components/heos/config_flow.py | 4 ++ tests/components/heos/test_config_flow.py | 48 ++++++++++++++++++-- tests/components/heos/test_init.py | 2 + 4 files changed, 54 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/heos/__init__.py b/homeassistant/components/heos/__init__.py index 53c65a6ab07..a76a29b2ed5 100644 --- a/homeassistant/components/heos/__init__.py +++ b/homeassistant/components/heos/__init__.py @@ -61,6 +61,10 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType): async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry): """Initialize config entry which represents the HEOS controller.""" + # For backwards compat + if entry.unique_id is None: + hass.config_entries.async_update_entry(entry, unique_id=DOMAIN) + host = entry.data[CONF_HOST] # Setting all_progress_events=False ensures that we only receive a # media position update upon start of playback or when media changes diff --git a/homeassistant/components/heos/config_flow.py b/homeassistant/components/heos/config_flow.py index 91dbc19ac95..138d1c4462c 100644 --- a/homeassistant/components/heos/config_flow.py +++ b/homeassistant/components/heos/config_flow.py @@ -33,12 +33,16 @@ class HeosFlowHandler(config_entries.ConfigFlow): # Abort if other flows in progress or an entry already exists if self._async_in_progress() or self._async_current_entries(): return self.async_abort(reason="already_setup") + await self.async_set_unique_id(DOMAIN) # Show selection form return self.async_show_form(step_id="user") async def async_step_import(self, user_input=None): """Occurs when an entry is setup through config.""" host = user_input[CONF_HOST] + # raise_on_progress is False here in case ssdp discovers + # heos first which would block the import + await self.async_set_unique_id(DOMAIN, raise_on_progress=False) return self.async_create_entry(title=format_title(host), data={CONF_HOST: host}) async def async_step_user(self, user_input=None): diff --git a/tests/components/heos/test_config_flow.py b/tests/components/heos/test_config_flow.py index d90c4263240..800814df013 100644 --- a/tests/components/heos/test_config_flow.py +++ b/tests/components/heos/test_config_flow.py @@ -6,7 +6,8 @@ from pyheos import HeosError from homeassistant import data_entry_flow from homeassistant.components import heos, ssdp from homeassistant.components.heos.config_flow import HeosFlowHandler -from homeassistant.components.heos.const import DATA_DISCOVERED_HOSTS +from homeassistant.components.heos.const import DATA_DISCOVERED_HOSTS, DOMAIN +from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_SSDP from homeassistant.const import CONF_HOST from tests.async_mock import patch @@ -55,6 +56,7 @@ async def test_create_entry_when_host_valid(hass, controller): heos.DOMAIN, context={"source": "user"}, data=data ) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["result"].unique_id == DOMAIN assert result["title"] == "Controller (127.0.0.1)" assert result["data"] == data assert controller.connect.call_count == 1 @@ -70,6 +72,7 @@ async def test_create_entry_when_friendly_name_valid(hass, controller): heos.DOMAIN, context={"source": "user"}, data=data ) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["result"].unique_id == DOMAIN assert result["title"] == "Controller (127.0.0.1)" assert result["data"] == {CONF_HOST: "127.0.0.1"} assert controller.connect.call_count == 1 @@ -79,28 +82,34 @@ async def test_create_entry_when_friendly_name_valid(hass, controller): async def test_discovery_shows_create_form(hass, controller, discovery_data): """Test discovery shows form to confirm setup and subsequent abort.""" + await hass.config_entries.flow.async_init( heos.DOMAIN, context={"source": "ssdp"}, data=discovery_data ) await hass.async_block_till_done() - assert len(hass.config_entries.flow.async_progress()) == 1 + flows_in_progress = hass.config_entries.flow.async_progress() + assert flows_in_progress[0]["context"]["unique_id"] == DOMAIN + assert len(flows_in_progress) == 1 assert hass.data[DATA_DISCOVERED_HOSTS] == {"Office (127.0.0.1)": "127.0.0.1"} port = urlparse(discovery_data[ssdp.ATTR_SSDP_LOCATION]).port discovery_data[ssdp.ATTR_SSDP_LOCATION] = f"http://127.0.0.2:{port}/" discovery_data[ssdp.ATTR_UPNP_FRIENDLY_NAME] = "Bedroom" + await hass.config_entries.flow.async_init( heos.DOMAIN, context={"source": "ssdp"}, data=discovery_data ) await hass.async_block_till_done() - assert len(hass.config_entries.flow.async_progress()) == 1 + flows_in_progress = hass.config_entries.flow.async_progress() + assert flows_in_progress[0]["context"]["unique_id"] == DOMAIN + assert len(flows_in_progress) == 1 assert hass.data[DATA_DISCOVERED_HOSTS] == { "Office (127.0.0.1)": "127.0.0.1", "Bedroom (127.0.0.2)": "127.0.0.2", } -async def test_disovery_flow_aborts_already_setup( +async def test_discovery_flow_aborts_already_setup( hass, controller, discovery_data, config_entry ): """Test discovery flow aborts when entry already setup.""" @@ -110,3 +119,34 @@ async def test_disovery_flow_aborts_already_setup( result = await flow.async_step_ssdp(discovery_data) assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result["reason"] == "already_setup" + + +async def test_discovery_sets_the_unique_id(hass, controller, discovery_data): + """Test discovery sets the unique id.""" + + port = urlparse(discovery_data[ssdp.ATTR_SSDP_LOCATION]).port + discovery_data[ssdp.ATTR_SSDP_LOCATION] = f"http://127.0.0.2:{port}/" + discovery_data[ssdp.ATTR_UPNP_FRIENDLY_NAME] = "Bedroom" + + await hass.config_entries.flow.async_init( + heos.DOMAIN, context={"source": SOURCE_SSDP}, data=discovery_data + ) + await hass.async_block_till_done() + flows_in_progress = hass.config_entries.flow.async_progress() + assert flows_in_progress[0]["context"]["unique_id"] == DOMAIN + assert len(flows_in_progress) == 1 + assert hass.data[DATA_DISCOVERED_HOSTS] == {"Bedroom (127.0.0.2)": "127.0.0.2"} + + +async def test_import_sets_the_unique_id(hass, controller): + """Test import sets the unique id.""" + + with patch("homeassistant.components.heos.async_setup_entry", return_value=True): + result = await hass.config_entries.flow.async_init( + heos.DOMAIN, + context={"source": SOURCE_IMPORT}, + data={CONF_HOST: "127.0.0.2"}, + ) + await hass.async_block_till_done() + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["result"].unique_id == DOMAIN diff --git a/tests/components/heos/test_init.py b/tests/components/heos/test_init.py index a6852e3db41..a32ea5dd08c 100644 --- a/tests/components/heos/test_init.py +++ b/tests/components/heos/test_init.py @@ -31,6 +31,7 @@ async def test_async_setup_creates_entry(hass, config): entry = entries[0] assert entry.title == "Controller (127.0.0.1)" assert entry.data == {CONF_HOST: "127.0.0.1"} + assert entry.unique_id == DOMAIN async def test_async_setup_updates_entry(hass, config_entry, config, controller): @@ -44,6 +45,7 @@ async def test_async_setup_updates_entry(hass, config_entry, config, controller) entry = entries[0] assert entry.title == "Controller (127.0.0.2)" assert entry.data == {CONF_HOST: "127.0.0.2"} + assert entry.unique_id == DOMAIN async def test_async_setup_returns_true(hass, config_entry, config): From 6507951bb12942c248b86b8def2197b247a2e2bd Mon Sep 17 00:00:00 2001 From: Spartan-II-117 Date: Tue, 26 May 2020 11:44:10 -0700 Subject: [PATCH 210/406] update PyPjlink to 1.2.1 (#36170) * Update manifest.json Bump PyPjlink to 1.2.1 * Update requirements_all.txt --- homeassistant/components/pjlink/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/pjlink/manifest.json b/homeassistant/components/pjlink/manifest.json index ca657923aa8..6b2dd94c0bd 100644 --- a/homeassistant/components/pjlink/manifest.json +++ b/homeassistant/components/pjlink/manifest.json @@ -2,6 +2,6 @@ "domain": "pjlink", "name": "PJLink", "documentation": "https://www.home-assistant.io/integrations/pjlink", - "requirements": ["pypjlink2==1.2.0"], + "requirements": ["pypjlink2==1.2.1"], "codeowners": [] } diff --git a/requirements_all.txt b/requirements_all.txt index be47621cf76..9b24388a815 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1542,7 +1542,7 @@ pypca==0.0.7 pypck==0.6.4 # homeassistant.components.pjlink -pypjlink2==1.2.0 +pypjlink2==1.2.1 # homeassistant.components.point pypoint==1.1.2 From 59c112a3f256caf8ed0e748c8df3145194e8f3ae Mon Sep 17 00:00:00 2001 From: jjlawren Date: Tue, 26 May 2020 14:39:56 -0500 Subject: [PATCH 211/406] Decouple media lookup from Plex play_media service (#35663) * Decouple media lookup from play_media service * More explicit input/search validation, cleanup, more tests * Minor cleanup * Normalize media_type string in lookup call * Move key lookup, add tests via service calls * Always allow play_media service calls * No need to pass arguments to nested functions --- homeassistant/components/plex/media_player.py | 106 +------- homeassistant/components/plex/server.py | 163 +++++++++++- tests/components/plex/mock_classes.py | 88 ++++++- tests/components/plex/test_server.py | 242 +++++++++++++++++- 4 files changed, 495 insertions(+), 104 deletions(-) diff --git a/homeassistant/components/plex/media_player.py b/homeassistant/components/plex/media_player.py index b19f687482c..a25765ec588 100644 --- a/homeassistant/components/plex/media_player.py +++ b/homeassistant/components/plex/media_player.py @@ -7,12 +7,9 @@ import requests.exceptions from homeassistant.components.media_player import DOMAIN as MP_DOMAIN, MediaPlayerEntity 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, @@ -487,7 +484,7 @@ class PlexMediaPlayer(MediaPlayerEntity): | SUPPORT_VOLUME_MUTE ) - return 0 + return SUPPORT_PLAY_MEDIA def set_volume_level(self, volume): """Set volume level, range 0..1.""" @@ -561,32 +558,12 @@ class PlexMediaPlayer(MediaPlayerEntity): ) return - media_type = media_type.lower() src = json.loads(media_id) - 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 + if isinstance(src, int): + src = {"plex_key": src} - 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 + shuffle = src.pop("shuffle", 0) + media = self.plex_server.lookup_media(media_type, **src) if media is None: _LOGGER.error("Media could not be found: %s", media_id) @@ -600,79 +577,6 @@ class PlexMediaPlayer(MediaPlayerEntity): except requests.exceptions.ConnectTimeout: _LOGGER.error("Timed out playing on %s", self.name) - def _get_music_media(self, library_name, src): - """Find music media and return a Plex media object.""" - artist_name = src["artist_name"] - album_name = src.get("album_name") - track_name = src.get("track_name") - track_number = src.get("track_number") - - artist = self.plex_server.library.section(library_name).get(artist_name) - - if album_name: - album = artist.album(album_name) - - if track_name: - return album.track(track_name) - - if track_number: - for track in album.tracks(): - if int(track.index) == int(track_number): - return track - return None - - return album - - if track_name: - return artist.searchTracks(track_name, maxresults=1) - return artist - - def _get_tv_media(self, library_name, src): - """Find TV media and return a Plex media object.""" - show_name = src["show_name"] - season_number = src.get("season_number") - episode_number = src.get("episode_number") - target_season = None - target_episode = None - - show = self.plex_server.library.section(library_name).get(show_name) - - if not season_number: - return show - - for season in show.seasons(): - if int(season.seasonNumber) == int(season_number): - target_season = season - break - - if target_season is None: - _LOGGER.error( - "Season not found: %s\\%s - S%sE%s", - library_name, - show_name, - str(season_number).zfill(2), - str(episode_number).zfill(2), - ) - else: - if not episode_number: - return target_season - - for episode in target_season.episodes(): - if int(episode.index) == int(episode_number): - target_episode = episode - break - - if target_episode is None: - _LOGGER.error( - "Episode not found: %s\\%s - S%sE%s", - library_name, - show_name, - str(season_number).zfill(2), - str(episode_number).zfill(2), - ) - - return target_episode - @property def device_state_attributes(self): """Return the scene state attributes.""" diff --git a/homeassistant/components/plex/server.py b/homeassistant/components/plex/server.py index a1f5af321f3..933a899e3b2 100644 --- a/homeassistant/components/plex/server.py +++ b/homeassistant/components/plex/server.py @@ -3,7 +3,7 @@ import logging import ssl from urllib.parse import urlparse -from plexapi.exceptions import Unauthorized +from plexapi.exceptions import NotFound, Unauthorized import plexapi.myplex import plexapi.playqueue import plexapi.server @@ -11,6 +11,12 @@ from requests import Session import requests.exceptions from homeassistant.components.media_player import DOMAIN as MP_DOMAIN +from homeassistant.components.media_player.const import ( + MEDIA_TYPE_EPISODE, + MEDIA_TYPE_MUSIC, + MEDIA_TYPE_PLAYLIST, + MEDIA_TYPE_VIDEO, +) from homeassistant.const import CONF_TOKEN, CONF_URL, CONF_VERIFY_SSL from homeassistant.core import callback from homeassistant.helpers.debounce import Debouncer @@ -25,6 +31,7 @@ from .const import ( CONF_USE_EPISODE_ART, DEBOUNCE_TIMEOUT, DEFAULT_VERIFY_SSL, + DOMAIN, PLEX_NEW_MP_SIGNAL, PLEX_UPDATE_MEDIA_PLAYER_SIGNAL, PLEX_UPDATE_SENSOR_SIGNAL, @@ -367,3 +374,157 @@ class PlexServer: def fetch_item(self, item): """Fetch item from Plex server.""" return self._plex_server.fetchItem(item) + + def lookup_media(self, media_type, **kwargs): + """Lookup a piece of media.""" + media_type = media_type.lower() + + if media_type == DOMAIN: + key = kwargs["plex_key"] + try: + return self.fetch_item(key) + except plexapi.exceptions.NotFound: + _LOGGER.error("Media for key %s not found", key) + return None + + if media_type == MEDIA_TYPE_PLAYLIST: + try: + playlist_name = kwargs["playlist_name"] + return self.playlist(playlist_name) + except KeyError: + _LOGGER.error("Must specify 'playlist_name' for this search") + return None + except NotFound: + _LOGGER.error( + "Playlist '%s' not found", playlist_name, + ) + return None + + try: + library_name = kwargs["library_name"] + library_section = self.library.section(library_name) + except KeyError: + _LOGGER.error("Must specify 'library_name' for this search") + return None + except NotFound: + _LOGGER.error("Library '%s' not found", library_name) + return None + + def lookup_music(): + """Search for music and return a Plex media object.""" + album_name = kwargs.get("album_name") + track_name = kwargs.get("track_name") + track_number = kwargs.get("track_number") + + try: + artist_name = kwargs["artist_name"] + artist = library_section.get(artist_name) + except KeyError: + _LOGGER.error("Must specify 'artist_name' for this search") + return None + except NotFound: + _LOGGER.error( + "Artist '%s' not found in '%s'", artist_name, library_name + ) + return None + + if album_name: + try: + album = artist.album(album_name) + except NotFound: + _LOGGER.error( + "Album '%s' by '%s' not found", album_name, artist_name + ) + return None + + if track_name: + try: + return album.track(track_name) + except NotFound: + _LOGGER.error( + "Track '%s' on '%s' by '%s' not found", + track_name, + album_name, + artist_name, + ) + return None + + if track_number: + for track in album.tracks(): + if int(track.index) == int(track_number): + return track + + _LOGGER.error( + "Track %d on '%s' by '%s' not found", + track_number, + album_name, + artist_name, + ) + return None + return album + + if track_name: + try: + return artist.get(track_name) + except NotFound: + _LOGGER.error( + "Track '%s' by '%s' not found", track_name, artist_name + ) + return None + + return artist + + def lookup_tv(): + """Find TV media and return a Plex media object.""" + season_number = kwargs.get("season_number") + episode_number = kwargs.get("episode_number") + + try: + show_name = kwargs["show_name"] + show = library_section.get(show_name) + except KeyError: + _LOGGER.error("Must specify 'show_name' for this search") + return None + except NotFound: + _LOGGER.error("Show '%s' not found in '%s'", show_name, library_name) + return None + + if not season_number: + return show + + try: + season = show.season(int(season_number)) + except NotFound: + _LOGGER.error( + "Season %d of '%s' not found", season_number, show_name, + ) + return None + + if not episode_number: + return season + + try: + return season.episode(episode=int(episode_number)) + except NotFound: + _LOGGER.error( + "Episode not found: %s - S%sE%s", + show_name, + str(season_number).zfill(2), + str(episode_number).zfill(2), + ) + return None + + if media_type == MEDIA_TYPE_MUSIC: + return lookup_music() + if media_type == MEDIA_TYPE_EPISODE: + return lookup_tv() + if media_type == MEDIA_TYPE_VIDEO: + try: + video_name = kwargs["video_name"] + return library_section.get(video_name) + except KeyError: + _LOGGER.error("Must specify 'video_name' for this search") + except NotFound: + _LOGGER.error( + "Movie '%s' not found in '%s'", video_name, library_name, + ) diff --git a/tests/components/plex/mock_classes.py b/tests/components/plex/mock_classes.py index ec1b490ddf5..0c082474eb2 100644 --- a/tests/components/plex/mock_classes.py +++ b/tests/components/plex/mock_classes.py @@ -152,6 +152,19 @@ class MockPlexServer: """Mock version of PlexServer.""" return "1.0" + @property + def library(self): + """Mock library object of PlexServer.""" + return MockPlexLibrary() + + def playlist(self, playlist): + """Mock the playlist lookup method.""" + return MockPlexMediaItem(playlist, mediatype="playlist") + + def fetchItem(self, item): + """Mock the fetchItem method.""" + return MockPlexMediaItem("Item Name") + class MockPlexClient: """Mock a PlexClient instance.""" @@ -186,7 +199,7 @@ class MockPlexClient: @property def protocolCapabilities(self): """Mock the protocolCapabilities attribute.""" - return ["player"] + return ["playback"] @property def state(self): @@ -203,6 +216,10 @@ class MockPlexClient: """Mock the version attribute.""" return "1.0" + def playMedia(self, item): + """Mock the playMedia method.""" + pass + class MockPlexSession: """Mock a PlexServer.sessions() instance.""" @@ -259,9 +276,78 @@ class MockPlexSession: return 2020 +class MockPlexLibrary: + """Mock a Plex Library instance.""" + + def __init__(self): + """Initialize the object.""" + + def section(self, library_name): + """Mock the LibrarySection lookup.""" + return MockPlexLibrarySection(library_name) + + class MockPlexLibrarySection: """Mock a Plex LibrarySection instance.""" def __init__(self, library="Movies"): """Initialize the object.""" self.title = library + + def get(self, query): + """Mock the get lookup method.""" + if self.title == "Music": + return MockPlexArtist(query) + return MockPlexMediaItem(query) + + +class MockPlexMediaItem: + """Mock a Plex Media instance.""" + + def __init__(self, title, mediatype="video"): + """Initialize the object.""" + self.title = str(title) + self.type = mediatype + + def album(self, album): + """Mock the album lookup method.""" + return MockPlexMediaItem(album, mediatype="album") + + def track(self, track): + """Mock the track lookup method.""" + return MockPlexMediaTrack() + + def tracks(self): + """Mock the tracks lookup method.""" + for index in range(1, 10): + yield MockPlexMediaTrack(index) + + def episode(self, episode): + """Mock the episode lookup method.""" + return MockPlexMediaItem(episode, mediatype="episode") + + def season(self, season): + """Mock the season lookup method.""" + return MockPlexMediaItem(season, mediatype="season") + + +class MockPlexArtist(MockPlexMediaItem): + """Mock a Plex Artist instance.""" + + def __init__(self, artist): + """Initialize the object.""" + super().__init__(artist) + self.type = "artist" + + def get(self, track): + """Mock the track lookup method.""" + return MockPlexMediaTrack() + + +class MockPlexMediaTrack(MockPlexMediaItem): + """Mock a Plex Track instance.""" + + def __init__(self, index=1): + """Initialize the object.""" + super().__init__(f"Track {index}", "track") + self.index = index diff --git a/tests/components/plex/test_server.py b/tests/components/plex/test_server.py index 694fcc4885e..6831b045da6 100644 --- a/tests/components/plex/test_server.py +++ b/tests/components/plex/test_server.py @@ -1,7 +1,18 @@ """Tests for Plex server.""" import copy +from plexapi.exceptions import NotFound + from homeassistant.components.media_player import DOMAIN as MP_DOMAIN +from homeassistant.components.media_player.const import ( + ATTR_MEDIA_CONTENT_ID, + ATTR_MEDIA_CONTENT_TYPE, + MEDIA_TYPE_EPISODE, + MEDIA_TYPE_MUSIC, + MEDIA_TYPE_PLAYLIST, + MEDIA_TYPE_VIDEO, + SERVICE_PLAY_MEDIA, +) from homeassistant.components.plex.const import ( CONF_IGNORE_NEW_SHARED_USERS, CONF_IGNORE_PLEX_WEB_CLIENTS, @@ -10,10 +21,17 @@ from homeassistant.components.plex.const import ( PLEX_UPDATE_PLATFORMS_SIGNAL, SERVERS, ) +from homeassistant.const import ATTR_ENTITY_ID from homeassistant.helpers.dispatcher import async_dispatcher_send from .const import DEFAULT_DATA, DEFAULT_OPTIONS -from .mock_classes import MockPlexServer +from .mock_classes import ( + MockPlexArtist, + MockPlexLibrary, + MockPlexLibrarySection, + MockPlexMediaItem, + MockPlexServer, +) from tests.async_mock import patch from tests.common import MockConfigEntry @@ -244,3 +262,225 @@ async def test_ignore_plex_web_client(hass): media_players = hass.states.async_entity_ids("media_player") assert len(media_players) == int(sensor.state) - 1 + + +async def test_media_lookups(hass): + """Test media lookups to Plex server.""" + + entry = MockConfigEntry( + domain=DOMAIN, + data=DEFAULT_DATA, + options=DEFAULT_OPTIONS, + unique_id=DEFAULT_DATA["server_id"], + ) + + 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() + + server_id = mock_plex_server.machineIdentifier + loaded_server = hass.data[DOMAIN][SERVERS][server_id] + + # Plex Key searches + async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id)) + await hass.async_block_till_done() + media_player_id = hass.states.async_entity_ids("media_player")[0] + with patch("homeassistant.components.plex.PlexServer.create_playqueue"): + assert await hass.services.async_call( + MP_DOMAIN, + SERVICE_PLAY_MEDIA, + { + ATTR_ENTITY_ID: media_player_id, + ATTR_MEDIA_CONTENT_TYPE: DOMAIN, + ATTR_MEDIA_CONTENT_ID: 123, + }, + True, + ) + with patch.object(MockPlexServer, "fetchItem", side_effect=NotFound): + assert await hass.services.async_call( + MP_DOMAIN, + SERVICE_PLAY_MEDIA, + { + ATTR_ENTITY_ID: media_player_id, + ATTR_MEDIA_CONTENT_TYPE: DOMAIN, + ATTR_MEDIA_CONTENT_ID: 123, + }, + True, + ) + + # TV show searches + with patch.object(MockPlexLibrary, "section", side_effect=NotFound): + assert ( + loaded_server.lookup_media( + MEDIA_TYPE_EPISODE, library_name="Not a Library", show_name="A TV Show" + ) + is None + ) + with patch.object(MockPlexLibrarySection, "get", side_effect=NotFound): + assert ( + loaded_server.lookup_media( + MEDIA_TYPE_EPISODE, library_name="TV Shows", show_name="Not a TV Show" + ) + is None + ) + assert ( + loaded_server.lookup_media( + MEDIA_TYPE_EPISODE, library_name="TV Shows", episode_name="An Episode" + ) + is None + ) + assert loaded_server.lookup_media( + MEDIA_TYPE_EPISODE, library_name="TV Shows", show_name="A TV Show" + ) + assert loaded_server.lookup_media( + MEDIA_TYPE_EPISODE, + library_name="TV Shows", + show_name="A TV Show", + season_number=2, + ) + assert loaded_server.lookup_media( + MEDIA_TYPE_EPISODE, + library_name="TV Shows", + show_name="A TV Show", + season_number=2, + episode_number=3, + ) + with patch.object(MockPlexMediaItem, "season", side_effect=NotFound): + assert ( + loaded_server.lookup_media( + MEDIA_TYPE_EPISODE, + library_name="TV Shows", + show_name="A TV Show", + season_number=2, + ) + is None + ) + with patch.object(MockPlexMediaItem, "episode", side_effect=NotFound): + assert ( + loaded_server.lookup_media( + MEDIA_TYPE_EPISODE, + library_name="TV Shows", + show_name="A TV Show", + season_number=2, + episode_number=1, + ) + is None + ) + + # Music searches + assert ( + loaded_server.lookup_media( + MEDIA_TYPE_MUSIC, library_name="Music", album_name="An Album" + ) + is None + ) + assert loaded_server.lookup_media( + MEDIA_TYPE_MUSIC, library_name="Music", artist_name="An Artist" + ) + assert loaded_server.lookup_media( + MEDIA_TYPE_MUSIC, + library_name="Music", + artist_name="An Artist", + track_name="A Track", + ) + assert loaded_server.lookup_media( + MEDIA_TYPE_MUSIC, + library_name="Music", + artist_name="An Artist", + album_name="An Album", + ) + with patch.object(MockPlexLibrarySection, "get", side_effect=NotFound): + assert ( + loaded_server.lookup_media( + MEDIA_TYPE_MUSIC, + library_name="Music", + artist_name="Not an Artist", + album_name="An Album", + ) + is None + ) + with patch.object(MockPlexArtist, "album", side_effect=NotFound): + assert ( + loaded_server.lookup_media( + MEDIA_TYPE_MUSIC, + library_name="Music", + artist_name="An Artist", + album_name="Not an Album", + ) + is None + ) + with patch.object(MockPlexMediaItem, "track", side_effect=NotFound): + assert ( + loaded_server.lookup_media( + MEDIA_TYPE_MUSIC, + library_name="Music", + artist_name="An Artist", + album_name="An Album", + track_name="Not a Track", + ) + is None + ) + with patch.object(MockPlexArtist, "get", side_effect=NotFound): + assert ( + loaded_server.lookup_media( + MEDIA_TYPE_MUSIC, + library_name="Music", + artist_name="An Artist", + track_name="Not a Track", + ) + is None + ) + assert loaded_server.lookup_media( + MEDIA_TYPE_MUSIC, + library_name="Music", + artist_name="An Artist", + album_name="An Album", + track_number=3, + ) + assert ( + loaded_server.lookup_media( + MEDIA_TYPE_MUSIC, + library_name="Music", + artist_name="An Artist", + album_name="An Album", + track_number=30, + ) + is None + ) + assert loaded_server.lookup_media( + MEDIA_TYPE_MUSIC, + library_name="Music", + artist_name="An Artist", + album_name="An Album", + track_name="A Track", + ) + + # Playlist searches + assert loaded_server.lookup_media(MEDIA_TYPE_PLAYLIST, playlist_name="A Playlist") + assert loaded_server.lookup_media(MEDIA_TYPE_PLAYLIST) is None + with patch.object(MockPlexServer, "playlist", side_effect=NotFound): + assert ( + loaded_server.lookup_media( + MEDIA_TYPE_PLAYLIST, playlist_name="Not a Playlist" + ) + is None + ) + + # Movie searches + assert loaded_server.lookup_media(MEDIA_TYPE_VIDEO, video_name="A Movie") is None + assert loaded_server.lookup_media(MEDIA_TYPE_VIDEO, library_name="Movies") is None + assert loaded_server.lookup_media( + MEDIA_TYPE_VIDEO, library_name="Movies", video_name="A Movie" + ) + with patch.object(MockPlexLibrarySection, "get", side_effect=NotFound): + assert ( + loaded_server.lookup_media( + MEDIA_TYPE_VIDEO, library_name="Movies", video_name="Not a Movie" + ) + is None + ) From ca1e643bdc43fa64f98940305b201777b9ec95d4 Mon Sep 17 00:00:00 2001 From: Charles Garwood Date: Tue, 26 May 2020 19:48:39 -0400 Subject: [PATCH 212/406] Add fan platform to ozw component (#35249) * Add fan platform * Add fan discovery schema * Use constants for dispatcher signal * Move fan platform to ozw * Fix fan discovery schema * Add previous speed to handle value 255 * Make fixture reading more robust * Add fan tests * Remove not needed fixture info * Validate speed Co-authored-by: Martin Hjelmare --- homeassistant/components/ozw/const.py | 2 + homeassistant/components/ozw/discovery.py | 12 ++ homeassistant/components/ozw/fan.py | 95 +++++++++++++++ tests/components/ozw/common.py | 5 +- tests/components/ozw/conftest.py | 17 +++ tests/components/ozw/test_fan.py | 135 ++++++++++++++++++++++ tests/fixtures/ozw/fan.json | 25 ++++ tests/fixtures/ozw/fan_network_dump.csv | 51 ++++++++ 8 files changed, 341 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/ozw/fan.py create mode 100644 tests/components/ozw/test_fan.py create mode 100644 tests/fixtures/ozw/fan.json create mode 100644 tests/fixtures/ozw/fan_network_dump.csv diff --git a/homeassistant/components/ozw/const.py b/homeassistant/components/ozw/const.py index cbd85af4b42..8115b18a0e8 100644 --- a/homeassistant/components/ozw/const.py +++ b/homeassistant/components/ozw/const.py @@ -1,6 +1,7 @@ """Constants for the ozw integration.""" from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN from homeassistant.components.climate import DOMAIN as CLIMATE_DOMAIN +from homeassistant.components.fan import DOMAIN as FAN_DOMAIN from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN @@ -11,6 +12,7 @@ DATA_UNSUBSCRIBE = "unsubscribe" PLATFORMS = [ BINARY_SENSOR_DOMAIN, CLIMATE_DOMAIN, + FAN_DOMAIN, LIGHT_DOMAIN, LOCK_DOMAIN, SENSOR_DOMAIN, diff --git a/homeassistant/components/ozw/discovery.py b/homeassistant/components/ozw/discovery.py index 2a403964370..f9b0bdb3551 100644 --- a/homeassistant/components/ozw/discovery.py +++ b/homeassistant/components/ozw/discovery.py @@ -131,6 +131,18 @@ DISCOVERY_SCHEMAS = ( }, }, }, + { # Fan + const.DISC_COMPONENT: "fan", + const.DISC_GENERIC_DEVICE_CLASS: const_ozw.GENERIC_TYPE_SWITCH_MULTILEVEL, + const.DISC_SPECIFIC_DEVICE_CLASS: const_ozw.SPECIFIC_TYPE_FAN_SWITCH, + const.DISC_VALUES: { + const.DISC_PRIMARY: { + const.DISC_COMMAND_CLASS: CommandClass.SWITCH_MULTILEVEL, + const.DISC_INDEX: ValueIndex.SWITCH_MULTILEVEL_LEVEL, + const.DISC_TYPE: ValueType.BYTE, + }, + }, + }, { # Light const.DISC_COMPONENT: "light", const.DISC_GENERIC_DEVICE_CLASS: ( diff --git a/homeassistant/components/ozw/fan.py b/homeassistant/components/ozw/fan.py new file mode 100644 index 00000000000..818bd710496 --- /dev/null +++ b/homeassistant/components/ozw/fan.py @@ -0,0 +1,95 @@ +"""Support for Z-Wave fans.""" +import logging +import math + +from homeassistant.components.fan import ( + DOMAIN as FAN_DOMAIN, + SPEED_HIGH, + SPEED_LOW, + SPEED_MEDIUM, + SPEED_OFF, + SUPPORT_SET_SPEED, + FanEntity, +) +from homeassistant.core import callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect + +from .const import DATA_UNSUBSCRIBE, DOMAIN +from .entity import ZWaveDeviceEntity + +_LOGGER = logging.getLogger(__name__) + +SUPPORTED_FEATURES = SUPPORT_SET_SPEED + +# Value will first be divided to an integer +VALUE_TO_SPEED = {0: SPEED_OFF, 1: SPEED_LOW, 2: SPEED_MEDIUM, 3: SPEED_HIGH} +SPEED_TO_VALUE = {SPEED_OFF: 0, SPEED_LOW: 1, SPEED_MEDIUM: 50, SPEED_HIGH: 99} +SPEED_LIST = [*SPEED_TO_VALUE] + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up Z-Wave Fan from Config Entry.""" + + @callback + def async_add_fan(values): + """Add Z-Wave Fan.""" + fan = ZwaveFan(values) + async_add_entities([fan]) + + hass.data[DOMAIN][config_entry.entry_id][DATA_UNSUBSCRIBE].append( + async_dispatcher_connect(hass, f"{DOMAIN}_new_{FAN_DOMAIN}", async_add_fan) + ) + + +class ZwaveFan(ZWaveDeviceEntity, FanEntity): + """Representation of a Z-Wave fan.""" + + def __init__(self, values): + """Initialize the fan.""" + super().__init__(values) + self._previous_speed = None + + async def async_set_speed(self, speed): + """Set the speed of the fan.""" + if speed not in SPEED_TO_VALUE: + _LOGGER.warning("Invalid speed received: %s", speed) + return + self._previous_speed = speed + self.values.primary.send_value(SPEED_TO_VALUE[speed]) + + async def async_turn_on(self, speed=None, **kwargs): + """Turn the device on.""" + if speed is None: + # Value 255 tells device to return to previous value + self.values.primary.send_value(255) + else: + await self.async_set_speed(speed) + + async def async_turn_off(self, **kwargs): + """Turn the device off.""" + self.values.primary.send_value(0) + + @property + def is_on(self): + """Return true if device is on (speed above 0).""" + return self.values.primary.value > 0 + + @property + def speed(self): + """Return the current speed. + + The Z-Wave speed value is a byte 0-255. 255 means previous value. + The normal range of the speed is 0-99. 0 means off. + """ + value = math.ceil(self.values.primary.value * 3 / 100) + return VALUE_TO_SPEED.get(value, self._previous_speed) + + @property + def speed_list(self): + """Get the list of available speeds.""" + return SPEED_LIST + + @property + def supported_features(self): + """Flag supported features.""" + return SUPPORTED_FEATURES diff --git a/tests/components/ozw/common.py b/tests/components/ozw/common.py index a71103fdf85..7a78d11a445 100644 --- a/tests/components/ozw/common.py +++ b/tests/components/ozw/common.py @@ -32,7 +32,10 @@ async def setup_ozw(hass, entry=None, fixture=None): if fixture is not None: for line in fixture.split("\n"): - topic, payload = line.strip().split(",", 1) + line = line.strip() + if not line: + continue + topic, payload = line.split(",", 1) receive_message(Mock(topic=topic, payload=payload)) await hass.async_block_till_done() diff --git a/tests/components/ozw/conftest.py b/tests/components/ozw/conftest.py index b9c7e17ba62..5f29435760c 100644 --- a/tests/components/ozw/conftest.py +++ b/tests/components/ozw/conftest.py @@ -15,6 +15,12 @@ def generic_data_fixture(): return load_fixture("ozw/generic_network_dump.csv") +@pytest.fixture(name="fan_data", scope="session") +def fan_data_fixture(): + """Load fan MQTT data and return it.""" + return load_fixture("ozw/fan_network_dump.csv") + + @pytest.fixture(name="light_data", scope="session") def light_data_fixture(): """Load light dimmer MQTT data and return it.""" @@ -47,6 +53,17 @@ def sent_messages_fixture(): yield sent_messages +@pytest.fixture(name="fan_msg") +async def fan_msg_fixture(hass): + """Return a mock MQTT msg with a fan actuator message.""" + fan_json = json.loads( + await hass.async_add_executor_job(load_fixture, "ozw/fan.json") + ) + message = MQTTMessage(topic=fan_json["topic"], payload=fan_json["payload"]) + message.encode() + return message + + @pytest.fixture(name="light_msg") async def light_msg_fixture(hass): """Return a mock MQTT msg with a light actuator message.""" diff --git a/tests/components/ozw/test_fan.py b/tests/components/ozw/test_fan.py new file mode 100644 index 00000000000..cca1143c44b --- /dev/null +++ b/tests/components/ozw/test_fan.py @@ -0,0 +1,135 @@ +"""Test Z-Wave Lights.""" +from homeassistant.components.ozw.fan import SPEED_TO_VALUE + +from .common import setup_ozw + + +async def test_fan(hass, fan_data, fan_msg, sent_messages, caplog): + """Test fan.""" + receive_message = await setup_ozw(hass, fixture=fan_data) + + # Test loaded + state = hass.states.get("fan.in_wall_smart_fan_control_level") + assert state is not None + assert state.state == "on" + + # Test turning off + await hass.services.async_call( + "fan", + "turn_off", + {"entity_id": "fan.in_wall_smart_fan_control_level"}, + blocking=True, + ) + + assert len(sent_messages) == 1 + msg = sent_messages[-1] + assert msg["topic"] == "OpenZWave/1/command/setvalue/" + assert msg["payload"] == {"Value": 0, "ValueIDKey": 172589073} + + # Feedback on state + fan_msg.decode() + fan_msg.payload["Value"] = 0 + fan_msg.encode() + receive_message(fan_msg) + await hass.async_block_till_done() + + state = hass.states.get("fan.in_wall_smart_fan_control_level") + assert state is not None + assert state.state == "off" + + # Test turning on + new_speed = "medium" + await hass.services.async_call( + "fan", + "turn_on", + {"entity_id": "fan.in_wall_smart_fan_control_level", "speed": new_speed}, + blocking=True, + ) + + assert len(sent_messages) == 2 + msg = sent_messages[-1] + assert msg["topic"] == "OpenZWave/1/command/setvalue/" + assert msg["payload"] == { + "Value": SPEED_TO_VALUE[new_speed], + "ValueIDKey": 172589073, + } + + # Feedback on state + fan_msg.decode() + fan_msg.payload["Value"] = SPEED_TO_VALUE[new_speed] + fan_msg.encode() + receive_message(fan_msg) + await hass.async_block_till_done() + + state = hass.states.get("fan.in_wall_smart_fan_control_level") + assert state is not None + assert state.state == "on" + assert state.attributes["speed"] == new_speed + + # Test turn on without speed + await hass.services.async_call( + "fan", + "turn_on", + {"entity_id": "fan.in_wall_smart_fan_control_level"}, + blocking=True, + ) + + assert len(sent_messages) == 3 + msg = sent_messages[-1] + assert msg["topic"] == "OpenZWave/1/command/setvalue/" + assert msg["payload"] == { + "Value": 255, + "ValueIDKey": 172589073, + } + + # Feedback on state + fan_msg.decode() + fan_msg.payload["Value"] = SPEED_TO_VALUE[new_speed] + fan_msg.encode() + receive_message(fan_msg) + await hass.async_block_till_done() + + state = hass.states.get("fan.in_wall_smart_fan_control_level") + assert state is not None + assert state.state == "on" + assert state.attributes["speed"] == new_speed + + # Test set speed to off + new_speed = "off" + await hass.services.async_call( + "fan", + "set_speed", + {"entity_id": "fan.in_wall_smart_fan_control_level", "speed": new_speed}, + blocking=True, + ) + + assert len(sent_messages) == 4 + msg = sent_messages[-1] + assert msg["topic"] == "OpenZWave/1/command/setvalue/" + assert msg["payload"] == { + "Value": SPEED_TO_VALUE[new_speed], + "ValueIDKey": 172589073, + } + + # Feedback on state + fan_msg.decode() + fan_msg.payload["Value"] = SPEED_TO_VALUE[new_speed] + fan_msg.encode() + receive_message(fan_msg) + await hass.async_block_till_done() + + state = hass.states.get("fan.in_wall_smart_fan_control_level") + assert state is not None + assert state.state == "off" + + # Test invalid speed + new_speed = "invalid" + await hass.services.async_call( + "fan", + "set_speed", + {"entity_id": "fan.in_wall_smart_fan_control_level", "speed": new_speed}, + blocking=True, + ) + + assert len(sent_messages) == 4 + assert "Invalid speed received: invalid" in caplog.text diff --git a/tests/fixtures/ozw/fan.json b/tests/fixtures/ozw/fan.json new file mode 100644 index 00000000000..2684e5f7385 --- /dev/null +++ b/tests/fixtures/ozw/fan.json @@ -0,0 +1,25 @@ +{ + "topic": "OpenZWave/1/node/10/instance/1/commandclass/38/value/172589073/", + "payload": { + "Label": "Level", + "Value": 41, + "Units": "", + "ValueSet": false, + "ValuePolled": false, + "ChangeVerified": false, + "Min": 0, + "Max": 255, + "Type": "Byte", + "Instance": 1, + "CommandClass": "COMMAND_CLASS_SWITCH_MULTILEVEL", + "Index": 0, + "Node": 10, + "Genre": "User", + "Help": "The Current Level of the Device", + "ValueIDKey": 172589073, + "ReadOnly": false, + "WriteOnly": false, + "Event": "valueAdded", + "TimeStamp": 1589997977 + } +} diff --git a/tests/fixtures/ozw/fan_network_dump.csv b/tests/fixtures/ozw/fan_network_dump.csv new file mode 100644 index 00000000000..54541271d14 --- /dev/null +++ b/tests/fixtures/ozw/fan_network_dump.csv @@ -0,0 +1,51 @@ +OpenZWave/1/status/,{ "OpenZWave_Version": "1.6.1123", "OZWDaemon_Version": "0.1.98", "QTOpenZWave_Version": "1.0.0", "QT_Version": "5.12.5", "Status": "driverAllNodesQueried", "TimeStamp": 1589998153, "ManufacturerSpecificDBReady": true, "homeID": 4188283268, "getControllerNodeId": 1, "getSUCNodeId": 1, "isPrimaryController": true, "isBridgeController": false, "hasExtendedTXStatistics": false, "getControllerLibraryVersion": "Z-Wave 4.54", "getControllerLibraryType": "Static Controller", "getControllerPath": "/dev/ttyACM0"} +OpenZWave/1/node/10/,{ "NodeID": 10, "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/0063:3031:4944", "ZWAProductURL": "", "ProductPic": "images/ge/12724-dimmer.png", "Description": "Transform any home into a smart home with the GE Z-Wave Smart Fan Control. The in-wall fan control easily replaces any standard in-wall switch remotely controls a ceiling fan in your home and features a three-speed control system. Your home will be equipped with ultimate flexibility with the GE Z-Wave Smart Fan Control, capable of being used by itself or with up to four GE add-on switches. Screw terminal installation provides improved space efficiency when replacing existing switches and the integrated LED indicator light allows you to easily locate the switch in a dark room. The GE Z-Wave Smart Fan Control is compatible with any Z-Wave certified gateway, providing access to many popular home automation systems. Take control of your home lighting with GE Z-Wave Smart Lighting Controls!", "ProductManualURL": "https://Products.Z-WaveAlliance.org/ProductManual/File?folder=&filename=Manuals/2506/Binder2.pdf", "ProductPageURL": "http://www.ezzwave.com", "InclusionHelp": "1. Follow the instructions for your Z-Wave certified controller to include a device to the Z-Wave network. 2. Once the controller is ready to include your device, press and release the top or bottom of the smart fan control switch (rocker) to include it in the network. 3. Once your controller has confirmed the device has been included, refresh the Z-Wave network to optimize performance.", "ExclusionHelp": "1. Follow the instructions for your Z-Wave certified controller to exclude a device from the Z-Wave network. 2. Once the controller is ready to Exclude your device, press and release the top or bottom of the wireless smart switch (rocker) to exclude it from the network.", "ResetHelp": "1. Quickly press ON (Top) button three (3) times then immediately press the OFF (Bottom) button three (3) times. The LED will flash ON/OFF 5 times when completed successfully. Note: This should only be used in the event your network’s primary controller is missing or otherwise inoperable.", "WakeupHelp": "", "ProductSupportURL": "", "Frequency": "", "Name": "In-Wall Smart Fan Control" }, "Event": "nodeQueriesComplete", "TimeStamp": 1589998151, "NodeManufacturerName": "GE (Jasco Products)", "NodeProductName": "14287 Fan Control Switch", "NodeBasicString": "Routing Slave", "NodeBasic": 4, "NodeGenericString": "Multilevel Switch", "NodeGeneric": 17, "NodeSpecificString": "Fan Switch", "NodeSpecific": 8, "NodeManufacturerID": "0x0063", "NodeProductType": "0x4944", "NodeProductID": "0x3131", "NodeBaudRate": 100000, "NodeVersion": 4, "NodeGroups": 3, "NodeName": "", "NodeLocation": "", "NodeDeviceTypeString": "Fan Switch", "NodeDeviceType": 1024, "NodeRole": 5, "NodeRoleString": "Always On Slave", "NodePlusType": 0, "NodePlusTypeString": "Z-Wave+ node", "Neighbors": [ 1, 9, 11, 12, 13, 14, 15, 16, 17, 18, 19, 21, 22, 29, 30, 32, 33 ]} +OpenZWave/1/node/10/instance/1/,{ "Instance": 1, "TimeStamp": 1589997977} +OpenZWave/1/node/10/instance/1/commandclass/38/,{ "Instance": 1, "CommandClassId": 38, "CommandClass": "COMMAND_CLASS_SWITCH_MULTILEVEL", "CommandClassVersion": 1, "TimeStamp": 1589997977} +OpenZWave/1/node/10/instance/1/commandclass/38/value/172589073/,{ "Label": "Level", "Value": 41, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_SWITCH_MULTILEVEL", "Index": 0, "Node": 10, "Genre": "User", "Help": "The Current Level of the Device", "ValueIDKey": 172589073, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1589997977} +OpenZWave/1/node/10/instance/1/commandclass/38/value/281475149299736/,{ "Label": "Bright", "Value": false, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "Button", "Instance": 1, "CommandClass": "COMMAND_CLASS_SWITCH_MULTILEVEL", "Index": 1, "Node": 10, "Genre": "User", "Help": "Increase the Brightness of the Device", "ValueIDKey": 281475149299736, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1589997977} +OpenZWave/1/node/10/instance/1/commandclass/38/value/562950126010392/,{ "Label": "Dim", "Value": false, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "Button", "Instance": 1, "CommandClass": "COMMAND_CLASS_SWITCH_MULTILEVEL", "Index": 2, "Node": 10, "Genre": "User", "Help": "Decrease the Brightness of the Device", "ValueIDKey": 562950126010392, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1589997977} +OpenZWave/1/node/10/instance/1/commandclass/38/value/844425111109648/,{ "Label": "Ignore Start Level", "Value": true, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "Bool", "Instance": 1, "CommandClass": "COMMAND_CLASS_SWITCH_MULTILEVEL", "Index": 3, "Node": 10, "Genre": "System", "Help": "Ignore the Start Level of the Device when increasing/decreasing brightness", "ValueIDKey": 844425111109648, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1589997977} +OpenZWave/1/node/10/instance/1/commandclass/38/value/1125900087820305/,{ "Label": "Start Level", "Value": 0, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_SWITCH_MULTILEVEL", "Index": 4, "Node": 10, "Genre": "System", "Help": "Start Level when Changing the Brightness of a Device", "ValueIDKey": 1125900087820305, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1589997977} +OpenZWave/1/node/10/instance/1/commandclass/39/,{ "Instance": 1, "CommandClassId": 39, "CommandClass": "COMMAND_CLASS_SWITCH_ALL", "CommandClassVersion": 1, "TimeStamp": 1589997977} +OpenZWave/1/node/10/instance/1/commandclass/39/value/180994068/,{ "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", "Selected_id": 255 }, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_SWITCH_ALL", "Index": 0, "Node": 10, "Genre": "System", "Help": "Switch All Devices On/Off", "ValueIDKey": 180994068, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1589997977} +OpenZWave/1/node/10/instance/1/commandclass/43/,{ "Instance": 1, "CommandClassId": 43, "CommandClass": "COMMAND_CLASS_SCENE_ACTIVATION", "CommandClassVersion": 1, "TimeStamp": 1589997977} +OpenZWave/1/node/10/instance/1/commandclass/43/value/172670995/,{ "Label": "Scene", "Value": 0, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_SCENE_ACTIVATION", "Index": 0, "Node": 10, "Genre": "User", "Help": "", "ValueIDKey": 172670995, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1589997977} +OpenZWave/1/node/10/instance/1/commandclass/43/value/281475149381651/,{ "Label": "Duration", "Value": 0, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_SCENE_ACTIVATION", "Index": 1, "Node": 10, "Genre": "User", "Help": "", "ValueIDKey": 281475149381651, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1589997977} +OpenZWave/1/node/10/instance/1/commandclass/94/,{ "Instance": 1, "CommandClassId": 94, "CommandClass": "COMMAND_CLASS_ZWAVEPLUS_INFO", "CommandClassVersion": 1, "TimeStamp": 1589997977} +OpenZWave/1/node/10/instance/1/commandclass/94/value/181895185/,{ "Label": "ZWave+ Version", "Value": 1, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_ZWAVEPLUS_INFO", "Index": 0, "Node": 10, "Genre": "System", "Help": "ZWave+ Version Supported on the Device", "ValueIDKey": 181895185, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1589997977} +OpenZWave/1/node/10/instance/1/commandclass/94/value/281475158605846/,{ "Label": "InstallerIcon", "Value": 1024, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_ZWAVEPLUS_INFO", "Index": 1, "Node": 10, "Genre": "System", "Help": "Icon File to use for the Installer Application", "ValueIDKey": 281475158605846, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1589997977} +OpenZWave/1/node/10/instance/1/commandclass/94/value/562950135316502/,{ "Label": "UserIcon", "Value": 1024, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_ZWAVEPLUS_INFO", "Index": 2, "Node": 10, "Genre": "System", "Help": "Icon File to use for the User Application", "ValueIDKey": 562950135316502, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1589997977} +OpenZWave/1/node/10/instance/1/commandclass/112/,{ "Instance": 1, "CommandClassId": 112, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "CommandClassVersion": 1, "TimeStamp": 1589997977} +OpenZWave/1/node/10/instance/1/commandclass/112/value/844425108127764/,{ "Label": "LED Light", "Value": { "List": [ { "Value": 0, "Label": "LED on when light off" }, { "Value": 1, "Label": "LED on when light on" }, { "Value": 2, "Label": "LED always off" } ], "Selected": "LED on when light off", "Selected_id": 0 }, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 2, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 3, "Node": 10, "Genre": "Config", "Help": "Sets when the LED on the switch is lit.", "ValueIDKey": 844425108127764, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1589997977} +OpenZWave/1/node/10/instance/1/commandclass/112/value/1125900084838420/,{ "Label": "Invert Switch", "Value": { "List": [ { "Value": 0, "Label": "No" }, { "Value": 1, "Label": "Yes" } ], "Selected": "No", "Selected_id": 0 }, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 1, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 4, "Node": 10, "Genre": "Config", "Help": "Change the top of the switch to OFF and the bottom of the switch to ON, if the switch was installed upside down.", "ValueIDKey": 1125900084838420, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1589997977} +OpenZWave/1/node/10/instance/1/commandclass/112/value/1970325014970385/,{ "Label": "Z-Wave Command Dim Step", "Value": 1, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 1, "Max": 99, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 7, "Node": 10, "Genre": "Config", "Help": "Indicates how many levels the dimmer will change for each dimming step.", "ValueIDKey": 1970325014970385, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1589997977} +OpenZWave/1/node/10/instance/1/commandclass/112/value/2251799991681041/,{ "Label": "Z-Wave Command Dim Rate", "Value": 3, "Units": "x 10 milliseconds", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 1, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 8, "Node": 10, "Genre": "Config", "Help": "This value indicates in 10 millisecond resolution, how often the dim level will change. For example, if you set this parameter to 1, then every 10ms the dim level will change. If you set it to 255, then every 2.55 seconds the dim level will change.", "ValueIDKey": 2251799991681041, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1589997977} +OpenZWave/1/node/10/instance/1/commandclass/112/value/2533274968391697/,{ "Label": "Local Control Dim Step", "Value": 1, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 1, "Max": 99, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 9, "Node": 10, "Genre": "Config", "Help": "Indicates how many levels the dimmer will change for each dimming step.", "ValueIDKey": 2533274968391697, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1589997977} +OpenZWave/1/node/10/instance/1/commandclass/112/value/2814749945102353/,{ "Label": "Local Control Dim Rate", "Value": 3, "Units": "x 10 milliseconds", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 1, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 10, "Node": 10, "Genre": "Config", "Help": "This value indicates in 10 millisecond resolution, how often the dim level will change. For example, if you set this parameter to 1, then every 10ms the dim level will change. If you set it to 255, then every 2.55 seconds the dim level will change.", "ValueIDKey": 2814749945102353, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1589997977} +OpenZWave/1/node/10/instance/1/commandclass/112/value/3096224921813009/,{ "Label": "ALL ON/ALL OFF Dim Step", "Value": 1, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 1, "Max": 99, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 11, "Node": 10, "Genre": "Config", "Help": "Indicates how many levels the dimmer will change for each dimming step.", "ValueIDKey": 3096224921813009, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1589997977} +OpenZWave/1/node/10/instance/1/commandclass/112/value/3377699898523665/,{ "Label": "ALL ON/ALL OFF Dim Rate", "Value": 3, "Units": "x 10 milliseconds", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 1, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_CONFIGURATION", "Index": 12, "Node": 10, "Genre": "Config", "Help": "This value indicates in 10 millisecond resolution, how often the dim level will change. For example, if you set this parameter to 1, then every 10ms the dim level will change. If you set it to 255, then every 2.55 seconds the dim level will change.", "ValueIDKey": 3377699898523665, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1589997977} +OpenZWave/1/node/10/instance/1/commandclass/114/,{ "Instance": 1, "CommandClassId": 114, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "CommandClassVersion": 2, "TimeStamp": 1589997977} +OpenZWave/1/node/10/instance/1/commandclass/114/value/182222867/,{ "Label": "Loaded Config Revision", "Value": 0, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 0, "Node": 10, "Genre": "System", "Help": "Revision of the Config file currently loaded", "ValueIDKey": 182222867, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1589997977} +OpenZWave/1/node/10/instance/1/commandclass/114/value/281475158933523/,{ "Label": "Config File Revision", "Value": 9, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 1, "Node": 10, "Genre": "System", "Help": "Revision of the Config file on the File System", "ValueIDKey": 281475158933523, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1589997977} +OpenZWave/1/node/10/instance/1/commandclass/114/value/562950135644179/,{ "Label": "Latest Available Config File Revision", "Value": 9, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": -2147483648, "Max": 2147483647, "Type": "Int", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 2, "Node": 10, "Genre": "System", "Help": "Latest Revision of the Config file available for download", "ValueIDKey": 562950135644179, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1589997977} +OpenZWave/1/node/10/instance/1/commandclass/114/value/844425112354839/,{ "Label": "Device ID", "Value": "", "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 3, "Node": 10, "Genre": "System", "Help": "Manufacturer Specific Device ID/Model", "ValueIDKey": 844425112354839, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1589997977} +OpenZWave/1/node/10/instance/1/commandclass/114/value/1125900089065495/,{ "Label": "Serial Number", "Value": "", "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_MANUFACTURER_SPECIFIC", "Index": 4, "Node": 10, "Genre": "System", "Help": "Device Serial Number", "ValueIDKey": 1125900089065495, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1589997977} +OpenZWave/1/node/10/instance/1/commandclass/115/,{ "Instance": 1, "CommandClassId": 115, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "CommandClassVersion": 1, "TimeStamp": 1589997977} +OpenZWave/1/node/10/instance/1/commandclass/115/value/182239252/,{ "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", "Selected_id": 0 }, "Units": "dB", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 0, "Node": 10, "Genre": "System", "Help": "Output RF PowerLevel", "ValueIDKey": 182239252, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1589997977} +OpenZWave/1/node/10/instance/1/commandclass/115/value/281475158949905/,{ "Label": "Timeout", "Value": 0, "Units": "seconds", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 1, "Node": 10, "Genre": "System", "Help": "Timeout till the PowerLevel is reset to Normal", "ValueIDKey": 281475158949905, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1589997977} +OpenZWave/1/node/10/instance/1/commandclass/115/value/562950135660568/,{ "Label": "Set Powerlevel", "Value": false, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "Button", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 2, "Node": 10, "Genre": "System", "Help": "Apply the Output PowerLevel and Timeout Values", "ValueIDKey": 562950135660568, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1589997977} +OpenZWave/1/node/10/instance/1/commandclass/115/value/844425112371217/,{ "Label": "Test Node", "Value": 0, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 255, "Type": "Byte", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 3, "Node": 10, "Genre": "System", "Help": "Node to Perform a test against", "ValueIDKey": 844425112371217, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1589997977} +OpenZWave/1/node/10/instance/1/commandclass/115/value/1125900089081876/,{ "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", "Selected_id": 0 }, "Units": "dB", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 4, "Node": 10, "Genre": "System", "Help": "PowerLevel to use for the Test", "ValueIDKey": 1125900089081876, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1589997977} +OpenZWave/1/node/10/instance/1/commandclass/115/value/1407375065792534/,{ "Label": "Frame Count", "Value": 0, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 5, "Node": 10, "Genre": "System", "Help": "How Many Messages to send to the Node for the Test", "ValueIDKey": 1407375065792534, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1589997977} +OpenZWave/1/node/10/instance/1/commandclass/115/value/1688850042503192/,{ "Label": "Test", "Value": false, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "Button", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 6, "Node": 10, "Genre": "System", "Help": "Perform a PowerLevel Test against the a Node", "ValueIDKey": 1688850042503192, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1589997977} +OpenZWave/1/node/10/instance/1/commandclass/115/value/1970325019213848/,{ "Label": "Report", "Value": false, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "Button", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 7, "Node": 10, "Genre": "System", "Help": "Get the results of the latest PowerLevel Test against a Node", "ValueIDKey": 1970325019213848, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1589997977} +OpenZWave/1/node/10/instance/1/commandclass/115/value/2251799995924500/,{ "Label": "Test Status", "Value": { "List": [ { "Value": 0, "Label": "Failed" }, { "Value": 1, "Label": "Success" }, { "Value": 2, "Label": "In Progress" } ], "Selected": "Failed", "Selected_id": 0 }, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "List", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 8, "Node": 10, "Genre": "System", "Help": "The Current Status of the last PowerNode Test Executed", "ValueIDKey": 2251799995924500, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1589997977} +OpenZWave/1/node/10/instance/1/commandclass/115/value/2533274972635158/,{ "Label": "Acked Frames", "Value": 0, "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": -32768, "Max": 32767, "Type": "Short", "Instance": 1, "CommandClass": "COMMAND_CLASS_POWERLEVEL", "Index": 9, "Node": 10, "Genre": "System", "Help": "Number of Messages successfully Acked by the Target Node", "ValueIDKey": 2533274972635158, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1589997977} +OpenZWave/1/node/10/instance/1/commandclass/134/,{ "Instance": 1, "CommandClassId": 134, "CommandClass": "COMMAND_CLASS_VERSION", "CommandClassVersion": 1, "TimeStamp": 1589997977} +OpenZWave/1/node/10/instance/1/commandclass/134/value/182550551/,{ "Label": "Library Version", "Value": "3", "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_VERSION", "Index": 0, "Node": 10, "Genre": "System", "Help": "Z-Wave Library Version", "ValueIDKey": 182550551, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1589997977} +OpenZWave/1/node/10/instance/1/commandclass/134/value/281475159261207/,{ "Label": "Protocol Version", "Value": "4.54", "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_VERSION", "Index": 1, "Node": 10, "Genre": "System", "Help": "Z-Wave Protocol Version", "ValueIDKey": 281475159261207, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1589997977} +OpenZWave/1/node/10/instance/1/commandclass/134/value/562950135971863/,{ "Label": "Application Version", "Value": "5.22", "Units": "", "ValueSet": false, "ValuePolled": false, "ChangeVerified": false, "Min": 0, "Max": 0, "Type": "String", "Instance": 1, "CommandClass": "COMMAND_CLASS_VERSION", "Index": 2, "Node": 10, "Genre": "System", "Help": "Application Version", "ValueIDKey": 562950135971863, "ReadOnly": false, "WriteOnly": false, "Event": "valueAdded", "TimeStamp": 1589997977} +OpenZWave/1/node/10/association/1/,{ "Name": "Group 1", "Help": "", "MaxAssociations": 5, "Members": [ "1.0" ], "TimeStamp": 1589997977} +OpenZWave/1/node/10/association/2/,{ "Name": "Group 2", "Help": "", "MaxAssociations": 5, "Members": [], "TimeStamp": 1589998004} +OpenZWave/1/node/10/association/3/,{ "Name": "Group 3", "Help": "", "MaxAssociations": 5, "Members": [], "TimeStamp": 1589998004} From 6a06d648d72e7dc478c569db346208ddb05c06b4 Mon Sep 17 00:00:00 2001 From: Jeff Irion Date: Tue, 26 May 2020 17:02:18 -0700 Subject: [PATCH 213/406] Fix Android TV icon when screencap option is disabled (#35710) * Don't return a media image hash if the screencap config option is False * 1-liner --- homeassistant/components/androidtv/media_player.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/androidtv/media_player.py b/homeassistant/components/androidtv/media_player.py index 44085273940..4fd7b70835d 100644 --- a/homeassistant/components/androidtv/media_player.py +++ b/homeassistant/components/androidtv/media_player.py @@ -452,6 +452,11 @@ class ADBDevice(MediaPlayerEntity): """Provide the last ADB command's response as an attribute.""" return {"adb_response": self._adb_response} + @property + def media_image_hash(self): + """Hash value for media image.""" + return f"{datetime.now().timestamp()}" if self._screencap else None + @property def name(self): """Return the device name.""" @@ -497,11 +502,6 @@ class ADBDevice(MediaPlayerEntity): """Raw image data.""" return self.aftv.adb_screencap() - @property - def media_image_hash(self): - """Hash value for media image.""" - return f"{datetime.now().timestamp()}" - @adb_decorator() def media_play(self): """Send play command.""" From 0a6deeb49bc74901730e9f3ae8a980b004591ddb Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 26 May 2020 21:53:56 -0500 Subject: [PATCH 214/406] Improve history api performance (#35822) * Improve history api performance A new option "minimal_response" reduces the amount of data sent between the first and last history states to only the "last_changed" and "state" fields. Calling to_native is now avoided where possible and only done at the end for rows that will be returned in the response. When sending the `minimal_response` option, the history api now returns a json response similar to the following for an entity Testing: History API Response time for 1 day Average of 10 runs with minimal_response Before: 19.89s. (content length : 3427428) After: 8.44s (content length: 592199) ``` [{ "attributes": {--TRUNCATED--}, "context": {--TRUNCATED--}, "entity_id": "binary_sensor.powerwall_status", "last_changed": "2020-05-18T23:20:03.213000+00:00", "last_updated": "2020-05-18T23:20:03.213000+00:00", "state": "on" }, ... { "last_changed": "2020-05-19T00:41:08Z", "state": "unavailable" }, ... { "attributes": {--TRUNCATED--}, "context": {--TRUNCATED--}, "entity_id": "binary_sensor.powerwall_status", "last_changed": "2020-05-19T00:42:08.069698+00:00", "last_updated": "2020-05-19T00:42:08.069698+00:00", "state": "on" }] ``` * Remove impossible state check * Remove another impossible state check * Update homeassistant/components/history/__init__.py Co-authored-by: Paulus Schoutsen * Reorder to save some indent per review * Make query response make sense with to_native=False * Update test for 00:00 to Z change * Update homeassistant/components/recorder/models.py Co-authored-by: Paulus Schoutsen Co-authored-by: Paulus Schoutsen --- homeassistant/components/history/__init__.py | 109 +++++++++++++++---- homeassistant/components/recorder/models.py | 10 +- homeassistant/components/recorder/util.py | 26 +++-- tests/components/history/test_init.py | 86 ++++++++++++++- 4 files changed, 194 insertions(+), 37 deletions(-) diff --git a/homeassistant/components/history/__init__.py b/homeassistant/components/history/__init__.py index 8228e1c67df..4933b00ffde 100644 --- a/homeassistant/components/history/__init__.py +++ b/homeassistant/components/history/__init__.py @@ -12,7 +12,7 @@ import voluptuous as vol from homeassistant.components import recorder from homeassistant.components.http import HomeAssistantView -from homeassistant.components.recorder.models import States +from homeassistant.components.recorder.models import DB_TIMEZONE, States from homeassistant.components.recorder.util import execute, session_scope from homeassistant.const import ( ATTR_HIDDEN, @@ -22,6 +22,7 @@ from homeassistant.const import ( CONF_INCLUDE, HTTP_BAD_REQUEST, ) +from homeassistant.core import split_entity_id import homeassistant.helpers.config_validation as cv import homeassistant.util.dt as dt_util @@ -32,6 +33,9 @@ _LOGGER = logging.getLogger(__name__) DOMAIN = "history" CONF_ORDER = "use_include_order" +STATE_KEY = "state" +LAST_CHANGED_KEY = "last_changed" + CONFIG_SCHEMA = vol.Schema( { DOMAIN: recorder.FILTER_SCHEMA.extend( @@ -43,6 +47,9 @@ CONFIG_SCHEMA = vol.Schema( SIGNIFICANT_DOMAINS = ("climate", "device_tracker", "thermostat", "water_heater") IGNORE_DOMAINS = ("zone", "scene") +NEED_ATTRIBUTE_DOMAINS = {"climate", "water_heater", "thermostat", "script"} +SCRIPT_DOMAIN = "script" +ATTR_CAN_CANCEL = "can_cancel" def get_significant_states(hass, *args, **kwargs): @@ -60,6 +67,7 @@ def _get_significant_states( filters=None, include_start_time_state=True, significant_changes_only=True, + minimal_response=False, ): """ Return states changes during UTC period start_time - end_time. @@ -87,19 +95,15 @@ def _get_significant_states( if end_time is not None: query = query.filter(States.last_updated < end_time) - query = query.order_by(States.last_updated) + query = query.order_by(States.entity_id, States.last_updated) - states = ( - state - for state in execute(query) - if (_is_significant(state) and not state.attributes.get(ATTR_HIDDEN, False)) - ) + states = execute(query, to_native=False) if _LOGGER.isEnabledFor(logging.DEBUG): elapsed = time.perf_counter() - timer_start _LOGGER.debug("get_significant_states took %fs", elapsed) - return _states_to_json( + return _sorted_states_to_json( hass, session, states, @@ -107,6 +111,7 @@ def _get_significant_states( entity_ids, filters, include_start_time_state, + minimal_response, ) @@ -127,9 +132,11 @@ def state_changes_during_period(hass, start_time, end_time=None, entity_id=None) entity_ids = [entity_id] if entity_id is not None else None - states = execute(query.order_by(States.last_updated)) + states = execute( + query.order_by(States.entity_id, States.last_updated), to_native=False + ) - return _states_to_json(hass, session, states, start_time, entity_ids) + return _sorted_states_to_json(hass, session, states, start_time, entity_ids) def get_last_state_changes(hass, number_of_states, entity_id): @@ -146,10 +153,13 @@ def get_last_state_changes(hass, number_of_states, entity_id): entity_ids = [entity_id] if entity_id is not None else None states = execute( - query.order_by(States.last_updated.desc()).limit(number_of_states) + query.order_by(States.entity_id, States.last_updated.desc()).limit( + number_of_states + ), + to_native=False, ) - return _states_to_json( + return _sorted_states_to_json( hass, session, reversed(states), @@ -252,7 +262,7 @@ def _get_states_with_session( ] -def _states_to_json( +def _sorted_states_to_json( hass, session, states, @@ -260,12 +270,15 @@ def _states_to_json( entity_ids, filters=None, include_start_time_state=True, + minimal_response=False, ): """Convert SQL results into JSON friendly data structure. This takes our state list and turns it into a JSON friendly data structure {'entity_id': [list of states], 'entity_id2': [list of states]} + States must be sorted by entity_id and last_updated + We also need to go back and create a synthetic zero data point for each list of states, otherwise our graphs won't start on the Y axis correctly. @@ -293,7 +306,61 @@ def _states_to_json( # Append all changes to it for ent_id, group in groupby(states, lambda state: state.entity_id): - result[ent_id].extend(group) + domain = split_entity_id(ent_id)[0] + ent_results = result[ent_id] + if not minimal_response or domain in NEED_ATTRIBUTE_DOMAINS: + ent_results.extend( + [ + native_state + for native_state in (db_state.to_native() for db_state in group) + if ( + domain != SCRIPT_DOMAIN + or native_state.attributes.get(ATTR_CAN_CANCEL) + ) + and not native_state.attributes.get(ATTR_HIDDEN, False) + ] + ) + continue + + # With minimal response we only provide a native + # State for the first and last response. All the states + # in-between only provide the "state" and the + # "last_changed". + if not ent_results: + ent_results.append(next(group).to_native()) + + initial_state = ent_results[-1] + prev_state = ent_results[-1] + initial_state_count = len(ent_results) + + for db_state in group: + if ATTR_HIDDEN in db_state.attributes and db_state.to_native().attributes.get( + ATTR_HIDDEN, False + ): + continue + + # With minimal response we do not care about attribute + # changes so we can filter out duplicate states + if db_state.state == prev_state.state: + continue + + ent_results.append( + { + STATE_KEY: db_state.state, + LAST_CHANGED_KEY: f"{str(db_state.last_changed).replace(' ','T').split('.')[0]}{DB_TIMEZONE}", + } + ) + prev_state = db_state + + if ( + prev_state + and prev_state != initial_state + and len(ent_results) != initial_state_count + ): + # There was at least one state change + # replace the last minimal state with + # a full state + ent_results[-1] = prev_state.to_native() # Filter out the empty lists if some states had 0 results. return {key: val for key, val in result.items() if val} @@ -378,6 +445,8 @@ class HistoryPeriodView(HomeAssistantView): request.query.get("significant_changes_only", "1") != "0" ) + minimal_response = "minimal_response" in request.query + hass = request.app["hass"] return cast( @@ -390,6 +459,7 @@ class HistoryPeriodView(HomeAssistantView): entity_ids, include_start_time_state, significant_changes_only, + minimal_response, ), ) @@ -401,6 +471,7 @@ class HistoryPeriodView(HomeAssistantView): entity_ids, include_start_time_state, significant_changes_only, + minimal_response, ): """Fetch significant stats from the database as json.""" timer_start = time.perf_counter() @@ -415,6 +486,7 @@ class HistoryPeriodView(HomeAssistantView): self.filters, include_start_time_state, significant_changes_only, + minimal_response, ) result = list(result.values()) @@ -500,12 +572,3 @@ class Filters: if self.excluded_entities: query = query.filter(~States.entity_id.in_(self.excluded_entities)) return query - - -def _is_significant(state): - """Test if state is significant for history charts. - - Will only test for things that are not filtered out in SQL. - """ - # scripts that are not cancellable will never change state - return state.domain != "script" or state.attributes.get("can_cancel") diff --git a/homeassistant/components/recorder/models.py b/homeassistant/components/recorder/models.py index 8a6f25d57c3..ce46ae25476 100644 --- a/homeassistant/components/recorder/models.py +++ b/homeassistant/components/recorder/models.py @@ -28,6 +28,8 @@ SCHEMA_VERSION = 7 _LOGGER = logging.getLogger(__name__) +DB_TIMEZONE = "Z" + class Events(Base): # type: ignore """Event history data.""" @@ -64,7 +66,7 @@ class Events(Base): # type: ignore self.event_type, json.loads(self.event_data), EventOrigin(self.origin), - _process_timestamp(self.time_fired), + process_timestamp(self.time_fired), context=context, ) except ValueError: @@ -133,8 +135,8 @@ class States(Base): # type: ignore self.entity_id, self.state, json.loads(self.attributes), - _process_timestamp(self.last_changed), - _process_timestamp(self.last_updated), + process_timestamp(self.last_changed), + process_timestamp(self.last_updated), context=context, # Temp, because database can still store invalid entity IDs # Remove with 1.0 or in 2020. @@ -193,7 +195,7 @@ class SchemaChanges(Base): # type: ignore changed = Column(DateTime(timezone=True), default=dt_util.utcnow) -def _process_timestamp(ts): +def process_timestamp(ts): """Process a timestamp into datetime object.""" if ts is None: return None diff --git a/homeassistant/components/recorder/util.py b/homeassistant/components/recorder/util.py index 693d88ae795..d7f0771b6f5 100644 --- a/homeassistant/components/recorder/util.py +++ b/homeassistant/components/recorder/util.py @@ -54,7 +54,7 @@ def commit(session, work): return False -def execute(qry): +def execute(qry, to_native=True): """Query the database and convert the objects to HA native form. This method also retries a few times in the case of stale connections. @@ -62,17 +62,25 @@ def execute(qry): for tryno in range(0, RETRIES): try: timer_start = time.perf_counter() - result = [ - row for row in (row.to_native() for row in qry) if row is not None - ] + if to_native: + result = [ + row for row in (row.to_native() for row in qry) if row is not None + ] + else: + result = list(qry) if _LOGGER.isEnabledFor(logging.DEBUG): elapsed = time.perf_counter() - timer_start - _LOGGER.debug( - "converting %d rows to native objects took %fs", - len(result), - elapsed, - ) + if to_native: + _LOGGER.debug( + "converting %d rows to native objects took %fs", + len(result), + elapsed, + ) + else: + _LOGGER.debug( + "querying %d rows took %fs", len(result), elapsed, + ) return result except SQLAlchemyError as err: diff --git a/tests/components/history/test_init.py b/tests/components/history/test_init.py index 16af2c64271..ba0e0b9f1c0 100644 --- a/tests/components/history/test_init.py +++ b/tests/components/history/test_init.py @@ -1,10 +1,13 @@ """The tests the History component.""" # pylint: disable=protected-access,invalid-name from datetime import timedelta +import json import unittest from homeassistant.components import history, recorder +from homeassistant.components.recorder.models import process_timestamp import homeassistant.core as ha +from homeassistant.helpers.json import JSONEncoder from homeassistant.setup import async_setup_component, setup_component import homeassistant.util.dt as dt_util @@ -197,6 +200,41 @@ class TestComponentHistory(unittest.TestCase): ) assert states == hist + def test_get_significant_states_minimal_response(self): + """Test that only significant states are returned. + + When minimal responses is set only the first and + last states return a complete state. + + We should get back every thermostat change that + includes an attribute change, but only the state updates for + media player (attribute changes are not significant and not returned). + """ + zero, four, states = self.record_states() + hist = history.get_significant_states( + self.hass, zero, four, filters=history.Filters(), minimal_response=True + ) + + # The second media_player.test state is reduced + # down to last_changed and state when minimal_response + # is set. We use JSONEncoder to make sure that are + # pre-encoded last_changed is always the same as what + # will happen with encoding a native state + input_state = states["media_player.test"][1] + orig_last_changed = json.dumps( + process_timestamp(input_state.last_changed.replace(microsecond=0)), + cls=JSONEncoder, + ).replace('"', "") + if orig_last_changed.endswith("+00:00"): + orig_last_changed = f"{orig_last_changed[:-6]}{recorder.models.DB_TIMEZONE}" + orig_state = input_state.state + states["media_player.test"][1] = { + "last_changed": orig_last_changed, + "state": orig_state, + } + + assert states == hist + def test_get_significant_states_with_initial(self): """Test that only significant states are returned. @@ -252,6 +290,7 @@ class TestComponentHistory(unittest.TestCase): """Test that only significant states are returned for one entity.""" zero, four, states = self.record_states() del states["media_player.test2"] + del states["media_player.test3"] del states["thermostat.test"] del states["thermostat.test2"] del states["script.can_cancel_this_one"] @@ -265,6 +304,7 @@ class TestComponentHistory(unittest.TestCase): """Test that only significant states are returned for one entity.""" zero, four, states = self.record_states() del states["media_player.test2"] + del states["media_player.test3"] del states["thermostat.test2"] del states["script.can_cancel_this_one"] @@ -286,6 +326,7 @@ class TestComponentHistory(unittest.TestCase): zero, four, states = self.record_states() del states["media_player.test"] del states["media_player.test2"] + del states["media_player.test3"] config = history.CONFIG_SCHEMA( { @@ -346,6 +387,7 @@ class TestComponentHistory(unittest.TestCase): """ zero, four, states = self.record_states() del states["media_player.test2"] + del states["media_player.test3"] del states["thermostat.test"] del states["thermostat.test2"] del states["script.can_cancel_this_one"] @@ -372,6 +414,7 @@ class TestComponentHistory(unittest.TestCase): zero, four, states = self.record_states() del states["media_player.test"] del states["media_player.test2"] + del states["media_player.test3"] config = history.CONFIG_SCHEMA( { @@ -392,6 +435,7 @@ class TestComponentHistory(unittest.TestCase): """ zero, four, states = self.record_states() del states["media_player.test2"] + del states["media_player.test3"] del states["thermostat.test"] del states["thermostat.test2"] del states["script.can_cancel_this_one"] @@ -414,6 +458,7 @@ class TestComponentHistory(unittest.TestCase): """ zero, four, states = self.record_states() del states["media_player.test2"] + del states["media_player.test3"] del states["script.can_cancel_this_one"] config = history.CONFIG_SCHEMA( @@ -438,6 +483,7 @@ class TestComponentHistory(unittest.TestCase): zero, four, states = self.record_states() del states["media_player.test"] del states["media_player.test2"] + del states["media_player.test3"] del states["thermostat.test"] del states["thermostat.test2"] del states["script.can_cancel_this_one"] @@ -462,6 +508,7 @@ class TestComponentHistory(unittest.TestCase): zero, four, states = self.record_states() del states["media_player.test"] del states["media_player.test2"] + del states["media_player.test3"] del states["thermostat.test"] del states["thermostat.test2"] del states["script.can_cancel_this_one"] @@ -607,6 +654,7 @@ class TestComponentHistory(unittest.TestCase): self.init_recorder() mp = "media_player.test" mp2 = "media_player.test2" + mp3 = "media_player.test3" therm = "thermostat.test" therm2 = "thermostat.test2" zone = "zone.home" @@ -625,7 +673,7 @@ class TestComponentHistory(unittest.TestCase): three = two + timedelta(seconds=1) four = three + timedelta(seconds=1) - states = {therm: [], therm2: [], mp: [], mp2: [], script_c: []} + states = {therm: [], therm2: [], mp: [], mp2: [], mp3: [], script_c: []} with patch( "homeassistant.components.recorder.dt_util.utcnow", return_value=one ): @@ -638,6 +686,9 @@ class TestComponentHistory(unittest.TestCase): states[mp2].append( set_state(mp2, "YouTube", attributes={"media_title": str(sentinel.mt2)}) ) + states[mp3].append( + set_state(mp3, "idle", attributes={"media_title": str(sentinel.mt1)}) + ) states[therm].append( set_state(therm, 20, attributes={"current_temperature": 19.5}) ) @@ -647,6 +698,12 @@ class TestComponentHistory(unittest.TestCase): ): # This state will be skipped only different in time set_state(mp, "YouTube", attributes={"media_title": str(sentinel.mt3)}) + # This state will be skipped as it hidden + set_state( + mp3, + "Apple TV", + attributes={"media_title": str(sentinel.mt2), "hidden": True}, + ) # This state will be skipped because domain blacklisted set_state(zone, "zoning") set_state(script_nc, "off") @@ -666,6 +723,9 @@ class TestComponentHistory(unittest.TestCase): states[mp].append( set_state(mp, "Netflix", attributes={"media_title": str(sentinel.mt4)}) ) + states[mp3].append( + set_state(mp3, "Netflix", attributes={"media_title": str(sentinel.mt3)}) + ) # Attributes changed even though state is the same states[therm].append( set_state(therm, 21, attributes={"current_temperature": 20}) @@ -686,6 +746,30 @@ async def test_fetch_period_api(hass, hass_client): assert response.status == 200 +async def test_fetch_period_api_with_use_include_order(hass, hass_client): + """Test the fetch period view for history with include order.""" + await hass.async_add_executor_job(init_recorder_component, hass) + await async_setup_component( + hass, "history", {history.DOMAIN: {history.CONF_ORDER: True}} + ) + await hass.async_add_job(hass.data[recorder.DATA_INSTANCE].block_till_done) + client = await hass_client() + response = await client.get(f"/api/history/period/{dt_util.utcnow().isoformat()}") + assert response.status == 200 + + +async def test_fetch_period_api_with_minimal_response(hass, hass_client): + """Test the fetch period view for history with minimal_response.""" + await hass.async_add_executor_job(init_recorder_component, hass) + await async_setup_component(hass, "history", {}) + await hass.async_add_job(hass.data[recorder.DATA_INSTANCE].block_till_done) + client = await hass_client() + response = await client.get( + f"/api/history/period/{dt_util.utcnow().isoformat()}?minimal_response" + ) + assert response.status == 200 + + async def test_fetch_period_api_with_include_order(hass, hass_client): """Test the fetch period view for history.""" await hass.async_add_executor_job(init_recorder_component, hass) From cb2821b51219cfe8be0014bad0c0524dd8990b5b Mon Sep 17 00:00:00 2001 From: Jason Hunter Date: Wed, 27 May 2020 00:16:15 -0400 Subject: [PATCH 215/406] Fix empty preset element in ONVIF response (#36182) --- homeassistant/components/onvif/device.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/onvif/device.py b/homeassistant/components/onvif/device.py index 0e9d3ddca98..bbce71f5d28 100644 --- a/homeassistant/components/onvif/device.py +++ b/homeassistant/components/onvif/device.py @@ -297,7 +297,7 @@ class ONVIFDevice: try: ptz_service = self.device.create_ptz_service() presets = await ptz_service.GetPresets(profile.token) - profile.ptz.presets = [preset.token for preset in presets] + profile.ptz.presets = [preset.token for preset in presets if preset] except (Fault, ServerDisconnectedError): # It's OK if Presets aren't supported profile.ptz.presets = [] From cfaa851b5b8ace3d14262c2e8c57a237d5f9d3ec Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 26 May 2020 22:11:40 -0700 Subject: [PATCH 216/406] Revert DSMR not calling entity methods (#36179) --- homeassistant/components/dsmr/sensor.py | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/dsmr/sensor.py b/homeassistant/components/dsmr/sensor.py index 484bd708489..4d780d48cd1 100644 --- a/homeassistant/components/dsmr/sensor.py +++ b/homeassistant/components/dsmr/sensor.py @@ -17,10 +17,6 @@ from homeassistant.const import ( ) from homeassistant.core import CoreState, callback from homeassistant.helpers import config_validation as cv -from homeassistant.helpers.dispatcher import ( - async_dispatcher_connect, - async_dispatcher_send, -) from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -113,7 +109,11 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities(devices) - update_entities_telegram = partial(async_dispatcher_send, hass, DOMAIN) + def update_entities_telegram(telegram): + """Update entities with latest telegram and trigger state update.""" + # Make all device entities aware of new telegram + for device in devices: + device.update_data(telegram) # Creates an asyncio.Protocol factory for reading DSMR telegrams from # serial and calls update_entities_telegram to update entities on arrival @@ -187,17 +187,12 @@ class DSMREntity(Entity): self._config = config self.telegram = {} - async def async_added_to_hass(self): - """When entity is added to hass.""" - self.async_on_remove( - async_dispatcher_connect(self.hass, DOMAIN, self.update_data) - ) - @callback def update_data(self, telegram): """Update data.""" self.telegram = telegram - self.async_write_ha_state() + if self.hass: + self.async_write_ha_state() def get_dsmr_object_attr(self, attribute): """Read attribute from last received telegram for this DSMR object.""" From 879e2d1afd332d78711bcfff100ca9f0ee46fff5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 27 May 2020 00:15:00 -0500 Subject: [PATCH 217/406] Improve stability of homekit accessory ids (#35691) --- .../components/homekit/aidmanager.py | 28 +- tests/components/homekit/test_aidmanager.py | 868 +++++++++--------- tests/components/homekit/test_homekit.py | 6 +- 3 files changed, 442 insertions(+), 460 deletions(-) diff --git a/homeassistant/components/homekit/aidmanager.py b/homeassistant/components/homekit/aidmanager.py index 487865f22ab..31c341c9144 100644 --- a/homeassistant/components/homekit/aidmanager.py +++ b/homeassistant/components/homekit/aidmanager.py @@ -11,7 +11,6 @@ This module generates and stores them in a HA storage. """ import logging import random -from zlib import adler32 from fnvhash import fnv1a_32 @@ -44,11 +43,6 @@ def get_system_unique_id(entity: RegistryEntry): def _generate_aids(unique_id: str, entity_id: str) -> int: """Generate accessory aid.""" - # Backward compatibility: Previously HA used to *only* do adler32 on the entity id. - # Not stable if entity ID changes - # Not robust against collisions - yield adler32(entity_id.encode("utf-8")) - if unique_id: # Use fnv1a_32 of the unique id as # fnv1a_32 has less collisions than @@ -96,15 +90,7 @@ class AccessoryAidStorage: # There is no data about aid allocations yet return - # 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.allocations = raw_storage.get(ALLOCATIONS_KEY, {}) self.allocated_aids = set(self.allocations.values()) def get_or_allocate_aid_for_entity_id(self, entity_id: str): @@ -118,17 +104,17 @@ class AccessoryAidStorage: def _get_or_allocate_aid(self, unique_id: str, entity_id: str): """Allocate (and return) a new aid for an accessory.""" - # 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] + if unique_id and unique_id in self.allocations: + return self.allocations[unique_id] + if entity_id in self.allocations: + return self.allocations[entity_id] for aid in _generate_aids(unique_id, entity_id): if aid in INVALID_AIDS: continue if aid not in self.allocated_aids: + # Prefer the unique_id over the entitiy_id + storage_key = unique_id or entity_id self.allocations[storage_key] = aid self.allocated_aids.add(aid) self.async_schedule_save() diff --git a/tests/components/homekit/test_aidmanager.py b/tests/components/homekit/test_aidmanager.py index ff55ba9afa4..b7c12e86443 100644 --- a/tests/components/homekit/test_aidmanager.py +++ b/tests/components/homekit/test_aidmanager.py @@ -1,7 +1,7 @@ """Tests for the HomeKit AID manager.""" import os -from zlib import adler32 +from fnvhash import fnv1a_32 import pytest from homeassistant.components.homekit.aidmanager import ( @@ -59,19 +59,19 @@ async def test_aid_generation(hass, device_reg, entity_reg): for _ in range(0, 2): assert ( aid_storage.get_or_allocate_aid_for_entity_id(light_ent.entity_id) - == 1692141785 + == 1953095294 ) assert ( aid_storage.get_or_allocate_aid_for_entity_id(light_ent2.entity_id) - == 2732133210 + == 1975378727 ) assert ( aid_storage.get_or_allocate_aid_for_entity_id(remote_ent.entity_id) - == 1867188557 + == 3508011530 ) assert ( aid_storage.get_or_allocate_aid_for_entity_id("remote.has_no_unique_id") - == 1872038229 + == 1751603975 ) aid_storage.delete_aid(get_system_unique_id(light_ent)) @@ -82,23 +82,23 @@ async def test_aid_generation(hass, device_reg, entity_reg): for _ in range(0, 2): assert ( aid_storage.get_or_allocate_aid_for_entity_id(light_ent.entity_id) - == 1692141785 + == 1953095294 ) assert ( aid_storage.get_or_allocate_aid_for_entity_id(light_ent2.entity_id) - == 2732133210 + == 1975378727 ) assert ( aid_storage.get_or_allocate_aid_for_entity_id(remote_ent.entity_id) - == 1867188557 + == 3508011530 ) assert ( aid_storage.get_or_allocate_aid_for_entity_id("remote.has_no_unique_id") - == 1872038229 + == 1751603975 ) -async def test_aid_adler32_collision(hass, device_reg, entity_reg): +async def test_no_aid_collision(hass, device_reg, entity_reg): """Test generating aids.""" config_entry = MockConfigEntry(domain="test", data={}) config_entry.add_to_hass(hass) @@ -130,16 +130,22 @@ async def test_aid_generation_no_unique_ids_handles_collision( ): """Test colliding aids is stable.""" config_entry = MockConfigEntry(domain="test", data={}) + config_entry.add_to_hass(hass) aid_storage = AccessoryAidStorage(hass, config_entry) await aid_storage.async_initialize() + device_entry = device_reg.async_get_or_create( + config_entry_id=config_entry.entry_id, + connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, + ) + 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")) + expected_aid = fnv1a_32(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) @@ -147,143 +153,131 @@ async def test_aid_generation_no_unique_ids_handles_collision( 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", - ] + light_ent = entity_reg.async_get_or_create( + "light", "device", "unique_id", device_id=device_entry.id + ) + hass.states.async_set(light_ent.entity_id, "on") + aid_storage.get_or_allocate_aid_for_entity_id(light_ent.entity_id) + + assert not collisions 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, + "device.light.unique_id": 1953095294, + "light.light0": 301577847, + "light.light1": 284800228, + "light.light10": 2367138236, + "light.light100": 2822760292, + "light.light101": 2839537911, + "light.light102": 2856315530, + "light.light103": 2873093149, + "light.light104": 2755649816, + "light.light105": 2772427435, + "light.light106": 2789205054, + "light.light107": 2805982673, + "light.light108": 2688539340, + "light.light109": 2705316959, + "light.light11": 2383915855, + "light.light110": 776141037, + "light.light111": 759363418, + "light.light112": 742585799, + "light.light113": 725808180, + "light.light114": 709030561, + "light.light115": 692252942, + "light.light116": 675475323, + "light.light117": 658697704, + "light.light118": 641920085, + "light.light119": 625142466, + "light.light12": 2400693474, + "light.light120": 340070038, + "light.light121": 356847657, + "light.light122": 306514800, + "light.light123": 323292419, + "light.light124": 407180514, + "light.light125": 423958133, + "light.light126": 373625276, + "light.light127": 390402895, + "light.light128": 474290990, + "light.light129": 491068609, + "light.light13": 2417471093, + "light.light130": 440882847, + "light.light131": 424105228, + "light.light132": 474438085, + "light.light133": 457660466, + "light.light134": 373772371, + "light.light135": 356994752, + "light.light136": 407327609, + "light.light137": 390549990, + "light.light138": 575103799, + "light.light139": 558326180, + "light.light14": 2300027760, + "light.light140": 271973824, + "light.light141": 288751443, + "light.light142": 305529062, + "light.light143": 322306681, + "light.light144": 339084300, + "light.light145": 355861919, + "light.light146": 372639538, + "light.light147": 389417157, + "light.light148": 406194776, + "light.light149": 422972395, + "light.light15": 2316805379, + "light.light150": 2520321865, + "light.light151": 2503544246, + "light.light152": 2486766627, + "light.light153": 2469989008, + "light.light154": 2587432341, + "light.light155": 2570654722, + "light.light156": 2553877103, + "light.light157": 2537099484, + "light.light158": 2654542817, + "light.light159": 2637765198, + "light.light16": 2333582998, + "light.light160": 2621134674, + "light.light161": 2637912293, + "light.light162": 2587579436, + "light.light163": 2604357055, + "light.light164": 2554024198, + "light.light165": 2570801817, + "light.light166": 2520468960, + "light.light167": 2537246579, + "light.light168": 2755355626, + "light.light169": 2772133245, + "light.light17": 2350360617, + "light.light170": 2721947483, + "light.light171": 2705169864, + "light.light172": 2755502721, + "light.light173": 2738725102, + "light.light174": 2789057959, + "light.light175": 2772280340, + "light.light176": 2822613197, + "light.light177": 2805835578, + "light.light178": 2587726531, + "light.light179": 2570948912, + "light.light18": 2501359188, + "light.light180": 408166252, + "light.light181": 424943871, + "light.light182": 441721490, + "light.light183": 458499109, + "light.light184": 341055776, + "light.light185": 357833395, + "light.light186": 374611014, + "light.light187": 391388633, + "light.light188": 542387204, + "light.light189": 559164823, + "light.light19": 2518136807, + "light.light190": 508979061, + "light.light191": 492201442, + "light.light192": 475423823, + "light.light193": 458646204, + "light.light194": 441868585, + "light.light195": 425090966, + "light.light196": 408313347, + "light.light197": 391535728, + "light.light198": 643200013, + "light.light199": 626422394, + "light.light2": 335133085, + "light.light20": 522144599, + "light.light200": 1698935589, "light.light201": 1682157970, "light.light202": 1665380351, "light.light203": 1648602732, @@ -293,8 +287,8 @@ async def test_aid_generation_no_unique_ids_handles_collision( "light.light207": 1581492256, "light.light208": 1833156541, "light.light209": 1816378922, - "light.light21": 594805954, - "light.light210": 677774578, + "light.light21": 505366980, + "light.light210": 1598122780, "light.light211": 1614900399, "light.light212": 1631678018, "light.light213": 1648455637, @@ -304,215 +298,217 @@ async def test_aid_generation_no_unique_ids_handles_collision( "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, + "light.light22": 555699837, + "light.light23": 538922218, + "light.light24": 455034123, + "light.light25": 438256504, + "light.light26": 488589361, + "light.light27": 471811742, + "light.light28": 387923647, + "light.light29": 371146028, + "light.light3": 318355466, + "light.light30": 421331790, + "light.light31": 438109409, + "light.light32": 387776552, + "light.light33": 404554171, + "light.light34": 488442266, + "light.light35": 505219885, + "light.light36": 454887028, + "light.light37": 471664647, + "light.light38": 287110838, + "light.light39": 303888457, + "light.light4": 234467371, + "light.light40": 454048385, + "light.light41": 437270766, + "light.light42": 420493147, + "light.light43": 403715528, + "light.light44": 521158861, + "light.light45": 504381242, + "light.light46": 487603623, + "light.light47": 470826004, + "light.light48": 319827433, + "light.light49": 303049814, + "light.light5": 217689752, + "light.light50": 353235576, + "light.light51": 370013195, + "light.light52": 386790814, + "light.light53": 403568433, + "light.light54": 420346052, + "light.light55": 437123671, + "light.light56": 453901290, + "light.light57": 470678909, + "light.light58": 219014624, + "light.light59": 235792243, + "light.light6": 268022609, + "light.light60": 2266325427, + "light.light61": 2249547808, + "light.light62": 2299880665, + "light.light63": 2283103046, + "light.light64": 2333435903, + "light.light65": 2316658284, + "light.light66": 2366991141, + "light.light67": 2350213522, + "light.light68": 2400546379, + "light.light69": 2383768760, + "light.light7": 251244990, + "light.light70": 554861194, + "light.light71": 571638813, + "light.light72": 521305956, + "light.light73": 538083575, + "light.light74": 487750718, + "light.light75": 504528337, + "light.light76": 454195480, + "light.light77": 470973099, + "light.light78": 420640242, + "light.light79": 437417861, + "light.light8": 167356895, + "light.light80": 2735113021, + "light.light81": 2718335402, + "light.light82": 2701557783, + "light.light83": 2684780164, + "light.light84": 2668002545, + "light.light85": 2651224926, + "light.light86": 2634447307, + "light.light87": 2617669688, + "light.light88": 2600892069, + "light.light89": 2584114450, + "light.light9": 150579276, + "light.light90": 2634300212, + "light.light91": 2651077831, + "light.light92": 2667855450, + "light.light93": 2684633069, + "light.light94": 2567189736, + "light.light95": 2583967355, + "light.light96": 2600744974, + "light.light97": 2617522593, + "light.light98": 2500079260, + "light.light99": 2516856879, } await aid_storage.async_save() await hass.async_block_till_done() - aid_storage = AccessoryAidStorage(hass, config_entry) + with patch("fnvhash.fnv1a_32", side_effect=Exception): + aid_storage = AccessoryAidStorage(hass, config_entry) 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, + "device.light.unique_id": 1953095294, + "light.light0": 301577847, + "light.light1": 284800228, + "light.light10": 2367138236, + "light.light100": 2822760292, + "light.light101": 2839537911, + "light.light102": 2856315530, + "light.light103": 2873093149, + "light.light104": 2755649816, + "light.light105": 2772427435, + "light.light106": 2789205054, + "light.light107": 2805982673, + "light.light108": 2688539340, + "light.light109": 2705316959, + "light.light11": 2383915855, + "light.light110": 776141037, + "light.light111": 759363418, + "light.light112": 742585799, + "light.light113": 725808180, + "light.light114": 709030561, + "light.light115": 692252942, + "light.light116": 675475323, + "light.light117": 658697704, + "light.light118": 641920085, + "light.light119": 625142466, + "light.light12": 2400693474, + "light.light120": 340070038, + "light.light121": 356847657, + "light.light122": 306514800, + "light.light123": 323292419, + "light.light124": 407180514, + "light.light125": 423958133, + "light.light126": 373625276, + "light.light127": 390402895, + "light.light128": 474290990, + "light.light129": 491068609, + "light.light13": 2417471093, + "light.light130": 440882847, + "light.light131": 424105228, + "light.light132": 474438085, + "light.light133": 457660466, + "light.light134": 373772371, + "light.light135": 356994752, + "light.light136": 407327609, + "light.light137": 390549990, + "light.light138": 575103799, + "light.light139": 558326180, + "light.light14": 2300027760, + "light.light140": 271973824, + "light.light141": 288751443, + "light.light142": 305529062, + "light.light143": 322306681, + "light.light144": 339084300, + "light.light145": 355861919, + "light.light146": 372639538, + "light.light147": 389417157, + "light.light148": 406194776, + "light.light149": 422972395, + "light.light15": 2316805379, + "light.light150": 2520321865, + "light.light151": 2503544246, + "light.light152": 2486766627, + "light.light153": 2469989008, + "light.light154": 2587432341, + "light.light155": 2570654722, + "light.light156": 2553877103, + "light.light157": 2537099484, + "light.light158": 2654542817, + "light.light159": 2637765198, + "light.light16": 2333582998, + "light.light160": 2621134674, + "light.light161": 2637912293, + "light.light162": 2587579436, + "light.light163": 2604357055, + "light.light164": 2554024198, + "light.light165": 2570801817, + "light.light166": 2520468960, + "light.light167": 2537246579, + "light.light168": 2755355626, + "light.light169": 2772133245, + "light.light17": 2350360617, + "light.light170": 2721947483, + "light.light171": 2705169864, + "light.light172": 2755502721, + "light.light173": 2738725102, + "light.light174": 2789057959, + "light.light175": 2772280340, + "light.light176": 2822613197, + "light.light177": 2805835578, + "light.light178": 2587726531, + "light.light179": 2570948912, + "light.light18": 2501359188, + "light.light180": 408166252, + "light.light181": 424943871, + "light.light182": 441721490, + "light.light183": 458499109, + "light.light184": 341055776, + "light.light185": 357833395, + "light.light186": 374611014, + "light.light187": 391388633, + "light.light188": 542387204, + "light.light189": 559164823, + "light.light19": 2518136807, + "light.light190": 508979061, + "light.light191": 492201442, + "light.light192": 475423823, + "light.light193": 458646204, + "light.light194": 441868585, + "light.light195": 425090966, + "light.light196": 408313347, + "light.light197": 391535728, + "light.light198": 643200013, + "light.light199": 626422394, + "light.light2": 335133085, + "light.light20": 522144599, + "light.light200": 1698935589, "light.light201": 1682157970, "light.light202": 1665380351, "light.light203": 1648602732, @@ -522,8 +518,8 @@ async def test_aid_generation_no_unique_ids_handles_collision( "light.light207": 1581492256, "light.light208": 1833156541, "light.light209": 1816378922, - "light.light21": 594805954, - "light.light210": 677774578, + "light.light21": 505366980, + "light.light210": 1598122780, "light.light211": 1614900399, "light.light212": 1631678018, "light.light213": 1648455637, @@ -533,91 +529,91 @@ async def test_aid_generation_no_unique_ids_handles_collision( "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, + "light.light22": 555699837, + "light.light23": 538922218, + "light.light24": 455034123, + "light.light25": 438256504, + "light.light26": 488589361, + "light.light27": 471811742, + "light.light28": 387923647, + "light.light29": 371146028, + "light.light3": 318355466, + "light.light30": 421331790, + "light.light31": 438109409, + "light.light32": 387776552, + "light.light33": 404554171, + "light.light34": 488442266, + "light.light35": 505219885, + "light.light36": 454887028, + "light.light37": 471664647, + "light.light38": 287110838, + "light.light39": 303888457, + "light.light4": 234467371, + "light.light40": 454048385, + "light.light41": 437270766, + "light.light42": 420493147, + "light.light43": 403715528, + "light.light44": 521158861, + "light.light45": 504381242, + "light.light46": 487603623, + "light.light47": 470826004, + "light.light48": 319827433, + "light.light49": 303049814, + "light.light5": 217689752, + "light.light50": 353235576, + "light.light51": 370013195, + "light.light52": 386790814, + "light.light53": 403568433, + "light.light54": 420346052, + "light.light55": 437123671, + "light.light56": 453901290, + "light.light57": 470678909, + "light.light58": 219014624, + "light.light59": 235792243, + "light.light6": 268022609, + "light.light60": 2266325427, + "light.light61": 2249547808, + "light.light62": 2299880665, + "light.light63": 2283103046, + "light.light64": 2333435903, + "light.light65": 2316658284, + "light.light66": 2366991141, + "light.light67": 2350213522, + "light.light68": 2400546379, + "light.light69": 2383768760, + "light.light7": 251244990, + "light.light70": 554861194, + "light.light71": 571638813, + "light.light72": 521305956, + "light.light73": 538083575, + "light.light74": 487750718, + "light.light75": 504528337, + "light.light76": 454195480, + "light.light77": 470973099, + "light.light78": 420640242, + "light.light79": 437417861, + "light.light8": 167356895, + "light.light80": 2735113021, + "light.light81": 2718335402, + "light.light82": 2701557783, + "light.light83": 2684780164, + "light.light84": 2668002545, + "light.light85": 2651224926, + "light.light86": 2634447307, + "light.light87": 2617669688, + "light.light88": 2600892069, + "light.light89": 2584114450, + "light.light9": 150579276, + "light.light90": 2634300212, + "light.light91": 2651077831, + "light.light92": 2667855450, + "light.light93": 2684633069, + "light.light94": 2567189736, + "light.light95": 2583967355, + "light.light96": 2600744974, + "light.light97": 2617522593, + "light.light98": 2500079260, + "light.light99": 2516856879, } aidstore = get_aid_storage_filename_for_entry_id(config_entry.entry_id) diff --git a/tests/components/homekit/test_homekit.py b/tests/components/homekit/test_homekit.py index 5e756ae51b5..dbca6135c89 100644 --- a/tests/components/homekit/test_homekit.py +++ b/tests/components/homekit/test_homekit.py @@ -390,15 +390,15 @@ async def test_homekit_add_accessory(hass): with patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc: mock_get_acc.side_effect = [None, "acc", None] homekit.add_bridge_accessory(State("light.demo", "on")) - mock_get_acc.assert_called_with(hass, "driver", ANY, 363398124, {}) + mock_get_acc.assert_called_with(hass, "driver", ANY, 1403373688, {}) assert not mock_bridge.add_accessory.called homekit.add_bridge_accessory(State("demo.test", "on")) - mock_get_acc.assert_called_with(hass, "driver", ANY, 294192020, {}) + mock_get_acc.assert_called_with(hass, "driver", ANY, 600325356, {}) assert mock_bridge.add_accessory.called homekit.add_bridge_accessory(State("demo.test_2", "on")) - mock_get_acc.assert_called_with(hass, "driver", ANY, 429982757, {}) + mock_get_acc.assert_called_with(hass, "driver", ANY, 1467253281, {}) mock_bridge.add_accessory.assert_called_with("acc") From ad6e21182ecc3f6ae883fe826fa339e5188ae9e1 Mon Sep 17 00:00:00 2001 From: Joakim Plate Date: Wed, 27 May 2020 08:16:21 +0200 Subject: [PATCH 218/406] Switch default media_player device class to settop for google assistant (#36003) --- homeassistant/components/google_assistant/const.py | 3 ++- tests/components/google_assistant/__init__.py | 8 ++++---- tests/components/google_assistant/test_smart_home.py | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/google_assistant/const.py b/homeassistant/components/google_assistant/const.py index 9a133b5e6b7..47122979173 100644 --- a/homeassistant/components/google_assistant/const.py +++ b/homeassistant/components/google_assistant/const.py @@ -73,6 +73,7 @@ TYPE_DOOR = f"{PREFIX_TYPES}DOOR" TYPE_TV = f"{PREFIX_TYPES}TV" TYPE_SPEAKER = f"{PREFIX_TYPES}SPEAKER" TYPE_ALARM = f"{PREFIX_TYPES}SECURITYSYSTEM" +TYPE_SETTOP = f"{PREFIX_TYPES}SETTOP" SERVICE_REQUEST_SYNC = "request_sync" HOMEGRAPH_URL = "https://homegraph.googleapis.com/" @@ -114,7 +115,7 @@ DOMAIN_TO_GOOGLE_TYPES = { input_boolean.DOMAIN: TYPE_SWITCH, light.DOMAIN: TYPE_LIGHT, lock.DOMAIN: TYPE_LOCK, - media_player.DOMAIN: TYPE_SWITCH, + media_player.DOMAIN: TYPE_SETTOP, scene.DOMAIN: TYPE_SCENE, script.DOMAIN: TYPE_SCENE, switch.DOMAIN: TYPE_SWITCH, diff --git a/tests/components/google_assistant/__init__.py b/tests/components/google_assistant/__init__.py index 8f6b0908f83..cd239f14b27 100644 --- a/tests/components/google_assistant/__init__.py +++ b/tests/components/google_assistant/__init__.py @@ -168,7 +168,7 @@ DEMO_DEVICES = [ "action.devices.traits.Volume", "action.devices.traits.Modes", ], - "type": "action.devices.types.SWITCH", + "type": "action.devices.types.SETTOP", "willReportState": False, }, { @@ -179,14 +179,14 @@ DEMO_DEVICES = [ "action.devices.traits.Volume", "action.devices.traits.Modes", ], - "type": "action.devices.types.SWITCH", + "type": "action.devices.types.SETTOP", "willReportState": False, }, { "id": "media_player.lounge_room", "name": {"name": "Lounge room"}, "traits": ["action.devices.traits.OnOff", "action.devices.traits.Modes"], - "type": "action.devices.types.SWITCH", + "type": "action.devices.types.SETTOP", "willReportState": False, }, { @@ -197,7 +197,7 @@ DEMO_DEVICES = [ "action.devices.traits.Volume", "action.devices.traits.Modes", ], - "type": "action.devices.types.SWITCH", + "type": "action.devices.types.SETTOP", "willReportState": False, }, { diff --git a/tests/components/google_assistant/test_smart_home.py b/tests/components/google_assistant/test_smart_home.py index e619356717f..c9fc159bdf5 100644 --- a/tests/components/google_assistant/test_smart_home.py +++ b/tests/components/google_assistant/test_smart_home.py @@ -743,7 +743,7 @@ async def test_device_class_cover(hass, device_class, google_type): @pytest.mark.parametrize( "device_class,google_type", [ - ("non_existing_class", "action.devices.types.SWITCH"), + ("non_existing_class", "action.devices.types.SETTOP"), ("tv", "action.devices.types.TV"), ], ) From a55c6c5f47a5fe84ee4ec959d83881cb1660afd7 Mon Sep 17 00:00:00 2001 From: Nacho Barrientos Date: Wed, 27 May 2020 09:59:40 +0200 Subject: [PATCH 219/406] Make prometheus light state report robust (#36134) --- homeassistant/components/prometheus/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/prometheus/__init__.py b/homeassistant/components/prometheus/__init__.py index 314695458b0..c09c20ec3b0 100644 --- a/homeassistant/components/prometheus/__init__.py +++ b/homeassistant/components/prometheus/__init__.py @@ -15,6 +15,7 @@ from homeassistant.const import ( ATTR_UNIT_OF_MEASUREMENT, CONTENT_TYPE_TEXT_PLAIN, EVENT_STATE_CHANGED, + STATE_ON, TEMP_CELSIUS, TEMP_FAHRENHEIT, UNIT_PERCENTAGE, @@ -249,7 +250,7 @@ class PrometheusMetrics: ) try: - if "brightness" in state.attributes: + if "brightness" in state.attributes and state.state == STATE_ON: value = state.attributes["brightness"] / 255.0 else: value = self.state_as_number(state) From c7e97f0cf85fd86ea6da0cfac301b3a4842d31a5 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 27 May 2020 13:31:26 +0200 Subject: [PATCH 220/406] Bump zeroconf, pychromecast. Log if zeroconf.get_service_info fails. (#36185) --- homeassistant/components/cast/manifest.json | 2 +- homeassistant/components/zeroconf/__init__.py | 1 + homeassistant/components/zeroconf/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 4 ++-- requirements_test_all.txt | 4 ++-- 6 files changed, 8 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/cast/manifest.json b/homeassistant/components/cast/manifest.json index baeeef6de65..0be595de549 100644 --- a/homeassistant/components/cast/manifest.json +++ b/homeassistant/components/cast/manifest.json @@ -3,7 +3,7 @@ "name": "Google Cast", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/cast", - "requirements": ["pychromecast==5.2.0"], + "requirements": ["pychromecast==5.3.0"], "after_dependencies": ["cloud","zeroconf"], "zeroconf": ["_googlecast._tcp.local."], "codeowners": ["@emontnemery"] diff --git a/homeassistant/components/zeroconf/__init__.py b/homeassistant/components/zeroconf/__init__.py index 5b5275f1d1b..436b38fd704 100644 --- a/homeassistant/components/zeroconf/__init__.py +++ b/homeassistant/components/zeroconf/__init__.py @@ -192,6 +192,7 @@ def setup(hass, config): if not service_info: # Prevent the browser thread from collapsing as # service_info can be None + _LOGGER.debug("Failed to get info for device %s", name) return info = info_from_service(service_info) diff --git a/homeassistant/components/zeroconf/manifest.json b/homeassistant/components/zeroconf/manifest.json index 9d4398f3b56..e28594d5598 100644 --- a/homeassistant/components/zeroconf/manifest.json +++ b/homeassistant/components/zeroconf/manifest.json @@ -2,7 +2,7 @@ "domain": "zeroconf", "name": "Zero-configuration networking (zeroconf)", "documentation": "https://www.home-assistant.io/integrations/zeroconf", - "requirements": ["zeroconf==0.26.2"], + "requirements": ["zeroconf==0.26.3"], "dependencies": ["api"], "codeowners": ["@robbiet480", "@Kane610"], "quality_scale": "internal" diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 278f41b1f45..637bddb2d9e 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -25,7 +25,7 @@ ruamel.yaml==0.15.100 sqlalchemy==1.3.17 voluptuous-serialize==2.3.0 voluptuous==0.11.7 -zeroconf==0.26.2 +zeroconf==0.26.3 pycryptodome>=3.6.6 diff --git a/requirements_all.txt b/requirements_all.txt index 9b24388a815..f3748a3acc9 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1248,7 +1248,7 @@ pycfdns==0.0.1 pychannels==1.0.0 # homeassistant.components.cast -pychromecast==5.2.0 +pychromecast==5.3.0 # homeassistant.components.cmus pycmus==0.1.1 @@ -2242,7 +2242,7 @@ youtube_dl==2020.05.08 zengge==0.2 # homeassistant.components.zeroconf -zeroconf==0.26.2 +zeroconf==0.26.3 # homeassistant.components.zha zha-quirks==0.0.39 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 88666ed685f..559a6653c5d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -533,7 +533,7 @@ pyblackbird==0.5 pybotvac==0.0.17 # homeassistant.components.cast -pychromecast==5.2.0 +pychromecast==5.3.0 # homeassistant.components.coolmaster pycoolmasternet==0.0.4 @@ -906,7 +906,7 @@ xmltodict==0.12.0 ya_ma==0.3.8 # homeassistant.components.zeroconf -zeroconf==0.26.2 +zeroconf==0.26.3 # homeassistant.components.zha zha-quirks==0.0.39 From 6f4829c390cf38ed6c727df1c2267ea4cc353af4 Mon Sep 17 00:00:00 2001 From: David Zhu Date: Wed, 27 May 2020 21:51:39 +0800 Subject: [PATCH 221/406] Add webostv payload option to command service (#36164) * added optional argument to command service * Fixed crash when optional argument is not provided * Updated argument description * fixed lint error * Fix isort error * switched to use dict for optional field instead of json string * switched to use ATTR_PAYLOAD * fixed test * actually fixed test --- homeassistant/components/webostv/__init__.py | 5 +++- homeassistant/components/webostv/const.py | 1 + .../components/webostv/media_player.py | 5 ++-- .../components/webostv/services.yaml | 7 +++++- tests/components/webostv/test_media_player.py | 23 ++++++++++++++++--- 5 files changed, 34 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/webostv/__init__.py b/homeassistant/components/webostv/__init__.py index 0790ece9333..ebac158c452 100644 --- a/homeassistant/components/webostv/__init__.py +++ b/homeassistant/components/webostv/__init__.py @@ -9,6 +9,7 @@ from websockets.exceptions import ConnectionClosed from homeassistant.components.webostv.const import ( ATTR_BUTTON, ATTR_COMMAND, + ATTR_PAYLOAD, CONF_ON_ACTION, CONF_SOURCES, DEFAULT_NAME, @@ -59,7 +60,9 @@ CALL_SCHEMA = vol.Schema({vol.Required(ATTR_ENTITY_ID): cv.comp_entity_ids}) BUTTON_SCHEMA = CALL_SCHEMA.extend({vol.Required(ATTR_BUTTON): cv.string}) -COMMAND_SCHEMA = CALL_SCHEMA.extend({vol.Required(ATTR_COMMAND): cv.string}) +COMMAND_SCHEMA = CALL_SCHEMA.extend( + {vol.Required(ATTR_COMMAND): cv.string, vol.Optional(ATTR_PAYLOAD): dict} +) SOUND_OUTPUT_SCHEMA = CALL_SCHEMA.extend({vol.Required(ATTR_SOUND_OUTPUT): cv.string}) diff --git a/homeassistant/components/webostv/const.py b/homeassistant/components/webostv/const.py index 3e1e790fc02..bea485a7d68 100644 --- a/homeassistant/components/webostv/const.py +++ b/homeassistant/components/webostv/const.py @@ -5,6 +5,7 @@ DEFAULT_NAME = "LG webOS Smart TV" ATTR_BUTTON = "button" ATTR_COMMAND = "command" +ATTR_PAYLOAD = "payload" ATTR_SOUND_OUTPUT = "sound_output" CONF_ON_ACTION = "turn_on_action" diff --git a/homeassistant/components/webostv/media_player.py b/homeassistant/components/webostv/media_player.py index 3e443929012..556ff7a287b 100644 --- a/homeassistant/components/webostv/media_player.py +++ b/homeassistant/components/webostv/media_player.py @@ -24,6 +24,7 @@ from homeassistant.components.media_player.const import ( SUPPORT_VOLUME_STEP, ) from homeassistant.components.webostv.const import ( + ATTR_PAYLOAD, ATTR_SOUND_OUTPUT, CONF_ON_ACTION, CONF_SOURCES, @@ -450,6 +451,6 @@ class LgWebOSMediaPlayerEntity(MediaPlayerEntity): await self._client.button(button) @cmd - async def async_command(self, command): + async def async_command(self, command, **kwargs): """Send a command.""" - await self._client.request(command) + await self._client.request(command, payload=kwargs.get(ATTR_PAYLOAD)) diff --git a/homeassistant/components/webostv/services.yaml b/homeassistant/components/webostv/services.yaml index 70aa94b6ea6..b88b3d839d7 100644 --- a/homeassistant/components/webostv/services.yaml +++ b/homeassistant/components/webostv/services.yaml @@ -24,7 +24,12 @@ command: description: >- Endpoint of the command. Known valid endpoints are listed in https://github.com/TheRealLink/pylgtv/blob/master/pylgtv/endpoints.py - example: "media.controls/rewind" + example: "system.launcher/open" + payload: + description: >- + An optional payload to provide to the endpoint in the format of key value pair(s). + example: >- + target: https://www.google.com select_sound_output: description: "Send the TV the command to change sound output." diff --git a/tests/components/webostv/test_media_player.py b/tests/components/webostv/test_media_player.py index 099927c6c1f..3b213303281 100644 --- a/tests/components/webostv/test_media_player.py +++ b/tests/components/webostv/test_media_player.py @@ -12,6 +12,7 @@ from homeassistant.components.media_player.const import ( from homeassistant.components.webostv.const import ( ATTR_BUTTON, ATTR_COMMAND, + ATTR_PAYLOAD, DOMAIN, SERVICE_BUTTON, SERVICE_COMMAND, @@ -102,8 +103,7 @@ async def test_button(hass, client): async def test_command(hass, client): - """Test generic button functionality.""" - + """Test generic command functionality.""" await setup_webostv(hass) data = { @@ -113,4 +113,21 @@ async def test_command(hass, client): await hass.services.async_call(DOMAIN, SERVICE_COMMAND, data) await hass.async_block_till_done() - client.request.assert_called_with("test") + client.request.assert_called_with("test", payload=None) + + +async def test_command_with_optional_arg(hass, client): + """Test generic command functionality.""" + await setup_webostv(hass) + + data = { + ATTR_ENTITY_ID: ENTITY_ID, + ATTR_COMMAND: "test", + ATTR_PAYLOAD: {"target": "https://www.google.com"}, + } + await hass.services.async_call(DOMAIN, SERVICE_COMMAND, data) + await hass.async_block_till_done() + + client.request.assert_called_with( + "test", payload={"target": "https://www.google.com"} + ) From 6fbc3b54bdee501fcc24c39bd1ed7bd66c332615 Mon Sep 17 00:00:00 2001 From: Chris Talkington Date: Wed, 27 May 2020 10:53:26 -0500 Subject: [PATCH 222/406] Fix roku select source with app ids (#36191) --- homeassistant/components/roku/media_player.py | 7 ++++++- tests/components/roku/test_media_player.py | 14 ++++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/roku/media_player.py b/homeassistant/components/roku/media_player.py index 0deeb44dbc2..69c9e24ad89 100644 --- a/homeassistant/components/roku/media_player.py +++ b/homeassistant/components/roku/media_player.py @@ -222,7 +222,12 @@ class RokuMediaPlayer(RokuEntity, MediaPlayerEntity): await self.coordinator.roku.remote("home") appl = next( - (app for app in self.coordinator.data.apps if app.name == source), None + ( + app + for app in self.coordinator.data.apps + if source in (app.name, app.app_id) + ), + None, ) if appl is not None: diff --git a/tests/components/roku/test_media_player.py b/tests/components/roku/test_media_player.py index 9d809cae433..9ac758585e9 100644 --- a/tests/components/roku/test_media_player.py +++ b/tests/components/roku/test_media_player.py @@ -332,7 +332,7 @@ async def test_services( remote_mock.assert_called_once_with("home") - with patch("homeassistant.components.roku.Roku.launch") as remote_mock: + with patch("homeassistant.components.roku.Roku.launch") as launch_mock: await hass.services.async_call( MP_DOMAIN, SERVICE_SELECT_SOURCE, @@ -340,7 +340,17 @@ async def test_services( blocking=True, ) - remote_mock.assert_called_once_with("12") + launch_mock.assert_called_once_with("12") + + with patch("homeassistant.components.roku.Roku.launch") as launch_mock: + await hass.services.async_call( + MP_DOMAIN, + SERVICE_SELECT_SOURCE, + {ATTR_ENTITY_ID: MAIN_ENTITY_ID, ATTR_INPUT_SOURCE: 12}, + blocking=True, + ) + + launch_mock.assert_called_once_with("12") async def test_tv_services( From f626129e2bbd5c6dae0d1c6b189bf1191f519507 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 27 May 2020 13:43:05 -0500 Subject: [PATCH 223/406] Proceed with startup if an integration setup blocks for more than 30m (#36082) * Proceed with startup if an integration setup blocks for more than 30m * Fix test location * Fix log call * naming * revert * do not shield from cancelation * Adjust test since we now cancel when we hit the timeout --- homeassistant/bootstrap.py | 1 + homeassistant/setup.py | 21 ++++++++++++++++++--- tests/helpers/test_entity_platform.py | 4 ++-- tests/test_setup.py | 26 ++++++++++++++++++++++++-- 4 files changed, 45 insertions(+), 7 deletions(-) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index ffcd1b7f3cf..32a7384c350 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -422,4 +422,5 @@ async def _async_set_up_integrations( await async_setup_multi_components(stage_2_domains) # Wrap up startup + _LOGGER.debug("Waiting for startup to wrap up") await hass.async_block_till_done() diff --git a/homeassistant/setup.py b/homeassistant/setup.py index 70321d364b8..9c20216d4ec 100644 --- a/homeassistant/setup.py +++ b/homeassistant/setup.py @@ -19,6 +19,9 @@ DATA_SETUP = "setup_tasks" DATA_DEPS_REQS = "deps_reqs_processed" SLOW_SETUP_WARNING = 10 +# Since a pip install can run, we wait +# 30 minutes to timeout +SLOW_SETUP_MAX_WAIT = 1800 def setup_component(hass: core.HomeAssistant, domain: str, config: ConfigType) -> bool: @@ -167,16 +170,28 @@ async def _async_setup_component( try: if hasattr(component, "async_setup"): - result = await component.async_setup( # type: ignore + task = component.async_setup( # type: ignore hass, processed_config ) elif hasattr(component, "setup"): - result = await hass.async_add_executor_job( - component.setup, hass, processed_config # type: ignore + # This should not be replaced with hass.async_add_executor_job because + # we don't want to track this task in case it blocks startup. + task = hass.loop.run_in_executor( + None, component.setup, hass, processed_config # type: ignore ) else: log_error("No setup function defined.") return False + + result = await asyncio.wait_for(task, SLOW_SETUP_MAX_WAIT) + except asyncio.TimeoutError: + _LOGGER.error( + "Setup of %s is taking longer than %s seconds." + " Startup will proceed without waiting any longer.", + domain, + SLOW_SETUP_MAX_WAIT, + ) + return False except Exception: # pylint: disable=broad-except _LOGGER.exception("Error during setup of component %s", domain) async_notify_setup_error(hass, domain, integration.documentation) diff --git a/tests/helpers/test_entity_platform.py b/tests/helpers/test_entity_platform.py index eb24ea971a7..039f83d7031 100644 --- a/tests/helpers/test_entity_platform.py +++ b/tests/helpers/test_entity_platform.py @@ -187,8 +187,8 @@ async def test_platform_warn_slow_setup(hass): assert mock_call.called # mock_calls[0] is the warning message for component setup - # mock_calls[3] is the warning message for platform setup - timeout, logger_method = mock_call.mock_calls[3][1][:2] + # mock_calls[5] is the warning message for platform setup + timeout, logger_method = mock_call.mock_calls[5][1][:2] assert timeout == entity_platform.SLOW_SETUP_WARNING assert logger_method == _LOGGER.warning diff --git a/tests/test_setup.py b/tests/test_setup.py index 4197fe7370a..820b0c4db23 100644 --- a/tests/test_setup.py +++ b/tests/test_setup.py @@ -489,13 +489,16 @@ async def test_component_warn_slow_setup(hass): result = await setup.async_setup_component(hass, "test_component1", {}) assert result assert mock_call.called - assert len(mock_call.mock_calls) == 3 + assert len(mock_call.mock_calls) == 5 timeout, logger_method = mock_call.mock_calls[0][1][:2] assert timeout == setup.SLOW_SETUP_WARNING assert logger_method == setup._LOGGER.warning + timeout, function = mock_call.mock_calls[1][1][:2] + assert timeout == setup.SLOW_SETUP_MAX_WAIT + assert mock_call().cancel.called @@ -507,7 +510,26 @@ async def test_platform_no_warn_slow(hass): 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 + timeout, function = mock_call.mock_calls[0][1][:2] + assert timeout == setup.SLOW_SETUP_MAX_WAIT + + +async def test_platform_error_slow_setup(hass, caplog): + """Don't block startup more than SLOW_SETUP_MAX_WAIT.""" + + with patch.object(setup, "SLOW_SETUP_MAX_WAIT", 1): + called = [] + + async def async_setup(*args): + """Tracking Setup.""" + called.append(1) + await asyncio.sleep(2) + + mock_integration(hass, MockModule("test_component1", async_setup=async_setup)) + result = await setup.async_setup_component(hass, "test_component1", {}) + assert len(called) == 1 + assert not result + assert "test_component1 is taking longer than 1 seconds" in caplog.text async def test_when_setup_already_loaded(hass): From 6609bd94e5f603e6abebb532413ef7d5d00c7825 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Wed, 27 May 2020 13:53:14 -0600 Subject: [PATCH 224/406] Prevent AirVisual from polling (#36199) * Prevent AirVisual from polling * Docstring --- homeassistant/components/airvisual/__init__.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/homeassistant/components/airvisual/__init__.py b/homeassistant/components/airvisual/__init__.py index 96b3770974a..8d41bd359b8 100644 --- a/homeassistant/components/airvisual/__init__.py +++ b/homeassistant/components/airvisual/__init__.py @@ -375,6 +375,11 @@ class AirVisualEntity(Entity): """Return the icon.""" return self._icon + @property + def should_poll(self) -> bool: + """Disable polling.""" + return False + @property def unit_of_measurement(self): """Return the unit the value is expressed in.""" From 3ac376ebca0fa3cc92eba0d1d0184d7af4b930a2 Mon Sep 17 00:00:00 2001 From: Ohad Levy Date: Thu, 28 May 2020 00:07:30 +0300 Subject: [PATCH 225/406] Fix hassio log message typo (#36194) minor contribution - thanks for HA :) --- homeassistant/components/hassio/__init__.py | 2 +- tests/components/hassio/test_init.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/hassio/__init__.py b/homeassistant/components/hassio/__init__.py index 6a383e28ff1..bb4663a9654 100644 --- a/homeassistant/components/hassio/__init__.py +++ b/homeassistant/components/hassio/__init__.py @@ -188,7 +188,7 @@ async def async_setup(hass, config): hass.data[DOMAIN] = hassio = HassIO(hass.loop, websession, host) if not await hassio.is_connected(): - _LOGGER.warning("Not connected with Hass.io / system to busy!") + _LOGGER.warning("Not connected with Hass.io / system too busy!") store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY) data = await store.async_load() diff --git a/tests/components/hassio/test_init.py b/tests/components/hassio/test_init.py index c3110b6599c..d0043747835 100644 --- a/tests/components/hassio/test_init.py +++ b/tests/components/hassio/test_init.py @@ -215,7 +215,7 @@ async def test_warn_when_cannot_connect(hass, caplog): assert result assert hass.components.hassio.is_hassio() - assert "Not connected with Hass.io / system to busy!" in caplog.text + assert "Not connected with Hass.io / system too busy!" in caplog.text async def test_service_register(hassio_env, hass): From a7842b6301b5ae15f96c8371a94bbad63c59dd22 Mon Sep 17 00:00:00 2001 From: Andre Lengwenus Date: Wed, 27 May 2020 23:08:51 +0200 Subject: [PATCH 226/406] Fix of LCN cover behavior (#35050) --- homeassistant/components/lcn/cover.py | 41 +++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/lcn/cover.py b/homeassistant/components/lcn/cover.py index 05ee17a7daf..a5bea69e9ae 100644 --- a/homeassistant/components/lcn/cover.py +++ b/homeassistant/components/lcn/cover.py @@ -122,7 +122,9 @@ class LcnRelayCover(LcnDevice, CoverEntity): self.motor_port_onoff = self.motor.value * 2 self.motor_port_updown = self.motor_port_onoff + 1 - self._closed = None + self._is_closed = False + self._is_closing = False + self._is_opening = False async def async_added_to_hass(self): """Run when entity about to be added to hass.""" @@ -132,11 +134,28 @@ class LcnRelayCover(LcnDevice, CoverEntity): @property def is_closed(self): """Return if the cover is closed.""" - return self._closed + return self._is_closed + + @property + def is_opening(self): + """Return if the cover is opening or not.""" + return self._is_opening + + @property + def is_closing(self): + """Return if the cover is closing or not.""" + return self._is_closing + + @property + def assumed_state(self): + """Return True if unable to access real state of the entity.""" + return True async def async_close_cover(self, **kwargs): """Close the cover.""" - self._closed = True + self._is_closed = True + self._is_opening = False + self._is_closing = True states = [pypck.lcn_defs.MotorStateModifier.NOCHANGE] * 4 states[self.motor.value] = pypck.lcn_defs.MotorStateModifier.DOWN self.address_connection.control_motors_relays(states) @@ -144,7 +163,9 @@ class LcnRelayCover(LcnDevice, CoverEntity): async def async_open_cover(self, **kwargs): """Open the cover.""" - self._closed = False + self._is_closed = False + self._is_opening = True + self._is_closing = False states = [pypck.lcn_defs.MotorStateModifier.NOCHANGE] * 4 states[self.motor.value] = pypck.lcn_defs.MotorStateModifier.UP self.address_connection.control_motors_relays(states) @@ -152,7 +173,10 @@ class LcnRelayCover(LcnDevice, CoverEntity): async def async_stop_cover(self, **kwargs): """Stop the cover.""" - self._closed = None + if self._is_opening or self._is_closing: + self._is_closed = self._is_closing + self._is_closing = False + self._is_opening = False states = [pypck.lcn_defs.MotorStateModifier.NOCHANGE] * 4 states[self.motor.value] = pypck.lcn_defs.MotorStateModifier.STOP self.address_connection.control_motors_relays(states) @@ -165,6 +189,11 @@ class LcnRelayCover(LcnDevice, CoverEntity): states = input_obj.states # list of boolean values (relay on/off) if states[self.motor_port_onoff]: # motor is on - self._closed = states[self.motor_port_updown] # set direction + self._is_opening = not states[self.motor_port_updown] # set direction + self._is_closing = states[self.motor_port_updown] # set direction + self._is_closed = self._is_closing + else: + self._is_opening = False + self._is_closing = False self.async_write_ha_state() From f14a4935dfc7723e701e22ace772ae7b3b53bd98 Mon Sep 17 00:00:00 2001 From: k2v1n58 <38071268+k2v1n58@users.noreply.github.com> Date: Thu, 28 May 2020 00:09:15 +0200 Subject: [PATCH 227/406] Widen songpal volume step change compatibility (#36152) * Update songpal / media_player.py Based on issue https://github.com/home-assistant/core/issues/36135 * Update test_media_player.py --- homeassistant/components/songpal/media_player.py | 4 ++-- tests/components/songpal/test_media_player.py | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/songpal/media_player.py b/homeassistant/components/songpal/media_player.py index 3777ecd8325..578cdaed367 100644 --- a/homeassistant/components/songpal/media_player.py +++ b/homeassistant/components/songpal/media_player.py @@ -316,11 +316,11 @@ class SongpalEntity(MediaPlayerEntity): async def async_volume_up(self): """Set volume up.""" - return await self._volume_control.set_volume("+1") + return await self._volume_control.set_volume(self._volume + 1) async def async_volume_down(self): """Set volume down.""" - return await self._volume_control.set_volume("-1") + return await self._volume_control.set_volume(self._volume - 1) async def async_turn_on(self): """Turn the device on.""" diff --git a/tests/components/songpal/test_media_player.py b/tests/components/songpal/test_media_player.py index 5a7ccfd846d..e4d03d03b0a 100644 --- a/tests/components/songpal/test_media_player.py +++ b/tests/components/songpal/test_media_player.py @@ -156,9 +156,7 @@ async def test_services(hass): await _call(media_player.SERVICE_VOLUME_UP) await _call(media_player.SERVICE_VOLUME_DOWN) assert mocked_device.volume1.set_volume.call_count == 3 - mocked_device.volume1.set_volume.assert_has_calls( - [call(60), call("+1"), call("-1")] - ) + mocked_device.volume1.set_volume.assert_has_calls([call(60), call(51), call(49)]) await _call(media_player.SERVICE_VOLUME_MUTE, is_volume_muted=True) mocked_device.volume1.set_mute.assert_called_once_with(True) From 1e9ec917f69bf23fb672d59532f0efacca14ca1f Mon Sep 17 00:00:00 2001 From: Phil Bruckner Date: Wed, 27 May 2020 17:10:28 -0500 Subject: [PATCH 228/406] Add support for simultaneous runs of Script helper - Part 3 (#36202) --- homeassistant/helpers/script.py | 101 +++++++++++++++++++++----------- 1 file changed, 67 insertions(+), 34 deletions(-) diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index c724b9e890d..107cd9e2106 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -393,58 +393,85 @@ class _ScriptRun(_ScriptRunBase): except KeyError: delay = None done = asyncio.Event() + tasks = [ + self._hass.async_create_task(flag.wait()) for flag in (self._stop, done) + ] try: async with timeout(delay): - _, pending = await asyncio.wait( - {self._stop.wait(), done.wait()}, - return_when=asyncio.FIRST_COMPLETED, - ) - for pending_task in pending: - pending_task.cancel() + await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED) except asyncio.TimeoutError: if not self._action.get(CONF_CONTINUE_ON_TIMEOUT, True): self._log(_TIMEOUT_MSG) raise _StopScript finally: + for task in tasks: + task.cancel() unsub() async def _async_call_service_step(self): """Call the service specified in the action.""" domain, service, service_data = self._prep_call_service_step() + running_script = ( + domain == "automation" + and service == "trigger" + or domain == "python_script" + or domain == "script" + and service != SERVICE_TURN_OFF + ) # If this might start a script then disable the call timeout. # Otherwise use the normal service call limit. - if domain == "script" and service != SERVICE_TURN_OFF: + if running_script: limit = None else: limit = SERVICE_CALL_LIMIT - coro = self._hass.services.async_call( - domain, - service, - service_data, - blocking=True, - context=self._context, - limit=limit, + service_task = self._hass.async_create_task( + self._hass.services.async_call( + domain, + service, + service_data, + blocking=True, + context=self._context, + limit=limit, + ) ) - if limit is not None: # There is a call limit, so just wait for it to finish. - await coro + await service_task return - # No call limit (i.e., potentially starting one or more fully blocking scripts) - # so watch for a stop request. - done, pending = await asyncio.wait( - {self._stop.wait(), coro}, return_when=asyncio.FIRST_COMPLETED, - ) - # Note that cancelling the service call, if it has not yet returned, will also - # stop any non-background script runs that it may have started. - for pending_task in pending: - pending_task.cancel() - # Propagate any exceptions that might have happened. - for done_task in done: - done_task.result() + async def async_cancel_service_task(): + # Stop service task and wait for it to finish. + service_task.cancel() + try: + await service_task + except Exception: # pylint: disable=broad-except + pass + + # No call limit so watch for a stop request. + stop_task = self._hass.async_create_task(self._stop.wait()) + try: + await asyncio.wait( + {service_task, stop_task}, return_when=asyncio.FIRST_COMPLETED + ) + # If our task is cancelled, then cancel service task, too. Note that if service + # task is cancelled otherwise the CancelledError exception will not be raised to + # here due to the call to asyncio.wait(). Rather we'll check for that below. + except asyncio.CancelledError: + await async_cancel_service_task() + raise + finally: + stop_task.cancel() + + if service_task.cancelled(): + raise asyncio.CancelledError + if service_task.done(): + # Propagate any exceptions that occurred. + service_task.result() + elif running_script: + # Stopped before service completed, so cancel service. + await async_cancel_service_task() class _QueuedScriptRun(_ScriptRun): @@ -459,12 +486,18 @@ class _QueuedScriptRun(_ScriptRun): lock_task = self._hass.async_create_task( self._script._queue_lck.acquire() # pylint: disable=protected-access ) - done, pending = await asyncio.wait( - {self._stop.wait(), lock_task}, return_when=asyncio.FIRST_COMPLETED - ) - for pending_task in pending: - pending_task.cancel() - self.lock_acquired = lock_task in done + stop_task = self._hass.async_create_task(self._stop.wait()) + try: + await asyncio.wait( + {lock_task, stop_task}, return_when=asyncio.FIRST_COMPLETED + ) + except asyncio.CancelledError: + lock_task.cancel() + self._finish() + raise + finally: + stop_task.cancel() + self.lock_acquired = lock_task.done() and not lock_task.cancelled() # If we've been told to stop, then just finish up. Otherwise, we've acquired the # lock so we can go ahead and start the run. From 4e74fae615032164ed6bc95edc1d22822348b3e1 Mon Sep 17 00:00:00 2001 From: jjlawren Date: Wed, 27 May 2020 17:36:08 -0500 Subject: [PATCH 229/406] Playback on Sonos speakers from Plex integration (#36177) Co-authored-by: Paulus Schoutsen --- homeassistant/components/plex/__init__.py | 76 ++++++++++- homeassistant/components/plex/const.py | 2 + homeassistant/components/plex/manifest.json | 1 + homeassistant/components/plex/server.py | 8 ++ homeassistant/components/plex/services.yaml | 13 ++ homeassistant/components/sonos/__init__.py | 22 ++- homeassistant/components/sonos/const.py | 1 + .../components/sonos/media_player.py | 3 +- tests/components/plex/mock_classes.py | 16 +++ tests/components/plex/test_playback.py | 127 ++++++++++++++++++ 10 files changed, 265 insertions(+), 4 deletions(-) create mode 100644 homeassistant/components/plex/services.yaml create mode 100644 tests/components/plex/test_playback.py diff --git a/homeassistant/components/plex/__init__.py b/homeassistant/components/plex/__init__.py index 7f8caf8390b..cc281ed8147 100644 --- a/homeassistant/components/plex/__init__.py +++ b/homeassistant/components/plex/__init__.py @@ -1,6 +1,7 @@ """Support to embed Plex.""" import asyncio import functools +import json import logging import plexapi.exceptions @@ -10,7 +11,12 @@ import voluptuous as vol from homeassistant import config_entries from homeassistant.components.media_player import DOMAIN as MP_DOMAIN +from homeassistant.components.media_player.const import ( + ATTR_MEDIA_CONTENT_ID, + ATTR_MEDIA_CONTENT_TYPE, +) from homeassistant.const import ( + ATTR_ENTITY_ID, CONF_HOST, CONF_PORT, CONF_SSL, @@ -19,7 +25,7 @@ from homeassistant.const import ( CONF_VERIFY_SSL, EVENT_HOMEASSISTANT_STOP, ) -from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.exceptions import ConfigEntryNotReady, HomeAssistantError from homeassistant.helpers import config_validation as cv from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.dispatcher import ( @@ -44,6 +50,7 @@ from .const import ( PLEX_SERVER_CONFIG, PLEX_UPDATE_PLATFORMS_SIGNAL, SERVERS, + SERVICE_PLAY_ON_SONOS, WEBSOCKETS, ) from .errors import ShouldUpdateConfigEntry @@ -215,6 +222,24 @@ async def async_setup_entry(hass, entry): ) task.add_done_callback(functools.partial(start_websocket_session, platform)) + async def async_play_on_sonos_service(service_call): + await hass.async_add_executor_job(play_on_sonos, hass, service_call) + + play_on_sonos_schema = vol.Schema( + { + vol.Required(ATTR_ENTITY_ID): cv.entity_id, + vol.Required(ATTR_MEDIA_CONTENT_ID): str, + vol.Optional(ATTR_MEDIA_CONTENT_TYPE): vol.In("music"), + } + ) + + hass.services.async_register( + PLEX_DOMAIN, + SERVICE_PLAY_ON_SONOS, + async_play_on_sonos_service, + schema=play_on_sonos_schema, + ) + return True @@ -244,3 +269,52 @@ async def async_options_updated(hass, entry): """Triggered by config entry options updates.""" server_id = entry.data[CONF_SERVER_IDENTIFIER] hass.data[PLEX_DOMAIN][SERVERS][server_id].options = entry.options + + +def play_on_sonos(hass, service_call): + """Play Plex media on a linked Sonos device.""" + entity_id = service_call.data[ATTR_ENTITY_ID] + content_id = service_call.data[ATTR_MEDIA_CONTENT_ID] + content = json.loads(content_id) + + sonos = hass.components.sonos + try: + sonos_id = sonos.get_coordinator_id(entity_id) + except HomeAssistantError as err: + _LOGGER.error("Cannot get Sonos device: %s", err) + return + + if isinstance(content, int): + content = {"plex_key": content} + + plex_server_name = content.get("plex_server") + shuffle = content.pop("shuffle", 0) + + plex_servers = hass.data[PLEX_DOMAIN][SERVERS].values() + if plex_server_name: + plex_server = [x for x in plex_servers if x.friendly_name == plex_server_name] + if not plex_server: + _LOGGER.error( + "Requested Plex server '%s' not found in %s", + plex_server_name, + list(map(lambda x: x.friendly_name, plex_servers)), + ) + return + else: + plex_server = next(iter(plex_servers)) + + sonos_speaker = plex_server.account.sonos_speaker_by_id(sonos_id) + if sonos_speaker is None: + _LOGGER.error( + "Sonos speaker '%s' could not be found on this Plex account", sonos_id + ) + return + + media = plex_server.lookup_media("music", **content) + if media is None: + _LOGGER.error("Media could not be found: %s", content) + return + + _LOGGER.debug("Attempting to play '%s' on %s", media, sonos_speaker) + playqueue = plex_server.create_playqueue(media, shuffle=shuffle) + sonos_speaker.playMedia(playqueue) diff --git a/homeassistant/components/plex/const.py b/homeassistant/components/plex/const.py index e8bcfb42ca6..d17710c4436 100644 --- a/homeassistant/components/plex/const.py +++ b/homeassistant/components/plex/const.py @@ -43,3 +43,5 @@ X_PLEX_VERSION = __version__ AUTOMATIC_SETUP_STRING = "Obtain a new token from plex.tv" MANUAL_SETUP_STRING = "Configure Plex server manually" + +SERVICE_PLAY_ON_SONOS = "play_on_sonos" diff --git a/homeassistant/components/plex/manifest.json b/homeassistant/components/plex/manifest.json index e48a37a77d5..b864cae3557 100644 --- a/homeassistant/components/plex/manifest.json +++ b/homeassistant/components/plex/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/plex", "requirements": ["plexapi==3.6.0", "plexauth==0.0.5", "plexwebsocket==0.0.8"], "dependencies": ["http"], + "after_dependencies": ["sonos"], "codeowners": ["@jjlawren"] } diff --git a/homeassistant/components/plex/server.py b/homeassistant/components/plex/server.py index 933a899e3b2..88b4b533f88 100644 --- a/homeassistant/components/plex/server.py +++ b/homeassistant/components/plex/server.py @@ -57,6 +57,7 @@ class PlexServer: def __init__(self, hass, server_config, known_server_id=None, options=None): """Initialize a Plex server instance.""" self.hass = hass + self._plex_account = None self._plex_server = None self._created_clients = set() self._known_clients = set() @@ -85,6 +86,13 @@ class PlexServer: plexapi.myplex.BASE_HEADERS = plexapi.reset_base_headers() plexapi.server.BASE_HEADERS = plexapi.reset_base_headers() + @property + def account(self): + """Return a MyPlexAccount instance.""" + if not self._plex_account: + self._plex_account = plexapi.myplex.MyPlexAccount(token=self._token) + return self._plex_account + def connect(self): """Connect to a Plex server directly, obtaining direct URL if necessary.""" config_entry_update_needed = False diff --git a/homeassistant/components/plex/services.yaml b/homeassistant/components/plex/services.yaml new file mode 100644 index 00000000000..0245edfb99e --- /dev/null +++ b/homeassistant/components/plex/services.yaml @@ -0,0 +1,13 @@ +play_on_sonos: + description: Play music hosted on a Plex server on a linked Sonos speaker. + fields: + entity_id: + description: Entity ID of a media_player from the Sonos integration. + example: "media_player.sonos_living_room" + media_content_id: + description: The ID of the content to play. See https://www.home-assistant.io/integrations/plex/#music for details. + example: >- + '{ "library_name": "Music", "artist_name": "Stevie Wonder" }' + media_content_type: + description: The type of content to play. Must be "music". + example: "music" diff --git a/homeassistant/components/sonos/__init__.py b/homeassistant/components/sonos/__init__.py index c3a977e32e1..f19816e865d 100644 --- a/homeassistant/components/sonos/__init__.py +++ b/homeassistant/components/sonos/__init__.py @@ -4,9 +4,11 @@ import voluptuous as vol from homeassistant import config_entries from homeassistant.components.media_player import DOMAIN as MP_DOMAIN from homeassistant.const import CONF_HOSTS +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_validation as cv +from homeassistant.loader import bind_hass -from .const import DOMAIN +from .const import DATA_SONOS, DOMAIN CONF_ADVERTISE_ADDR = "advertise_addr" CONF_INTERFACE_ADDR = "interface_addr" @@ -53,3 +55,21 @@ async def async_setup_entry(hass, entry): hass.config_entries.async_forward_entry_setup(entry, MP_DOMAIN) ) return True + + +@bind_hass +def get_coordinator_id(hass, entity_id): + """Obtain the unique_id of a device's coordinator. + + This function is safe to run inside the event loop. + """ + if DATA_SONOS not in hass.data: + raise HomeAssistantError("Sonos integration not set up") + + device = next( + (x for x in hass.data[DATA_SONOS].entities if x.entity_id == entity_id), None + ) + + if device.is_coordinator: + return device.unique_id + return device.coordinator.unique_id diff --git a/homeassistant/components/sonos/const.py b/homeassistant/components/sonos/const.py index 5858f2bca9b..0d88249f740 100644 --- a/homeassistant/components/sonos/const.py +++ b/homeassistant/components/sonos/const.py @@ -1,3 +1,4 @@ """Const for Sonos.""" DOMAIN = "sonos" +DATA_SONOS = "sonos_media_player" diff --git a/homeassistant/components/sonos/media_player.py b/homeassistant/components/sonos/media_player.py index b4a0fb7d208..15a168047e9 100644 --- a/homeassistant/components/sonos/media_player.py +++ b/homeassistant/components/sonos/media_player.py @@ -47,6 +47,7 @@ from . import ( CONF_ADVERTISE_ADDR, CONF_HOSTS, CONF_INTERFACE_ADDR, + DATA_SONOS, DOMAIN as SONOS_DOMAIN, ) @@ -70,8 +71,6 @@ SUPPORT_SONOS = ( | SUPPORT_CLEAR_PLAYLIST ) -DATA_SONOS = "sonos_media_player" - SOURCE_LINEIN = "Line-in" SOURCE_TV = "TV" diff --git a/tests/components/plex/mock_classes.py b/tests/components/plex/mock_classes.py index 0c082474eb2..2d69801e797 100644 --- a/tests/components/plex/mock_classes.py +++ b/tests/components/plex/mock_classes.py @@ -69,6 +69,10 @@ class MockPlexAccount: """Mock the PlexAccount resources listing method.""" return self._resources + def sonos_speaker_by_id(self, machine_identifier): + """Mock the PlexAccount Sonos lookup method.""" + return MockPlexSonosClient(machine_identifier) + class MockPlexSystemAccount: """Mock a PlexSystemAccount instance.""" @@ -351,3 +355,15 @@ class MockPlexMediaTrack(MockPlexMediaItem): """Initialize the object.""" super().__init__(f"Track {index}", "track") self.index = index + + +class MockPlexSonosClient: + """Mock a PlexSonosClient instance.""" + + def __init__(self, machine_identifier): + """Initialize the object.""" + self.machineIdentifier = machine_identifier + + def playMedia(self, item): + """Mock the playMedia method.""" + pass diff --git a/tests/components/plex/test_playback.py b/tests/components/plex/test_playback.py new file mode 100644 index 00000000000..7a90d8dfad8 --- /dev/null +++ b/tests/components/plex/test_playback.py @@ -0,0 +1,127 @@ +"""Tests for Plex player playback methods/services.""" +from homeassistant.components.media_player.const import ( + ATTR_MEDIA_CONTENT_ID, + ATTR_MEDIA_CONTENT_TYPE, + MEDIA_TYPE_MUSIC, +) +from homeassistant.components.plex.const import DOMAIN, SERVERS, SERVICE_PLAY_ON_SONOS +from homeassistant.const import ATTR_ENTITY_ID +from homeassistant.exceptions import HomeAssistantError + +from .const import DEFAULT_DATA, DEFAULT_OPTIONS +from .mock_classes import MockPlexAccount, MockPlexServer + +from tests.async_mock import patch +from tests.common import MockConfigEntry + + +async def test_sonos_playback(hass): + """Test playing media on a Sonos speaker.""" + + entry = MockConfigEntry( + domain=DOMAIN, + data=DEFAULT_DATA, + options=DEFAULT_OPTIONS, + unique_id=DEFAULT_DATA["server_id"], + ) + + 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() + + server_id = mock_plex_server.machineIdentifier + loaded_server = hass.data[DOMAIN][SERVERS][server_id] + + with patch("plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount()): + # Access and cache PlexAccount + assert loaded_server.account + + # Test Sonos integration lookup failure + with patch.object( + hass.components.sonos, "get_coordinator_id", side_effect=HomeAssistantError + ): + assert await hass.services.async_call( + DOMAIN, + SERVICE_PLAY_ON_SONOS, + { + ATTR_ENTITY_ID: "media_player.sonos_kitchen", + ATTR_MEDIA_CONTENT_TYPE: MEDIA_TYPE_MUSIC, + ATTR_MEDIA_CONTENT_ID: '{"library_name": "Music", "artist_name": "Artist", "album_name": "Album"}', + }, + True, + ) + + # Test success with dict + with patch.object( + hass.components.sonos, + "get_coordinator_id", + return_value="media_player.sonos_kitchen", + ), patch("plexapi.playqueue.PlayQueue.create"): + assert await hass.services.async_call( + DOMAIN, + SERVICE_PLAY_ON_SONOS, + { + ATTR_ENTITY_ID: "media_player.sonos_kitchen", + ATTR_MEDIA_CONTENT_TYPE: MEDIA_TYPE_MUSIC, + ATTR_MEDIA_CONTENT_ID: "2", + }, + True, + ) + + # Test success with plex_key + with patch.object( + hass.components.sonos, + "get_coordinator_id", + return_value="media_player.sonos_kitchen", + ), patch("plexapi.playqueue.PlayQueue.create"): + assert await hass.services.async_call( + DOMAIN, + SERVICE_PLAY_ON_SONOS, + { + ATTR_ENTITY_ID: "media_player.sonos_kitchen", + ATTR_MEDIA_CONTENT_TYPE: MEDIA_TYPE_MUSIC, + ATTR_MEDIA_CONTENT_ID: '{"library_name": "Music", "artist_name": "Artist", "album_name": "Album"}', + }, + True, + ) + + # Test invalid Plex server requested + with patch.object( + hass.components.sonos, + "get_coordinator_id", + return_value="media_player.sonos_kitchen", + ): + assert await hass.services.async_call( + DOMAIN, + SERVICE_PLAY_ON_SONOS, + { + ATTR_ENTITY_ID: "media_player.sonos_kitchen", + ATTR_MEDIA_CONTENT_TYPE: MEDIA_TYPE_MUSIC, + ATTR_MEDIA_CONTENT_ID: '{"plex_server": "unknown_plex_server", "library_name": "Music", "artist_name": "Artist", "album_name": "Album"}', + }, + True, + ) + + # Test no speakers available + with patch.object( + loaded_server.account, "sonos_speaker_by_id", return_value=None + ), patch.object( + hass.components.sonos, + "get_coordinator_id", + return_value="media_player.sonos_kitchen", + ): + assert await hass.services.async_call( + DOMAIN, + SERVICE_PLAY_ON_SONOS, + { + ATTR_ENTITY_ID: "media_player.sonos_kitchen", + ATTR_MEDIA_CONTENT_TYPE: MEDIA_TYPE_MUSIC, + ATTR_MEDIA_CONTENT_ID: '{"library_name": "Music", "artist_name": "Artist", "album_name": "Album"}', + }, + True, + ) From 7571fdc95700defb1477fdfbcd69cfd3f4f079fa Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Wed, 27 May 2020 16:45:28 -0600 Subject: [PATCH 230/406] Allow Guardian config flow to be ignored (#36207) * Allow Guardian config flow to be ignored * Tests --- .../components/guardian/config_flow.py | 38 ++++++++++++++----- tests/components/guardian/test_config_flow.py | 24 ++++++++++-- 2 files changed, 49 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/guardian/config_flow.py b/homeassistant/components/guardian/config_flow.py index 3a7558bb222..dae8fafb1e0 100644 --- a/homeassistant/components/guardian/config_flow.py +++ b/homeassistant/components/guardian/config_flow.py @@ -5,6 +5,7 @@ import voluptuous as vol from homeassistant import config_entries, core from homeassistant.const import CONF_IP_ADDRESS, CONF_PORT +from homeassistant.core import callback from .const import CONF_UID, DOMAIN, LOGGER # pylint:disable=unused-import @@ -12,6 +13,20 @@ DATA_SCHEMA = vol.Schema( {vol.Required(CONF_IP_ADDRESS): str, vol.Required(CONF_PORT, default=7777): int} ) +UNIQUE_ID = "guardian_{0}" + + +@callback +def async_get_pin_from_discovery_hostname(hostname): + """Get the device's 4-digit PIN from its zeroconf-discovered hostname.""" + return hostname.split(".")[0].split("-")[1] + + +@callback +def async_get_pin_from_uid(uid): + """Get the device's 4-digit PIN from its UID.""" + return uid[-4:] + async def validate_input(hass: core.HomeAssistant, data): """Validate the user input allows us to connect. @@ -22,7 +37,6 @@ async def validate_input(hass: core.HomeAssistant, data): ping_data = await client.device.ping() return { - "title": f"Elexa Guardian ({data[CONF_IP_ADDRESS]})", CONF_UID: ping_data["data"]["uid"], } @@ -37,6 +51,11 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """Initialize.""" self.discovery_info = {} + async def _async_set_unique_id(self, pin): + """Set the config entry's unique ID (based on the device's 4-digit PIN).""" + await self.async_set_unique_id(UNIQUE_ID.format(pin)) + self._abort_if_unique_id_configured() + async def async_step_user(self, user_input=None): """Handle configuration via the UI.""" if user_input is None: @@ -44,9 +63,6 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): step_id="user", data_schema=DATA_SCHEMA, errors={} ) - await self.async_set_unique_id(user_input[CONF_IP_ADDRESS]) - self._abort_if_unique_id_configured() - try: info = await validate_input(self.hass, user_input) except GuardianError as err: @@ -57,8 +73,11 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): errors={CONF_IP_ADDRESS: "cannot_connect"}, ) + pin = async_get_pin_from_uid(info[CONF_UID]) + await self._async_set_unique_id(pin) + return self.async_create_entry( - title=info["title"], data={CONF_UID: info["uid"], **user_input} + title=info[CONF_UID], data={CONF_UID: info["uid"], **user_input} ) async def async_step_zeroconf(self, discovery_info=None): @@ -66,19 +85,20 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): if discovery_info is None: return self.async_abort(reason="connection_error") - ip_address = discovery_info["host"] + pin = async_get_pin_from_discovery_hostname(discovery_info["hostname"]) + await self._async_set_unique_id(pin) # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 - self.context[CONF_IP_ADDRESS] = ip_address + self.context[CONF_IP_ADDRESS] = discovery_info["host"] if any( - ip_address == flow["context"][CONF_IP_ADDRESS] + discovery_info["host"] == flow["context"][CONF_IP_ADDRESS] for flow in self._async_in_progress() ): return self.async_abort(reason="already_in_progress") self.discovery_info = { - CONF_IP_ADDRESS: ip_address, + CONF_IP_ADDRESS: discovery_info["host"], CONF_PORT: discovery_info["port"], } diff --git a/tests/components/guardian/test_config_flow.py b/tests/components/guardian/test_config_flow.py index 1625e48f1ba..8e44b9f1417 100644 --- a/tests/components/guardian/test_config_flow.py +++ b/tests/components/guardian/test_config_flow.py @@ -4,17 +4,21 @@ from asynctest import patch from homeassistant import data_entry_flow from homeassistant.components.guardian import CONF_UID, DOMAIN +from homeassistant.components.guardian.config_flow import ( + async_get_pin_from_discovery_hostname, + async_get_pin_from_uid, +) from homeassistant.config_entries import SOURCE_USER, SOURCE_ZEROCONF from homeassistant.const import CONF_IP_ADDRESS, CONF_PORT from tests.common import MockConfigEntry -async def test_duplicate_error(hass): +async def test_duplicate_error(hass, ping_client): """Test that errors are shown when duplicate entries are added.""" conf = {CONF_IP_ADDRESS: "192.168.1.100", CONF_PORT: 7777} - MockConfigEntry(domain=DOMAIN, unique_id="192.168.1.100", data=conf).add_to_hass( + MockConfigEntry(domain=DOMAIN, unique_id="guardian_3456", data=conf).add_to_hass( hass ) @@ -40,6 +44,18 @@ async def test_connect_error(hass): assert result["errors"] == {CONF_IP_ADDRESS: "cannot_connect"} +async def test_get_pin_from_discovery_hostname(): + """Test getting a device PIN from the zeroconf-discovered hostname.""" + pin = async_get_pin_from_discovery_hostname("GVC1-3456.local.") + assert pin == "3456" + + +async def test_get_pin_from_uid(): + """Test getting a device PIN from its UID.""" + pin = async_get_pin_from_uid("ABCDEF123456") + assert pin == "3456" + + async def test_step_user(hass, ping_client): """Test the user step.""" conf = {CONF_IP_ADDRESS: "192.168.1.100", CONF_PORT: 7777} @@ -54,7 +70,7 @@ async def test_step_user(hass, ping_client): DOMAIN, context={"source": SOURCE_USER}, data=conf ) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result["title"] == "Elexa Guardian (192.168.1.100)" + assert result["title"] == "ABCDEF123456" assert result["data"] == { CONF_IP_ADDRESS: "192.168.1.100", CONF_PORT: 7777, @@ -83,7 +99,7 @@ async def test_step_zeroconf(hass, ping_client): result["flow_id"], user_input={} ) assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result["title"] == "Elexa Guardian (192.168.1.100)" + assert result["title"] == "ABCDEF123456" assert result["data"] == { CONF_IP_ADDRESS: "192.168.1.100", CONF_PORT: 7777, From c18ba6aec0627e6afb6442c678edb5ff2bb17db6 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Wed, 27 May 2020 16:48:28 -0600 Subject: [PATCH 231/406] Remove configuration.yaml support for OpenUV (#36148) Co-authored-by: Paulus Schoutsen --- homeassistant/components/openuv/__init__.py | 84 +++--------- .../components/openuv/config_flow.py | 46 +++---- homeassistant/components/openuv/strings.json | 7 +- .../components/openuv/translations/en.json | 5 +- tests/components/openuv/test_config_flow.py | 121 +++++++----------- 5 files changed, 90 insertions(+), 173 deletions(-) diff --git a/homeassistant/components/openuv/__init__.py b/homeassistant/components/openuv/__init__.py index e6a302975a5..1403778aae2 100644 --- a/homeassistant/components/openuv/__init__.py +++ b/homeassistant/components/openuv/__init__.py @@ -4,9 +4,7 @@ import logging from pyopenuv import Client from pyopenuv.errors import OpenUvError -import voluptuous as vol -from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import ( ATTR_ATTRIBUTION, CONF_API_KEY, @@ -26,7 +24,6 @@ from homeassistant.helpers.dispatcher import ( from homeassistant.helpers.entity import Entity from homeassistant.helpers.service import verify_domain_control -from .config_flow import configured_instances from .const import DOMAIN _LOGGER = logging.getLogger(__name__) @@ -55,54 +52,14 @@ TYPE_SAFE_EXPOSURE_TIME_4 = "safe_exposure_time_type_4" TYPE_SAFE_EXPOSURE_TIME_5 = "safe_exposure_time_type_5" TYPE_SAFE_EXPOSURE_TIME_6 = "safe_exposure_time_type_6" +PLATFORMS = ["binary_sensor", "sensor"] -CONFIG_SCHEMA = vol.Schema( - { - DOMAIN: vol.Schema( - { - vol.Required(CONF_API_KEY): cv.string, - vol.Optional(CONF_ELEVATION): float, - vol.Optional(CONF_LATITUDE): cv.latitude, - vol.Optional(CONF_LONGITUDE): cv.longitude, - } - ) - }, - extra=vol.ALLOW_EXTRA, -) +CONFIG_SCHEMA = cv.deprecated(DOMAIN, invalidation_version="0.115") async def async_setup(hass, config): """Set up the OpenUV component.""" - hass.data[DOMAIN] = {} - hass.data[DOMAIN][DATA_OPENUV_CLIENT] = {} - hass.data[DOMAIN][DATA_OPENUV_LISTENER] = {} - - if DOMAIN not in config: - return True - - conf = config[DOMAIN] - - identifier = ( - f"{conf.get(CONF_LATITUDE, hass.config.latitude)}, " - f"{conf.get(CONF_LONGITUDE, hass.config.longitude)}" - ) - if identifier in configured_instances(hass): - return True - - data = {CONF_API_KEY: conf[CONF_API_KEY]} - if CONF_LATITUDE in conf: - data[CONF_LATITUDE] = conf[CONF_LATITUDE] - if CONF_LONGITUDE in conf: - data[CONF_LONGITUDE] = conf[CONF_LONGITUDE] - if CONF_ELEVATION in conf: - data[CONF_ELEVATION] = conf[CONF_ELEVATION] - - hass.async_create_task( - hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_IMPORT}, data=data - ) - ) - + hass.data[DOMAIN] = {DATA_OPENUV_CLIENT: {}, DATA_OPENUV_LISTENER: {}} return True @@ -127,7 +84,7 @@ async def async_setup_entry(hass, config_entry): _LOGGER.error("Config entry failed: %s", err) raise ConfigEntryNotReady - for component in ("binary_sensor", "sensor"): + for component in PLATFORMS: hass.async_create_task( hass.config_entries.async_forward_entry_setup(config_entry, component) ) @@ -139,8 +96,6 @@ async def async_setup_entry(hass, config_entry): await openuv.async_update() async_dispatcher_send(hass, TOPIC_UPDATE) - hass.services.async_register(DOMAIN, "update_data", update_data) - @_verify_domain_control async def update_uv_index_data(service): """Refresh OpenUV UV index data.""" @@ -148,8 +103,6 @@ async def async_setup_entry(hass, config_entry): await openuv.async_update_uv_index_data() async_dispatcher_send(hass, TOPIC_UPDATE) - hass.services.async_register(DOMAIN, "update_uv_index_data", update_uv_index_data) - @_verify_domain_control async def update_protection_data(service): """Refresh OpenUV protection window data.""" @@ -157,25 +110,30 @@ async def async_setup_entry(hass, config_entry): await openuv.async_update_protection_data() async_dispatcher_send(hass, TOPIC_UPDATE) - hass.services.async_register( - DOMAIN, "update_protection_data", update_protection_data - ) + for service, method in [ + ("update_data", update_data), + ("update_uv_index_data", update_uv_index_data), + ("update_protection_data", update_protection_data), + ]: + hass.services.async_register(DOMAIN, service, method) return True async def async_unload_entry(hass, config_entry): """Unload an OpenUV config entry.""" - hass.data[DOMAIN][DATA_OPENUV_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_OPENUV_CLIENT].pop(config_entry.entry_id) - tasks = [ - hass.config_entries.async_forward_entry_unload(config_entry, component) - for component in ("binary_sensor", "sensor") - ] - - await asyncio.gather(*tasks) - - return True + return unload_ok async def async_migrate_entry(hass, config_entry): diff --git a/homeassistant/components/openuv/config_flow.py b/homeassistant/components/openuv/config_flow.py index 821c9624c58..bf359ef1b30 100644 --- a/homeassistant/components/openuv/config_flow.py +++ b/homeassistant/components/openuv/config_flow.py @@ -10,24 +10,21 @@ from homeassistant.const import ( CONF_LATITUDE, CONF_LONGITUDE, ) -from homeassistant.core import callback from homeassistant.helpers import aiohttp_client, config_validation as cv -from .const import DOMAIN +from .const import DOMAIN # pylint: disable=unused-import - -@callback -def configured_instances(hass): - """Return a set of configured OpenUV instances.""" - return { - f"{entry.data.get(CONF_LATITUDE, hass.config.latitude)}, " - f"{entry.data.get(CONF_LONGITUDE, hass.config.longitude)}" - for entry in hass.config_entries.async_entries(DOMAIN) +CONFIG_SCHEMA = vol.Schema( + { + vol.Required(CONF_API_KEY): str, + vol.Inclusive(CONF_LATITUDE, "coords"): cv.latitude, + vol.Inclusive(CONF_LONGITUDE, "coords"): cv.longitude, + vol.Optional(CONF_ELEVATION): vol.Coerce(float), } +) -@config_entries.HANDLERS.register(DOMAIN) -class OpenUvFlowHandler(config_entries.ConfigFlow): +class OpenUvFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Handle an OpenUV config flow.""" VERSION = 2 @@ -35,17 +32,8 @@ class OpenUvFlowHandler(config_entries.ConfigFlow): async def _show_form(self, errors=None): """Show the form to the user.""" - data_schema = vol.Schema( - { - vol.Required(CONF_API_KEY): str, - vol.Optional(CONF_LATITUDE): cv.latitude, - vol.Optional(CONF_LONGITUDE): cv.longitude, - vol.Optional(CONF_ELEVATION): vol.Coerce(float), - } - ) - return self.async_show_form( - step_id="user", data_schema=data_schema, errors=errors if errors else {} + step_id="user", data_schema=CONFIG_SCHEMA, errors=errors if errors else {}, ) async def async_step_import(self, import_config): @@ -54,16 +42,16 @@ class OpenUvFlowHandler(config_entries.ConfigFlow): async def async_step_user(self, user_input=None): """Handle the start of the config flow.""" - if not user_input: return await self._show_form() - identifier = ( - f"{user_input.get(CONF_LATITUDE, self.hass.config.latitude)}, " - f"{user_input.get(CONF_LONGITUDE, self.hass.config.longitude)}" - ) - if identifier in configured_instances(self.hass): - return await self._show_form({CONF_LATITUDE: "identifier_exists"}) + if user_input.get(CONF_LATITUDE): + identifier = f"{user_input[CONF_LATITUDE]}, {user_input[CONF_LONGITUDE]}" + else: + identifier = "Default Coordinates" + + await self.async_set_unique_id(identifier) + self._abort_if_unique_id_configured() websession = aiohttp_client.async_get_clientsession(self.hass) client = Client(user_input[CONF_API_KEY], 0, 0, websession) diff --git a/homeassistant/components/openuv/strings.json b/homeassistant/components/openuv/strings.json index 0c4913cf6c1..0777b139cf9 100644 --- a/homeassistant/components/openuv/strings.json +++ b/homeassistant/components/openuv/strings.json @@ -13,7 +13,10 @@ }, "error": { "identifier_exists": "Coordinates already registered", - "invalid_api_key": "Invalid API key" + "invalid_api_key": "[%key:common::config_flow::error::invalid_api_key%]" + }, + "abort": { + "already_configured": "These coordinates are already registered." } } -} \ No newline at end of file +} diff --git a/homeassistant/components/openuv/translations/en.json b/homeassistant/components/openuv/translations/en.json index 4c59a587fcd..0d2eec6341d 100644 --- a/homeassistant/components/openuv/translations/en.json +++ b/homeassistant/components/openuv/translations/en.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "These coordinates are already registered." + }, "error": { "identifier_exists": "Coordinates already registered", "invalid_api_key": "Invalid API key" @@ -7,7 +10,7 @@ "step": { "user": { "data": { - "api_key": "API Key", + "api_key": "[%key:common::config_flow::data::api_key%]", "elevation": "Elevation", "latitude": "Latitude", "longitude": "Longitude" diff --git a/tests/components/openuv/test_config_flow.py b/tests/components/openuv/test_config_flow.py index 3aa67abdc4f..b85805efa94 100644 --- a/tests/components/openuv/test_config_flow.py +++ b/tests/components/openuv/test_config_flow.py @@ -1,11 +1,9 @@ """Define tests for the OpenUV config flow.""" -from unittest.mock import patch - -from pyopenuv.errors import OpenUvError -import pytest +from pyopenuv.errors import InvalidApiKeyError from homeassistant import data_entry_flow -from homeassistant.components.openuv import DOMAIN, config_flow +from homeassistant.components.openuv import DOMAIN +from homeassistant.config_entries import SOURCE_USER from homeassistant.const import ( CONF_API_KEY, CONF_ELEVATION, @@ -13,21 +11,8 @@ from homeassistant.const import ( CONF_LONGITUDE, ) -from tests.common import MockConfigEntry, mock_coro - - -@pytest.fixture -def uv_index_response(): - """Define a fixture for a successful /uv response.""" - return mock_coro() - - -@pytest.fixture -def mock_pyopenuv(uv_index_response): - """Mock the pyopenuv library.""" - with patch("homeassistant.components.openuv.config_flow.Client") as MockClient: - MockClient().uv_index.return_value = uv_index_response - yield MockClient +from tests.async_mock import patch +from tests.common import MockConfigEntry async def test_duplicate_error(hass): @@ -39,16 +24,19 @@ async def test_duplicate_error(hass): CONF_LONGITUDE: -104.9812612, } - MockConfigEntry(domain=DOMAIN, data=conf).add_to_hass(hass) - flow = config_flow.OpenUvFlowHandler() - flow.hass = hass + MockConfigEntry( + domain=DOMAIN, unique_id="39.128712, -104.9812612", data=conf + ).add_to_hass(hass) - result = await flow.async_step_user(user_input=conf) - assert result["errors"] == {CONF_LATITUDE: "identifier_exists"} + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER}, data=conf + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "already_configured" -@pytest.mark.parametrize("uv_index_response", [mock_coro(exception=OpenUvError)]) -async def test_invalid_api_key(hass, mock_pyopenuv): +async def test_invalid_api_key(hass): """Test that an invalid API key throws an error.""" conf = { CONF_API_KEY: "12345abcde", @@ -57,48 +45,17 @@ async def test_invalid_api_key(hass, mock_pyopenuv): CONF_LONGITUDE: -104.9812612, } - flow = config_flow.OpenUvFlowHandler() - flow.hass = hass - - result = await flow.async_step_user(user_input=conf) - assert result["errors"] == {CONF_API_KEY: "invalid_api_key"} + with patch( + "pyopenuv.client.Client.uv_index", side_effect=InvalidApiKeyError, + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER}, data=conf + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] == {CONF_API_KEY: "invalid_api_key"} -async def test_show_form(hass): - """Test that the form is served with no input.""" - flow = config_flow.OpenUvFlowHandler() - flow.hass = hass - - result = await flow.async_step_user(user_input=None) - - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "user" - - -async def test_step_import(hass, mock_pyopenuv): - """Test that the import step works.""" - conf = { - CONF_API_KEY: "12345abcde", - CONF_ELEVATION: 59.1234, - CONF_LATITUDE: 39.128712, - CONF_LONGITUDE: -104.9812612, - } - - flow = config_flow.OpenUvFlowHandler() - flow.hass = hass - - result = await flow.async_step_import(import_config=conf) - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result["title"] == "39.128712, -104.9812612" - assert result["data"] == { - CONF_API_KEY: "12345abcde", - CONF_ELEVATION: 59.1234, - CONF_LATITUDE: 39.128712, - CONF_LONGITUDE: -104.9812612, - } - - -async def test_step_user(hass, mock_pyopenuv): +async def test_step_user(hass): """Test that the user step works.""" conf = { CONF_API_KEY: "12345abcde", @@ -107,15 +64,23 @@ async def test_step_user(hass, mock_pyopenuv): CONF_LONGITUDE: -104.9812612, } - flow = config_flow.OpenUvFlowHandler() - flow.hass = hass + with patch( + "homeassistant.components.airvisual.async_setup_entry", return_value=True + ), patch("pyopenuv.client.Client.uv_index"): + 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" - result = await flow.async_step_user(user_input=conf) - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result["title"] == "39.128712, -104.9812612" - assert result["data"] == { - CONF_API_KEY: "12345abcde", - CONF_ELEVATION: 59.1234, - CONF_LATITUDE: 39.128712, - CONF_LONGITUDE: -104.9812612, - } + 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"] == "39.128712, -104.9812612" + assert result["data"] == { + CONF_API_KEY: "12345abcde", + CONF_ELEVATION: 59.1234, + CONF_LATITUDE: 39.128712, + CONF_LONGITUDE: -104.9812612, + } From 3d02b3afc30fa1b6072d0b37504f28283059b9f4 Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Thu, 28 May 2020 12:27:15 +0200 Subject: [PATCH 232/406] Bump pdunehd library to version 1.3.1 (#36198) --- homeassistant/components/dunehd/manifest.json | 2 +- homeassistant/components/dunehd/media_player.py | 11 ++++++++--- requirements_all.txt | 2 +- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/dunehd/manifest.json b/homeassistant/components/dunehd/manifest.json index 2dfafdf0451..db837d9f8df 100644 --- a/homeassistant/components/dunehd/manifest.json +++ b/homeassistant/components/dunehd/manifest.json @@ -2,6 +2,6 @@ "domain": "dunehd", "name": "DuneHD", "documentation": "https://www.home-assistant.io/integrations/dunehd", - "requirements": ["pdunehd==1.3"], + "requirements": ["pdunehd==1.3.1"], "codeowners": [] } diff --git a/homeassistant/components/dunehd/media_player.py b/homeassistant/components/dunehd/media_player.py index 7ef0171dd6c..6fd5a8d7cd0 100644 --- a/homeassistant/components/dunehd/media_player.py +++ b/homeassistant/components/dunehd/media_player.py @@ -78,11 +78,11 @@ class DuneHDPlayerEntity(MediaPlayerEntity): state = STATE_OFF if "playback_position" in self._state: state = STATE_PLAYING - if self._state["player_state"] in ("playing", "buffering"): + if self._state.get("player_state") in ("playing", "buffering"): state = STATE_PLAYING if int(self._state.get("playback_speed", 1234)) == 0: state = STATE_PAUSED - if self._state["player_state"] == "navigator": + if self._state.get("player_state") == "navigator": state = STATE_ON return state @@ -91,6 +91,11 @@ class DuneHDPlayerEntity(MediaPlayerEntity): """Return the name of the device.""" return self._name + @property + def available(self): + """Return True if entity is available.""" + return bool(self._state) + @property def volume_level(self): """Return the volume level of the media player (0..1).""" @@ -153,7 +158,7 @@ class DuneHDPlayerEntity(MediaPlayerEntity): return self._state.get("playback_url", "Not playing") def __update_title(self): - if self._state["player_state"] == "bluray_playback": + if self._state.get("player_state") == "bluray_playback": self._media_title = "Blu-Ray" elif "playback_url" in self._state: sources = self._sources diff --git a/requirements_all.txt b/requirements_all.txt index f3748a3acc9..24a54c3e2be 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1043,7 +1043,7 @@ panasonic_viera==0.3.5 pcal9535a==0.7 # homeassistant.components.dunehd -pdunehd==1.3 +pdunehd==1.3.1 # homeassistant.components.pencom pencompy==0.0.3 From 03f420279651dba92cd74ad9723b103bcd180910 Mon Sep 17 00:00:00 2001 From: Fredrik Erlandsson Date: Thu, 28 May 2020 12:31:02 +0200 Subject: [PATCH 233/406] Bump pydaikin version to 2.1.0 (#36217) --- homeassistant/components/daikin/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/daikin/manifest.json b/homeassistant/components/daikin/manifest.json index 9b4e76e5eb1..2dbfa289808 100644 --- a/homeassistant/components/daikin/manifest.json +++ b/homeassistant/components/daikin/manifest.json @@ -3,7 +3,7 @@ "name": "Daikin AC", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/daikin", - "requirements": ["pydaikin==2.0.4"], + "requirements": ["pydaikin==2.1.0"], "codeowners": ["@fredrike"], "quality_scale": "platinum" } diff --git a/requirements_all.txt b/requirements_all.txt index 24a54c3e2be..faeb59a2c53 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1266,7 +1266,7 @@ pycsspeechtts==1.0.3 # pycups==1.9.73 # homeassistant.components.daikin -pydaikin==2.0.4 +pydaikin==2.1.0 # homeassistant.components.danfoss_air pydanfossair==0.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 559a6653c5d..474b527d717 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -539,7 +539,7 @@ pychromecast==5.3.0 pycoolmasternet==0.0.4 # homeassistant.components.daikin -pydaikin==2.0.4 +pydaikin==2.1.0 # homeassistant.components.deconz pydeconz==70 From 5c516ad01303f7ba5a1ed8db631ab361463228d1 Mon Sep 17 00:00:00 2001 From: shbatm Date: Thu, 28 May 2020 08:51:56 -0500 Subject: [PATCH 234/406] Add support for Insteon 2444-222 to ISY994 (#36212) * Add support for Insteon 2444-222 Micro Open/Close Module * Avoid breaking changes on cover * Update homeassistant/components/isy994/cover.py Co-authored-by: J. Nick Koston Co-authored-by: J. Nick Koston --- homeassistant/components/isy994/const.py | 45 +++++++++++++----------- homeassistant/components/isy994/cover.py | 36 +++++++++++++++++-- 2 files changed, 57 insertions(+), 24 deletions(-) diff --git a/homeassistant/components/isy994/const.py b/homeassistant/components/isy994/const.py index f7042a5860a..cf8b304179d 100644 --- a/homeassistant/components/isy994/const.py +++ b/homeassistant/components/isy994/const.py @@ -168,6 +168,23 @@ UNDO_UPDATE_LISTENER = "undo_update_listener" UDN_UUID_PREFIX = "uuid:" ISY_URL_POSTFIX = "/desc" +# Special Units of Measure +UOM_ISYV4_DEGREES = "degrees" +UOM_ISYV4_NONE = "n/a" + +UOM_ISY_CELSIUS = 1 +UOM_ISY_FAHRENHEIT = 2 + +UOM_8_BIT_RANGE = "100" +UOM_BARRIER = "97" +UOM_DOUBLE_TEMP = "101" +UOM_HVAC_ACTIONS = "66" +UOM_HVAC_MODE_GENERIC = "67" +UOM_HVAC_MODE_INSTEON = "98" +UOM_FAN_MODES = "99" +UOM_INDEX = "25" +UOM_ON_OFF = "2" + # Do not use the Home Assistant consts for the states here - we're matching exact API # responses, not using them for Home Assistant states # Insteon Types: https://www.universal-devices.com/developers/wsdk/5.0.4/1_fam.xml @@ -232,10 +249,10 @@ NODE_FILTERS = { FILTER_ZWAVE_CAT: [], }, COVER: { - FILTER_UOM: ["97"], + FILTER_UOM: [UOM_BARRIER], FILTER_STATES: ["open", "closed", "closing", "opening", "stopped"], - FILTER_NODE_DEF_ID: [], - FILTER_INSTEON_TYPE: [], + FILTER_NODE_DEF_ID: ["DimmerMotorSwitch_ADV"], + FILTER_INSTEON_TYPE: [TYPE_CATEGORY_COVER], FILTER_ZWAVE_CAT: [], }, LIGHT: { @@ -256,7 +273,7 @@ NODE_FILTERS = { FILTER_ZWAVE_CAT: ["109", "119"], }, SWITCH: { - FILTER_UOM: ["2", "78"], + FILTER_UOM: [UOM_ON_OFF, "78"], FILTER_STATES: ["on", "off"], FILTER_NODE_DEF_ID: [ "AlertModuleArmed", @@ -286,7 +303,7 @@ NODE_FILTERS = { FILTER_ZWAVE_CAT: ["121", "122", "123", "137", "141", "147"], }, CLIMATE: { - FILTER_UOM: ["2"], + FILTER_UOM: [UOM_ON_OFF], FILTER_STATES: ["heating", "cooling", "idle", "fan_only", "off"], FILTER_NODE_DEF_ID: ["TempLinc", "Thermostat"], FILTER_INSTEON_TYPE: ["4.8", TYPE_CATEGORY_CLIMATE], @@ -294,20 +311,6 @@ NODE_FILTERS = { }, } -UOM_ISYV4_DEGREES = "degrees" -UOM_ISYV4_NONE = "n/a" - -UOM_ISY_CELSIUS = 1 -UOM_ISY_FAHRENHEIT = 2 - -UOM_DOUBLE_TEMP = "101" -UOM_HVAC_ACTIONS = "66" -UOM_HVAC_MODE_GENERIC = "67" -UOM_HVAC_MODE_INSTEON = "98" -UOM_FAN_MODES = "99" -UOM_INDEX = "25" -UOM_ON_OFF = "2" - UOM_FRIENDLY_NAME = { "1": "A", "3": f"btu/{TIME_HOURS}", @@ -388,7 +391,7 @@ UOM_FRIENDLY_NAME = { "90": FREQUENCY_HERTZ, "91": DEGREE, "92": f"{DEGREE} South", - "100": "", # Range 0-255, no unit. + UOM_8_BIT_RANGE: "", # Range 0-255, no unit. UOM_DOUBLE_TEMP: UOM_DOUBLE_TEMP, "102": "kWs", "103": "$", @@ -556,7 +559,7 @@ UOM_TO_STATES = { 3: "moderately polluted", 4: "highly polluted", }, - "97": { # Barrier Status + UOM_BARRIER: { # Barrier Status **{ 0: STATE_CLOSED, 100: STATE_OPEN, diff --git a/homeassistant/components/isy994/cover.py b/homeassistant/components/isy994/cover.py index e0e47592a37..bbcc6f3bf15 100644 --- a/homeassistant/components/isy994/cover.py +++ b/homeassistant/components/isy994/cover.py @@ -3,11 +3,25 @@ from typing import Callable from pyisy.constants import ISY_VALUE_UNKNOWN -from homeassistant.components.cover import DOMAIN as COVER, CoverEntity +from homeassistant.components.cover import ( + ATTR_POSITION, + DOMAIN as COVER, + SUPPORT_CLOSE, + SUPPORT_OPEN, + SUPPORT_SET_POSITION, + CoverEntity, +) from homeassistant.config_entries import ConfigEntry from homeassistant.helpers.typing import HomeAssistantType -from .const import _LOGGER, DOMAIN as ISY994_DOMAIN, ISY994_NODES, ISY994_PROGRAMS +from .const import ( + _LOGGER, + DOMAIN as ISY994_DOMAIN, + ISY994_NODES, + ISY994_PROGRAMS, + UOM_8_BIT_RANGE, + UOM_BARRIER, +) from .entity import ISYNodeEntity, ISYProgramEntity from .helpers import migrate_old_unique_ids from .services import async_setup_device_services @@ -40,6 +54,8 @@ class ISYCoverEntity(ISYNodeEntity, CoverEntity): """Return the current cover position.""" if self._node.status == ISY_VALUE_UNKNOWN: return None + if self._node.uom == UOM_8_BIT_RANGE: + return int(self._node.status * 100 / 255) return sorted((0, self._node.status, 100))[1] @property @@ -49,9 +65,15 @@ class ISYCoverEntity(ISYNodeEntity, CoverEntity): return None return self._node.status == 0 + @property + def supported_features(self): + """Flag supported features.""" + return SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION + def open_cover(self, **kwargs) -> None: """Send the open cover command to the ISY994 cover device.""" - if not self._node.turn_on(val=100): + val = 100 if self._node.uom == UOM_BARRIER else None + if not self._node.turn_on(val=val): _LOGGER.error("Unable to open the cover") def close_cover(self, **kwargs) -> None: @@ -59,6 +81,14 @@ class ISYCoverEntity(ISYNodeEntity, CoverEntity): if not self._node.turn_off(): _LOGGER.error("Unable to close the cover") + def set_cover_position(self, **kwargs): + """Move the cover to a specific position.""" + position = kwargs.get(ATTR_POSITION) + if position and self._node.uom == UOM_8_BIT_RANGE: + position = int(position * 255 / 100) + if not self._node.turn_on(val=position): + _LOGGER.error("Unable to set cover position") + class ISYCoverProgramEntity(ISYProgramEntity, CoverEntity): """Representation of an ISY994 cover program.""" From 7e693afcf317a34fa23ddefc36c33a07ca4b0140 Mon Sep 17 00:00:00 2001 From: Tom Date: Thu, 28 May 2020 17:52:25 +0200 Subject: [PATCH 235/406] Update plugwise to async and config_flow (#33691) * Update plugwise async, config_flow and multi entity * Update battery percentage * Fix yamllint on services * Fix yamllint on services * Fix formatting for pyupgrade * Update homeassistant/components/plugwise/__init__.py Co-Authored-By: Robert Svensson * Add try/except on setup * Bump module version, battery version and valve position * Removing sensor, switch, water_heater for later (child) PRs * Catchup and version bump * Remove title from strings.json * Readd already reviewd await try/except * Readd already reviewed config_flow * Fix pylint * Fix per 0.109 translations * Remove unused import from merge * Update plugwise async, config_flow and multi entity * Update battery percentage * Fix yamllint on services * Fix yamllint on services * Bump module version * Bump module version, battery version and valve position * Removing sensor, switch, water_heater for later (child) PRs * Catchup and version bump * Remove title from strings.json * Fix pylint * Fix per 0.109 translations * Translations and config_flow, module version bump with required changes * Translations and config_flow, module version bump with required changes * Fix requirements * Fix requirements * Fix pylint * Fix pylint * Update homeassistant/components/plugwise/__init__.py Improvement Co-authored-by: J. Nick Koston * Update homeassistant/components/plugwise/__init__.py Improvement Co-authored-by: J. Nick Koston * Update homeassistant/components/plugwise/__init__.py Improvement Co-authored-by: J. Nick Koston * Include configentrynotready on import * Update __init__.py * DataUpdateCoordinator and comment non-PR-platforms * Fix reqs * Rename devices variable in favor of entities * Rework updates with DataUpdateCoordinator * Peer review * Peer review second part * Cleanup comments and redundant code * Added required config_flow test * Peer review third part * Update service was replaced by DataUpdateCoordinator * Corrected testing, version bump for InvalidAuth, move uniq_id * Remove according to review * Await connect (py38) * Remove unneccesary code * Show only when multiple * Improve config_flow, rename consts * Update homeassistant/components/plugwise/climate.py Co-authored-by: J. Nick Koston * Update homeassistant/components/plugwise/climate.py Co-authored-by: J. Nick Koston * Process review comments Co-authored-by: Robert Svensson Co-authored-by: J. Nick Koston --- .coveragerc | 3 +- CODEOWNERS | 2 +- homeassistant/components/plugwise/__init__.py | 162 +++++++- homeassistant/components/plugwise/climate.py | 371 +++++++++--------- .../components/plugwise/config_flow.py | 81 ++++ homeassistant/components/plugwise/const.py | 42 ++ .../components/plugwise/manifest.json | 8 +- .../components/plugwise/strings.json | 22 ++ .../components/plugwise/translations/en.json | 22 ++ .../components/plugwise/translations/nl.json | 22 ++ homeassistant/generated/config_flows.py | 1 + requirements_all.txt | 6 +- requirements_test_all.txt | 3 + tests/components/plugwise/__init__.py | 1 + tests/components/plugwise/test_config_flow.py | 83 ++++ 15 files changed, 638 insertions(+), 191 deletions(-) create mode 100644 homeassistant/components/plugwise/config_flow.py create mode 100644 homeassistant/components/plugwise/const.py create mode 100644 homeassistant/components/plugwise/strings.json create mode 100644 homeassistant/components/plugwise/translations/en.json create mode 100644 homeassistant/components/plugwise/translations/nl.json create mode 100644 tests/components/plugwise/__init__.py create mode 100644 tests/components/plugwise/test_config_flow.py diff --git a/.coveragerc b/.coveragerc index 9f29ab80bb8..bd09465f643 100644 --- a/.coveragerc +++ b/.coveragerc @@ -602,7 +602,8 @@ omit = homeassistant/components/plaato/* homeassistant/components/plex/media_player.py homeassistant/components/plex/sensor.py - homeassistant/components/plugwise/* + homeassistant/components/plugwise/__init__.py + homeassistant/components/plugwise/climate.py homeassistant/components/plum_lightpad/* homeassistant/components/pocketcasts/sensor.py homeassistant/components/point/* diff --git a/CODEOWNERS b/CODEOWNERS index 569d104d0bb..26d9c1f58b9 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -310,7 +310,7 @@ homeassistant/components/pilight/* @trekky12 homeassistant/components/plaato/* @JohNan homeassistant/components/plant/* @ChristianKuehnel homeassistant/components/plex/* @jjlawren -homeassistant/components/plugwise/* @laetificat @CoMPaTech @bouwew +homeassistant/components/plugwise/* @CoMPaTech @bouwew homeassistant/components/plum_lightpad/* @ColinHarrington homeassistant/components/point/* @fredrike homeassistant/components/powerwall/* @bdraco @jrester diff --git a/homeassistant/components/plugwise/__init__.py b/homeassistant/components/plugwise/__init__.py index 489e7d3f496..c6509091e1c 100644 --- a/homeassistant/components/plugwise/__init__.py +++ b/homeassistant/components/plugwise/__init__.py @@ -1 +1,161 @@ -"""Plugwise Climate (current only Anna) component for Home Assistant.""" +"""Plugwise platform for Home Assistant Core.""" + +import asyncio +from datetime import timedelta +import logging + +from Plugwise_Smile.Smile import Smile +import async_timeout +import voluptuous as vol + +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers import device_registry as dr +from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.helpers.entity import Entity +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed + +from .const import DOMAIN + +CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.Schema({})}, extra=vol.ALLOW_EXTRA) + +_LOGGER = logging.getLogger(__name__) + +ALL_PLATFORMS = ["climate"] + + +async def async_setup(hass: HomeAssistant, config: dict): + """Set up the Plugwise platform.""" + return True + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up Plugwise Smiles from a config entry.""" + websession = async_get_clientsession(hass, verify_ssl=False) + api = Smile( + host=entry.data["host"], password=entry.data["password"], websession=websession + ) + + try: + connected = await api.connect() + + if not connected: + _LOGGER.error("Unable to connect to Smile") + raise ConfigEntryNotReady + + except Smile.InvalidAuthentication: + _LOGGER.error("Invalid Smile ID") + return False + + except Smile.PlugwiseError: + _LOGGER.error("Error while communicating to device") + raise ConfigEntryNotReady + + except asyncio.TimeoutError: + _LOGGER.error("Timeout while connecting to Smile") + raise ConfigEntryNotReady + + if api.smile_type == "power": + update_interval = timedelta(seconds=10) + else: + update_interval = timedelta(seconds=60) + + async def async_update_data(): + """Update data via API endpoint.""" + try: + async with async_timeout.timeout(10): + await api.full_update_device() + return True + except Smile.XMLDataMissingError: + raise UpdateFailed("Smile update failed") + + coordinator = DataUpdateCoordinator( + hass, + _LOGGER, + name="Smile", + update_method=async_update_data, + update_interval=update_interval, + ) + + await coordinator.async_refresh() + + if not coordinator.last_update_success: + raise ConfigEntryNotReady + + api.get_all_devices() + + hass.data.setdefault(DOMAIN, {})[entry.entry_id] = { + "api": api, + "coordinator": coordinator, + } + + device_registry = await dr.async_get_registry(hass) + device_registry.async_get_or_create( + config_entry_id=entry.entry_id, + identifiers={(DOMAIN, api.gateway_id)}, + manufacturer="Plugwise", + name=entry.title, + model=f"Smile {api.smile_name}", + sw_version=api.smile_version[0], + ) + + for component in ALL_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 ALL_PLATFORMS + ] + ) + ) + if unload_ok: + hass.data[DOMAIN].pop(entry.entry_id) + + return unload_ok + + +class SmileGateway(Entity): + """Represent Smile Gateway.""" + + def __init__(self, api, coordinator): + """Initialise the sensor.""" + self._api = api + self._coordinator = coordinator + self._unique_id = None + + @property + def unique_id(self): + """Return a unique ID.""" + return self._unique_id + + @property + def should_poll(self): + """Return False, updates are controlled via coordinator.""" + return False + + @property + def available(self): + """Return True if entity is available.""" + return self._coordinator.last_update_success + + async def async_added_to_hass(self): + """Subscribe to updates.""" + self.async_on_remove(self._coordinator.async_add_listener(self._process_data)) + + def _process_data(self): + """Interpret and process API data.""" + raise NotImplementedError + + async def async_update(self): + """Update the entity.""" + await self._coordinator.async_request_refresh() diff --git a/homeassistant/components/plugwise/climate.py b/homeassistant/components/plugwise/climate.py index 8e2e525217a..209fdcdd242 100644 --- a/homeassistant/components/plugwise/climate.py +++ b/homeassistant/components/plugwise/climate.py @@ -1,11 +1,11 @@ """Plugwise Climate component for Home Assistant.""" import logging +from typing import Dict -import haanna -import voluptuous as vol +from Plugwise_Smile.Smile import Smile -from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateEntity +from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( CURRENT_HVAC_COOL, CURRENT_HVAC_HEAT, @@ -13,129 +13,140 @@ from homeassistant.components.climate.const import ( HVAC_MODE_AUTO, HVAC_MODE_HEAT, HVAC_MODE_HEAT_COOL, - HVAC_MODE_OFF, SUPPORT_PRESET_MODE, SUPPORT_TARGET_TEMPERATURE, ) -from homeassistant.const import ( - ATTR_TEMPERATURE, - CONF_HOST, - CONF_NAME, - CONF_PASSWORD, - CONF_PORT, - CONF_USERNAME, - TEMP_CELSIUS, +from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS + +from . import SmileGateway +from .const import ( + DEFAULT_MAX_TEMP, + DEFAULT_MIN_TEMP, + DOMAIN, + SCHEDULE_OFF, + SCHEDULE_ON, + THERMOSTAT_ICON, ) -from homeassistant.exceptions import PlatformNotReady -import homeassistant.helpers.config_validation as cv + +HVAC_MODES_HEAT_ONLY = [HVAC_MODE_HEAT, HVAC_MODE_AUTO] +HVAC_MODES_HEAT_COOL = [HVAC_MODE_HEAT_COOL, HVAC_MODE_AUTO] SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE _LOGGER = logging.getLogger(__name__) -# Configuration directives -CONF_MIN_TEMP = "min_temp" -CONF_MAX_TEMP = "max_temp" -CONF_LEGACY = "legacy_anna" -# Default directives -DEFAULT_NAME = "Plugwise Thermostat" -DEFAULT_USERNAME = "smile" -DEFAULT_TIMEOUT = 10 -DEFAULT_PORT = 80 -DEFAULT_ICON = "mdi:thermometer" -DEFAULT_MIN_TEMP = 4 -DEFAULT_MAX_TEMP = 30 +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up the Smile Thermostats from a config entry.""" + api = hass.data[DOMAIN][config_entry.entry_id]["api"] + coordinator = hass.data[DOMAIN][config_entry.entry_id]["coordinator"] -# HVAC modes -HVAC_MODES_1 = [HVAC_MODE_HEAT, HVAC_MODE_AUTO] -HVAC_MODES_2 = [HVAC_MODE_HEAT_COOL, HVAC_MODE_AUTO] - -# Read platform configuration -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( - { - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_LEGACY, default=False): cv.boolean, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, - vol.Optional(CONF_USERNAME, default=DEFAULT_USERNAME): cv.string, - vol.Optional(CONF_MIN_TEMP, default=DEFAULT_MIN_TEMP): cv.positive_int, - vol.Optional(CONF_MAX_TEMP, default=DEFAULT_MAX_TEMP): cv.positive_int, - } -) - - -def setup_platform(hass, config, add_entities, discovery_info=None): - """Add the Plugwise (Anna) Thermostat.""" - api = haanna.Haanna( - config[CONF_USERNAME], - config[CONF_PASSWORD], - config[CONF_HOST], - config[CONF_PORT], - config[CONF_LEGACY], - ) - try: - api.ping_anna_thermostat() - except OSError: - _LOGGER.debug("Ping failed, retrying later", exc_info=True) - raise PlatformNotReady - devices = [ - ThermostatDevice( - api, config[CONF_NAME], config[CONF_MIN_TEMP], config[CONF_MAX_TEMP] - ) + entities = [] + thermostat_classes = [ + "thermostat", + "zone_thermostat", + "thermostatic_radiator_valve", ] - add_entities(devices, True) + all_entities = api.get_all_devices() + + for dev_id, device in all_entities.items(): + + if device["class"] not in thermostat_classes: + continue + + thermostat = PwThermostat( + api, + coordinator, + device["name"], + dev_id, + device["location"], + device["class"], + DEFAULT_MIN_TEMP, + DEFAULT_MAX_TEMP, + ) + + entities.append(thermostat) + + async_add_entities(entities, True) -class ThermostatDevice(ClimateEntity): - """Representation of the Plugwise thermostat.""" +class PwThermostat(SmileGateway, ClimateEntity): + """Representation of an Plugwise thermostat.""" - def __init__(self, api, name, min_temp, max_temp): + def __init__( + self, api, coordinator, name, dev_id, loc_id, model, min_temp, max_temp + ): """Set up the Plugwise API.""" + super().__init__(api, coordinator) + self._api = api + self._name = name + self._dev_id = dev_id + self._loc_id = loc_id + self._model = model self._min_temp = min_temp self._max_temp = max_temp - self._name = name - self._direct_objects = None - self._domain_objects = None - self._outdoor_temperature = None + self._selected_schema = None self._last_active_schema = None self._preset_mode = None self._presets = None self._presets_list = None - self._boiler_status = None - self._heating_status = None - self._cooling_status = None - self._dhw_status = None + self._boiler_state = None + self._heating_state = None + self._cooling_state = None + self._dhw_state = None + self._hvac_mode = None self._schema_names = None self._schema_status = None - self._current_temperature = None - self._thermostat_temperature = None - self._boiler_temperature = None + self._temperature = None + self._setpoint = None self._water_pressure = None - self._schedule_temperature = None + self._schedule_temp = None self._hvac_mode = None + self._single_thermostat = self._api.single_master_thermostat() + self._unique_id = f"{dev_id}-climate" @property def hvac_action(self): - """Return the current hvac action.""" - if self._heating_status or self._boiler_status or self._dhw_status: - return CURRENT_HVAC_HEAT - if self._cooling_status: - return CURRENT_HVAC_COOL - return CURRENT_HVAC_IDLE + """Return the current action.""" + if self._single_thermostat: + if self._heating_state or self._boiler_state: + return CURRENT_HVAC_HEAT + if self._cooling_state: + return CURRENT_HVAC_COOL + return CURRENT_HVAC_IDLE + if self._heating_state is not None or self._boiler_state is not None: + if self._setpoint > self._temperature: + return CURRENT_HVAC_HEAT + return CURRENT_HVAC_IDLE @property def name(self): """Return the name of the thermostat, if any.""" return self._name + @property + def device_info(self) -> Dict[str, any]: + """Return the device information.""" + + device_information = { + "identifiers": {(DOMAIN, self._dev_id)}, + "name": self._name, + "manufacturer": "Plugwise", + "model": self._model.replace("_", " ").title(), + } + + if self._dev_id != self._api.gateway_id: + device_information["via_device"] = (DOMAIN, self._api.gateway_id) + del device_information["via_device"] + + return device_information + @property def icon(self): """Return the icon to use in the frontend.""" - return DEFAULT_ICON + return THERMOSTAT_ICON @property def supported_features(self): @@ -146,82 +157,47 @@ class ThermostatDevice(ClimateEntity): def device_state_attributes(self): """Return the device specific state attributes.""" attributes = {} - if self._outdoor_temperature: - attributes["outdoor_temperature"] = self._outdoor_temperature if self._schema_names: - attributes["available_schemas"] = self._schema_names + if len(self._schema_names) > 1: + attributes["available_schemas"] = self._schema_names if self._selected_schema: attributes["selected_schema"] = self._selected_schema - if self._boiler_temperature: - attributes["boiler_temperature"] = self._boiler_temperature - if self._water_pressure: - attributes["water_pressure"] = self._water_pressure return attributes @property def preset_modes(self): - """Return the available preset modes list. - - And make the presets with their temperatures available. - """ + """Return the available preset modes list.""" return self._presets_list @property def hvac_modes(self): """Return the available hvac modes list.""" - if self._heating_status is not None or self._boiler_status is not None: - if self._cooling_status is not None: - return HVAC_MODES_2 - return HVAC_MODES_1 - return None + if self._heating_state is not None or self._boiler_state is not None: + if self._cooling_state is not None: + return HVAC_MODES_HEAT_COOL + return HVAC_MODES_HEAT_ONLY @property def hvac_mode(self): """Return current active hvac state.""" - if self._schema_status: - return HVAC_MODE_AUTO - if self._heating_status or self._boiler_status or self._dhw_status: - if self._cooling_status: - return HVAC_MODE_HEAT_COOL - return HVAC_MODE_HEAT - return HVAC_MODE_OFF + return self._hvac_mode @property def target_temperature(self): - """Return the target_temperature. - - From the XML the thermostat-value is used because it updates 'immediately' - compared to the target_temperature-value. This way the information on the card - is "immediately" updated after changing the preset, temperature, etc. - """ - return self._thermostat_temperature + """Return the target_temperature.""" + return self._setpoint @property def preset_mode(self): - """Return the active selected schedule-name. - - Or, return the active preset, or return Temporary in case of a manual change - in the set-temperature with a weekschedule active. - Or return Manual in case of a manual change and no weekschedule active. - """ + """Return the active preset.""" if self._presets: - presets = self._presets - preset_temperature = presets.get(self._preset_mode, "none") - if self.hvac_mode == HVAC_MODE_AUTO: - if self._thermostat_temperature == self._schedule_temperature: - return f"{self._selected_schema}" - if self._thermostat_temperature == preset_temperature: - return self._preset_mode - return "Temporary" - if self._thermostat_temperature != preset_temperature: - return "Manual" return self._preset_mode return None @property def current_temperature(self): """Return the current room temperature.""" - return self._current_temperature + return self._temperature @property def min_temp(self): @@ -238,62 +214,93 @@ class ThermostatDevice(ClimateEntity): """Return the unit of measured temperature.""" return TEMP_CELSIUS - def set_temperature(self, **kwargs): + async def async_set_temperature(self, **kwargs): """Set new target temperature.""" - _LOGGER.debug("Adjusting temperature") temperature = kwargs.get(ATTR_TEMPERATURE) - if temperature is not None and self._min_temp < temperature < self._max_temp: - _LOGGER.debug("Changing temporary temperature") - self._api.set_temperature(self._domain_objects, temperature) + if (temperature is not None) and ( + self._min_temp < temperature < self._max_temp + ): + try: + await self._api.set_temperature(self._loc_id, temperature) + self._setpoint = temperature + self.async_write_ha_state() + except Smile.PlugwiseError: + _LOGGER.error("Error while communicating to device") else: _LOGGER.error("Invalid temperature requested") - def set_hvac_mode(self, hvac_mode): + async def async_set_hvac_mode(self, hvac_mode): """Set the hvac mode.""" - _LOGGER.debug("Adjusting hvac_mode (i.e. schedule/schema)") - schema_mode = "false" + state = SCHEDULE_OFF if hvac_mode == HVAC_MODE_AUTO: - schema_mode = "true" - self._api.set_schema_state( - self._domain_objects, self._last_active_schema, schema_mode - ) + state = SCHEDULE_ON + try: + await self._api.set_temperature(self._loc_id, self._schedule_temp) + self._setpoint = self._schedule_temp + except Smile.PlugwiseError: + _LOGGER.error("Error while communicating to device") + try: + await self._api.set_schedule_state( + self._loc_id, self._last_active_schema, state + ) + self._hvac_mode = hvac_mode + self.async_write_ha_state() + except Smile.PlugwiseError: + _LOGGER.error("Error while communicating to device") - def set_preset_mode(self, preset_mode): + async def async_set_preset_mode(self, preset_mode): """Set the preset mode.""" - _LOGGER.debug("Changing preset mode") - self._api.set_preset(self._domain_objects, preset_mode) + try: + await self._api.set_preset(self._loc_id, preset_mode) + self._preset_mode = preset_mode + self._setpoint = self._presets.get(self._preset_mode, "none")[0] + self.async_write_ha_state() + except Smile.PlugwiseError: + _LOGGER.error("Error while communicating to device") - def update(self): - """Update the data from the thermostat.""" - _LOGGER.debug("Update called") - self._direct_objects = self._api.get_direct_objects() - self._domain_objects = self._api.get_domain_objects() - self._outdoor_temperature = self._api.get_outdoor_temperature( - self._domain_objects - ) - self._selected_schema = self._api.get_active_schema_name(self._domain_objects) - self._last_active_schema = self._api.get_last_active_schema_name( - self._domain_objects - ) - self._preset_mode = self._api.get_current_preset(self._domain_objects) - self._presets = self._api.get_presets(self._domain_objects) - self._presets_list = list(self._api.get_presets(self._domain_objects)) - self._boiler_status = self._api.get_boiler_status(self._direct_objects) - self._heating_status = self._api.get_heating_status(self._direct_objects) - self._cooling_status = self._api.get_cooling_status(self._direct_objects) - self._dhw_status = self._api.get_domestic_hot_water_status(self._direct_objects) - self._schema_names = self._api.get_schema_names(self._domain_objects) - self._schema_status = self._api.get_schema_state(self._domain_objects) - self._current_temperature = self._api.get_current_temperature( - self._domain_objects - ) - self._thermostat_temperature = self._api.get_thermostat_temperature( - self._domain_objects - ) - self._schedule_temperature = self._api.get_schedule_temperature( - self._domain_objects - ) - self._boiler_temperature = self._api.get_boiler_temperature( - self._domain_objects - ) - self._water_pressure = self._api.get_water_pressure(self._domain_objects) + def _process_data(self): + """Update the data for this climate device.""" + climate_data = self._api.get_device_data(self._dev_id) + heater_central_data = self._api.get_device_data(self._api.heater_id) + + if "setpoint" in climate_data: + self._setpoint = climate_data["setpoint"] + if "temperature" in climate_data: + self._temperature = climate_data["temperature"] + if "schedule_temperature" in climate_data: + self._schedule_temp = climate_data["schedule_temperature"] + if "available_schedules" in climate_data: + self._schema_names = climate_data["available_schedules"] + if "selected_schedule" in climate_data: + self._selected_schema = climate_data["selected_schedule"] + if self._selected_schema is not None: + self._schema_status = True + else: + self._schema_status = False + if "last_used" in climate_data: + self._last_active_schema = climate_data["last_used"] + if "presets" in climate_data: + self._presets = climate_data["presets"] + if self._presets: + self._presets_list = list(self._presets) + if "active_preset" in climate_data: + self._preset_mode = climate_data["active_preset"] + + if "boiler_state" in heater_central_data: + if heater_central_data["boiler_state"] is not None: + self._boiler_state = heater_central_data["boiler_state"] + if "heating_state" in heater_central_data: + if heater_central_data["heating_state"] is not None: + self._heating_state = heater_central_data["heating_state"] + if "cooling_state" in heater_central_data: + if heater_central_data["cooling_state"] is not None: + self._cooling_state = heater_central_data["cooling_state"] + + if self._schema_status: + self._hvac_mode = HVAC_MODE_AUTO + elif self._heating_state is not None or self._boiler_state is not None: + self._hvac_mode = HVAC_MODE_HEAT + if self._cooling_state is not None: + self._hvac_mode = HVAC_MODE_HEAT_COOL + + self.async_write_ha_state() diff --git a/homeassistant/components/plugwise/config_flow.py b/homeassistant/components/plugwise/config_flow.py new file mode 100644 index 00000000000..7182665a506 --- /dev/null +++ b/homeassistant/components/plugwise/config_flow.py @@ -0,0 +1,81 @@ +"""Config flow for Plugwise integration.""" +import logging + +from Plugwise_Smile.Smile import Smile +import voluptuous as vol + +from homeassistant import config_entries, core, exceptions +from homeassistant.const import CONF_HOST, CONF_PASSWORD +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_HOST): str, vol.Required(CONF_PASSWORD): str} +) + + +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. + """ + websession = async_get_clientsession(hass, verify_ssl=False) + api = Smile( + host=data["host"], password=data["password"], timeout=30, websession=websession + ) + + try: + await api.connect() + except Smile.InvalidAuthentication: + raise InvalidAuth + except Smile.ConnectionFailedError: + raise CannotConnect + + return api + + +class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow for Plugwise Smile.""" + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL + + async def async_step_user(self, user_input=None): + """Handle the initial step.""" + errors = {} + api = None + if user_input is not None: + + try: + api = await validate_input(self.hass, user_input) + + return self.async_create_entry(title=api.smile_name, data=user_input) + except CannotConnect: + errors["base"] = "cannot_connect" + except InvalidAuth: + errors["base"] = "invalid_auth" + except Exception: # pylint: disable=broad-except + _LOGGER.exception("Unexpected exception") + errors["base"] = "unknown" + + if not errors: + await self.async_set_unique_id(api.gateway_id) + self._abort_if_unique_id_configured() + + return self.async_create_entry(title=api.smile_name, data=user_input) + + return self.async_show_form( + step_id="user", data_schema=DATA_SCHEMA, errors=errors + ) + + +class CannotConnect(exceptions.HomeAssistantError): + """Error to indicate we cannot connect.""" + + +class InvalidAuth(exceptions.HomeAssistantError): + """Error to indicate there is invalid auth.""" diff --git a/homeassistant/components/plugwise/const.py b/homeassistant/components/plugwise/const.py new file mode 100644 index 00000000000..2f804ef09a3 --- /dev/null +++ b/homeassistant/components/plugwise/const.py @@ -0,0 +1,42 @@ +"""Constant for Plugwise component.""" +DOMAIN = "plugwise" + +# Default directives +DEFAULT_NAME = "Smile" +DEFAULT_USERNAME = "smile" +DEFAULT_TIMEOUT = 10 +DEFAULT_PORT = 80 +DEFAULT_MIN_TEMP = 4 +DEFAULT_MAX_TEMP = 30 +DEFAULT_SCAN_INTERVAL = {"thermostat": 60, "power": 10} + +DEVICE_CLASS_GAS = "gas" + +# Configuration directives +CONF_MIN_TEMP = "min_temp" +CONF_MAX_TEMP = "max_temp" +CONF_THERMOSTAT = "thermostat" +CONF_POWER = "power" +CONF_HEATER = "heater" +CONF_SOLAR = "solar" +CONF_GAS = "gas" + +ATTR_ILLUMINANCE = "illuminance" +CURRENT_HVAC_DHW = "hot_water" +DEVICE_STATE = "device_state" + +SCHEDULE_ON = "true" +SCHEDULE_OFF = "false" + +# Icons +SWITCH_ICON = "mdi:electric-switch" +THERMOSTAT_ICON = "mdi:thermometer" +WATER_ICON = "mdi:water-pump" +FLAME_ICON = "mdi:fire" +COOL_ICON = "mdi:snowflake" +IDLE_ICON = "mdi:circle-off-outline" +GAS_ICON = "mdi:fire" +POWER_ICON = "mdi:flash" +POWER_FAILURE_ICON = "mdi:flash-off" +SWELL_SAG_ICON = "mdi:pulse" +VALVE_ICON = "mdi:valve" diff --git a/homeassistant/components/plugwise/manifest.json b/homeassistant/components/plugwise/manifest.json index 55e43c2a29f..2a0d5a1e0fc 100644 --- a/homeassistant/components/plugwise/manifest.json +++ b/homeassistant/components/plugwise/manifest.json @@ -1,7 +1,9 @@ { "domain": "plugwise", - "name": "Plugwise Anna", + "name": "Plugwise", "documentation": "https://www.home-assistant.io/integrations/plugwise", - "codeowners": ["@laetificat", "@CoMPaTech", "@bouwew"], - "requirements": ["haanna==0.15.0"] + "requirements": ["Plugwise_Smile==0.2.10"], + "dependencies": [], + "codeowners": ["@CoMPaTech", "@bouwew"], + "config_flow": true } diff --git a/homeassistant/components/plugwise/strings.json b/homeassistant/components/plugwise/strings.json new file mode 100644 index 00000000000..00499a26ac2 --- /dev/null +++ b/homeassistant/components/plugwise/strings.json @@ -0,0 +1,22 @@ +{ + "config": { + "step": { + "user": { + "title": "Connect to the Smile", + "description": "Details", + "data": { + "host": "Smile IP address", + "password": "Smile ID" + } + } + }, + "error": { + "cannot_connect": "Failed to connect, please try again", + "invalid_auth": "Invalid authentication, check the 8 characters of your Smile ID", + "unknown": "Unexpected error" + }, + "abort": { + "already_configured": "This Smile is already configured" + } + } +} diff --git a/homeassistant/components/plugwise/translations/en.json b/homeassistant/components/plugwise/translations/en.json new file mode 100644 index 00000000000..b7aa7ec957d --- /dev/null +++ b/homeassistant/components/plugwise/translations/en.json @@ -0,0 +1,22 @@ +{ + "config": { + "step": { + "user": { + "title": "Connect to the Smile", + "description": "Details", + "data": { + "host": "Smile IP address", + "password": "Smile ID" + } + } + }, + "error": { + "cannot_connect": "Failed to connect, please try again", + "invalid_auth": "Invalid authentication, check the 8 characters of your Smile ID", + "unknown": "Unexpected error" + }, + "abort": { + "already_configured": "This Smile is already configured" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/plugwise/translations/nl.json b/homeassistant/components/plugwise/translations/nl.json new file mode 100644 index 00000000000..7665136a58e --- /dev/null +++ b/homeassistant/components/plugwise/translations/nl.json @@ -0,0 +1,22 @@ +{ + "config": { + "step": { + "user": { + "title": "Verbinden met de Smile", + "description": "Gegevens", + "data": { + "host": "IP adres van de Smile", + "password": "Smile ID" + } + } + }, + "error": { + "cannot_connect": "Verbinding mislukt, probeer het opnieuw", + "invalid_auth": "Authenticatie mislukt, voer de 8 karakters van je Smile goed in", + "unknown": "Overwachte fout" + }, + "abort": { + "already_configured": "Deze Smile is al geconfigureerd" + } + } +} \ No newline at end of file diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 1c5ae1a4d7b..b0309482205 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -114,6 +114,7 @@ FLOWS = [ "pi_hole", "plaato", "plex", + "plugwise", "point", "powerwall", "ps4", diff --git a/requirements_all.txt b/requirements_all.txt index faeb59a2c53..707f3dae386 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -43,6 +43,9 @@ Mastodon.py==1.5.1 # homeassistant.components.orangepi_gpio OPi.GPIO==0.4.0 +# homeassistant.components.plugwise +Plugwise_Smile==0.2.10 + # homeassistant.components.essent PyEssent==0.13 @@ -697,9 +700,6 @@ ha-ffmpeg==2.0 # homeassistant.components.philips_js ha-philipsjs==0.0.8 -# homeassistant.components.plugwise -haanna==0.15.0 - # homeassistant.components.habitica habitipy==0.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 474b527d717..5afb1307533 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -6,6 +6,9 @@ # homeassistant.components.homekit HAP-python==2.8.4 +# homeassistant.components.plugwise +Plugwise_Smile==0.2.10 + # homeassistant.components.flick_electric PyFlick==0.0.2 diff --git a/tests/components/plugwise/__init__.py b/tests/components/plugwise/__init__.py new file mode 100644 index 00000000000..1904b581ab1 --- /dev/null +++ b/tests/components/plugwise/__init__.py @@ -0,0 +1 @@ +"""Tests for the Plugwise integration.""" diff --git a/tests/components/plugwise/test_config_flow.py b/tests/components/plugwise/test_config_flow.py new file mode 100644 index 00000000000..b70b658bd65 --- /dev/null +++ b/tests/components/plugwise/test_config_flow.py @@ -0,0 +1,83 @@ +"""Test the Plugwise config flow.""" +from Plugwise_Smile.Smile import Smile +import pytest + +from homeassistant import config_entries, setup +from homeassistant.components.plugwise.const import DOMAIN + +from tests.async_mock import patch + + +@pytest.fixture(name="mock_smile") +def mock_smile(): + """Create a Mock Smile for testing exceptions.""" + with patch("homeassistant.components.plugwise.config_flow.Smile",) as smile_mock: + smile_mock.InvalidAuthentication = Smile.InvalidAuthentication + smile_mock.ConnectionFailedError = Smile.ConnectionFailedError + smile_mock.return_value.connect.return_value = True + yield smile_mock.return_value + + +async def test_form(hass): + """Test we get the form.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == "form" + assert result["errors"] == {} + + with patch( + "homeassistant.components.plugwise.config_flow.Smile.connect", + return_value=True, + ), patch( + "homeassistant.components.plugwise.async_setup", return_value=True, + ) as mock_setup, patch( + "homeassistant.components.plugwise.async_setup_entry", return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], {"host": "1.1.1.1", "password": "test-password"}, + ) + + assert result2["type"] == "create_entry" + assert result2["data"] == { + "host": "1.1.1.1", + "password": "test-password", + } + 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_invalid_auth(hass, mock_smile): + """Test we handle invalid auth.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + mock_smile.connect.side_effect = Smile.InvalidAuthentication + mock_smile.gateway_id = "0a636a4fc1704ab4a24e4f7e37fb187a" + + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], {"host": "1.1.1.1", "password": "test-password"}, + ) + + assert result2["type"] == "form" + assert result2["errors"] == {"base": "invalid_auth"} + + +async def test_form_cannot_connect(hass, mock_smile): + """Test we handle cannot connect error.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + mock_smile.connect.side_effect = Smile.ConnectionFailedError + mock_smile.gateway_id = "0a636a4fc1704ab4a24e4f7e37fb187a" + + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], {"host": "1.1.1.1", "password": "test-password"}, + ) + + assert result2["type"] == "form" + assert result2["errors"] == {"base": "cannot_connect"} From 31250eafe8310b5c6249a6596d7d1538f165fa2a Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 28 May 2020 19:37:09 +0200 Subject: [PATCH 236/406] Fix custom position range (#36222) --- homeassistant/components/mqtt/cover.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/mqtt/cover.py b/homeassistant/components/mqtt/cover.py index 4ed128f1ff3..c9fc388e1a3 100644 --- a/homeassistant/components/mqtt/cover.py +++ b/homeassistant/components/mqtt/cover.py @@ -530,7 +530,7 @@ class MqttCover( async def async_set_cover_tilt_position(self, **kwargs): """Move the cover tilt to a specific position.""" - position = float(kwargs[ATTR_TILT_POSITION]) + position = kwargs[ATTR_TILT_POSITION] # The position needs to be between min and max level = self.find_in_range_from_percent(position) @@ -550,10 +550,7 @@ class MqttCover( percentage_position = position if set_position_template is not None: position = set_position_template.async_render(**kwargs) - elif ( - self._config[CONF_POSITION_OPEN] != 100 - and self._config[CONF_POSITION_CLOSED] != 0 - ): + else: position = self.find_in_range_from_percent(position, COVER_PAYLOAD) mqtt.async_publish( From f1af5b71e2964005da2b5612b41ec1ff73f0a59e Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 29 May 2020 00:50:23 +0200 Subject: [PATCH 237/406] Correct MQTT device trigger reconfiguration with same topic (#36234) --- homeassistant/components/mqtt/__init__.py | 1 + .../components/mqtt/device_trigger.py | 12 ++-- tests/components/mqtt/test_device_trigger.py | 62 ++++++++++++++++++- 3 files changed, 70 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index 64b25ad9486..a1eaf2f3a2a 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -887,6 +887,7 @@ class MQTT: This method is a coroutine. """ + _LOGGER.debug("Unsubscribing from %s", topic) async with self._paho_lock: result: int = None result, _ = await self.hass.async_add_executor_job( diff --git a/homeassistant/components/mqtt/device_trigger.py b/homeassistant/components/mqtt/device_trigger.py index 3b65243d078..7ff40ffe27d 100644 --- a/homeassistant/components/mqtt/device_trigger.py +++ b/homeassistant/components/mqtt/device_trigger.py @@ -138,13 +138,17 @@ class Trigger: self.remove_signal = remove_signal self.type = config[CONF_TYPE] self.subtype = config[CONF_SUBTYPE] - self.topic = config[CONF_TOPIC] self.payload = config[CONF_PAYLOAD] self.qos = config[CONF_QOS] + topic_changed = self.topic != config[CONF_TOPIC] + self.topic = config[CONF_TOPIC] - # Unsubscribe+subscribe if this trigger is in use - for trig in self.trigger_instances: - await trig.async_attach_trigger() + # Unsubscribe+subscribe if this trigger is in use and topic has changed + # If topic is same unsubscribe+subscribe will execute in the wrong order + # because unsubscribe is done with help of async_create_task + if topic_changed: + for trig in self.trigger_instances: + await trig.async_attach_trigger() def detach_trigger(self): """Remove MQTT device trigger.""" diff --git a/tests/components/mqtt/test_device_trigger.py b/tests/components/mqtt/test_device_trigger.py index f77ccca57ef..aa7dc746951 100644 --- a/tests/components/mqtt/test_device_trigger.py +++ b/tests/components/mqtt/test_device_trigger.py @@ -14,6 +14,7 @@ from tests.common import ( assert_lists_same, async_fire_mqtt_message, async_get_device_automations, + async_mock_mqtt_component, async_mock_service, mock_device_registry, mock_registry, @@ -456,7 +457,7 @@ async def test_if_fires_on_mqtt_message_after_update( await hass.async_block_till_done() assert len(calls) == 1 - # Update the trigger + # Update the trigger with different topic async_fire_mqtt_message(hass, "homeassistant/device_automation/bla1/config", data2) await hass.async_block_till_done() @@ -468,6 +469,65 @@ async def test_if_fires_on_mqtt_message_after_update( await hass.async_block_till_done() assert len(calls) == 2 + # Update the trigger with same topic + async_fire_mqtt_message(hass, "homeassistant/device_automation/bla1/config", data2) + await hass.async_block_till_done() + + async_fire_mqtt_message(hass, "foobar/triggers/button1", "") + await hass.async_block_till_done() + assert len(calls) == 2 + + async_fire_mqtt_message(hass, "foobar/triggers/buttonOne", "") + await hass.async_block_till_done() + assert len(calls) == 3 + + +async def test_no_resubscribe_same_topic(hass, device_reg, mqtt_mock): + """Test subscription to topics without change.""" + mock_mqtt = await async_mock_mqtt_component(hass) + config_entry = MockConfigEntry(domain=DOMAIN, data={}) + config_entry.add_to_hass(hass) + await async_start(hass, "homeassistant", {}, config_entry) + + data1 = ( + '{ "automation_type":"trigger",' + ' "device":{"identifiers":["0AFFD2"]},' + ' "topic": "foobar/triggers/button1",' + ' "type": "button_short_press",' + ' "subtype": "button_1" }' + ) + async_fire_mqtt_message(hass, "homeassistant/device_automation/bla1/config", data1) + await hass.async_block_till_done() + device_entry = device_reg.async_get_device({("mqtt", "0AFFD2")}, set()) + + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: [ + { + "trigger": { + "platform": "device", + "domain": DOMAIN, + "device_id": device_entry.id, + "discovery_id": "bla1", + "type": "button_short_press", + "subtype": "button_1", + }, + "action": { + "service": "test.automation", + "data_template": {"some": ("short_press")}, + }, + }, + ] + }, + ) + + call_count = mock_mqtt.async_subscribe.call_count + async_fire_mqtt_message(hass, "homeassistant/device_automation/bla1/config", data1) + await hass.async_block_till_done() + assert mock_mqtt.async_subscribe.call_count == call_count + async def test_not_fires_on_mqtt_message_after_remove_by_mqtt( hass, device_reg, calls, mqtt_mock From b928d5d4b6ef0c4a6125e21df78981c494fbc8de Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 28 May 2020 16:21:58 -0700 Subject: [PATCH 238/406] Bump hass_nabucasa to 0.34.4 (#36236) --- 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 fcd6738b77c..33a13b9462d 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.3"], + "requirements": ["hass-nabucasa==0.34.4"], "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 637bddb2d9e..87f0b1feb76 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -11,7 +11,7 @@ ciso8601==2.1.3 cryptography==2.9.2 defusedxml==0.6.0 distro==1.5.0 -hass-nabucasa==0.34.3 +hass-nabucasa==0.34.4 home-assistant-frontend==20200519.5 importlib-metadata==1.6.0 jinja2>=2.11.1 diff --git a/requirements_all.txt b/requirements_all.txt index 707f3dae386..a60ad79707f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -707,7 +707,7 @@ habitipy==0.2.0 hangups==0.4.9 # homeassistant.components.cloud -hass-nabucasa==0.34.3 +hass-nabucasa==0.34.4 # homeassistant.components.mqtt hbmqtt==0.9.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 5afb1307533..bc1ca515e55 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -303,7 +303,7 @@ ha-ffmpeg==2.0 hangups==0.4.9 # homeassistant.components.cloud -hass-nabucasa==0.34.3 +hass-nabucasa==0.34.4 # homeassistant.components.mqtt hbmqtt==0.9.5 From f9aff4fc415a936f4ea3485a61ae642925a9e4a7 Mon Sep 17 00:00:00 2001 From: gjbadros Date: Thu, 28 May 2020 16:26:03 -0700 Subject: [PATCH 239/406] Use new elkm1-lib module's LD log data support to correctly identify user_ids (#36211) * Requires 0.7.18 of elkm1 library to decode LD messages, and uses those messages to reliably set the arming/disarming user when there are more than one area. See https://github.com/home-assistant/core/issues/35310. * Fixed typo * Fixed off by one error -- LD command reports 1-based user-numbers which is the changed_by_id we want, but we need 0-based indices as argument to username. * Bump required version of elkm1, remove logging message I was using for testing; prepping for PR. * Black formatted * Fixed bug whereby I needed to ref elements when running against released build of elkm1-lib --- .../components/elkm1/alarm_control_panel.py | 21 +++++++++++++++++-- homeassistant/components/elkm1/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 22 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/elkm1/alarm_control_panel.py b/homeassistant/components/elkm1/alarm_control_panel.py index 3e9ab114837..1c299e68803 100644 --- a/homeassistant/components/elkm1/alarm_control_panel.py +++ b/homeassistant/components/elkm1/alarm_control_panel.py @@ -125,8 +125,10 @@ class ElkArea(ElkAttachedEntity, AlarmControlPanelEntity, RestoreEntity): async def async_added_to_hass(self): """Register callback for ElkM1 changes.""" await super().async_added_to_hass() - for keypad in self._elk.keypads: - keypad.add_callback(self._watch_keypad) + if len(self._elk.areas.elements) == 1: + for keypad in self._elk.keypads: + keypad.add_callback(self._watch_keypad) + self._element.add_callback(self._watch_area) # We do not get changed_by back from resync. last_state = await self.async_get_last_state() @@ -152,6 +154,21 @@ class ElkArea(ElkAttachedEntity, AlarmControlPanelEntity, RestoreEntity): self._changed_by = username(self._elk, keypad.last_user) self.async_write_ha_state() + def _watch_area(self, area, changeset): + if not changeset.get("log_event"): + return + self._changed_by_keypad = None + self._changed_by_id = area.log_number + self._changed_by = username(self._elk, area.log_number - 1) + self._changed_by_time = "%04d-%02d-%02dT%02d:%02d" % ( + area.log_year, + area.log_month, + area.log_day, + area.log_hour, + area.log_minute, + ) + self.async_write_ha_state() + @property def code_format(self): """Return the alarm code format.""" diff --git a/homeassistant/components/elkm1/manifest.json b/homeassistant/components/elkm1/manifest.json index 5a88792208a..20b8195d5b8 100644 --- a/homeassistant/components/elkm1/manifest.json +++ b/homeassistant/components/elkm1/manifest.json @@ -2,7 +2,7 @@ "domain": "elkm1", "name": "Elk-M1 Control", "documentation": "https://www.home-assistant.io/integrations/elkm1", - "requirements": ["elkm1-lib==0.7.17"], + "requirements": ["elkm1-lib==0.7.18"], "codeowners": ["@bdraco"], "config_flow": true } diff --git a/requirements_all.txt b/requirements_all.txt index a60ad79707f..11f5d9a79e1 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -526,7 +526,7 @@ elgato==0.2.0 eliqonline==1.2.2 # homeassistant.components.elkm1 -elkm1-lib==0.7.17 +elkm1-lib==0.7.18 # homeassistant.components.emulated_roku emulated_roku==0.2.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index bc1ca515e55..4c62ea89b63 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -233,7 +233,7 @@ eebrightbox==0.0.4 elgato==0.2.0 # homeassistant.components.elkm1 -elkm1-lib==0.7.17 +elkm1-lib==0.7.18 # homeassistant.components.emulated_roku emulated_roku==0.2.1 From e1fd14e00ac3355cc7f829278f82fb5f8324f9bd Mon Sep 17 00:00:00 2001 From: Fredrik Erlandsson Date: Fri, 29 May 2020 01:30:01 +0200 Subject: [PATCH 240/406] Add support for zeroconf for Daikin (#35769) --- homeassistant/components/daikin/config_flow.py | 8 ++++++++ homeassistant/components/daikin/manifest.json | 1 + homeassistant/generated/zeroconf.py | 3 +++ tests/components/daikin/test_config_flow.py | 15 ++++++++++++--- 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/daikin/config_flow.py b/homeassistant/components/daikin/config_flow.py index cd5be5cef29..f999a5376b1 100644 --- a/homeassistant/components/daikin/config_flow.py +++ b/homeassistant/components/daikin/config_flow.py @@ -124,3 +124,11 @@ class FlowHandler(config_entries.ConfigFlow): self._abort_if_unique_id_configured() self.host = discovery_info[KEY_IP] return await self.async_step_user() + + async def async_step_zeroconf(self, discovery_info): + """Prepare configuration for a discovered Daikin device.""" + _LOGGER.debug("Zeroconf discovery_info: %s", discovery_info) + await self.async_set_unique_id(discovery_info.get(CONF_HOST)) + self._abort_if_unique_id_configured() + self.host = discovery_info.get(CONF_HOST) + return await self.async_step_user() diff --git a/homeassistant/components/daikin/manifest.json b/homeassistant/components/daikin/manifest.json index 2dbfa289808..d642b2dc820 100644 --- a/homeassistant/components/daikin/manifest.json +++ b/homeassistant/components/daikin/manifest.json @@ -5,5 +5,6 @@ "documentation": "https://www.home-assistant.io/integrations/daikin", "requirements": ["pydaikin==2.1.0"], "codeowners": ["@fredrike"], + "zeroconf": ["_dkapi._tcp.local."], "quality_scale": "platinum" } diff --git a/homeassistant/generated/zeroconf.py b/homeassistant/generated/zeroconf.py index 5982c23b3bd..ead2c0fa42d 100644 --- a/homeassistant/generated/zeroconf.py +++ b/homeassistant/generated/zeroconf.py @@ -16,6 +16,9 @@ ZEROCONF = { "_daap._tcp.local.": [ "forked_daapd" ], + "_dkapi._tcp.local.": [ + "daikin" + ], "_elg._tcp.local.": [ "elgato" ], diff --git a/tests/components/daikin/test_config_flow.py b/tests/components/daikin/test_config_flow.py index 25fc8ba26f2..989064afe6c 100644 --- a/tests/components/daikin/test_config_flow.py +++ b/tests/components/daikin/test_config_flow.py @@ -7,7 +7,12 @@ from aiohttp.web_exceptions import HTTPForbidden import pytest from homeassistant.components.daikin.const import KEY_IP, KEY_MAC -from homeassistant.config_entries import SOURCE_DISCOVERY, SOURCE_IMPORT, SOURCE_USER +from homeassistant.config_entries import ( + SOURCE_DISCOVERY, + SOURCE_IMPORT, + SOURCE_USER, + SOURCE_ZEROCONF, +) from homeassistant.const import CONF_HOST from homeassistant.data_entry_flow import ( RESULT_TYPE_ABORT, @@ -104,9 +109,13 @@ async def test_device_abort(hass, mock_daikin, s_effect, reason): @pytest.mark.parametrize( - "source, data, unique_id", [(SOURCE_DISCOVERY, {KEY_IP: HOST, KEY_MAC: MAC}, MAC)], + "source, data, unique_id", + [ + (SOURCE_DISCOVERY, {KEY_IP: HOST, KEY_MAC: MAC}, MAC), + (SOURCE_ZEROCONF, {CONF_HOST: HOST}, HOST), + ], ) -async def test_discovery(hass, mock_daikin, source, data, unique_id): +async def test_discovery_zeroconf(hass, mock_daikin, source, data, unique_id): """Test discovery/zeroconf step.""" result = await hass.config_entries.flow.async_init( "daikin", context={"source": source}, data=data, From 5183c40b235ba2dd97327f861d203f93bedcd78d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 28 May 2020 18:48:42 -0500 Subject: [PATCH 241/406] Periodically log when integrations are taking a while to setup (#36208) * Periodicly log when intergrations are taking a while to setup When one or more intergrations are taking a while to setup it is hard to determine which one is the cause. We can help narrow this down for the user with a periodic log message about which domains are still waiting to be setup every 30s. * 30 -> 60 per discussion * only log when the integration is actually doing setup * reduce, fix race in test --- homeassistant/bootstrap.py | 21 +++++++++++++++++- homeassistant/setup.py | 9 ++++++++ tests/test_bootstrap.py | 44 +++++++++++++++++++++++++++++++++++++- 3 files changed, 72 insertions(+), 2 deletions(-) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 32a7384c350..1a4a7de4c18 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -20,7 +20,7 @@ from homeassistant.const import ( ) from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.typing import ConfigType -from homeassistant.setup import DATA_SETUP, async_setup_component +from homeassistant.setup import DATA_SETUP, DATA_SETUP_STARTED, async_setup_component from homeassistant.util.logging import async_activate_log_queue_handler from homeassistant.util.package import async_get_user_site, is_virtual_env from homeassistant.util.yaml import clear_secret_cache @@ -32,6 +32,8 @@ ERROR_LOG_FILENAME = "home-assistant.log" # hass.data key for logging information. DATA_LOGGING = "logging" +LOG_SLOW_STARTUP_INTERVAL = 60 + DEBUGGER_INTEGRATIONS = {"ptvsd"} CORE_INTEGRATIONS = ("homeassistant", "persistent_notification") LOGGING_INTEGRATIONS = {"logger", "system_log", "sentry"} @@ -323,13 +325,30 @@ async def _async_set_up_integrations( ) -> None: """Set up all the integrations.""" + setup_started = hass.data[DATA_SETUP_STARTED] = {} + async def async_setup_multi_components(domains: Set[str]) -> None: """Set up multiple domains. Log on failure.""" + + async def _async_log_pending_setups() -> None: + """Periodic log of setups that are pending for longer than LOG_SLOW_STARTUP_INTERVAL.""" + while True: + await asyncio.sleep(LOG_SLOW_STARTUP_INTERVAL) + remaining = [domain for domain in domains if domain in setup_started] + + if remaining: + _LOGGER.info( + "Waiting on integrations to complete setup: %s", + ", ".join(remaining), + ) + futures = { domain: hass.async_create_task(async_setup_component(hass, domain, config)) for domain in domains } + log_task = hass.loop.create_task(_async_log_pending_setups()) await asyncio.wait(futures.values()) + log_task.cancel() errors = [domain for domain in domains if futures[domain].exception()] for domain in errors: exception = futures[domain].exception() diff --git a/homeassistant/setup.py b/homeassistant/setup.py index 9c20216d4ec..67d9200df61 100644 --- a/homeassistant/setup.py +++ b/homeassistant/setup.py @@ -10,11 +10,13 @@ from homeassistant.config import async_notify_setup_error from homeassistant.const import EVENT_COMPONENT_LOADED, PLATFORM_FORMAT from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.typing import ConfigType +from homeassistant.util import dt as dt_util _LOGGER = logging.getLogger(__name__) ATTR_COMPONENT = "component" +DATA_SETUP_STARTED = "setup_started" DATA_SETUP = "setup_tasks" DATA_DEPS_REQS = "deps_reqs_processed" @@ -155,6 +157,7 @@ async def _async_setup_component( start = timer() _LOGGER.info("Setting up %s", domain) + hass.data.setdefault(DATA_SETUP_STARTED, {})[domain] = dt_util.utcnow() if hasattr(component, "PLATFORM_SCHEMA"): # Entity components have their own warning @@ -181,6 +184,7 @@ async def _async_setup_component( ) else: log_error("No setup function defined.") + hass.data[DATA_SETUP_STARTED].pop(domain) return False result = await asyncio.wait_for(task, SLOW_SETUP_MAX_WAIT) @@ -191,10 +195,12 @@ async def _async_setup_component( domain, SLOW_SETUP_MAX_WAIT, ) + hass.data[DATA_SETUP_STARTED].pop(domain) return False except Exception: # pylint: disable=broad-except _LOGGER.exception("Error during setup of component %s", domain) async_notify_setup_error(hass, domain, integration.documentation) + hass.data[DATA_SETUP_STARTED].pop(domain) return False finally: end = timer() @@ -204,12 +210,14 @@ async def _async_setup_component( if result is False: log_error("Integration failed to initialize.") + hass.data[DATA_SETUP_STARTED].pop(domain) return False if result is not True: log_error( f"Integration {domain!r} did not return boolean if setup was " "successful. Disabling component." ) + hass.data[DATA_SETUP_STARTED].pop(domain) return False # Flush out async_setup calling create_task. Fragile but covered by test. @@ -224,6 +232,7 @@ async def _async_setup_component( ) hass.config.components.add(domain) + hass.data[DATA_SETUP_STARTED].pop(domain) # Cleanup if domain in hass.data[DATA_SETUP]: diff --git a/tests/test_bootstrap.py b/tests/test_bootstrap.py index fc535d7e3a5..9597dfa60b8 100644 --- a/tests/test_bootstrap.py +++ b/tests/test_bootstrap.py @@ -1,5 +1,6 @@ """Test the bootstrapping.""" # pylint: disable=protected-access +import asyncio import logging import os from unittest.mock import Mock @@ -249,6 +250,7 @@ async def test_setup_hass( mock_mount_local_lib_path, mock_ensure_config_exists, mock_process_ha_config_upgrade, + caplog, ): """Test it works.""" verbose = Mock() @@ -259,7 +261,7 @@ async def test_setup_hass( with patch( "homeassistant.config.async_hass_config_yaml", return_value={"browser": {}, "frontend": {}}, - ): + ), patch.object(bootstrap, "LOG_SLOW_STARTUP_INTERVAL", 5000): hass = await bootstrap.async_setup_hass( config_dir=get_test_config_dir(), verbose=verbose, @@ -270,6 +272,8 @@ async def test_setup_hass( safe_mode=False, ) + assert "Waiting on integrations to complete setup" not in caplog.text + assert "browser" in hass.config.components assert "safe_mode" not in hass.config.components @@ -286,6 +290,44 @@ async def test_setup_hass( assert len(mock_process_ha_config_upgrade.mock_calls) == 1 +async def test_setup_hass_takes_longer_than_log_slow_startup( + mock_enable_logging, + mock_is_virtual_env, + mock_mount_local_lib_path, + mock_ensure_config_exists, + mock_process_ha_config_upgrade, + caplog, +): + """Test it works.""" + verbose = Mock() + log_rotate_days = Mock() + log_file = Mock() + log_no_color = Mock() + + async def _async_setup_that_blocks_startup(*args, **kwargs): + await asyncio.sleep(0.6) + return True + + with patch( + "homeassistant.config.async_hass_config_yaml", + return_value={"browser": {}, "frontend": {}}, + ), patch.object(bootstrap, "LOG_SLOW_STARTUP_INTERVAL", 0.3), patch( + "homeassistant.components.frontend.async_setup", + side_effect=_async_setup_that_blocks_startup, + ): + await bootstrap.async_setup_hass( + config_dir=get_test_config_dir(), + verbose=verbose, + log_rotate_days=log_rotate_days, + log_file=log_file, + log_no_color=log_no_color, + skip_pip=True, + safe_mode=False, + ) + + assert "Waiting on integrations to complete setup" in caplog.text + + async def test_setup_hass_invalid_yaml( mock_enable_logging, mock_is_virtual_env, From 564fb1d1e508a6d4ab5cf48f3d4c4eb32dc53611 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Fri, 29 May 2020 00:04:16 +0000 Subject: [PATCH 242/406] [ci skip] Translation update --- .../components/acmeda/translations/ca.json | 3 ++ .../components/adguard/translations/bg.json | 3 +- .../components/adguard/translations/ca.json | 3 +- .../components/adguard/translations/da.json | 3 +- .../components/adguard/translations/de.json | 3 +- .../components/adguard/translations/en.json | 3 +- .../adguard/translations/es-419.json | 3 +- .../components/adguard/translations/es.json | 3 +- .../components/adguard/translations/fr.json | 3 +- .../components/adguard/translations/it.json | 3 +- .../components/adguard/translations/ko.json | 3 +- .../components/adguard/translations/lb.json | 3 +- .../components/adguard/translations/nl.json | 3 +- .../components/adguard/translations/no.json | 3 +- .../components/adguard/translations/pl.json | 3 +- .../adguard/translations/pt-BR.json | 3 +- .../components/adguard/translations/ru.json | 3 +- .../components/adguard/translations/sl.json | 3 +- .../components/adguard/translations/sv.json | 3 +- .../adguard/translations/zh-Hant.json | 3 +- .../components/airvisual/translations/ca.json | 4 --- .../components/airvisual/translations/de.json | 4 --- .../components/airvisual/translations/en.json | 4 --- .../airvisual/translations/es-419.json | 4 --- .../components/airvisual/translations/es.json | 6 +--- .../components/airvisual/translations/fr.json | 4 --- .../components/airvisual/translations/it.json | 4 --- .../components/airvisual/translations/ko.json | 4 --- .../components/airvisual/translations/lb.json | 4 --- .../components/airvisual/translations/nl.json | 4 --- .../components/airvisual/translations/no.json | 4 --- .../components/airvisual/translations/pl.json | 4 --- .../components/airvisual/translations/ru.json | 4 --- .../components/airvisual/translations/sl.json | 4 --- .../components/airvisual/translations/sv.json | 3 -- .../airvisual/translations/zh-Hant.json | 4 --- .../alarm_control_panel/translations/es.json | 4 +-- .../components/arcam_fmj/translations/ca.json | 3 +- .../components/arcam_fmj/translations/de.json | 3 +- .../components/arcam_fmj/translations/en.json | 3 +- .../components/arcam_fmj/translations/es.json | 3 +- .../components/arcam_fmj/translations/it.json | 3 +- .../components/arcam_fmj/translations/ko.json | 3 +- .../components/arcam_fmj/translations/lb.json | 3 +- .../components/arcam_fmj/translations/nl.json | 3 +- .../components/arcam_fmj/translations/no.json | 3 +- .../components/arcam_fmj/translations/pl.json | 3 +- .../arcam_fmj/translations/pt-BR.json | 3 +- .../components/arcam_fmj/translations/ru.json | 3 +- .../arcam_fmj/translations/zh-Hant.json | 3 +- .../components/atag/translations/ca.json | 3 +- .../components/atag/translations/en.json | 5 +-- .../components/atag/translations/es.json | 3 +- .../components/atag/translations/lb.json | 3 +- .../components/atag/translations/no.json | 5 +-- .../components/atag/translations/ru.json | 5 +-- .../components/atag/translations/zh-Hant.json | 5 +-- .../components/axis/translations/bg.json | 3 +- .../components/axis/translations/ca.json | 3 +- .../components/axis/translations/da.json | 3 +- .../components/axis/translations/de.json | 3 +- .../components/axis/translations/en.json | 3 +- .../components/axis/translations/es-419.json | 3 +- .../components/axis/translations/es.json | 3 +- .../components/axis/translations/fi.json | 3 +- .../components/axis/translations/fr.json | 3 +- .../components/axis/translations/hu.json | 3 +- .../components/axis/translations/it.json | 3 +- .../components/axis/translations/ko.json | 3 +- .../components/axis/translations/lb.json | 3 +- .../components/axis/translations/nl.json | 3 +- .../components/axis/translations/no.json | 3 +- .../components/axis/translations/pl.json | 3 +- .../components/axis/translations/pt-BR.json | 3 +- .../components/axis/translations/pt.json | 3 +- .../components/axis/translations/ru.json | 3 +- .../components/axis/translations/sl.json | 3 +- .../components/axis/translations/sv.json | 3 +- .../components/axis/translations/zh-Hant.json | 3 +- .../components/blebox/translations/lb.json | 1 + .../components/blink/translations/lb.json | 11 ++++++ .../components/braviatv/translations/ca.json | 3 +- .../components/braviatv/translations/en.json | 3 +- .../components/braviatv/translations/es.json | 3 +- .../components/braviatv/translations/lb.json | 3 +- .../components/braviatv/translations/no.json | 3 +- .../components/braviatv/translations/ru.json | 3 +- .../braviatv/translations/zh-Hant.json | 3 +- .../components/brother/translations/ca.json | 3 +- .../components/brother/translations/da.json | 3 +- .../components/brother/translations/de.json | 3 +- .../components/brother/translations/en.json | 3 +- .../brother/translations/es-419.json | 3 +- .../components/brother/translations/es.json | 3 +- .../components/brother/translations/fr.json | 3 +- .../components/brother/translations/hu.json | 3 +- .../components/brother/translations/it.json | 3 +- .../components/brother/translations/ko.json | 3 +- .../components/brother/translations/lb.json | 3 +- .../components/brother/translations/nl.json | 3 +- .../components/brother/translations/no.json | 3 +- .../components/brother/translations/pl.json | 3 +- .../brother/translations/pt-BR.json | 3 +- .../components/brother/translations/ru.json | 3 +- .../components/brother/translations/sl.json | 3 +- .../components/brother/translations/sv.json | 3 +- .../brother/translations/zh-Hant.json | 3 +- .../components/bsblan/translations/no.json | 4 +-- .../components/cast/translations/bg.json | 3 +- .../components/cast/translations/ca.json | 3 +- .../components/cast/translations/cs.json | 3 +- .../components/cast/translations/da.json | 3 +- .../components/cast/translations/de.json | 3 +- .../components/cast/translations/en.json | 3 +- .../components/cast/translations/es-419.json | 3 +- .../components/cast/translations/es.json | 3 +- .../components/cast/translations/fi.json | 3 +- .../components/cast/translations/fr.json | 3 +- .../components/cast/translations/he.json | 3 +- .../components/cast/translations/hu.json | 3 +- .../components/cast/translations/id.json | 3 +- .../components/cast/translations/it.json | 3 +- .../components/cast/translations/ja.json | 3 +- .../components/cast/translations/ko.json | 3 +- .../components/cast/translations/lb.json | 3 +- .../components/cast/translations/nl.json | 3 +- .../components/cast/translations/nn.json | 3 +- .../components/cast/translations/no.json | 3 +- .../components/cast/translations/pl.json | 3 +- .../components/cast/translations/pt-BR.json | 3 +- .../components/cast/translations/pt.json | 3 +- .../components/cast/translations/ro.json | 3 +- .../components/cast/translations/ru.json | 3 +- .../components/cast/translations/sl.json | 3 +- .../components/cast/translations/sv.json | 3 +- .../components/cast/translations/th.json | 3 +- .../components/cast/translations/vi.json | 3 +- .../components/cast/translations/zh-Hans.json | 3 +- .../components/cast/translations/zh-Hant.json | 3 +- .../components/daikin/translations/bg.json | 4 +-- .../components/daikin/translations/ca.json | 4 +-- .../components/daikin/translations/da.json | 4 +-- .../components/daikin/translations/de.json | 4 +-- .../components/daikin/translations/en.json | 4 +-- .../daikin/translations/es-419.json | 4 +-- .../components/daikin/translations/es.json | 4 +-- .../components/daikin/translations/fr.json | 4 +-- .../components/daikin/translations/hu.json | 4 +-- .../components/daikin/translations/it.json | 4 +-- .../components/daikin/translations/ko.json | 4 +-- .../components/daikin/translations/lb.json | 4 +-- .../components/daikin/translations/nl.json | 4 +-- .../components/daikin/translations/no.json | 8 ++--- .../components/daikin/translations/pl.json | 4 +-- .../components/daikin/translations/pt-BR.json | 4 +-- .../components/daikin/translations/pt.json | 4 +-- .../components/daikin/translations/ru.json | 4 +-- .../components/daikin/translations/sl.json | 4 +-- .../components/daikin/translations/sv.json | 4 +-- .../daikin/translations/zh-Hans.json | 4 +-- .../daikin/translations/zh-Hant.json | 4 +-- .../components/deconz/translations/bg.json | 9 ----- .../components/deconz/translations/ca.json | 15 ++------ .../components/deconz/translations/cs.json | 9 ----- .../components/deconz/translations/cy.json | 9 ----- .../components/deconz/translations/da.json | 9 ----- .../components/deconz/translations/de.json | 15 ++------ .../components/deconz/translations/en.json | 15 ++------ .../deconz/translations/es-419.json | 15 ++------ .../components/deconz/translations/es.json | 15 ++------ .../components/deconz/translations/fi.json | 9 ----- .../components/deconz/translations/fr.json | 12 +------ .../components/deconz/translations/he.json | 9 ----- .../components/deconz/translations/hu.json | 9 ----- .../components/deconz/translations/id.json | 9 ----- .../components/deconz/translations/it.json | 15 ++------ .../components/deconz/translations/ja.json | 8 ----- .../components/deconz/translations/ko.json | 15 ++------ .../components/deconz/translations/lb.json | 15 ++------ .../components/deconz/translations/nl.json | 15 ++------ .../components/deconz/translations/nn.json | 9 ----- .../components/deconz/translations/no.json | 15 ++------ .../components/deconz/translations/pl.json | 15 ++------ .../components/deconz/translations/pt-BR.json | 9 ----- .../components/deconz/translations/pt.json | 9 ----- .../components/deconz/translations/ro.json | 5 --- .../components/deconz/translations/ru.json | 15 ++------ .../components/deconz/translations/sl.json | 15 ++------ .../components/deconz/translations/sv.json | 9 ----- .../components/deconz/translations/vi.json | 7 ---- .../deconz/translations/zh-Hans.json | 9 ----- .../deconz/translations/zh-Hant.json | 15 ++------ .../devolo_home_control/translations/ca.json | 3 -- .../devolo_home_control/translations/de.json | 3 -- .../devolo_home_control/translations/en.json | 3 -- .../devolo_home_control/translations/es.json | 3 -- .../devolo_home_control/translations/fi.json | 1 - .../devolo_home_control/translations/fr.json | 1 - .../devolo_home_control/translations/it.json | 3 -- .../devolo_home_control/translations/ko.json | 3 -- .../devolo_home_control/translations/lb.json | 3 -- .../devolo_home_control/translations/no.json | 5 +-- .../devolo_home_control/translations/pl.json | 3 -- .../devolo_home_control/translations/ru.json | 3 -- .../devolo_home_control/translations/sv.json | 3 -- .../translations/zh-Hant.json | 3 -- .../components/directv/translations/ca.json | 6 ++-- .../components/directv/translations/de.json | 6 ++-- .../components/directv/translations/en.json | 6 ++-- .../directv/translations/es-419.json | 6 ++-- .../components/directv/translations/es.json | 6 ++-- .../components/directv/translations/fr.json | 6 ++-- .../components/directv/translations/it.json | 6 ++-- .../components/directv/translations/ko.json | 6 ++-- .../components/directv/translations/lb.json | 6 ++-- .../components/directv/translations/nl.json | 6 ++-- .../components/directv/translations/no.json | 6 ++-- .../components/directv/translations/pl.json | 6 ++-- .../components/directv/translations/ru.json | 6 ++-- .../components/directv/translations/sl.json | 6 ++-- .../directv/translations/zh-Hant.json | 6 ++-- .../components/elgato/translations/ca.json | 3 +- .../components/elgato/translations/da.json | 3 +- .../components/elgato/translations/de.json | 3 +- .../components/elgato/translations/en.json | 3 +- .../elgato/translations/es-419.json | 3 +- .../components/elgato/translations/es.json | 3 +- .../components/elgato/translations/fr.json | 3 +- .../components/elgato/translations/it.json | 3 +- .../components/elgato/translations/ko.json | 3 +- .../components/elgato/translations/lb.json | 3 +- .../components/elgato/translations/nl.json | 3 +- .../components/elgato/translations/no.json | 3 +- .../components/elgato/translations/pl.json | 3 +- .../components/elgato/translations/ru.json | 3 +- .../components/elgato/translations/sl.json | 3 +- .../components/elgato/translations/sv.json | 3 +- .../elgato/translations/zh-Hant.json | 3 +- .../components/esphome/translations/bg.json | 6 ++-- .../components/esphome/translations/ca.json | 6 ++-- .../components/esphome/translations/cs.json | 6 ++-- .../components/esphome/translations/da.json | 6 ++-- .../components/esphome/translations/de.json | 6 ++-- .../components/esphome/translations/en.json | 6 ++-- .../esphome/translations/es-419.json | 6 ++-- .../components/esphome/translations/es.json | 6 ++-- .../components/esphome/translations/fi.json | 6 +--- .../components/esphome/translations/fr.json | 6 ++-- .../components/esphome/translations/hu.json | 6 ++-- .../components/esphome/translations/id.json | 6 +--- .../components/esphome/translations/it.json | 6 ++-- .../components/esphome/translations/ko.json | 6 ++-- .../components/esphome/translations/lb.json | 6 ++-- .../components/esphome/translations/nl.json | 6 ++-- .../components/esphome/translations/nn.json | 3 -- .../components/esphome/translations/no.json | 6 ++-- .../components/esphome/translations/pl.json | 6 ++-- .../esphome/translations/pt-BR.json | 6 ++-- .../components/esphome/translations/pt.json | 6 ++-- .../components/esphome/translations/ru.json | 6 ++-- .../components/esphome/translations/sl.json | 6 ++-- .../components/esphome/translations/sv.json | 6 ++-- .../components/esphome/translations/th.json | 6 +--- .../components/esphome/translations/uk.json | 6 ++-- .../esphome/translations/zh-Hans.json | 6 ++-- .../esphome/translations/zh-Hant.json | 6 ++-- .../forked_daapd/translations/no.json | 2 +- .../components/fritzbox/translations/ca.json | 6 ++-- .../components/fritzbox/translations/de.json | 6 ++-- .../components/fritzbox/translations/en.json | 6 ++-- .../fritzbox/translations/es-419.json | 6 ++-- .../components/fritzbox/translations/es.json | 6 ++-- .../components/fritzbox/translations/fi.json | 3 +- .../components/fritzbox/translations/fr.json | 6 ++-- .../components/fritzbox/translations/it.json | 6 ++-- .../components/fritzbox/translations/ko.json | 6 ++-- .../components/fritzbox/translations/lb.json | 6 ++-- .../components/fritzbox/translations/nl.json | 6 ++-- .../components/fritzbox/translations/no.json | 6 ++-- .../components/fritzbox/translations/pl.json | 6 ++-- .../components/fritzbox/translations/ru.json | 6 ++-- .../components/fritzbox/translations/sl.json | 6 ++-- .../fritzbox/translations/zh-Hant.json | 6 ++-- .../components/gogogate2/translations/lb.json | 11 +++++- .../components/guardian/translations/ca.json | 22 ++++++++++++ .../components/guardian/translations/es.json | 22 ++++++++++++ .../components/guardian/translations/lb.json | 22 ++++++++++++ .../components/guardian/translations/no.json | 22 ++++++++++++ .../components/guardian/translations/ru.json | 22 ++++++++++++ .../guardian/translations/zh-Hant.json | 22 ++++++++++++ .../components/heos/translations/bg.json | 1 - .../components/heos/translations/ca.json | 1 - .../components/heos/translations/da.json | 1 - .../components/heos/translations/de.json | 1 - .../components/heos/translations/en.json | 1 - .../components/heos/translations/es-419.json | 1 - .../components/heos/translations/es.json | 1 - .../components/heos/translations/fi.json | 3 -- .../components/heos/translations/fr.json | 1 - .../components/heos/translations/hu.json | 1 - .../components/heos/translations/it.json | 1 - .../components/heos/translations/ko.json | 1 - .../components/heos/translations/lb.json | 1 - .../components/heos/translations/nl.json | 1 - .../components/heos/translations/no.json | 1 - .../components/heos/translations/pl.json | 1 - .../components/heos/translations/pt-BR.json | 1 - .../components/heos/translations/pt.json | 1 - .../components/heos/translations/ru.json | 1 - .../components/heos/translations/sl.json | 1 - .../components/heos/translations/sv.json | 1 - .../components/heos/translations/zh-Hant.json | 1 - .../hisense_aehw4a1/translations/bg.json | 3 +- .../hisense_aehw4a1/translations/ca.json | 3 +- .../hisense_aehw4a1/translations/da.json | 3 +- .../hisense_aehw4a1/translations/de.json | 3 +- .../hisense_aehw4a1/translations/en.json | 3 +- .../hisense_aehw4a1/translations/es-419.json | 3 +- .../hisense_aehw4a1/translations/es.json | 3 +- .../hisense_aehw4a1/translations/fr.json | 3 +- .../hisense_aehw4a1/translations/hu.json | 3 +- .../hisense_aehw4a1/translations/it.json | 3 +- .../hisense_aehw4a1/translations/ko.json | 3 +- .../hisense_aehw4a1/translations/lb.json | 3 +- .../hisense_aehw4a1/translations/nl.json | 3 +- .../hisense_aehw4a1/translations/no.json | 3 +- .../hisense_aehw4a1/translations/pl.json | 3 +- .../hisense_aehw4a1/translations/ru.json | 3 +- .../hisense_aehw4a1/translations/sl.json | 3 +- .../hisense_aehw4a1/translations/sv.json | 3 +- .../hisense_aehw4a1/translations/zh-Hant.json | 3 +- .../components/icloud/translations/lb.json | 1 + .../components/ios/translations/bg.json | 3 +- .../components/ios/translations/ca.json | 3 +- .../components/ios/translations/cs.json | 3 +- .../components/ios/translations/da.json | 3 +- .../components/ios/translations/de.json | 3 +- .../components/ios/translations/en.json | 3 +- .../components/ios/translations/es-419.json | 3 +- .../components/ios/translations/es.json | 3 +- .../components/ios/translations/fr.json | 3 +- .../components/ios/translations/he.json | 3 +- .../components/ios/translations/hu.json | 3 +- .../components/ios/translations/id.json | 3 +- .../components/ios/translations/it.json | 3 +- .../components/ios/translations/ko.json | 3 +- .../components/ios/translations/lb.json | 3 +- .../components/ios/translations/nl.json | 3 +- .../components/ios/translations/nn.json | 3 +- .../components/ios/translations/no.json | 3 +- .../components/ios/translations/pl.json | 3 +- .../components/ios/translations/pt-BR.json | 3 +- .../components/ios/translations/pt.json | 3 +- .../components/ios/translations/ro.json | 3 +- .../components/ios/translations/ru.json | 3 +- .../components/ios/translations/sl.json | 3 +- .../components/ios/translations/sv.json | 3 +- .../components/ios/translations/zh-Hans.json | 3 +- .../components/ios/translations/zh-Hant.json | 3 +- .../components/ipp/translations/no.json | 2 +- .../components/izone/translations/bg.json | 3 +- .../components/izone/translations/ca.json | 3 +- .../components/izone/translations/da.json | 3 +- .../components/izone/translations/de.json | 3 +- .../components/izone/translations/en.json | 3 +- .../components/izone/translations/es-419.json | 3 +- .../components/izone/translations/es.json | 3 +- .../components/izone/translations/fr.json | 3 +- .../components/izone/translations/hu.json | 3 +- .../components/izone/translations/it.json | 3 +- .../components/izone/translations/ko.json | 3 +- .../components/izone/translations/lb.json | 3 +- .../components/izone/translations/nl.json | 3 +- .../components/izone/translations/no.json | 3 +- .../components/izone/translations/pl.json | 3 +- .../components/izone/translations/ru.json | 3 +- .../components/izone/translations/sl.json | 3 +- .../components/izone/translations/sv.json | 3 +- .../izone/translations/zh-Hant.json | 3 +- .../components/konnected/translations/ca.json | 3 +- .../components/konnected/translations/da.json | 3 +- .../components/konnected/translations/de.json | 3 +- .../components/konnected/translations/en.json | 3 +- .../konnected/translations/es-419.json | 3 +- .../components/konnected/translations/es.json | 3 +- .../components/konnected/translations/fr.json | 3 +- .../components/konnected/translations/it.json | 3 +- .../components/konnected/translations/ko.json | 3 +- .../components/konnected/translations/lb.json | 3 +- .../components/konnected/translations/nl.json | 3 +- .../components/konnected/translations/no.json | 3 +- .../components/konnected/translations/pl.json | 3 +- .../konnected/translations/pt-BR.json | 3 +- .../components/konnected/translations/ru.json | 3 +- .../components/konnected/translations/sl.json | 3 +- .../components/konnected/translations/sv.json | 3 +- .../konnected/translations/zh-Hant.json | 3 +- .../components/lifx/translations/bg.json | 3 +- .../components/lifx/translations/ca.json | 3 +- .../components/lifx/translations/cs.json | 3 +- .../components/lifx/translations/da.json | 3 +- .../components/lifx/translations/de.json | 3 +- .../components/lifx/translations/en.json | 3 +- .../components/lifx/translations/es-419.json | 3 +- .../components/lifx/translations/es.json | 3 +- .../components/lifx/translations/fi.json | 3 +- .../components/lifx/translations/fr.json | 3 +- .../components/lifx/translations/hu.json | 3 +- .../components/lifx/translations/it.json | 3 +- .../components/lifx/translations/ko.json | 3 +- .../components/lifx/translations/lb.json | 3 +- .../components/lifx/translations/nl.json | 3 +- .../components/lifx/translations/no.json | 3 +- .../components/lifx/translations/pl.json | 3 +- .../components/lifx/translations/pt-BR.json | 3 +- .../components/lifx/translations/pt.json | 3 +- .../components/lifx/translations/ro.json | 3 +- .../components/lifx/translations/ru.json | 3 +- .../components/lifx/translations/sl.json | 3 +- .../components/lifx/translations/sv.json | 3 +- .../components/lifx/translations/zh-Hans.json | 3 +- .../components/lifx/translations/zh-Hant.json | 3 +- .../lutron_caseta/translations/ca.json | 3 +- .../lutron_caseta/translations/en.json | 3 +- .../lutron_caseta/translations/es.json | 3 +- .../lutron_caseta/translations/it.json | 3 +- .../lutron_caseta/translations/ko.json | 3 +- .../lutron_caseta/translations/lb.json | 3 +- .../lutron_caseta/translations/no.json | 3 +- .../lutron_caseta/translations/ru.json | 3 +- .../lutron_caseta/translations/zh-Hant.json | 3 +- .../mobile_app/translations/bg.json | 3 +- .../mobile_app/translations/ca.json | 3 +- .../mobile_app/translations/cs.json | 3 +- .../mobile_app/translations/da.json | 3 +- .../mobile_app/translations/de.json | 3 +- .../mobile_app/translations/en.json | 3 +- .../mobile_app/translations/es-419.json | 3 +- .../mobile_app/translations/es.json | 3 +- .../mobile_app/translations/fr.json | 3 +- .../mobile_app/translations/hu.json | 3 +- .../mobile_app/translations/it.json | 3 +- .../mobile_app/translations/ko.json | 3 +- .../mobile_app/translations/lb.json | 3 +- .../mobile_app/translations/nl.json | 3 +- .../mobile_app/translations/no.json | 3 +- .../mobile_app/translations/pl.json | 3 +- .../mobile_app/translations/pt-BR.json | 3 +- .../mobile_app/translations/ru.json | 3 +- .../mobile_app/translations/sl.json | 3 +- .../mobile_app/translations/sv.json | 3 +- .../mobile_app/translations/uk.json | 3 +- .../mobile_app/translations/vi.json | 3 +- .../mobile_app/translations/zh-Hans.json | 3 +- .../mobile_app/translations/zh-Hant.json | 3 +- .../components/mqtt/translations/bg.json | 3 +- .../components/mqtt/translations/ca.json | 3 +- .../components/mqtt/translations/cs.json | 3 +- .../components/mqtt/translations/da.json | 3 +- .../components/mqtt/translations/de.json | 3 +- .../components/mqtt/translations/en.json | 3 +- .../components/mqtt/translations/es-419.json | 3 +- .../components/mqtt/translations/es.json | 3 +- .../components/mqtt/translations/fi.json | 3 +- .../components/mqtt/translations/fr.json | 3 +- .../components/mqtt/translations/he.json | 3 +- .../components/mqtt/translations/hr.json | 3 +- .../components/mqtt/translations/hu.json | 3 +- .../components/mqtt/translations/id.json | 3 +- .../components/mqtt/translations/it.json | 3 +- .../components/mqtt/translations/ko.json | 3 +- .../components/mqtt/translations/lb.json | 3 +- .../components/mqtt/translations/nl.json | 3 +- .../components/mqtt/translations/nn.json | 3 +- .../components/mqtt/translations/no.json | 3 +- .../components/mqtt/translations/pl.json | 3 +- .../components/mqtt/translations/pt-BR.json | 3 +- .../components/mqtt/translations/pt.json | 3 +- .../components/mqtt/translations/ro.json | 3 +- .../components/mqtt/translations/ru.json | 3 +- .../components/mqtt/translations/sl.json | 3 +- .../components/mqtt/translations/sv.json | 3 +- .../components/mqtt/translations/zh-Hans.json | 3 +- .../components/mqtt/translations/zh-Hant.json | 3 +- .../components/openuv/translations/ca.json | 3 ++ .../components/openuv/translations/en.json | 2 +- .../components/openuv/translations/es.json | 3 ++ .../components/openuv/translations/lb.json | 3 ++ .../components/openuv/translations/ru.json | 3 ++ .../openuv/translations/zh-Hant.json | 3 ++ .../components/pi_hole/translations/lb.json | 6 ++++ .../components/plex/translations/bg.json | 4 --- .../components/plex/translations/ca.json | 4 --- .../components/plex/translations/da.json | 4 --- .../components/plex/translations/de.json | 4 --- .../components/plex/translations/en.json | 4 --- .../components/plex/translations/es-419.json | 4 --- .../components/plex/translations/es.json | 4 --- .../components/plex/translations/fr.json | 4 --- .../components/plex/translations/hu.json | 4 --- .../components/plex/translations/it.json | 4 --- .../components/plex/translations/ko.json | 4 --- .../components/plex/translations/lb.json | 4 --- .../components/plex/translations/nl.json | 4 --- .../components/plex/translations/no.json | 6 ---- .../components/plex/translations/pl.json | 4 --- .../components/plex/translations/ru.json | 4 --- .../components/plex/translations/sl.json | 4 --- .../components/plex/translations/sv.json | 4 --- .../components/plex/translations/zh-Hant.json | 4 --- .../components/plugwise/translations/ca.json | 22 ++++++++++++ .../components/plugwise/translations/en.json | 36 +++++++++---------- .../components/plugwise/translations/es.json | 22 ++++++++++++ .../components/roku/translations/ca.json | 3 +- .../components/roku/translations/de.json | 3 +- .../components/roku/translations/en.json | 3 +- .../components/roku/translations/es-419.json | 3 +- .../components/roku/translations/es.json | 3 +- .../components/roku/translations/fr.json | 3 +- .../components/roku/translations/it.json | 3 +- .../components/roku/translations/ko.json | 3 +- .../components/roku/translations/lb.json | 3 +- .../components/roku/translations/nl.json | 3 +- .../components/roku/translations/no.json | 3 +- .../components/roku/translations/pl.json | 3 +- .../components/roku/translations/pt-BR.json | 3 +- .../components/roku/translations/ru.json | 3 +- .../components/roku/translations/sl.json | 3 +- .../components/roku/translations/zh-Hant.json | 3 +- .../components/roomba/translations/ca.json | 4 +-- .../components/roomba/translations/de.json | 4 +-- .../components/roomba/translations/en.json | 4 +-- .../roomba/translations/es-419.json | 4 +-- .../components/roomba/translations/es.json | 4 +-- .../components/roomba/translations/fr.json | 4 --- .../components/roomba/translations/it.json | 4 +-- .../components/roomba/translations/ko.json | 4 +-- .../components/roomba/translations/lb.json | 4 +-- .../components/roomba/translations/nl.json | 4 +-- .../components/roomba/translations/no.json | 4 +-- .../components/roomba/translations/pl.json | 4 +-- .../components/roomba/translations/pt-BR.json | 4 +-- .../components/roomba/translations/pt.json | 4 +-- .../components/roomba/translations/ru.json | 4 +-- .../components/roomba/translations/sl.json | 4 +-- .../components/roomba/translations/sv.json | 4 +-- .../roomba/translations/zh-Hant.json | 4 +-- .../components/samsungtv/translations/ca.json | 3 +- .../components/samsungtv/translations/da.json | 3 +- .../components/samsungtv/translations/de.json | 3 +- .../components/samsungtv/translations/en.json | 3 +- .../samsungtv/translations/es-419.json | 3 +- .../components/samsungtv/translations/es.json | 3 +- .../components/samsungtv/translations/fr.json | 3 +- .../components/samsungtv/translations/hu.json | 3 +- .../components/samsungtv/translations/it.json | 3 +- .../components/samsungtv/translations/ko.json | 3 +- .../components/samsungtv/translations/lb.json | 3 +- .../components/samsungtv/translations/nl.json | 3 +- .../components/samsungtv/translations/no.json | 3 +- .../components/samsungtv/translations/pl.json | 3 +- .../components/samsungtv/translations/ru.json | 3 +- .../components/samsungtv/translations/sl.json | 3 +- .../components/samsungtv/translations/sv.json | 3 +- .../components/samsungtv/translations/tr.json | 3 +- .../samsungtv/translations/zh-Hant.json | 3 +- .../components/smhi/translations/fi.json | 1 + .../components/songpal/translations/ca.json | 9 ++--- .../components/songpal/translations/de.json | 9 ++--- .../components/songpal/translations/en.json | 9 ++--- .../components/songpal/translations/es.json | 9 ++--- .../components/songpal/translations/fi.json | 9 ++--- .../components/songpal/translations/fr.json | 6 +--- .../components/songpal/translations/it.json | 9 ++--- .../components/songpal/translations/ko.json | 9 ++--- .../components/songpal/translations/lb.json | 9 ++--- .../components/songpal/translations/no.json | 10 ++---- .../components/songpal/translations/pl.json | 9 ++--- .../components/songpal/translations/ru.json | 9 ++--- .../components/songpal/translations/sv.json | 9 ++--- .../songpal/translations/zh-Hant.json | 9 ++--- .../components/sonos/translations/bg.json | 3 +- .../components/sonos/translations/ca.json | 3 +- .../components/sonos/translations/cs.json | 3 +- .../components/sonos/translations/da.json | 3 +- .../components/sonos/translations/de.json | 3 +- .../components/sonos/translations/en.json | 3 +- .../components/sonos/translations/es-419.json | 3 +- .../components/sonos/translations/es.json | 3 +- .../components/sonos/translations/fi.json | 3 +- .../components/sonos/translations/fr.json | 3 +- .../components/sonos/translations/he.json | 3 +- .../components/sonos/translations/hu.json | 3 +- .../components/sonos/translations/id.json | 3 +- .../components/sonos/translations/it.json | 3 +- .../components/sonos/translations/ko.json | 3 +- .../components/sonos/translations/lb.json | 3 +- .../components/sonos/translations/nl.json | 3 +- .../components/sonos/translations/nn.json | 3 +- .../components/sonos/translations/no.json | 3 +- .../components/sonos/translations/pl.json | 3 +- .../components/sonos/translations/pt-BR.json | 3 +- .../components/sonos/translations/pt.json | 3 +- .../components/sonos/translations/ro.json | 3 +- .../components/sonos/translations/ru.json | 3 +- .../components/sonos/translations/sl.json | 3 +- .../components/sonos/translations/sv.json | 3 +- .../components/sonos/translations/vi.json | 3 +- .../sonos/translations/zh-Hans.json | 3 +- .../sonos/translations/zh-Hant.json | 3 +- .../synology_dsm/translations/ca.json | 2 -- .../synology_dsm/translations/de.json | 2 -- .../synology_dsm/translations/en.json | 2 -- .../synology_dsm/translations/es-419.json | 2 -- .../synology_dsm/translations/es.json | 2 -- .../synology_dsm/translations/fr.json | 2 -- .../synology_dsm/translations/it.json | 2 -- .../synology_dsm/translations/ko.json | 2 -- .../synology_dsm/translations/lb.json | 2 -- .../synology_dsm/translations/nl.json | 2 -- .../synology_dsm/translations/no.json | 2 -- .../synology_dsm/translations/pl.json | 2 -- .../synology_dsm/translations/pt-BR.json | 1 - .../synology_dsm/translations/ru.json | 2 -- .../synology_dsm/translations/sl.json | 2 -- .../synology_dsm/translations/zh-Hant.json | 2 -- .../components/tplink/translations/bg.json | 3 +- .../components/tplink/translations/ca.json | 3 +- .../components/tplink/translations/da.json | 3 +- .../components/tplink/translations/de.json | 3 +- .../components/tplink/translations/en.json | 3 +- .../tplink/translations/es-419.json | 3 +- .../components/tplink/translations/es.json | 3 +- .../components/tplink/translations/fr.json | 3 +- .../components/tplink/translations/he.json | 3 +- .../components/tplink/translations/it.json | 3 +- .../components/tplink/translations/ko.json | 3 +- .../components/tplink/translations/lb.json | 3 +- .../components/tplink/translations/nl.json | 3 +- .../components/tplink/translations/no.json | 3 +- .../components/tplink/translations/pl.json | 3 +- .../components/tplink/translations/pt-BR.json | 3 +- .../components/tplink/translations/pt.json | 3 +- .../components/tplink/translations/ru.json | 3 +- .../components/tplink/translations/sl.json | 3 +- .../components/tplink/translations/sv.json | 3 +- .../tplink/translations/zh-Hans.json | 3 +- .../tplink/translations/zh-Hant.json | 3 +- .../components/tuya/translations/ca.json | 2 +- .../components/tuya/translations/en.json | 1 - .../components/tuya/translations/es.json | 1 - .../components/tuya/translations/fi.json | 1 - .../components/tuya/translations/fr.json | 3 -- .../components/tuya/translations/it.json | 1 - .../components/tuya/translations/ko.json | 1 - .../components/tuya/translations/lb.json | 1 - .../components/tuya/translations/nl.json | 1 - .../components/tuya/translations/no.json | 3 -- .../components/tuya/translations/pl.json | 1 - .../components/tuya/translations/pt-BR.json | 3 -- .../components/tuya/translations/ru.json | 1 - .../components/tuya/translations/zh-Hant.json | 1 - .../components/unifi/translations/bg.json | 3 +- .../components/unifi/translations/ca.json | 6 ++-- .../components/unifi/translations/cs.json | 3 +- .../components/unifi/translations/da.json | 3 +- .../components/unifi/translations/de.json | 5 +-- .../components/unifi/translations/en.json | 5 +-- .../components/unifi/translations/es-419.json | 4 +-- .../components/unifi/translations/es.json | 5 +-- .../components/unifi/translations/fi.json | 3 -- .../components/unifi/translations/fr.json | 7 ++-- .../components/unifi/translations/hu.json | 3 -- .../components/unifi/translations/it.json | 5 +-- .../components/unifi/translations/ko.json | 5 +-- .../components/unifi/translations/lb.json | 5 +-- .../components/unifi/translations/nl.json | 5 +-- .../components/unifi/translations/no.json | 5 +-- .../components/unifi/translations/pl.json | 5 +-- .../components/unifi/translations/pt-BR.json | 6 ++-- .../components/unifi/translations/pt.json | 3 +- .../components/unifi/translations/ro.json | 3 -- .../components/unifi/translations/ru.json | 6 ++-- .../components/unifi/translations/sl.json | 5 +-- .../components/unifi/translations/sv.json | 3 +- .../unifi/translations/zh-Hans.json | 3 +- .../unifi/translations/zh-Hant.json | 5 +-- .../components/upb/translations/lb.json | 1 + .../components/upnp/translations/bg.json | 19 +--------- .../components/upnp/translations/ca.json | 15 ++------ .../components/upnp/translations/cs.json | 19 +--------- .../components/upnp/translations/da.json | 19 +--------- .../components/upnp/translations/de.json | 15 ++------ .../components/upnp/translations/en.json | 15 ++------ .../components/upnp/translations/es-419.json | 19 +--------- .../components/upnp/translations/es.json | 15 ++------ .../components/upnp/translations/fi.json | 6 ---- .../components/upnp/translations/fr.json | 15 ++------ .../components/upnp/translations/hu.json | 18 +--------- .../components/upnp/translations/it.json | 15 ++------ .../components/upnp/translations/ko.json | 15 ++------ .../components/upnp/translations/lb.json | 15 ++------ .../components/upnp/translations/nl.json | 15 ++------ .../components/upnp/translations/nn.json | 13 ------- .../components/upnp/translations/no.json | 15 ++------ .../components/upnp/translations/pl.json | 15 ++------ .../components/upnp/translations/pt-BR.json | 15 ++------ .../components/upnp/translations/pt.json | 19 +--------- .../components/upnp/translations/ro.json | 10 ------ .../components/upnp/translations/ru.json | 15 ++------ .../components/upnp/translations/sl.json | 15 ++------ .../components/upnp/translations/sv.json | 15 ++------ .../components/upnp/translations/uk.json | 12 +------ .../components/upnp/translations/zh-Hans.json | 15 ++------ .../components/upnp/translations/zh-Hant.json | 15 ++------ .../components/vizio/translations/ca.json | 5 ++- .../components/vizio/translations/da.json | 2 -- .../components/vizio/translations/de.json | 3 -- .../components/vizio/translations/en.json | 17 +++++---- .../components/vizio/translations/es-419.json | 3 -- .../components/vizio/translations/es.json | 5 ++- .../components/vizio/translations/fr.json | 3 -- .../components/vizio/translations/hu.json | 2 -- .../components/vizio/translations/it.json | 3 -- .../components/vizio/translations/ko.json | 3 -- .../components/vizio/translations/lb.json | 5 ++- .../components/vizio/translations/nl.json | 2 -- .../components/vizio/translations/no.json | 11 ++---- .../components/vizio/translations/pl.json | 3 -- .../components/vizio/translations/pt-BR.json | 1 - .../components/vizio/translations/ru.json | 7 ++-- .../components/vizio/translations/sl.json | 3 -- .../components/vizio/translations/sv.json | 2 -- .../vizio/translations/zh-Hant.json | 17 +++++---- .../components/wemo/translations/bg.json | 3 +- .../components/wemo/translations/ca.json | 3 +- .../components/wemo/translations/da.json | 3 +- .../components/wemo/translations/de.json | 3 +- .../components/wemo/translations/en.json | 3 +- .../components/wemo/translations/es-419.json | 3 +- .../components/wemo/translations/es.json | 3 +- .../components/wemo/translations/fr.json | 3 +- .../components/wemo/translations/it.json | 3 +- .../components/wemo/translations/ko.json | 3 +- .../components/wemo/translations/lb.json | 3 +- .../components/wemo/translations/nl.json | 3 +- .../components/wemo/translations/no.json | 3 +- .../components/wemo/translations/pl.json | 3 +- .../components/wemo/translations/pt-BR.json | 3 +- .../components/wemo/translations/ru.json | 3 +- .../components/wemo/translations/sl.json | 3 +- .../components/wemo/translations/sv.json | 3 +- .../components/wemo/translations/zh-Hant.json | 3 +- .../components/wled/translations/af.json | 3 +- .../components/wled/translations/ar.json | 3 +- .../components/wled/translations/bg.json | 3 +- .../components/wled/translations/bs.json | 3 +- .../components/wled/translations/ca.json | 3 +- .../components/wled/translations/cs.json | 3 +- .../components/wled/translations/cy.json | 3 +- .../components/wled/translations/da.json | 3 +- .../components/wled/translations/de.json | 3 +- .../components/wled/translations/el.json | 3 +- .../components/wled/translations/en.json | 3 +- .../components/wled/translations/eo.json | 3 +- .../components/wled/translations/es-419.json | 3 +- .../components/wled/translations/es.json | 3 +- .../components/wled/translations/et.json | 3 +- .../components/wled/translations/eu.json | 3 +- .../components/wled/translations/fa.json | 3 +- .../components/wled/translations/fi.json | 3 +- .../components/wled/translations/fr.json | 3 +- .../components/wled/translations/gsw.json | 3 +- .../components/wled/translations/he.json | 3 +- .../components/wled/translations/hi.json | 3 +- .../components/wled/translations/hr.json | 3 +- .../components/wled/translations/hu.json | 3 +- .../components/wled/translations/id.json | 3 +- .../components/wled/translations/is.json | 3 +- .../components/wled/translations/it.json | 3 +- .../components/wled/translations/ja.json | 3 +- .../components/wled/translations/ko.json | 3 +- .../components/wled/translations/lb.json | 3 +- .../components/wled/translations/lt.json | 3 +- .../components/wled/translations/lv.json | 3 +- .../components/wled/translations/nl.json | 3 +- .../components/wled/translations/nn.json | 3 +- .../components/wled/translations/no.json | 3 +- .../components/wled/translations/pl.json | 3 +- .../components/wled/translations/pt-BR.json | 3 +- .../components/wled/translations/pt.json | 3 +- .../components/wled/translations/ro.json | 3 +- .../components/wled/translations/ru.json | 3 +- .../components/wled/translations/sk.json | 3 +- .../components/wled/translations/sl.json | 3 +- .../components/wled/translations/sr-Latn.json | 3 +- .../components/wled/translations/sr.json | 3 +- .../components/wled/translations/sv.json | 3 +- .../components/wled/translations/ta.json | 3 +- .../components/wled/translations/te.json | 3 +- .../components/wled/translations/th.json | 3 +- .../components/wled/translations/tr.json | 3 +- .../components/wled/translations/uk.json | 3 +- .../components/wled/translations/ur.json | 3 +- .../components/wled/translations/vi.json | 3 +- .../components/wled/translations/zh-Hans.json | 3 +- .../components/wled/translations/zh-Hant.json | 3 +- .../components/zerproc/translations/lb.json | 5 +++ .../components/zha/translations/bg.json | 3 -- .../components/zha/translations/ca.json | 4 +-- .../components/zha/translations/da.json | 4 +-- .../components/zha/translations/de.json | 4 +-- .../components/zha/translations/en.json | 4 +-- .../components/zha/translations/es-419.json | 3 -- .../components/zha/translations/es.json | 4 +-- .../components/zha/translations/fi.json | 3 -- .../components/zha/translations/fr.json | 3 +- .../components/zha/translations/hu.json | 4 --- .../components/zha/translations/it.json | 4 +-- .../components/zha/translations/ko.json | 4 +-- .../components/zha/translations/lb.json | 4 +-- .../components/zha/translations/nl.json | 3 +- .../components/zha/translations/no.json | 4 +-- .../components/zha/translations/pl.json | 4 +-- .../components/zha/translations/pt-BR.json | 3 -- .../components/zha/translations/pt.json | 3 -- .../components/zha/translations/ru.json | 4 +-- .../components/zha/translations/sl.json | 4 --- .../components/zha/translations/sv.json | 3 +- .../components/zha/translations/zh-Hans.json | 3 -- .../components/zha/translations/zh-Hant.json | 4 +-- 831 files changed, 1067 insertions(+), 2568 deletions(-) create mode 100644 homeassistant/components/guardian/translations/ca.json create mode 100644 homeassistant/components/guardian/translations/es.json create mode 100644 homeassistant/components/guardian/translations/lb.json create mode 100644 homeassistant/components/guardian/translations/no.json create mode 100644 homeassistant/components/guardian/translations/ru.json create mode 100644 homeassistant/components/guardian/translations/zh-Hant.json create mode 100644 homeassistant/components/plugwise/translations/ca.json create mode 100644 homeassistant/components/plugwise/translations/es.json diff --git a/homeassistant/components/acmeda/translations/ca.json b/homeassistant/components/acmeda/translations/ca.json index 81bceddfcff..0812387ab7f 100644 --- a/homeassistant/components/acmeda/translations/ca.json +++ b/homeassistant/components/acmeda/translations/ca.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "all_configured": "No s'han descobert nous hubs de Pulse." + }, "step": { "user": { "data": { diff --git a/homeassistant/components/adguard/translations/bg.json b/homeassistant/components/adguard/translations/bg.json index cc68f3e3b28..bc8dff7fedd 100644 --- a/homeassistant/components/adguard/translations/bg.json +++ b/homeassistant/components/adguard/translations/bg.json @@ -21,8 +21,7 @@ "username": "\u041f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b\u0441\u043a\u043e \u0438\u043c\u0435", "verify_ssl": "AdGuard Home \u0438\u0437\u043f\u043e\u043b\u0437\u0432\u0430 \u043d\u0430\u0434\u0435\u0436\u0434\u0435\u043d \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442" }, - "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 \u0412\u0430\u0448\u0438\u044f AdGuard Home, \u0437\u0430 \u0434\u0430 \u043f\u043e\u0437\u0432\u043e\u043b\u0438\u0442\u0435 \u043d\u0430\u0431\u043b\u044e\u0434\u0435\u043d\u0438\u0435 \u0438 \u043a\u043e\u043d\u0442\u0440\u043e\u043b.", - "title": "\u0421\u0432\u044a\u0440\u0436\u0435\u0442\u0435 \u0412\u0430\u0448\u0438\u044f AdGuard Home." + "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 \u0412\u0430\u0448\u0438\u044f AdGuard Home, \u0437\u0430 \u0434\u0430 \u043f\u043e\u0437\u0432\u043e\u043b\u0438\u0442\u0435 \u043d\u0430\u0431\u043b\u044e\u0434\u0435\u043d\u0438\u0435 \u0438 \u043a\u043e\u043d\u0442\u0440\u043e\u043b." } } } diff --git a/homeassistant/components/adguard/translations/ca.json b/homeassistant/components/adguard/translations/ca.json index 06b355273fe..ae142c7382c 100644 --- a/homeassistant/components/adguard/translations/ca.json +++ b/homeassistant/components/adguard/translations/ca.json @@ -23,8 +23,7 @@ "username": "[%key::common::config_flow::data::username%]", "verify_ssl": "AdGuard Home utilitza un certificat adequat" }, - "description": "Configuraci\u00f3 de la inst\u00e0ncia d'AdGuard Home, permet el control i la monitoritzaci\u00f3.", - "title": "Enlla\u00e7ar AdGuard Home." + "description": "Configuraci\u00f3 de la inst\u00e0ncia d'AdGuard Home, permet el control i la monitoritzaci\u00f3." } } } diff --git a/homeassistant/components/adguard/translations/da.json b/homeassistant/components/adguard/translations/da.json index 9d3460b1ed2..20fd721eccc 100644 --- a/homeassistant/components/adguard/translations/da.json +++ b/homeassistant/components/adguard/translations/da.json @@ -21,8 +21,7 @@ "username": "Brugernavn", "verify_ssl": "AdGuard Home bruger et korrekt certifikat" }, - "description": "Konfigurer din AdGuard Home-instans for at tillade overv\u00e5gning og kontrol.", - "title": "Forbind din AdGuard Home." + "description": "Konfigurer din AdGuard Home-instans for at tillade overv\u00e5gning og kontrol." } } } diff --git a/homeassistant/components/adguard/translations/de.json b/homeassistant/components/adguard/translations/de.json index 19c1a5ce6fc..2e320d65e39 100644 --- a/homeassistant/components/adguard/translations/de.json +++ b/homeassistant/components/adguard/translations/de.json @@ -23,8 +23,7 @@ "username": "Benutzername", "verify_ssl": "AdGuard Home verwendet ein richtiges Zertifikat" }, - "description": "Richte deine AdGuard Home-Instanz ein um sie zu \u00dcberwachen und zu Steuern.", - "title": "Verkn\u00fcpfe AdGuard Home." + "description": "Richte deine AdGuard Home-Instanz ein um sie zu \u00dcberwachen und zu Steuern." } } } diff --git a/homeassistant/components/adguard/translations/en.json b/homeassistant/components/adguard/translations/en.json index d9b5d81b469..ffeb11af839 100644 --- a/homeassistant/components/adguard/translations/en.json +++ b/homeassistant/components/adguard/translations/en.json @@ -23,8 +23,7 @@ "username": "Username", "verify_ssl": "AdGuard Home uses a proper certificate" }, - "description": "Set up your AdGuard Home instance to allow monitoring and control.", - "title": "Link your AdGuard Home." + "description": "Set up your AdGuard Home instance to allow monitoring and control." } } } diff --git a/homeassistant/components/adguard/translations/es-419.json b/homeassistant/components/adguard/translations/es-419.json index f2ce862b083..450bb05c886 100644 --- a/homeassistant/components/adguard/translations/es-419.json +++ b/homeassistant/components/adguard/translations/es-419.json @@ -23,8 +23,7 @@ "username": "Nombre de usuario", "verify_ssl": "AdGuard Home utiliza un certificado adecuado" }, - "description": "Configure su instancia de AdGuard Home para permitir la supervisi\u00f3n y el control.", - "title": "Enlace su AdGuard Home." + "description": "Configure su instancia de AdGuard Home para permitir la supervisi\u00f3n y el control." } } } diff --git a/homeassistant/components/adguard/translations/es.json b/homeassistant/components/adguard/translations/es.json index 8e4bc821fdf..70900e15eb5 100644 --- a/homeassistant/components/adguard/translations/es.json +++ b/homeassistant/components/adguard/translations/es.json @@ -23,8 +23,7 @@ "username": "Usuario", "verify_ssl": "AdGuard Home utiliza un certificado apropiado" }, - "description": "Configure su instancia de AdGuard Home para permitir la supervisi\u00f3n y el control.", - "title": "Enlace su AdGuard Home." + "description": "Configure su instancia de AdGuard Home para permitir la supervisi\u00f3n y el control." } } } diff --git a/homeassistant/components/adguard/translations/fr.json b/homeassistant/components/adguard/translations/fr.json index 777e1e992af..3819aa9ec76 100644 --- a/homeassistant/components/adguard/translations/fr.json +++ b/homeassistant/components/adguard/translations/fr.json @@ -23,8 +23,7 @@ "username": "Nom d'utilisateur", "verify_ssl": "AdGuard Home utilise un certificat appropri\u00e9" }, - "description": "Configurez votre instance AdGuard Home pour permettre la surveillance et le contr\u00f4le.", - "title": "Liez votre AdGuard Home." + "description": "Configurez votre instance AdGuard Home pour permettre la surveillance et le contr\u00f4le." } } } diff --git a/homeassistant/components/adguard/translations/it.json b/homeassistant/components/adguard/translations/it.json index c3c9aef22c9..0100f2914b4 100644 --- a/homeassistant/components/adguard/translations/it.json +++ b/homeassistant/components/adguard/translations/it.json @@ -23,8 +23,7 @@ "username": "Nome utente", "verify_ssl": "AdGuard Home utilizza un certificato appropriato" }, - "description": "Configura l'istanza di AdGuard Home per consentire il monitoraggio e il controllo.", - "title": "Collega la tua AdGuard Home." + "description": "Configura l'istanza di AdGuard Home per consentire il monitoraggio e il controllo." } } } diff --git a/homeassistant/components/adguard/translations/ko.json b/homeassistant/components/adguard/translations/ko.json index b5b77e434ca..cdb453b930f 100644 --- a/homeassistant/components/adguard/translations/ko.json +++ b/homeassistant/components/adguard/translations/ko.json @@ -23,8 +23,7 @@ "username": "\uc0ac\uc6a9\uc790 \uc774\ub984", "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\ud558\uae30" + "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." } } } diff --git a/homeassistant/components/adguard/translations/lb.json b/homeassistant/components/adguard/translations/lb.json index 76c85138976..97cefb46bc0 100644 --- a/homeassistant/components/adguard/translations/lb.json +++ b/homeassistant/components/adguard/translations/lb.json @@ -23,8 +23,7 @@ "username": "Benotzernumm", "verify_ssl": "AdGuard Home benotzt een eegenen Zertifikat" }, - "description": "Konfigur\u00e9iert \u00e4r AdGuard Home Instanz fir d'Iwwerwaachung an d'Kontroll z'erlaben.", - "title": "Verbannt \u00e4ren AdGuard Home" + "description": "Konfigur\u00e9iert \u00e4r AdGuard Home Instanz fir d'Iwwerwaachung an d'Kontroll z'erlaben." } } } diff --git a/homeassistant/components/adguard/translations/nl.json b/homeassistant/components/adguard/translations/nl.json index 427894eeff4..6d09824a699 100644 --- a/homeassistant/components/adguard/translations/nl.json +++ b/homeassistant/components/adguard/translations/nl.json @@ -21,8 +21,7 @@ "username": "Gebruikersnaam", "verify_ssl": "AdGuard Home maakt gebruik van een goed certificaat" }, - "description": "Stel uw AdGuard Home-instantie in om toezicht en controle mogelijk te maken.", - "title": "Link uw AdGuard Home." + "description": "Stel uw AdGuard Home-instantie in om toezicht en controle mogelijk te maken." } } } diff --git a/homeassistant/components/adguard/translations/no.json b/homeassistant/components/adguard/translations/no.json index 60385c586e2..0633e817db9 100644 --- a/homeassistant/components/adguard/translations/no.json +++ b/homeassistant/components/adguard/translations/no.json @@ -21,8 +21,7 @@ "ssl": "AdGuard Hjem bruker et SSL-sertifikat", "verify_ssl": "AdGuard Home bruker et riktig sertifikat" }, - "description": "Sett opp din AdGuard Hjem instans for \u00e5 tillate overv\u00e5king og kontroll.", - "title": "Koble til ditt AdGuard Hjem." + "description": "Sett opp din AdGuard Hjem instans for \u00e5 tillate overv\u00e5king og kontroll." } } } diff --git a/homeassistant/components/adguard/translations/pl.json b/homeassistant/components/adguard/translations/pl.json index 71264e906e4..6451f902642 100644 --- a/homeassistant/components/adguard/translations/pl.json +++ b/homeassistant/components/adguard/translations/pl.json @@ -23,8 +23,7 @@ "username": "Nazwa u\u017cytkownika", "verify_ssl": "AdGuard Home u\u017cywa odpowiedniego certyfikatu." }, - "description": "Skonfiguruj instancj\u0119 AdGuard Home, aby umo\u017cliwi\u0107 monitorowanie i kontrol\u0119.", - "title": "Po\u0142\u0105cz AdGuard Home" + "description": "Skonfiguruj instancj\u0119 AdGuard Home, aby umo\u017cliwi\u0107 monitorowanie i kontrol\u0119." } } } diff --git a/homeassistant/components/adguard/translations/pt-BR.json b/homeassistant/components/adguard/translations/pt-BR.json index e2dcdf7d312..942c793b9b4 100644 --- a/homeassistant/components/adguard/translations/pt-BR.json +++ b/homeassistant/components/adguard/translations/pt-BR.json @@ -19,8 +19,7 @@ "username": "Nome de usu\u00e1rio", "verify_ssl": "O AdGuard Home usa um certificado apropriado" }, - "description": "Configure sua inst\u00e2ncia do AdGuard Home para permitir o monitoramento e o controle.", - "title": "Vincule o seu AdGuard Home." + "description": "Configure sua inst\u00e2ncia do AdGuard Home para permitir o monitoramento e o controle." } } } diff --git a/homeassistant/components/adguard/translations/ru.json b/homeassistant/components/adguard/translations/ru.json index 1287b408544..e9998587353 100644 --- a/homeassistant/components/adguard/translations/ru.json +++ b/homeassistant/components/adguard/translations/ru.json @@ -23,8 +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 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" + "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0438\u043d\u0433\u0430 \u0438 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044f AdGuard Home." } } } diff --git a/homeassistant/components/adguard/translations/sl.json b/homeassistant/components/adguard/translations/sl.json index 7cad7c1ac3a..45f0f2a2d0d 100644 --- a/homeassistant/components/adguard/translations/sl.json +++ b/homeassistant/components/adguard/translations/sl.json @@ -23,8 +23,7 @@ "username": "Uporabni\u0161ko ime", "verify_ssl": "AdGuard Home uporablja ustrezen certifikat" }, - "description": "Nastavite primerek AdGuard Home, da omogo\u010dite spremljanje in nadzor.", - "title": "Pove\u017eite svoj AdGuard Home." + "description": "Nastavite primerek AdGuard Home, da omogo\u010dite spremljanje in nadzor." } } } diff --git a/homeassistant/components/adguard/translations/sv.json b/homeassistant/components/adguard/translations/sv.json index 2a7e4d7a40d..ce501bf459c 100644 --- a/homeassistant/components/adguard/translations/sv.json +++ b/homeassistant/components/adguard/translations/sv.json @@ -21,8 +21,7 @@ "username": "Anv\u00e4ndarnamn", "verify_ssl": "AdGuard Home anv\u00e4nder ett korrekt certifikat" }, - "description": "St\u00e4ll in din AdGuard Home-instans f\u00f6r att till\u00e5ta \u00f6vervakning och kontroll.", - "title": "L\u00e4nka din AdGuard Home." + "description": "St\u00e4ll in din AdGuard Home-instans f\u00f6r att till\u00e5ta \u00f6vervakning och kontroll." } } } diff --git a/homeassistant/components/adguard/translations/zh-Hant.json b/homeassistant/components/adguard/translations/zh-Hant.json index f3473fd3197..3bbe86352b8 100644 --- a/homeassistant/components/adguard/translations/zh-Hant.json +++ b/homeassistant/components/adguard/translations/zh-Hant.json @@ -23,8 +23,7 @@ "username": "\u4f7f\u7528\u8005\u540d\u7a31", "verify_ssl": "AdGuard Home \u4f7f\u7528\u5c0d\u61c9\u8a8d\u8b49" }, - "description": "\u8a2d\u5b9a AdGuard Home \u4ee5\u9032\u884c\u76e3\u63a7\u3002", - "title": "\u9023\u7d50 AdGuard Home\u3002" + "description": "\u8a2d\u5b9a AdGuard Home \u4ee5\u9032\u884c\u76e3\u63a7\u3002" } } } diff --git a/homeassistant/components/airvisual/translations/ca.json b/homeassistant/components/airvisual/translations/ca.json index 12a3cccdad6..31c3dfaf1ab 100644 --- a/homeassistant/components/airvisual/translations/ca.json +++ b/homeassistant/components/airvisual/translations/ca.json @@ -28,10 +28,7 @@ }, "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" }, @@ -46,7 +43,6 @@ "data": { "show_on_map": "Mostra al mapa l'\u00e0rea geogr\u00e0fica monitoritzada" }, - "description": "Estableix les diferents opcions de la integraci\u00f3 AirVisual.", "title": "Configuraci\u00f3 d'AirVisual" } } diff --git a/homeassistant/components/airvisual/translations/de.json b/homeassistant/components/airvisual/translations/de.json index 5e66daa3919..e25cfe805ed 100644 --- a/homeassistant/components/airvisual/translations/de.json +++ b/homeassistant/components/airvisual/translations/de.json @@ -27,10 +27,7 @@ }, "user": { "data": { - "api_key": "API-Schl\u00fcssel", "cloud_api": "Geografische Position", - "latitude": "Breitengrad", - "longitude": "L\u00e4ngengrad", "node_pro": "AirVisual Node Pro", "type": "Integrationstyp" }, @@ -45,7 +42,6 @@ "data": { "show_on_map": "Zeigen Sie die \u00fcberwachte Geografie auf der Karte an" }, - "description": "Legen Sie verschiedene Optionen f\u00fcr die AirVisual-Integration fest.", "title": "Konfigurieren Sie AirVisual" } } diff --git a/homeassistant/components/airvisual/translations/en.json b/homeassistant/components/airvisual/translations/en.json index 6298193f59b..a85c2ba75ce 100644 --- a/homeassistant/components/airvisual/translations/en.json +++ b/homeassistant/components/airvisual/translations/en.json @@ -28,10 +28,7 @@ }, "user": { "data": { - "api_key": "API Key", "cloud_api": "Geographical Location", - "latitude": "Latitude", - "longitude": "Longitude", "node_pro": "AirVisual Node Pro", "type": "Integration Type" }, @@ -46,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/homeassistant/components/airvisual/translations/es-419.json b/homeassistant/components/airvisual/translations/es-419.json index 86fb471df64..e552deb2242 100644 --- a/homeassistant/components/airvisual/translations/es-419.json +++ b/homeassistant/components/airvisual/translations/es-419.json @@ -28,10 +28,7 @@ }, "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" }, @@ -46,7 +43,6 @@ "data": { "show_on_map": "Mostrar geograf\u00eda monitoreada en el mapa" }, - "description": "Establezca varias opciones para la integraci\u00f3n de AirVisual.", "title": "Configurar AirVisual" } } diff --git a/homeassistant/components/airvisual/translations/es.json b/homeassistant/components/airvisual/translations/es.json index 0bd06eaf7bd..4872f736915 100644 --- a/homeassistant/components/airvisual/translations/es.json +++ b/homeassistant/components/airvisual/translations/es.json @@ -28,14 +28,11 @@ }, "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": "Elige qu\u00e9 tipo de datos de AirVisual quieres monitorear.", + "description": "Elige qu\u00e9 tipo de datos de AirVisual quieres monitorizar.", "title": "Configurar AirVisual" } } @@ -46,7 +43,6 @@ "data": { "show_on_map": "Mostrar geograf\u00eda monitorizada en el mapa" }, - "description": "Ajustar varias opciones para la integraci\u00f3n de AirVisual.", "title": "Configurar AirVisual" } } diff --git a/homeassistant/components/airvisual/translations/fr.json b/homeassistant/components/airvisual/translations/fr.json index 73efe479b2c..8d9eef019e0 100644 --- a/homeassistant/components/airvisual/translations/fr.json +++ b/homeassistant/components/airvisual/translations/fr.json @@ -28,10 +28,7 @@ }, "user": { "data": { - "api_key": "Cl\u00e9 API", "cloud_api": "Localisation g\u00e9ographique", - "latitude": "Latitude", - "longitude": "Longitude", "node_pro": "AirVisual Node Pro", "type": "Type d'int\u00e9gration" }, @@ -46,7 +43,6 @@ "data": { "show_on_map": "Afficher la g\u00e9ographie surveill\u00e9e sur la carte" }, - "description": "D\u00e9finissez diverses options pour l'int\u00e9gration d'AirVisual.", "title": "Configurer AirVisual" } } diff --git a/homeassistant/components/airvisual/translations/it.json b/homeassistant/components/airvisual/translations/it.json index 334900cd731..c22481918a0 100644 --- a/homeassistant/components/airvisual/translations/it.json +++ b/homeassistant/components/airvisual/translations/it.json @@ -28,10 +28,7 @@ }, "user": { "data": { - "api_key": "Chiave API", "cloud_api": "Posizione geografica", - "latitude": "Latitudine", - "longitude": "Logitudine", "node_pro": "AirVisual Node Pro", "type": "Tipo di integrazione" }, @@ -46,7 +43,6 @@ "data": { "show_on_map": "Mostra l'area geografica monitorata sulla mappa" }, - "description": "Impostare varie opzioni per l'integrazione AirVisual.", "title": "Configurare AirVisual" } } diff --git a/homeassistant/components/airvisual/translations/ko.json b/homeassistant/components/airvisual/translations/ko.json index e60e0a7ef1c..19cd136cc0f 100644 --- a/homeassistant/components/airvisual/translations/ko.json +++ b/homeassistant/components/airvisual/translations/ko.json @@ -28,10 +28,7 @@ }, "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" }, @@ -46,7 +43,6 @@ "data": { "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\ud558\uae30" } } diff --git a/homeassistant/components/airvisual/translations/lb.json b/homeassistant/components/airvisual/translations/lb.json index 52e55242a05..a9645c6e3b7 100644 --- a/homeassistant/components/airvisual/translations/lb.json +++ b/homeassistant/components/airvisual/translations/lb.json @@ -28,10 +28,7 @@ }, "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" }, @@ -46,7 +43,6 @@ "data": { "show_on_map": "Iwwerwaachte Geografie op der Kaart uweisen" }, - "description": "Verschidden Optioune fir d'AirVisual Integratioun d\u00e9fin\u00e9ieren.", "title": "Airvisual ariichten" } } diff --git a/homeassistant/components/airvisual/translations/nl.json b/homeassistant/components/airvisual/translations/nl.json index 102b08cb91e..cfccef38e89 100644 --- a/homeassistant/components/airvisual/translations/nl.json +++ b/homeassistant/components/airvisual/translations/nl.json @@ -26,10 +26,7 @@ }, "user": { "data": { - "api_key": "API-sleutel", "cloud_api": "Geografische ligging", - "latitude": "Breedtegraad", - "longitude": "Lengtegraad", "node_pro": "AirVisual Node Pro", "type": "Integratietype" }, @@ -44,7 +41,6 @@ "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/no.json b/homeassistant/components/airvisual/translations/no.json index 888e1f58e35..28cf8c9a5bb 100644 --- a/homeassistant/components/airvisual/translations/no.json +++ b/homeassistant/components/airvisual/translations/no.json @@ -28,10 +28,7 @@ }, "user": { "data": { - "api_key": "API-n\u00f8kkel", "cloud_api": "Geografisk plassering", - "latitude": "Breddegrad", - "longitude": "Lengdegrad", "node_pro": "", "type": "Integrasjonstype" }, @@ -46,7 +43,6 @@ "data": { "show_on_map": "Vis overv\u00e5ket geografi p\u00e5 kartet" }, - "description": "Angi forskjellige alternativer for AirVisual-integrasjonen.", "title": "Konfigurer AirVisual" } } diff --git a/homeassistant/components/airvisual/translations/pl.json b/homeassistant/components/airvisual/translations/pl.json index b90482deee2..dea77a233aa 100644 --- a/homeassistant/components/airvisual/translations/pl.json +++ b/homeassistant/components/airvisual/translations/pl.json @@ -28,10 +28,7 @@ }, "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" }, @@ -46,7 +43,6 @@ "data": { "show_on_map": "Wy\u015bwietlaj encje na mapie" }, - "description": "Konfiguracja opcji integracji AirVisual.", "title": "Konfiguracja AirVisual" } } diff --git a/homeassistant/components/airvisual/translations/ru.json b/homeassistant/components/airvisual/translations/ru.json index ecc8999fd18..67af449c9b0 100644 --- a/homeassistant/components/airvisual/translations/ru.json +++ b/homeassistant/components/airvisual/translations/ru.json @@ -28,10 +28,7 @@ }, "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": "\u0422\u0438\u043f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438" }, @@ -46,7 +43,6 @@ "data": { "show_on_map": "\u041f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u043e\u0442\u0441\u043b\u0435\u0436\u0438\u0432\u0430\u0435\u043c\u0443\u044e \u043e\u0431\u043b\u0430\u0441\u0442\u044c \u043d\u0430 \u043a\u0430\u0440\u0442\u0435" }, - "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 AirVisual.", "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 AirVisual" } } diff --git a/homeassistant/components/airvisual/translations/sl.json b/homeassistant/components/airvisual/translations/sl.json index 18765467fd9..f9121852d62 100644 --- a/homeassistant/components/airvisual/translations/sl.json +++ b/homeassistant/components/airvisual/translations/sl.json @@ -28,10 +28,7 @@ }, "user": { "data": { - "api_key": "API Klju\u010d", "cloud_api": "Geografska lokacija", - "latitude": "Zemljepisna \u0161irina", - "longitude": "Zemljepisna dol\u017eina", "node_pro": "AirVisual Node Pro", "type": "Vrsta integracije" }, @@ -46,7 +43,6 @@ "data": { "show_on_map": "Prika\u017ei nadzorovano obmo\u010dje na zemljevidu" }, - "description": "Nastavite razli\u010dne mo\u017enosti za integracijo AirVisual.", "title": "Nastavite AirVisual" } } diff --git a/homeassistant/components/airvisual/translations/sv.json b/homeassistant/components/airvisual/translations/sv.json index 4c54fcf002c..4c4e1271d72 100644 --- a/homeassistant/components/airvisual/translations/sv.json +++ b/homeassistant/components/airvisual/translations/sv.json @@ -18,10 +18,7 @@ }, "user": { "data": { - "api_key": "API-nyckel", "cloud_api": "Geografisk Plats", - "latitude": "Latitud", - "longitude": "Longitud", "type": "Integrationstyp" } } diff --git a/homeassistant/components/airvisual/translations/zh-Hant.json b/homeassistant/components/airvisual/translations/zh-Hant.json index 19a1a42fc7a..80e8372f3f6 100644 --- a/homeassistant/components/airvisual/translations/zh-Hant.json +++ b/homeassistant/components/airvisual/translations/zh-Hant.json @@ -28,10 +28,7 @@ }, "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" }, @@ -46,7 +43,6 @@ "data": { "show_on_map": "\u65bc\u5730\u5716\u4e0a\u986f\u793a\u76e3\u63a7\u4f4d\u7f6e\u3002" }, - "description": "\u8a2d\u5b9a AirVisual \u6574\u5408\u9078\u9805\u3002", "title": "\u8a2d\u5b9a AirVisual" } } diff --git a/homeassistant/components/alarm_control_panel/translations/es.json b/homeassistant/components/alarm_control_panel/translations/es.json index fc76102d0fe..4002c26cd29 100644 --- a/homeassistant/components/alarm_control_panel/translations/es.json +++ b/homeassistant/components/alarm_control_panel/translations/es.json @@ -24,13 +24,13 @@ }, "state": { "_": { - "armed": "Armado", + "armed": "Armada", "armed_away": "Armada ausente", "armed_custom_bypass": "Armada personalizada", "armed_home": "Armada en casa", "armed_night": "Armada noche", "arming": "Armando", - "disarmed": "Desarmado", + "disarmed": "Desarmada", "disarming": "Desarmando", "pending": "Pendiente", "triggered": "Disparada" diff --git a/homeassistant/components/arcam_fmj/translations/ca.json b/homeassistant/components/arcam_fmj/translations/ca.json index c8cce461571..33af7b119be 100644 --- a/homeassistant/components/arcam_fmj/translations/ca.json +++ b/homeassistant/components/arcam_fmj/translations/ca.json @@ -3,6 +3,5 @@ "trigger_type": { "turn_on": "S'ha sol\u00b7licitat l'activaci\u00f3 de {entity_name}" } - }, - "title": "Arcam FMJ" + } } \ No newline at end of file diff --git a/homeassistant/components/arcam_fmj/translations/de.json b/homeassistant/components/arcam_fmj/translations/de.json index cc744954121..85032362472 100644 --- a/homeassistant/components/arcam_fmj/translations/de.json +++ b/homeassistant/components/arcam_fmj/translations/de.json @@ -3,6 +3,5 @@ "trigger_type": { "turn_on": "{entity_name} wurde zum Einschalten aufgefordert" } - }, - "title": "Arcam FMJ" + } } \ No newline at end of file diff --git a/homeassistant/components/arcam_fmj/translations/en.json b/homeassistant/components/arcam_fmj/translations/en.json index 95fedf09e93..6f60c9e2471 100644 --- a/homeassistant/components/arcam_fmj/translations/en.json +++ b/homeassistant/components/arcam_fmj/translations/en.json @@ -3,6 +3,5 @@ "trigger_type": { "turn_on": "{entity_name} was requested to turn on" } - }, - "title": "Arcam FMJ" + } } \ No newline at end of file diff --git a/homeassistant/components/arcam_fmj/translations/es.json b/homeassistant/components/arcam_fmj/translations/es.json index 0205985aa95..87a165511cc 100644 --- a/homeassistant/components/arcam_fmj/translations/es.json +++ b/homeassistant/components/arcam_fmj/translations/es.json @@ -3,6 +3,5 @@ "trigger_type": { "turn_on": "Se solicit\u00f3 encender {entity_name}" } - }, - "title": "Arcam FMJ" + } } \ No newline at end of file diff --git a/homeassistant/components/arcam_fmj/translations/it.json b/homeassistant/components/arcam_fmj/translations/it.json index 3f0165cbb4a..71deb04fd1e 100644 --- a/homeassistant/components/arcam_fmj/translations/it.json +++ b/homeassistant/components/arcam_fmj/translations/it.json @@ -3,6 +3,5 @@ "trigger_type": { "turn_on": "\u00c8 stato richiesto di attivare {entity_name}" } - }, - "title": "Arcam FMJ" + } } \ No newline at end of file diff --git a/homeassistant/components/arcam_fmj/translations/ko.json b/homeassistant/components/arcam_fmj/translations/ko.json index 641679e4a84..7a2005017af 100644 --- a/homeassistant/components/arcam_fmj/translations/ko.json +++ b/homeassistant/components/arcam_fmj/translations/ko.json @@ -3,6 +3,5 @@ "trigger_type": { "turn_on": "{entity_name} \uc774(\uac00) \ucf1c\uc9c0\ub3c4\ub85d \uc694\uccad\ub418\uc5c8\uc744 \ub54c" } - }, - "title": "Arcam FMJ" + } } \ No newline at end of file diff --git a/homeassistant/components/arcam_fmj/translations/lb.json b/homeassistant/components/arcam_fmj/translations/lb.json index 6a56ce5f7c7..a057827bb6f 100644 --- a/homeassistant/components/arcam_fmj/translations/lb.json +++ b/homeassistant/components/arcam_fmj/translations/lb.json @@ -3,6 +3,5 @@ "trigger_type": { "turn_on": "{entity_name} soll ugeschalt ginn" } - }, - "title": "Arcam FMJ" + } } \ No newline at end of file diff --git a/homeassistant/components/arcam_fmj/translations/nl.json b/homeassistant/components/arcam_fmj/translations/nl.json index d9781c001b9..761d57a48cb 100644 --- a/homeassistant/components/arcam_fmj/translations/nl.json +++ b/homeassistant/components/arcam_fmj/translations/nl.json @@ -3,6 +3,5 @@ "trigger_type": { "turn_on": "{entity_name} is gevraagd in te schakelen" } - }, - "title": "Arcam FMJ" + } } \ No newline at end of file diff --git a/homeassistant/components/arcam_fmj/translations/no.json b/homeassistant/components/arcam_fmj/translations/no.json index 8dcc7852d41..81404be2ace 100644 --- a/homeassistant/components/arcam_fmj/translations/no.json +++ b/homeassistant/components/arcam_fmj/translations/no.json @@ -3,6 +3,5 @@ "trigger_type": { "turn_on": "{entity_name} ble bedt om \u00e5 sl\u00e5 p\u00e5" } - }, - "title": "" + } } \ No newline at end of file diff --git a/homeassistant/components/arcam_fmj/translations/pl.json b/homeassistant/components/arcam_fmj/translations/pl.json index 2171454a4c6..cad2b4adb25 100644 --- a/homeassistant/components/arcam_fmj/translations/pl.json +++ b/homeassistant/components/arcam_fmj/translations/pl.json @@ -3,6 +3,5 @@ "trigger_type": { "turn_on": "{entity_name} zostanie poproszony o w\u0142\u0105czenie" } - }, - "title": "Arcam FMJ" + } } \ No newline at end of file diff --git a/homeassistant/components/arcam_fmj/translations/pt-BR.json b/homeassistant/components/arcam_fmj/translations/pt-BR.json index 0936f0bd9d8..8071efb001f 100644 --- a/homeassistant/components/arcam_fmj/translations/pt-BR.json +++ b/homeassistant/components/arcam_fmj/translations/pt-BR.json @@ -3,6 +3,5 @@ "trigger_type": { "turn_on": "Foi solicitado que {entity_name} ligue" } - }, - "title": "Arcam FMJ" + } } \ No newline at end of file diff --git a/homeassistant/components/arcam_fmj/translations/ru.json b/homeassistant/components/arcam_fmj/translations/ru.json index be8c240918d..58f4fd5ea3b 100644 --- a/homeassistant/components/arcam_fmj/translations/ru.json +++ b/homeassistant/components/arcam_fmj/translations/ru.json @@ -3,6 +3,5 @@ "trigger_type": { "turn_on": "\u0437\u0430\u043f\u0440\u043e\u0448\u0435\u043d\u043e \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 {entity_name}" } - }, - "title": "Arcam FMJ" + } } \ No newline at end of file diff --git a/homeassistant/components/arcam_fmj/translations/zh-Hant.json b/homeassistant/components/arcam_fmj/translations/zh-Hant.json index 859aea4b08e..18ea7d22eb1 100644 --- a/homeassistant/components/arcam_fmj/translations/zh-Hant.json +++ b/homeassistant/components/arcam_fmj/translations/zh-Hant.json @@ -3,6 +3,5 @@ "trigger_type": { "turn_on": "{entity_name} \u4f9d\u9700\u6c42\u958b\u555f" } - }, - "title": "Arcam FMJ" + } } \ No newline at end of file diff --git a/homeassistant/components/atag/translations/ca.json b/homeassistant/components/atag/translations/ca.json index f0c5211e8e5..0ccafe15f97 100644 --- a/homeassistant/components/atag/translations/ca.json +++ b/homeassistant/components/atag/translations/ca.json @@ -4,7 +4,8 @@ "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" + "connection_error": "No s'ha pogut connectar, torna-ho a provar", + "unauthorized": "L'emparellament s'ha denegat, comprova si hi ha una sol\u00b7licitud d'autenticaci\u00f3 al dispositiu" }, "step": { "user": { diff --git a/homeassistant/components/atag/translations/en.json b/homeassistant/components/atag/translations/en.json index 978f3a15453..8901a417065 100644 --- a/homeassistant/components/atag/translations/en.json +++ b/homeassistant/components/atag/translations/en.json @@ -1,10 +1,11 @@ { "config": { "abort": { - "already_configured": "Only one Atag device can be added to Home Assistant" + "already_configured": "This device has already been added to HomeAssistant" }, "error": { - "connection_error": "Failed to connect, please try again" + "connection_error": "Failed to connect, please try again", + "unauthorized": "Pairing denied, check device for auth request" }, "step": { "user": { diff --git a/homeassistant/components/atag/translations/es.json b/homeassistant/components/atag/translations/es.json index be9ca1b67a3..b3d6fee9114 100644 --- a/homeassistant/components/atag/translations/es.json +++ b/homeassistant/components/atag/translations/es.json @@ -4,7 +4,8 @@ "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" + "connection_error": "No se pudo conectar, por favor, int\u00e9ntalo de nuevo", + "unauthorized": "Emparejamiento denegado, comprobar el dispositivo para la solicitud de autorizaci\u00f3n" }, "step": { "user": { diff --git a/homeassistant/components/atag/translations/lb.json b/homeassistant/components/atag/translations/lb.json index 3850ab33419..72f5820e2d3 100644 --- a/homeassistant/components/atag/translations/lb.json +++ b/homeassistant/components/atag/translations/lb.json @@ -4,7 +4,8 @@ "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." + "connection_error": "Feeler beim verbannen, prob\u00e9ier w.e.g. nach emol.", + "unauthorized": "Kopplung verweigert, iwwerpr\u00e9if den Apparat fir auth request" }, "step": { "user": { diff --git a/homeassistant/components/atag/translations/no.json b/homeassistant/components/atag/translations/no.json index ee9811c581f..3f446a5f21b 100644 --- a/homeassistant/components/atag/translations/no.json +++ b/homeassistant/components/atag/translations/no.json @@ -1,10 +1,11 @@ { "config": { "abort": { - "already_configured": "Bare en Atag-enhet kan legges til Home Assistant" + "already_configured": "Denne enheten er allerede lagt til i HomeAssistant" }, "error": { - "connection_error": "Klarte ikke \u00e5 koble til, vennligst pr\u00f8v igjen" + "connection_error": "Klarte ikke \u00e5 koble til, vennligst pr\u00f8v igjen", + "unauthorized": "Parring nektet, sjekk enheten for autorisasjonsforesp\u00f8rsel" }, "step": { "user": { diff --git a/homeassistant/components/atag/translations/ru.json b/homeassistant/components/atag/translations/ru.json index daaff9d9dda..caf80b415e6 100644 --- a/homeassistant/components/atag/translations/ru.json +++ b/homeassistant/components/atag/translations/ru.json @@ -1,10 +1,11 @@ { "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." + "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": { - "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." + "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.", + "unauthorized": "\u0421\u043e\u043f\u0440\u044f\u0436\u0435\u043d\u0438\u0435 \u0437\u0430\u043f\u0440\u0435\u0449\u0435\u043d\u043e, \u043f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u043d\u0430 \u0437\u0430\u043f\u0440\u043e\u0441 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438." }, "step": { "user": { diff --git a/homeassistant/components/atag/translations/zh-Hant.json b/homeassistant/components/atag/translations/zh-Hant.json index 46d39b186ac..6fc2e520d17 100644 --- a/homeassistant/components/atag/translations/zh-Hant.json +++ b/homeassistant/components/atag/translations/zh-Hant.json @@ -1,10 +1,11 @@ { "config": { "abort": { - "already_configured": "\u50c5\u80fd\u65b0\u589e\u4e00\u7d44 Atag \u8a2d\u5099\u81f3 Home Assistant" + "already_configured": "\u6b64\u8a2d\u5099\u5df2\u7d93\u65b0\u589e\u81f3 Home Assistant" }, "error": { - "connection_error": "\u9023\u7dda\u5931\u6557\uff0c\u8acb\u518d\u8a66\u4e00\u6b21" + "connection_error": "\u9023\u7dda\u5931\u6557\uff0c\u8acb\u518d\u8a66\u4e00\u6b21", + "unauthorized": "\u914d\u5c0d\u906d\u62d2\uff0c\u8acb\u6aa2\u67e5\u8a2d\u5099\u8a8d\u8b49\u8acb\u6c42" }, "step": { "user": { diff --git a/homeassistant/components/axis/translations/bg.json b/homeassistant/components/axis/translations/bg.json index 83fbfa0118c..d5bf9373112 100644 --- a/homeassistant/components/axis/translations/bg.json +++ b/homeassistant/components/axis/translations/bg.json @@ -24,6 +24,5 @@ "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0432\u0430\u043d\u0435 \u043d\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u043e\u0442 Axis" } } - }, - "title": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e Axis" + } } \ No newline at end of file diff --git a/homeassistant/components/axis/translations/ca.json b/homeassistant/components/axis/translations/ca.json index 5ca4c474bc9..a636c3c40c8 100644 --- a/homeassistant/components/axis/translations/ca.json +++ b/homeassistant/components/axis/translations/ca.json @@ -24,6 +24,5 @@ "title": "Configuraci\u00f3 de dispositiu Axis" } } - }, - "title": "Dispositiu Axis" + } } \ No newline at end of file diff --git a/homeassistant/components/axis/translations/da.json b/homeassistant/components/axis/translations/da.json index 78e4e200082..3f2130238a2 100644 --- a/homeassistant/components/axis/translations/da.json +++ b/homeassistant/components/axis/translations/da.json @@ -24,6 +24,5 @@ "title": "Indstil Axis-enhed" } } - }, - "title": "Axis-enhed" + } } \ No newline at end of file diff --git a/homeassistant/components/axis/translations/de.json b/homeassistant/components/axis/translations/de.json index 7410a79bdc9..7753e5ad1e8 100644 --- a/homeassistant/components/axis/translations/de.json +++ b/homeassistant/components/axis/translations/de.json @@ -24,6 +24,5 @@ "title": "Axis Ger\u00e4t einrichten" } } - }, - "title": "Axis Ger\u00e4t" + } } \ No newline at end of file diff --git a/homeassistant/components/axis/translations/en.json b/homeassistant/components/axis/translations/en.json index cc76571b01b..2bc9650c010 100644 --- a/homeassistant/components/axis/translations/en.json +++ b/homeassistant/components/axis/translations/en.json @@ -24,6 +24,5 @@ "title": "Set up Axis device" } } - }, - "title": "Axis device" + } } \ No newline at end of file diff --git a/homeassistant/components/axis/translations/es-419.json b/homeassistant/components/axis/translations/es-419.json index b5d1cb4ca7b..151ef346be2 100644 --- a/homeassistant/components/axis/translations/es-419.json +++ b/homeassistant/components/axis/translations/es-419.json @@ -24,6 +24,5 @@ "title": "Configurar dispositivo Axis" } } - }, - "title": "Dispositivo Axis" + } } \ No newline at end of file diff --git a/homeassistant/components/axis/translations/es.json b/homeassistant/components/axis/translations/es.json index 19a774fe170..f4c6cbbb3a7 100644 --- a/homeassistant/components/axis/translations/es.json +++ b/homeassistant/components/axis/translations/es.json @@ -24,6 +24,5 @@ "title": "Configurar dispositivo Axis" } } - }, - "title": "Dispositivo Axis" + } } \ No newline at end of file diff --git a/homeassistant/components/axis/translations/fi.json b/homeassistant/components/axis/translations/fi.json index c3a755b16a2..a3740dd8bcf 100644 --- a/homeassistant/components/axis/translations/fi.json +++ b/homeassistant/components/axis/translations/fi.json @@ -5,6 +5,5 @@ "title": "Asenna Axis-laite" } } - }, - "title": "Axis-laite" + } } \ No newline at end of file diff --git a/homeassistant/components/axis/translations/fr.json b/homeassistant/components/axis/translations/fr.json index 4fb4f928768..2b5d1b24ef7 100644 --- a/homeassistant/components/axis/translations/fr.json +++ b/homeassistant/components/axis/translations/fr.json @@ -24,6 +24,5 @@ "title": "Configurer l'appareil Axis" } } - }, - "title": "Appareil Axis" + } } \ No newline at end of file diff --git a/homeassistant/components/axis/translations/hu.json b/homeassistant/components/axis/translations/hu.json index ac8538c6943..d749df6a783 100644 --- a/homeassistant/components/axis/translations/hu.json +++ b/homeassistant/components/axis/translations/hu.json @@ -16,6 +16,5 @@ } } } - }, - "title": "Axis eszk\u00f6z" + } } \ No newline at end of file diff --git a/homeassistant/components/axis/translations/it.json b/homeassistant/components/axis/translations/it.json index e3083687b07..b771c6069bf 100644 --- a/homeassistant/components/axis/translations/it.json +++ b/homeassistant/components/axis/translations/it.json @@ -24,6 +24,5 @@ "title": "Impostazione del dispositivo Axis" } } - }, - "title": "Dispositivo Axis" + } } \ No newline at end of file diff --git a/homeassistant/components/axis/translations/ko.json b/homeassistant/components/axis/translations/ko.json index b336a290f9e..45adcca576f 100644 --- a/homeassistant/components/axis/translations/ko.json +++ b/homeassistant/components/axis/translations/ko.json @@ -24,6 +24,5 @@ "title": "Axis \uae30\uae30 \uc124\uc815\ud558\uae30" } } - }, - "title": "Axis \uae30\uae30" + } } \ No newline at end of file diff --git a/homeassistant/components/axis/translations/lb.json b/homeassistant/components/axis/translations/lb.json index c19c50381fc..d73489f6a43 100644 --- a/homeassistant/components/axis/translations/lb.json +++ b/homeassistant/components/axis/translations/lb.json @@ -24,6 +24,5 @@ "title": "Axis Apparat ariichten" } } - }, - "title": "Axis Apparat" + } } \ No newline at end of file diff --git a/homeassistant/components/axis/translations/nl.json b/homeassistant/components/axis/translations/nl.json index 852933c6204..8919514e44a 100644 --- a/homeassistant/components/axis/translations/nl.json +++ b/homeassistant/components/axis/translations/nl.json @@ -24,6 +24,5 @@ "title": "Stel het Axis-apparaat in" } } - }, - "title": "Axis-apparaat" + } } \ No newline at end of file diff --git a/homeassistant/components/axis/translations/no.json b/homeassistant/components/axis/translations/no.json index a316ca726b5..855bc4880ed 100644 --- a/homeassistant/components/axis/translations/no.json +++ b/homeassistant/components/axis/translations/no.json @@ -24,6 +24,5 @@ "title": "Sett opp Axis enhet" } } - }, - "title": "Axis enhet" + } } \ No newline at end of file diff --git a/homeassistant/components/axis/translations/pl.json b/homeassistant/components/axis/translations/pl.json index 7473c62f668..9b4d27f6205 100644 --- a/homeassistant/components/axis/translations/pl.json +++ b/homeassistant/components/axis/translations/pl.json @@ -24,6 +24,5 @@ "title": "Konfiguracja urz\u0105dzenia Axis" } } - }, - "title": "Urz\u0105dzenie Axis" + } } \ No newline at end of file diff --git a/homeassistant/components/axis/translations/pt-BR.json b/homeassistant/components/axis/translations/pt-BR.json index 10e9fb563ce..c63bbf7ed0a 100644 --- a/homeassistant/components/axis/translations/pt-BR.json +++ b/homeassistant/components/axis/translations/pt-BR.json @@ -24,6 +24,5 @@ "title": "Configurar o dispositivo Axis" } } - }, - "title": "Dispositivo Axis" + } } \ No newline at end of file diff --git a/homeassistant/components/axis/translations/pt.json b/homeassistant/components/axis/translations/pt.json index 2dc5a14249f..77ce7025f70 100644 --- a/homeassistant/components/axis/translations/pt.json +++ b/homeassistant/components/axis/translations/pt.json @@ -10,6 +10,5 @@ } } } - }, - "title": "Dispositivo Axis" + } } \ No newline at end of file diff --git a/homeassistant/components/axis/translations/ru.json b/homeassistant/components/axis/translations/ru.json index 578e642d4d2..c970377084e 100644 --- a/homeassistant/components/axis/translations/ru.json +++ b/homeassistant/components/axis/translations/ru.json @@ -24,6 +24,5 @@ "title": "Axis" } } - }, - "title": "Axis" + } } \ No newline at end of file diff --git a/homeassistant/components/axis/translations/sl.json b/homeassistant/components/axis/translations/sl.json index a41e5ddd652..ac7b359b19e 100644 --- a/homeassistant/components/axis/translations/sl.json +++ b/homeassistant/components/axis/translations/sl.json @@ -24,6 +24,5 @@ "title": "Nastavite plo\u0161\u010dek" } } - }, - "title": "Plo\u0161\u010dek" + } } \ No newline at end of file diff --git a/homeassistant/components/axis/translations/sv.json b/homeassistant/components/axis/translations/sv.json index c208838ed1a..f7c2e8902f7 100644 --- a/homeassistant/components/axis/translations/sv.json +++ b/homeassistant/components/axis/translations/sv.json @@ -24,6 +24,5 @@ "title": "Konfigurera Axis-enhet" } } - }, - "title": "Axis enhet" + } } \ No newline at end of file diff --git a/homeassistant/components/axis/translations/zh-Hant.json b/homeassistant/components/axis/translations/zh-Hant.json index e24c29a86bc..362d7205461 100644 --- a/homeassistant/components/axis/translations/zh-Hant.json +++ b/homeassistant/components/axis/translations/zh-Hant.json @@ -24,6 +24,5 @@ "title": "\u8a2d\u5b9a Axis \u8a2d\u5099" } } - }, - "title": "Axis \u8a2d\u5099" + } } \ No newline at end of file diff --git a/homeassistant/components/blebox/translations/lb.json b/homeassistant/components/blebox/translations/lb.json index dae53828764..ffe37b1f907 100644 --- a/homeassistant/components/blebox/translations/lb.json +++ b/homeassistant/components/blebox/translations/lb.json @@ -13,6 +13,7 @@ "step": { "user": { "data": { + "host": "IP Adresse", "port": "Port" }, "description": "BleBox ariichten fir d'Integratioun mam Home Assistant.", diff --git a/homeassistant/components/blink/translations/lb.json b/homeassistant/components/blink/translations/lb.json index bca5231d380..27ab3e6fd87 100644 --- a/homeassistant/components/blink/translations/lb.json +++ b/homeassistant/components/blink/translations/lb.json @@ -1,5 +1,12 @@ { "config": { + "abort": { + "already_configured": "Apparat ass scho konfigur\u00e9iert" + }, + "error": { + "invalid_auth": "Ong\u00eblteg Authentifikatioun", + "unknown": "Onerwaarte Feeler" + }, "step": { "2fa": { "data": { @@ -9,6 +16,10 @@ "title": "2-Faktor-Authentifikatioun" }, "user": { + "data": { + "password": "Passwuert", + "username": "Benotzernumm" + }, "title": "Mam Blink Kont verbannen" } } diff --git a/homeassistant/components/braviatv/translations/ca.json b/homeassistant/components/braviatv/translations/ca.json index 6c05b4b2fc4..c35dc3ce857 100644 --- a/homeassistant/components/braviatv/translations/ca.json +++ b/homeassistant/components/braviatv/translations/ca.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Aquest televisor ja est\u00e0 configurat." + "already_configured": "Aquest televisor ja est\u00e0 configurat.", + "no_ip_control": "El control IP del teu televisor est\u00e0 desactivat o aquest no \u00e9s compatible." }, "error": { "cannot_connect": "No s'ha pogut connectar, amfitri\u00f3 o codi PIN inv\u00e0lids.", diff --git a/homeassistant/components/braviatv/translations/en.json b/homeassistant/components/braviatv/translations/en.json index c9bc5a4469b..98a569b3ab1 100644 --- a/homeassistant/components/braviatv/translations/en.json +++ b/homeassistant/components/braviatv/translations/en.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "This TV is already configured." + "already_configured": "This TV is already configured.", + "no_ip_control": "IP Control is disabled on your TV or the TV is not supported." }, "error": { "cannot_connect": "Failed to connect, invalid host or PIN code.", diff --git a/homeassistant/components/braviatv/translations/es.json b/homeassistant/components/braviatv/translations/es.json index fa858a73079..61dac5c9d62 100644 --- a/homeassistant/components/braviatv/translations/es.json +++ b/homeassistant/components/braviatv/translations/es.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Este televisor ya est\u00e1 configurado." + "already_configured": "Este televisor ya est\u00e1 configurado.", + "no_ip_control": "El Control de IP est\u00e1 desactivado en tu televisor o el televisor no es compatible." }, "error": { "cannot_connect": "No se pudo conectar, host o c\u00f3digo PIN no v\u00e1lido.", diff --git a/homeassistant/components/braviatv/translations/lb.json b/homeassistant/components/braviatv/translations/lb.json index 37366eee6dd..7a00464bc3a 100644 --- a/homeassistant/components/braviatv/translations/lb.json +++ b/homeassistant/components/braviatv/translations/lb.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "D\u00ebse Fernseh ass scho konfigur\u00e9iert." + "already_configured": "D\u00ebse Fernseh ass scho konfigur\u00e9iert.", + "no_ip_control": "IP Kontroll ass d\u00e9aktiv\u00e9iert um TV oder den TV g\u00ebtt net \u00ebnnerst\u00ebtzt." }, "error": { "cannot_connect": "Feeler beim verbannen, ong\u00ebltege Numm oder PIN code.", diff --git a/homeassistant/components/braviatv/translations/no.json b/homeassistant/components/braviatv/translations/no.json index f6100b7843a..ff86974f763 100644 --- a/homeassistant/components/braviatv/translations/no.json +++ b/homeassistant/components/braviatv/translations/no.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Denne TV-en er allerede konfigurert." + "already_configured": "Denne TV-en er allerede konfigurert.", + "no_ip_control": "IP-kontrollen er deaktivert p\u00e5 TVen eller TV-en st\u00f8ttes ikke." }, "error": { "cannot_connect": "Kunne ikke koble til, ugyldig vert eller PIN-kode.", diff --git a/homeassistant/components/braviatv/translations/ru.json b/homeassistant/components/braviatv/translations/ru.json index e1cabf3bf4e..a799a29831f 100644 --- a/homeassistant/components/braviatv/translations/ru.json +++ b/homeassistant/components/braviatv/translations/ru.json @@ -1,7 +1,8 @@ { "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_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.", + "no_ip_control": "\u041d\u0430 \u0442\u0435\u043b\u0435\u0432\u0438\u0437\u043e\u0440\u0435 \u043e\u0442\u043a\u043b\u044e\u0447\u0435\u043d\u043e \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043f\u043e IP, \u043b\u0438\u0431\u043e \u044d\u0442\u0430 \u043c\u043e\u0434\u0435\u043b\u044c \u043d\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044f." }, "error": { "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f, \u043d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u0445\u043e\u0441\u0442 \u0438\u043b\u0438 PIN-\u043a\u043e\u0434.", diff --git a/homeassistant/components/braviatv/translations/zh-Hant.json b/homeassistant/components/braviatv/translations/zh-Hant.json index 5841c550330..027e44abaa4 100644 --- a/homeassistant/components/braviatv/translations/zh-Hant.json +++ b/homeassistant/components/braviatv/translations/zh-Hant.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "\u6b64\u96fb\u8996\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3002" + "already_configured": "\u6b64\u96fb\u8996\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3002", + "no_ip_control": "\u96fb\u8996\u4e0a\u7684 IP \u5df2\u95dc\u9589\u6216\u4e0d\u652f\u63f4\u6b64\u6b3e\u96fb\u8996\u3002" }, "error": { "cannot_connect": "\u9023\u7dda\u5931\u6557\uff0c\u7121\u6548\u7684\u4e3b\u6a5f\u540d\u7a31\u6216 PIN \u78bc\u3002", diff --git a/homeassistant/components/brother/translations/ca.json b/homeassistant/components/brother/translations/ca.json index a27c29239d8..6f8c1ca912f 100644 --- a/homeassistant/components/brother/translations/ca.json +++ b/homeassistant/components/brother/translations/ca.json @@ -16,8 +16,7 @@ "host": "Amfitri\u00f3", "type": "Tipus d'impressora" }, - "description": "Configura la integraci\u00f3 d'impressora Brother. Si tens problemes amb la configuraci\u00f3, visita: https://www.home-assistant.io/integrations/brother", - "title": "Impressora Brother" + "description": "Configura la integraci\u00f3 d'impressora Brother. Si tens problemes amb la configuraci\u00f3, visita: https://www.home-assistant.io/integrations/brother" }, "zeroconf_confirm": { "data": { diff --git a/homeassistant/components/brother/translations/da.json b/homeassistant/components/brother/translations/da.json index 6d9edc1fcad..09a8ca031dd 100644 --- a/homeassistant/components/brother/translations/da.json +++ b/homeassistant/components/brother/translations/da.json @@ -16,8 +16,7 @@ "host": "Printerens v\u00e6rtsnavn eller IP-adresse", "type": "Type af printer" }, - "description": "Konfigurer Brother-printerintegration. Hvis du har problemer med konfiguration, kan du g\u00e5 til: https://www.home-assistant.io/integrations/brother", - "title": "Brother-printer" + "description": "Konfigurer Brother-printerintegration. Hvis du har problemer med konfiguration, kan du g\u00e5 til: https://www.home-assistant.io/integrations/brother" }, "zeroconf_confirm": { "data": { diff --git a/homeassistant/components/brother/translations/de.json b/homeassistant/components/brother/translations/de.json index fb1a1420397..42d92777699 100644 --- a/homeassistant/components/brother/translations/de.json +++ b/homeassistant/components/brother/translations/de.json @@ -16,8 +16,7 @@ "host": "Drucker Hostname oder IP-Adresse", "type": "Typ des Druckers" }, - "description": "Einrichten der Brother-Drucker-Integration. Wenn Du Probleme mit der Konfiguration hast, gehe zu: https://www.home-assistant.io/integrations/brother", - "title": "Brother Drucker" + "description": "Einrichten der Brother-Drucker-Integration. Wenn Du Probleme mit der Konfiguration hast, gehe zu: https://www.home-assistant.io/integrations/brother" }, "zeroconf_confirm": { "data": { diff --git a/homeassistant/components/brother/translations/en.json b/homeassistant/components/brother/translations/en.json index 78fdc344d65..8cd1ed46919 100644 --- a/homeassistant/components/brother/translations/en.json +++ b/homeassistant/components/brother/translations/en.json @@ -16,8 +16,7 @@ "host": "Host", "type": "Type of the printer" }, - "description": "Set up Brother printer integration. If you have problems with configuration go to: https://www.home-assistant.io/integrations/brother", - "title": "Brother Printer" + "description": "Set up Brother printer integration. If you have problems with configuration go to: https://www.home-assistant.io/integrations/brother" }, "zeroconf_confirm": { "data": { diff --git a/homeassistant/components/brother/translations/es-419.json b/homeassistant/components/brother/translations/es-419.json index 286851ba454..17b1b8f9c89 100644 --- a/homeassistant/components/brother/translations/es-419.json +++ b/homeassistant/components/brother/translations/es-419.json @@ -16,8 +16,7 @@ "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", - "title": "Impresora Brother" + "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" }, "zeroconf_confirm": { "data": { diff --git a/homeassistant/components/brother/translations/es.json b/homeassistant/components/brother/translations/es.json index ffd0bd9c7a0..af05a39d9c9 100644 --- a/homeassistant/components/brother/translations/es.json +++ b/homeassistant/components/brother/translations/es.json @@ -16,8 +16,7 @@ "host": "Nombre del host o direcci\u00f3n IP de la impresora", "type": "Tipo de impresora" }, - "description": "Configura la integraci\u00f3n de impresoras Brother. Si tienes problemas con la configuraci\u00f3n, ve a: https://www.home-assistant.io/integrations/brother", - "title": "Impresora Brother" + "description": "Configura la integraci\u00f3n de impresoras Brother. Si tienes problemas con la configuraci\u00f3n, ve a: https://www.home-assistant.io/integrations/brother" }, "zeroconf_confirm": { "data": { diff --git a/homeassistant/components/brother/translations/fr.json b/homeassistant/components/brother/translations/fr.json index 9dba52055ac..1744197c499 100644 --- a/homeassistant/components/brother/translations/fr.json +++ b/homeassistant/components/brother/translations/fr.json @@ -16,8 +16,7 @@ "host": "Nom d'h\u00f4te ou adresse IP de l'imprimante", "type": "Type d'imprimante" }, - "description": "Configurez l'int\u00e9gration de l'imprimante Brother. Si vous avez des probl\u00e8mes avec la configuration, allez \u00e0 : https://www.home-assistant.io/integrations/brother", - "title": "Imprimante Brother" + "description": "Configurez l'int\u00e9gration de l'imprimante Brother. Si vous avez des probl\u00e8mes avec la configuration, allez \u00e0 : https://www.home-assistant.io/integrations/brother" }, "zeroconf_confirm": { "data": { diff --git a/homeassistant/components/brother/translations/hu.json b/homeassistant/components/brother/translations/hu.json index d952410b609..3c29c7101b9 100644 --- a/homeassistant/components/brother/translations/hu.json +++ b/homeassistant/components/brother/translations/hu.json @@ -16,8 +16,7 @@ "host": "Hoszt", "type": "A nyomtat\u00f3 t\u00edpusa" }, - "description": "A Brother nyomtat\u00f3 integr\u00e1ci\u00f3j\u00e1nak be\u00e1ll\u00edt\u00e1sa. Ha probl\u00e9m\u00e1id vannak a konfigur\u00e1ci\u00f3val, l\u00e1togass el a k\u00f6vetkez\u0151 oldalra: https://www.home-assistant.io/integrations/brother", - "title": "Brother nyomtat\u00f3" + "description": "A Brother nyomtat\u00f3 integr\u00e1ci\u00f3j\u00e1nak be\u00e1ll\u00edt\u00e1sa. Ha probl\u00e9m\u00e1id vannak a konfigur\u00e1ci\u00f3val, l\u00e1togass el a k\u00f6vetkez\u0151 oldalra: https://www.home-assistant.io/integrations/brother" }, "zeroconf_confirm": { "data": { diff --git a/homeassistant/components/brother/translations/it.json b/homeassistant/components/brother/translations/it.json index d007b8d0394..47d378606f1 100644 --- a/homeassistant/components/brother/translations/it.json +++ b/homeassistant/components/brother/translations/it.json @@ -16,8 +16,7 @@ "host": "Host", "type": "Tipo di stampante" }, - "description": "Configurare l'integrazione della stampante Brother. In caso di problemi con la configurazione, visitare: https://www.home-assistant.io/integrations/brother", - "title": "Stampante Brother" + "description": "Configurare l'integrazione della stampante Brother. In caso di problemi con la configurazione, visitare: https://www.home-assistant.io/integrations/brother" }, "zeroconf_confirm": { "data": { diff --git a/homeassistant/components/brother/translations/ko.json b/homeassistant/components/brother/translations/ko.json index 9c8c2cfb165..ce0231c497c 100644 --- a/homeassistant/components/brother/translations/ko.json +++ b/homeassistant/components/brother/translations/ko.json @@ -16,8 +16,7 @@ "host": "\ud638\uc2a4\ud2b8", "type": "\ud504\ub9b0\ud130\uc758 \uc885\ub958" }, - "description": "\ube0c\ub77c\ub354 \ud504\ub9b0\ud130 \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud569\ub2c8\ub2e4. \uad6c\uc131\uc5d0 \ubb38\uc81c\uac00\uc788\ub294 \uacbd\uc6b0 https://www.home-assistant.io/integrations/brother \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694", - "title": "\ube0c\ub77c\ub354 \ud504\ub9b0\ud130" + "description": "\ube0c\ub77c\ub354 \ud504\ub9b0\ud130 \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud569\ub2c8\ub2e4. \uad6c\uc131\uc5d0 \ubb38\uc81c\uac00\uc788\ub294 \uacbd\uc6b0 https://www.home-assistant.io/integrations/brother \ub97c \ucc38\uc870\ud574\uc8fc\uc138\uc694" }, "zeroconf_confirm": { "data": { diff --git a/homeassistant/components/brother/translations/lb.json b/homeassistant/components/brother/translations/lb.json index d8d49b0b3ac..12a92518e88 100644 --- a/homeassistant/components/brother/translations/lb.json +++ b/homeassistant/components/brother/translations/lb.json @@ -16,8 +16,7 @@ "host": "Printer Numm oder IP Adresse", "type": "Typ vum Printer" }, - "description": "Brother Printer Integratioun ariichten. Am Fall vun Problemer kuckt op: https://www.home-assistant.io/integrations/brother", - "title": "Brother Printer" + "description": "Brother Printer Integratioun ariichten. Am Fall vun Problemer kuckt op: https://www.home-assistant.io/integrations/brother" }, "zeroconf_confirm": { "data": { diff --git a/homeassistant/components/brother/translations/nl.json b/homeassistant/components/brother/translations/nl.json index 2dfef67fb18..5b641652971 100644 --- a/homeassistant/components/brother/translations/nl.json +++ b/homeassistant/components/brother/translations/nl.json @@ -16,8 +16,7 @@ "host": "Printerhostnaam of IP-adres", "type": "Type printer" }, - "description": "Zet Brother printerintegratie op. Als u problemen heeft met de configuratie ga dan naar: https://www.home-assistant.io/integrations/brother", - "title": "Brother Printer" + "description": "Zet Brother printerintegratie op. Als u problemen heeft met de configuratie ga dan naar: https://www.home-assistant.io/integrations/brother" }, "zeroconf_confirm": { "data": { diff --git a/homeassistant/components/brother/translations/no.json b/homeassistant/components/brother/translations/no.json index 26f7d0a79e7..51716f66e3d 100644 --- a/homeassistant/components/brother/translations/no.json +++ b/homeassistant/components/brother/translations/no.json @@ -16,8 +16,7 @@ "host": "Vertsnavn eller IP-adresse til skriveren", "type": "Skriver type" }, - "description": "Sett opp Brother skriver integrasjonen. Hvis du har problemer med konfigurasjonen, bes\u00f8k dokumentasjonen her: [https://www.home-assistant.io/integrations/brother](https://www.home-assistant.io/integrations/brother)", - "title": "Brother skriver" + "description": "Sett opp Brother skriver integrasjonen. Hvis du har problemer med konfigurasjonen, bes\u00f8k dokumentasjonen her: [https://www.home-assistant.io/integrations/brother](https://www.home-assistant.io/integrations/brother)" }, "zeroconf_confirm": { "data": { diff --git a/homeassistant/components/brother/translations/pl.json b/homeassistant/components/brother/translations/pl.json index 28b475013f3..cb7c3365628 100644 --- a/homeassistant/components/brother/translations/pl.json +++ b/homeassistant/components/brother/translations/pl.json @@ -16,8 +16,7 @@ "host": "Nazwa hosta lub adres IP", "type": "Typ drukarki" }, - "description": "Konfiguracja integracji drukarek Brother. Je\u015bli masz problemy z konfiguracj\u0105, przejd\u017a na stron\u0119: https://www.home-assistant.io/integrations/brother", - "title": "Drukarka Brother" + "description": "Konfiguracja integracji drukarek Brother. Je\u015bli masz problemy z konfiguracj\u0105, przejd\u017a na stron\u0119: https://www.home-assistant.io/integrations/brother" }, "zeroconf_confirm": { "data": { diff --git a/homeassistant/components/brother/translations/pt-BR.json b/homeassistant/components/brother/translations/pt-BR.json index b59501e8cfb..03f0788cb85 100644 --- a/homeassistant/components/brother/translations/pt-BR.json +++ b/homeassistant/components/brother/translations/pt-BR.json @@ -14,8 +14,7 @@ "host": "Nome do host ou endere\u00e7o IP da impressora", "type": "Tipo de impressora" }, - "description": "Configure a integra\u00e7\u00e3o da impressora Brother. Se voc\u00ea tiver problemas com a configura\u00e7\u00e3o, acesse: https://www.home-assistant.io/integrations/brother", - "title": "Impressora Brother" + "description": "Configure a integra\u00e7\u00e3o da impressora Brother. Se voc\u00ea tiver problemas com a configura\u00e7\u00e3o, acesse: https://www.home-assistant.io/integrations/brother" } } } diff --git a/homeassistant/components/brother/translations/ru.json b/homeassistant/components/brother/translations/ru.json index 57d84b17636..f880f754590 100644 --- a/homeassistant/components/brother/translations/ru.json +++ b/homeassistant/components/brother/translations/ru.json @@ -16,8 +16,7 @@ "host": "\u0425\u043e\u0441\u0442", "type": "\u0422\u0438\u043f \u043f\u0440\u0438\u043d\u0442\u0435\u0440\u0430" }, - "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: https://www.home-assistant.io/integrations/brother.", - "title": "\u041f\u0440\u0438\u043d\u0442\u0435\u0440 Brother" + "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: https://www.home-assistant.io/integrations/brother." }, "zeroconf_confirm": { "data": { diff --git a/homeassistant/components/brother/translations/sl.json b/homeassistant/components/brother/translations/sl.json index 91085e32ba1..2736366be02 100644 --- a/homeassistant/components/brother/translations/sl.json +++ b/homeassistant/components/brother/translations/sl.json @@ -16,8 +16,7 @@ "host": "Gostiteljsko ime tiskalnika ali naslov IP", "type": "Vrsta tiskalnika" }, - "description": "Nastavite integracijo tiskalnika Brother. \u010ce imate te\u017eave s konfiguracijo, pojdite na: https://www.home-assistant.io/integrations/brother", - "title": "Brother Tiskalnik" + "description": "Nastavite integracijo tiskalnika Brother. \u010ce imate te\u017eave s konfiguracijo, pojdite na: https://www.home-assistant.io/integrations/brother" }, "zeroconf_confirm": { "data": { diff --git a/homeassistant/components/brother/translations/sv.json b/homeassistant/components/brother/translations/sv.json index ad6372c423e..204064c0ab4 100644 --- a/homeassistant/components/brother/translations/sv.json +++ b/homeassistant/components/brother/translations/sv.json @@ -16,8 +16,7 @@ "host": "Skrivarens v\u00e4rdnamn eller IP-adress", "type": "Typ av skrivare" }, - "description": "St\u00e4ll in Brother-skrivarintegration. Om du har problem med konfigurationen g\u00e5r du till: https://www.home-assistant.io/integrations/brother", - "title": "Brother-skrivare" + "description": "St\u00e4ll in Brother-skrivarintegration. Om du har problem med konfigurationen g\u00e5r du till: https://www.home-assistant.io/integrations/brother" }, "zeroconf_confirm": { "data": { diff --git a/homeassistant/components/brother/translations/zh-Hant.json b/homeassistant/components/brother/translations/zh-Hant.json index 949f3c66854..8a9f811cddb 100644 --- a/homeassistant/components/brother/translations/zh-Hant.json +++ b/homeassistant/components/brother/translations/zh-Hant.json @@ -16,8 +16,7 @@ "host": "\u4e3b\u6a5f\u7aef", "type": "\u5370\u8868\u6a5f\u985e\u578b" }, - "description": "\u8a2d\u5b9a Brother \u5370\u8868\u6a5f\u6574\u5408\u3002\u5047\u5982\u9700\u8981\u5354\u52a9\uff0c\u8acb\u53c3\u8003\uff1ahttps://www.home-assistant.io/integrations/brother", - "title": "Brother \u5370\u8868\u6a5f" + "description": "\u8a2d\u5b9a Brother \u5370\u8868\u6a5f\u6574\u5408\u3002\u5047\u5982\u9700\u8981\u5354\u52a9\uff0c\u8acb\u53c3\u8003\uff1ahttps://www.home-assistant.io/integrations/brother" }, "zeroconf_confirm": { "data": { diff --git a/homeassistant/components/bsblan/translations/no.json b/homeassistant/components/bsblan/translations/no.json index a90cc5536b5..393024d2eff 100644 --- a/homeassistant/components/bsblan/translations/no.json +++ b/homeassistant/components/bsblan/translations/no.json @@ -10,9 +10,9 @@ "step": { "user": { "data": { - "host": "Vert eller IP-adresse", + "host": "Vert", "passkey": "Tilgangsn\u00f8kkel streng", - "port": "Portnummer" + "port": "Port" }, "description": "Konfigurer din BSB-Lan-enhet for \u00e5 integrere med Home Assistant.", "title": "Koble til BSB-Lan-enheten" diff --git a/homeassistant/components/cast/translations/bg.json b/homeassistant/components/cast/translations/bg.json index 746278595d0..92a840cc5af 100644 --- a/homeassistant/components/cast/translations/bg.json +++ b/homeassistant/components/cast/translations/bg.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "\u0418\u0441\u043a\u0430\u0442\u0435 \u043b\u0438 \u0434\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u0435 Google Cast?", - "title": "Google Cast" + "description": "\u0418\u0441\u043a\u0430\u0442\u0435 \u043b\u0438 \u0434\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u0435 Google Cast?" } } } diff --git a/homeassistant/components/cast/translations/ca.json b/homeassistant/components/cast/translations/ca.json index 0b358293304..02a2459fdc0 100644 --- a/homeassistant/components/cast/translations/ca.json +++ b/homeassistant/components/cast/translations/ca.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Vols configurar Google Cast?", - "title": "Google Cast" + "description": "Vols configurar Google Cast?" } } } diff --git a/homeassistant/components/cast/translations/cs.json b/homeassistant/components/cast/translations/cs.json index 79694e427ca..3f67763b48b 100644 --- a/homeassistant/components/cast/translations/cs.json +++ b/homeassistant/components/cast/translations/cs.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Chcete nastavit Google Cast?", - "title": "Google Cast" + "description": "Chcete nastavit Google Cast?" } } } diff --git a/homeassistant/components/cast/translations/da.json b/homeassistant/components/cast/translations/da.json index 3230e215bff..fe6ed03bb5a 100644 --- a/homeassistant/components/cast/translations/da.json +++ b/homeassistant/components/cast/translations/da.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Vil du ops\u00e6tte Google Cast?", - "title": "Google Cast" + "description": "Vil du ops\u00e6tte Google Cast?" } } } diff --git a/homeassistant/components/cast/translations/de.json b/homeassistant/components/cast/translations/de.json index 5a87e714bf6..87f8e7cb2bc 100644 --- a/homeassistant/components/cast/translations/de.json +++ b/homeassistant/components/cast/translations/de.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "M\u00f6chtest du Google Cast einrichten?", - "title": "Google Cast" + "description": "M\u00f6chtest du Google Cast einrichten?" } } } diff --git a/homeassistant/components/cast/translations/en.json b/homeassistant/components/cast/translations/en.json index 81ba0457240..e06a94f700b 100644 --- a/homeassistant/components/cast/translations/en.json +++ b/homeassistant/components/cast/translations/en.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Do you want to set up Google Cast?", - "title": "Google Cast" + "description": "Do you want to set up Google Cast?" } } } diff --git a/homeassistant/components/cast/translations/es-419.json b/homeassistant/components/cast/translations/es-419.json index c4374997d8a..c62ece17721 100644 --- a/homeassistant/components/cast/translations/es-419.json +++ b/homeassistant/components/cast/translations/es-419.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "\u00bfDesea configurar Google Cast?", - "title": "Google Cast" + "description": "\u00bfDesea configurar Google Cast?" } } } diff --git a/homeassistant/components/cast/translations/es.json b/homeassistant/components/cast/translations/es.json index 3a5f5653237..9b4efee18d5 100644 --- a/homeassistant/components/cast/translations/es.json +++ b/homeassistant/components/cast/translations/es.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "\u00bfQuieres configurar Google Cast?", - "title": "Google Cast" + "description": "\u00bfQuieres configurar Google Cast?" } } } diff --git a/homeassistant/components/cast/translations/fi.json b/homeassistant/components/cast/translations/fi.json index 21ec8206165..730f3ebf9b3 100644 --- a/homeassistant/components/cast/translations/fi.json +++ b/homeassistant/components/cast/translations/fi.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Haluatko m\u00e4\u00e4ritt\u00e4\u00e4 Google Castin?", - "title": "Google Cast" + "description": "Haluatko m\u00e4\u00e4ritt\u00e4\u00e4 Google Castin?" } } } diff --git a/homeassistant/components/cast/translations/fr.json b/homeassistant/components/cast/translations/fr.json index a4fef5a7c52..afa4b094fad 100644 --- a/homeassistant/components/cast/translations/fr.json +++ b/homeassistant/components/cast/translations/fr.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Voulez-vous configurer Google Cast?", - "title": "Google Cast" + "description": "Voulez-vous configurer Google Cast?" } } } diff --git a/homeassistant/components/cast/translations/he.json b/homeassistant/components/cast/translations/he.json index 019561c2b87..0bc4e834cb7 100644 --- a/homeassistant/components/cast/translations/he.json +++ b/homeassistant/components/cast/translations/he.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "\u05d4\u05d0\u05dd \u05d1\u05e8\u05e6\u05d5\u05e0\u05da \u05dc\u05d4\u05d2\u05d3\u05d9\u05e8 \u05d0\u05ea Google Cast?", - "title": "Google Cast" + "description": "\u05d4\u05d0\u05dd \u05d1\u05e8\u05e6\u05d5\u05e0\u05da \u05dc\u05d4\u05d2\u05d3\u05d9\u05e8 \u05d0\u05ea Google Cast?" } } } diff --git a/homeassistant/components/cast/translations/hu.json b/homeassistant/components/cast/translations/hu.json index 83f0f959c71..dc55cd224f8 100644 --- a/homeassistant/components/cast/translations/hu.json +++ b/homeassistant/components/cast/translations/hu.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Be szeretn\u00e9d \u00e1ll\u00edtani a Google Cast szolg\u00e1ltat\u00e1st?", - "title": "Google Cast" + "description": "Be szeretn\u00e9d \u00e1ll\u00edtani a Google Cast szolg\u00e1ltat\u00e1st?" } } } diff --git a/homeassistant/components/cast/translations/id.json b/homeassistant/components/cast/translations/id.json index f4d66facce1..d3e2bb5f360 100644 --- a/homeassistant/components/cast/translations/id.json +++ b/homeassistant/components/cast/translations/id.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Apakah Anda ingin menyiapkan Google Cast?", - "title": "Google Cast" + "description": "Apakah Anda ingin menyiapkan Google Cast?" } } } diff --git a/homeassistant/components/cast/translations/it.json b/homeassistant/components/cast/translations/it.json index ba3cdc0645d..6e56218c992 100644 --- a/homeassistant/components/cast/translations/it.json +++ b/homeassistant/components/cast/translations/it.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Vuoi configurare Google Cast?", - "title": "Google Cast" + "description": "Vuoi configurare Google Cast?" } } } diff --git a/homeassistant/components/cast/translations/ja.json b/homeassistant/components/cast/translations/ja.json index f078c7c13e9..fa1d4031562 100644 --- a/homeassistant/components/cast/translations/ja.json +++ b/homeassistant/components/cast/translations/ja.json @@ -5,8 +5,7 @@ }, "step": { "confirm": { - "description": "Google Cast\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3057\u307e\u3059\u304b\uff1f", - "title": "Google Cast" + "description": "Google Cast\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3057\u307e\u3059\u304b\uff1f" } } } diff --git a/homeassistant/components/cast/translations/ko.json b/homeassistant/components/cast/translations/ko.json index 1de8e74ec84..e57fceb7705 100644 --- a/homeassistant/components/cast/translations/ko.json +++ b/homeassistant/components/cast/translations/ko.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Google \uce90\uc2a4\ud2b8\ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", - "title": "Google \uce90\uc2a4\ud2b8" + "description": "Google \uce90\uc2a4\ud2b8\ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" } } } diff --git a/homeassistant/components/cast/translations/lb.json b/homeassistant/components/cast/translations/lb.json index 9ea5e36b379..813ffd10072 100644 --- a/homeassistant/components/cast/translations/lb.json +++ b/homeassistant/components/cast/translations/lb.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Soll Google Cast konfigur\u00e9iert ginn?", - "title": "Google Cast" + "description": "Soll Google Cast konfigur\u00e9iert ginn?" } } } diff --git a/homeassistant/components/cast/translations/nl.json b/homeassistant/components/cast/translations/nl.json index b22c25f1292..d42ef3e850c 100644 --- a/homeassistant/components/cast/translations/nl.json +++ b/homeassistant/components/cast/translations/nl.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Wilt u Google Cast instellen?", - "title": "Google Cast" + "description": "Wilt u Google Cast instellen?" } } } diff --git a/homeassistant/components/cast/translations/nn.json b/homeassistant/components/cast/translations/nn.json index 0c7d56d3ad8..44d26792812 100644 --- a/homeassistant/components/cast/translations/nn.json +++ b/homeassistant/components/cast/translations/nn.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Vil du sette opp Google Cast?", - "title": "Google Cast" + "description": "Vil du sette opp Google Cast?" } } } diff --git a/homeassistant/components/cast/translations/no.json b/homeassistant/components/cast/translations/no.json index de041d89f81..b96be399cc8 100644 --- a/homeassistant/components/cast/translations/no.json +++ b/homeassistant/components/cast/translations/no.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "\u00d8nsker du \u00e5 sette opp Google Cast?", - "title": "" + "description": "\u00d8nsker du \u00e5 sette opp Google Cast?" } } } diff --git a/homeassistant/components/cast/translations/pl.json b/homeassistant/components/cast/translations/pl.json index 26897da5da3..965ee22ee5d 100644 --- a/homeassistant/components/cast/translations/pl.json +++ b/homeassistant/components/cast/translations/pl.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Czy chcesz skonfigurowa\u0107 Google Cast?", - "title": "Google Cast" + "description": "Czy chcesz skonfigurowa\u0107 Google Cast?" } } } diff --git a/homeassistant/components/cast/translations/pt-BR.json b/homeassistant/components/cast/translations/pt-BR.json index 25c0adf866f..000971f9e4c 100644 --- a/homeassistant/components/cast/translations/pt-BR.json +++ b/homeassistant/components/cast/translations/pt-BR.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Deseja configurar o Google Cast?", - "title": "Google Cast" + "description": "Deseja configurar o Google Cast?" } } } diff --git a/homeassistant/components/cast/translations/pt.json b/homeassistant/components/cast/translations/pt.json index dcf0391c420..9dd9a69a94c 100644 --- a/homeassistant/components/cast/translations/pt.json +++ b/homeassistant/components/cast/translations/pt.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Deseja configurar o Google Cast?", - "title": "Google Cast" + "description": "Deseja configurar o Google Cast?" } } } diff --git a/homeassistant/components/cast/translations/ro.json b/homeassistant/components/cast/translations/ro.json index 4dd8c04c381..6e93a0fdb1a 100644 --- a/homeassistant/components/cast/translations/ro.json +++ b/homeassistant/components/cast/translations/ro.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Dori\u021bi s\u0103 configura\u021bi Google Cast?", - "title": "Google Cast" + "description": "Dori\u021bi s\u0103 configura\u021bi Google Cast?" } } } diff --git a/homeassistant/components/cast/translations/ru.json b/homeassistant/components/cast/translations/ru.json index fae0cd417ff..a62f33832e0 100644 --- a/homeassistant/components/cast/translations/ru.json +++ b/homeassistant/components/cast/translations/ru.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c Google Cast?", - "title": "Google Cast" + "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c Google Cast?" } } } diff --git a/homeassistant/components/cast/translations/sl.json b/homeassistant/components/cast/translations/sl.json index eb4e930af86..c4d2ba98006 100644 --- a/homeassistant/components/cast/translations/sl.json +++ b/homeassistant/components/cast/translations/sl.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Ali \u017eelite nastaviti Google Cast?", - "title": "Google Cast" + "description": "Ali \u017eelite nastaviti Google Cast?" } } } diff --git a/homeassistant/components/cast/translations/sv.json b/homeassistant/components/cast/translations/sv.json index 937604b1000..056c00b1765 100644 --- a/homeassistant/components/cast/translations/sv.json +++ b/homeassistant/components/cast/translations/sv.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Vill du konfigurera Google Cast?", - "title": "Google Cast" + "description": "Vill du konfigurera Google Cast?" } } } diff --git a/homeassistant/components/cast/translations/th.json b/homeassistant/components/cast/translations/th.json index 9806057716a..64a5eaa3085 100644 --- a/homeassistant/components/cast/translations/th.json +++ b/homeassistant/components/cast/translations/th.json @@ -5,8 +5,7 @@ }, "step": { "confirm": { - "description": "\u0e04\u0e38\u0e13\u0e15\u0e49\u0e2d\u0e07\u0e01\u0e32\u0e23\u0e15\u0e31\u0e49\u0e07\u0e04\u0e48\u0e32 Google Cast \u0e2b\u0e23\u0e37\u0e2d\u0e44\u0e21\u0e48?", - "title": "Google Cast" + "description": "\u0e04\u0e38\u0e13\u0e15\u0e49\u0e2d\u0e07\u0e01\u0e32\u0e23\u0e15\u0e31\u0e49\u0e07\u0e04\u0e48\u0e32 Google Cast \u0e2b\u0e23\u0e37\u0e2d\u0e44\u0e21\u0e48?" } } } diff --git a/homeassistant/components/cast/translations/vi.json b/homeassistant/components/cast/translations/vi.json index 7e75cfce4fa..f65f3c58ebe 100644 --- a/homeassistant/components/cast/translations/vi.json +++ b/homeassistant/components/cast/translations/vi.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "B\u1ea1n c\u00f3 mu\u1ed1n thi\u1ebft l\u1eadp Google Cast kh\u00f4ng?", - "title": "Google Cast" + "description": "B\u1ea1n c\u00f3 mu\u1ed1n thi\u1ebft l\u1eadp Google Cast kh\u00f4ng?" } } } diff --git a/homeassistant/components/cast/translations/zh-Hans.json b/homeassistant/components/cast/translations/zh-Hans.json index 9f834a98990..1c2024f8b81 100644 --- a/homeassistant/components/cast/translations/zh-Hans.json +++ b/homeassistant/components/cast/translations/zh-Hans.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "\u60a8\u60f3\u8981\u914d\u7f6e Google Cast \u5417\uff1f", - "title": "Google Cast" + "description": "\u60a8\u60f3\u8981\u914d\u7f6e Google Cast \u5417\uff1f" } } } diff --git a/homeassistant/components/cast/translations/zh-Hant.json b/homeassistant/components/cast/translations/zh-Hant.json index 0b7101b5cc8..3bb3e70d688 100644 --- a/homeassistant/components/cast/translations/zh-Hant.json +++ b/homeassistant/components/cast/translations/zh-Hant.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "\u662f\u5426\u8981\u8a2d\u5b9a Google Cast\uff1f", - "title": "Google Cast" + "description": "\u662f\u5426\u8981\u8a2d\u5b9a Google Cast\uff1f" } } } diff --git a/homeassistant/components/daikin/translations/bg.json b/homeassistant/components/daikin/translations/bg.json index dd80874adaa..a1f8209b2bf 100644 --- a/homeassistant/components/daikin/translations/bg.json +++ b/homeassistant/components/daikin/translations/bg.json @@ -1,9 +1,7 @@ { "config": { "abort": { - "already_configured": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e", - "device_fail": "\u041d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430 \u043f\u0440\u0438 \u0441\u044a\u0437\u0434\u0430\u0432\u0430\u043d\u0435\u0442\u043e \u043d\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e.", - "device_timeout": "\u041d\u0435\u0432\u044a\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442 \u0437\u0430 \u0441\u0432\u043e\u0435\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435 \u0441 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e." + "already_configured": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e" }, "step": { "user": { diff --git a/homeassistant/components/daikin/translations/ca.json b/homeassistant/components/daikin/translations/ca.json index 4445c05e7d8..825c637610a 100644 --- a/homeassistant/components/daikin/translations/ca.json +++ b/homeassistant/components/daikin/translations/ca.json @@ -1,9 +1,7 @@ { "config": { "abort": { - "already_configured": "El dispositiu ja est\u00e0 configurat", - "device_fail": "S'ha produ\u00eft un error inesperat al crear el dispositiu.", - "device_timeout": "S'ha acabat el temps d'espera en la connexi\u00f3 amb el dispositiu." + "already_configured": "El dispositiu ja est\u00e0 configurat" }, "error": { "device_fail": "Error inesperat", diff --git a/homeassistant/components/daikin/translations/da.json b/homeassistant/components/daikin/translations/da.json index 230bd7ecbd8..6502ced4751 100644 --- a/homeassistant/components/daikin/translations/da.json +++ b/homeassistant/components/daikin/translations/da.json @@ -1,9 +1,7 @@ { "config": { "abort": { - "already_configured": "Enheden er allerede konfigureret", - "device_fail": "Uventet fejl ved oprettelse af enhed.", - "device_timeout": "Timeout ved tilslutning til enheden." + "already_configured": "Enheden er allerede konfigureret" }, "step": { "user": { diff --git a/homeassistant/components/daikin/translations/de.json b/homeassistant/components/daikin/translations/de.json index 98ccb6433a4..e8e3cd0bf6a 100644 --- a/homeassistant/components/daikin/translations/de.json +++ b/homeassistant/components/daikin/translations/de.json @@ -1,9 +1,7 @@ { "config": { "abort": { - "already_configured": "Ger\u00e4t ist bereits konfiguriert", - "device_fail": "Unerwarteter Fehler beim Erstellen des Ger\u00e4ts.", - "device_timeout": "Zeit\u00fcberschreitung beim Verbinden mit dem Ger\u00e4t." + "already_configured": "Ger\u00e4t ist bereits konfiguriert" }, "error": { "device_timeout": "Verbindung fehlgeschlagen" diff --git a/homeassistant/components/daikin/translations/en.json b/homeassistant/components/daikin/translations/en.json index 573a4c5973e..cf0f679c7ca 100644 --- a/homeassistant/components/daikin/translations/en.json +++ b/homeassistant/components/daikin/translations/en.json @@ -1,9 +1,7 @@ { "config": { "abort": { - "already_configured": "Device is already configured", - "device_fail": "Unexpected error creating device.", - "device_timeout": "Timeout connecting to the device." + "already_configured": "Device is already configured" }, "error": { "device_fail": "Unexpected error", diff --git a/homeassistant/components/daikin/translations/es-419.json b/homeassistant/components/daikin/translations/es-419.json index 3facdce66d4..8667011e2e4 100644 --- a/homeassistant/components/daikin/translations/es-419.json +++ b/homeassistant/components/daikin/translations/es-419.json @@ -1,9 +1,7 @@ { "config": { "abort": { - "already_configured": "El dispositivo ya est\u00e1 configurado", - "device_fail": "Error inesperado al crear el dispositivo.", - "device_timeout": "Tiempo de espera de conexi\u00f3n al dispositivo." + "already_configured": "El dispositivo ya est\u00e1 configurado" }, "step": { "user": { diff --git a/homeassistant/components/daikin/translations/es.json b/homeassistant/components/daikin/translations/es.json index 2bada00d0cb..3e667fa65cf 100644 --- a/homeassistant/components/daikin/translations/es.json +++ b/homeassistant/components/daikin/translations/es.json @@ -1,9 +1,7 @@ { "config": { "abort": { - "already_configured": "El dispositivo ya est\u00e1 configurado", - "device_fail": "Error inesperado al crear el dispositivo.", - "device_timeout": "Tiempo de espera agotado al conectar con el dispositivo." + "already_configured": "El dispositivo ya est\u00e1 configurado" }, "error": { "device_fail": "Error inesperado", diff --git a/homeassistant/components/daikin/translations/fr.json b/homeassistant/components/daikin/translations/fr.json index 3a3f08ae1f3..f3351631fa8 100644 --- a/homeassistant/components/daikin/translations/fr.json +++ b/homeassistant/components/daikin/translations/fr.json @@ -1,9 +1,7 @@ { "config": { "abort": { - "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9", - "device_fail": "Erreur inattendue lors de la cr\u00e9ation du p\u00e9riph\u00e9rique.", - "device_timeout": "D\u00e9lai de connexion au p\u00e9riph\u00e9rique expir\u00e9." + "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9" }, "step": { "user": { diff --git a/homeassistant/components/daikin/translations/hu.json b/homeassistant/components/daikin/translations/hu.json index c71d4f08fc8..b11dba9028e 100644 --- a/homeassistant/components/daikin/translations/hu.json +++ b/homeassistant/components/daikin/translations/hu.json @@ -1,9 +1,7 @@ { "config": { "abort": { - "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", - "device_fail": "Az eszk\u00f6z l\u00e9trehoz\u00e1sakor v\u00e1ratlan hiba l\u00e9pett fel.", - "device_timeout": "Id\u0151t\u00fall\u00e9p\u00e9s a k\u00e9sz\u00fcl\u00e9k csatlakoz\u00e1sakor." + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" }, "error": { "device_fail": "V\u00e1ratlan hiba", diff --git a/homeassistant/components/daikin/translations/it.json b/homeassistant/components/daikin/translations/it.json index 08715eaec4c..373dbcd53d2 100644 --- a/homeassistant/components/daikin/translations/it.json +++ b/homeassistant/components/daikin/translations/it.json @@ -1,9 +1,7 @@ { "config": { "abort": { - "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", - "device_fail": "Errore inatteso durante la creazione del dispositivo.", - "device_timeout": "Tempo scaduto per la connessione al dispositivo." + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato" }, "error": { "device_fail": "Errore imprevisto", diff --git a/homeassistant/components/daikin/translations/ko.json b/homeassistant/components/daikin/translations/ko.json index a1b2dd6ee4a..ff79df2de84 100644 --- a/homeassistant/components/daikin/translations/ko.json +++ b/homeassistant/components/daikin/translations/ko.json @@ -1,9 +1,7 @@ { "config": { "abort": { - "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "device_fail": "\uae30\uae30\ub97c \uad6c\uc131\ud558\ub294 \ub3c4\uc911 \uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.", - "device_timeout": "\uae30\uae30 \uc5f0\uacb0 \uc2dc\uac04\uc774 \ucd08\uacfc\ud588\uc2b5\ub2c8\ub2e4." + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { "device_fail": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4", diff --git a/homeassistant/components/daikin/translations/lb.json b/homeassistant/components/daikin/translations/lb.json index 25039f5af80..16d7caf4b37 100644 --- a/homeassistant/components/daikin/translations/lb.json +++ b/homeassistant/components/daikin/translations/lb.json @@ -1,9 +1,7 @@ { "config": { "abort": { - "already_configured": "Apparat ass scho konfigur\u00e9iert", - "device_fail": "Onerwaarte Feeler beim erstelle vum Apparat.", - "device_timeout": "Z\u00e4it Iwwerschreidung beim verbannen mam Apparat." + "already_configured": "Apparat ass scho konfigur\u00e9iert" }, "error": { "device_fail": "Onerwaarte Feeler", diff --git a/homeassistant/components/daikin/translations/nl.json b/homeassistant/components/daikin/translations/nl.json index 0e0db0c907c..6fa2362ee59 100644 --- a/homeassistant/components/daikin/translations/nl.json +++ b/homeassistant/components/daikin/translations/nl.json @@ -1,9 +1,7 @@ { "config": { "abort": { - "already_configured": "Apparaat is al geconfigureerd", - "device_fail": "Onverwachte fout bij het aanmaken van een apparaat.", - "device_timeout": "Time-out voor verbinding met het apparaat." + "already_configured": "Apparaat is al geconfigureerd" }, "step": { "user": { diff --git a/homeassistant/components/daikin/translations/no.json b/homeassistant/components/daikin/translations/no.json index 2bfddeb9973..98d93a29952 100644 --- a/homeassistant/components/daikin/translations/no.json +++ b/homeassistant/components/daikin/translations/no.json @@ -1,16 +1,12 @@ { "config": { "abort": { - "already_configured": "Enheten er allerede konfigurert", - "device_fail": "Uventet feil under oppretting av enheten.", - "device_timeout": "Tidsavbrudd for tilkobling til enheten." + "already_configured": "Enheten er allerede konfigurert" }, "step": { "user": { "data": { - "host": "Vert", - "key": "Godkjenningsn\u00f8kkel (brukes bare av BRP072C/Zena enheter)", - "password": "Enhetspassord (brukes bare av SKYFi-enheter)" + "host": "Vert" }, "description": "Fyll inn IP-adressen til din Daikin AC.", "title": "Konfigurer Daikin AC" diff --git a/homeassistant/components/daikin/translations/pl.json b/homeassistant/components/daikin/translations/pl.json index 9eaa748d03c..fc39f78c2d0 100644 --- a/homeassistant/components/daikin/translations/pl.json +++ b/homeassistant/components/daikin/translations/pl.json @@ -1,9 +1,7 @@ { "config": { "abort": { - "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane.", - "device_fail": "Nieoczekiwany b\u0142\u0105d tworzenia urz\u0105dzenia.", - "device_timeout": "Przekroczono limit czasu \u0142\u0105czenia z urz\u0105dzeniem." + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane." }, "error": { "device_fail": "Nieoczekiwany b\u0142\u0105d.", diff --git a/homeassistant/components/daikin/translations/pt-BR.json b/homeassistant/components/daikin/translations/pt-BR.json index 8c4eaed25ae..7853563a53c 100644 --- a/homeassistant/components/daikin/translations/pt-BR.json +++ b/homeassistant/components/daikin/translations/pt-BR.json @@ -1,9 +1,7 @@ { "config": { "abort": { - "already_configured": "O dispositivo j\u00e1 est\u00e1 configurado", - "device_fail": "Erro inesperado ao criar dispositivo.", - "device_timeout": "Excedido tempo limite conectando ao dispositivo" + "already_configured": "O dispositivo j\u00e1 est\u00e1 configurado" }, "error": { "device_fail": "Erro inesperado", diff --git a/homeassistant/components/daikin/translations/pt.json b/homeassistant/components/daikin/translations/pt.json index 7e8f0086194..2c9dc8fab29 100644 --- a/homeassistant/components/daikin/translations/pt.json +++ b/homeassistant/components/daikin/translations/pt.json @@ -1,9 +1,7 @@ { "config": { "abort": { - "already_configured": "O dispositivo j\u00e1 est\u00e1 configurado", - "device_fail": "Erro inesperado ao criar dispositivo.", - "device_timeout": "Tempo excedido a tentar ligar ao dispositivo." + "already_configured": "O dispositivo j\u00e1 est\u00e1 configurado" }, "step": { "user": { diff --git a/homeassistant/components/daikin/translations/ru.json b/homeassistant/components/daikin/translations/ru.json index 7aa72c15438..5332ac046e1 100644 --- a/homeassistant/components/daikin/translations/ru.json +++ b/homeassistant/components/daikin/translations/ru.json @@ -1,9 +1,7 @@ { "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.", - "device_fail": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430.", - "device_timeout": "\u0418\u0441\u0442\u0435\u043a\u043b\u043e \u0432\u0440\u0435\u043c\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443." + "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": { "device_fail": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430.", diff --git a/homeassistant/components/daikin/translations/sl.json b/homeassistant/components/daikin/translations/sl.json index f48d729b83c..a9f8514146f 100644 --- a/homeassistant/components/daikin/translations/sl.json +++ b/homeassistant/components/daikin/translations/sl.json @@ -1,9 +1,7 @@ { "config": { "abort": { - "already_configured": "Naprava je \u017ee konfigurirana", - "device_fail": "Nepri\u010dakovana napaka pri ustvarjanju naprave.", - "device_timeout": "\u010casovna omejitev za priklop na napravo je potekla." + "already_configured": "Naprava je \u017ee konfigurirana" }, "step": { "user": { diff --git a/homeassistant/components/daikin/translations/sv.json b/homeassistant/components/daikin/translations/sv.json index db785feab0b..a704822f0b7 100644 --- a/homeassistant/components/daikin/translations/sv.json +++ b/homeassistant/components/daikin/translations/sv.json @@ -1,9 +1,7 @@ { "config": { "abort": { - "already_configured": "Enheten \u00e4r redan konfigurerad", - "device_fail": "Ov\u00e4ntat fel vid skapande av enhet.", - "device_timeout": "Timeout f\u00f6r anslutning till enheten." + "already_configured": "Enheten \u00e4r redan konfigurerad" }, "step": { "user": { diff --git a/homeassistant/components/daikin/translations/zh-Hans.json b/homeassistant/components/daikin/translations/zh-Hans.json index 57b891d1adf..d27301c2f20 100644 --- a/homeassistant/components/daikin/translations/zh-Hans.json +++ b/homeassistant/components/daikin/translations/zh-Hans.json @@ -1,9 +1,7 @@ { "config": { "abort": { - "already_configured": "\u8bbe\u5907\u5df2\u914d\u7f6e\u5b8c\u6210", - "device_fail": "\u521b\u5efa\u8bbe\u5907\u65f6\u51fa\u73b0\u610f\u5916\u9519\u8bef\u3002", - "device_timeout": "\u8fde\u63a5\u8bbe\u5907\u8d85\u65f6\u3002" + "already_configured": "\u8bbe\u5907\u5df2\u914d\u7f6e\u5b8c\u6210" }, "step": { "user": { diff --git a/homeassistant/components/daikin/translations/zh-Hant.json b/homeassistant/components/daikin/translations/zh-Hant.json index 9bffbc2b728..20121842a37 100644 --- a/homeassistant/components/daikin/translations/zh-Hant.json +++ b/homeassistant/components/daikin/translations/zh-Hant.json @@ -1,9 +1,7 @@ { "config": { "abort": { - "already_configured": "\u8a2d\u5099\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", - "device_fail": "\u5275\u5efa\u8a2d\u5099\u6642\u767c\u751f\u672a\u77e5\u932f\u8aa4\u3002", - "device_timeout": "\u9023\u7dda\u81f3\u8a2d\u5099\u903e\u6642\u3002" + "already_configured": "\u8a2d\u5099\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" }, "error": { "device_fail": "\u672a\u9810\u671f\u932f\u8aa4", diff --git a/homeassistant/components/deconz/translations/bg.json b/homeassistant/components/deconz/translations/bg.json index ad79cb9d584..11a91d859a3 100644 --- a/homeassistant/components/deconz/translations/bg.json +++ b/homeassistant/components/deconz/translations/bg.json @@ -17,18 +17,9 @@ "description": "\u0418\u0441\u043a\u0430\u0442\u0435 \u043b\u0438 \u0434\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u0442\u0435 Home Assistant \u0434\u0430 \u0441\u0435 \u0441\u0432\u044a\u0440\u0437\u0432\u0430 \u0441 deCONZ \u0431\u0430\u0437\u043e\u0432\u0430 \u0441\u0442\u0430\u043d\u0446\u0438\u044f, \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u0435\u043d \u043e\u0442 \u0434\u043e\u0431\u0430\u0432\u043a\u0430\u0442\u0430 \u0437\u0430 hass.io {addon}?", "title": "deCONZ Zigbee \u0431\u0430\u0437\u043e\u0432\u0430 \u0441\u0442\u0430\u043d\u0446\u0438\u044f \u0447\u0440\u0435\u0437 Hass.io \u0434\u043e\u0431\u0430\u0432\u043a\u0430" }, - "init": { - "title": "\u0414\u0435\u0444\u0438\u043d\u0438\u0440\u0430\u043d\u0435 \u043d\u0430 deCONZ \u0448\u043b\u044e\u0437" - }, "link": { "description": "\u041e\u0442\u043a\u043b\u044e\u0447\u0438 deCONZ \u0448\u043b\u044e\u0437\u0430 \u0437\u0430 \u0434\u0430 \u0441\u0435 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u0430 \u0441 Home Assistant.\n\n1. \u041e\u0442\u0438\u0434\u0435\u0442\u0435 \u043d\u0430 deCONZ Settings -> Gateway -> Advanced\n2. \u041d\u0430\u0442\u0438\u0441\u043d\u0435\u0442\u0435 \u0431\u0443\u0442\u043e\u043d\u0430 \"Authenticate app\"", "title": "\u0412\u0440\u044a\u0437\u043a\u0430 \u0441 deCONZ" - }, - "manual_confirm": { - "data": { - "host": "\u0410\u0434\u0440\u0435\u0441", - "port": "\u041f\u043e\u0440\u0442" - } } } }, diff --git a/homeassistant/components/deconz/translations/ca.json b/homeassistant/components/deconz/translations/ca.json index 9559dcfb211..1c8500b3cc1 100644 --- a/homeassistant/components/deconz/translations/ca.json +++ b/homeassistant/components/deconz/translations/ca.json @@ -17,31 +17,20 @@ "description": "Vols configurar Home Assistant perqu\u00e8 es connecti amb la passarel\u00b7la deCONZ proporcionada pel complement de Hass.io: {addon}?", "title": "Passarel\u00b7la d'enlla\u00e7 deCONZ Zigbee (complement de Hass.io)" }, - "init": { - "title": "Definici\u00f3 de la passarel\u00b7la deCONZ" - }, "link": { "description": "Desbloqueja la teva passarel\u00b7la d'enlla\u00e7 deCONZ per a registrar-te amb Home Assistant.\n\n1. V\u00e9s a la configuraci\u00f3 del sistema deCONZ -> Passarel\u00b7la -> Avan\u00e7at\n2. Prem el bot\u00f3 \"Autenticar applicaci\u00f3\"", "title": "Vincular amb deCONZ" }, - "manual_confirm": { - "data": { - "host": "Amfitri\u00f3", - "port": "Port" - } - }, "manual_input": { "data": { "host": "Amfitri\u00f3", "port": "Port" - }, - "title": "Configuraci\u00f3 de la passarel\u00b7la deCONZ" + } }, "user": { "data": { "host": "Selecciona la passarel\u00b7la deCONZ descoberta" - }, - "title": "Selecci\u00f3 de la passarel\u00b7la deCONZ" + } } } }, diff --git a/homeassistant/components/deconz/translations/cs.json b/homeassistant/components/deconz/translations/cs.json index 360cc9e113f..79c173d692a 100644 --- a/homeassistant/components/deconz/translations/cs.json +++ b/homeassistant/components/deconz/translations/cs.json @@ -14,18 +14,9 @@ "description": "Chcete nakonfigurovat slu\u017ebu Home Assistant pro p\u0159ipojen\u00ed k deCONZ br\u00e1n\u011b pomoc\u00ed hass.io {addon}?", "title": "deCONZ Zigbee br\u00e1na prost\u0159ednictv\u00edm dopl\u0148ku Hass.io" }, - "init": { - "title": "Definujte br\u00e1nu deCONZ" - }, "link": { "description": "Odemkn\u011bte br\u00e1nu deCONZ, pro registraci v Home Assistant. \n\n 1. P\u0159ejd\u011bte do nastaven\u00ed syst\u00e9mu deCONZ \n 2. Stiskn\u011bte tla\u010d\u00edtko \"Unlock Gateway\"", "title": "Propojit s deCONZ" - }, - "manual_confirm": { - "data": { - "host": "Hostitel", - "port": "Port" - } } } } diff --git a/homeassistant/components/deconz/translations/cy.json b/homeassistant/components/deconz/translations/cy.json index 594ea26ee6f..6119486f841 100644 --- a/homeassistant/components/deconz/translations/cy.json +++ b/homeassistant/components/deconz/translations/cy.json @@ -9,18 +9,9 @@ "no_key": "Methu cael allwedd API" }, "step": { - "init": { - "title": "Diffiniwch porth dad-adeiladu" - }, "link": { "description": "Datgloi eich porth deCONZ i gofrestru gyda Cynorthwydd Cartref.\n\n1. Ewch i osodiadau system deCONZ \n2. Bwyso botwm \"Datgloi porth\"", "title": "Cysylltu \u00e2 deCONZ" - }, - "manual_confirm": { - "data": { - "host": "Gwesteiwr", - "port": "Port (gwerth diofyn: '80')" - } } } } diff --git a/homeassistant/components/deconz/translations/da.json b/homeassistant/components/deconz/translations/da.json index 348eba18ae3..e4f57869779 100644 --- a/homeassistant/components/deconz/translations/da.json +++ b/homeassistant/components/deconz/translations/da.json @@ -17,18 +17,9 @@ "description": "Vil du konfigurere Home Assistant til at oprette forbindelse til deCONZ-gateway'en leveret af Hass.io-tilf\u00f8jelsen {addon}?", "title": "deCONZ Zigbee-gateway via Hass.io-tilf\u00f8jelse" }, - "init": { - "title": "Definer deCONZ-gateway" - }, "link": { "description": "L\u00e5s din deCONZ-gateway op for at registrere dig med Home Assistant. \n\n 1. G\u00e5 til deCONZ settings -> Gateway -> Advanced\n 2. Tryk p\u00e5 knappen \"Authenticate app\"", "title": "Forbind med deCONZ" - }, - "manual_confirm": { - "data": { - "host": "V\u00e6rt", - "port": "Port" - } } } }, diff --git a/homeassistant/components/deconz/translations/de.json b/homeassistant/components/deconz/translations/de.json index be359a00ca8..6b9fcc0d3a6 100644 --- a/homeassistant/components/deconz/translations/de.json +++ b/homeassistant/components/deconz/translations/de.json @@ -17,31 +17,20 @@ "description": "M\u00f6chtest du Home Assistant so konfigurieren, dass er eine Verbindung mit dem deCONZ Gateway herstellt, der vom Add-on hass.io {addon} bereitgestellt wird?", "title": "deCONZ Zigbee Gateway \u00fcber das Hass.io Add-on" }, - "init": { - "title": "Definiere das deCONZ-Gateway" - }, "link": { "description": "Entsperre dein deCONZ-Gateway, um es bei Home Assistant zu registrieren. \n\n 1. Gehe in die deCONZ-Systemeinstellungen \n 2. Dr\u00fccke die Taste \"Gateway entsperren\"", "title": "Mit deCONZ verbinden" }, - "manual_confirm": { - "data": { - "host": "Host", - "port": "Port" - } - }, "manual_input": { "data": { "host": "Host", "port": "Port" - }, - "title": "Konfigurieren Sie das deCONZ-Gateway" + } }, "user": { "data": { "host": "W\u00e4hlen Sie das erkannte deCONZ-Gateway aus" - }, - "title": "W\u00e4hlen Sie das deCONZ-Gateway" + } } } }, diff --git a/homeassistant/components/deconz/translations/en.json b/homeassistant/components/deconz/translations/en.json index 159171a65d2..fa329d2e1b3 100644 --- a/homeassistant/components/deconz/translations/en.json +++ b/homeassistant/components/deconz/translations/en.json @@ -17,31 +17,20 @@ "description": "Do you want to configure Home Assistant to connect to the deCONZ gateway provided by the Hass.io add-on {addon}?", "title": "deCONZ Zigbee gateway via Hass.io add-on" }, - "init": { - "title": "Define deCONZ gateway" - }, "link": { "description": "Unlock your deCONZ gateway to register with Home Assistant.\n\n1. Go to deCONZ Settings -> Gateway -> Advanced\n2. Press \"Authenticate app\" button", "title": "Link with deCONZ" }, - "manual_confirm": { - "data": { - "host": "Host", - "port": "Port" - } - }, "manual_input": { "data": { "host": "Host", "port": "Port" - }, - "title": "Configure deCONZ gateway" + } }, "user": { "data": { "host": "Select discovered deCONZ gateway" - }, - "title": "Select deCONZ gateway" + } } } }, diff --git a/homeassistant/components/deconz/translations/es-419.json b/homeassistant/components/deconz/translations/es-419.json index 208616b7ebe..79a43c76b50 100644 --- a/homeassistant/components/deconz/translations/es-419.json +++ b/homeassistant/components/deconz/translations/es-419.json @@ -17,31 +17,20 @@ "description": "\u00bfDesea configurar Home Assistant para conectarse a la puerta de enlace deCONZ proporcionada por el complemento hass.io {addon}?", "title": "deCONZ Zigbee gateway a trav\u00e9s del complemento Hass.io" }, - "init": { - "title": "Definir el gateway deCONZ" - }, "link": { "description": "Desbloquee su puerta de enlace deCONZ para registrarse con Home Assistant. \n\n 1. Vaya a Configuraci\u00f3n deCONZ - > Gateway - > Avanzado \n 2. Presione el bot\u00f3n \"Autenticar aplicaci\u00f3n\"", "title": "Enlazar con deCONZ" }, - "manual_confirm": { - "data": { - "host": "Host", - "port": "Puerto" - } - }, "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" + } } } }, diff --git a/homeassistant/components/deconz/translations/es.json b/homeassistant/components/deconz/translations/es.json index 3299ecbdc55..5a4a3f29258 100644 --- a/homeassistant/components/deconz/translations/es.json +++ b/homeassistant/components/deconz/translations/es.json @@ -17,31 +17,20 @@ "description": "\u00bfQuieres configurar Home Assistant para que se conecte al gateway de deCONZ proporcionado por el add-on {addon} de hass.io?", "title": "Add-on deCONZ Zigbee v\u00eda Hass.io" }, - "init": { - "title": "Definir pasarela deCONZ" - }, "link": { "description": "Desbloquea tu gateway de deCONZ para registrarte con Home Assistant.\n\n1. Dir\u00edgete a deCONZ Settings -> Gateway -> Advanced\n2. Pulsa el bot\u00f3n \"Authenticate app\"", "title": "Enlazar con deCONZ" }, - "manual_confirm": { - "data": { - "host": "Host", - "port": "Puerto" - } - }, "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" + } } } }, diff --git a/homeassistant/components/deconz/translations/fi.json b/homeassistant/components/deconz/translations/fi.json index 2a3882a0957..f1829e7bfee 100644 --- a/homeassistant/components/deconz/translations/fi.json +++ b/homeassistant/components/deconz/translations/fi.json @@ -8,17 +8,8 @@ "no_key": "API-avainta ei voitu saada" }, "step": { - "init": { - "title": "M\u00e4\u00e4rit\u00e4 deCONZ-yhdysk\u00e4yt\u00e4v\u00e4" - }, "link": { "title": "Linkit\u00e4 deCONZiin" - }, - "manual_confirm": { - "data": { - "host": "Palvelin", - "port": "Portti" - } } } } diff --git a/homeassistant/components/deconz/translations/fr.json b/homeassistant/components/deconz/translations/fr.json index 9a1c4120149..33ca4889894 100644 --- a/homeassistant/components/deconz/translations/fr.json +++ b/homeassistant/components/deconz/translations/fr.json @@ -17,25 +17,15 @@ "description": "Voulez-vous configurer Home Assistant pour qu'il se connecte \u00e0 la passerelle deCONZ fournie par l'add-on hass.io {addon} ?", "title": "Passerelle deCONZ Zigbee via l'add-on Hass.io" }, - "init": { - "title": "Initialiser la passerelle deCONZ" - }, "link": { "description": "D\u00e9verrouillez votre passerelle deCONZ pour vous enregistrer avec Home Assistant. \n\n 1. Acc\u00e9dez aux param\u00e8tres avanc\u00e9s du syst\u00e8me deCONZ \n 2. Cliquez sur \"D\u00e9verrouiller la passerelle\"", "title": "Lien vers deCONZ" }, - "manual_confirm": { - "data": { - "host": "H\u00f4te", - "port": "Port" - } - }, "manual_input": { "data": { "host": "H\u00f4te", "port": "Port" - }, - "title": "Configurer la passerelle deCONZ" + } } } }, diff --git a/homeassistant/components/deconz/translations/he.json b/homeassistant/components/deconz/translations/he.json index 3a6dff48933..2011ebdde0d 100644 --- a/homeassistant/components/deconz/translations/he.json +++ b/homeassistant/components/deconz/translations/he.json @@ -9,18 +9,9 @@ "no_key": "\u05dc\u05d0 \u05e0\u05d9\u05ea\u05df \u05d4\u05d9\u05d4 \u05dc\u05e7\u05d1\u05dc \u05de\u05e4\u05ea\u05d7 API" }, "step": { - "init": { - "title": "\u05d4\u05d2\u05d3\u05e8 \u05de\u05d2\u05e9\u05e8 deCONZ Zigbee" - }, "link": { "description": "\u05d1\u05d8\u05dc \u05d0\u05ea \u05e0\u05e2\u05d9\u05dc\u05ea \u05d4\u05de\u05e9\u05e8 deCONZ \u05e9\u05dc\u05da \u05db\u05d3\u05d9 \u05dc\u05d4\u05ea\u05d7\u05d1\u05e8 \u05e2\u05dd Home Assistant.\n\n 1. \u05e2\u05d1\u05d5\u05e8 \u05d0\u05dc \u05d4\u05d2\u05d3\u05e8\u05d5\u05ea \u05de\u05e2\u05e8\u05db\u05ea deCONZ \n .2 \u05dc\u05d7\u05e5 \u05e2\u05dc \"Unlock Gateway\"", "title": "\u05e7\u05e9\u05e8 \u05e2\u05dd deCONZ" - }, - "manual_confirm": { - "data": { - "host": "\u05de\u05d0\u05e8\u05d7", - "port": "\u05e4\u05d5\u05e8\u05d8 (\u05d1\u05e8\u05d9\u05e8\u05ea \u05de\u05d7\u05d3\u05dc: '80')" - } } } } diff --git a/homeassistant/components/deconz/translations/hu.json b/homeassistant/components/deconz/translations/hu.json index a6a80705978..bc07dd2cf97 100644 --- a/homeassistant/components/deconz/translations/hu.json +++ b/homeassistant/components/deconz/translations/hu.json @@ -16,19 +16,10 @@ "hassio_confirm": { "title": "deCONZ Zigbee \u00e1tj\u00e1r\u00f3 a Hass.io kieg\u00e9sz\u00edt\u0151 seg\u00edts\u00e9g\u00e9vel" }, - "init": { - "title": "deCONZ \u00e1tj\u00e1r\u00f3 megad\u00e1sa" - }, "link": { "description": "Oldja fel a deCONZ \u00e1tj\u00e1r\u00f3t a Home Assistant-ban val\u00f3 regisztr\u00e1l\u00e1shoz.\n\n1. Menjen a deCONZ rendszer be\u00e1ll\u00edt\u00e1sokhoz\n2. Nyomja meg az \"\u00c1tj\u00e1r\u00f3 felold\u00e1sa\" gombot", "title": "Kapcsol\u00f3d\u00e1s a deCONZ-hoz" }, - "manual_confirm": { - "data": { - "host": "Hoszt", - "port": "Port" - } - }, "manual_input": { "data": { "host": "Hoszt", diff --git a/homeassistant/components/deconz/translations/id.json b/homeassistant/components/deconz/translations/id.json index ba8b5d76869..9c57eadb0ca 100644 --- a/homeassistant/components/deconz/translations/id.json +++ b/homeassistant/components/deconz/translations/id.json @@ -9,18 +9,9 @@ "no_key": "Tidak bisa mendapatkan kunci API" }, "step": { - "init": { - "title": "Tentukan deCONZ gateway" - }, "link": { "description": "Buka gerbang deCONZ Anda untuk mendaftar dengan Home Assistant. \n\n 1. Pergi ke pengaturan sistem deCONZ \n 2. Tekan tombol \"Buka Kunci Gateway\"", "title": "Tautan dengan deCONZ" - }, - "manual_confirm": { - "data": { - "host": "Host", - "port": "Port (nilai default: '80')" - } } } } diff --git a/homeassistant/components/deconz/translations/it.json b/homeassistant/components/deconz/translations/it.json index 55f20056020..26a1a14e32a 100644 --- a/homeassistant/components/deconz/translations/it.json +++ b/homeassistant/components/deconz/translations/it.json @@ -17,31 +17,20 @@ "description": "Vuoi configurare Home Assistant per connettersi al gateway deCONZ fornito dal componente aggiuntivo di Hass.io: {addon}?", "title": "Gateway Pigmee deCONZ tramite il componente aggiuntivo di Hass.io" }, - "init": { - "title": "Definisci il gateway deCONZ" - }, "link": { "description": "Sblocca il tuo gateway deCONZ per registrarti con Home Assistant.\n\n1. Vai a Impostazioni deCONZ -> Gateway -> Avanzate\n2. Premere il pulsante \"Autentica app\"", "title": "Collega con deCONZ" }, - "manual_confirm": { - "data": { - "host": "Host", - "port": "Porta" - } - }, "manual_input": { "data": { "host": "Host", "port": "Porta" - }, - "title": "Configurare il gateway deCONZ" + } }, "user": { "data": { "host": "Selezionare il gateway deCONZ rilevato" - }, - "title": "Selezionare il gateway deCONZ" + } } } }, diff --git a/homeassistant/components/deconz/translations/ja.json b/homeassistant/components/deconz/translations/ja.json index a1d40f49d34..240e04262e4 100644 --- a/homeassistant/components/deconz/translations/ja.json +++ b/homeassistant/components/deconz/translations/ja.json @@ -2,14 +2,6 @@ "config": { "error": { "no_key": "API\u30ad\u30fc\u3092\u53d6\u5f97\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f" - }, - "step": { - "manual_confirm": { - "data": { - "host": "\u30db\u30b9\u30c8", - "port": "\u30dd\u30fc\u30c8\uff08\u30c7\u30d5\u30a9\u30eb\u30c8\u5024\uff1a'80'\uff09" - } - } } } } \ No newline at end of file diff --git a/homeassistant/components/deconz/translations/ko.json b/homeassistant/components/deconz/translations/ko.json index 92e39523e34..74905c7dacc 100644 --- a/homeassistant/components/deconz/translations/ko.json +++ b/homeassistant/components/deconz/translations/ko.json @@ -17,31 +17,20 @@ "description": "Hass.io {addon} \uc560\ub4dc\uc628\uc5d0\uc11c \uc81c\uacf5\ub41c deCONZ \uac8c\uc774\ud2b8\uc6e8\uc774\uc5d0 \uc5f0\uacb0\ud558\ub3c4\ub85d Home Assistant \ub97c \uad6c\uc131\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", "title": "Hass.io \uc560\ub4dc\uc628\uc758 deCONZ Zigbee \uac8c\uc774\ud2b8\uc6e8\uc774" }, - "init": { - "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 \uc5f0\uacb0\ud558\uae30" }, - "manual_confirm": { - "data": { - "host": "\ud638\uc2a4\ud2b8", - "port": "\ud3ec\ud2b8" - } - }, "manual_input": { "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/deconz/translations/lb.json b/homeassistant/components/deconz/translations/lb.json index c6f2dfbf189..6e80c2393ac 100644 --- a/homeassistant/components/deconz/translations/lb.json +++ b/homeassistant/components/deconz/translations/lb.json @@ -17,31 +17,20 @@ "description": "W\u00ebllt dir Home Assistant konfigur\u00e9iere fir sech mat der deCONZ gateway ze verbannen d\u00e9i vum hass.io add-on {addon} bereet gestallt g\u00ebtt?", "title": "deCONZ Zigbee gateway via Hass.io add-on" }, - "init": { - "title": "deCONZ gateway d\u00e9fin\u00e9ieren" - }, "link": { "description": "Entsperrt \u00e4r deCONZ gateway fir se mat Home Assistant ze registr\u00e9ieren.\n\n1. Gidd op deCONZ System Astellungen\n2. Dr\u00e9ckt \"Unlock\" Gateway Kn\u00e4ppchen", "title": "Link mat deCONZ" }, - "manual_confirm": { - "data": { - "host": "Host", - "port": "Port" - } - }, "manual_input": { "data": { "host": "Apparat", "port": "Port" - }, - "title": "deCONZ Gateway ariichten" + } }, "user": { "data": { "host": "Entdeckte deCONZ Gateway auswielen" - }, - "title": "deCONZ Gateway auswielen" + } } } }, diff --git a/homeassistant/components/deconz/translations/nl.json b/homeassistant/components/deconz/translations/nl.json index 9d932c52123..6c64e50caca 100644 --- a/homeassistant/components/deconz/translations/nl.json +++ b/homeassistant/components/deconz/translations/nl.json @@ -17,31 +17,20 @@ "description": "Wilt u de Home Assistant configureren om verbinding te maken met de deCONZ gateway van de hass.io add-on {addon}?", "title": "deCONZ Zigbee Gateway via Hass.io add-on" }, - "init": { - "title": "Definieer deCONZ gateway" - }, "link": { "description": "Ontgrendel je deCONZ gateway om te registreren met Home Assistant.\n\n1. Ga naar deCONZ systeeminstellingen (Instellingen -> Gateway -> Geavanceerd)\n2. Druk op de knop \"Gateway ontgrendelen\"", "title": "Koppel met deCONZ" }, - "manual_confirm": { - "data": { - "host": "Host", - "port": "Poort" - } - }, "manual_input": { "data": { "host": "Host", "port": "Poort" - }, - "title": "Configureer deCONZ gateway" + } }, "user": { "data": { "host": "Selecteer gevonden deCONZ gateway" - }, - "title": "Selecteer DeCONZ gateway" + } } } }, diff --git a/homeassistant/components/deconz/translations/nn.json b/homeassistant/components/deconz/translations/nn.json index d6d73478a0b..9b962ae0577 100644 --- a/homeassistant/components/deconz/translations/nn.json +++ b/homeassistant/components/deconz/translations/nn.json @@ -9,18 +9,9 @@ "no_key": "Kunne ikkje f\u00e5 ein API-n\u00f8kkel" }, "step": { - "init": { - "title": "Definer deCONZ-gateway" - }, "link": { "description": "L\u00e5s opp deCONZ-gatewayen din for \u00e5 registrere den med Home Assistant.\n\n1. G\u00e5 til systeminnstillingane til deCONZ\n2. Trykk p\u00e5 \"L\u00e5s opp gateway\"-knappen", "title": "Link med deCONZ" - }, - "manual_confirm": { - "data": { - "host": "Vert", - "port": "Port (standardverdi: '80')" - } } } } diff --git a/homeassistant/components/deconz/translations/no.json b/homeassistant/components/deconz/translations/no.json index 19c6358baf5..f25ad1d5886 100644 --- a/homeassistant/components/deconz/translations/no.json +++ b/homeassistant/components/deconz/translations/no.json @@ -17,31 +17,20 @@ "description": "Vil du konfigurere Home Assistant til \u00e5 koble seg til deCONZ-gateway levert av Hass.io-tillegget {addon} ?", "title": "deCONZ Zigbee gateway via Hass.io tillegg" }, - "init": { - "title": "Definer deCONZ-gatewayen" - }, "link": { "description": "L\u00e5s opp deCONZ-gatewayen din for \u00e5 registrere deg med Home Assistant. \n\n 1. G\u00e5 til deCONZ-systeminnstillinger -> Gateway -> Avansert \n 2. Trykk p\u00e5 \"Autentiser app\" knappen", "title": "Koble til deCONZ" }, - "manual_confirm": { - "data": { - "host": "Vert", - "port": "" - } - }, "manual_input": { "data": { "host": "Vert", "port": "Port" - }, - "title": "Konfigurer deCONZ gateway" + } }, "user": { "data": { "host": "Velg oppdaget deCONZ gateway" - }, - "title": "Velg deCONZ gateway" + } } } }, diff --git a/homeassistant/components/deconz/translations/pl.json b/homeassistant/components/deconz/translations/pl.json index 2edcb2a2611..72434f4700f 100644 --- a/homeassistant/components/deconz/translations/pl.json +++ b/homeassistant/components/deconz/translations/pl.json @@ -17,31 +17,20 @@ "description": "Czy chcesz skonfigurowa\u0107 Home Assistant, aby po\u0142\u0105czy\u0142 si\u0119 z bramk\u0105 deCONZ dostarczon\u0105 przez dodatek Hass.io {addon}?", "title": "Bramka deCONZ Zigbee przez dodatek Hass.io" }, - "init": { - "title": "Zdefiniuj bramk\u0119 deCONZ" - }, "link": { "description": "Odblokuj bramk\u0119 deCONZ, aby zarejestrowa\u0107 j\u0105 w Home Assistant. \n\n 1. Przejd\u017a do ustawienia deCONZ > bramka > Zaawansowane\n 2. Naci\u015bnij przycisk \"Uwierzytelnij aplikacj\u0119\"", "title": "Po\u0142\u0105czenie z deCONZ" }, - "manual_confirm": { - "data": { - "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" + } } } }, diff --git a/homeassistant/components/deconz/translations/pt-BR.json b/homeassistant/components/deconz/translations/pt-BR.json index c43ecdb0303..e8f1f2e39e2 100644 --- a/homeassistant/components/deconz/translations/pt-BR.json +++ b/homeassistant/components/deconz/translations/pt-BR.json @@ -16,18 +16,9 @@ "description": "Deseja configurar o Home Assistant para conectar-se ao gateway deCONZ fornecido pelo add-on hass.io {addon} ?", "title": "Gateway deCONZ Zigbee via add-on Hass.io" }, - "init": { - "title": "Defina o gateway deCONZ" - }, "link": { "description": "Desbloqueie o seu gateway deCONZ para se registar no Home Assistant. \n\n 1. V\u00e1 para as configura\u00e7\u00f5es do sistema deCONZ \n 2. Pressione o bot\u00e3o \"Desbloquear Gateway\"", "title": "Linkar com deCONZ" - }, - "manual_confirm": { - "data": { - "host": "Hospedeiro", - "port": "Porta (valor padr\u00e3o: '80')" - } } } }, diff --git a/homeassistant/components/deconz/translations/pt.json b/homeassistant/components/deconz/translations/pt.json index b385af86ce5..9cb21e03568 100644 --- a/homeassistant/components/deconz/translations/pt.json +++ b/homeassistant/components/deconz/translations/pt.json @@ -9,19 +9,10 @@ "no_key": "N\u00e3o foi poss\u00edvel obter uma chave de API" }, "step": { - "init": { - "title": "Defina o gateway deCONZ" - }, "link": { "description": "Desbloqueie o seu gateway deCONZ para se registar no Home Assistant. \n\n 1. V\u00e1 para as configura\u00e7\u00f5es do sistema deCONZ \n 2. Pressione o bot\u00e3o \"Desbloquear Gateway\"", "title": "Liga\u00e7\u00e3o com deCONZ" }, - "manual_confirm": { - "data": { - "host": "Servidor", - "port": "Porta" - } - }, "manual_input": { "data": { "host": "Servidor", diff --git a/homeassistant/components/deconz/translations/ro.json b/homeassistant/components/deconz/translations/ro.json index a997db44380..1e7a890b8f1 100644 --- a/homeassistant/components/deconz/translations/ro.json +++ b/homeassistant/components/deconz/translations/ro.json @@ -3,11 +3,6 @@ "step": { "link": { "description": "Debloca\u021bi gateway-ul DECONZ pentru a v\u0103 \u00eenregistra la Home Assistant. \n\n 1. Accesa\u021bi Set\u0103rile deCONZ - > Gateway - > Avansat \n 2. Ap\u0103sa\u021bi butonul \u201eAutentifica\u021bi aplica\u021bia\u201d" - }, - "manual_confirm": { - "data": { - "port": "Port" - } } } } diff --git a/homeassistant/components/deconz/translations/ru.json b/homeassistant/components/deconz/translations/ru.json index 6e42969eb29..32bf2001f44 100644 --- a/homeassistant/components/deconz/translations/ru.json +++ b/homeassistant/components/deconz/translations/ru.json @@ -17,31 +17,20 @@ "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a deCONZ (\u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u0435 \u0434\u043b\u044f Hass.io \"{addon}\")?", "title": "Zigbee \u0448\u043b\u044e\u0437 deCONZ (\u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u0435 \u0434\u043b\u044f Hass.io)" }, - "init": { - "title": "deCONZ" - }, "link": { "description": "\u0420\u0430\u0437\u0431\u043b\u043e\u043a\u0438\u0440\u0443\u0439\u0442\u0435 \u0448\u043b\u044e\u0437 deCONZ \u0434\u043b\u044f \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u0432 Home Assistant:\n\n1. \u041f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u043a \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430\u043c \u0441\u0438\u0441\u0442\u0435\u043c\u044b deCONZ -> Gateway -> Advanced.\n2. \u041d\u0430\u0436\u043c\u0438\u0442\u0435 \u043a\u043d\u043e\u043f\u043a\u0443 \u00abAuthenticate app\u00bb.", "title": "\u0421\u0432\u044f\u0437\u044c \u0441 deCONZ" }, - "manual_confirm": { - "data": { - "host": "\u0425\u043e\u0441\u0442", - "port": "\u041f\u043e\u0440\u0442" - } - }, "manual_input": { "data": { "host": "\u0425\u043e\u0441\u0442", "port": "\u041f\u043e\u0440\u0442" - }, - "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0448\u043b\u044e\u0437\u0430 deCONZ" + } }, "user": { "data": { "host": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u043d\u044b\u0439 \u0448\u043b\u044e\u0437 deCONZ" - }, - "title": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0448\u043b\u044e\u0437 deCONZ" + } } } }, diff --git a/homeassistant/components/deconz/translations/sl.json b/homeassistant/components/deconz/translations/sl.json index af23635af80..092d65fb4cb 100644 --- a/homeassistant/components/deconz/translations/sl.json +++ b/homeassistant/components/deconz/translations/sl.json @@ -17,31 +17,20 @@ "description": "Ali \u017eelite konfigurirati Home Assistant za povezavo s prehodom deCONZ, ki ga ponuja dodatek Hass.io {addon} ?", "title": "deCONZ Zigbee prehod preko dodatka Hass.io" }, - "init": { - "title": "Dolo\u010dite deCONZ prehod" - }, "link": { "description": "Odklenite va\u0161 deCONZ gateway za registracijo s Home Assistant-om. \n1. Pojdite v deCONZ sistemske nastavitve\n2. Pritisnite tipko \"odkleni prehod\"", "title": "Povezava z deCONZ" }, - "manual_confirm": { - "data": { - "host": "Gostitelj", - "port": "Vrata" - } - }, "manual_input": { "data": { "host": "Gostitelj", "port": "Vrata" - }, - "title": "Konfigurirajte prehod deCONZ" + } }, "user": { "data": { "host": "Izberite odkrit prehod deCONZ" - }, - "title": "Izberite prehod deCONZ" + } } } }, diff --git a/homeassistant/components/deconz/translations/sv.json b/homeassistant/components/deconz/translations/sv.json index e7e0f5d917f..795a2464ad9 100644 --- a/homeassistant/components/deconz/translations/sv.json +++ b/homeassistant/components/deconz/translations/sv.json @@ -17,19 +17,10 @@ "description": "Vill du konfigurera Home Assistant att ansluta till den deCONZ-gateway som tillhandah\u00e5lls av Hass.io-till\u00e4gget {addon}?", "title": "deCONZ Zigbee gateway via Hass.io till\u00e4gg" }, - "init": { - "title": "Definiera deCONZ-gatewaye" - }, "link": { "description": "L\u00e5s upp din deCONZ-gateway f\u00f6r att registrera dig med Home Assistant. \n\n 1. G\u00e5 till deCONZ-systeminst\u00e4llningarna \n 2. Tryck p\u00e5 \"L\u00e5s upp gateway\"-knappen", "title": "L\u00e4nka med deCONZ" }, - "manual_confirm": { - "data": { - "host": "V\u00e4rd", - "port": "Port (standardv\u00e4rde: '80')" - } - }, "manual_input": { "data": { "host": "V\u00e4rd", diff --git a/homeassistant/components/deconz/translations/vi.json b/homeassistant/components/deconz/translations/vi.json index 95880d43e39..ad4ff457a77 100644 --- a/homeassistant/components/deconz/translations/vi.json +++ b/homeassistant/components/deconz/translations/vi.json @@ -7,13 +7,6 @@ }, "error": { "no_key": "Kh\u00f4ng th\u1ec3 l\u1ea5y kh\u00f3a API" - }, - "step": { - "manual_confirm": { - "data": { - "port": "C\u1ed5ng (gi\u00e1 tr\u1ecb m\u1eb7c \u0111\u1ecbnh: '80')" - } - } } } } \ No newline at end of file diff --git a/homeassistant/components/deconz/translations/zh-Hans.json b/homeassistant/components/deconz/translations/zh-Hans.json index 1152d5c394d..42b8067f647 100644 --- a/homeassistant/components/deconz/translations/zh-Hans.json +++ b/homeassistant/components/deconz/translations/zh-Hans.json @@ -9,18 +9,9 @@ "no_key": "\u65e0\u6cd5\u83b7\u53d6 API \u5bc6\u94a5" }, "step": { - "init": { - "title": "\u5b9a\u4e49 deCONZ \u7f51\u5173" - }, "link": { "description": "\u89e3\u9501\u60a8\u7684 deCONZ \u7f51\u5173\u4ee5\u6ce8\u518c\u5230 Home Assistant\u3002 \n\n 1. \u524d\u5f80 deCONZ \u7cfb\u7edf\u8bbe\u7f6e\n 2. \u70b9\u51fb\u201c\u89e3\u9501\u7f51\u5173\u201d\u6309\u94ae", "title": "\u8fde\u63a5 deCONZ" - }, - "manual_confirm": { - "data": { - "host": "\u4e3b\u673a", - "port": "\u7aef\u53e3\uff08\u9ed8\u8ba4\u503c\uff1a'80'\uff09" - } } } }, diff --git a/homeassistant/components/deconz/translations/zh-Hant.json b/homeassistant/components/deconz/translations/zh-Hant.json index e80524ce23f..41711eb81aa 100644 --- a/homeassistant/components/deconz/translations/zh-Hant.json +++ b/homeassistant/components/deconz/translations/zh-Hant.json @@ -17,31 +17,20 @@ "description": "\u662f\u5426\u8981\u8a2d\u5b9a Home Assistant \u4ee5\u9023\u7dda\u81f3 Hass.io \u9644\u52a0\u6574\u5408 {addon} \u4e4b deCONZ \u9598\u9053\u5668\uff1f", "title": "\u900f\u904e Hass.io \u9644\u52a0\u7d44\u4ef6 deCONZ Zigbee \u9598\u9053\u5668" }, - "init": { - "title": "\u5b9a\u7fa9 deCONZ \u9598\u9053\u5668" - }, "link": { "description": "\u89e3\u9664 deCONZ \u9598\u9053\u5668\u9396\u5b9a\uff0c\u4ee5\u65bc Home Assistant \u9032\u884c\u8a3b\u518a\u3002\n\n1. \u9032\u5165 deCONZ \u7cfb\u7d71\u8a2d\u5b9a -> \u9598\u9053\u5668 -> \u9032\u968e\u8a2d\u5b9a\n2. \u6309\u4e0b\u300c\u8a8d\u8b49\u7a0b\u5f0f\uff08Authenticate app\uff09\u300d\u6309\u9215", "title": "\u9023\u7d50\u81f3 deCONZ" }, - "manual_confirm": { - "data": { - "host": "\u4e3b\u6a5f\u7aef", - "port": "\u901a\u8a0a\u57e0" - } - }, "manual_input": { "data": { "host": "\u4e3b\u6a5f\u7aef", "port": "\u901a\u8a0a\u57e0" - }, - "title": "\u8a2d\u5b9a deCONZ \u9598\u9053\u5668" + } }, "user": { "data": { "host": "\u9078\u64c7\u6240\u63a2\u7d22\u5230\u7684 deCONZ \u9598\u9053\u5668" - }, - "title": "\u9078\u64c7 deCONZ \u9598\u9053\u5668" + } } } }, diff --git a/homeassistant/components/devolo_home_control/translations/ca.json b/homeassistant/components/devolo_home_control/translations/ca.json index 920e9a0780e..af0b4eb105a 100644 --- a/homeassistant/components/devolo_home_control/translations/ca.json +++ b/homeassistant/components/devolo_home_control/translations/ca.json @@ -9,14 +9,11 @@ "step": { "user": { "data": { - "Home_Control_URL": "URL de Home Control", - "Mydevolo_URL": "URL de mydevolo", "home_control_url": "URL de Home Control", "mydevolo_url": "URL de mydevolo", "password": "Contrasenya", "username": "Correu electr\u00f2nic / ID de devolo" }, - "description": "Configura el teu Home Control de devolo.", "title": "Home Control devolo" } } diff --git a/homeassistant/components/devolo_home_control/translations/de.json b/homeassistant/components/devolo_home_control/translations/de.json index b8a464d6222..456be47b3d8 100644 --- a/homeassistant/components/devolo_home_control/translations/de.json +++ b/homeassistant/components/devolo_home_control/translations/de.json @@ -9,14 +9,11 @@ "step": { "user": { "data": { - "Home_Control_URL": "Home Control URL", - "Mydevolo_URL": "mydevolo URL", "home_control_url": "Home Control URL", "mydevolo_url": "mydevolo URL", "password": "Passwort", "username": "E-Mail-Adresse / devolo ID" }, - "description": "Richten Sie Ihr devolo Home Control ein.", "title": "devolo Home Control" } } diff --git a/homeassistant/components/devolo_home_control/translations/en.json b/homeassistant/components/devolo_home_control/translations/en.json index 3f1ab4d45e3..ae888d37f4a 100644 --- a/homeassistant/components/devolo_home_control/translations/en.json +++ b/homeassistant/components/devolo_home_control/translations/en.json @@ -9,14 +9,11 @@ "step": { "user": { "data": { - "Home_Control_URL": "Home Control URL", - "Mydevolo_URL": "mydevolo URL", "home_control_url": "Home Control URL", "mydevolo_url": "mydevolo URL", "password": "Password", "username": "E-Mail-Address / devolo ID" }, - "description": "Set up your devolo Home Control.", "title": "devolo Home Control" } } diff --git a/homeassistant/components/devolo_home_control/translations/es.json b/homeassistant/components/devolo_home_control/translations/es.json index 108b36b187e..9eb7f04f923 100644 --- a/homeassistant/components/devolo_home_control/translations/es.json +++ b/homeassistant/components/devolo_home_control/translations/es.json @@ -9,14 +9,11 @@ "step": { "user": { "data": { - "Home_Control_URL": "URL de Home Control", - "Mydevolo_URL": "URL de mydevolo", "home_control_url": "URL de Home Control", "mydevolo_url": "URL de mydevolo", "password": "Contrase\u00f1a", "username": "Direcci\u00f3n de correo electr\u00f3nico / ID de devolo" }, - "description": "Configurar el devolo Home Control.", "title": "devolo Home Control" } } diff --git a/homeassistant/components/devolo_home_control/translations/fi.json b/homeassistant/components/devolo_home_control/translations/fi.json index c2957e7c2b5..51dc72c408a 100644 --- a/homeassistant/components/devolo_home_control/translations/fi.json +++ b/homeassistant/components/devolo_home_control/translations/fi.json @@ -3,7 +3,6 @@ "step": { "user": { "data": { - "Mydevolo_URL": "mydevolo URL", "home_control_url": "Home Control URL", "mydevolo_url": "mydevolo URL", "password": "Salasana" diff --git a/homeassistant/components/devolo_home_control/translations/fr.json b/homeassistant/components/devolo_home_control/translations/fr.json index 34d4c7524e4..7ced4c4840d 100644 --- a/homeassistant/components/devolo_home_control/translations/fr.json +++ b/homeassistant/components/devolo_home_control/translations/fr.json @@ -10,7 +10,6 @@ "password": "Mot de passe", "username": "Adresse e-mail / devolo ID" }, - "description": "Installez votre devolo Home Control.", "title": "devolo Home Control" } } diff --git a/homeassistant/components/devolo_home_control/translations/it.json b/homeassistant/components/devolo_home_control/translations/it.json index 14997ea7b7a..89bc030a7c8 100644 --- a/homeassistant/components/devolo_home_control/translations/it.json +++ b/homeassistant/components/devolo_home_control/translations/it.json @@ -9,14 +9,11 @@ "step": { "user": { "data": { - "Home_Control_URL": "URL di Home Control", - "Mydevolo_URL": "URL di mydevolo", "home_control_url": "URL di Home Control", "mydevolo_url": "URL di mydevolo", "password": "Password", "username": "Indirizzo e-mail / devolo ID" }, - "description": "Configura devolo Home Control.", "title": "devolo Home Control" } } diff --git a/homeassistant/components/devolo_home_control/translations/ko.json b/homeassistant/components/devolo_home_control/translations/ko.json index cf6ea82353a..6cb8d05f17e 100644 --- a/homeassistant/components/devolo_home_control/translations/ko.json +++ b/homeassistant/components/devolo_home_control/translations/ko.json @@ -9,14 +9,11 @@ "step": { "user": { "data": { - "Home_Control_URL": "Home Control URL", - "Mydevolo_URL": "mydevolo URL", "home_control_url": "Home Control URL", "mydevolo_url": "mydevolo URL", "password": "\ube44\ubc00\ubc88\ud638", "username": "\uc774\uba54\uc77c \uc8fc\uc18c / devolo ID" }, - "description": "devolo Home Control \uc744 \uc124\uc815\ud574\uc8fc\uc138\uc694.", "title": "devolo Home Control" } } diff --git a/homeassistant/components/devolo_home_control/translations/lb.json b/homeassistant/components/devolo_home_control/translations/lb.json index 0f95319fb2c..1976e953f8a 100644 --- a/homeassistant/components/devolo_home_control/translations/lb.json +++ b/homeassistant/components/devolo_home_control/translations/lb.json @@ -9,14 +9,11 @@ "step": { "user": { "data": { - "Home_Control_URL": "Home Control URL", - "Mydevolo_URL": "mydevolo URL", "home_control_url": "Home Control URL", "mydevolo_url": "mydevolo URL", "password": "Passwuert", "username": "Benotzernumm" }, - "description": "devolo Home Control ariichten.", "title": "devolo Home Control" } } diff --git a/homeassistant/components/devolo_home_control/translations/no.json b/homeassistant/components/devolo_home_control/translations/no.json index 950c7b6736f..5bf7990ff59 100644 --- a/homeassistant/components/devolo_home_control/translations/no.json +++ b/homeassistant/components/devolo_home_control/translations/no.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Denne Home Control Central er allerede konfigurert." + "already_configured": "Denne hjemmekontrollsentralenheten er allerede i bruk." }, "error": { "invalid_credentials": "Ugyldig brukernavn og/eller passord" @@ -9,14 +9,11 @@ "step": { "user": { "data": { - "Home_Control_URL": "", - "Mydevolo_URL": "", "home_control_url": "Home Control URL", "mydevolo_url": "mydevolo URL", "password": "Passord", "username": "Brukernavn" }, - "description": "Sett opp din devolo Home Control.", "title": "" } } diff --git a/homeassistant/components/devolo_home_control/translations/pl.json b/homeassistant/components/devolo_home_control/translations/pl.json index 8386f5686e0..0d1c2338e33 100644 --- a/homeassistant/components/devolo_home_control/translations/pl.json +++ b/homeassistant/components/devolo_home_control/translations/pl.json @@ -9,14 +9,11 @@ "step": { "user": { "data": { - "Home_Control_URL": "URL Home Control", - "Mydevolo_URL": "URL mydevolo", "home_control_url": "URL Home Control", "mydevolo_url": "URL mydevolo", "password": "Has\u0142o", "username": "Nazwa u\u017cytkownika" }, - "description": "Konfiguracja devolo Home Control", "title": "devolo Home Control" } } diff --git a/homeassistant/components/devolo_home_control/translations/ru.json b/homeassistant/components/devolo_home_control/translations/ru.json index 12760361ff8..b65a45d7227 100644 --- a/homeassistant/components/devolo_home_control/translations/ru.json +++ b/homeassistant/components/devolo_home_control/translations/ru.json @@ -9,14 +9,11 @@ "step": { "user": { "data": { - "Home_Control_URL": "Home Control URL", - "Mydevolo_URL": "mydevolo URL", "home_control_url": "Home Control URL", "mydevolo_url": "mydevolo URL", "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 / devolo ID" }, - "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 devolo Home Control.", "title": "devolo Home Control" } } diff --git a/homeassistant/components/devolo_home_control/translations/sv.json b/homeassistant/components/devolo_home_control/translations/sv.json index a932043a52f..7ac5f75d405 100644 --- a/homeassistant/components/devolo_home_control/translations/sv.json +++ b/homeassistant/components/devolo_home_control/translations/sv.json @@ -6,14 +6,11 @@ "step": { "user": { "data": { - "Home_Control_URL": "Home Control URL", - "Mydevolo_URL": "mydevolo URL", "home_control_url": "Home Control URL", "mydevolo_url": "mydevolo URL", "password": "L\u00f6senord", "username": "E-postadress / devolo-ID" }, - "description": "St\u00e4ll in din devolo Home Control.", "title": "devolo Home Control" } } diff --git a/homeassistant/components/devolo_home_control/translations/zh-Hant.json b/homeassistant/components/devolo_home_control/translations/zh-Hant.json index 581420cba08..ef2407fe5bd 100644 --- a/homeassistant/components/devolo_home_control/translations/zh-Hant.json +++ b/homeassistant/components/devolo_home_control/translations/zh-Hant.json @@ -9,14 +9,11 @@ "step": { "user": { "data": { - "Home_Control_URL": "Home Control URL", - "Mydevolo_URL": "mydevolo URL", "home_control_url": "Home Control URL", "mydevolo_url": "mydevolo URL", "password": "\u5bc6\u78bc", "username": "E-Mail \u4f4d\u5740 / devolo ID" }, - "description": "\u8a2d\u5b9a devolo Home Control\u3002", "title": "devolo Home Control" } } diff --git a/homeassistant/components/directv/translations/ca.json b/homeassistant/components/directv/translations/ca.json index d906fa2b6fc..be96883d2d0 100644 --- a/homeassistant/components/directv/translations/ca.json +++ b/homeassistant/components/directv/translations/ca.json @@ -10,14 +10,12 @@ "flow_title": "DirecTV: {name}", "step": { "ssdp_confirm": { - "description": "Vols configurar {name}?", - "title": "Connexi\u00f3 amb el receptor DirecTV" + "description": "Vols configurar {name}?" }, "user": { "data": { "host": "Amfitri\u00f3" - }, - "title": "Connexi\u00f3 amb el receptor DirecTV" + } } } } diff --git a/homeassistant/components/directv/translations/de.json b/homeassistant/components/directv/translations/de.json index 0b3fa8f29e8..57d3c41b9d1 100644 --- a/homeassistant/components/directv/translations/de.json +++ b/homeassistant/components/directv/translations/de.json @@ -14,14 +14,12 @@ "one": "eins", "other": "andere" }, - "description": "M\u00f6chten Sie {name} einrichten?", - "title": "Stellen Sie eine Verbindung zum DirecTV-Empf\u00e4nger her" + "description": "M\u00f6chten Sie {name} einrichten?" }, "user": { "data": { "host": "Host oder IP-Adresse" - }, - "title": "Schlie\u00dfen Sie den DirecTV-Empf\u00e4nger an" + } } } } diff --git a/homeassistant/components/directv/translations/en.json b/homeassistant/components/directv/translations/en.json index 8df2c1aec66..0275d50d8fc 100644 --- a/homeassistant/components/directv/translations/en.json +++ b/homeassistant/components/directv/translations/en.json @@ -10,14 +10,12 @@ "flow_title": "DirecTV: {name}", "step": { "ssdp_confirm": { - "description": "Do you want to set up {name}?", - "title": "Connect to the DirecTV receiver" + "description": "Do you want to set up {name}?" }, "user": { "data": { "host": "Host" - }, - "title": "Connect to the DirecTV receiver" + } } } } diff --git a/homeassistant/components/directv/translations/es-419.json b/homeassistant/components/directv/translations/es-419.json index 6db50cd6b5a..618a1d04765 100644 --- a/homeassistant/components/directv/translations/es-419.json +++ b/homeassistant/components/directv/translations/es-419.json @@ -10,14 +10,12 @@ "flow_title": "DirecTV: {name}", "step": { "ssdp_confirm": { - "description": "\u00bfDesea configurar {name}?", - "title": "Conectarse al receptor DirecTV" + "description": "\u00bfDesea configurar {name}?" }, "user": { "data": { "host": "Host o direcci\u00f3n IP" - }, - "title": "Conectarse al receptor DirecTV" + } } } } diff --git a/homeassistant/components/directv/translations/es.json b/homeassistant/components/directv/translations/es.json index cb47b845de6..e6a0d6d07ea 100644 --- a/homeassistant/components/directv/translations/es.json +++ b/homeassistant/components/directv/translations/es.json @@ -10,14 +10,12 @@ "flow_title": "DirecTV: {name}", "step": { "ssdp_confirm": { - "description": "\u00bfQuieres configurar {name}?", - "title": "Conectar con el receptor DirecTV" + "description": "\u00bfQuieres configurar {name}?" }, "user": { "data": { "host": "Host o direcci\u00f3n IP" - }, - "title": "Conectar con el receptor DirecTV" + } } } } diff --git a/homeassistant/components/directv/translations/fr.json b/homeassistant/components/directv/translations/fr.json index 0ff3dd4edb4..b4e19f7150f 100644 --- a/homeassistant/components/directv/translations/fr.json +++ b/homeassistant/components/directv/translations/fr.json @@ -14,14 +14,12 @@ "one": "Vide", "other": "Vide" }, - "description": "Voulez-vous configurer {name} ?", - "title": "Connectez-vous au r\u00e9cepteur DirecTV" + "description": "Voulez-vous configurer {name} ?" }, "user": { "data": { "host": "H\u00f4te ou adresse IP" - }, - "title": "Connectez-vous au r\u00e9cepteur DirecTV" + } } } } diff --git a/homeassistant/components/directv/translations/it.json b/homeassistant/components/directv/translations/it.json index d75f10976d9..cdb2f8b418d 100644 --- a/homeassistant/components/directv/translations/it.json +++ b/homeassistant/components/directv/translations/it.json @@ -14,14 +14,12 @@ "one": "uno", "other": "altri" }, - "description": "Vuoi impostare {name} ?", - "title": "Connettersi al ricevitore DirecTV" + "description": "Vuoi impostare {name} ?" }, "user": { "data": { "host": "Host" - }, - "title": "Collegamento al ricevitore DirecTV" + } } } } diff --git a/homeassistant/components/directv/translations/ko.json b/homeassistant/components/directv/translations/ko.json index 4a1fbd3dbbe..f2526418d08 100644 --- a/homeassistant/components/directv/translations/ko.json +++ b/homeassistant/components/directv/translations/ko.json @@ -10,14 +10,12 @@ "flow_title": "DirecTV: {name}", "step": { "ssdp_confirm": { - "description": "{name} \uc744(\ub97c) \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", - "title": "DirecTV \ub9ac\uc2dc\ubc84\uc5d0 \uc5f0\uacb0\ud558\uae30" + "description": "{name} \uc744(\ub97c) \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" }, "user": { "data": { "host": "\ud638\uc2a4\ud2b8" - }, - "title": "DirecTV \ub9ac\uc2dc\ubc84\uc5d0 \uc5f0\uacb0\ud558\uae30" + } } } } diff --git a/homeassistant/components/directv/translations/lb.json b/homeassistant/components/directv/translations/lb.json index 1fb8b72cde8..cc54cfc9567 100644 --- a/homeassistant/components/directv/translations/lb.json +++ b/homeassistant/components/directv/translations/lb.json @@ -14,14 +14,12 @@ "one": "Een", "other": "Aner" }, - "description": "Soll {name} konfigur\u00e9iert ginn?", - "title": "Mam DirecTV Receiver verbannen" + "description": "Soll {name} konfigur\u00e9iert ginn?" }, "user": { "data": { "host": "Numm oder IP Adresse" - }, - "title": "Mam DirecTV Receiver verbannen" + } } } } diff --git a/homeassistant/components/directv/translations/nl.json b/homeassistant/components/directv/translations/nl.json index b6635311064..26b6e65e811 100644 --- a/homeassistant/components/directv/translations/nl.json +++ b/homeassistant/components/directv/translations/nl.json @@ -10,14 +10,12 @@ "flow_title": "DirecTV: {name}", "step": { "ssdp_confirm": { - "description": "Wilt u {name} instellen?", - "title": "Maak verbinding met de DirecTV-ontvanger" + "description": "Wilt u {name} instellen?" }, "user": { "data": { "host": "Host- of IP-adres" - }, - "title": "Maak verbinding met de DirecTV-ontvanger" + } } } } diff --git a/homeassistant/components/directv/translations/no.json b/homeassistant/components/directv/translations/no.json index 9e0906ea2ac..e0f363d5bd0 100644 --- a/homeassistant/components/directv/translations/no.json +++ b/homeassistant/components/directv/translations/no.json @@ -10,14 +10,12 @@ "flow_title": "", "step": { "ssdp_confirm": { - "description": "Vil du sette opp {name} ?", - "title": "Koble til DirecTV-mottakeren" + "description": "Vil du sette opp {name} ?" }, "user": { "data": { "host": "Vert eller IP-adresse" - }, - "title": "Koble til DirecTV-mottakeren" + } } } } diff --git a/homeassistant/components/directv/translations/pl.json b/homeassistant/components/directv/translations/pl.json index b23274ec71e..bec0198ca70 100644 --- a/homeassistant/components/directv/translations/pl.json +++ b/homeassistant/components/directv/translations/pl.json @@ -16,14 +16,12 @@ "one": "jeden", "other": "inne" }, - "description": "Czy chcesz skonfigurowa\u0107 {name}?", - "title": "Po\u0142\u0105czenie z odbiornikiem DirecTV" + "description": "Czy chcesz skonfigurowa\u0107 {name}?" }, "user": { "data": { "host": "Nazwa hosta lub adres IP" - }, - "title": "Po\u0142\u0105czenie z odbiornikiem DirecTV" + } } } } diff --git a/homeassistant/components/directv/translations/ru.json b/homeassistant/components/directv/translations/ru.json index 5c48aa0be58..e2625644238 100644 --- a/homeassistant/components/directv/translations/ru.json +++ b/homeassistant/components/directv/translations/ru.json @@ -10,14 +10,12 @@ "flow_title": "DirecTV: {name}", "step": { "ssdp_confirm": { - "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c {name}?", - "title": "DirecTV" + "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c {name}?" }, "user": { "data": { "host": "\u0425\u043e\u0441\u0442" - }, - "title": "DirecTV" + } } } } diff --git a/homeassistant/components/directv/translations/sl.json b/homeassistant/components/directv/translations/sl.json index 19387c68db0..04440e6e2c5 100644 --- a/homeassistant/components/directv/translations/sl.json +++ b/homeassistant/components/directv/translations/sl.json @@ -16,14 +16,12 @@ "other": "drugo", "two": "dva" }, - "description": "Ali \u017eelite nastaviti {name} ?", - "title": "Pove\u017eite se s sprejemnikom DirecTV" + "description": "Ali \u017eelite nastaviti {name} ?" }, "user": { "data": { "host": "Gostitelj ali IP naslov" - }, - "title": "Pove\u017eite se s sprejemnikom DirecTV" + } } } } diff --git a/homeassistant/components/directv/translations/zh-Hant.json b/homeassistant/components/directv/translations/zh-Hant.json index 7668adb6c24..9be7ac31e60 100644 --- a/homeassistant/components/directv/translations/zh-Hant.json +++ b/homeassistant/components/directv/translations/zh-Hant.json @@ -10,14 +10,12 @@ "flow_title": "DirecTV\uff1a{name}", "step": { "ssdp_confirm": { - "description": "\u662f\u5426\u8981\u8a2d\u5b9a {name}\uff1f", - "title": "\u9023\u7dda\u81f3 DirecTV \u63a5\u6536\u5668" + "description": "\u662f\u5426\u8981\u8a2d\u5b9a {name}\uff1f" }, "user": { "data": { "host": "\u4e3b\u6a5f\u7aef" - }, - "title": "\u9023\u7dda\u81f3 DirecTV \u63a5\u6536\u5668" + } } } } diff --git a/homeassistant/components/elgato/translations/ca.json b/homeassistant/components/elgato/translations/ca.json index b6b53986d36..1f02dc1f79f 100644 --- a/homeassistant/components/elgato/translations/ca.json +++ b/homeassistant/components/elgato/translations/ca.json @@ -14,8 +14,7 @@ "host": "Amfitri\u00f3", "port": "Port" }, - "description": "Configura l'Elgato Key Light per integrar-lo amb Home Assistant.", - "title": "Enlla\u00e7 amb Elgato Key Light" + "description": "Configura l'Elgato Key Light per integrar-lo amb Home Assistant." }, "zeroconf_confirm": { "description": "Vols afegir l'Elgato Key Light amb n\u00famero de s\u00e8rie `{serial_number}` a Home Assistant?", diff --git a/homeassistant/components/elgato/translations/da.json b/homeassistant/components/elgato/translations/da.json index 99ae908db6b..172b3b9b806 100644 --- a/homeassistant/components/elgato/translations/da.json +++ b/homeassistant/components/elgato/translations/da.json @@ -14,8 +14,7 @@ "host": "V\u00e6rt eller IP-adresse", "port": "Portnummer" }, - "description": "Indstil din Elgato Key Light til at integrere med Home Assistant.", - "title": "Forbind din Elgato Key Light" + "description": "Indstil din Elgato Key Light til at integrere med Home Assistant." }, "zeroconf_confirm": { "description": "Vil du tilf\u00f8je Elgato Key Light med serienummer `{serial_number}` til Home Assistant?", diff --git a/homeassistant/components/elgato/translations/de.json b/homeassistant/components/elgato/translations/de.json index fcebb7aaa05..8387f51e79f 100644 --- a/homeassistant/components/elgato/translations/de.json +++ b/homeassistant/components/elgato/translations/de.json @@ -14,8 +14,7 @@ "host": "Host oder IP-Adresse", "port": "Port-Nummer" }, - "description": "Richten dein Elgato Key Light f\u00fcr die Integration mit Home Assistant ein.", - "title": "Verkn\u00fcpfe dein Elgato Key Light" + "description": "Richten dein Elgato Key Light f\u00fcr die Integration mit Home Assistant ein." }, "zeroconf_confirm": { "description": "M\u00f6chtest du das Elgato Key Light mit der Seriennummer \"{serial_number} \" zu Home Assistant hinzuf\u00fcgen?", diff --git a/homeassistant/components/elgato/translations/en.json b/homeassistant/components/elgato/translations/en.json index d96b67c3210..fea7945ecf4 100644 --- a/homeassistant/components/elgato/translations/en.json +++ b/homeassistant/components/elgato/translations/en.json @@ -14,8 +14,7 @@ "host": "Host", "port": "Port" }, - "description": "Set up your Elgato Key Light to integrate with Home Assistant.", - "title": "Link your Elgato Key Light" + "description": "Set up your Elgato Key Light to integrate with Home Assistant." }, "zeroconf_confirm": { "description": "Do you want to add the Elgato Key Light with serial number `{serial_number}` to Home Assistant?", diff --git a/homeassistant/components/elgato/translations/es-419.json b/homeassistant/components/elgato/translations/es-419.json index 9d12537851d..4482d630bd8 100644 --- a/homeassistant/components/elgato/translations/es-419.json +++ b/homeassistant/components/elgato/translations/es-419.json @@ -14,8 +14,7 @@ "host": "Host o direcci\u00f3n IP", "port": "N\u00famero de puerto" }, - "description": "Configure su Elgato Key Light para integrarse con Home Assistant.", - "title": "Vincule su Elgato Key Light" + "description": "Configure su Elgato Key Light para integrarse con Home Assistant." }, "zeroconf_confirm": { "description": "\u00bfDesea agregar el disposiivo Elgato Key Light con el n\u00famero de serie `{serial_number}` a Home Assistant?", diff --git a/homeassistant/components/elgato/translations/es.json b/homeassistant/components/elgato/translations/es.json index fd450d391fc..46ced57ad2d 100644 --- a/homeassistant/components/elgato/translations/es.json +++ b/homeassistant/components/elgato/translations/es.json @@ -14,8 +14,7 @@ "host": "Host o direcci\u00f3n IP", "port": "N\u00famero de puerto" }, - "description": "Configura tu Elgato Key Light para integrarlo con Home Assistant.", - "title": "Conecte su Elgato Key Light" + "description": "Configura tu Elgato Key Light para integrarlo con Home Assistant." }, "zeroconf_confirm": { "description": "\u00bfDesea a\u00f1adir Elgato Key Light con el n\u00famero de serie `{serial_number}` a Home Assistant?", diff --git a/homeassistant/components/elgato/translations/fr.json b/homeassistant/components/elgato/translations/fr.json index 7eca88dc2bd..0f01c2cc052 100644 --- a/homeassistant/components/elgato/translations/fr.json +++ b/homeassistant/components/elgato/translations/fr.json @@ -14,8 +14,7 @@ "host": "H\u00f4te ou adresse IP", "port": "Port" }, - "description": "Configurez votre Elgato Key Light pour l'int\u00e9grer \u00e0 Home Assistant.", - "title": "Associez votre Elgato Key Light" + "description": "Configurez votre Elgato Key Light pour l'int\u00e9grer \u00e0 Home Assistant." }, "zeroconf_confirm": { "description": "Voulez-vous ajouter l'Elgato Key Light avec le num\u00e9ro de s\u00e9rie `{serial_number}` \u00e0 Home Assistant?", diff --git a/homeassistant/components/elgato/translations/it.json b/homeassistant/components/elgato/translations/it.json index d354c97b8c1..77e6e7e827e 100644 --- a/homeassistant/components/elgato/translations/it.json +++ b/homeassistant/components/elgato/translations/it.json @@ -14,8 +14,7 @@ "host": "Host", "port": "Porta" }, - "description": "Configura Elgato Key Light per l'integrazione con Home Assistant.", - "title": "Collega il tuo Elgato Key Light" + "description": "Configura Elgato Key Light per l'integrazione con Home Assistant." }, "zeroconf_confirm": { "description": "Vuoi aggiungere il dispositivo Elgato Key Light con il numero di serie {serial_number} a Home Assistant?", diff --git a/homeassistant/components/elgato/translations/ko.json b/homeassistant/components/elgato/translations/ko.json index fcb8922aaf1..3e24c487850 100644 --- a/homeassistant/components/elgato/translations/ko.json +++ b/homeassistant/components/elgato/translations/ko.json @@ -14,8 +14,7 @@ "host": "\ud638\uc2a4\ud2b8", "port": "\ud3ec\ud2b8" }, - "description": "Home Assistant \uc5d0 Elgato Key Light \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud569\ub2c8\ub2e4.", - "title": "Elgato Key Light \uc5f0\uacb0\ud558\uae30" + "description": "Home Assistant \uc5d0 Elgato Key Light \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud569\ub2c8\ub2e4." }, "zeroconf_confirm": { "description": "Elgato Key Light \uc2dc\ub9ac\uc5bc \ubc88\ud638 `{serial_number}` \uc744(\ub97c) Home Assistant \uc5d0 \ucd94\uac00\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", diff --git a/homeassistant/components/elgato/translations/lb.json b/homeassistant/components/elgato/translations/lb.json index 7c2e3cf4e9f..02056dda421 100644 --- a/homeassistant/components/elgato/translations/lb.json +++ b/homeassistant/components/elgato/translations/lb.json @@ -14,8 +14,7 @@ "host": "Numm oder IP Adresse", "port": "Port Nummer" }, - "description": "\u00c4ren Elgator Key Light als Integratioun mam Home Assistant ariichten.", - "title": "\u00c4ren Elgato Key Light verbannen" + "description": "\u00c4ren Elgator Key Light als Integratioun mam Home Assistant ariichten." }, "zeroconf_confirm": { "description": "W\u00ebllt dir den Elgato Key Light mat der Seriennummer `{serial_number}` am Home Assistant dob\u00e4isetzen?", diff --git a/homeassistant/components/elgato/translations/nl.json b/homeassistant/components/elgato/translations/nl.json index 7f027b63f8d..2e50f63d56e 100644 --- a/homeassistant/components/elgato/translations/nl.json +++ b/homeassistant/components/elgato/translations/nl.json @@ -14,8 +14,7 @@ "host": "Hostnaam of IP-adres", "port": "Poortnummer" }, - "description": "Stel uw Elgato Key Light in om te integreren met Home Assistant.", - "title": "Koppel uw Elgato Key Light" + "description": "Stel uw Elgato Key Light in om te integreren met Home Assistant." }, "zeroconf_confirm": { "description": "Wilt u de Elgato Key Light met serienummer ` {serial_number} ` toevoegen aan Home Assistant?", diff --git a/homeassistant/components/elgato/translations/no.json b/homeassistant/components/elgato/translations/no.json index 2d60155cbb8..ebd10d6aa0e 100644 --- a/homeassistant/components/elgato/translations/no.json +++ b/homeassistant/components/elgato/translations/no.json @@ -14,8 +14,7 @@ "host": "Vert eller IP-adresse", "port": "Portnummer" }, - "description": "Sett opp Elgato Key Light for \u00e5 integrere med Home Assistant.", - "title": "Linken ditt Elgato Key Light" + "description": "Sett opp Elgato Key Light for \u00e5 integrere med Home Assistant." }, "zeroconf_confirm": { "description": "Vil du legge Elgato Key Light med serienummer ` {serial_number} til Home Assistant?", diff --git a/homeassistant/components/elgato/translations/pl.json b/homeassistant/components/elgato/translations/pl.json index 300ae515230..263c67a67ca 100644 --- a/homeassistant/components/elgato/translations/pl.json +++ b/homeassistant/components/elgato/translations/pl.json @@ -14,8 +14,7 @@ "host": "Nazwa hosta lub adres IP", "port": "Port" }, - "description": "Konfiguracja Elgato Key Light w celu integracji z Home Assistantem.", - "title": "Po\u0142\u0105cz swoje Elgato Key Light" + "description": "Konfiguracja Elgato Key Light w celu integracji z Home Assistantem." }, "zeroconf_confirm": { "description": "Czy chcesz doda\u0107 urz\u0105dzenie Elgato Key Light o numerze seryjnym `{serial_number}` do Home Assistanta?", diff --git a/homeassistant/components/elgato/translations/ru.json b/homeassistant/components/elgato/translations/ru.json index d8bcb43520a..7cd7036b861 100644 --- a/homeassistant/components/elgato/translations/ru.json +++ b/homeassistant/components/elgato/translations/ru.json @@ -14,8 +14,7 @@ "host": "\u0425\u043e\u0441\u0442", "port": "\u041f\u043e\u0440\u0442" }, - "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 Elgato Key Light \u0434\u043b\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0441 Home Assistant.", - "title": "Elgato Key Light" + "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 Elgato Key Light \u0434\u043b\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0441 Home Assistant." }, "zeroconf_confirm": { "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c Elgato Key Light \u0441 \u0441\u0435\u0440\u0438\u0439\u043d\u044b\u043c \u043d\u043e\u043c\u0435\u0440\u043e\u043c `{serial_number}`?", diff --git a/homeassistant/components/elgato/translations/sl.json b/homeassistant/components/elgato/translations/sl.json index eac8bcdb295..16094c361f0 100644 --- a/homeassistant/components/elgato/translations/sl.json +++ b/homeassistant/components/elgato/translations/sl.json @@ -14,8 +14,7 @@ "host": "Gostitelj ali IP naslov", "port": "\u0160tevilka vrat" }, - "description": "Nastavite svojo Elgato Key Light tako, da se bo vklju\u010dila v Home Assistant.", - "title": "Pove\u017eite svojo Elgato Key Light" + "description": "Nastavite svojo Elgato Key Light tako, da se bo vklju\u010dila v Home Assistant." }, "zeroconf_confirm": { "description": "Ali \u017eelite dodati Elgato Key Light s serijsko \u0161tevilko ' {serial_number} ' v Home Assistant-a?", diff --git a/homeassistant/components/elgato/translations/sv.json b/homeassistant/components/elgato/translations/sv.json index f2b3001ae14..b338716a92f 100644 --- a/homeassistant/components/elgato/translations/sv.json +++ b/homeassistant/components/elgato/translations/sv.json @@ -14,8 +14,7 @@ "host": "V\u00e4rd eller IP-adress", "port": "Portnummer" }, - "description": "St\u00e4ll in ditt Elgato Key Light f\u00f6r att integrera med Home Assistant.", - "title": "L\u00e4nk din Elgato Key Light" + "description": "St\u00e4ll in ditt Elgato Key Light f\u00f6r att integrera med Home Assistant." }, "zeroconf_confirm": { "description": "Vill du l\u00e4gga till Elgato Key Light med serienummer `{serial_number}` till Home Assistant?", diff --git a/homeassistant/components/elgato/translations/zh-Hant.json b/homeassistant/components/elgato/translations/zh-Hant.json index 03b71b70d77..9952a3be6a3 100644 --- a/homeassistant/components/elgato/translations/zh-Hant.json +++ b/homeassistant/components/elgato/translations/zh-Hant.json @@ -14,8 +14,7 @@ "host": "\u4e3b\u6a5f\u7aef", "port": "\u901a\u8a0a\u57e0" }, - "description": "\u8a2d\u5b9a Elgato Key \u7167\u660e\u4ee5\u6574\u5408\u81f3 Home Assistant\u3002", - "title": "\u9023\u7d50 Elgato Key \u7167\u660e\u3002" + "description": "\u8a2d\u5b9a Elgato Key \u7167\u660e\u4ee5\u6574\u5408\u81f3 Home Assistant\u3002" }, "zeroconf_confirm": { "description": "\u662f\u5426\u8981\u5c07 Elgato Key \u7167\u660e\u5e8f\u865f `{serial_number}` \u65b0\u589e\u81f3 Home Assistant\uff1f", diff --git a/homeassistant/components/esphome/translations/bg.json b/homeassistant/components/esphome/translations/bg.json index f77d3957ea1..2b96f4264e8 100644 --- a/homeassistant/components/esphome/translations/bg.json +++ b/homeassistant/components/esphome/translations/bg.json @@ -14,8 +14,7 @@ "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u0430" }, - "description": "\u041c\u043e\u043b\u044f, \u0432\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u043f\u0430\u0440\u043e\u043b\u0430\u0442\u0430, \u043a\u043e\u044f\u0442\u043e \u0441\u0442\u0435 \u0437\u0430\u0434\u0430\u043b\u0438 \u0432 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f\u0442\u0430 \u0441\u0438 \u0437\u0430 {name} .", - "title": "\u041f\u0430\u0440\u043e\u043b\u0430" + "description": "\u041c\u043e\u043b\u044f, \u0432\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u043f\u0430\u0440\u043e\u043b\u0430\u0442\u0430, \u043a\u043e\u044f\u0442\u043e \u0441\u0442\u0435 \u0437\u0430\u0434\u0430\u043b\u0438 \u0432 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f\u0442\u0430 \u0441\u0438 \u0437\u0430 {name} ." }, "discovery_confirm": { "description": "\u0418\u0441\u043a\u0430\u0442\u0435 \u043b\u0438 \u0434\u0430 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u0435 ESPHome \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e ` {name} ` \u043a\u044a\u043c Home Assistant?", @@ -26,8 +25,7 @@ "host": "\u0410\u0434\u0440\u0435\u0441", "port": "\u041f\u043e\u0440\u0442" }, - "description": "\u041c\u043e\u043b\u044f, \u0432\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\u0442\u0435 \u0437\u0430 \u0432\u0440\u044a\u0437\u043a\u0430 \u0441 [ESPHome](https://esphomelib.com/).", - "title": "[%key:component::esphome::title%]" + "description": "\u041c\u043e\u043b\u044f, \u0432\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\u0442\u0435 \u0437\u0430 \u0432\u0440\u044a\u0437\u043a\u0430 \u0441 [ESPHome](https://esphomelib.com/)." } } } diff --git a/homeassistant/components/esphome/translations/ca.json b/homeassistant/components/esphome/translations/ca.json index 828ac08c07b..8833749d071 100644 --- a/homeassistant/components/esphome/translations/ca.json +++ b/homeassistant/components/esphome/translations/ca.json @@ -15,8 +15,7 @@ "data": { "password": "Contrasenya" }, - "description": "Introdueix la contrasenya que has posat en la teva configuraci\u00f3 com a {name}.", - "title": "Introdueix la contrasenya" + "description": "Introdueix la contrasenya que has posat en la teva configuraci\u00f3 com a {name}." }, "discovery_confirm": { "description": "Vols afegir el node `{name}` d'ESPHome a Home Assistant?", @@ -27,8 +26,7 @@ "host": "Amfitri\u00f3", "port": "Port" }, - "description": "Introdueix la informaci\u00f3 de connexi\u00f3 del teu node [ESPHome](https://esphomelib.com/).", - "title": "ESPHome" + "description": "Introdueix la informaci\u00f3 de connexi\u00f3 del teu node [ESPHome](https://esphomelib.com/)." } } } diff --git a/homeassistant/components/esphome/translations/cs.json b/homeassistant/components/esphome/translations/cs.json index 36a600befaa..b437d6d79fd 100644 --- a/homeassistant/components/esphome/translations/cs.json +++ b/homeassistant/components/esphome/translations/cs.json @@ -15,8 +15,7 @@ "data": { "password": "Heslo" }, - "description": "Zadejte heslo, kter\u00e9 jste nastavili ve va\u0161\u00ed konfiguraci pro {name} .", - "title": "Zadejte heslo" + "description": "Zadejte heslo, kter\u00e9 jste nastavili ve va\u0161\u00ed konfiguraci pro {name} ." }, "discovery_confirm": { "description": "Chcete do domovsk\u00e9ho asistenta p\u0159idat uzel ESPHome `{name}`?", @@ -27,8 +26,7 @@ "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%]" + "description": "Zadejte pros\u00edm nastaven\u00ed p\u0159ipojen\u00ed va\u0161eho [ESPHome](https://esphomelib.com/) uzlu." } } } diff --git a/homeassistant/components/esphome/translations/da.json b/homeassistant/components/esphome/translations/da.json index c05e2d34f01..db4cdc72fca 100644 --- a/homeassistant/components/esphome/translations/da.json +++ b/homeassistant/components/esphome/translations/da.json @@ -14,8 +14,7 @@ "data": { "password": "Adgangskode" }, - "description": "Indtast venligst den adgangskode du har angivet i din konfiguration for {name}.", - "title": "Indtast adgangskode" + "description": "Indtast venligst den adgangskode du har angivet i din konfiguration for {name}." }, "discovery_confirm": { "description": "Vil du tilf\u00f8je ESPHome-knudepunkt `{name}` til Home Assistant?", @@ -26,8 +25,7 @@ "host": "V\u00e6rt", "port": "Port" }, - "description": "Angiv forbindelsesindstillinger for din [ESPHome](https://esphomelib.com/) node.", - "title": "[%key:component::esphome::title%]" + "description": "Angiv forbindelsesindstillinger for din [ESPHome](https://esphomelib.com/) node." } } } diff --git a/homeassistant/components/esphome/translations/de.json b/homeassistant/components/esphome/translations/de.json index 299660cb348..978ff5e52d9 100644 --- a/homeassistant/components/esphome/translations/de.json +++ b/homeassistant/components/esphome/translations/de.json @@ -15,8 +15,7 @@ "data": { "password": "Passwort" }, - "description": "Bitte gebe das Passwort der ESPHome-Konfiguration f\u00fcr {name} ein:", - "title": "Passwort eingeben" + "description": "Bitte gebe das Passwort der ESPHome-Konfiguration f\u00fcr {name} ein:" }, "discovery_confirm": { "description": "Willst du den ESPHome-Knoten `{name}` zu Home Assistant hinzuf\u00fcgen?", @@ -27,8 +26,7 @@ "host": "Host", "port": "Port" }, - "description": "Bitte gib die Verbindungseinstellungen deines [ESPHome](https://esphomelib.com/)-Knotens ein.", - "title": "ESPHome" + "description": "Bitte gib die Verbindungseinstellungen deines [ESPHome](https://esphomelib.com/)-Knotens ein." } } } diff --git a/homeassistant/components/esphome/translations/en.json b/homeassistant/components/esphome/translations/en.json index 3cc24dea78e..6386e1f7093 100644 --- a/homeassistant/components/esphome/translations/en.json +++ b/homeassistant/components/esphome/translations/en.json @@ -15,8 +15,7 @@ "data": { "password": "Password" }, - "description": "Please enter the password you set in your configuration for {name}.", - "title": "Enter Password" + "description": "Please enter the password you set in your configuration for {name}." }, "discovery_confirm": { "description": "Do you want to add the ESPHome node `{name}` to Home Assistant?", @@ -27,8 +26,7 @@ "host": "Host", "port": "Port" }, - "description": "Please enter connection settings of your [ESPHome](https://esphomelib.com/) node.", - "title": "ESPHome" + "description": "Please enter connection settings of your [ESPHome](https://esphomelib.com/) node." } } } diff --git a/homeassistant/components/esphome/translations/es-419.json b/homeassistant/components/esphome/translations/es-419.json index 2774ff7ea68..c5da73a7fbf 100644 --- a/homeassistant/components/esphome/translations/es-419.json +++ b/homeassistant/components/esphome/translations/es-419.json @@ -15,8 +15,7 @@ "data": { "password": "Contrase\u00f1a" }, - "description": "Por favor ingrese la contrase\u00f1a que estableci\u00f3 en su configuraci\u00f3n para {name} .", - "title": "Escriba la contrase\u00f1a" + "description": "Por favor ingrese la contrase\u00f1a que estableci\u00f3 en su configuraci\u00f3n para {name} ." }, "discovery_confirm": { "description": "\u00bfDesea agregar el nodo ESPHome `{name}` a Home Assistant?", @@ -27,8 +26,7 @@ "host": "Host", "port": "Puerto" }, - "description": "Por favor Ingrese la configuraci\u00f3n de conexi\u00f3n de su nodo [ESPHome] (https://esphomelib.com/).", - "title": "[%key:component::esphome::title%]" + "description": "Por favor Ingrese la configuraci\u00f3n de conexi\u00f3n de su nodo [ESPHome] (https://esphomelib.com/)." } } } diff --git a/homeassistant/components/esphome/translations/es.json b/homeassistant/components/esphome/translations/es.json index 042ac419503..e30825180b4 100644 --- a/homeassistant/components/esphome/translations/es.json +++ b/homeassistant/components/esphome/translations/es.json @@ -15,8 +15,7 @@ "data": { "password": "Contrase\u00f1a" }, - "description": "Escribe la contrase\u00f1a que hayas puesto en la configuraci\u00f3n para {name}.", - "title": "Escribe la contrase\u00f1a" + "description": "Escribe la contrase\u00f1a que hayas puesto en la configuraci\u00f3n para {name}." }, "discovery_confirm": { "description": "\u00bfQuieres a\u00f1adir el nodo `{name}` de ESPHome a Home Assistant?", @@ -27,8 +26,7 @@ "host": "Host", "port": "Puerto" }, - "description": "Introduce la configuraci\u00f3n de la conexi\u00f3n de tu nodo [ESPHome](https://esphomelib.com/).", - "title": "[%key:component::esphome::title%]" + "description": "Introduce la configuraci\u00f3n de la conexi\u00f3n de tu nodo [ESPHome](https://esphomelib.com/)." } } } diff --git a/homeassistant/components/esphome/translations/fi.json b/homeassistant/components/esphome/translations/fi.json index 367df7e1704..8b9525132ec 100644 --- a/homeassistant/components/esphome/translations/fi.json +++ b/homeassistant/components/esphome/translations/fi.json @@ -8,15 +8,11 @@ }, "flow_title": "ESPHome: {name}", "step": { - "authenticate": { - "title": "Sy\u00f6t\u00e4 salasana" - }, "user": { "data": { "host": "Palvelin", "port": "Portti" - }, - "title": "[%key:component::esphome::title%]" + } } } } diff --git a/homeassistant/components/esphome/translations/fr.json b/homeassistant/components/esphome/translations/fr.json index a6620218f0e..d597d57ee49 100644 --- a/homeassistant/components/esphome/translations/fr.json +++ b/homeassistant/components/esphome/translations/fr.json @@ -15,8 +15,7 @@ "data": { "password": "Mot de passe" }, - "description": "Veuillez saisir le mot de passe que vous avez d\u00e9fini dans votre configuration pour {name}", - "title": "Entrer votre mot de passe" + "description": "Veuillez saisir le mot de passe que vous avez d\u00e9fini dans votre configuration pour {name}" }, "discovery_confirm": { "description": "Voulez-vous ajouter le n\u0153ud ESPHome ` {name} ` \u00e0 Home Assistant?", @@ -27,8 +26,7 @@ "host": "H\u00f4te", "port": "Port" }, - "description": "Veuillez saisir les param\u00e8tres de connexion de votre n\u0153ud [ESPHome] (https://esphomelib.com/).", - "title": "[%key:component::esphome::title%]" + "description": "Veuillez saisir les param\u00e8tres de connexion de votre n\u0153ud [ESPHome] (https://esphomelib.com/)." } } } diff --git a/homeassistant/components/esphome/translations/hu.json b/homeassistant/components/esphome/translations/hu.json index a178860d420..19691a94891 100644 --- a/homeassistant/components/esphome/translations/hu.json +++ b/homeassistant/components/esphome/translations/hu.json @@ -14,8 +14,7 @@ "data": { "password": "Jelsz\u00f3" }, - "description": "K\u00e9rlek, add meg a konfigur\u00e1ci\u00f3ban {name} n\u00e9vhez be\u00e1ll\u00edtott jelsz\u00f3t.", - "title": "Add meg a jelsz\u00f3t" + "description": "K\u00e9rlek, add meg a konfigur\u00e1ci\u00f3ban {name} n\u00e9vhez be\u00e1ll\u00edtott jelsz\u00f3t." }, "discovery_confirm": { "description": "Szeretn\u00e9d hozz\u00e1adni a(z) `{name}` ESPHome csom\u00f3pontot a Home Assistant-hoz?", @@ -26,8 +25,7 @@ "host": "Hoszt", "port": "Port" }, - "description": "K\u00e9rlek, add meg az [ESPHome](https://esphomelib.com/) csom\u00f3pontod kapcsol\u00f3d\u00e1si be\u00e1ll\u00edt\u00e1sait.", - "title": "ESPHome" + "description": "K\u00e9rlek, add meg az [ESPHome](https://esphomelib.com/) csom\u00f3pontod kapcsol\u00f3d\u00e1si be\u00e1ll\u00edt\u00e1sait." } } } diff --git a/homeassistant/components/esphome/translations/id.json b/homeassistant/components/esphome/translations/id.json index 9c646def82c..9f6cd012949 100644 --- a/homeassistant/components/esphome/translations/id.json +++ b/homeassistant/components/esphome/translations/id.json @@ -8,11 +8,7 @@ "data": { "password": "Kata kunci" }, - "description": "Silakan masukkan kata kunci yang Anda atur di konfigurasi Anda.", - "title": "Masukkan kata kunci" - }, - "user": { - "title": "[%key:component::esphome::title%]" + "description": "Silakan masukkan kata kunci yang Anda atur di konfigurasi Anda." } } } diff --git a/homeassistant/components/esphome/translations/it.json b/homeassistant/components/esphome/translations/it.json index 60ba2a31890..58f6dda968a 100644 --- a/homeassistant/components/esphome/translations/it.json +++ b/homeassistant/components/esphome/translations/it.json @@ -15,8 +15,7 @@ "data": { "password": "Password" }, - "description": "Inserisci la password per {name} che hai impostato nella tua configurazione.", - "title": "Inserisci la password" + "description": "Inserisci la password per {name} che hai impostato nella tua configurazione." }, "discovery_confirm": { "description": "Vuoi aggiungere il nodo ESPHome `{name}` a Home Assistant?", @@ -27,8 +26,7 @@ "host": "Host", "port": "Porta" }, - "description": "Inserisci le impostazioni di connessione del tuo nodo [ESPHome] (https://esphomelib.com/).", - "title": "ESPHome" + "description": "Inserisci le impostazioni di connessione del tuo nodo [ESPHome] (https://esphomelib.com/)." } } } diff --git a/homeassistant/components/esphome/translations/ko.json b/homeassistant/components/esphome/translations/ko.json index 9ed518a9985..b3278416350 100644 --- a/homeassistant/components/esphome/translations/ko.json +++ b/homeassistant/components/esphome/translations/ko.json @@ -15,8 +15,7 @@ "data": { "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\ud558\uae30" + "description": "{name} \uc758 \uad6c\uc131\uc5d0 \uc124\uc815\ud55c \ube44\ubc00\ubc88\ud638\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694." }, "discovery_confirm": { "description": "Home Assistant \uc5d0 ESPHome node `{name}` \uc744(\ub97c) \ucd94\uac00\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", @@ -27,8 +26,7 @@ "host": "\ud638\uc2a4\ud2b8", "port": "\ud3ec\ud2b8" }, - "description": "[ESPHome](https://esphomelib.com/) \ub178\ub4dc\uc758 \uc5f0\uacb0 \uad6c\uc131\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694.", - "title": "ESPHome" + "description": "[ESPHome](https://esphomelib.com/) \ub178\ub4dc\uc758 \uc5f0\uacb0 \uad6c\uc131\uc744 \uc785\ub825\ud574\uc8fc\uc138\uc694." } } } diff --git a/homeassistant/components/esphome/translations/lb.json b/homeassistant/components/esphome/translations/lb.json index 35d9c8472cf..939385372fa 100644 --- a/homeassistant/components/esphome/translations/lb.json +++ b/homeassistant/components/esphome/translations/lb.json @@ -15,8 +15,7 @@ "data": { "password": "Passwuert" }, - "description": "Gitt d'Passwuert vun \u00e4rer Konfiguratioun an fir {name}.", - "title": "Passwuert aginn" + "description": "Gitt d'Passwuert vun \u00e4rer Konfiguratioun an fir {name}." }, "discovery_confirm": { "description": "W\u00ebllt dir den ESPHome Provider `{name}` am Home Assistant dob\u00e4isetzen?", @@ -27,8 +26,7 @@ "host": "Apparat", "port": "Port" }, - "description": "Gitt Verbindungs Informatioune vun \u00e4rem [ESPHome](https://esphomelib.com/) an.", - "title": "ESPHome" + "description": "Gitt Verbindungs Informatioune vun \u00e4rem [ESPHome](https://esphomelib.com/) an." } } } diff --git a/homeassistant/components/esphome/translations/nl.json b/homeassistant/components/esphome/translations/nl.json index 4edc46a372a..2ba68398603 100644 --- a/homeassistant/components/esphome/translations/nl.json +++ b/homeassistant/components/esphome/translations/nl.json @@ -14,8 +14,7 @@ "data": { "password": "Wachtwoord" }, - "description": "Voer het wachtwoord in dat u in uw configuratie heeft ingesteld voor {name}.", - "title": "Voer wachtwoord in" + "description": "Voer het wachtwoord in dat u in uw configuratie heeft ingesteld voor {name}." }, "discovery_confirm": { "description": "Wil je de ESPHome-node `{name}` toevoegen aan de Home Assistant?", @@ -26,8 +25,7 @@ "host": "Host", "port": "Poort" }, - "description": "Voer de verbindingsinstellingen in van uw [ESPHome](https://esphomelib.com/) node.", - "title": "[%key:component::esphome::title%]" + "description": "Voer de verbindingsinstellingen in van uw [ESPHome](https://esphomelib.com/) node." } } } diff --git a/homeassistant/components/esphome/translations/nn.json b/homeassistant/components/esphome/translations/nn.json index 628fb01a2cc..5f7971eb873 100644 --- a/homeassistant/components/esphome/translations/nn.json +++ b/homeassistant/components/esphome/translations/nn.json @@ -4,9 +4,6 @@ "step": { "discovery_confirm": { "title": "Fann ESPhome node" - }, - "user": { - "title": "[%key:component::esphome::title%]" } } } diff --git a/homeassistant/components/esphome/translations/no.json b/homeassistant/components/esphome/translations/no.json index 93ce6efab44..200481cc7b4 100644 --- a/homeassistant/components/esphome/translations/no.json +++ b/homeassistant/components/esphome/translations/no.json @@ -15,8 +15,7 @@ "data": { "password": "Passord" }, - "description": "Vennligst fyll inn passordet du har angitt i din konfigurasjon for {name}.", - "title": "Fyll inn passord" + "description": "Vennligst fyll inn passordet du har angitt i din konfigurasjon for {name}." }, "discovery_confirm": { "description": "\u00d8nsker du \u00e5 legge ESPHome noden `{name}` til Home Assistant?", @@ -27,8 +26,7 @@ "host": "Vert", "port": "Port" }, - "description": "Vennligst fyll inn tilkoblingsinnstillinger for din [ESPHome](https://esphomelib.com/) node.", - "title": "" + "description": "Vennligst fyll inn tilkoblingsinnstillinger for din [ESPHome](https://esphomelib.com/) node." } } } diff --git a/homeassistant/components/esphome/translations/pl.json b/homeassistant/components/esphome/translations/pl.json index 6ac0409a1dd..1e6da3d4572 100644 --- a/homeassistant/components/esphome/translations/pl.json +++ b/homeassistant/components/esphome/translations/pl.json @@ -15,8 +15,7 @@ "data": { "password": "Has\u0142o" }, - "description": "Wprowad\u017a has\u0142o ustawione w konfiguracji dla {name}.", - "title": "Wprowad\u017a has\u0142o" + "description": "Wprowad\u017a has\u0142o ustawione w konfiguracji dla {name}." }, "discovery_confirm": { "description": "Czy chcesz doda\u0107 w\u0119ze\u0142 ESPHome `{name}` do Home Assistant?", @@ -27,8 +26,7 @@ "host": "Nazwa hosta lub adres IP", "port": "Port" }, - "description": "Wprowad\u017a ustawienia po\u0142\u0105czenia [ESPHome](https://esphomelib.com/) w\u0119z\u0142a.", - "title": "ESPHome" + "description": "Wprowad\u017a ustawienia po\u0142\u0105czenia [ESPHome](https://esphomelib.com/) w\u0119z\u0142a." } } } diff --git a/homeassistant/components/esphome/translations/pt-BR.json b/homeassistant/components/esphome/translations/pt-BR.json index bbc56421899..df7f51ccd51 100644 --- a/homeassistant/components/esphome/translations/pt-BR.json +++ b/homeassistant/components/esphome/translations/pt-BR.json @@ -14,8 +14,7 @@ "data": { "password": "Senha" }, - "description": "Por favor, digite a senha que voc\u00ea definiu em sua configura\u00e7\u00e3o.", - "title": "Digite a senha" + "description": "Por favor, digite a senha que voc\u00ea definiu em sua configura\u00e7\u00e3o." }, "discovery_confirm": { "description": "Voc\u00ea quer adicionar o n\u00f3 ESPHome ` {name} ` ao Home Assistant?", @@ -26,8 +25,7 @@ "host": "Host", "port": "Porta" }, - "description": "Por favor insira as configura\u00e7\u00f5es de conex\u00e3o de seu n\u00f3 de [ESPHome] (https://esphomelib.com/).", - "title": "[%key:component::esphome::title%]" + "description": "Por favor insira as configura\u00e7\u00f5es de conex\u00e3o de seu n\u00f3 de [ESPHome] (https://esphomelib.com/)." } } } diff --git a/homeassistant/components/esphome/translations/pt.json b/homeassistant/components/esphome/translations/pt.json index 7a5e8621d4c..7522fa94f96 100644 --- a/homeassistant/components/esphome/translations/pt.json +++ b/homeassistant/components/esphome/translations/pt.json @@ -13,8 +13,7 @@ "data": { "password": "Palavra-passe" }, - "description": "Por favor, insira a palavra-passe que colocou na configura\u00e7\u00e3o para {name}", - "title": "Palavra-passe" + "description": "Por favor, insira a palavra-passe que colocou na configura\u00e7\u00e3o para {name}" }, "discovery_confirm": { "description": "Deseja adicionar um n\u00f3 ESPHome `{name}` ao Home Assistant?", @@ -25,8 +24,7 @@ "host": "Servidor", "port": "Porta" }, - "description": "Por favor, insira as configura\u00e7\u00f5es de liga\u00e7\u00e3o ao seu n\u00f3 [ESPHome] (https://esphomelib.com/).", - "title": "[%key:component::esphome::title%]" + "description": "Por favor, insira as configura\u00e7\u00f5es de liga\u00e7\u00e3o ao seu n\u00f3 [ESPHome] (https://esphomelib.com/)." } } } diff --git a/homeassistant/components/esphome/translations/ru.json b/homeassistant/components/esphome/translations/ru.json index d9407b1c20e..c03e4619ec9 100644 --- a/homeassistant/components/esphome/translations/ru.json +++ b/homeassistant/components/esphome/translations/ru.json @@ -15,8 +15,7 @@ "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c" }, - "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u043f\u0430\u0440\u043e\u043b\u044c, \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044b\u0439 \u0432 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 {name}.", - "title": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u043f\u0430\u0440\u043e\u043b\u044c" + "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u043f\u0430\u0440\u043e\u043b\u044c, \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044b\u0439 \u0432 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 {name}." }, "discovery_confirm": { "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c ESPHome `{name}`?", @@ -27,8 +26,7 @@ "host": "\u0425\u043e\u0441\u0442", "port": "\u041f\u043e\u0440\u0442" }, - "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a \u0412\u0430\u0448\u0435\u043c\u0443 [ESPHome](https://esphomelib.com/).", - "title": "ESPHome" + "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a \u0412\u0430\u0448\u0435\u043c\u0443 [ESPHome](https://esphomelib.com/)." } } } diff --git a/homeassistant/components/esphome/translations/sl.json b/homeassistant/components/esphome/translations/sl.json index 15c3577cf9e..b16a5cd4226 100644 --- a/homeassistant/components/esphome/translations/sl.json +++ b/homeassistant/components/esphome/translations/sl.json @@ -15,8 +15,7 @@ "data": { "password": "Geslo" }, - "description": "Vnesite geslo, ki ste ga nastavili v konfiguraciji za {name}.", - "title": "Vnesite geslo" + "description": "Vnesite geslo, ki ste ga nastavili v konfiguraciji za {name}." }, "discovery_confirm": { "description": "\u017delite dodati ESPHome vozli\u0161\u010de ` {name} ` v Home Assistant?", @@ -27,8 +26,7 @@ "host": "Gostitelj", "port": "Vrata" }, - "description": "Prosimo, vnesite nastavitve povezave va\u0161ega vozli\u0161\u010da [ESPHome] (https://esphomelib.com/).", - "title": "ESPHome" + "description": "Prosimo, vnesite nastavitve povezave va\u0161ega vozli\u0161\u010da [ESPHome] (https://esphomelib.com/)." } } } diff --git a/homeassistant/components/esphome/translations/sv.json b/homeassistant/components/esphome/translations/sv.json index 3c3a4a53f64..5b57dc18617 100644 --- a/homeassistant/components/esphome/translations/sv.json +++ b/homeassistant/components/esphome/translations/sv.json @@ -14,8 +14,7 @@ "data": { "password": "L\u00f6senord" }, - "description": "Ange det l\u00f6senord du angett i din konfiguration f\u00f6r {name}.", - "title": "Ange l\u00f6senord" + "description": "Ange det l\u00f6senord du angett i din konfiguration f\u00f6r {name}." }, "discovery_confirm": { "description": "Vill du l\u00e4gga till ESPHome noden ` {name} ` till Home Assistant?", @@ -26,8 +25,7 @@ "host": "V\u00e4rddatorn", "port": "Port" }, - "description": "Ange anslutningsinst\u00e4llningarna f\u00f6r noden [ESPHome](https://esphomelib.com/).", - "title": "[%key:component::esphome::title%]" + "description": "Ange anslutningsinst\u00e4llningarna f\u00f6r noden [ESPHome](https://esphomelib.com/)." } } } diff --git a/homeassistant/components/esphome/translations/th.json b/homeassistant/components/esphome/translations/th.json index 10520139bd2..f7596ba4dd9 100644 --- a/homeassistant/components/esphome/translations/th.json +++ b/homeassistant/components/esphome/translations/th.json @@ -7,11 +7,7 @@ "authenticate": { "data": { "password": "\u0e23\u0e2b\u0e31\u0e2a\u0e1c\u0e48\u0e32\u0e19" - }, - "title": "\u0e43\u0e2a\u0e48\u0e23\u0e2b\u0e31\u0e2a\u0e1c\u0e48\u0e32\u0e19" - }, - "user": { - "title": "[%key:component::esphome::title%]" + } } } } diff --git a/homeassistant/components/esphome/translations/uk.json b/homeassistant/components/esphome/translations/uk.json index ec22664e46b..f30ac672d4d 100644 --- a/homeassistant/components/esphome/translations/uk.json +++ b/homeassistant/components/esphome/translations/uk.json @@ -13,8 +13,7 @@ "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c" }, - "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u043f\u0430\u0440\u043e\u043b\u044c, \u044f\u043a\u0438\u0439 \u0432\u0438 \u0432\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043b\u0438 \u0443 \u0441\u0432\u043e\u0457\u0439 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u0457.", - "title": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u043f\u0430\u0440\u043e\u043b\u044c" + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u043f\u0430\u0440\u043e\u043b\u044c, \u044f\u043a\u0438\u0439 \u0432\u0438 \u0432\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043b\u0438 \u0443 \u0441\u0432\u043e\u0457\u0439 \u043a\u043e\u043d\u0444\u0456\u0433\u0443\u0440\u0430\u0446\u0456\u0457." }, "discovery_confirm": { "description": "\u0414\u043e\u0434\u0430\u0442\u0438 ESPHome \u0432\u0443\u0437\u043e\u043b {name} \u0443 Home Assistant?", @@ -25,8 +24,7 @@ "host": "\u0425\u043e\u0441\u0442", "port": "\u041f\u043e\u0440\u0442" }, - "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0432\u0430\u0448\u043e\u0433\u043e \u0432\u0443\u0437\u043b\u0430 [ESPHome] (https://esphomelib.com/).", - "title": "[%key:component::esphome::title%]" + "description": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438 \u043f\u0456\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043d\u044f \u0432\u0430\u0448\u043e\u0433\u043e \u0432\u0443\u0437\u043b\u0430 [ESPHome] (https://esphomelib.com/)." } } } diff --git a/homeassistant/components/esphome/translations/zh-Hans.json b/homeassistant/components/esphome/translations/zh-Hans.json index 8c756a69686..bda2140c4f0 100644 --- a/homeassistant/components/esphome/translations/zh-Hans.json +++ b/homeassistant/components/esphome/translations/zh-Hans.json @@ -14,8 +14,7 @@ "data": { "password": "\u5bc6\u7801" }, - "description": "\u8bf7\u8f93\u5165\u60a8\u5728\u914d\u7f6e\u4e2d\u4e3a\u201c{name}\u201d\u8bbe\u7f6e\u7684\u5bc6\u7801\u3002", - "title": "\u8f93\u5165\u5bc6\u7801" + "description": "\u8bf7\u8f93\u5165\u60a8\u5728\u914d\u7f6e\u4e2d\u4e3a\u201c{name}\u201d\u8bbe\u7f6e\u7684\u5bc6\u7801\u3002" }, "discovery_confirm": { "description": "\u662f\u5426\u8981\u5c06 ESPHome \u8282\u70b9 `{name}` \u6dfb\u52a0\u5230 Home Assistant\uff1f", @@ -26,8 +25,7 @@ "host": "\u4e3b\u673a", "port": "\u7aef\u53e3" }, - "description": "\u8bf7\u8f93\u5165\u60a8\u7684 [ESPHome](https://esphomelib.com/) \u8282\u70b9\u7684\u8fde\u63a5\u8bbe\u7f6e\u3002", - "title": "[%key:component::esphome::title%]" + "description": "\u8bf7\u8f93\u5165\u60a8\u7684 [ESPHome](https://esphomelib.com/) \u8282\u70b9\u7684\u8fde\u63a5\u8bbe\u7f6e\u3002" } } } diff --git a/homeassistant/components/esphome/translations/zh-Hant.json b/homeassistant/components/esphome/translations/zh-Hant.json index 3657af88ce9..be25ddd6366 100644 --- a/homeassistant/components/esphome/translations/zh-Hant.json +++ b/homeassistant/components/esphome/translations/zh-Hant.json @@ -15,8 +15,7 @@ "data": { "password": "\u5bc6\u78bc" }, - "description": "\u8acb\u8f38\u5165 {name} \u8a2d\u5b9a\u5167\u6240\u8a2d\u5b9a\u4e4b\u5bc6\u78bc\u3002", - "title": "\u8f38\u5165\u5bc6\u78bc" + "description": "\u8acb\u8f38\u5165 {name} \u8a2d\u5b9a\u5167\u6240\u8a2d\u5b9a\u4e4b\u5bc6\u78bc\u3002" }, "discovery_confirm": { "description": "\u662f\u5426\u8981\u5c07 ESPHome \u7bc0\u9ede `{name}` \u65b0\u589e\u81f3 Home Assistant\uff1f", @@ -27,8 +26,7 @@ "host": "\u4e3b\u6a5f\u7aef", "port": "\u901a\u8a0a\u57e0" }, - "description": "\u8acb\u8f38\u5165 [ESPHome](https://esphomelib.com/) \u7bc0\u9ede\u9023\u7dda\u8cc7\u8a0a\u3002", - "title": "[%key:component::esphome::title%]" + "description": "\u8acb\u8f38\u5165 [ESPHome](https://esphomelib.com/) \u7bc0\u9ede\u9023\u7dda\u8cc7\u8a0a\u3002" } } } diff --git a/homeassistant/components/forked_daapd/translations/no.json b/homeassistant/components/forked_daapd/translations/no.json index 2bf6b1e2324..8cb7ec812ae 100644 --- a/homeassistant/components/forked_daapd/translations/no.json +++ b/homeassistant/components/forked_daapd/translations/no.json @@ -9,7 +9,7 @@ "websocket_not_enabled": "websocket for forked-daapd server ikke aktivert.", "wrong_host_or_port": "Kan ikke koble til. Vennligst sjekk vert og port.", "wrong_password": "Feil passord.", - "wrong_server_type": "Ikke en forked-daapd-server." + "wrong_server_type": "Forked-daapd integrasjon krever en gaffel-daapd server med versjon \"= 27.0." }, "flow_title": "forked-daapd-server: {name} ( {host} )", "step": { diff --git a/homeassistant/components/fritzbox/translations/ca.json b/homeassistant/components/fritzbox/translations/ca.json index 1951e35a923..af82f1fc582 100644 --- a/homeassistant/components/fritzbox/translations/ca.json +++ b/homeassistant/components/fritzbox/translations/ca.json @@ -16,8 +16,7 @@ "password": "Contrasenya", "username": "Nom d'usuari" }, - "description": "Vols configurar {name}?", - "title": "AVM FRITZ!Box" + "description": "Vols configurar {name}?" }, "user": { "data": { @@ -25,8 +24,7 @@ "password": "Contrasenya", "username": "Nom d'usuari" }, - "description": "Introdueix la teva informaci\u00f3 de AVM FRITZ!Box.", - "title": "AVM FRITZ!Box" + "description": "Introdueix la teva informaci\u00f3 de AVM FRITZ!Box." } } } diff --git a/homeassistant/components/fritzbox/translations/de.json b/homeassistant/components/fritzbox/translations/de.json index 1a2e046d933..e6e485497a3 100644 --- a/homeassistant/components/fritzbox/translations/de.json +++ b/homeassistant/components/fritzbox/translations/de.json @@ -16,8 +16,7 @@ "password": "Passwort", "username": "Benutzername" }, - "description": "M\u00f6chten Sie {name} einrichten?", - "title": "AVM FRITZ! Box" + "description": "M\u00f6chten Sie {name} einrichten?" }, "user": { "data": { @@ -25,8 +24,7 @@ "password": "Passwort", "username": "Benutzername" }, - "description": "Geben Sie Ihre AVM FRITZ! Box-Informationen ein.", - "title": "AVM FRITZ! Box" + "description": "Geben Sie Ihre AVM FRITZ! Box-Informationen ein." } } } diff --git a/homeassistant/components/fritzbox/translations/en.json b/homeassistant/components/fritzbox/translations/en.json index 4f43626c667..cc9b13619a7 100644 --- a/homeassistant/components/fritzbox/translations/en.json +++ b/homeassistant/components/fritzbox/translations/en.json @@ -16,8 +16,7 @@ "password": "Password", "username": "Username" }, - "description": "Do you want to set up {name}?", - "title": "AVM FRITZ!Box" + "description": "Do you want to set up {name}?" }, "user": { "data": { @@ -25,8 +24,7 @@ "password": "Password", "username": "Username" }, - "description": "Enter your AVM FRITZ!Box information.", - "title": "AVM FRITZ!Box" + "description": "Enter your AVM FRITZ!Box information." } } } diff --git a/homeassistant/components/fritzbox/translations/es-419.json b/homeassistant/components/fritzbox/translations/es-419.json index 4e8003a06d8..9cc1a40daa3 100644 --- a/homeassistant/components/fritzbox/translations/es-419.json +++ b/homeassistant/components/fritzbox/translations/es-419.json @@ -16,8 +16,7 @@ "password": "Contrase\u00f1a", "username": "Nombre de usuario" }, - "description": "\u00bfDesea configurar {name}?", - "title": "AVM FRITZ!Box" + "description": "\u00bfDesea configurar {name}?" }, "user": { "data": { @@ -25,8 +24,7 @@ "password": "Contrase\u00f1a", "username": "Nombre de usuario" }, - "description": "Ingrese la informaci\u00f3n de su AVM FRITZ!Box.", - "title": "AVM FRITZ!Box" + "description": "Ingrese la informaci\u00f3n de su AVM FRITZ!Box." } } } diff --git a/homeassistant/components/fritzbox/translations/es.json b/homeassistant/components/fritzbox/translations/es.json index 05f956d2382..123b98ee9dc 100644 --- a/homeassistant/components/fritzbox/translations/es.json +++ b/homeassistant/components/fritzbox/translations/es.json @@ -16,8 +16,7 @@ "password": "Contrase\u00f1a", "username": "Usuario" }, - "description": "\u00bfQuieres configurar {name}?", - "title": "AVM FRITZ! Box" + "description": "\u00bfQuieres configurar {name}?" }, "user": { "data": { @@ -25,8 +24,7 @@ "password": "Contrase\u00f1a", "username": "Usuario" }, - "description": "Introduce tu informaci\u00f3n de AVM FRITZ!Box.", - "title": "AVM FRITZ! Box" + "description": "Introduce tu informaci\u00f3n de AVM FRITZ!Box." } } } diff --git a/homeassistant/components/fritzbox/translations/fi.json b/homeassistant/components/fritzbox/translations/fi.json index bb4fb818a67..db9b821bee6 100644 --- a/homeassistant/components/fritzbox/translations/fi.json +++ b/homeassistant/components/fritzbox/translations/fi.json @@ -11,8 +11,7 @@ "data": { "password": "Salasana", "username": "K\u00e4ytt\u00e4j\u00e4tunnus" - }, - "title": "AVM FRITZ!Box" + } } } } diff --git a/homeassistant/components/fritzbox/translations/fr.json b/homeassistant/components/fritzbox/translations/fr.json index 97f8d97430d..0a84e1ec2f3 100644 --- a/homeassistant/components/fritzbox/translations/fr.json +++ b/homeassistant/components/fritzbox/translations/fr.json @@ -16,8 +16,7 @@ "password": "Mot de passe", "username": "Nom d'utilisateur" }, - "description": "Voulez-vous configurer {name} ?", - "title": "AVM FRITZ!Box" + "description": "Voulez-vous configurer {name} ?" }, "user": { "data": { @@ -25,8 +24,7 @@ "password": "Mot de passe", "username": "Nom d'utilisateur" }, - "description": "Entrez les informations de votre AVM FRITZ!Box.", - "title": "AVM FRITZ!Box" + "description": "Entrez les informations de votre AVM FRITZ!Box." } } } diff --git a/homeassistant/components/fritzbox/translations/it.json b/homeassistant/components/fritzbox/translations/it.json index 8e1a1fe9b4d..3b99e985871 100644 --- a/homeassistant/components/fritzbox/translations/it.json +++ b/homeassistant/components/fritzbox/translations/it.json @@ -16,8 +16,7 @@ "password": "Password", "username": "Nome utente" }, - "description": "Vuoi impostare {name}?", - "title": "AVM FRITZ!Box" + "description": "Vuoi impostare {name}?" }, "user": { "data": { @@ -25,8 +24,7 @@ "password": "Password", "username": "Nome utente" }, - "description": "Inserisci le informazioni del tuo AVM FRITZ!Box .", - "title": "AVM FRITZ!Box" + "description": "Inserisci le informazioni del tuo AVM FRITZ!Box ." } } } diff --git a/homeassistant/components/fritzbox/translations/ko.json b/homeassistant/components/fritzbox/translations/ko.json index 395749b2b9f..0d27f8e0606 100644 --- a/homeassistant/components/fritzbox/translations/ko.json +++ b/homeassistant/components/fritzbox/translations/ko.json @@ -16,8 +16,7 @@ "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" + "description": "{name} \uc744(\ub97c) \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" }, "user": { "data": { @@ -25,8 +24,7 @@ "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" + "description": "AVM FRITZ!Box \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694." } } } diff --git a/homeassistant/components/fritzbox/translations/lb.json b/homeassistant/components/fritzbox/translations/lb.json index d9126243577..12ff01c1306 100644 --- a/homeassistant/components/fritzbox/translations/lb.json +++ b/homeassistant/components/fritzbox/translations/lb.json @@ -16,8 +16,7 @@ "password": "Passwuert", "username": "Benotzernumm" }, - "description": "Soll {name} konfigur\u00e9iert ginn?", - "title": "AVM FRITZ!Box" + "description": "Soll {name} konfigur\u00e9iert ginn?" }, "user": { "data": { @@ -25,8 +24,7 @@ "password": "Passwuert", "username": "Benotzernumm" }, - "description": "F\u00ebll d\u00e9ng AVM FRITZ!Box Informatiounen aus.", - "title": "AVM FRITZ!Box" + "description": "F\u00ebll d\u00e9ng AVM FRITZ!Box Informatiounen aus." } } } diff --git a/homeassistant/components/fritzbox/translations/nl.json b/homeassistant/components/fritzbox/translations/nl.json index d6391d16803..ea8b715b7ea 100644 --- a/homeassistant/components/fritzbox/translations/nl.json +++ b/homeassistant/components/fritzbox/translations/nl.json @@ -16,8 +16,7 @@ "password": "Wachtwoord", "username": "Gebruikersnaam" }, - "description": "Wilt u {name} instellen?", - "title": "AVM FRITZ!Box" + "description": "Wilt u {name} instellen?" }, "user": { "data": { @@ -25,8 +24,7 @@ "password": "Wachtwoord", "username": "Gebruikersnaam" }, - "description": "Voer uw AVM FRITZ!Box informatie in.", - "title": "AVM FRITZ!Box" + "description": "Voer uw AVM FRITZ!Box informatie in." } } } diff --git a/homeassistant/components/fritzbox/translations/no.json b/homeassistant/components/fritzbox/translations/no.json index a21067bb112..55518d0288a 100644 --- a/homeassistant/components/fritzbox/translations/no.json +++ b/homeassistant/components/fritzbox/translations/no.json @@ -16,8 +16,7 @@ "password": "Passord", "username": "Brukernavn" }, - "description": "Vil du sette opp {name} ?", - "title": "" + "description": "Vil du sette opp {name} ?" }, "user": { "data": { @@ -25,8 +24,7 @@ "password": "Passord", "username": "Brukernavn" }, - "description": "Fyll inn AVM FRITZ!Box informasjonen.", - "title": "" + "description": "Fyll inn AVM FRITZ!Box informasjonen." } } } diff --git a/homeassistant/components/fritzbox/translations/pl.json b/homeassistant/components/fritzbox/translations/pl.json index 923f4ba2186..0c8f96e83a4 100644 --- a/homeassistant/components/fritzbox/translations/pl.json +++ b/homeassistant/components/fritzbox/translations/pl.json @@ -16,8 +16,7 @@ "password": "Has\u0142o", "username": "Nazwa u\u017cytkownika" }, - "description": "Czy chcesz skonfigurowa\u0107 {name}?", - "title": "AVM FRITZ! Box" + "description": "Czy chcesz skonfigurowa\u0107 {name}?" }, "user": { "data": { @@ -25,8 +24,7 @@ "password": "Has\u0142o", "username": "Nazwa u\u017cytkownika" }, - "description": "Wprowad\u017a informacje o urz\u0105dzeniu AVM FRITZ! Box.", - "title": "AVM FRITZ! Box" + "description": "Wprowad\u017a informacje o urz\u0105dzeniu AVM FRITZ! Box." } } } diff --git a/homeassistant/components/fritzbox/translations/ru.json b/homeassistant/components/fritzbox/translations/ru.json index ce8f88bc4e4..635e0a2b891 100644 --- a/homeassistant/components/fritzbox/translations/ru.json +++ b/homeassistant/components/fritzbox/translations/ru.json @@ -16,8 +16,7 @@ "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "username": "\u041b\u043e\u0433\u0438\u043d" }, - "description": "\u0425\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c {name}?", - "title": "AVM FRITZ!Box" + "description": "\u0425\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c {name}?" }, "user": { "data": { @@ -25,8 +24,7 @@ "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "username": "\u041b\u043e\u0433\u0438\u043d" }, - "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0441 AVM FRITZ!Box.", - "title": "AVM FRITZ!Box" + "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0441 AVM FRITZ!Box." } } } diff --git a/homeassistant/components/fritzbox/translations/sl.json b/homeassistant/components/fritzbox/translations/sl.json index 484ab28b265..cd85c58af24 100644 --- a/homeassistant/components/fritzbox/translations/sl.json +++ b/homeassistant/components/fritzbox/translations/sl.json @@ -16,8 +16,7 @@ "password": "Geslo", "username": "Uporabni\u0161ko ime" }, - "description": "Ali \u017eelite nastaviti {name} ?", - "title": "AVM FRITZ!Box" + "description": "Ali \u017eelite nastaviti {name} ?" }, "user": { "data": { @@ -25,8 +24,7 @@ "password": "Geslo", "username": "Uporabni\u0161ko ime" }, - "description": "Vnesite svoje podatke za AVM FRITZ! Box.", - "title": "AVM FRITZ!Box" + "description": "Vnesite svoje podatke za AVM FRITZ! Box." } } } diff --git a/homeassistant/components/fritzbox/translations/zh-Hant.json b/homeassistant/components/fritzbox/translations/zh-Hant.json index f517a667d97..415ad9c71bd 100644 --- a/homeassistant/components/fritzbox/translations/zh-Hant.json +++ b/homeassistant/components/fritzbox/translations/zh-Hant.json @@ -16,8 +16,7 @@ "password": "\u5bc6\u78bc", "username": "\u4f7f\u7528\u8005\u540d\u7a31" }, - "description": "\u662f\u5426\u8981\u8a2d\u5b9a {name}\uff1f", - "title": "AVM FRITZ!Box" + "description": "\u662f\u5426\u8981\u8a2d\u5b9a {name}\uff1f" }, "user": { "data": { @@ -25,8 +24,7 @@ "password": "\u5bc6\u78bc", "username": "\u4f7f\u7528\u8005\u540d\u7a31" }, - "description": "\u8f38\u5165 AVM FRITZ!Box \u8cc7\u8a0a\u3002", - "title": "AVM FRITZ!Box" + "description": "\u8f38\u5165 AVM FRITZ!Box \u8cc7\u8a0a\u3002" } } } diff --git a/homeassistant/components/gogogate2/translations/lb.json b/homeassistant/components/gogogate2/translations/lb.json index b0d5d418ae9..4183e7dcbc7 100644 --- a/homeassistant/components/gogogate2/translations/lb.json +++ b/homeassistant/components/gogogate2/translations/lb.json @@ -1,9 +1,18 @@ { "config": { + "abort": { + "cannot_connect": "Feeler beim verbannen" + }, + "error": { + "cannot_connect": "Feeler beim verbannen", + "invalid_auth": "Ong\u00eblteg Authentifikatioun" + }, "step": { "user": { "data": { - "ip_address": "IP Adresse" + "ip_address": "IP Adresse", + "password": "Passwuert", + "username": "Benotzernumm" }, "description": "G\u00ebff noutwendeg Informatioun hei \u00ebnnen un.", "title": "GogoGate 2 ariichten" diff --git a/homeassistant/components/guardian/translations/ca.json b/homeassistant/components/guardian/translations/ca.json new file mode 100644 index 00000000000..a94126753be --- /dev/null +++ b/homeassistant/components/guardian/translations/ca.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Aquest dispositiu Guardian ja est\u00e0 configurat.", + "already_in_progress": "La configuraci\u00f3 del dispositiu Guardian ja est\u00e0 en curs.", + "connection_error": "No s'ha pogut connectar amb el dispositiu Guardian." + }, + "step": { + "user": { + "data": { + "ip_address": "Adre\u00e7a IP", + "port": "Port" + }, + "description": "Configura un dispositiu Elexa Guardian local." + }, + "zeroconf_confirm": { + "description": "Vols configurar aquest dispositiu Guardian?" + } + } + }, + "title": "Elexa Guardian" +} \ No newline at end of file diff --git a/homeassistant/components/guardian/translations/es.json b/homeassistant/components/guardian/translations/es.json new file mode 100644 index 00000000000..ec2a724e944 --- /dev/null +++ b/homeassistant/components/guardian/translations/es.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Este dispositivo Guardian ya ha sido configurado", + "already_in_progress": "La configuraci\u00f3n del dispositivo Guardian ya est\u00e1 en proceso.", + "connection_error": "No se ha podido conectar con el dispositivo Guardian." + }, + "step": { + "user": { + "data": { + "ip_address": "Direcci\u00f3n IP", + "port": "Puerto" + }, + "description": "Configurar un dispositivo local Elexa Guardian." + }, + "zeroconf_confirm": { + "description": "\u00bfQuieres configurar este dispositivo Guardian?" + } + } + }, + "title": "Elexa Guardian" +} \ No newline at end of file diff --git a/homeassistant/components/guardian/translations/lb.json b/homeassistant/components/guardian/translations/lb.json new file mode 100644 index 00000000000..1de8bb9baea --- /dev/null +++ b/homeassistant/components/guardian/translations/lb.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "D\u00ebse Guardian Apparat ass scho konfigur\u00e9iert.", + "already_in_progress": "Guardian Apparat Konfiguratioun ass schonn am gaang.", + "connection_error": "Feeler beim verbannen mam Guardian Apparat." + }, + "step": { + "user": { + "data": { + "ip_address": "IP Adresse", + "port": "Port" + }, + "description": "Ee lokalen Elexa Guardian Apparat ariichten." + }, + "zeroconf_confirm": { + "description": "Soll d\u00ebsen Guardian Apparat konfigur\u00e9iert ginn?" + } + } + }, + "title": "Elexa Guardian" +} \ No newline at end of file diff --git a/homeassistant/components/guardian/translations/no.json b/homeassistant/components/guardian/translations/no.json new file mode 100644 index 00000000000..b398079cc36 --- /dev/null +++ b/homeassistant/components/guardian/translations/no.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Denne Guardian-enheten er allerede konfigurert.", + "already_in_progress": "Konfigurasjon av Guardian-enheter er allerede i gang.", + "connection_error": "Kan ikke koble til Guardian-enheten." + }, + "step": { + "user": { + "data": { + "ip_address": "IP adresse", + "port": "Port" + }, + "description": "Konfigurer en lokal Elexa Guardian-enhet." + }, + "zeroconf_confirm": { + "description": "Vil du konfigurere denne Guardian-enheten?" + } + } + }, + "title": "Elexa Guardian" +} \ No newline at end of file diff --git a/homeassistant/components/guardian/translations/ru.json b/homeassistant/components/guardian/translations/ru.json new file mode 100644 index 00000000000..c9fe3b07ff7 --- /dev/null +++ b/homeassistant/components/guardian/translations/ru.json @@ -0,0 +1,22 @@ +{ + "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.", + "connection_error": "\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": { + "data": { + "ip_address": "IP-\u0430\u0434\u0440\u0435\u0441", + "port": "\u041f\u043e\u0440\u0442" + }, + "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0441 Elexa Guardian." + }, + "zeroconf_confirm": { + "description": "\u0425\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u044d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e Elexa Guardian?" + } + } + }, + "title": "Elexa Guardian" +} \ No newline at end of file diff --git a/homeassistant/components/guardian/translations/zh-Hant.json b/homeassistant/components/guardian/translations/zh-Hant.json new file mode 100644 index 00000000000..d91c0c3ba8c --- /dev/null +++ b/homeassistant/components/guardian/translations/zh-Hant.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Guardian \u8a2d\u5099\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "already_in_progress": "Guardian \u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d\u3002", + "connection_error": "Guardian \u8a2d\u5099\u9023\u7dda\u5931\u6557\u3002" + }, + "step": { + "user": { + "data": { + "ip_address": "IP \u4f4d\u5740", + "port": "\u901a\u8a0a\u57e0" + }, + "description": "\u8a2d\u5b9a\u5340\u57df Elexa Guardian \u8a2d\u5099\u3002" + }, + "zeroconf_confirm": { + "description": "\u662f\u5426\u8981\u8a2d\u5b9a Guardian \u8a2d\u5099\uff1f" + } + } + }, + "title": "Elexa Guardian" +} \ No newline at end of file diff --git a/homeassistant/components/heos/translations/bg.json b/homeassistant/components/heos/translations/bg.json index 4f52830af09..40156a8b3cd 100644 --- a/homeassistant/components/heos/translations/bg.json +++ b/homeassistant/components/heos/translations/bg.json @@ -9,7 +9,6 @@ "step": { "user": { "data": { - "access_token": "\u0410\u0434\u0440\u0435\u0441", "host": "\u0410\u0434\u0440\u0435\u0441" }, "description": "\u041c\u043e\u043b\u044f, \u0432\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u0438\u043c\u0435\u0442\u043e \u043d\u0430 \u0445\u043e\u0441\u0442\u0430 \u0438\u043b\u0438 IP \u0430\u0434\u0440\u0435\u0441\u0430 \u043d\u0430 Heos \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e (\u0437\u0430 \u043f\u0440\u0435\u0434\u043f\u043e\u0447\u0438\u0442\u0430\u043d\u0435 \u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e \u0434\u0430 \u0435 \u0441\u0432\u044a\u0440\u0437\u0430\u043d\u043e \u0441 \u043a\u0430\u0431\u0435\u043b \u043a\u044a\u043c \u043c\u0440\u0435\u0436\u0430\u0442\u0430).", diff --git a/homeassistant/components/heos/translations/ca.json b/homeassistant/components/heos/translations/ca.json index 02e8e22d920..d8bcf494615 100644 --- a/homeassistant/components/heos/translations/ca.json +++ b/homeassistant/components/heos/translations/ca.json @@ -9,7 +9,6 @@ "step": { "user": { "data": { - "access_token": "Amfitri\u00f3", "host": "Amfitri\u00f3" }, "description": "Introdueix el nom de l'amfitri\u00f3 o l'adre\u00e7a IP d'un dispositiu Heos (preferiblement un connectat a la xarxa per cable).", diff --git a/homeassistant/components/heos/translations/da.json b/homeassistant/components/heos/translations/da.json index b395497d67a..3537ba30553 100644 --- a/homeassistant/components/heos/translations/da.json +++ b/homeassistant/components/heos/translations/da.json @@ -9,7 +9,6 @@ "step": { "user": { "data": { - "access_token": "V\u00e6rt", "host": "V\u00e6rt" }, "description": "Indtast v\u00e6rtsnavnet eller IP-adressen p\u00e5 en Heos-enhed (helst en tilsluttet via ledning til netv\u00e6rket).", diff --git a/homeassistant/components/heos/translations/de.json b/homeassistant/components/heos/translations/de.json index bbd0d8beec7..4a886f0550b 100644 --- a/homeassistant/components/heos/translations/de.json +++ b/homeassistant/components/heos/translations/de.json @@ -9,7 +9,6 @@ "step": { "user": { "data": { - "access_token": "Host", "host": "Host" }, "description": "Bitte gib den Hostnamen oder die IP-Adresse eines Heos-Ger\u00e4ts ein (vorzugsweise eines, das per Kabel mit dem Netzwerk verbunden ist).", diff --git a/homeassistant/components/heos/translations/en.json b/homeassistant/components/heos/translations/en.json index 3227e5115f9..be84c08fa5e 100644 --- a/homeassistant/components/heos/translations/en.json +++ b/homeassistant/components/heos/translations/en.json @@ -9,7 +9,6 @@ "step": { "user": { "data": { - "access_token": "Host", "host": "Host" }, "description": "Please enter the host name or IP address of a Heos device (preferably one connected via wire to the network).", diff --git a/homeassistant/components/heos/translations/es-419.json b/homeassistant/components/heos/translations/es-419.json index 902f65bf5e1..6c45936d8be 100644 --- a/homeassistant/components/heos/translations/es-419.json +++ b/homeassistant/components/heos/translations/es-419.json @@ -9,7 +9,6 @@ "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).", diff --git a/homeassistant/components/heos/translations/es.json b/homeassistant/components/heos/translations/es.json index b79871d487c..ecde21aaf6b 100644 --- a/homeassistant/components/heos/translations/es.json +++ b/homeassistant/components/heos/translations/es.json @@ -9,7 +9,6 @@ "step": { "user": { "data": { - "access_token": "Host", "host": "Host" }, "description": "Introduce el nombre de host o direcci\u00f3n IP de un dispositivo Heos (preferiblemente conectado por cable a la red).", diff --git a/homeassistant/components/heos/translations/fi.json b/homeassistant/components/heos/translations/fi.json index 73e50dbf8ce..dbed4d27dd2 100644 --- a/homeassistant/components/heos/translations/fi.json +++ b/homeassistant/components/heos/translations/fi.json @@ -2,9 +2,6 @@ "config": { "step": { "user": { - "data": { - "access_token": "Palvelin" - }, "title": "Yhdist\u00e4 Heosiin" } } diff --git a/homeassistant/components/heos/translations/fr.json b/homeassistant/components/heos/translations/fr.json index 7f76c932c71..39cb39e33f5 100644 --- a/homeassistant/components/heos/translations/fr.json +++ b/homeassistant/components/heos/translations/fr.json @@ -9,7 +9,6 @@ "step": { "user": { "data": { - "access_token": "H\u00f4te", "host": "H\u00f4te" }, "description": "Veuillez saisir le nom d\u2019h\u00f4te ou l\u2019adresse IP d\u2019un p\u00e9riph\u00e9rique Heos (de pr\u00e9f\u00e9rence connect\u00e9 au r\u00e9seau filaire).", diff --git a/homeassistant/components/heos/translations/hu.json b/homeassistant/components/heos/translations/hu.json index eb7f856a000..cbf055e2fba 100644 --- a/homeassistant/components/heos/translations/hu.json +++ b/homeassistant/components/heos/translations/hu.json @@ -3,7 +3,6 @@ "step": { "user": { "data": { - "access_token": "Kiszolg\u00e1l\u00f3", "host": "Hoszt" } } diff --git a/homeassistant/components/heos/translations/it.json b/homeassistant/components/heos/translations/it.json index c40bbb95554..81350edf9ad 100644 --- a/homeassistant/components/heos/translations/it.json +++ b/homeassistant/components/heos/translations/it.json @@ -9,7 +9,6 @@ "step": { "user": { "data": { - "access_token": "Host", "host": "Host" }, "description": "Inserire il nome host o l'indirizzo IP di un dispositivo Heos (preferibilmente uno connesso alla rete tramite cavo).", diff --git a/homeassistant/components/heos/translations/ko.json b/homeassistant/components/heos/translations/ko.json index 1e7902adfb3..acf26df1cec 100644 --- a/homeassistant/components/heos/translations/ko.json +++ b/homeassistant/components/heos/translations/ko.json @@ -9,7 +9,6 @@ "step": { "user": { "data": { - "access_token": "\ud638\uc2a4\ud2b8", "host": "\ud638\uc2a4\ud2b8" }, "description": "Heos \uae30\uae30\uc758 \ud638\uc2a4\ud2b8 \uc774\ub984 \ub610\ub294 IP \uc8fc\uc18c\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694. (\uc720\uc120 \ub124\ud2b8\uc6cc\ud06c\ub85c \uc5f0\uacb0\ud558\ub294 \uac83\uc774 \uc88b\uc2b5\ub2c8\ub2e4)", diff --git a/homeassistant/components/heos/translations/lb.json b/homeassistant/components/heos/translations/lb.json index de124207d64..e7997f68b8c 100644 --- a/homeassistant/components/heos/translations/lb.json +++ b/homeassistant/components/heos/translations/lb.json @@ -9,7 +9,6 @@ "step": { "user": { "data": { - "access_token": "Apparat", "host": "Apparat" }, "description": "Gitt den Numm oder IP-Adress vun engem Heos-Apparat an (am beschten iwwer Kabel mam Reseau verbonnen).", diff --git a/homeassistant/components/heos/translations/nl.json b/homeassistant/components/heos/translations/nl.json index f3e9dfed7e3..2b85788f7af 100644 --- a/homeassistant/components/heos/translations/nl.json +++ b/homeassistant/components/heos/translations/nl.json @@ -9,7 +9,6 @@ "step": { "user": { "data": { - "access_token": "Host", "host": "Host" }, "description": "Voer de hostnaam of het IP-adres van een Heos-apparaat in (bij voorkeur een die via een kabel is verbonden met het netwerk).", diff --git a/homeassistant/components/heos/translations/no.json b/homeassistant/components/heos/translations/no.json index 706fbc31cb4..7a8a9ccb7f1 100644 --- a/homeassistant/components/heos/translations/no.json +++ b/homeassistant/components/heos/translations/no.json @@ -9,7 +9,6 @@ "step": { "user": { "data": { - "access_token": "Vert", "host": "Vert" }, "description": "Vennligst fyll inn vertsnavnet eller IP-adressen til en Heos-enhet (helst en tilkoblet nettverket via kabel).", diff --git a/homeassistant/components/heos/translations/pl.json b/homeassistant/components/heos/translations/pl.json index 0c0b9ade13f..5394d57bb74 100644 --- a/homeassistant/components/heos/translations/pl.json +++ b/homeassistant/components/heos/translations/pl.json @@ -9,7 +9,6 @@ "step": { "user": { "data": { - "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).", diff --git a/homeassistant/components/heos/translations/pt-BR.json b/homeassistant/components/heos/translations/pt-BR.json index abacf5c8ca1..55d7b76d96e 100644 --- a/homeassistant/components/heos/translations/pt-BR.json +++ b/homeassistant/components/heos/translations/pt-BR.json @@ -9,7 +9,6 @@ "step": { "user": { "data": { - "access_token": "Host", "host": "Host" }, "description": "Por favor, digite o nome do host ou o endere\u00e7o IP de um dispositivo Heos (de prefer\u00eancia para conex\u00f5es conectadas por cabo \u00e0 sua rede).", diff --git a/homeassistant/components/heos/translations/pt.json b/homeassistant/components/heos/translations/pt.json index d0c219cefa9..ce7cbc3f548 100644 --- a/homeassistant/components/heos/translations/pt.json +++ b/homeassistant/components/heos/translations/pt.json @@ -3,7 +3,6 @@ "step": { "user": { "data": { - "access_token": "Servidor", "host": "Servidor" } } diff --git a/homeassistant/components/heos/translations/ru.json b/homeassistant/components/heos/translations/ru.json index 9983242b349..b92ea253ebe 100644 --- a/homeassistant/components/heos/translations/ru.json +++ b/homeassistant/components/heos/translations/ru.json @@ -9,7 +9,6 @@ "step": { "user": { "data": { - "access_token": "\u0425\u043e\u0441\u0442", "host": "\u0425\u043e\u0441\u0442" }, "description": "\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0432\u0432\u0435\u0434\u0438\u0442\u0435 \u0438\u043c\u044f \u0445\u043e\u0441\u0442\u0430 \u0438\u043b\u0438 IP-\u0430\u0434\u0440\u0435\u0441 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 HEOS (\u043f\u0440\u0435\u0434\u043f\u043e\u0447\u0442\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a \u0441\u0435\u0442\u0438 \u0447\u0435\u0440\u0435\u0437 \u043a\u0430\u0431\u0435\u043b\u044c).", diff --git a/homeassistant/components/heos/translations/sl.json b/homeassistant/components/heos/translations/sl.json index 76fe3aadc5d..abe3d6dc97a 100644 --- a/homeassistant/components/heos/translations/sl.json +++ b/homeassistant/components/heos/translations/sl.json @@ -9,7 +9,6 @@ "step": { "user": { "data": { - "access_token": "Gostitelj", "host": "Gostitelj" }, "description": "Vnesite ime gostitelja ali naslov IP naprave Heos (po mo\u017enosti eno, ki je z omre\u017ejem povezana \u017ei\u010dno).", diff --git a/homeassistant/components/heos/translations/sv.json b/homeassistant/components/heos/translations/sv.json index 8215388a161..4ade6944e51 100644 --- a/homeassistant/components/heos/translations/sv.json +++ b/homeassistant/components/heos/translations/sv.json @@ -9,7 +9,6 @@ "step": { "user": { "data": { - "access_token": "V\u00e4rd", "host": "V\u00e4rd" }, "description": "Ange v\u00e4rdnamnet eller IP-adressen f\u00f6r en Heos-enhet (helst en ansluten via kabel till n\u00e4tverket).", diff --git a/homeassistant/components/heos/translations/zh-Hant.json b/homeassistant/components/heos/translations/zh-Hant.json index 01ca002dd54..937a53a1e9f 100644 --- a/homeassistant/components/heos/translations/zh-Hant.json +++ b/homeassistant/components/heos/translations/zh-Hant.json @@ -9,7 +9,6 @@ "step": { "user": { "data": { - "access_token": "\u4e3b\u6a5f\u7aef", "host": "\u4e3b\u6a5f\u7aef" }, "description": "\u8acb\u8f38\u5165\u4e3b\u6a5f\u6bb5\u540d\u7a31\u6216 Heos \u8a2d\u5099 IP \u4f4d\u5740\uff08\u5df2\u900f\u904e\u6709\u7dda\u7db2\u8def\u9023\u7dda\uff09\u3002", diff --git a/homeassistant/components/hisense_aehw4a1/translations/bg.json b/homeassistant/components/hisense_aehw4a1/translations/bg.json index 607347ff9e9..ed54398ecab 100644 --- a/homeassistant/components/hisense_aehw4a1/translations/bg.json +++ b/homeassistant/components/hisense_aehw4a1/translations/bg.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "\u0418\u0441\u043a\u0430\u0442\u0435 \u043b\u0438 \u0434\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u0435 Hisense AEH-W4A1?", - "title": "Hisense AEH-W4A1" + "description": "\u0418\u0441\u043a\u0430\u0442\u0435 \u043b\u0438 \u0434\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u0435 Hisense AEH-W4A1?" } } } diff --git a/homeassistant/components/hisense_aehw4a1/translations/ca.json b/homeassistant/components/hisense_aehw4a1/translations/ca.json index a0aef80e031..3a05c1d6123 100644 --- a/homeassistant/components/hisense_aehw4a1/translations/ca.json +++ b/homeassistant/components/hisense_aehw4a1/translations/ca.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Vols configurar AEH-W4A1 de Hisense?", - "title": "Hisense AEH-W4A1" + "description": "Vols configurar AEH-W4A1 de Hisense?" } } } diff --git a/homeassistant/components/hisense_aehw4a1/translations/da.json b/homeassistant/components/hisense_aehw4a1/translations/da.json index d75ffed4c56..90184302eec 100644 --- a/homeassistant/components/hisense_aehw4a1/translations/da.json +++ b/homeassistant/components/hisense_aehw4a1/translations/da.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Vil du konfigurere Hisense AEH-W4A1?", - "title": "Hisense AEH-W4A1" + "description": "Vil du konfigurere Hisense AEH-W4A1?" } } } diff --git a/homeassistant/components/hisense_aehw4a1/translations/de.json b/homeassistant/components/hisense_aehw4a1/translations/de.json index e42d91082e8..d5f4f429740 100644 --- a/homeassistant/components/hisense_aehw4a1/translations/de.json +++ b/homeassistant/components/hisense_aehw4a1/translations/de.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "M\u00f6chtest du Hisense AEH-W4A1 einrichten?", - "title": "Hisense AEH-W4A1" + "description": "M\u00f6chtest du Hisense AEH-W4A1 einrichten?" } } } diff --git a/homeassistant/components/hisense_aehw4a1/translations/en.json b/homeassistant/components/hisense_aehw4a1/translations/en.json index ca0738ec9a8..4b292600249 100644 --- a/homeassistant/components/hisense_aehw4a1/translations/en.json +++ b/homeassistant/components/hisense_aehw4a1/translations/en.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Do you want to set up Hisense AEH-W4A1?", - "title": "Hisense AEH-W4A1" + "description": "Do you want to set up Hisense AEH-W4A1?" } } } diff --git a/homeassistant/components/hisense_aehw4a1/translations/es-419.json b/homeassistant/components/hisense_aehw4a1/translations/es-419.json index c9c4270360a..1a3a8ca221d 100644 --- a/homeassistant/components/hisense_aehw4a1/translations/es-419.json +++ b/homeassistant/components/hisense_aehw4a1/translations/es-419.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "\u00bfDesea configurar Hisense AEH-W4A1?", - "title": "Hisense AEH-W4A1" + "description": "\u00bfDesea configurar Hisense AEH-W4A1?" } } } diff --git a/homeassistant/components/hisense_aehw4a1/translations/es.json b/homeassistant/components/hisense_aehw4a1/translations/es.json index c9c4270360a..1a3a8ca221d 100644 --- a/homeassistant/components/hisense_aehw4a1/translations/es.json +++ b/homeassistant/components/hisense_aehw4a1/translations/es.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "\u00bfDesea configurar Hisense AEH-W4A1?", - "title": "Hisense AEH-W4A1" + "description": "\u00bfDesea configurar Hisense AEH-W4A1?" } } } diff --git a/homeassistant/components/hisense_aehw4a1/translations/fr.json b/homeassistant/components/hisense_aehw4a1/translations/fr.json index dafe3836a50..7fa1598fa76 100644 --- a/homeassistant/components/hisense_aehw4a1/translations/fr.json +++ b/homeassistant/components/hisense_aehw4a1/translations/fr.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Voulez-vous configurer AEH-W4A1?", - "title": "Hisense AEH-W4A1" + "description": "Voulez-vous configurer AEH-W4A1?" } } } diff --git a/homeassistant/components/hisense_aehw4a1/translations/hu.json b/homeassistant/components/hisense_aehw4a1/translations/hu.json index 389653422fd..0b21d7c4c32 100644 --- a/homeassistant/components/hisense_aehw4a1/translations/hu.json +++ b/homeassistant/components/hisense_aehw4a1/translations/hu.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Szeretn\u00e9 be\u00e1ll\u00edtani Hisense AEH-W4A1-et?", - "title": "Hisense AEH-W4A1" + "description": "Szeretn\u00e9 be\u00e1ll\u00edtani Hisense AEH-W4A1-et?" } } } diff --git a/homeassistant/components/hisense_aehw4a1/translations/it.json b/homeassistant/components/hisense_aehw4a1/translations/it.json index 3d878ed40be..0da43b27a82 100644 --- a/homeassistant/components/hisense_aehw4a1/translations/it.json +++ b/homeassistant/components/hisense_aehw4a1/translations/it.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Voui configurare Hisense AEH-W4A1", - "title": "Hisense AEH-W4A1" + "description": "Voui configurare Hisense AEH-W4A1" } } } diff --git a/homeassistant/components/hisense_aehw4a1/translations/ko.json b/homeassistant/components/hisense_aehw4a1/translations/ko.json index 2c472277b00..27d0ff88f6a 100644 --- a/homeassistant/components/hisense_aehw4a1/translations/ko.json +++ b/homeassistant/components/hisense_aehw4a1/translations/ko.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Hisense AEH-W4A1 \uc744 \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", - "title": "Hisense AEH-W4A1" + "description": "Hisense AEH-W4A1 \uc744 \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" } } } diff --git a/homeassistant/components/hisense_aehw4a1/translations/lb.json b/homeassistant/components/hisense_aehw4a1/translations/lb.json index cdfbb069c0f..d97f9fc2893 100644 --- a/homeassistant/components/hisense_aehw4a1/translations/lb.json +++ b/homeassistant/components/hisense_aehw4a1/translations/lb.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Soll Hisense AEH-W4A1 konfigur\u00e9iert ginn?", - "title": "Hisense AEH-W4A1" + "description": "Soll Hisense AEH-W4A1 konfigur\u00e9iert ginn?" } } } diff --git a/homeassistant/components/hisense_aehw4a1/translations/nl.json b/homeassistant/components/hisense_aehw4a1/translations/nl.json index 9fef289f545..14f2445f63e 100644 --- a/homeassistant/components/hisense_aehw4a1/translations/nl.json +++ b/homeassistant/components/hisense_aehw4a1/translations/nl.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Wilt u Hisense AEH-W4A1 instellen?", - "title": "Hisense AEH-W4A1" + "description": "Wilt u Hisense AEH-W4A1 instellen?" } } } diff --git a/homeassistant/components/hisense_aehw4a1/translations/no.json b/homeassistant/components/hisense_aehw4a1/translations/no.json index bc048ef2286..6a8d6cbe443 100644 --- a/homeassistant/components/hisense_aehw4a1/translations/no.json +++ b/homeassistant/components/hisense_aehw4a1/translations/no.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Vil du sette opp Hisense AEH-W4A1?", - "title": "" + "description": "Vil du sette opp Hisense AEH-W4A1?" } } } diff --git a/homeassistant/components/hisense_aehw4a1/translations/pl.json b/homeassistant/components/hisense_aehw4a1/translations/pl.json index 77e5c6298eb..61f15a99806 100644 --- a/homeassistant/components/hisense_aehw4a1/translations/pl.json +++ b/homeassistant/components/hisense_aehw4a1/translations/pl.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Chcesz skonfigurowa\u0107 AEH-W4A1?", - "title": "Hisense AEH-W4A1" + "description": "Chcesz skonfigurowa\u0107 AEH-W4A1?" } } } diff --git a/homeassistant/components/hisense_aehw4a1/translations/ru.json b/homeassistant/components/hisense_aehw4a1/translations/ru.json index bb406e90f92..0d982c13eb0 100644 --- a/homeassistant/components/hisense_aehw4a1/translations/ru.json +++ b/homeassistant/components/hisense_aehw4a1/translations/ru.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c Hisense AEH-W4A1?", - "title": "Hisense AEH-W4A1" + "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c Hisense AEH-W4A1?" } } } diff --git a/homeassistant/components/hisense_aehw4a1/translations/sl.json b/homeassistant/components/hisense_aehw4a1/translations/sl.json index d24c8398652..daeabc2b84c 100644 --- a/homeassistant/components/hisense_aehw4a1/translations/sl.json +++ b/homeassistant/components/hisense_aehw4a1/translations/sl.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Ali \u017eelite nastaviti Hisense AEH-W4A1?", - "title": "Hisense AEH-W4A1" + "description": "Ali \u017eelite nastaviti Hisense AEH-W4A1?" } } } diff --git a/homeassistant/components/hisense_aehw4a1/translations/sv.json b/homeassistant/components/hisense_aehw4a1/translations/sv.json index 01d484075e0..84798e81fa9 100644 --- a/homeassistant/components/hisense_aehw4a1/translations/sv.json +++ b/homeassistant/components/hisense_aehw4a1/translations/sv.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Vill du konfigurera Hisense AEH-W4A1?", - "title": "Hisense AEH-W4A1" + "description": "Vill du konfigurera Hisense AEH-W4A1?" } } } diff --git a/homeassistant/components/hisense_aehw4a1/translations/zh-Hant.json b/homeassistant/components/hisense_aehw4a1/translations/zh-Hant.json index 44feda4fffc..296fbb5d9d5 100644 --- a/homeassistant/components/hisense_aehw4a1/translations/zh-Hant.json +++ b/homeassistant/components/hisense_aehw4a1/translations/zh-Hant.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "\u662f\u5426\u8981\u8a2d\u5b9a\u6d77\u4fe1 AEH-W4A1\uff1f", - "title": "\u6d77\u4fe1 AEH-W4A1" + "description": "\u662f\u5426\u8981\u8a2d\u5b9a\u6d77\u4fe1 AEH-W4A1\uff1f" } } } diff --git a/homeassistant/components/icloud/translations/lb.json b/homeassistant/components/icloud/translations/lb.json index 9e923d6ffd1..b6e9abe94bc 100644 --- a/homeassistant/components/icloud/translations/lb.json +++ b/homeassistant/components/icloud/translations/lb.json @@ -20,6 +20,7 @@ "user": { "data": { "password": "Passwuert", + "username": "E-Mail", "with_family": "Mat der Famill" }, "description": "F\u00ebllt \u00e4r Umeldungs Informatiounen aus", diff --git a/homeassistant/components/ios/translations/bg.json b/homeassistant/components/ios/translations/bg.json index 69e1523ac1f..3160ff48272 100644 --- a/homeassistant/components/ios/translations/bg.json +++ b/homeassistant/components/ios/translations/bg.json @@ -5,8 +5,7 @@ }, "step": { "confirm": { - "description": "\u0418\u0441\u043a\u0430\u0442\u0435 \u043b\u0438 \u0434\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u0435 Home Assistant iOS \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430?", - "title": "Home Assistant iOS" + "description": "\u0418\u0441\u043a\u0430\u0442\u0435 \u043b\u0438 \u0434\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u0435 Home Assistant iOS \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430?" } } } diff --git a/homeassistant/components/ios/translations/ca.json b/homeassistant/components/ios/translations/ca.json index 3f16dbc2204..4b90303b435 100644 --- a/homeassistant/components/ios/translations/ca.json +++ b/homeassistant/components/ios/translations/ca.json @@ -5,8 +5,7 @@ }, "step": { "confirm": { - "description": "Vols configurar el component Home Assistant iOS?", - "title": "Home Assistant iOS" + "description": "Vols configurar el component Home Assistant iOS?" } } } diff --git a/homeassistant/components/ios/translations/cs.json b/homeassistant/components/ios/translations/cs.json index afb0307492d..a215b9fe13c 100644 --- a/homeassistant/components/ios/translations/cs.json +++ b/homeassistant/components/ios/translations/cs.json @@ -5,8 +5,7 @@ }, "step": { "confirm": { - "description": "Chcete nastavit komponenty Home Assistant iOS?", - "title": "Home Assistant iOS" + "description": "Chcete nastavit komponenty Home Assistant iOS?" } } } diff --git a/homeassistant/components/ios/translations/da.json b/homeassistant/components/ios/translations/da.json index 37db62d1205..046874653da 100644 --- a/homeassistant/components/ios/translations/da.json +++ b/homeassistant/components/ios/translations/da.json @@ -5,8 +5,7 @@ }, "step": { "confirm": { - "description": "Er du sikker p\u00e5 at du vil konfigurere Home Assistant iOS?", - "title": "Home Assistant iOS" + "description": "Er du sikker p\u00e5 at du vil konfigurere Home Assistant iOS?" } } } diff --git a/homeassistant/components/ios/translations/de.json b/homeassistant/components/ios/translations/de.json index 337ed3ad7c0..e9e592d18c2 100644 --- a/homeassistant/components/ios/translations/de.json +++ b/homeassistant/components/ios/translations/de.json @@ -5,8 +5,7 @@ }, "step": { "confirm": { - "description": "M\u00f6chtest du die Home Assistant iOS-Komponente einrichten?", - "title": "Home Assistant iOS" + "description": "M\u00f6chtest du die Home Assistant iOS-Komponente einrichten?" } } } diff --git a/homeassistant/components/ios/translations/en.json b/homeassistant/components/ios/translations/en.json index 6352142e717..46857921642 100644 --- a/homeassistant/components/ios/translations/en.json +++ b/homeassistant/components/ios/translations/en.json @@ -5,8 +5,7 @@ }, "step": { "confirm": { - "description": "Do you want to set up the Home Assistant iOS component?", - "title": "Home Assistant iOS" + "description": "Do you want to set up the Home Assistant iOS component?" } } } diff --git a/homeassistant/components/ios/translations/es-419.json b/homeassistant/components/ios/translations/es-419.json index 5938e27930e..a359cc3ae82 100644 --- a/homeassistant/components/ios/translations/es-419.json +++ b/homeassistant/components/ios/translations/es-419.json @@ -5,8 +5,7 @@ }, "step": { "confirm": { - "description": "\u00bfDesea configurar el componente iOS de Home Assistant?", - "title": "Home Assistant iOS" + "description": "\u00bfDesea configurar el componente iOS de Home Assistant?" } } } diff --git a/homeassistant/components/ios/translations/es.json b/homeassistant/components/ios/translations/es.json index 9e94d536718..e66c48a2355 100644 --- a/homeassistant/components/ios/translations/es.json +++ b/homeassistant/components/ios/translations/es.json @@ -5,8 +5,7 @@ }, "step": { "confirm": { - "description": "\u00bfDesea configurar el componente iOS de Home Assistant?", - "title": "Home Assistant iOS" + "description": "\u00bfDesea configurar el componente iOS de Home Assistant?" } } } diff --git a/homeassistant/components/ios/translations/fr.json b/homeassistant/components/ios/translations/fr.json index 02fb127e363..a6318718f94 100644 --- a/homeassistant/components/ios/translations/fr.json +++ b/homeassistant/components/ios/translations/fr.json @@ -5,8 +5,7 @@ }, "step": { "confirm": { - "description": "Voulez-vous configurer le composant Home Assistant iOS?", - "title": "Home Assistant iOS" + "description": "Voulez-vous configurer le composant Home Assistant iOS?" } } } diff --git a/homeassistant/components/ios/translations/he.json b/homeassistant/components/ios/translations/he.json index fd0c5c7c470..deb8eae6b38 100644 --- a/homeassistant/components/ios/translations/he.json +++ b/homeassistant/components/ios/translations/he.json @@ -5,8 +5,7 @@ }, "step": { "confirm": { - "description": "\u05d4\u05d0\u05dd \u05d1\u05e8\u05e6\u05d5\u05e0\u05da \u05dc\u05d4\u05d2\u05d3\u05d9\u05e8 \u05d0\u05ea Home Assistant iOS?", - "title": "Home Assistant iOS" + "description": "\u05d4\u05d0\u05dd \u05d1\u05e8\u05e6\u05d5\u05e0\u05da \u05dc\u05d4\u05d2\u05d3\u05d9\u05e8 \u05d0\u05ea Home Assistant iOS?" } } } diff --git a/homeassistant/components/ios/translations/hu.json b/homeassistant/components/ios/translations/hu.json index 77bbc56e392..f716fd36e9a 100644 --- a/homeassistant/components/ios/translations/hu.json +++ b/homeassistant/components/ios/translations/hu.json @@ -5,8 +5,7 @@ }, "step": { "confirm": { - "description": "Be szeretn\u00e9d \u00e1ll\u00edtani a Home Assistant iOS komponenst?", - "title": "Home Assistant iOS" + "description": "Be szeretn\u00e9d \u00e1ll\u00edtani a Home Assistant iOS komponenst?" } } } diff --git a/homeassistant/components/ios/translations/id.json b/homeassistant/components/ios/translations/id.json index 1c69ea0a48a..46449f1c044 100644 --- a/homeassistant/components/ios/translations/id.json +++ b/homeassistant/components/ios/translations/id.json @@ -5,8 +5,7 @@ }, "step": { "confirm": { - "description": "Apakah Anda ingin mengatur komponen iOS Home Assistant?", - "title": "Home Asisten iOS" + "description": "Apakah Anda ingin mengatur komponen iOS Home Assistant?" } } } diff --git a/homeassistant/components/ios/translations/it.json b/homeassistant/components/ios/translations/it.json index e41382ea62e..763293d6b54 100644 --- a/homeassistant/components/ios/translations/it.json +++ b/homeassistant/components/ios/translations/it.json @@ -5,8 +5,7 @@ }, "step": { "confirm": { - "description": "Vuoi configurare il componente Home Assistant iOS?", - "title": "Home Assistant iOS" + "description": "Vuoi configurare il componente Home Assistant iOS?" } } } diff --git a/homeassistant/components/ios/translations/ko.json b/homeassistant/components/ios/translations/ko.json index fdd036f26a8..6abe9380473 100644 --- a/homeassistant/components/ios/translations/ko.json +++ b/homeassistant/components/ios/translations/ko.json @@ -5,8 +5,7 @@ }, "step": { "confirm": { - "description": "Home Assistant iOS \ucef4\ud3ec\ub10c\ud2b8\ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", - "title": "Home Assistant iOS" + "description": "Home Assistant iOS \ucef4\ud3ec\ub10c\ud2b8\ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" } } } diff --git a/homeassistant/components/ios/translations/lb.json b/homeassistant/components/ios/translations/lb.json index bb6ec7b9f8a..85d78613d55 100644 --- a/homeassistant/components/ios/translations/lb.json +++ b/homeassistant/components/ios/translations/lb.json @@ -5,8 +5,7 @@ }, "step": { "confirm": { - "description": "W\u00ebllt dir d'Home Assistant iOS Komponent ariichten?", - "title": "Home Assistant iOS" + "description": "W\u00ebllt dir d'Home Assistant iOS Komponent ariichten?" } } } diff --git a/homeassistant/components/ios/translations/nl.json b/homeassistant/components/ios/translations/nl.json index 34050865768..0575b4558af 100644 --- a/homeassistant/components/ios/translations/nl.json +++ b/homeassistant/components/ios/translations/nl.json @@ -5,8 +5,7 @@ }, "step": { "confirm": { - "description": "Wilt u het Home Assistant iOS component instellen?", - "title": "Home Assistant iOS" + "description": "Wilt u het Home Assistant iOS component instellen?" } } } diff --git a/homeassistant/components/ios/translations/nn.json b/homeassistant/components/ios/translations/nn.json index 2fc5a3f1c52..5fb94712039 100644 --- a/homeassistant/components/ios/translations/nn.json +++ b/homeassistant/components/ios/translations/nn.json @@ -5,8 +5,7 @@ }, "step": { "confirm": { - "description": "Vil du sette opp Home Assistant iOS-komponenten?", - "title": "Home Assistant Ios" + "description": "Vil du sette opp Home Assistant iOS-komponenten?" } } } diff --git a/homeassistant/components/ios/translations/no.json b/homeassistant/components/ios/translations/no.json index 2814d73f555..1b8dd7b3c8e 100644 --- a/homeassistant/components/ios/translations/no.json +++ b/homeassistant/components/ios/translations/no.json @@ -5,8 +5,7 @@ }, "step": { "confirm": { - "description": "\u00d8nsker du \u00e5 sette opp Home Assistant iOS-komponenten?", - "title": "" + "description": "\u00d8nsker du \u00e5 sette opp Home Assistant iOS-komponenten?" } } } diff --git a/homeassistant/components/ios/translations/pl.json b/homeassistant/components/ios/translations/pl.json index 58b02fe8cad..4defef3926c 100644 --- a/homeassistant/components/ios/translations/pl.json +++ b/homeassistant/components/ios/translations/pl.json @@ -5,8 +5,7 @@ }, "step": { "confirm": { - "description": "Czy chcesz skonfigurowa\u0107 Home Assistant iOS?", - "title": "Home Assistant iOS" + "description": "Czy chcesz skonfigurowa\u0107 Home Assistant iOS?" } } } diff --git a/homeassistant/components/ios/translations/pt-BR.json b/homeassistant/components/ios/translations/pt-BR.json index 8eee79de746..fffbfae2249 100644 --- a/homeassistant/components/ios/translations/pt-BR.json +++ b/homeassistant/components/ios/translations/pt-BR.json @@ -5,8 +5,7 @@ }, "step": { "confirm": { - "description": "Deseja configurar o componente iOS do Home Assistant?", - "title": "Home Assistant iOS" + "description": "Deseja configurar o componente iOS do Home Assistant?" } } } diff --git a/homeassistant/components/ios/translations/pt.json b/homeassistant/components/ios/translations/pt.json index 52a1a66ba32..319ba1e3759 100644 --- a/homeassistant/components/ios/translations/pt.json +++ b/homeassistant/components/ios/translations/pt.json @@ -5,8 +5,7 @@ }, "step": { "confirm": { - "description": "Deseja configurar o componente iOS do Home Assistant?", - "title": "Home Assistant iOS" + "description": "Deseja configurar o componente iOS do Home Assistant?" } } } diff --git a/homeassistant/components/ios/translations/ro.json b/homeassistant/components/ios/translations/ro.json index b4b13d3955a..0cf0f56f68f 100644 --- a/homeassistant/components/ios/translations/ro.json +++ b/homeassistant/components/ios/translations/ro.json @@ -5,8 +5,7 @@ }, "step": { "confirm": { - "description": "Dori\u021bi s\u0103 configura\u021bi componenta Home Assistant iOS?", - "title": "Home Assistant iOS" + "description": "Dori\u021bi s\u0103 configura\u021bi componenta Home Assistant iOS?" } } } diff --git a/homeassistant/components/ios/translations/ru.json b/homeassistant/components/ios/translations/ru.json index d19343acc89..11df628045d 100644 --- a/homeassistant/components/ios/translations/ru.json +++ b/homeassistant/components/ios/translations/ru.json @@ -5,8 +5,7 @@ }, "step": { "confirm": { - "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c Home Assistant iOS?", - "title": "Home Assistant iOS" + "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c Home Assistant iOS?" } } } diff --git a/homeassistant/components/ios/translations/sl.json b/homeassistant/components/ios/translations/sl.json index e0958432a6e..8e104033bd3 100644 --- a/homeassistant/components/ios/translations/sl.json +++ b/homeassistant/components/ios/translations/sl.json @@ -5,8 +5,7 @@ }, "step": { "confirm": { - "description": "Ali \u017eelite nastaviti komponento za Home Assistant iOS?", - "title": "Home Assistant iOS" + "description": "Ali \u017eelite nastaviti komponento za Home Assistant iOS?" } } } diff --git a/homeassistant/components/ios/translations/sv.json b/homeassistant/components/ios/translations/sv.json index 03d91e02193..f2f0791b9c2 100644 --- a/homeassistant/components/ios/translations/sv.json +++ b/homeassistant/components/ios/translations/sv.json @@ -5,8 +5,7 @@ }, "step": { "confirm": { - "description": "Vill du konfigurera Home Assistants iOS komponent?", - "title": "Home Assistant iOS" + "description": "Vill du konfigurera Home Assistants iOS komponent?" } } } diff --git a/homeassistant/components/ios/translations/zh-Hans.json b/homeassistant/components/ios/translations/zh-Hans.json index 8e659dbadc1..5c5c29a41d5 100644 --- a/homeassistant/components/ios/translations/zh-Hans.json +++ b/homeassistant/components/ios/translations/zh-Hans.json @@ -5,8 +5,7 @@ }, "step": { "confirm": { - "description": "\u662f\u5426\u8981\u8bbe\u7f6e Home Assistant iOS \u7ec4\u4ef6\uff1f", - "title": "Home Assistant iOS" + "description": "\u662f\u5426\u8981\u8bbe\u7f6e Home Assistant iOS \u7ec4\u4ef6\uff1f" } } } diff --git a/homeassistant/components/ios/translations/zh-Hant.json b/homeassistant/components/ios/translations/zh-Hant.json index ba85332cb7f..8ca797ecf84 100644 --- a/homeassistant/components/ios/translations/zh-Hant.json +++ b/homeassistant/components/ios/translations/zh-Hant.json @@ -5,8 +5,7 @@ }, "step": { "confirm": { - "description": "\u662f\u5426\u8981\u8a2d\u5b9a Home Assistant iOS \u5143\u4ef6\uff1f", - "title": "Home Assistant iOS" + "description": "\u662f\u5426\u8981\u8a2d\u5b9a Home Assistant iOS \u5143\u4ef6\uff1f" } } } diff --git a/homeassistant/components/ipp/translations/no.json b/homeassistant/components/ipp/translations/no.json index 59565eb0053..c031864cf4d 100644 --- a/homeassistant/components/ipp/translations/no.json +++ b/homeassistant/components/ipp/translations/no.json @@ -27,7 +27,7 @@ "title": "Koble til skriveren din" }, "zeroconf_confirm": { - "description": "\u00d8nsker du \u00e5 legge skriveren med navnet {name} til Home Assistant?", + "description": "Vil du konfigurere {name}?", "title": "Oppdaget skriver" } } diff --git a/homeassistant/components/izone/translations/bg.json b/homeassistant/components/izone/translations/bg.json index 2aed0f309b4..d9866ef1d13 100644 --- a/homeassistant/components/izone/translations/bg.json +++ b/homeassistant/components/izone/translations/bg.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "\u0418\u0441\u043a\u0430\u0442\u0435 \u043b\u0438 \u0434\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u0435 iZone?", - "title": "iZone" + "description": "\u0418\u0441\u043a\u0430\u0442\u0435 \u043b\u0438 \u0434\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u0435 iZone?" } } } diff --git a/homeassistant/components/izone/translations/ca.json b/homeassistant/components/izone/translations/ca.json index 016e0fb070e..811b3cc8c29 100644 --- a/homeassistant/components/izone/translations/ca.json +++ b/homeassistant/components/izone/translations/ca.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Vols configurar iZone?", - "title": "iZone" + "description": "Vols configurar iZone?" } } } diff --git a/homeassistant/components/izone/translations/da.json b/homeassistant/components/izone/translations/da.json index 6c08029d9e0..f6343f917f8 100644 --- a/homeassistant/components/izone/translations/da.json +++ b/homeassistant/components/izone/translations/da.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Vil du konfigurere iZone?", - "title": "iZone" + "description": "Vil du konfigurere iZone?" } } } diff --git a/homeassistant/components/izone/translations/de.json b/homeassistant/components/izone/translations/de.json index 47032413607..ea59cc39b27 100644 --- a/homeassistant/components/izone/translations/de.json +++ b/homeassistant/components/izone/translations/de.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "M\u00f6chtest du iZone einrichten?", - "title": "iZone" + "description": "M\u00f6chtest du iZone einrichten?" } } } diff --git a/homeassistant/components/izone/translations/en.json b/homeassistant/components/izone/translations/en.json index 7c9b7008f24..03cc003752b 100644 --- a/homeassistant/components/izone/translations/en.json +++ b/homeassistant/components/izone/translations/en.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Do you want to set up iZone?", - "title": "iZone" + "description": "Do you want to set up iZone?" } } } diff --git a/homeassistant/components/izone/translations/es-419.json b/homeassistant/components/izone/translations/es-419.json index b645c427e3e..6b346f7a962 100644 --- a/homeassistant/components/izone/translations/es-419.json +++ b/homeassistant/components/izone/translations/es-419.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "\u00bfDesea configurar iZone?", - "title": "iZone" + "description": "\u00bfDesea configurar iZone?" } } } diff --git a/homeassistant/components/izone/translations/es.json b/homeassistant/components/izone/translations/es.json index 664cc72d96f..0ae7e16a512 100644 --- a/homeassistant/components/izone/translations/es.json +++ b/homeassistant/components/izone/translations/es.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "\u00bfQuieres configurar iZone?", - "title": "iZone" + "description": "\u00bfQuieres configurar iZone?" } } } diff --git a/homeassistant/components/izone/translations/fr.json b/homeassistant/components/izone/translations/fr.json index 9fb3163ae15..0c6faf83e6e 100644 --- a/homeassistant/components/izone/translations/fr.json +++ b/homeassistant/components/izone/translations/fr.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Voulez-vous configurer iZone?", - "title": "iZone" + "description": "Voulez-vous configurer iZone?" } } } diff --git a/homeassistant/components/izone/translations/hu.json b/homeassistant/components/izone/translations/hu.json index 76b88ebb6b4..026093232a3 100644 --- a/homeassistant/components/izone/translations/hu.json +++ b/homeassistant/components/izone/translations/hu.json @@ -2,8 +2,7 @@ "config": { "step": { "confirm": { - "description": "Szeretn\u00e9 be\u00e1ll\u00edtani az iZone-t?", - "title": "iZone" + "description": "Szeretn\u00e9 be\u00e1ll\u00edtani az iZone-t?" } } } diff --git a/homeassistant/components/izone/translations/it.json b/homeassistant/components/izone/translations/it.json index 8fd2930e8ff..79151ca44a4 100644 --- a/homeassistant/components/izone/translations/it.json +++ b/homeassistant/components/izone/translations/it.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Vuoi configurare iZone?", - "title": "iZone" + "description": "Vuoi configurare iZone?" } } } diff --git a/homeassistant/components/izone/translations/ko.json b/homeassistant/components/izone/translations/ko.json index 4174226ab55..85aec276562 100644 --- a/homeassistant/components/izone/translations/ko.json +++ b/homeassistant/components/izone/translations/ko.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "iZone \uc744 \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", - "title": "iZone" + "description": "iZone \uc744 \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" } } } diff --git a/homeassistant/components/izone/translations/lb.json b/homeassistant/components/izone/translations/lb.json index 5b0576a12eb..2c8f2573e75 100644 --- a/homeassistant/components/izone/translations/lb.json +++ b/homeassistant/components/izone/translations/lb.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Soll iZone konfigur\u00e9iert ginn?", - "title": "iZone" + "description": "Soll iZone konfigur\u00e9iert ginn?" } } } diff --git a/homeassistant/components/izone/translations/nl.json b/homeassistant/components/izone/translations/nl.json index 21dfa2f9ad4..22d1a3c4963 100644 --- a/homeassistant/components/izone/translations/nl.json +++ b/homeassistant/components/izone/translations/nl.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Wilt u iZone instellen?", - "title": "iZone" + "description": "Wilt u iZone instellen?" } } } diff --git a/homeassistant/components/izone/translations/no.json b/homeassistant/components/izone/translations/no.json index 854805948ee..5c3e4248339 100644 --- a/homeassistant/components/izone/translations/no.json +++ b/homeassistant/components/izone/translations/no.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Vil du \u00e5 sette opp iZone?", - "title": "" + "description": "Vil du \u00e5 sette opp iZone?" } } } diff --git a/homeassistant/components/izone/translations/pl.json b/homeassistant/components/izone/translations/pl.json index 2d0bcdd3292..de8eff21232 100644 --- a/homeassistant/components/izone/translations/pl.json +++ b/homeassistant/components/izone/translations/pl.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Chcesz skonfigurowa\u0107 iZone?", - "title": "iZone" + "description": "Chcesz skonfigurowa\u0107 iZone?" } } } diff --git a/homeassistant/components/izone/translations/ru.json b/homeassistant/components/izone/translations/ru.json index 41c39c32c68..23b0a85370c 100644 --- a/homeassistant/components/izone/translations/ru.json +++ b/homeassistant/components/izone/translations/ru.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c iZone?", - "title": "iZone" + "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c iZone?" } } } diff --git a/homeassistant/components/izone/translations/sl.json b/homeassistant/components/izone/translations/sl.json index 2e05823ebe6..6ce860a74af 100644 --- a/homeassistant/components/izone/translations/sl.json +++ b/homeassistant/components/izone/translations/sl.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Ali \u017eelite nastaviti iZone?", - "title": "iZone" + "description": "Ali \u017eelite nastaviti iZone?" } } } diff --git a/homeassistant/components/izone/translations/sv.json b/homeassistant/components/izone/translations/sv.json index fce7cd70960..473f70d6f94 100644 --- a/homeassistant/components/izone/translations/sv.json +++ b/homeassistant/components/izone/translations/sv.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Vill du konfigurera iZone?", - "title": "iZone" + "description": "Vill du konfigurera iZone?" } } } diff --git a/homeassistant/components/izone/translations/zh-Hant.json b/homeassistant/components/izone/translations/zh-Hant.json index 7f7bb327ea9..283136b5c86 100644 --- a/homeassistant/components/izone/translations/zh-Hant.json +++ b/homeassistant/components/izone/translations/zh-Hant.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "\u662f\u5426\u8981\u8a2d\u5b9a iZone\uff1f", - "title": "iZone" + "description": "\u662f\u5426\u8981\u8a2d\u5b9a iZone\uff1f" } } } diff --git a/homeassistant/components/konnected/translations/ca.json b/homeassistant/components/konnected/translations/ca.json index 0740d091c94..86ef5ab5fc6 100644 --- a/homeassistant/components/konnected/translations/ca.json +++ b/homeassistant/components/konnected/translations/ca.json @@ -23,8 +23,7 @@ "host": "Adre\u00e7a IP", "port": "Port" }, - "description": "Introdueix la informaci\u00f3 d'amfitri\u00f3 del panell Konnected.", - "title": "Descoberta de dispositiu Konnected" + "description": "Introdueix la informaci\u00f3 d'amfitri\u00f3 del panell Konnected." } } }, diff --git a/homeassistant/components/konnected/translations/da.json b/homeassistant/components/konnected/translations/da.json index 48751d0d8dc..e8ff101a25b 100644 --- a/homeassistant/components/konnected/translations/da.json +++ b/homeassistant/components/konnected/translations/da.json @@ -23,8 +23,7 @@ "host": "Konnected-enhedens IP-adresse", "port": "Konnected-enhedsport" }, - "description": "Indtast v\u00e6rtsinformationen for dit Konnected-panel.", - "title": "Find Konnected-enhed" + "description": "Indtast v\u00e6rtsinformationen for dit Konnected-panel." } } }, diff --git a/homeassistant/components/konnected/translations/de.json b/homeassistant/components/konnected/translations/de.json index fd88b143914..3fd857851fb 100644 --- a/homeassistant/components/konnected/translations/de.json +++ b/homeassistant/components/konnected/translations/de.json @@ -23,8 +23,7 @@ "host": "Konnected Ger\u00e4t IP-Adresse", "port": "Konnected Device Port" }, - "description": "Bitte geben Sie die Hostinformationen f\u00fcr Ihr Konnected Panel ein.", - "title": "Entdecken Sie Konnected Ger\u00e4t" + "description": "Bitte geben Sie die Hostinformationen f\u00fcr Ihr Konnected Panel ein." } } }, diff --git a/homeassistant/components/konnected/translations/en.json b/homeassistant/components/konnected/translations/en.json index 694255903c0..12ddb5d3cb1 100644 --- a/homeassistant/components/konnected/translations/en.json +++ b/homeassistant/components/konnected/translations/en.json @@ -23,8 +23,7 @@ "host": "IP address", "port": "Port" }, - "description": "Please enter the host information for your Konnected Panel.", - "title": "Discover Konnected Device" + "description": "Please enter the host information for your Konnected Panel." } } }, diff --git a/homeassistant/components/konnected/translations/es-419.json b/homeassistant/components/konnected/translations/es-419.json index a63ba501960..b333b527fdb 100644 --- a/homeassistant/components/konnected/translations/es-419.json +++ b/homeassistant/components/konnected/translations/es-419.json @@ -23,8 +23,7 @@ "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" + "description": "Ingrese la informaci\u00f3n del host para su Panel Konnected." } } }, diff --git a/homeassistant/components/konnected/translations/es.json b/homeassistant/components/konnected/translations/es.json index 05e3fa3368c..eae14b2ca1a 100644 --- a/homeassistant/components/konnected/translations/es.json +++ b/homeassistant/components/konnected/translations/es.json @@ -23,8 +23,7 @@ "host": "Direcci\u00f3n IP del dispositivo Konnected", "port": "Puerto del dispositivo Konnected" }, - "description": "Introduzca la informaci\u00f3n del host de su panel Konnected.", - "title": "Descubrir el dispositivo Konnected" + "description": "Introduzca la informaci\u00f3n del host de su panel Konnected." } } }, diff --git a/homeassistant/components/konnected/translations/fr.json b/homeassistant/components/konnected/translations/fr.json index 23330311f74..c9ab6062daa 100644 --- a/homeassistant/components/konnected/translations/fr.json +++ b/homeassistant/components/konnected/translations/fr.json @@ -23,8 +23,7 @@ "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" + "description": "Veuillez saisir les informations de l\u2019h\u00f4te de votre panneau Konnected." } } }, diff --git a/homeassistant/components/konnected/translations/it.json b/homeassistant/components/konnected/translations/it.json index 684e561156b..bdef475dc6e 100644 --- a/homeassistant/components/konnected/translations/it.json +++ b/homeassistant/components/konnected/translations/it.json @@ -23,8 +23,7 @@ "host": "Indirizzo IP", "port": "Porta" }, - "description": "Si prega di inserire le informazioni dell'host per il tuo Pannello Konnected.", - "title": "Rileva il dispositivo Konnected" + "description": "Si prega di inserire le informazioni dell'host per il tuo Pannello Konnected." } } }, diff --git a/homeassistant/components/konnected/translations/ko.json b/homeassistant/components/konnected/translations/ko.json index ba36f231141..811c134255f 100644 --- a/homeassistant/components/konnected/translations/ko.json +++ b/homeassistant/components/konnected/translations/ko.json @@ -23,8 +23,7 @@ "host": "IP \uc8fc\uc18c", "port": "\ud3ec\ud2b8" }, - "description": "Konnected \ud328\ub110\uc758 \ud638\uc2a4\ud2b8 \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694.", - "title": "Konnected \uae30\uae30 \ucc3e\uae30" + "description": "Konnected \ud328\ub110\uc758 \ud638\uc2a4\ud2b8 \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694." } } }, diff --git a/homeassistant/components/konnected/translations/lb.json b/homeassistant/components/konnected/translations/lb.json index 7e8ed675992..1ef36601d04 100644 --- a/homeassistant/components/konnected/translations/lb.json +++ b/homeassistant/components/konnected/translations/lb.json @@ -23,8 +23,7 @@ "host": "Konnected Apparat IP Adress", "port": "Konnected Apparat Port" }, - "description": "Informatioune vum Konnected Panel aginn.", - "title": "Konnected Apparat entdecken" + "description": "Informatioune vum Konnected Panel aginn." } } }, diff --git a/homeassistant/components/konnected/translations/nl.json b/homeassistant/components/konnected/translations/nl.json index 17bb20be765..dcb5f1ed6c4 100644 --- a/homeassistant/components/konnected/translations/nl.json +++ b/homeassistant/components/konnected/translations/nl.json @@ -17,8 +17,7 @@ "host": "IP-adres van Konnected apparaat", "port": "Konnected apparaat poort" }, - "description": "Voer de host-informatie in voor uw Konnected-paneel.", - "title": "Ontdek Konnected Device" + "description": "Voer de host-informatie in voor uw Konnected-paneel." } } }, diff --git a/homeassistant/components/konnected/translations/no.json b/homeassistant/components/konnected/translations/no.json index b0504c4e3aa..391bf848673 100644 --- a/homeassistant/components/konnected/translations/no.json +++ b/homeassistant/components/konnected/translations/no.json @@ -23,8 +23,7 @@ "host": "Konnected enhet IP-adresse", "port": "Koblet enhetsport" }, - "description": "Vennligst skriv inn verten informasjon for din Konnected Panel.", - "title": "Oppdag Konnected Enheten" + "description": "Vennligst skriv inn verten informasjon for din Konnected Panel." } } }, diff --git a/homeassistant/components/konnected/translations/pl.json b/homeassistant/components/konnected/translations/pl.json index f4a80b60bdf..1f9b60bbae3 100644 --- a/homeassistant/components/konnected/translations/pl.json +++ b/homeassistant/components/konnected/translations/pl.json @@ -23,8 +23,7 @@ "host": "Adres IP", "port": "Port" }, - "description": "Wprowad\u017a informacje o ho\u015bcie panelu Konnected.", - "title": "Wykryj urz\u0105dzenie Konnected" + "description": "Wprowad\u017a informacje o ho\u015bcie panelu Konnected." } } }, diff --git a/homeassistant/components/konnected/translations/pt-BR.json b/homeassistant/components/konnected/translations/pt-BR.json index 501d685e3cd..b31bd6feb8a 100644 --- a/homeassistant/components/konnected/translations/pt-BR.json +++ b/homeassistant/components/konnected/translations/pt-BR.json @@ -2,8 +2,7 @@ "config": { "step": { "user": { - "description": "Por favor, digite as informa\u00e7\u00f5es do host para o seu Painel Konnected.", - "title": "Descubra o dispositivo Konnected" + "description": "Por favor, digite as informa\u00e7\u00f5es do host para o seu Painel Konnected." } } }, diff --git a/homeassistant/components/konnected/translations/ru.json b/homeassistant/components/konnected/translations/ru.json index 75ee7761d55..d3c2816963c 100644 --- a/homeassistant/components/konnected/translations/ru.json +++ b/homeassistant/components/konnected/translations/ru.json @@ -23,8 +23,7 @@ "host": "IP-\u0430\u0434\u0440\u0435\u0441", "port": "\u041f\u043e\u0440\u0442" }, - "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438 \u043a \u043f\u0430\u043d\u0435\u043b\u0438 Konnected.", - "title": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e Konnected" + "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438 \u043a \u043f\u0430\u043d\u0435\u043b\u0438 Konnected." } } }, diff --git a/homeassistant/components/konnected/translations/sl.json b/homeassistant/components/konnected/translations/sl.json index 0a6a52d2fb1..88e0b696416 100644 --- a/homeassistant/components/konnected/translations/sl.json +++ b/homeassistant/components/konnected/translations/sl.json @@ -23,8 +23,7 @@ "host": "IP-naslov Konnected naprave", "port": "Vrata Konnected naprave" }, - "description": "Vnesite podatke o gostitelju v svoj Konnected Panel.", - "title": "Odkrijte Konnected napravo" + "description": "Vnesite podatke o gostitelju v svoj Konnected Panel." } } }, diff --git a/homeassistant/components/konnected/translations/sv.json b/homeassistant/components/konnected/translations/sv.json index 3e6136fb2c3..3b875e17738 100644 --- a/homeassistant/components/konnected/translations/sv.json +++ b/homeassistant/components/konnected/translations/sv.json @@ -19,8 +19,7 @@ "host": "Konnected-enhetens IP-adress", "port": "Konnected-enhetens port" }, - "description": "Ange v\u00e4rdinformationen f\u00f6r din Konnected Panel.", - "title": "Uppt\u00e4ck Konnected-enhet" + "description": "Ange v\u00e4rdinformationen f\u00f6r din Konnected Panel." } } }, diff --git a/homeassistant/components/konnected/translations/zh-Hant.json b/homeassistant/components/konnected/translations/zh-Hant.json index 4660e3c2f29..45d57eee3c4 100644 --- a/homeassistant/components/konnected/translations/zh-Hant.json +++ b/homeassistant/components/konnected/translations/zh-Hant.json @@ -23,8 +23,7 @@ "host": "IP \u4f4d\u5740", "port": "\u901a\u8a0a\u57e0" }, - "description": "\u8acb\u8f38\u5165 Konnected \u9762\u677f\u4e3b\u6a5f\u7aef\u8cc7\u8a0a\u3002", - "title": "\u641c\u7d22 Konnected \u8a2d\u5099" + "description": "\u8acb\u8f38\u5165 Konnected \u9762\u677f\u4e3b\u6a5f\u7aef\u8cc7\u8a0a\u3002" } } }, diff --git a/homeassistant/components/lifx/translations/bg.json b/homeassistant/components/lifx/translations/bg.json index e44a4524eef..e7ce46d836e 100644 --- a/homeassistant/components/lifx/translations/bg.json +++ b/homeassistant/components/lifx/translations/bg.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "\u0418\u0441\u043a\u0430\u0442\u0435 \u043b\u0438 \u0434\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u0435 LIFX?", - "title": "LIFX" + "description": "\u0418\u0441\u043a\u0430\u0442\u0435 \u043b\u0438 \u0434\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u0435 LIFX?" } } } diff --git a/homeassistant/components/lifx/translations/ca.json b/homeassistant/components/lifx/translations/ca.json index a9a45f1f485..edc525a92cb 100644 --- a/homeassistant/components/lifx/translations/ca.json +++ b/homeassistant/components/lifx/translations/ca.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Vols configurar LIFX?", - "title": "LIFX" + "description": "Vols configurar LIFX?" } } } diff --git a/homeassistant/components/lifx/translations/cs.json b/homeassistant/components/lifx/translations/cs.json index 9ebb8c43b42..7deccd0eac8 100644 --- a/homeassistant/components/lifx/translations/cs.json +++ b/homeassistant/components/lifx/translations/cs.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Chcete nastavit LIFX?", - "title": "LIFX" + "description": "Chcete nastavit LIFX?" } } } diff --git a/homeassistant/components/lifx/translations/da.json b/homeassistant/components/lifx/translations/da.json index b6faac51acc..14fbf83cbed 100644 --- a/homeassistant/components/lifx/translations/da.json +++ b/homeassistant/components/lifx/translations/da.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Konfigurer LIFX?", - "title": "LIFX" + "description": "Konfigurer LIFX?" } } } diff --git a/homeassistant/components/lifx/translations/de.json b/homeassistant/components/lifx/translations/de.json index 654fbe82aeb..f88e27ff168 100644 --- a/homeassistant/components/lifx/translations/de.json +++ b/homeassistant/components/lifx/translations/de.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "M\u00f6chtest du LIFX einrichten?", - "title": "LIFX" + "description": "M\u00f6chtest du LIFX einrichten?" } } } diff --git a/homeassistant/components/lifx/translations/en.json b/homeassistant/components/lifx/translations/en.json index fe7daaaed7d..ab4d5458d82 100644 --- a/homeassistant/components/lifx/translations/en.json +++ b/homeassistant/components/lifx/translations/en.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Do you want to set up LIFX?", - "title": "LIFX" + "description": "Do you want to set up LIFX?" } } } diff --git a/homeassistant/components/lifx/translations/es-419.json b/homeassistant/components/lifx/translations/es-419.json index 8676326e995..023cec6a6db 100644 --- a/homeassistant/components/lifx/translations/es-419.json +++ b/homeassistant/components/lifx/translations/es-419.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "\u00bfDesea configurar LIFX?", - "title": "LIFX" + "description": "\u00bfDesea configurar LIFX?" } } } diff --git a/homeassistant/components/lifx/translations/es.json b/homeassistant/components/lifx/translations/es.json index 5ed177125fc..c5b157a1499 100644 --- a/homeassistant/components/lifx/translations/es.json +++ b/homeassistant/components/lifx/translations/es.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "\u00bfQuieres configurar LIFX?", - "title": "LIFX" + "description": "\u00bfQuieres configurar LIFX?" } } } diff --git a/homeassistant/components/lifx/translations/fi.json b/homeassistant/components/lifx/translations/fi.json index 16fffbb2e5c..a92bc699280 100644 --- a/homeassistant/components/lifx/translations/fi.json +++ b/homeassistant/components/lifx/translations/fi.json @@ -2,8 +2,7 @@ "config": { "step": { "confirm": { - "description": "Haluatko m\u00e4\u00e4ritt\u00e4\u00e4 LIFX:n?", - "title": "LIFX" + "description": "Haluatko m\u00e4\u00e4ritt\u00e4\u00e4 LIFX:n?" } } } diff --git a/homeassistant/components/lifx/translations/fr.json b/homeassistant/components/lifx/translations/fr.json index 60dc00a32fe..837ca29a314 100644 --- a/homeassistant/components/lifx/translations/fr.json +++ b/homeassistant/components/lifx/translations/fr.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Voulez-vous configurer LIFX?", - "title": "LIFX" + "description": "Voulez-vous configurer LIFX?" } } } diff --git a/homeassistant/components/lifx/translations/hu.json b/homeassistant/components/lifx/translations/hu.json index 9b8fe3632c2..0b6cdb39fd4 100644 --- a/homeassistant/components/lifx/translations/hu.json +++ b/homeassistant/components/lifx/translations/hu.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Be szeretn\u00e9d \u00e1ll\u00edtani a LIFX-t?", - "title": "LIFX" + "description": "Be szeretn\u00e9d \u00e1ll\u00edtani a LIFX-t?" } } } diff --git a/homeassistant/components/lifx/translations/it.json b/homeassistant/components/lifx/translations/it.json index 380acd0a875..40b4c98f490 100644 --- a/homeassistant/components/lifx/translations/it.json +++ b/homeassistant/components/lifx/translations/it.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Vuoi configurare LIFX?", - "title": "LIFX" + "description": "Vuoi configurare LIFX?" } } } diff --git a/homeassistant/components/lifx/translations/ko.json b/homeassistant/components/lifx/translations/ko.json index 13a5ca08152..040ac405e2d 100644 --- a/homeassistant/components/lifx/translations/ko.json +++ b/homeassistant/components/lifx/translations/ko.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "LIFX \ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", - "title": "LIFX" + "description": "LIFX \ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" } } } diff --git a/homeassistant/components/lifx/translations/lb.json b/homeassistant/components/lifx/translations/lb.json index 8bf15060ed4..0dcc857010c 100644 --- a/homeassistant/components/lifx/translations/lb.json +++ b/homeassistant/components/lifx/translations/lb.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Soll LIFX konfigur\u00e9iert ginn?", - "title": "LIFX" + "description": "Soll LIFX konfigur\u00e9iert ginn?" } } } diff --git a/homeassistant/components/lifx/translations/nl.json b/homeassistant/components/lifx/translations/nl.json index e8a42bd5676..60efcdffa46 100644 --- a/homeassistant/components/lifx/translations/nl.json +++ b/homeassistant/components/lifx/translations/nl.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Wilt u LIFX instellen?", - "title": "LIFX" + "description": "Wilt u LIFX instellen?" } } } diff --git a/homeassistant/components/lifx/translations/no.json b/homeassistant/components/lifx/translations/no.json index e646db4b2ad..708efba9cc7 100644 --- a/homeassistant/components/lifx/translations/no.json +++ b/homeassistant/components/lifx/translations/no.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "\u00d8nsker du \u00e5 sette opp LIFX?", - "title": "" + "description": "\u00d8nsker du \u00e5 sette opp LIFX?" } } } diff --git a/homeassistant/components/lifx/translations/pl.json b/homeassistant/components/lifx/translations/pl.json index 53e623e05df..d24cfd2473e 100644 --- a/homeassistant/components/lifx/translations/pl.json +++ b/homeassistant/components/lifx/translations/pl.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Czy chcesz skonfigurowa\u0107 LIFX?", - "title": "LIFX" + "description": "Czy chcesz skonfigurowa\u0107 LIFX?" } } } diff --git a/homeassistant/components/lifx/translations/pt-BR.json b/homeassistant/components/lifx/translations/pt-BR.json index e95f452c1e4..cf374894623 100644 --- a/homeassistant/components/lifx/translations/pt-BR.json +++ b/homeassistant/components/lifx/translations/pt-BR.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Voc\u00ea quer configurar o LIFX?", - "title": "LIFX" + "description": "Voc\u00ea quer configurar o LIFX?" } } } diff --git a/homeassistant/components/lifx/translations/pt.json b/homeassistant/components/lifx/translations/pt.json index 73fdb846b1e..56064a70e0d 100644 --- a/homeassistant/components/lifx/translations/pt.json +++ b/homeassistant/components/lifx/translations/pt.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Deseja configurar o LIFX?", - "title": "LIFX" + "description": "Deseja configurar o LIFX?" } } } diff --git a/homeassistant/components/lifx/translations/ro.json b/homeassistant/components/lifx/translations/ro.json index 6ef2bd8697c..56e9307a8b3 100644 --- a/homeassistant/components/lifx/translations/ro.json +++ b/homeassistant/components/lifx/translations/ro.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Dori\u021bi s\u0103 configura\u021bi LIFX?", - "title": "LIFX" + "description": "Dori\u021bi s\u0103 configura\u021bi LIFX?" } } } diff --git a/homeassistant/components/lifx/translations/ru.json b/homeassistant/components/lifx/translations/ru.json index a7f6dfbd6a8..9ffd9a95270 100644 --- a/homeassistant/components/lifx/translations/ru.json +++ b/homeassistant/components/lifx/translations/ru.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c LIFX?", - "title": "LIFX" + "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c LIFX?" } } } diff --git a/homeassistant/components/lifx/translations/sl.json b/homeassistant/components/lifx/translations/sl.json index 5bcb8b328c4..dbbe051afd1 100644 --- a/homeassistant/components/lifx/translations/sl.json +++ b/homeassistant/components/lifx/translations/sl.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Ali \u017eelite nastaviti LIFX?", - "title": "LIFX" + "description": "Ali \u017eelite nastaviti LIFX?" } } } diff --git a/homeassistant/components/lifx/translations/sv.json b/homeassistant/components/lifx/translations/sv.json index c85449078de..82a55b48edb 100644 --- a/homeassistant/components/lifx/translations/sv.json +++ b/homeassistant/components/lifx/translations/sv.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Vill du st\u00e4lla in LIFX?", - "title": "LIFX" + "description": "Vill du st\u00e4lla in LIFX?" } } } diff --git a/homeassistant/components/lifx/translations/zh-Hans.json b/homeassistant/components/lifx/translations/zh-Hans.json index 6d30adcd453..bf9b4277312 100644 --- a/homeassistant/components/lifx/translations/zh-Hans.json +++ b/homeassistant/components/lifx/translations/zh-Hans.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "\u60a8\u60f3\u8981\u914d\u7f6e LIFX \u5417\uff1f", - "title": "LIFX" + "description": "\u60a8\u60f3\u8981\u914d\u7f6e LIFX \u5417\uff1f" } } } diff --git a/homeassistant/components/lifx/translations/zh-Hant.json b/homeassistant/components/lifx/translations/zh-Hant.json index fc9e407b4b2..bc20e93c596 100644 --- a/homeassistant/components/lifx/translations/zh-Hant.json +++ b/homeassistant/components/lifx/translations/zh-Hant.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "\u662f\u5426\u8981\u8a2d\u5b9a LIFX\uff1f", - "title": "LIFX" + "description": "\u662f\u5426\u8981\u8a2d\u5b9a LIFX\uff1f" } } } diff --git a/homeassistant/components/lutron_caseta/translations/ca.json b/homeassistant/components/lutron_caseta/translations/ca.json index 40ad8a42f82..4b6be6e3a6e 100644 --- a/homeassistant/components/lutron_caseta/translations/ca.json +++ b/homeassistant/components/lutron_caseta/translations/ca.json @@ -13,6 +13,5 @@ "title": "No s'ha pogut importar la configuraci\u00f3 de l'enlla\u00e7 de Cas\u00e9ta." } } - }, - "title": "Lutron Cas\u00e9ta" + } } \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/translations/en.json b/homeassistant/components/lutron_caseta/translations/en.json index 5397985b936..469bcae37c7 100644 --- a/homeassistant/components/lutron_caseta/translations/en.json +++ b/homeassistant/components/lutron_caseta/translations/en.json @@ -13,6 +13,5 @@ "title": "Failed to import Cas\u00e9ta bridge configuration." } } - }, - "title": "Lutron Cas\u00e9ta" + } } \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/translations/es.json b/homeassistant/components/lutron_caseta/translations/es.json index db3a46f3221..3440342d40f 100644 --- a/homeassistant/components/lutron_caseta/translations/es.json +++ b/homeassistant/components/lutron_caseta/translations/es.json @@ -13,6 +13,5 @@ "title": "Error al importar la configuraci\u00f3n del bridge Cas\u00e9ta." } } - }, - "title": "Lutron Cas\u00e9ta" + } } \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/translations/it.json b/homeassistant/components/lutron_caseta/translations/it.json index a73e72fa1ae..9e4cb9ec02c 100644 --- a/homeassistant/components/lutron_caseta/translations/it.json +++ b/homeassistant/components/lutron_caseta/translations/it.json @@ -13,6 +13,5 @@ "title": "Impossibile importare la configurazione del bridge Cas\u00e9ta." } } - }, - "title": "Lutron Cas\u00e9ta" + } } \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/translations/ko.json b/homeassistant/components/lutron_caseta/translations/ko.json index c1578edeed4..8c5caec998e 100644 --- a/homeassistant/components/lutron_caseta/translations/ko.json +++ b/homeassistant/components/lutron_caseta/translations/ko.json @@ -13,6 +13,5 @@ "title": "Cas\u00e9ta \ube0c\ub9ac\uc9c0 \uad6c\uc131\uc744 \uac00\uc838\uc624\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4." } } - }, - "title": "Lutron Cas\u00e9ta" + } } \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/translations/lb.json b/homeassistant/components/lutron_caseta/translations/lb.json index 90549b6f464..8da7ed29d7f 100644 --- a/homeassistant/components/lutron_caseta/translations/lb.json +++ b/homeassistant/components/lutron_caseta/translations/lb.json @@ -13,6 +13,5 @@ "title": "Feeler beim import vun der Cas\u00e9ta Bridge Konfiguratioun" } } - }, - "title": "Lutron Cas\u00e9ta" + } } \ 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 5d09a7bf9a1..b63e8fb4111 100644 --- a/homeassistant/components/lutron_caseta/translations/no.json +++ b/homeassistant/components/lutron_caseta/translations/no.json @@ -13,6 +13,5 @@ "title": "Kan ikke importere Cas\u00e9ta bridge-konfigurasjon." } } - }, - "title": "" + } } \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/translations/ru.json b/homeassistant/components/lutron_caseta/translations/ru.json index 9b2b56a70e7..f7f566b1f54 100644 --- a/homeassistant/components/lutron_caseta/translations/ru.json +++ b/homeassistant/components/lutron_caseta/translations/ru.json @@ -13,6 +13,5 @@ "title": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0438\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e \u0448\u043b\u044e\u0437\u0430." } } - }, - "title": "Lutron Cas\u00e9ta" + } } \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/translations/zh-Hant.json b/homeassistant/components/lutron_caseta/translations/zh-Hant.json index ae46bc41258..f975949e6e7 100644 --- a/homeassistant/components/lutron_caseta/translations/zh-Hant.json +++ b/homeassistant/components/lutron_caseta/translations/zh-Hant.json @@ -13,6 +13,5 @@ "title": "\u532f\u5165 Cas\u00e9ta bridge \u8a2d\u5b9a\u5931\u6557\u3002" } } - }, - "title": "Lutron Cas\u00e9ta" + } } \ No newline at end of file diff --git a/homeassistant/components/mobile_app/translations/bg.json b/homeassistant/components/mobile_app/translations/bg.json index 487a036e44b..6cbe826000c 100644 --- a/homeassistant/components/mobile_app/translations/bg.json +++ b/homeassistant/components/mobile_app/translations/bg.json @@ -5,8 +5,7 @@ }, "step": { "confirm": { - "description": "\u0418\u0441\u043a\u0430\u0442\u0435 \u043b\u0438 \u0434\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u0435 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \"\u041c\u043e\u0431\u0438\u043b\u043d\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435\"?", - "title": "\u041c\u043e\u0431\u0438\u043b\u043d\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435" + "description": "\u0418\u0441\u043a\u0430\u0442\u0435 \u043b\u0438 \u0434\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u0435 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \"\u041c\u043e\u0431\u0438\u043b\u043d\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435\"?" } } } diff --git a/homeassistant/components/mobile_app/translations/ca.json b/homeassistant/components/mobile_app/translations/ca.json index 84e613ca978..bb070f391a7 100644 --- a/homeassistant/components/mobile_app/translations/ca.json +++ b/homeassistant/components/mobile_app/translations/ca.json @@ -5,8 +5,7 @@ }, "step": { "confirm": { - "description": "Vols configurar el component d'aplicaci\u00f3 m\u00f2bil?", - "title": "Aplicaci\u00f3 m\u00f2bil" + "description": "Vols configurar el component d'aplicaci\u00f3 m\u00f2bil?" } } } diff --git a/homeassistant/components/mobile_app/translations/cs.json b/homeassistant/components/mobile_app/translations/cs.json index b1d244cc4d6..5b5a68db066 100644 --- a/homeassistant/components/mobile_app/translations/cs.json +++ b/homeassistant/components/mobile_app/translations/cs.json @@ -5,8 +5,7 @@ }, "step": { "confirm": { - "description": "Chcete nastavit komponentu Mobiln\u00ed aplikace?", - "title": "Mobiln\u00ed aplikace" + "description": "Chcete nastavit komponentu Mobiln\u00ed aplikace?" } } } diff --git a/homeassistant/components/mobile_app/translations/da.json b/homeassistant/components/mobile_app/translations/da.json index e497ef546e3..19061af3686 100644 --- a/homeassistant/components/mobile_app/translations/da.json +++ b/homeassistant/components/mobile_app/translations/da.json @@ -5,8 +5,7 @@ }, "step": { "confirm": { - "description": "Vil du konfigurere mobilapp-komponenten?", - "title": "Mobilapp" + "description": "Vil du konfigurere mobilapp-komponenten?" } } } diff --git a/homeassistant/components/mobile_app/translations/de.json b/homeassistant/components/mobile_app/translations/de.json index f7181424964..0a2f8461bed 100644 --- a/homeassistant/components/mobile_app/translations/de.json +++ b/homeassistant/components/mobile_app/translations/de.json @@ -5,8 +5,7 @@ }, "step": { "confirm": { - "description": "M\u00f6chtest du die Mobile App-Komponente einrichten?", - "title": "Mobile App" + "description": "M\u00f6chtest du die Mobile App-Komponente einrichten?" } } } diff --git a/homeassistant/components/mobile_app/translations/en.json b/homeassistant/components/mobile_app/translations/en.json index 9ddf3c95586..6def5e98582 100644 --- a/homeassistant/components/mobile_app/translations/en.json +++ b/homeassistant/components/mobile_app/translations/en.json @@ -5,8 +5,7 @@ }, "step": { "confirm": { - "description": "Do you want to set up the Mobile App component?", - "title": "Mobile App" + "description": "Do you want to set up the Mobile App component?" } } } diff --git a/homeassistant/components/mobile_app/translations/es-419.json b/homeassistant/components/mobile_app/translations/es-419.json index 0ad6fb613f9..cf5d9c4e872 100644 --- a/homeassistant/components/mobile_app/translations/es-419.json +++ b/homeassistant/components/mobile_app/translations/es-419.json @@ -5,8 +5,7 @@ }, "step": { "confirm": { - "description": "\u00bfDesea configurar el componente de la aplicaci\u00f3n m\u00f3vil?", - "title": "Aplicaci\u00f3n movil" + "description": "\u00bfDesea configurar el componente de la aplicaci\u00f3n m\u00f3vil?" } } } diff --git a/homeassistant/components/mobile_app/translations/es.json b/homeassistant/components/mobile_app/translations/es.json index ca145bd68c2..c442cc88a26 100644 --- a/homeassistant/components/mobile_app/translations/es.json +++ b/homeassistant/components/mobile_app/translations/es.json @@ -5,8 +5,7 @@ }, "step": { "confirm": { - "description": "\u00bfQuieres configurar el componente de la aplicaci\u00f3n para el m\u00f3vil?", - "title": "Aplicaci\u00f3n para el m\u00f3vil" + "description": "\u00bfQuieres configurar el componente de la aplicaci\u00f3n para el m\u00f3vil?" } } } diff --git a/homeassistant/components/mobile_app/translations/fr.json b/homeassistant/components/mobile_app/translations/fr.json index 55d1d4e0d9e..09317e4a00d 100644 --- a/homeassistant/components/mobile_app/translations/fr.json +++ b/homeassistant/components/mobile_app/translations/fr.json @@ -5,8 +5,7 @@ }, "step": { "confirm": { - "description": "Voulez-vous configurer le composant Application mobile?", - "title": "Application mobile" + "description": "Voulez-vous configurer le composant Application mobile?" } } } diff --git a/homeassistant/components/mobile_app/translations/hu.json b/homeassistant/components/mobile_app/translations/hu.json index 608a4a7879b..c44f51b02e1 100644 --- a/homeassistant/components/mobile_app/translations/hu.json +++ b/homeassistant/components/mobile_app/translations/hu.json @@ -5,8 +5,7 @@ }, "step": { "confirm": { - "description": "Be szeretn\u00e9d \u00e1ll\u00edtani a mobil alkalmaz\u00e1s komponenst?", - "title": "Mobil alkalmaz\u00e1s" + "description": "Be szeretn\u00e9d \u00e1ll\u00edtani a mobil alkalmaz\u00e1s komponenst?" } } } diff --git a/homeassistant/components/mobile_app/translations/it.json b/homeassistant/components/mobile_app/translations/it.json index 1b91c566a53..8ff6dfb982e 100644 --- a/homeassistant/components/mobile_app/translations/it.json +++ b/homeassistant/components/mobile_app/translations/it.json @@ -5,8 +5,7 @@ }, "step": { "confirm": { - "description": "Si desidera configurare il componente App per dispositivi mobili?", - "title": "App per dispositivi mobili" + "description": "Si desidera configurare il componente App per dispositivi mobili?" } } } diff --git a/homeassistant/components/mobile_app/translations/ko.json b/homeassistant/components/mobile_app/translations/ko.json index f427f3e3a12..03478e1cf2a 100644 --- a/homeassistant/components/mobile_app/translations/ko.json +++ b/homeassistant/components/mobile_app/translations/ko.json @@ -5,8 +5,7 @@ }, "step": { "confirm": { - "description": "\ubaa8\ubc14\uc77c \uc571 \ucef4\ud3ec\ub10c\ud2b8\uc758 \uc124\uc815\uc744 \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", - "title": "\ubaa8\ubc14\uc77c \uc571" + "description": "\ubaa8\ubc14\uc77c \uc571 \ucef4\ud3ec\ub10c\ud2b8\uc758 \uc124\uc815\uc744 \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" } } } diff --git a/homeassistant/components/mobile_app/translations/lb.json b/homeassistant/components/mobile_app/translations/lb.json index 77fd6517410..6e15dd34634 100644 --- a/homeassistant/components/mobile_app/translations/lb.json +++ b/homeassistant/components/mobile_app/translations/lb.json @@ -5,8 +5,7 @@ }, "step": { "confirm": { - "description": "Soll d'Mobil App konfigur\u00e9iert ginn?", - "title": "Mobil App" + "description": "Soll d'Mobil App konfigur\u00e9iert ginn?" } } } diff --git a/homeassistant/components/mobile_app/translations/nl.json b/homeassistant/components/mobile_app/translations/nl.json index 06375b4a0b6..9d5bdcfa20a 100644 --- a/homeassistant/components/mobile_app/translations/nl.json +++ b/homeassistant/components/mobile_app/translations/nl.json @@ -5,8 +5,7 @@ }, "step": { "confirm": { - "description": "Wilt u de Mobile App component instellen?", - "title": "Mobiele app" + "description": "Wilt u de Mobile App component instellen?" } } } diff --git a/homeassistant/components/mobile_app/translations/no.json b/homeassistant/components/mobile_app/translations/no.json index 131e007751c..346a5eb6b6d 100644 --- a/homeassistant/components/mobile_app/translations/no.json +++ b/homeassistant/components/mobile_app/translations/no.json @@ -5,8 +5,7 @@ }, "step": { "confirm": { - "description": "Vil du sette opp mobilapp-komponenten?", - "title": "Mobilapp" + "description": "Vil du sette opp mobilapp-komponenten?" } } } diff --git a/homeassistant/components/mobile_app/translations/pl.json b/homeassistant/components/mobile_app/translations/pl.json index a6e7e738903..e5440984b37 100644 --- a/homeassistant/components/mobile_app/translations/pl.json +++ b/homeassistant/components/mobile_app/translations/pl.json @@ -5,8 +5,7 @@ }, "step": { "confirm": { - "description": "Czy chcesz skonfigurowa\u0107 komponent aplikacji mobilnej?", - "title": "Aplikacja mobilna" + "description": "Czy chcesz skonfigurowa\u0107 komponent aplikacji mobilnej?" } } } diff --git a/homeassistant/components/mobile_app/translations/pt-BR.json b/homeassistant/components/mobile_app/translations/pt-BR.json index 79917f35d78..4c211f4bc53 100644 --- a/homeassistant/components/mobile_app/translations/pt-BR.json +++ b/homeassistant/components/mobile_app/translations/pt-BR.json @@ -5,8 +5,7 @@ }, "step": { "confirm": { - "description": "Deseja configurar o componente do aplicativo m\u00f3vel?", - "title": "Aplicativo m\u00f3vel" + "description": "Deseja configurar o componente do aplicativo m\u00f3vel?" } } } diff --git a/homeassistant/components/mobile_app/translations/ru.json b/homeassistant/components/mobile_app/translations/ru.json index f2d2200f448..7bb103b852e 100644 --- a/homeassistant/components/mobile_app/translations/ru.json +++ b/homeassistant/components/mobile_app/translations/ru.json @@ -5,8 +5,7 @@ }, "step": { "confirm": { - "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043c\u043e\u0431\u0438\u043b\u044c\u043d\u043e\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435?", - "title": "\u041c\u043e\u0431\u0438\u043b\u044c\u043d\u043e\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435" + "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043c\u043e\u0431\u0438\u043b\u044c\u043d\u043e\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435?" } } } diff --git a/homeassistant/components/mobile_app/translations/sl.json b/homeassistant/components/mobile_app/translations/sl.json index bb79e2d1e52..777776ce42d 100644 --- a/homeassistant/components/mobile_app/translations/sl.json +++ b/homeassistant/components/mobile_app/translations/sl.json @@ -5,8 +5,7 @@ }, "step": { "confirm": { - "description": "Ali \u017eelite nastaviti komponento aplikacije Mobile App?", - "title": "Mobilna Aplikacija" + "description": "Ali \u017eelite nastaviti komponento aplikacije Mobile App?" } } } diff --git a/homeassistant/components/mobile_app/translations/sv.json b/homeassistant/components/mobile_app/translations/sv.json index 497179b2a19..68473a92763 100644 --- a/homeassistant/components/mobile_app/translations/sv.json +++ b/homeassistant/components/mobile_app/translations/sv.json @@ -5,8 +5,7 @@ }, "step": { "confirm": { - "description": "Vill du konfigurera komponenten Mobile App?", - "title": "Mobilapp" + "description": "Vill du konfigurera komponenten Mobile App?" } } } diff --git a/homeassistant/components/mobile_app/translations/uk.json b/homeassistant/components/mobile_app/translations/uk.json index 0916a0ac34f..4a48dd3775d 100644 --- a/homeassistant/components/mobile_app/translations/uk.json +++ b/homeassistant/components/mobile_app/translations/uk.json @@ -2,8 +2,7 @@ "config": { "step": { "confirm": { - "description": "\u0412\u0438 \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u043c\u043e\u0431\u0456\u043b\u044c\u043d\u043e\u0433\u043e \u0434\u043e\u0434\u0430\u0442\u043a\u0430?", - "title": "\u041c\u043e\u0431\u0456\u043b\u044c\u043d\u0438\u0439 \u0434\u043e\u0434\u0430\u0442\u043e\u043a" + "description": "\u0412\u0438 \u0445\u043e\u0447\u0435\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u0442\u0438 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u043c\u043e\u0431\u0456\u043b\u044c\u043d\u043e\u0433\u043e \u0434\u043e\u0434\u0430\u0442\u043a\u0430?" } } } diff --git a/homeassistant/components/mobile_app/translations/vi.json b/homeassistant/components/mobile_app/translations/vi.json index 272e4d6f04f..478ce0e45fd 100644 --- a/homeassistant/components/mobile_app/translations/vi.json +++ b/homeassistant/components/mobile_app/translations/vi.json @@ -5,8 +5,7 @@ }, "step": { "confirm": { - "description": "B\u1ea1n c\u00f3 mu\u1ed1n thi\u1ebft l\u1eadp th\u00e0nh ph\u1ea7n \u1ee8ng d\u1ee5ng di \u0111\u1ed9ng kh\u00f4ng?", - "title": "\u1ee8ng d\u1ee5ng di \u0111\u1ed9ng" + "description": "B\u1ea1n c\u00f3 mu\u1ed1n thi\u1ebft l\u1eadp th\u00e0nh ph\u1ea7n \u1ee8ng d\u1ee5ng di \u0111\u1ed9ng kh\u00f4ng?" } } } diff --git a/homeassistant/components/mobile_app/translations/zh-Hans.json b/homeassistant/components/mobile_app/translations/zh-Hans.json index 5bef54b6df6..b48ca1e4263 100644 --- a/homeassistant/components/mobile_app/translations/zh-Hans.json +++ b/homeassistant/components/mobile_app/translations/zh-Hans.json @@ -5,8 +5,7 @@ }, "step": { "confirm": { - "description": "\u60a8\u60f3\u8981\u914d\u7f6e\u79fb\u52a8\u5e94\u7528\u7a0b\u5e8f\u7ec4\u4ef6\u5417\uff1f", - "title": "\u79fb\u52a8\u5e94\u7528" + "description": "\u60a8\u60f3\u8981\u914d\u7f6e\u79fb\u52a8\u5e94\u7528\u7a0b\u5e8f\u7ec4\u4ef6\u5417\uff1f" } } } diff --git a/homeassistant/components/mobile_app/translations/zh-Hant.json b/homeassistant/components/mobile_app/translations/zh-Hant.json index d088b64b4d4..e09710e4235 100644 --- a/homeassistant/components/mobile_app/translations/zh-Hant.json +++ b/homeassistant/components/mobile_app/translations/zh-Hant.json @@ -5,8 +5,7 @@ }, "step": { "confirm": { - "description": "\u662f\u5426\u8981\u8a2d\u5b9a\u624b\u6a5f App \u5143\u4ef6\uff1f", - "title": "\u624b\u6a5f App" + "description": "\u662f\u5426\u8981\u8a2d\u5b9a\u624b\u6a5f App \u5143\u4ef6\uff1f" } } } diff --git a/homeassistant/components/mqtt/translations/bg.json b/homeassistant/components/mqtt/translations/bg.json index 384efdebffc..7a470b74272 100644 --- a/homeassistant/components/mqtt/translations/bg.json +++ b/homeassistant/components/mqtt/translations/bg.json @@ -15,8 +15,7 @@ "port": "\u041f\u043e\u0440\u0442", "username": "\u041f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b\u0441\u043a\u043e \u0438\u043c\u0435" }, - "description": "\u041c\u043e\u043b\u044f, \u0432\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f\u0442\u0430 \u0437\u0430 \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435 \u0441 \u0412\u0430\u0448\u0438\u044f MQTT \u0431\u0440\u043e\u043a\u0435\u0440.", - "title": "MQTT" + "description": "\u041c\u043e\u043b\u044f, \u0432\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f\u0442\u0430 \u0437\u0430 \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435 \u0441 \u0412\u0430\u0448\u0438\u044f MQTT \u0431\u0440\u043e\u043a\u0435\u0440." }, "hassio_confirm": { "data": { diff --git a/homeassistant/components/mqtt/translations/ca.json b/homeassistant/components/mqtt/translations/ca.json index 7b93dfe2fbd..f0c9b5d50d0 100644 --- a/homeassistant/components/mqtt/translations/ca.json +++ b/homeassistant/components/mqtt/translations/ca.json @@ -15,8 +15,7 @@ "port": "Port", "username": "Nom d'usuari" }, - "description": "Introdueix la informaci\u00f3 de connexi\u00f3 del teu broker MQTT.", - "title": "MQTT" + "description": "Introdueix la informaci\u00f3 de connexi\u00f3 del teu broker MQTT." }, "hassio_confirm": { "data": { diff --git a/homeassistant/components/mqtt/translations/cs.json b/homeassistant/components/mqtt/translations/cs.json index d021c1fcba8..b49bc8cf343 100644 --- a/homeassistant/components/mqtt/translations/cs.json +++ b/homeassistant/components/mqtt/translations/cs.json @@ -15,8 +15,7 @@ "port": "Port", "username": "U\u017eivatelsk\u00e9 jm\u00e9no" }, - "description": "Zadejte informace proo p\u0159ipojen\u00ed zprost\u0159edkovatele protokolu MQTT.", - "title": "MQTT" + "description": "Zadejte informace proo p\u0159ipojen\u00ed zprost\u0159edkovatele protokolu MQTT." }, "hassio_confirm": { "data": { diff --git a/homeassistant/components/mqtt/translations/da.json b/homeassistant/components/mqtt/translations/da.json index ba605bd6595..7ff0f2b0a70 100644 --- a/homeassistant/components/mqtt/translations/da.json +++ b/homeassistant/components/mqtt/translations/da.json @@ -15,8 +15,7 @@ "port": "Port", "username": "Brugernavn" }, - "description": "Indtast venligst forbindelsesindstillinger for din MQTT-broker.", - "title": "MQTT" + "description": "Indtast venligst forbindelsesindstillinger for din MQTT-broker." }, "hassio_confirm": { "data": { diff --git a/homeassistant/components/mqtt/translations/de.json b/homeassistant/components/mqtt/translations/de.json index 12e1c5bc46c..75e9908de41 100644 --- a/homeassistant/components/mqtt/translations/de.json +++ b/homeassistant/components/mqtt/translations/de.json @@ -15,8 +15,7 @@ "port": "Port", "username": "Benutzername" }, - "description": "Bitte gib die Verbindungsinformationen deines MQTT-Brokers ein.", - "title": "MQTT" + "description": "Bitte gib die Verbindungsinformationen deines MQTT-Brokers ein." }, "hassio_confirm": { "data": { diff --git a/homeassistant/components/mqtt/translations/en.json b/homeassistant/components/mqtt/translations/en.json index f7bc89da80b..dc3231533d0 100644 --- a/homeassistant/components/mqtt/translations/en.json +++ b/homeassistant/components/mqtt/translations/en.json @@ -15,8 +15,7 @@ "port": "Port", "username": "Username" }, - "description": "Please enter the connection information of your MQTT broker.", - "title": "MQTT" + "description": "Please enter the connection information of your MQTT broker." }, "hassio_confirm": { "data": { diff --git a/homeassistant/components/mqtt/translations/es-419.json b/homeassistant/components/mqtt/translations/es-419.json index afb55aca1ce..d2ddc6691d1 100644 --- a/homeassistant/components/mqtt/translations/es-419.json +++ b/homeassistant/components/mqtt/translations/es-419.json @@ -15,8 +15,7 @@ "port": "Puerto", "username": "Nombre de usuario" }, - "description": "Por favor ingrese la informaci\u00f3n de conexi\u00f3n de su agente MQTT.", - "title": "MQTT" + "description": "Por favor ingrese la informaci\u00f3n de conexi\u00f3n de su agente MQTT." }, "hassio_confirm": { "data": { diff --git a/homeassistant/components/mqtt/translations/es.json b/homeassistant/components/mqtt/translations/es.json index 5ba2211f912..a55d2d7bd07 100644 --- a/homeassistant/components/mqtt/translations/es.json +++ b/homeassistant/components/mqtt/translations/es.json @@ -15,8 +15,7 @@ "port": "Puerto", "username": "Usuario" }, - "description": "Por favor, introduce la informaci\u00f3n de tu agente MQTT", - "title": "MQTT" + "description": "Por favor, introduce la informaci\u00f3n de tu agente MQTT" }, "hassio_confirm": { "data": { diff --git a/homeassistant/components/mqtt/translations/fi.json b/homeassistant/components/mqtt/translations/fi.json index 3659e489d8a..27a956beb33 100644 --- a/homeassistant/components/mqtt/translations/fi.json +++ b/homeassistant/components/mqtt/translations/fi.json @@ -8,8 +8,7 @@ "password": "Salasana", "port": "Portti", "username": "K\u00e4ytt\u00e4j\u00e4tunnus" - }, - "title": "MQTT" + } }, "hassio_confirm": { "data": { diff --git a/homeassistant/components/mqtt/translations/fr.json b/homeassistant/components/mqtt/translations/fr.json index f97807b015f..6571ce3b724 100644 --- a/homeassistant/components/mqtt/translations/fr.json +++ b/homeassistant/components/mqtt/translations/fr.json @@ -15,8 +15,7 @@ "port": "Port", "username": "Nom d'utilisateur" }, - "description": "Veuillez entrer les informations de connexion de votre broker MQTT.", - "title": "MQTT" + "description": "Veuillez entrer les informations de connexion de votre broker MQTT." }, "hassio_confirm": { "data": { diff --git a/homeassistant/components/mqtt/translations/he.json b/homeassistant/components/mqtt/translations/he.json index f8defa49708..bd083dfc1ec 100644 --- a/homeassistant/components/mqtt/translations/he.json +++ b/homeassistant/components/mqtt/translations/he.json @@ -15,8 +15,7 @@ "port": "\u05e4\u05d5\u05e8\u05d8", "username": "\u05e9\u05dd \u05de\u05e9\u05ea\u05de\u05e9" }, - "description": "\u05e0\u05d0 \u05dc\u05d4\u05d6\u05d9\u05df \u05d0\u05ea \u05e4\u05e8\u05d8\u05d9 \u05d4\u05d7\u05d9\u05d1\u05d5\u05e8 \u05e9\u05dc \u05d4\u05d1\u05e8\u05d5\u05e7\u05e8 MQTT \u05e9\u05dc\u05da.", - "title": "MQTT" + "description": "\u05e0\u05d0 \u05dc\u05d4\u05d6\u05d9\u05df \u05d0\u05ea \u05e4\u05e8\u05d8\u05d9 \u05d4\u05d7\u05d9\u05d1\u05d5\u05e8 \u05e9\u05dc \u05d4\u05d1\u05e8\u05d5\u05e7\u05e8 MQTT \u05e9\u05dc\u05da." } } } diff --git a/homeassistant/components/mqtt/translations/hr.json b/homeassistant/components/mqtt/translations/hr.json index 67994227513..6ddc827fff9 100644 --- a/homeassistant/components/mqtt/translations/hr.json +++ b/homeassistant/components/mqtt/translations/hr.json @@ -6,8 +6,7 @@ "password": "Lozinka", "port": "Port", "username": "Korisni\u010dko ime" - }, - "title": "MQTT" + } } } } diff --git a/homeassistant/components/mqtt/translations/hu.json b/homeassistant/components/mqtt/translations/hu.json index f9d8c5f1594..8cc6aa0857f 100644 --- a/homeassistant/components/mqtt/translations/hu.json +++ b/homeassistant/components/mqtt/translations/hu.json @@ -15,8 +15,7 @@ "port": "Port", "username": "Felhaszn\u00e1l\u00f3n\u00e9v" }, - "description": "K\u00e9rlek, add meg az MQTT br\u00f3ker kapcsol\u00f3d\u00e1si adatait.", - "title": "MQTT" + "description": "K\u00e9rlek, add meg az MQTT br\u00f3ker kapcsol\u00f3d\u00e1si adatait." }, "hassio_confirm": { "data": { diff --git a/homeassistant/components/mqtt/translations/id.json b/homeassistant/components/mqtt/translations/id.json index 0afc9ee7021..e21052a501f 100644 --- a/homeassistant/components/mqtt/translations/id.json +++ b/homeassistant/components/mqtt/translations/id.json @@ -14,8 +14,7 @@ "port": "Port", "username": "Nama pengguna" }, - "description": "Harap masukkan informasi koneksi dari broker MQTT Anda.", - "title": "MQTT" + "description": "Harap masukkan informasi koneksi dari broker MQTT Anda." } } } diff --git a/homeassistant/components/mqtt/translations/it.json b/homeassistant/components/mqtt/translations/it.json index 2007a946dd5..3506049abce 100644 --- a/homeassistant/components/mqtt/translations/it.json +++ b/homeassistant/components/mqtt/translations/it.json @@ -15,8 +15,7 @@ "port": "Porta", "username": "Nome utente" }, - "description": "Inserisci le informazioni di connessione del tuo broker MQTT.", - "title": "MQTT" + "description": "Inserisci le informazioni di connessione del tuo broker MQTT." }, "hassio_confirm": { "data": { diff --git a/homeassistant/components/mqtt/translations/ko.json b/homeassistant/components/mqtt/translations/ko.json index 659cec20394..a337c05fe63 100644 --- a/homeassistant/components/mqtt/translations/ko.json +++ b/homeassistant/components/mqtt/translations/ko.json @@ -15,8 +15,7 @@ "port": "\ud3ec\ud2b8", "username": "\uc0ac\uc6a9\uc790 \uc774\ub984" }, - "description": "MQTT \ube0c\ub85c\ucee4\uc758 \uc5f0\uacb0 \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694.", - "title": "MQTT" + "description": "MQTT \ube0c\ub85c\ucee4\uc758 \uc5f0\uacb0 \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694." }, "hassio_confirm": { "data": { diff --git a/homeassistant/components/mqtt/translations/lb.json b/homeassistant/components/mqtt/translations/lb.json index ba84a2e30c5..4ffa66b10bd 100644 --- a/homeassistant/components/mqtt/translations/lb.json +++ b/homeassistant/components/mqtt/translations/lb.json @@ -15,8 +15,7 @@ "port": "Port", "username": "Benotzernumm" }, - "description": "Gitt Verbindungs Informatioune vun \u00e4rem MQTT Broker an.", - "title": "MQTT" + "description": "Gitt Verbindungs Informatioune vun \u00e4rem MQTT Broker an." }, "hassio_confirm": { "data": { diff --git a/homeassistant/components/mqtt/translations/nl.json b/homeassistant/components/mqtt/translations/nl.json index f7c099c2cb6..7953c744f27 100644 --- a/homeassistant/components/mqtt/translations/nl.json +++ b/homeassistant/components/mqtt/translations/nl.json @@ -15,8 +15,7 @@ "port": "Poort", "username": "Gebruikersnaam" }, - "description": "MQTT", - "title": "MQTT" + "description": "MQTT" }, "hassio_confirm": { "data": { diff --git a/homeassistant/components/mqtt/translations/nn.json b/homeassistant/components/mqtt/translations/nn.json index ab64bd1b9fc..fb62ec04585 100644 --- a/homeassistant/components/mqtt/translations/nn.json +++ b/homeassistant/components/mqtt/translations/nn.json @@ -14,8 +14,7 @@ "port": "Port", "username": "Brukarnamn" }, - "description": "Ver vennleg \u00e5 skriv inn tilkoplingsinformasjonen for MQTT-meglaren din", - "title": "MQTT" + "description": "Ver vennleg \u00e5 skriv inn tilkoplingsinformasjonen for MQTT-meglaren din" } } } diff --git a/homeassistant/components/mqtt/translations/no.json b/homeassistant/components/mqtt/translations/no.json index 456b757cb88..962da69062b 100644 --- a/homeassistant/components/mqtt/translations/no.json +++ b/homeassistant/components/mqtt/translations/no.json @@ -15,8 +15,7 @@ "port": "Port", "username": "Brukernavn" }, - "description": "Vennligst fyll ut tilkoblingsinformasjonen for din MQTT megler.", - "title": "" + "description": "Vennligst fyll ut tilkoblingsinformasjonen for din MQTT megler." }, "hassio_confirm": { "data": { diff --git a/homeassistant/components/mqtt/translations/pl.json b/homeassistant/components/mqtt/translations/pl.json index bc847771908..3606ac35481 100644 --- a/homeassistant/components/mqtt/translations/pl.json +++ b/homeassistant/components/mqtt/translations/pl.json @@ -15,8 +15,7 @@ "port": "Port", "username": "Nazwa u\u017cytkownika" }, - "description": "Wprowad\u017a informacje o po\u0142\u0105czeniu po\u015brednika MQTT.", - "title": "MQTT" + "description": "Wprowad\u017a informacje o po\u0142\u0105czeniu po\u015brednika MQTT." }, "hassio_confirm": { "data": { diff --git a/homeassistant/components/mqtt/translations/pt-BR.json b/homeassistant/components/mqtt/translations/pt-BR.json index 392b4c78952..3a441d0c1f1 100644 --- a/homeassistant/components/mqtt/translations/pt-BR.json +++ b/homeassistant/components/mqtt/translations/pt-BR.json @@ -15,8 +15,7 @@ "port": "Porta", "username": "Nome de usu\u00e1rio" }, - "description": "Por favor, insira as informa\u00e7\u00f5es de conex\u00e3o do seu agente MQTT.", - "title": "MQTT" + "description": "Por favor, insira as informa\u00e7\u00f5es de conex\u00e3o do seu agente MQTT." }, "hassio_confirm": { "data": { diff --git a/homeassistant/components/mqtt/translations/pt.json b/homeassistant/components/mqtt/translations/pt.json index ef17b291566..7fa10a5592b 100644 --- a/homeassistant/components/mqtt/translations/pt.json +++ b/homeassistant/components/mqtt/translations/pt.json @@ -15,8 +15,7 @@ "port": "Porto", "username": "Nome de Utilizador" }, - "description": "Por favor, insira os detalhes de liga\u00e7\u00e3o ao seu broker MQTT.", - "title": "MQTT" + "description": "Por favor, insira os detalhes de liga\u00e7\u00e3o ao seu broker MQTT." }, "hassio_confirm": { "data": { diff --git a/homeassistant/components/mqtt/translations/ro.json b/homeassistant/components/mqtt/translations/ro.json index 5fb6d3a3e65..2292b58d01d 100644 --- a/homeassistant/components/mqtt/translations/ro.json +++ b/homeassistant/components/mqtt/translations/ro.json @@ -15,8 +15,7 @@ "port": "Port", "username": "Nume de utilizator" }, - "description": "Introduce\u021bi informa\u021biile de conectare ale brokerului dvs. MQTT.", - "title": "MQTT" + "description": "Introduce\u021bi informa\u021biile de conectare ale brokerului dvs. MQTT." }, "hassio_confirm": { "data": { diff --git a/homeassistant/components/mqtt/translations/ru.json b/homeassistant/components/mqtt/translations/ru.json index 3de009aa002..e1c5c0d979e 100644 --- a/homeassistant/components/mqtt/translations/ru.json +++ b/homeassistant/components/mqtt/translations/ru.json @@ -15,8 +15,7 @@ "port": "\u041f\u043e\u0440\u0442", "username": "\u041b\u043e\u0433\u0438\u043d" }, - "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438 \u043a \u0412\u0430\u0448\u0435\u043c\u0443 \u0431\u0440\u043e\u043a\u0435\u0440\u0443 MQTT.", - "title": "MQTT" + "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438 \u043a \u0412\u0430\u0448\u0435\u043c\u0443 \u0431\u0440\u043e\u043a\u0435\u0440\u0443 MQTT." }, "hassio_confirm": { "data": { diff --git a/homeassistant/components/mqtt/translations/sl.json b/homeassistant/components/mqtt/translations/sl.json index 9ca44a36d84..691fdcf058a 100644 --- a/homeassistant/components/mqtt/translations/sl.json +++ b/homeassistant/components/mqtt/translations/sl.json @@ -15,8 +15,7 @@ "port": "port", "username": "Uporabni\u0161ko ime" }, - "description": "Prosimo vnesite informacije o povezavi va\u0161ega MQTT posrednika.", - "title": "MQTT" + "description": "Prosimo vnesite informacije o povezavi va\u0161ega MQTT posrednika." }, "hassio_confirm": { "data": { diff --git a/homeassistant/components/mqtt/translations/sv.json b/homeassistant/components/mqtt/translations/sv.json index 9415beedbf7..d5d73c4ae55 100644 --- a/homeassistant/components/mqtt/translations/sv.json +++ b/homeassistant/components/mqtt/translations/sv.json @@ -15,8 +15,7 @@ "port": "Port", "username": "Anv\u00e4ndarnamn" }, - "description": "V\u00e4nligen ange anslutningsinformationen f\u00f6r din MQTT broker.", - "title": "MQTT" + "description": "V\u00e4nligen ange anslutningsinformationen f\u00f6r din MQTT broker." }, "hassio_confirm": { "data": { diff --git a/homeassistant/components/mqtt/translations/zh-Hans.json b/homeassistant/components/mqtt/translations/zh-Hans.json index a5da0304c5d..4a9b860f873 100644 --- a/homeassistant/components/mqtt/translations/zh-Hans.json +++ b/homeassistant/components/mqtt/translations/zh-Hans.json @@ -15,8 +15,7 @@ "port": "\u7aef\u53e3", "username": "\u7528\u6237\u540d" }, - "description": "\u8bf7\u8f93\u5165\u60a8\u7684 MQTT \u670d\u52a1\u5668\u7684\u8fde\u63a5\u4fe1\u606f\u3002", - "title": "MQTT" + "description": "\u8bf7\u8f93\u5165\u60a8\u7684 MQTT \u670d\u52a1\u5668\u7684\u8fde\u63a5\u4fe1\u606f\u3002" }, "hassio_confirm": { "data": { diff --git a/homeassistant/components/mqtt/translations/zh-Hant.json b/homeassistant/components/mqtt/translations/zh-Hant.json index 0bf4064bf52..2b50e38ae7e 100644 --- a/homeassistant/components/mqtt/translations/zh-Hant.json +++ b/homeassistant/components/mqtt/translations/zh-Hant.json @@ -15,8 +15,7 @@ "port": "\u901a\u8a0a\u57e0", "username": "\u4f7f\u7528\u8005\u540d\u7a31" }, - "description": "\u8acb\u8f38\u5165 MQTT Broker \u9023\u7dda\u8cc7\u8a0a\u3002", - "title": "MQTT" + "description": "\u8acb\u8f38\u5165 MQTT Broker \u9023\u7dda\u8cc7\u8a0a\u3002" }, "hassio_confirm": { "data": { diff --git a/homeassistant/components/openuv/translations/ca.json b/homeassistant/components/openuv/translations/ca.json index 3f139b4f74f..973148e86bd 100644 --- a/homeassistant/components/openuv/translations/ca.json +++ b/homeassistant/components/openuv/translations/ca.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "Aquestes coordenades ja estan registrades." + }, "error": { "identifier_exists": "Les coordenades ja estan registrades", "invalid_api_key": "Clau API no v\u00e0lida" diff --git a/homeassistant/components/openuv/translations/en.json b/homeassistant/components/openuv/translations/en.json index 0d2eec6341d..77bdae11e05 100644 --- a/homeassistant/components/openuv/translations/en.json +++ b/homeassistant/components/openuv/translations/en.json @@ -10,7 +10,7 @@ "step": { "user": { "data": { - "api_key": "[%key:common::config_flow::data::api_key%]", + "api_key": "API Key", "elevation": "Elevation", "latitude": "Latitude", "longitude": "Longitude" diff --git a/homeassistant/components/openuv/translations/es.json b/homeassistant/components/openuv/translations/es.json index 3c87781d2c5..4eb27857310 100644 --- a/homeassistant/components/openuv/translations/es.json +++ b/homeassistant/components/openuv/translations/es.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "Estas coordenadas ya est\u00e1n registradas." + }, "error": { "identifier_exists": "Coordenadas ya registradas", "invalid_api_key": "Clave API inv\u00e1lida" diff --git a/homeassistant/components/openuv/translations/lb.json b/homeassistant/components/openuv/translations/lb.json index ebb09f2ded6..7ca44391875 100644 --- a/homeassistant/components/openuv/translations/lb.json +++ b/homeassistant/components/openuv/translations/lb.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "D\u00ebs Koordinate si scho registr\u00e9iert" + }, "error": { "identifier_exists": "Koordinate si scho\u00a0registr\u00e9iert", "invalid_api_key": "Ong\u00ebltegen API Schl\u00ebssel" diff --git a/homeassistant/components/openuv/translations/ru.json b/homeassistant/components/openuv/translations/ru.json index 5b4ce86e963..0e748093241 100644 --- a/homeassistant/components/openuv/translations/ru.json +++ b/homeassistant/components/openuv/translations/ru.json @@ -1,5 +1,8 @@ { "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." + }, "error": { "identifier_exists": "\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.", "invalid_api_key": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043a\u043b\u044e\u0447 API." diff --git a/homeassistant/components/openuv/translations/zh-Hant.json b/homeassistant/components/openuv/translations/zh-Hant.json index 70e4b1da62c..e589915ee89 100644 --- a/homeassistant/components/openuv/translations/zh-Hant.json +++ b/homeassistant/components/openuv/translations/zh-Hant.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "\u6b64\u4e9b\u5ea7\u6a19\u5df2\u8a3b\u518a\u3002" + }, "error": { "identifier_exists": "\u8a72\u5ea7\u6a19\u5df2\u8a3b\u518a", "invalid_api_key": "API \u5bc6\u9470\u7121\u6548" diff --git a/homeassistant/components/pi_hole/translations/lb.json b/homeassistant/components/pi_hole/translations/lb.json index 0ce64fc6908..4224546df43 100644 --- a/homeassistant/components/pi_hole/translations/lb.json +++ b/homeassistant/components/pi_hole/translations/lb.json @@ -1,13 +1,19 @@ { "config": { "abort": { + "already_configured": "Service ass scho konfigur\u00e9iert", "duplicated_name": "Numm g\u00ebtt et schonn" }, + "error": { + "cannot_connect": "Feeler beim verbannen" + }, "step": { "user": { "data": { "api_key": "API Schl\u00ebssel (Optionell)", + "host": "Host", "name": "Numm", + "port": "Port", "ssl": "SSL benotzen", "verify_ssl": "SSL Zertifikat iwwerpr\u00e9iwen" } diff --git a/homeassistant/components/plex/translations/bg.json b/homeassistant/components/plex/translations/bg.json index fabb44dc9ea..92cd6f59819 100644 --- a/homeassistant/components/plex/translations/bg.json +++ b/homeassistant/components/plex/translations/bg.json @@ -21,10 +21,6 @@ }, "description": "\u041d\u0430\u043b\u0438\u0447\u043d\u0438 \u0441\u0430 \u043d\u044f\u043a\u043e\u043b\u043a\u043e \u0441\u044a\u0440\u0432\u044a\u0440\u0430, \u0438\u0437\u0431\u0435\u0440\u0435\u0442\u0435 \u0435\u0434\u0438\u043d:", "title": "\u0418\u0437\u0431\u0435\u0440\u0435\u0442\u0435 Plex \u0441\u044a\u0440\u0432\u044a\u0440" - }, - "start_website_auth": { - "description": "\u041f\u0440\u043e\u0434\u044a\u043b\u0436\u0435\u0442\u0435 \u0441 \u043e\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f\u0442\u0430 \u043d\u0430 plex.tv.", - "title": "\u0421\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435 \u043d\u0430 Plex \u0441\u044a\u0440\u0432\u044a\u0440" } } }, diff --git a/homeassistant/components/plex/translations/ca.json b/homeassistant/components/plex/translations/ca.json index a5c5c294880..49d7b5d6278 100644 --- a/homeassistant/components/plex/translations/ca.json +++ b/homeassistant/components/plex/translations/ca.json @@ -35,10 +35,6 @@ "description": "Hi ha diversos servidors disponibles, selecciona'n un:", "title": "Selecciona servidor Plex" }, - "start_website_auth": { - "description": "Continua l'autoritzaci\u00f3 a plex.tv.", - "title": "Connexi\u00f3 amb el servidor Plex" - }, "user": { "description": "V\u00e9s a [plex.tv](https://plex.tv) per enlla\u00e7ar un servidor Plex.", "title": "Servidor Multim\u00e8dia Plex" diff --git a/homeassistant/components/plex/translations/da.json b/homeassistant/components/plex/translations/da.json index 06b40f1b1ad..24e51410d00 100644 --- a/homeassistant/components/plex/translations/da.json +++ b/homeassistant/components/plex/translations/da.json @@ -21,10 +21,6 @@ }, "description": "Flere servere til r\u00e5dighed, v\u00e6lg en:", "title": "V\u00e6lg Plex-server" - }, - "start_website_auth": { - "description": "Forts\u00e6t for at godkende p\u00e5 plex.tv.", - "title": "Forbind Plex-server" } } }, diff --git a/homeassistant/components/plex/translations/de.json b/homeassistant/components/plex/translations/de.json index 3d002cc299c..4208e9a528b 100644 --- a/homeassistant/components/plex/translations/de.json +++ b/homeassistant/components/plex/translations/de.json @@ -34,10 +34,6 @@ "description": "Mehrere Server verf\u00fcgbar, w\u00e4hle einen aus:", "title": "Plex-Server ausw\u00e4hlen" }, - "start_website_auth": { - "description": "Weiter zur Autorisierung unter plex.tv.", - "title": "Plex Server verbinden" - }, "user": { "description": "Gehen Sie zu [plex.tv] (https://plex.tv), um einen Plex-Server zu verbinden", "title": "Plex Media Server" diff --git a/homeassistant/components/plex/translations/en.json b/homeassistant/components/plex/translations/en.json index d0c9a0d4e32..78c250ccc37 100644 --- a/homeassistant/components/plex/translations/en.json +++ b/homeassistant/components/plex/translations/en.json @@ -35,10 +35,6 @@ "description": "Multiple servers available, select one:", "title": "Select Plex server" }, - "start_website_auth": { - "description": "Continue to authorize at plex.tv.", - "title": "Connect Plex server" - }, "user": { "description": "Continue to [plex.tv](https://plex.tv) to link a Plex server.", "title": "Plex Media Server" diff --git a/homeassistant/components/plex/translations/es-419.json b/homeassistant/components/plex/translations/es-419.json index 72cd3deefbb..62f5d6dd6d5 100644 --- a/homeassistant/components/plex/translations/es-419.json +++ b/homeassistant/components/plex/translations/es-419.json @@ -21,10 +21,6 @@ }, "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" } } }, diff --git a/homeassistant/components/plex/translations/es.json b/homeassistant/components/plex/translations/es.json index d2503fc06b8..b28d6279ade 100644 --- a/homeassistant/components/plex/translations/es.json +++ b/homeassistant/components/plex/translations/es.json @@ -35,10 +35,6 @@ "description": "Varios servidores disponibles, seleccione uno:", "title": "Seleccione el servidor Plex" }, - "start_website_auth": { - "description": "Contin\u00fae en plex.tv para autorizar", - "title": "Conectar servidor Plex" - }, "user": { "description": "Continuar hacia [plex.tv](https://plex.tv) para vincular un servidor Plex.", "title": "Plex Media Server" diff --git a/homeassistant/components/plex/translations/fr.json b/homeassistant/components/plex/translations/fr.json index 7ec9b29c7ff..b27d648a911 100644 --- a/homeassistant/components/plex/translations/fr.json +++ b/homeassistant/components/plex/translations/fr.json @@ -33,10 +33,6 @@ "description": "Plusieurs serveurs disponibles, s\u00e9lectionnez-en un:", "title": "S\u00e9lectionnez le serveur Plex" }, - "start_website_auth": { - "description": "Continuer d'autoriser sur plex.tv.", - "title": "Connecter un serveur Plex" - }, "user": { "title": "Plex Media Server" }, diff --git a/homeassistant/components/plex/translations/hu.json b/homeassistant/components/plex/translations/hu.json index a9cdf058566..48a2da53998 100644 --- a/homeassistant/components/plex/translations/hu.json +++ b/homeassistant/components/plex/translations/hu.json @@ -29,10 +29,6 @@ "description": "T\u00f6bb szerver el\u00e9rhet\u0151, v\u00e1lasszon egyet:", "title": "Plex-kiszolg\u00e1l\u00f3 kiv\u00e1laszt\u00e1sa" }, - "start_website_auth": { - "description": "Folytassa az enged\u00e9lyez\u00e9st a plex.tv webhelyen.", - "title": "Plex-kiszolg\u00e1l\u00f3 csatlakoztat\u00e1sa" - }, "user": { "title": "Plex Media Server" }, diff --git a/homeassistant/components/plex/translations/it.json b/homeassistant/components/plex/translations/it.json index 8a95984fd9a..db0de6b3e13 100644 --- a/homeassistant/components/plex/translations/it.json +++ b/homeassistant/components/plex/translations/it.json @@ -35,10 +35,6 @@ "description": "Sono disponibili pi\u00f9 server, selezionarne uno:", "title": "Selezionare il server Plex" }, - "start_website_auth": { - "description": "Continuare ad autorizzare su plex.tv.", - "title": "Collegare il server Plex" - }, "user": { "description": "Continuare su [plex.tv](https://plex.tv) per collegare un server Plex.", "title": "Plex Media Server" diff --git a/homeassistant/components/plex/translations/ko.json b/homeassistant/components/plex/translations/ko.json index 98b48e778af..e376913eef6 100644 --- a/homeassistant/components/plex/translations/ko.json +++ b/homeassistant/components/plex/translations/ko.json @@ -35,10 +35,6 @@ "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\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\ud558\uae30" - }, "user": { "description": "Plex \uc11c\ubc84\ub97c \uc5f0\uacb0\ud558\ub824\uba74 [plex.tv](https://plex.tv) \ub85c \uacc4\uc18d \uc9c4\ud589\ud574\uc8fc\uc138\uc694.", "title": "Plex \ubbf8\ub514\uc5b4 \uc11c\ubc84" diff --git a/homeassistant/components/plex/translations/lb.json b/homeassistant/components/plex/translations/lb.json index f38c867a8da..155fd66f90b 100644 --- a/homeassistant/components/plex/translations/lb.json +++ b/homeassistant/components/plex/translations/lb.json @@ -35,10 +35,6 @@ "description": "M\u00e9i Server disponibel, wielt een aus:", "title": "Plex Server auswielen" }, - "start_website_auth": { - "description": "Weiderfueren op plex.tv fir d'Autorisatioun.", - "title": "Plex Server verbannen" - }, "user": { "description": "Verbann dech mat [plex.tv](https://pley.tv) fir ee Plex Server ze verlinken.", "title": "Plex Media Server" diff --git a/homeassistant/components/plex/translations/nl.json b/homeassistant/components/plex/translations/nl.json index 5a3372fa902..da13477c4af 100644 --- a/homeassistant/components/plex/translations/nl.json +++ b/homeassistant/components/plex/translations/nl.json @@ -22,10 +22,6 @@ }, "description": "Meerdere servers beschikbaar, selecteer er een:", "title": "Selecteer Plex server" - }, - "start_website_auth": { - "description": "Ga verder met autoriseren bij plex.tv.", - "title": "Verbind de Plex server" } } }, diff --git a/homeassistant/components/plex/translations/no.json b/homeassistant/components/plex/translations/no.json index 20d828356b7..ab72275070a 100644 --- a/homeassistant/components/plex/translations/no.json +++ b/homeassistant/components/plex/translations/no.json @@ -20,8 +20,6 @@ "step": { "manual_setup": { "data": { - "host": "Host (valgfritt hvis token f\u00f8lger med)", - "port": "Port", "ssl": "Bruk SSL", "token": "Token (valgfritt)", "verify_ssl": "Verifisere SSL-sertifikat" @@ -35,10 +33,6 @@ "description": "Flere servere tilgjengelig, velg en:", "title": "Velg Plex-server" }, - "start_website_auth": { - "description": "Fortsett \u00e5 godkjenne p\u00e5 plex.tv", - "title": "Koble til Plex-server" - }, "user": { "description": "Fortsett til [plex.tv] (https://plex.tv) for \u00e5 koble en Plex-server.", "title": "Plex Media Server" diff --git a/homeassistant/components/plex/translations/pl.json b/homeassistant/components/plex/translations/pl.json index 15d272f3d93..ce67ab36168 100644 --- a/homeassistant/components/plex/translations/pl.json +++ b/homeassistant/components/plex/translations/pl.json @@ -35,10 +35,6 @@ "description": "Dost\u0119pnych jest wiele serwer\u00f3w, wybierz jeden:", "title": "Wybierz serwer Plex" }, - "start_website_auth": { - "description": "Kontynuuj, by dokona\u0107 autoryzacji w plex.tv.", - "title": "Po\u0142\u0105czenie z serwerem Plex" - }, "user": { "description": "Przejd\u017a do [plex.tv](https://plex.tv), aby po\u0142\u0105czy\u0107 serwer Plex.", "title": "Serwer medi\u00f3w Plex" diff --git a/homeassistant/components/plex/translations/ru.json b/homeassistant/components/plex/translations/ru.json index 8c08d3df703..dbf2e6ab63f 100644 --- a/homeassistant/components/plex/translations/ru.json +++ b/homeassistant/components/plex/translations/ru.json @@ -35,10 +35,6 @@ "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043e\u0434\u0438\u043d \u0438\u0437 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u0445 \u0441\u0435\u0440\u0432\u0435\u0440\u043e\u0432:", "title": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0441\u0435\u0440\u0432\u0435\u0440 Plex" }, - "start_website_auth": { - "description": "\u041f\u0440\u043e\u0439\u0434\u0438\u0442\u0435 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044e \u043d\u0430 plex.tv.", - "title": "Plex" - }, "user": { "description": "\u041f\u0435\u0440\u0435\u0439\u0434\u0438\u0442\u0435 \u043d\u0430 [plex.tv](https://plex.tv), \u0447\u0442\u043e\u0431\u044b \u043f\u0440\u0438\u0432\u044f\u0437\u0430\u0442\u044c \u0441\u0435\u0440\u0432\u0435\u0440 Plex \u043a Home Assistant.", "title": "Plex Media Server" diff --git a/homeassistant/components/plex/translations/sl.json b/homeassistant/components/plex/translations/sl.json index 0d5fb88aae2..36471b43df8 100644 --- a/homeassistant/components/plex/translations/sl.json +++ b/homeassistant/components/plex/translations/sl.json @@ -34,10 +34,6 @@ "description": "Na voljo je ve\u010d stre\u017enikov, izberite enega:", "title": "Izberite stre\u017enik Plex" }, - "start_website_auth": { - "description": "Nadaljujte z avtorizacijo na plex.tv.", - "title": "Pove\u017eite stre\u017enik Plex" - }, "user": { "description": "Nadaljujte do [plex.tv] (https://plex.tv), da pove\u017eete stre\u017enik Plex.", "title": "Plex medijski stre\u017enik" diff --git a/homeassistant/components/plex/translations/sv.json b/homeassistant/components/plex/translations/sv.json index 61490e36c82..ef0af0c7090 100644 --- a/homeassistant/components/plex/translations/sv.json +++ b/homeassistant/components/plex/translations/sv.json @@ -22,10 +22,6 @@ "description": "V\u00e4lj flera servrar tillg\u00e4ngliga, v\u00e4lj en:", "title": "V\u00e4lj Plex-server" }, - "start_website_auth": { - "description": "Forts\u00e4tt att auktorisera p\u00e5 plex.tv.", - "title": "Anslut Plex-servern" - }, "user": { "title": "Plex Media Server" }, diff --git a/homeassistant/components/plex/translations/zh-Hant.json b/homeassistant/components/plex/translations/zh-Hant.json index 4efd0fb99b6..7e5ced6e034 100644 --- a/homeassistant/components/plex/translations/zh-Hant.json +++ b/homeassistant/components/plex/translations/zh-Hant.json @@ -35,10 +35,6 @@ "description": "\u627e\u5230\u591a\u500b\u4f3a\u670d\u5668\uff0c\u8acb\u9078\u64c7\u4e00\u7d44\uff1a", "title": "\u9078\u64c7 Plex \u4f3a\u670d\u5668" }, - "start_website_auth": { - "description": "\u7e7c\u7e8c\u65bc Plex.tv \u9032\u884c\u8a8d\u8b49\u3002", - "title": "\u9023\u7dda\u81f3 Plex \u4f3a\u670d\u5668" - }, "user": { "description": "\u7e7c\u7e8c\u81f3 [plex.tv](https://plex.tv) \u4ee5\u9023\u7d50\u4e00\u7d44 Plex \u4f3a\u670d\u5668\u3002", "title": "Plex \u5a92\u9ad4\u4f3a\u670d\u5668" diff --git a/homeassistant/components/plugwise/translations/ca.json b/homeassistant/components/plugwise/translations/ca.json new file mode 100644 index 00000000000..46acd2a352f --- /dev/null +++ b/homeassistant/components/plugwise/translations/ca.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Smile ja est\u00e0 configurat" + }, + "error": { + "cannot_connect": "No s'ha pogut connectar, torna-ho a provar", + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida, comprova els 8 car\u00e0cters de l'ID de Smile.", + "unknown": "Error inesperat" + }, + "step": { + "user": { + "data": { + "host": "Adre\u00e7a IP de Smile", + "password": "ID de Smile" + }, + "description": "Detalls", + "title": "Connecta\u2019t amb el Smile" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/plugwise/translations/en.json b/homeassistant/components/plugwise/translations/en.json index b7aa7ec957d..8d249e20ed4 100644 --- a/homeassistant/components/plugwise/translations/en.json +++ b/homeassistant/components/plugwise/translations/en.json @@ -1,22 +1,22 @@ { - "config": { - "step": { - "user": { - "title": "Connect to the Smile", - "description": "Details", - "data": { - "host": "Smile IP address", - "password": "Smile ID" + "config": { + "abort": { + "already_configured": "This Smile is already configured" + }, + "error": { + "cannot_connect": "Failed to connect, please try again", + "invalid_auth": "Invalid authentication, check the 8 characters of your Smile ID", + "unknown": "Unexpected error" + }, + "step": { + "user": { + "data": { + "host": "Smile IP address", + "password": "Smile ID" + }, + "description": "Details", + "title": "Connect to the Smile" + } } - } - }, - "error": { - "cannot_connect": "Failed to connect, please try again", - "invalid_auth": "Invalid authentication, check the 8 characters of your Smile ID", - "unknown": "Unexpected error" - }, - "abort": { - "already_configured": "This Smile is already configured" } - } } \ No newline at end of file diff --git a/homeassistant/components/plugwise/translations/es.json b/homeassistant/components/plugwise/translations/es.json new file mode 100644 index 00000000000..c73deaf0853 --- /dev/null +++ b/homeassistant/components/plugwise/translations/es.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Este Smile ya est\u00e1 configurado" + }, + "error": { + "cannot_connect": "No se ha podido conectar, por favor, int\u00e9ntelo de nuevo.", + "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida, comprueba los 8 caracteres de tu Smile ID", + "unknown": "Error inesperado" + }, + "step": { + "user": { + "data": { + "host": "Direcci\u00f3n IP de Smile", + "password": "ID Smile" + }, + "description": "Detalles", + "title": "Conectarse a Smile" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/roku/translations/ca.json b/homeassistant/components/roku/translations/ca.json index 3eee84ffdf9..7044b9c7bc0 100644 --- a/homeassistant/components/roku/translations/ca.json +++ b/homeassistant/components/roku/translations/ca.json @@ -17,8 +17,7 @@ "data": { "host": "Amfitri\u00f3" }, - "description": "Introdueix la teva informaci\u00f3 de Roku.", - "title": "Roku" + "description": "Introdueix la teva informaci\u00f3 de Roku." } } } diff --git a/homeassistant/components/roku/translations/de.json b/homeassistant/components/roku/translations/de.json index 90b30dfafdf..45693f05f88 100644 --- a/homeassistant/components/roku/translations/de.json +++ b/homeassistant/components/roku/translations/de.json @@ -21,8 +21,7 @@ "data": { "host": "Host oder IP-Adresse" }, - "description": "Geben Sie Ihre Roku-Informationen ein.", - "title": "Roku" + "description": "Geben Sie Ihre Roku-Informationen ein." } } } diff --git a/homeassistant/components/roku/translations/en.json b/homeassistant/components/roku/translations/en.json index 194705cc7cc..6facd1f3a7c 100644 --- a/homeassistant/components/roku/translations/en.json +++ b/homeassistant/components/roku/translations/en.json @@ -17,8 +17,7 @@ "data": { "host": "Host" }, - "description": "Enter your Roku information.", - "title": "Roku" + "description": "Enter your Roku information." } } } diff --git a/homeassistant/components/roku/translations/es-419.json b/homeassistant/components/roku/translations/es-419.json index 40a76670fe1..00b69c53c72 100644 --- a/homeassistant/components/roku/translations/es-419.json +++ b/homeassistant/components/roku/translations/es-419.json @@ -17,8 +17,7 @@ "data": { "host": "Host o direcci\u00f3n IP" }, - "description": "Ingrese su informaci\u00f3n de Roku.", - "title": "Roku" + "description": "Ingrese su informaci\u00f3n de Roku." } } } diff --git a/homeassistant/components/roku/translations/es.json b/homeassistant/components/roku/translations/es.json index 3f190d41331..222dd1eddec 100644 --- a/homeassistant/components/roku/translations/es.json +++ b/homeassistant/components/roku/translations/es.json @@ -17,8 +17,7 @@ "data": { "host": "Host o direcci\u00f3n IP" }, - "description": "Introduce tu informaci\u00f3n de Roku.", - "title": "Roku" + "description": "Introduce tu informaci\u00f3n de Roku." } } } diff --git a/homeassistant/components/roku/translations/fr.json b/homeassistant/components/roku/translations/fr.json index e2fb99f1d2a..9f7e98423d7 100644 --- a/homeassistant/components/roku/translations/fr.json +++ b/homeassistant/components/roku/translations/fr.json @@ -21,8 +21,7 @@ "data": { "host": "H\u00f4te ou adresse IP" }, - "description": "Entrez vos informations Roku.", - "title": "Roku" + "description": "Entrez vos informations Roku." } } } diff --git a/homeassistant/components/roku/translations/it.json b/homeassistant/components/roku/translations/it.json index 3116497faf1..abbe29fb2a7 100644 --- a/homeassistant/components/roku/translations/it.json +++ b/homeassistant/components/roku/translations/it.json @@ -21,8 +21,7 @@ "data": { "host": "Host" }, - "description": "Inserisci le tue informazioni Roku.", - "title": "Roku" + "description": "Inserisci le tue informazioni Roku." } } } diff --git a/homeassistant/components/roku/translations/ko.json b/homeassistant/components/roku/translations/ko.json index f6b4066948b..054c1674884 100644 --- a/homeassistant/components/roku/translations/ko.json +++ b/homeassistant/components/roku/translations/ko.json @@ -17,8 +17,7 @@ "data": { "host": "\ud638\uc2a4\ud2b8" }, - "description": "Roku \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694.", - "title": "Roku" + "description": "Roku \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694." } } } diff --git a/homeassistant/components/roku/translations/lb.json b/homeassistant/components/roku/translations/lb.json index 8674c6802be..896efe8b2fa 100644 --- a/homeassistant/components/roku/translations/lb.json +++ b/homeassistant/components/roku/translations/lb.json @@ -21,8 +21,7 @@ "data": { "host": "Numm oder IP Adresse" }, - "description": "F\u00ebll d\u00e9ng Roku Informatiounen aus.", - "title": "Roku" + "description": "F\u00ebll d\u00e9ng Roku Informatiounen aus." } } } diff --git a/homeassistant/components/roku/translations/nl.json b/homeassistant/components/roku/translations/nl.json index 8f2fae7146e..1d793df1bf4 100644 --- a/homeassistant/components/roku/translations/nl.json +++ b/homeassistant/components/roku/translations/nl.json @@ -17,8 +17,7 @@ "data": { "host": "Host- of IP-adres" }, - "description": "Voer uw Roku-informatie in.", - "title": "Roku" + "description": "Voer uw Roku-informatie in." } } } diff --git a/homeassistant/components/roku/translations/no.json b/homeassistant/components/roku/translations/no.json index e96931bce47..e2c637ac957 100644 --- a/homeassistant/components/roku/translations/no.json +++ b/homeassistant/components/roku/translations/no.json @@ -17,8 +17,7 @@ "data": { "host": "Vert eller IP-adresse" }, - "description": "Fyll inn Roku-informasjonen din.", - "title": "" + "description": "Fyll inn Roku-informasjonen din." } } } diff --git a/homeassistant/components/roku/translations/pl.json b/homeassistant/components/roku/translations/pl.json index 2cffb477417..7ca0148ce35 100644 --- a/homeassistant/components/roku/translations/pl.json +++ b/homeassistant/components/roku/translations/pl.json @@ -23,8 +23,7 @@ "data": { "host": "Nazwa hosta lub adres IP" }, - "description": "Wprowad\u017a dane Roku.", - "title": "Roku" + "description": "Wprowad\u017a dane Roku." } } } diff --git a/homeassistant/components/roku/translations/pt-BR.json b/homeassistant/components/roku/translations/pt-BR.json index 8f5916b3f36..d866e4d4ee2 100644 --- a/homeassistant/components/roku/translations/pt-BR.json +++ b/homeassistant/components/roku/translations/pt-BR.json @@ -7,8 +7,7 @@ "title": "Roku" }, "user": { - "description": "Digite suas informa\u00e7\u00f5es de Roku.", - "title": "Roku" + "description": "Digite suas informa\u00e7\u00f5es de Roku." } } } diff --git a/homeassistant/components/roku/translations/ru.json b/homeassistant/components/roku/translations/ru.json index 3500aa96e04..bddfab208c3 100644 --- a/homeassistant/components/roku/translations/ru.json +++ b/homeassistant/components/roku/translations/ru.json @@ -17,8 +17,7 @@ "data": { "host": "\u0425\u043e\u0441\u0442" }, - "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e Roku.", - "title": "Roku" + "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e Roku." } } } diff --git a/homeassistant/components/roku/translations/sl.json b/homeassistant/components/roku/translations/sl.json index fd442881b81..4a198b3b5c9 100644 --- a/homeassistant/components/roku/translations/sl.json +++ b/homeassistant/components/roku/translations/sl.json @@ -23,8 +23,7 @@ "data": { "host": "Gostitelj ali IP naslov" }, - "description": "Vnesite va\u0161e Roku podatke.", - "title": "Roku" + "description": "Vnesite va\u0161e Roku podatke." } } } diff --git a/homeassistant/components/roku/translations/zh-Hant.json b/homeassistant/components/roku/translations/zh-Hant.json index b0599ce200a..94e6d6cb489 100644 --- a/homeassistant/components/roku/translations/zh-Hant.json +++ b/homeassistant/components/roku/translations/zh-Hant.json @@ -17,8 +17,7 @@ "data": { "host": "\u4e3b\u6a5f\u7aef" }, - "description": "\u8f38\u5165 Roku \u8cc7\u8a0a\u3002", - "title": "Roku" + "description": "\u8f38\u5165 Roku \u8cc7\u8a0a\u3002" } } } diff --git a/homeassistant/components/roomba/translations/ca.json b/homeassistant/components/roomba/translations/ca.json index 8d94edc2b02..3d7efbe53ae 100644 --- a/homeassistant/components/roomba/translations/ca.json +++ b/homeassistant/components/roomba/translations/ca.json @@ -1,14 +1,12 @@ { "config": { "error": { - "cannot_connect": "No s'ha pogut connectar, torna-ho a provar", - "unknown": "Error inesperat" + "cannot_connect": "No s'ha pogut connectar, torna-ho a provar" }, "step": { "user": { "data": { "blid": "BLID", - "certificate": "Certificat", "continuous": "Cont\u00ednua", "delay": "Retard", "host": "Amfitri\u00f3", diff --git a/homeassistant/components/roomba/translations/de.json b/homeassistant/components/roomba/translations/de.json index 5781f5553ba..667f68c454c 100644 --- a/homeassistant/components/roomba/translations/de.json +++ b/homeassistant/components/roomba/translations/de.json @@ -1,14 +1,12 @@ { "config": { "error": { - "cannot_connect": "Verbindung fehlgeschlagen, versuchen Sie es erneut", - "unknown": "Unerwarteter Fehler" + "cannot_connect": "Verbindung fehlgeschlagen, versuchen Sie es erneut" }, "step": { "user": { "data": { "blid": "BLID", - "certificate": "Zertifikat", "continuous": "Kontinuierlich", "delay": "Verz\u00f6gerung", "host": "Hostname oder IP-Adresse", diff --git a/homeassistant/components/roomba/translations/en.json b/homeassistant/components/roomba/translations/en.json index a4afc71e5df..677ea5f4749 100644 --- a/homeassistant/components/roomba/translations/en.json +++ b/homeassistant/components/roomba/translations/en.json @@ -1,14 +1,12 @@ { "config": { "error": { - "cannot_connect": "Failed to connect, please try again", - "unknown": "Unexpected error" + "cannot_connect": "Failed to connect, please try again" }, "step": { "user": { "data": { "blid": "BLID", - "certificate": "Certificate", "continuous": "Continuous", "delay": "Delay", "host": "Host", diff --git a/homeassistant/components/roomba/translations/es-419.json b/homeassistant/components/roomba/translations/es-419.json index d452c82b112..6d0295f7ce0 100644 --- a/homeassistant/components/roomba/translations/es-419.json +++ b/homeassistant/components/roomba/translations/es-419.json @@ -1,14 +1,12 @@ { "config": { "error": { - "cannot_connect": "No se pudo conectar, intente nuevamente", - "unknown": "Error inesperado" + "cannot_connect": "No se pudo conectar, intente nuevamente" }, "step": { "user": { "data": { "blid": "BLID", - "certificate": "Certificado", "continuous": "Continuo", "delay": "Retraso", "host": "Nombre de host o direcci\u00f3n IP", diff --git a/homeassistant/components/roomba/translations/es.json b/homeassistant/components/roomba/translations/es.json index 28539c8fd5f..22f1ead1a56 100644 --- a/homeassistant/components/roomba/translations/es.json +++ b/homeassistant/components/roomba/translations/es.json @@ -1,14 +1,12 @@ { "config": { "error": { - "cannot_connect": "No se pudo conectar, por favor, int\u00e9ntalo de nuevo", - "unknown": "Error inesperado" + "cannot_connect": "No se pudo conectar, por favor, int\u00e9ntalo de nuevo" }, "step": { "user": { "data": { "blid": "BLID", - "certificate": "Certificado", "continuous": "Continuo", "delay": "Retardo", "host": "Nombre del host o direcci\u00f3n IP", diff --git a/homeassistant/components/roomba/translations/fr.json b/homeassistant/components/roomba/translations/fr.json index 49d021bc198..d4025d2cddf 100644 --- a/homeassistant/components/roomba/translations/fr.json +++ b/homeassistant/components/roomba/translations/fr.json @@ -1,12 +1,8 @@ { "config": { - "error": { - "unknown": "Erreur inattendue" - }, "step": { "user": { "data": { - "certificate": "Certificat", "continuous": "En continu", "delay": "D\u00e9lai", "host": "Nom d'h\u00f4te ou adresse IP", diff --git a/homeassistant/components/roomba/translations/it.json b/homeassistant/components/roomba/translations/it.json index cea9d1d0f77..babd2082b3c 100644 --- a/homeassistant/components/roomba/translations/it.json +++ b/homeassistant/components/roomba/translations/it.json @@ -1,14 +1,12 @@ { "config": { "error": { - "cannot_connect": "Impossibile connettersi, si prega di riprovare", - "unknown": "Errore imprevisto" + "cannot_connect": "Impossibile connettersi, si prega di riprovare" }, "step": { "user": { "data": { "blid": "BLID", - "certificate": "Certificato", "continuous": "Continuo", "delay": "Ritardo", "host": "Host", diff --git a/homeassistant/components/roomba/translations/ko.json b/homeassistant/components/roomba/translations/ko.json index 7990cd22bdb..ebf9056c037 100644 --- a/homeassistant/components/roomba/translations/ko.json +++ b/homeassistant/components/roomba/translations/ko.json @@ -1,14 +1,12 @@ { "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" + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694." }, "step": { "user": { "data": { "blid": "BLID", - "certificate": "\uc778\uc99d\uc11c", "continuous": "\uc5f0\uc18d", "delay": "\uc9c0\uc5f0", "host": "\ud638\uc2a4\ud2b8", diff --git a/homeassistant/components/roomba/translations/lb.json b/homeassistant/components/roomba/translations/lb.json index 2771b3f5b2a..9898d7fbd04 100644 --- a/homeassistant/components/roomba/translations/lb.json +++ b/homeassistant/components/roomba/translations/lb.json @@ -1,14 +1,12 @@ { "config": { "error": { - "cannot_connect": "Feeler beim verbannen, prob\u00e9ier w.e.g. nach emol.", - "unknown": "Onerwaarte Feeler" + "cannot_connect": "Feeler beim verbannen, prob\u00e9ier w.e.g. nach emol." }, "step": { "user": { "data": { "blid": "BLID", - "certificate": "Zertifikat", "continuous": "Kontinu\u00e9ierlech", "delay": "Delai", "host": "Host Numm oder IP Adresse", diff --git a/homeassistant/components/roomba/translations/nl.json b/homeassistant/components/roomba/translations/nl.json index d49a9f488de..f5268bdf799 100644 --- a/homeassistant/components/roomba/translations/nl.json +++ b/homeassistant/components/roomba/translations/nl.json @@ -1,14 +1,12 @@ { "config": { "error": { - "cannot_connect": "Verbinding mislukt, probeer het opnieuw", - "unknown": "Onverwachte fout" + "cannot_connect": "Verbinding mislukt, probeer het opnieuw" }, "step": { "user": { "data": { "blid": "BLID", - "certificate": "Certificaat", "continuous": "Doorlopend", "delay": "Vertraging", "host": "Hostnaam of IP-adres", diff --git a/homeassistant/components/roomba/translations/no.json b/homeassistant/components/roomba/translations/no.json index c145637a656..10b65f4f315 100644 --- a/homeassistant/components/roomba/translations/no.json +++ b/homeassistant/components/roomba/translations/no.json @@ -1,14 +1,12 @@ { "config": { "error": { - "cannot_connect": "Klarte ikke \u00e5 koble til, vennligst pr\u00f8v igjen", - "unknown": "Uventet feil" + "cannot_connect": "Klarte ikke \u00e5 koble til, vennligst pr\u00f8v igjen" }, "step": { "user": { "data": { "blid": "Blid", - "certificate": "Sertifikat", "continuous": "Kontinuerlige", "delay": "Forsinkelse", "host": "Vertsnavn eller IP-adresse", diff --git a/homeassistant/components/roomba/translations/pl.json b/homeassistant/components/roomba/translations/pl.json index 312768889e4..4600e3f5c95 100644 --- a/homeassistant/components/roomba/translations/pl.json +++ b/homeassistant/components/roomba/translations/pl.json @@ -1,14 +1,12 @@ { "config": { "error": { - "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie.", - "unknown": "Nieoczekiwany b\u0142\u0105d." + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie." }, "step": { "user": { "data": { "blid": "BLID", - "certificate": "Certyfikat", "continuous": "Ci\u0105g\u0142y", "delay": "Op\u00f3\u017anienie", "host": "Nazwa hosta lub adres IP", diff --git a/homeassistant/components/roomba/translations/pt-BR.json b/homeassistant/components/roomba/translations/pt-BR.json index bdd6b93a01a..a148d1976ad 100644 --- a/homeassistant/components/roomba/translations/pt-BR.json +++ b/homeassistant/components/roomba/translations/pt-BR.json @@ -1,14 +1,12 @@ { "config": { "error": { - "cannot_connect": "Falha ao conectar, tente novamente", - "unknown": "Erro inesperado" + "cannot_connect": "Falha ao conectar, tente novamente" }, "step": { "user": { "data": { "blid": "BLID", - "certificate": "Certificado", "continuous": "Cont\u00ednuo", "delay": "Atraso" }, diff --git a/homeassistant/components/roomba/translations/pt.json b/homeassistant/components/roomba/translations/pt.json index b063d57eb39..559712b7e9b 100644 --- a/homeassistant/components/roomba/translations/pt.json +++ b/homeassistant/components/roomba/translations/pt.json @@ -1,13 +1,11 @@ { "config": { "error": { - "cannot_connect": "Falha ao conectar, tente novamente", - "unknown": "Erro inesperado" + "cannot_connect": "Falha ao conectar, tente novamente" }, "step": { "user": { "data": { - "certificate": "Certificado", "continuous": "Cont\u00ednuo", "delay": "Atraso", "host": "Nome servidor ou endere\u00e7o IP", diff --git a/homeassistant/components/roomba/translations/ru.json b/homeassistant/components/roomba/translations/ru.json index fd8af88a9e2..12217a1bc3f 100644 --- a/homeassistant/components/roomba/translations/ru.json +++ b/homeassistant/components/roomba/translations/ru.json @@ -1,14 +1,12 @@ { "config": { "error": { - "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f, \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." + "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." }, "step": { "user": { "data": { "blid": "BLID", - "certificate": "\u0421\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442", "continuous": "\u041d\u0435\u043f\u0440\u0435\u0440\u044b\u0432\u043d\u044b\u0439 \u0440\u0435\u0436\u0438\u043c", "delay": "\u0417\u0430\u0434\u0435\u0440\u0436\u043a\u0430 (\u0441\u0435\u043a.)", "host": "\u0425\u043e\u0441\u0442", diff --git a/homeassistant/components/roomba/translations/sl.json b/homeassistant/components/roomba/translations/sl.json index 4b834d05fc8..9ad90ab82aa 100644 --- a/homeassistant/components/roomba/translations/sl.json +++ b/homeassistant/components/roomba/translations/sl.json @@ -1,14 +1,12 @@ { "config": { "error": { - "cannot_connect": "Povezava ni uspela, poskusite znova", - "unknown": "Nepri\u010dakovana napaka" + "cannot_connect": "Povezava ni uspela, poskusite znova" }, "step": { "user": { "data": { "blid": "BLID", - "certificate": "Potrdilo", "continuous": "Nenehno", "delay": "Zamik", "host": "Ime gostitelja ali naslov IP", diff --git a/homeassistant/components/roomba/translations/sv.json b/homeassistant/components/roomba/translations/sv.json index 46e0474cc50..ee1f8972ef9 100644 --- a/homeassistant/components/roomba/translations/sv.json +++ b/homeassistant/components/roomba/translations/sv.json @@ -1,14 +1,12 @@ { "config": { "error": { - "cannot_connect": "Det gick inte att ansluta, f\u00f6rs\u00f6k igen", - "unknown": "Ov\u00e4ntat fel" + "cannot_connect": "Det gick inte att ansluta, f\u00f6rs\u00f6k igen" }, "step": { "user": { "data": { "blid": "BLID", - "certificate": "Certifikat", "continuous": "Kontinuerlig", "delay": "F\u00f6rdr\u00f6jning", "host": "V\u00e4rdnamn eller IP-adress", diff --git a/homeassistant/components/roomba/translations/zh-Hant.json b/homeassistant/components/roomba/translations/zh-Hant.json index 13968bf7dda..5e51af5efb5 100644 --- a/homeassistant/components/roomba/translations/zh-Hant.json +++ b/homeassistant/components/roomba/translations/zh-Hant.json @@ -1,14 +1,12 @@ { "config": { "error": { - "cannot_connect": "\u9023\u7dda\u5931\u6557\uff0c\u8acb\u518d\u8a66\u4e00\u6b21", - "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + "cannot_connect": "\u9023\u7dda\u5931\u6557\uff0c\u8acb\u518d\u8a66\u4e00\u6b21" }, "step": { "user": { "data": { "blid": "BLID", - "certificate": "\u8a8d\u8b49", "continuous": "\u9023\u7e8c", "delay": "\u5ef6\u9072", "host": "\u4e3b\u6a5f\u7aef", diff --git a/homeassistant/components/samsungtv/translations/ca.json b/homeassistant/components/samsungtv/translations/ca.json index 8a0843c22a0..5a5fe7e7986 100644 --- a/homeassistant/components/samsungtv/translations/ca.json +++ b/homeassistant/components/samsungtv/translations/ca.json @@ -18,8 +18,7 @@ "host": "Amfitri\u00f3", "name": "Nom" }, - "description": "Introdeix les dades del televisor Samsung. Si mai abans l'has connectat a Home Assistant haur\u00edes de veure una finestra emergent demanant autenticaci\u00f3.", - "title": "Televisor Samsung" + "description": "Introdeix les dades del televisor Samsung. Si mai abans l'has connectat a Home Assistant haur\u00edes de veure una finestra emergent demanant autenticaci\u00f3." } } } diff --git a/homeassistant/components/samsungtv/translations/da.json b/homeassistant/components/samsungtv/translations/da.json index ec83db6aab8..925abbe66ef 100644 --- a/homeassistant/components/samsungtv/translations/da.json +++ b/homeassistant/components/samsungtv/translations/da.json @@ -18,8 +18,7 @@ "host": "V\u00e6rt eller IP-adresse", "name": "Navn" }, - "description": "Indtast dine Samsung-tv-oplysninger. Hvis du aldrig har oprettet forbindelse til Home Assistant f\u00f8r, b\u00f8r du se en popup p\u00e5 dit tv, der beder om godkendelse.", - "title": "Samsung-tv" + "description": "Indtast dine Samsung-tv-oplysninger. Hvis du aldrig har oprettet forbindelse til Home Assistant f\u00f8r, b\u00f8r du se en popup p\u00e5 dit tv, der beder om godkendelse." } } } diff --git a/homeassistant/components/samsungtv/translations/de.json b/homeassistant/components/samsungtv/translations/de.json index 026f00a8cb2..dff4f98a438 100644 --- a/homeassistant/components/samsungtv/translations/de.json +++ b/homeassistant/components/samsungtv/translations/de.json @@ -18,8 +18,7 @@ "host": "Host oder IP-Adresse", "name": "Name" }, - "description": "Gib deine Samsung TV-Informationen ein. Wenn du noch nie eine Verbindung zum Home Assistant hergestellt hast, solltest du ein Popup-Fenster auf deinem Fernseher sehen, das nach einer Authentifizierung fragt.", - "title": "Samsung TV" + "description": "Gib deine Samsung TV-Informationen ein. Wenn du noch nie eine Verbindung zum Home Assistant hergestellt hast, solltest du ein Popup-Fenster auf deinem Fernseher sehen, das nach einer Authentifizierung fragt." } } } diff --git a/homeassistant/components/samsungtv/translations/en.json b/homeassistant/components/samsungtv/translations/en.json index 7ee7c6bd8f4..35510858c55 100644 --- a/homeassistant/components/samsungtv/translations/en.json +++ b/homeassistant/components/samsungtv/translations/en.json @@ -18,8 +18,7 @@ "host": "Host", "name": "Name" }, - "description": "Enter your Samsung TV information. If you never connected Home Assistant before you should see a popup on your TV asking for authorization.", - "title": "Samsung TV" + "description": "Enter your Samsung TV information. If you never connected Home Assistant before you should see a popup on your TV asking for authorization." } } } diff --git a/homeassistant/components/samsungtv/translations/es-419.json b/homeassistant/components/samsungtv/translations/es-419.json index b35146e181e..c4938543d4a 100644 --- a/homeassistant/components/samsungtv/translations/es-419.json +++ b/homeassistant/components/samsungtv/translations/es-419.json @@ -18,8 +18,7 @@ "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" + "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." } } } diff --git a/homeassistant/components/samsungtv/translations/es.json b/homeassistant/components/samsungtv/translations/es.json index a12cce712ed..20bfe052924 100644 --- a/homeassistant/components/samsungtv/translations/es.json +++ b/homeassistant/components/samsungtv/translations/es.json @@ -18,8 +18,7 @@ "host": "Host o direcci\u00f3n IP", "name": "Nombre" }, - "description": "Introduce la informaci\u00f3n de tu televisi\u00f3n Samsung. Si nunca antes te conectaste con Home Assistant, deber\u00edas ver un mensaje en tu televisi\u00f3n pidiendo autorizaci\u00f3n.", - "title": "Samsung TV" + "description": "Introduce la informaci\u00f3n de tu televisi\u00f3n Samsung. Si nunca antes te conectaste con Home Assistant, deber\u00edas ver un mensaje en tu televisi\u00f3n pidiendo autorizaci\u00f3n." } } } diff --git a/homeassistant/components/samsungtv/translations/fr.json b/homeassistant/components/samsungtv/translations/fr.json index e37b3104b1c..5721aa7719c 100644 --- a/homeassistant/components/samsungtv/translations/fr.json +++ b/homeassistant/components/samsungtv/translations/fr.json @@ -18,8 +18,7 @@ "host": "H\u00f4te ou adresse IP", "name": "Nom" }, - "description": "Entrez les informations relatives \u00e0 votre t\u00e9l\u00e9viseur Samsung. Si vous n'avez jamais connect\u00e9 Home Assistant avant, vous devriez voir une fen\u00eatre contextuelle sur votre t\u00e9l\u00e9viseur demandant une authentification.", - "title": "TV Samsung" + "description": "Entrez les informations relatives \u00e0 votre t\u00e9l\u00e9viseur Samsung. Si vous n'avez jamais connect\u00e9 Home Assistant avant, vous devriez voir une fen\u00eatre contextuelle sur votre t\u00e9l\u00e9viseur demandant une authentification." } } } diff --git a/homeassistant/components/samsungtv/translations/hu.json b/homeassistant/components/samsungtv/translations/hu.json index 3f0cdcadfea..8c47eb3ed2a 100644 --- a/homeassistant/components/samsungtv/translations/hu.json +++ b/homeassistant/components/samsungtv/translations/hu.json @@ -18,8 +18,7 @@ "host": "Hoszt", "name": "N\u00e9v" }, - "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" + "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." } } } diff --git a/homeassistant/components/samsungtv/translations/it.json b/homeassistant/components/samsungtv/translations/it.json index 244f8079fc5..4236b1d8ed8 100644 --- a/homeassistant/components/samsungtv/translations/it.json +++ b/homeassistant/components/samsungtv/translations/it.json @@ -18,8 +18,7 @@ "host": "Host", "name": "Nome" }, - "description": "Inserisci le informazioni del tuo Samsung TV. Se non hai mai connesso Home Assistant in precedenza, dovresti vedere un messaggio sul TV in cui \u00e8 richiesta l'autorizzazione.", - "title": "Samsung TV" + "description": "Inserisci le informazioni del tuo Samsung TV. Se non hai mai connesso Home Assistant in precedenza, dovresti vedere un messaggio sul TV in cui \u00e8 richiesta l'autorizzazione." } } } diff --git a/homeassistant/components/samsungtv/translations/ko.json b/homeassistant/components/samsungtv/translations/ko.json index 523d9f8c45e..b635057750b 100644 --- a/homeassistant/components/samsungtv/translations/ko.json +++ b/homeassistant/components/samsungtv/translations/ko.json @@ -18,8 +18,7 @@ "host": "\ud638\uc2a4\ud2b8", "name": "\uc774\ub984" }, - "description": "\uc0bc\uc131 TV \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694. Home Assistant \ub97c \uc5f0\uacb0 \ud55c \uc801\uc774 \uc5c6\ub2e4\uba74 TV \uc5d0\uc11c \uc778\uc99d\uc744 \uc694\uccad\ud558\ub294 \ud31d\uc5c5\uc774 \ud45c\uc2dc\ub429\ub2c8\ub2e4.", - "title": "\uc0bc\uc131 TV" + "description": "\uc0bc\uc131 TV \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694. Home Assistant \ub97c \uc5f0\uacb0 \ud55c \uc801\uc774 \uc5c6\ub2e4\uba74 TV \uc5d0\uc11c \uc778\uc99d\uc744 \uc694\uccad\ud558\ub294 \ud31d\uc5c5\uc774 \ud45c\uc2dc\ub429\ub2c8\ub2e4." } } } diff --git a/homeassistant/components/samsungtv/translations/lb.json b/homeassistant/components/samsungtv/translations/lb.json index 8e54495a1b8..1fd1ce67f27 100644 --- a/homeassistant/components/samsungtv/translations/lb.json +++ b/homeassistant/components/samsungtv/translations/lb.json @@ -18,8 +18,7 @@ "host": "Numm oder IP Adresse", "name": "Numm" }, - "description": "Gitt \u00e4r Samsung TV Informatiounen un. Falls dir Home Assistant nach ni domat verbonnen hutt misst den TV eng Meldung mat enger Authentifiz\u00e9ierung uweisen.", - "title": "Samsnung TV" + "description": "Gitt \u00e4r Samsung TV Informatiounen un. Falls dir Home Assistant nach ni domat verbonnen hutt misst den TV eng Meldung mat enger Authentifiz\u00e9ierung uweisen." } } } diff --git a/homeassistant/components/samsungtv/translations/nl.json b/homeassistant/components/samsungtv/translations/nl.json index 2760ec7a181..17298e3bc8a 100644 --- a/homeassistant/components/samsungtv/translations/nl.json +++ b/homeassistant/components/samsungtv/translations/nl.json @@ -18,8 +18,7 @@ "host": "Hostnaam of IP-adres", "name": "Naam" }, - "description": "Voer uw Samsung TV informatie in. Als u nooit eerder Home Assistant hebt verbonden dan zou u een popup op uw TV moeten zien waarin u om toestemming wordt vraagt.", - "title": "Samsung TV" + "description": "Voer uw Samsung TV informatie in. Als u nooit eerder Home Assistant hebt verbonden dan zou u een popup op uw TV moeten zien waarin u om toestemming wordt vraagt." } } } diff --git a/homeassistant/components/samsungtv/translations/no.json b/homeassistant/components/samsungtv/translations/no.json index ac6b4ac87f8..afd5f7c633f 100644 --- a/homeassistant/components/samsungtv/translations/no.json +++ b/homeassistant/components/samsungtv/translations/no.json @@ -18,8 +18,7 @@ "host": "Vert eller IP-adresse", "name": "Navn" }, - "description": "Fyll inn Samsung TV-informasjonen din. Hvis du aldri har koblet til Home Assistant f\u00f8r, vil en popup p\u00e5 TVen be om godkjenning.", - "title": "" + "description": "Fyll inn Samsung TV-informasjonen din. Hvis du aldri har koblet til Home Assistant f\u00f8r, vil en popup p\u00e5 TVen be om godkjenning." } } } diff --git a/homeassistant/components/samsungtv/translations/pl.json b/homeassistant/components/samsungtv/translations/pl.json index c82a7c96e66..11de0686a33 100644 --- a/homeassistant/components/samsungtv/translations/pl.json +++ b/homeassistant/components/samsungtv/translations/pl.json @@ -18,8 +18,7 @@ "host": "Nazwa hosta lub adres IP", "name": "Nazwa" }, - "description": "Wprowad\u017a informacje o telewizorze Samsung. Je\u015bli nigdy wcze\u015bniej ten telewizor nie by\u0142 \u0142\u0105czony z Home Assistantem.", - "title": "Samsung TV" + "description": "Wprowad\u017a informacje o telewizorze Samsung. Je\u015bli nigdy wcze\u015bniej ten telewizor nie by\u0142 \u0142\u0105czony z Home Assistantem." } } } diff --git a/homeassistant/components/samsungtv/translations/ru.json b/homeassistant/components/samsungtv/translations/ru.json index 53b9dcc3206..c5ee5e8348c 100644 --- a/homeassistant/components/samsungtv/translations/ru.json +++ b/homeassistant/components/samsungtv/translations/ru.json @@ -18,8 +18,7 @@ "host": "\u0425\u043e\u0441\u0442", "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435" }, - "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u0442\u0435\u043b\u0435\u0432\u0438\u0437\u043e\u0440\u0435 Samsung. \u0415\u0441\u043b\u0438 \u044d\u0442\u043e\u0442 \u0442\u0435\u043b\u0435\u0432\u0438\u0437\u043e\u0440 \u0440\u0430\u043d\u0435\u0435 \u043d\u0435 \u0431\u044b\u043b \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d \u043a Home Assistant, \u043d\u0430 \u044d\u043a\u0440\u0430\u043d\u0435 \u0442\u0435\u043b\u0435\u0432\u0438\u0437\u043e\u0440\u0430 \u0434\u043e\u043b\u0436\u043d\u043e \u043f\u043e\u044f\u0432\u0438\u0442\u044c\u0441\u044f \u0432\u0441\u043f\u043b\u044b\u0432\u0430\u044e\u0449\u0435\u0435 \u043e\u043a\u043d\u043e \u0441 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u043c \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438.", - "title": "\u0422\u0435\u043b\u0435\u0432\u0438\u0437\u043e\u0440 Samsung" + "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u0442\u0435\u043b\u0435\u0432\u0438\u0437\u043e\u0440\u0435 Samsung. \u0415\u0441\u043b\u0438 \u044d\u0442\u043e\u0442 \u0442\u0435\u043b\u0435\u0432\u0438\u0437\u043e\u0440 \u0440\u0430\u043d\u0435\u0435 \u043d\u0435 \u0431\u044b\u043b \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d \u043a Home Assistant, \u043d\u0430 \u044d\u043a\u0440\u0430\u043d\u0435 \u0442\u0435\u043b\u0435\u0432\u0438\u0437\u043e\u0440\u0430 \u0434\u043e\u043b\u0436\u043d\u043e \u043f\u043e\u044f\u0432\u0438\u0442\u044c\u0441\u044f \u0432\u0441\u043f\u043b\u044b\u0432\u0430\u044e\u0449\u0435\u0435 \u043e\u043a\u043d\u043e \u0441 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u043c \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438." } } } diff --git a/homeassistant/components/samsungtv/translations/sl.json b/homeassistant/components/samsungtv/translations/sl.json index f147edad4d9..1002be5efd1 100644 --- a/homeassistant/components/samsungtv/translations/sl.json +++ b/homeassistant/components/samsungtv/translations/sl.json @@ -18,8 +18,7 @@ "host": "Gostitelj ali IP naslov", "name": "Ime" }, - "description": "Vnesite podatke o televizorju Samsung. \u010ce \u0161e nikoli niste povezali Home Assistant, bi morali na televizorju videli pojavno okno, ki zahteva va\u0161e dovoljenje.", - "title": "Samsung TV" + "description": "Vnesite podatke o televizorju Samsung. \u010ce \u0161e nikoli niste povezali Home Assistant, bi morali na televizorju videli pojavno okno, ki zahteva va\u0161e dovoljenje." } } } diff --git a/homeassistant/components/samsungtv/translations/sv.json b/homeassistant/components/samsungtv/translations/sv.json index ce75d775f14..5caa8daa275 100644 --- a/homeassistant/components/samsungtv/translations/sv.json +++ b/homeassistant/components/samsungtv/translations/sv.json @@ -18,8 +18,7 @@ "host": "V\u00e4rdnamn eller IP-adress", "name": "Namn" }, - "description": "Ange informationen f\u00f6r din Samsung TV. Om du aldrig har anslutit denna till Home Assistant tidigare borde du se en popup om autentisering p\u00e5 din TV.", - "title": "Samsung TV" + "description": "Ange informationen f\u00f6r din Samsung TV. Om du aldrig har anslutit denna till Home Assistant tidigare borde du se en popup om autentisering p\u00e5 din TV." } } } diff --git a/homeassistant/components/samsungtv/translations/tr.json b/homeassistant/components/samsungtv/translations/tr.json index 3246b833382..296579c17f4 100644 --- a/homeassistant/components/samsungtv/translations/tr.json +++ b/homeassistant/components/samsungtv/translations/tr.json @@ -14,8 +14,7 @@ "host": "Host veya IP adresi", "name": "Ad" }, - "description": "Samsung TV bilgilerini gir. Daha \u00f6nce hi\u00e7 Home Assistant'a ba\u011flamad\u0131ysan, TV'nde izin isteyen bir pencere g\u00f6receksindir.", - "title": "Samsung TV" + "description": "Samsung TV bilgilerini gir. Daha \u00f6nce hi\u00e7 Home Assistant'a ba\u011flamad\u0131ysan, TV'nde izin isteyen bir pencere g\u00f6receksindir." } } } diff --git a/homeassistant/components/samsungtv/translations/zh-Hant.json b/homeassistant/components/samsungtv/translations/zh-Hant.json index 2442cbcaf5f..973b84dd2ee 100644 --- a/homeassistant/components/samsungtv/translations/zh-Hant.json +++ b/homeassistant/components/samsungtv/translations/zh-Hant.json @@ -18,8 +18,7 @@ "host": "\u4e3b\u6a5f\u7aef", "name": "\u540d\u7a31" }, - "description": "\u8f38\u5165\u4e09\u661f\u96fb\u8996\u8cc7\u8a0a\u3002\u5047\u5982\u60a8\u4e4b\u524d\u672a\u66fe\u9023\u7dda\u81f3 Home Assistant\uff0c\u61c9\u8a72\u6703\u65bc\u96fb\u8996\u4e0a\u6536\u5230\u9a57\u8b49\u8a0a\u606f\u3002", - "title": "\u4e09\u661f\u96fb\u8996" + "description": "\u8f38\u5165\u4e09\u661f\u96fb\u8996\u8cc7\u8a0a\u3002\u5047\u5982\u60a8\u4e4b\u524d\u672a\u66fe\u9023\u7dda\u81f3 Home Assistant\uff0c\u61c9\u8a72\u6703\u65bc\u96fb\u8996\u4e0a\u6536\u5230\u9a57\u8b49\u8a0a\u606f\u3002" } } } diff --git a/homeassistant/components/smhi/translations/fi.json b/homeassistant/components/smhi/translations/fi.json index 88aeef608d1..2a05c47dcbc 100644 --- a/homeassistant/components/smhi/translations/fi.json +++ b/homeassistant/components/smhi/translations/fi.json @@ -1,6 +1,7 @@ { "config": { "error": { + "name_exists": "Nimi on jo olemassa", "wrong_location": "Sijainti vain Ruotsi" }, "step": { diff --git a/homeassistant/components/songpal/translations/ca.json b/homeassistant/components/songpal/translations/ca.json index 9f7c18187c3..10060f63ed4 100644 --- a/homeassistant/components/songpal/translations/ca.json +++ b/homeassistant/components/songpal/translations/ca.json @@ -5,20 +5,17 @@ "not_songpal_device": "No \u00e9s un dispositiu Songpal" }, "error": { - "cannot_connect": "No s'ha pogut connectar", - "connection": "Error de connexi\u00f3: comprova l'endpoint seleccionat" + "cannot_connect": "No s'ha pogut connectar" }, "flow_title": "Sony Songpal {name} ({host})", "step": { "init": { - "description": "Vols configurar {name} ({host})?", - "title": "Sony Songpal" + "description": "Vols configurar {name} ({host})?" }, "user": { "data": { "endpoint": "Endpoint" - }, - "title": "Sony Songpal" + } } } } diff --git a/homeassistant/components/songpal/translations/de.json b/homeassistant/components/songpal/translations/de.json index 4786dde4ca0..851e0b5f77c 100644 --- a/homeassistant/components/songpal/translations/de.json +++ b/homeassistant/components/songpal/translations/de.json @@ -5,20 +5,17 @@ "not_songpal_device": "Kein Songpal-Ger\u00e4t" }, "error": { - "cannot_connect": "Verbindung fehlgeschlagen", - "connection": "Verbindungsfehler: Bitte \u00fcberpr\u00fcfen Sie Ihren Endpunkt" + "cannot_connect": "Verbindung fehlgeschlagen" }, "flow_title": "Sony Songpal {name} ({host})", "step": { "init": { - "description": "M\u00f6chten Sie {name} ({host}) einrichten?", - "title": "Sony Songpal" + "description": "M\u00f6chten Sie {name} ({host}) einrichten?" }, "user": { "data": { "endpoint": "Endpunkt" - }, - "title": "Sony Songpal" + } } } } diff --git a/homeassistant/components/songpal/translations/en.json b/homeassistant/components/songpal/translations/en.json index f84510040ac..a44ba6e8668 100644 --- a/homeassistant/components/songpal/translations/en.json +++ b/homeassistant/components/songpal/translations/en.json @@ -5,20 +5,17 @@ "not_songpal_device": "Not a Songpal device" }, "error": { - "cannot_connect": "Failed to connect", - "connection": "Connection error: please check your endpoint" + "cannot_connect": "Failed to connect" }, "flow_title": "Sony Songpal {name} ({host})", "step": { "init": { - "description": "Do you want to set up {name} ({host})?", - "title": "Sony Songpal" + "description": "Do you want to set up {name} ({host})?" }, "user": { "data": { "endpoint": "Endpoint" - }, - "title": "Sony Songpal" + } } } } diff --git a/homeassistant/components/songpal/translations/es.json b/homeassistant/components/songpal/translations/es.json index 614da4b6580..fa62eb3ba1e 100644 --- a/homeassistant/components/songpal/translations/es.json +++ b/homeassistant/components/songpal/translations/es.json @@ -5,20 +5,17 @@ "not_songpal_device": "No es un dispositivo Songpal" }, "error": { - "cannot_connect": "No se pudo conectar", - "connection": "Error de conexi\u00f3n: comprueba tu endpoint" + "cannot_connect": "No se pudo conectar" }, "flow_title": "Sony Songpal {name} ({host})", "step": { "init": { - "description": "\u00bfQuieres configurar {name} ({host})?", - "title": "Sony Songpal" + "description": "\u00bfQuieres configurar {name} ({host})?" }, "user": { "data": { "endpoint": "Endpoint" - }, - "title": "Sony Songpal" + } } } } diff --git a/homeassistant/components/songpal/translations/fi.json b/homeassistant/components/songpal/translations/fi.json index 3e06c6f1461..c88c95f42c6 100644 --- a/homeassistant/components/songpal/translations/fi.json +++ b/homeassistant/components/songpal/translations/fi.json @@ -4,20 +4,15 @@ "already_configured": "Laite on jo m\u00e4\u00e4ritetty", "not_songpal_device": "Ei Songpal-laite" }, - "error": { - "connection": "Yhteysvirhe: tarkista p\u00e4\u00e4tepisteesi" - }, "flow_title": "Sony Songpal {name} ({host})", "step": { "init": { - "description": "Haluatko m\u00e4\u00e4ritt\u00e4\u00e4 kohteen {name} ({host})?", - "title": "Sony Songpal" + "description": "Haluatko m\u00e4\u00e4ritt\u00e4\u00e4 kohteen {name} ({host})?" }, "user": { "data": { "endpoint": "P\u00e4\u00e4tepiste" - }, - "title": "Sony Songpal" + } } } } diff --git a/homeassistant/components/songpal/translations/fr.json b/homeassistant/components/songpal/translations/fr.json index f8a278a288e..aadbf7b3e56 100644 --- a/homeassistant/components/songpal/translations/fr.json +++ b/homeassistant/components/songpal/translations/fr.json @@ -7,11 +7,7 @@ "flow_title": "Sony Songpal {name} ({host})", "step": { "init": { - "description": "Voulez-vous configurer {name} ({host})?", - "title": "Sony Songpal" - }, - "user": { - "title": "Sony Songpal" + "description": "Voulez-vous configurer {name} ({host})?" } } } diff --git a/homeassistant/components/songpal/translations/it.json b/homeassistant/components/songpal/translations/it.json index 884f468d356..fff030a6d08 100644 --- a/homeassistant/components/songpal/translations/it.json +++ b/homeassistant/components/songpal/translations/it.json @@ -5,20 +5,17 @@ "not_songpal_device": "Non \u00e8 un dispositivo Songpal" }, "error": { - "cannot_connect": "Impossibile connettersi", - "connection": "Errore di connessione: controlla il tuo endpoint" + "cannot_connect": "Impossibile connettersi" }, "flow_title": "Sony Songpal {name} ({host})", "step": { "init": { - "description": "Vuoi impostare {name} ({host})?", - "title": "Sony Songpal" + "description": "Vuoi impostare {name} ({host})?" }, "user": { "data": { "endpoint": "Endpoint" - }, - "title": "Sony Songpal" + } } } } diff --git a/homeassistant/components/songpal/translations/ko.json b/homeassistant/components/songpal/translations/ko.json index 1ab96064169..abe7f9b384c 100644 --- a/homeassistant/components/songpal/translations/ko.json +++ b/homeassistant/components/songpal/translations/ko.json @@ -5,20 +5,17 @@ "not_songpal_device": "Songpal \uae30\uae30\uac00 \uc544\ub2d9\ub2c8\ub2e4" }, "error": { - "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", - "connection": "\uc5f0\uacb0 \uc624\ub958: \uc5d4\ub4dc\ud3ec\uc778\ud2b8\ub97c \ud655\uc778\ud574\uc8fc\uc138\uc694" + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" }, "flow_title": "Sony Songpal: {name} ({host})", "step": { "init": { - "description": "{name} ({host}) \uc744(\ub97c) \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", - "title": "Sony Songpal" + "description": "{name} ({host}) \uc744(\ub97c) \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" }, "user": { "data": { "endpoint": "\uc5d4\ub4dc\ud3ec\uc778\ud2b8" - }, - "title": "Sony Songpal" + } } } } diff --git a/homeassistant/components/songpal/translations/lb.json b/homeassistant/components/songpal/translations/lb.json index c3d31b81e53..f322bff8006 100644 --- a/homeassistant/components/songpal/translations/lb.json +++ b/homeassistant/components/songpal/translations/lb.json @@ -5,20 +5,17 @@ "not_songpal_device": "Keen Songpal Apparat" }, "error": { - "cannot_connect": "Feeler beim verbannen", - "connection": "Feeler beim verbannen Iwwerpr\u00e9if w.e.g. den Endpunkt" + "cannot_connect": "Feeler beim verbannen" }, "flow_title": "Sony Songpal {name} ({host})", "step": { "init": { - "description": "Soll {name} ({host}) konfigur\u00e9iert ginn?", - "title": "Sony Songpal" + "description": "Soll {name} ({host}) konfigur\u00e9iert ginn?" }, "user": { "data": { "endpoint": "Endpunkt" - }, - "title": "Sony Songpal" + } } } } diff --git a/homeassistant/components/songpal/translations/no.json b/homeassistant/components/songpal/translations/no.json index adbee2adc9f..4c3ef9e6c0d 100644 --- a/homeassistant/components/songpal/translations/no.json +++ b/homeassistant/components/songpal/translations/no.json @@ -1,23 +1,17 @@ { "config": { "abort": { - "already_configured": "Enheten er allerede konfigurert", "not_songpal_device": "Ikke en Songpal-enhet" }, - "error": { - "connection": "Tilkoblingsfeil: vennligst sjekk endepunktet ditt" - }, "flow_title": "", "step": { "init": { - "description": "Vil du sette opp {name} ({host})?", - "title": "" + "description": "Vil du sette opp {name} ({host})?" }, "user": { "data": { "endpoint": "Endepunkt" - }, - "title": "" + } } } } diff --git a/homeassistant/components/songpal/translations/pl.json b/homeassistant/components/songpal/translations/pl.json index c56e1a9d005..cc420f0f83a 100644 --- a/homeassistant/components/songpal/translations/pl.json +++ b/homeassistant/components/songpal/translations/pl.json @@ -5,20 +5,17 @@ "not_songpal_device": "To nie jest urz\u0105dzenie Songpal." }, "error": { - "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia.", - "connection": "B\u0142\u0105d po\u0142\u0105czenia: sprawd\u017a punkt ko\u0144cowy" + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia." }, "flow_title": "Sony Songpal {name} ({host})", "step": { "init": { - "description": "Czy chcesz skonfigurowa\u0107 {name} ({host})?", - "title": "Sony Songpal" + "description": "Czy chcesz skonfigurowa\u0107 {name} ({host})?" }, "user": { "data": { "endpoint": "Punkt ko\u0144cowy" - }, - "title": "Sony Songpal" + } } } } diff --git a/homeassistant/components/songpal/translations/ru.json b/homeassistant/components/songpal/translations/ru.json index 3f9fc7ecc08..9bd54b442bc 100644 --- a/homeassistant/components/songpal/translations/ru.json +++ b/homeassistant/components/songpal/translations/ru.json @@ -5,20 +5,17 @@ "not_songpal_device": "\u041d\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e Songpal." }, "error": { - "cannot_connect": "\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f.", - "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\u0443\u044e \u043a\u043e\u043d\u0435\u0447\u043d\u0443\u044e \u0442\u043e\u0447\u043a\u0443." + "cannot_connect": "\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f." }, "flow_title": "Sony Songpal {name} ({host})", "step": { "init": { - "description": "\u0425\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c {name} ({host})?", - "title": "Sony Songpal" + "description": "\u0425\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c {name} ({host})?" }, "user": { "data": { "endpoint": "\u041a\u043e\u043d\u0435\u0447\u043d\u0430\u044f \u0442\u043e\u0447\u043a\u0430" - }, - "title": "Sony Songpal" + } } } } diff --git a/homeassistant/components/songpal/translations/sv.json b/homeassistant/components/songpal/translations/sv.json index 9913f89f3d9..ea2b5e2d715 100644 --- a/homeassistant/components/songpal/translations/sv.json +++ b/homeassistant/components/songpal/translations/sv.json @@ -4,20 +4,15 @@ "already_configured": "Enheten \u00e4r redan konfigurerad", "not_songpal_device": "Inte en Songpal-enhet" }, - "error": { - "connection": "Anslutningsfel: Kontrollera destinationen" - }, "flow_title": "Sony Songpal {name} ({host})", "step": { "init": { - "description": "Do vill du konfigurera {name} ({host})?", - "title": "Sony Songpal" + "description": "Do vill du konfigurera {name} ({host})?" }, "user": { "data": { "endpoint": "Destination." - }, - "title": "Sony Songpal" + } } } } diff --git a/homeassistant/components/songpal/translations/zh-Hant.json b/homeassistant/components/songpal/translations/zh-Hant.json index c505979b85e..b73aaade30c 100644 --- a/homeassistant/components/songpal/translations/zh-Hant.json +++ b/homeassistant/components/songpal/translations/zh-Hant.json @@ -5,20 +5,17 @@ "not_songpal_device": "\u4e26\u975e Songpal \u8a2d\u5099" }, "error": { - "cannot_connect": "\u9023\u7dda\u5931\u6557", - "connection": "\u9023\u7dda\u932f\u8aa4\uff1a\u8acb\u6aa2\u67e5\u7aef\u9ede" + "cannot_connect": "\u9023\u7dda\u5931\u6557" }, "flow_title": "Sony Songpal {name} ({host})", "step": { "init": { - "description": "\u662f\u5426\u8981\u8a2d\u5b9a {name} ({host})\uff1f", - "title": "Sony Songpal" + "description": "\u662f\u5426\u8981\u8a2d\u5b9a {name} ({host})\uff1f" }, "user": { "data": { "endpoint": "\u7aef\u9ede" - }, - "title": "Sony Songpal" + } } } } diff --git a/homeassistant/components/sonos/translations/bg.json b/homeassistant/components/sonos/translations/bg.json index 0c9d9a8d8e5..1b8c4d5cbee 100644 --- a/homeassistant/components/sonos/translations/bg.json +++ b/homeassistant/components/sonos/translations/bg.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "\u0418\u0441\u043a\u0430\u0442\u0435 \u043b\u0438 \u0434\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u0435 Sonos?", - "title": "Sonos" + "description": "\u0418\u0441\u043a\u0430\u0442\u0435 \u043b\u0438 \u0434\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u0435 Sonos?" } } } diff --git a/homeassistant/components/sonos/translations/ca.json b/homeassistant/components/sonos/translations/ca.json index 683149c6fd8..c7bbae58a41 100644 --- a/homeassistant/components/sonos/translations/ca.json +++ b/homeassistant/components/sonos/translations/ca.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Vols configurar Sonos?", - "title": "Sonos" + "description": "Vols configurar Sonos?" } } } diff --git a/homeassistant/components/sonos/translations/cs.json b/homeassistant/components/sonos/translations/cs.json index 9f87f719b5e..20aab9ac861 100644 --- a/homeassistant/components/sonos/translations/cs.json +++ b/homeassistant/components/sonos/translations/cs.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Chcete nastavit Sonos?", - "title": "Sonos" + "description": "Chcete nastavit Sonos?" } } } diff --git a/homeassistant/components/sonos/translations/da.json b/homeassistant/components/sonos/translations/da.json index 5c8c3cdeb3e..0a6600dca08 100644 --- a/homeassistant/components/sonos/translations/da.json +++ b/homeassistant/components/sonos/translations/da.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Vil du ops\u00e6tte Sonos?", - "title": "Sonos" + "description": "Vil du ops\u00e6tte Sonos?" } } } diff --git a/homeassistant/components/sonos/translations/de.json b/homeassistant/components/sonos/translations/de.json index b08900034ed..93b25cf0b97 100644 --- a/homeassistant/components/sonos/translations/de.json +++ b/homeassistant/components/sonos/translations/de.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "M\u00f6chtest du Sonos einrichten?", - "title": "Sonos" + "description": "M\u00f6chtest du Sonos einrichten?" } } } diff --git a/homeassistant/components/sonos/translations/en.json b/homeassistant/components/sonos/translations/en.json index 69b8bd6c3b8..3cd46eae627 100644 --- a/homeassistant/components/sonos/translations/en.json +++ b/homeassistant/components/sonos/translations/en.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Do you want to set up Sonos?", - "title": "Sonos" + "description": "Do you want to set up Sonos?" } } } diff --git a/homeassistant/components/sonos/translations/es-419.json b/homeassistant/components/sonos/translations/es-419.json index ba750323426..873fee687e1 100644 --- a/homeassistant/components/sonos/translations/es-419.json +++ b/homeassistant/components/sonos/translations/es-419.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "\u00bfDesea configurar Sonos?", - "title": "Sonos" + "description": "\u00bfDesea configurar Sonos?" } } } diff --git a/homeassistant/components/sonos/translations/es.json b/homeassistant/components/sonos/translations/es.json index 296dc3f222e..e1cc4b22ef7 100644 --- a/homeassistant/components/sonos/translations/es.json +++ b/homeassistant/components/sonos/translations/es.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "\u00bfQuieres configurar Sonos?", - "title": "Sonos" + "description": "\u00bfQuieres configurar Sonos?" } } } diff --git a/homeassistant/components/sonos/translations/fi.json b/homeassistant/components/sonos/translations/fi.json index a01f678b03c..314809cfcd8 100644 --- a/homeassistant/components/sonos/translations/fi.json +++ b/homeassistant/components/sonos/translations/fi.json @@ -2,8 +2,7 @@ "config": { "step": { "confirm": { - "description": "Haluatko m\u00e4\u00e4ritt\u00e4\u00e4 Sonosin?", - "title": "Sonos" + "description": "Haluatko m\u00e4\u00e4ritt\u00e4\u00e4 Sonosin?" } } } diff --git a/homeassistant/components/sonos/translations/fr.json b/homeassistant/components/sonos/translations/fr.json index d15390fdcf0..2bae0a69826 100644 --- a/homeassistant/components/sonos/translations/fr.json +++ b/homeassistant/components/sonos/translations/fr.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Voulez-vous configurer Sonos?", - "title": "Sonos" + "description": "Voulez-vous configurer Sonos?" } } } diff --git a/homeassistant/components/sonos/translations/he.json b/homeassistant/components/sonos/translations/he.json index 609cc3747be..91cbe81a2a6 100644 --- a/homeassistant/components/sonos/translations/he.json +++ b/homeassistant/components/sonos/translations/he.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "\u05d4\u05d0\u05dd \u05d1\u05e8\u05e6\u05d5\u05e0\u05da \u05dc\u05d4\u05d2\u05d3\u05d9\u05e8 \u05d0\u05ea Sonos?", - "title": "Sonos" + "description": "\u05d4\u05d0\u05dd \u05d1\u05e8\u05e6\u05d5\u05e0\u05da \u05dc\u05d4\u05d2\u05d3\u05d9\u05e8 \u05d0\u05ea Sonos?" } } } diff --git a/homeassistant/components/sonos/translations/hu.json b/homeassistant/components/sonos/translations/hu.json index 771f48d1d9b..aa10087a884 100644 --- a/homeassistant/components/sonos/translations/hu.json +++ b/homeassistant/components/sonos/translations/hu.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Be szeretn\u00e9d \u00e1ll\u00edtani a Sonos-t?", - "title": "Sonos" + "description": "Be szeretn\u00e9d \u00e1ll\u00edtani a Sonos-t?" } } } diff --git a/homeassistant/components/sonos/translations/id.json b/homeassistant/components/sonos/translations/id.json index b5b421d2d79..ef88cab5814 100644 --- a/homeassistant/components/sonos/translations/id.json +++ b/homeassistant/components/sonos/translations/id.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Apakah Anda ingin mengatur Sonos?", - "title": "Sonos" + "description": "Apakah Anda ingin mengatur Sonos?" } } } diff --git a/homeassistant/components/sonos/translations/it.json b/homeassistant/components/sonos/translations/it.json index 1307f4cfe7b..70353a2613a 100644 --- a/homeassistant/components/sonos/translations/it.json +++ b/homeassistant/components/sonos/translations/it.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Vuoi configurare Sonos?", - "title": "Sonos" + "description": "Vuoi configurare Sonos?" } } } diff --git a/homeassistant/components/sonos/translations/ko.json b/homeassistant/components/sonos/translations/ko.json index baa5200f264..c92b50a0f83 100644 --- a/homeassistant/components/sonos/translations/ko.json +++ b/homeassistant/components/sonos/translations/ko.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Sonos \ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", - "title": "Sonos" + "description": "Sonos \ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" } } } diff --git a/homeassistant/components/sonos/translations/lb.json b/homeassistant/components/sonos/translations/lb.json index f2c1675a14c..902d7d8b3cc 100644 --- a/homeassistant/components/sonos/translations/lb.json +++ b/homeassistant/components/sonos/translations/lb.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Soll Sonos konfigur\u00e9iert ginn?", - "title": "Sonos" + "description": "Soll Sonos konfigur\u00e9iert ginn?" } } } diff --git a/homeassistant/components/sonos/translations/nl.json b/homeassistant/components/sonos/translations/nl.json index 5efd65b1c79..e52111fc50f 100644 --- a/homeassistant/components/sonos/translations/nl.json +++ b/homeassistant/components/sonos/translations/nl.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Wilt u Sonos instellen?", - "title": "Sonos" + "description": "Wilt u Sonos instellen?" } } } diff --git a/homeassistant/components/sonos/translations/nn.json b/homeassistant/components/sonos/translations/nn.json index cb08944d26b..0a43eda6343 100644 --- a/homeassistant/components/sonos/translations/nn.json +++ b/homeassistant/components/sonos/translations/nn.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Vil du sette opp Sonos?", - "title": "Sonos" + "description": "Vil du sette opp Sonos?" } } } diff --git a/homeassistant/components/sonos/translations/no.json b/homeassistant/components/sonos/translations/no.json index db792405988..e5e4792eaeb 100644 --- a/homeassistant/components/sonos/translations/no.json +++ b/homeassistant/components/sonos/translations/no.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "\u00d8nsker du \u00e5 sette opp Sonos?", - "title": "" + "description": "\u00d8nsker du \u00e5 sette opp Sonos?" } } } diff --git a/homeassistant/components/sonos/translations/pl.json b/homeassistant/components/sonos/translations/pl.json index 56a0e6d5341..d37bf4c1ea9 100644 --- a/homeassistant/components/sonos/translations/pl.json +++ b/homeassistant/components/sonos/translations/pl.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Czy chcesz skonfigurowa\u0107 Sonos?", - "title": "Sonos" + "description": "Czy chcesz skonfigurowa\u0107 Sonos?" } } } diff --git a/homeassistant/components/sonos/translations/pt-BR.json b/homeassistant/components/sonos/translations/pt-BR.json index 77e2c75540a..f2467135d35 100644 --- a/homeassistant/components/sonos/translations/pt-BR.json +++ b/homeassistant/components/sonos/translations/pt-BR.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Voc\u00ea quer configurar o Sonos?", - "title": "Sonos" + "description": "Voc\u00ea quer configurar o Sonos?" } } } diff --git a/homeassistant/components/sonos/translations/pt.json b/homeassistant/components/sonos/translations/pt.json index 765d5803675..51fbd16a20d 100644 --- a/homeassistant/components/sonos/translations/pt.json +++ b/homeassistant/components/sonos/translations/pt.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Deseja configurar o Sonos?", - "title": "Sonos" + "description": "Deseja configurar o Sonos?" } } } diff --git a/homeassistant/components/sonos/translations/ro.json b/homeassistant/components/sonos/translations/ro.json index 338539bf25b..4c6b00fb697 100644 --- a/homeassistant/components/sonos/translations/ro.json +++ b/homeassistant/components/sonos/translations/ro.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Dori\u021bi s\u0103 configura\u021bi Sonos?", - "title": "Sonos" + "description": "Dori\u021bi s\u0103 configura\u021bi Sonos?" } } } diff --git a/homeassistant/components/sonos/translations/ru.json b/homeassistant/components/sonos/translations/ru.json index fd5f3f6ef09..fa153fda3ce 100644 --- a/homeassistant/components/sonos/translations/ru.json +++ b/homeassistant/components/sonos/translations/ru.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c Sonos?", - "title": "Sonos" + "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c Sonos?" } } } diff --git a/homeassistant/components/sonos/translations/sl.json b/homeassistant/components/sonos/translations/sl.json index b3d4cc352f5..23a9837e1dc 100644 --- a/homeassistant/components/sonos/translations/sl.json +++ b/homeassistant/components/sonos/translations/sl.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Ali \u017eelite nastaviti Sonos?", - "title": "Sonos" + "description": "Ali \u017eelite nastaviti Sonos?" } } } diff --git a/homeassistant/components/sonos/translations/sv.json b/homeassistant/components/sonos/translations/sv.json index 789c9b84a65..4f2202ff8f5 100644 --- a/homeassistant/components/sonos/translations/sv.json +++ b/homeassistant/components/sonos/translations/sv.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Vill du konfigurera Sonos?", - "title": "Sonos" + "description": "Vill du konfigurera Sonos?" } } } diff --git a/homeassistant/components/sonos/translations/vi.json b/homeassistant/components/sonos/translations/vi.json index b047e636eea..126a574f475 100644 --- a/homeassistant/components/sonos/translations/vi.json +++ b/homeassistant/components/sonos/translations/vi.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "B\u1ea1n c\u00f3 mu\u1ed1n thi\u1ebft l\u1eadp Sonos kh\u00f4ng?", - "title": "Sonos" + "description": "B\u1ea1n c\u00f3 mu\u1ed1n thi\u1ebft l\u1eadp Sonos kh\u00f4ng?" } } } diff --git a/homeassistant/components/sonos/translations/zh-Hans.json b/homeassistant/components/sonos/translations/zh-Hans.json index 7ea329dc903..1b703cd1ac6 100644 --- a/homeassistant/components/sonos/translations/zh-Hans.json +++ b/homeassistant/components/sonos/translations/zh-Hans.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "\u60a8\u60f3\u8981\u914d\u7f6e Sonos \u5417\uff1f", - "title": "Sonos" + "description": "\u60a8\u60f3\u8981\u914d\u7f6e Sonos \u5417\uff1f" } } } diff --git a/homeassistant/components/sonos/translations/zh-Hant.json b/homeassistant/components/sonos/translations/zh-Hant.json index 3b56f9ece6b..8ffa1577abe 100644 --- a/homeassistant/components/sonos/translations/zh-Hant.json +++ b/homeassistant/components/sonos/translations/zh-Hant.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "\u662f\u5426\u8981\u8a2d\u5b9a Sonos\uff1f", - "title": "Sonos" + "description": "\u662f\u5426\u8981\u8a2d\u5b9a Sonos\uff1f" } } } diff --git a/homeassistant/components/synology_dsm/translations/ca.json b/homeassistant/components/synology_dsm/translations/ca.json index b4c939429ef..fef0bca2ce4 100644 --- a/homeassistant/components/synology_dsm/translations/ca.json +++ b/homeassistant/components/synology_dsm/translations/ca.json @@ -20,7 +20,6 @@ }, "link": { "data": { - "api_version": "Versi\u00f3 DSM", "password": "Contrasenya", "port": "Port", "ssl": "Utilitza SSL/TLS per connectar-te al servidor NAS", @@ -31,7 +30,6 @@ }, "user": { "data": { - "api_version": "Versi\u00f3 DSM", "host": "Amfitri\u00f3", "password": "Contrasenya", "port": "Port", diff --git a/homeassistant/components/synology_dsm/translations/de.json b/homeassistant/components/synology_dsm/translations/de.json index 54ee7a9b623..b5e7064066c 100644 --- a/homeassistant/components/synology_dsm/translations/de.json +++ b/homeassistant/components/synology_dsm/translations/de.json @@ -20,7 +20,6 @@ }, "link": { "data": { - "api_version": "DSM-Version", "password": "Passwort", "port": "Port (optional)", "ssl": "Verwenden Sie SSL/TLS, um eine Verbindung zu Ihrem NAS herzustellen", @@ -31,7 +30,6 @@ }, "user": { "data": { - "api_version": "DSM-Version", "host": "Host", "password": "Passwort", "port": "Port (optional)", diff --git a/homeassistant/components/synology_dsm/translations/en.json b/homeassistant/components/synology_dsm/translations/en.json index ed3dae27151..48a8118528a 100644 --- a/homeassistant/components/synology_dsm/translations/en.json +++ b/homeassistant/components/synology_dsm/translations/en.json @@ -20,7 +20,6 @@ }, "link": { "data": { - "api_version": "DSM version", "password": "Password", "port": "Port", "ssl": "Use SSL/TLS to connect to your NAS", @@ -31,7 +30,6 @@ }, "user": { "data": { - "api_version": "DSM version", "host": "Host", "password": "Password", "port": "Port", diff --git a/homeassistant/components/synology_dsm/translations/es-419.json b/homeassistant/components/synology_dsm/translations/es-419.json index cad627e0dfb..a7c83782df5 100644 --- a/homeassistant/components/synology_dsm/translations/es-419.json +++ b/homeassistant/components/synology_dsm/translations/es-419.json @@ -20,7 +20,6 @@ }, "link": { "data": { - "api_version": "Versi\u00f3n DSM", "password": "Contrase\u00f1a", "port": "Puerto (opcional)", "ssl": "Utilice SSL/TLS para conectarse a su NAS", @@ -31,7 +30,6 @@ }, "user": { "data": { - "api_version": "Versi\u00f3n DSM", "host": "Host", "password": "Contrase\u00f1a", "port": "Puerto (opcional)", diff --git a/homeassistant/components/synology_dsm/translations/es.json b/homeassistant/components/synology_dsm/translations/es.json index 6efe92020d2..390f67d4667 100644 --- a/homeassistant/components/synology_dsm/translations/es.json +++ b/homeassistant/components/synology_dsm/translations/es.json @@ -20,7 +20,6 @@ }, "link": { "data": { - "api_version": "Versi\u00f3n del DSM", "password": "Contrase\u00f1a", "port": "Puerto (opcional)", "ssl": "Usar SSL/TLS para conectar con tu NAS", @@ -31,7 +30,6 @@ }, "user": { "data": { - "api_version": "Versi\u00f3n del DSM", "host": "Host", "password": "Contrase\u00f1a", "port": "Puerto (opcional)", diff --git a/homeassistant/components/synology_dsm/translations/fr.json b/homeassistant/components/synology_dsm/translations/fr.json index 4859b10073c..bdb10425424 100644 --- a/homeassistant/components/synology_dsm/translations/fr.json +++ b/homeassistant/components/synology_dsm/translations/fr.json @@ -20,7 +20,6 @@ }, "link": { "data": { - "api_version": "Version du DSM", "password": "Mot de passe", "port": "Port (facultatif)", "ssl": "Utilisez SSL/TLS pour vous connecter \u00e0 votre NAS", @@ -31,7 +30,6 @@ }, "user": { "data": { - "api_version": "Version du DSM", "host": "H\u00f4te", "password": "Mot de passe", "port": "Port (facultatif)", diff --git a/homeassistant/components/synology_dsm/translations/it.json b/homeassistant/components/synology_dsm/translations/it.json index ef02d9fe977..1bb6dc026d8 100644 --- a/homeassistant/components/synology_dsm/translations/it.json +++ b/homeassistant/components/synology_dsm/translations/it.json @@ -20,7 +20,6 @@ }, "link": { "data": { - "api_version": "Versione DSM", "password": "Password", "port": "Porta", "ssl": "Utilizzare SSL/TLS per connettersi al NAS", @@ -31,7 +30,6 @@ }, "user": { "data": { - "api_version": "Versione DSM", "host": "Host", "password": "Password", "port": "Porta", diff --git a/homeassistant/components/synology_dsm/translations/ko.json b/homeassistant/components/synology_dsm/translations/ko.json index 8090b6d6c7e..81b7d5f1435 100644 --- a/homeassistant/components/synology_dsm/translations/ko.json +++ b/homeassistant/components/synology_dsm/translations/ko.json @@ -20,7 +20,6 @@ }, "link": { "data": { - "api_version": "DSM \ubc84\uc804", "password": "\ube44\ubc00\ubc88\ud638", "port": "\ud3ec\ud2b8", "ssl": "SSL/TLS \ub97c \uc0ac\uc6a9\ud558\uc5ec NAS \uc5d0 \uc5f0\uacb0", @@ -31,7 +30,6 @@ }, "user": { "data": { - "api_version": "DSM \ubc84\uc804", "host": "\ud638\uc2a4\ud2b8", "password": "\ube44\ubc00\ubc88\ud638", "port": "\ud3ec\ud2b8", diff --git a/homeassistant/components/synology_dsm/translations/lb.json b/homeassistant/components/synology_dsm/translations/lb.json index 42bbc08b6cf..0e3d8300248 100644 --- a/homeassistant/components/synology_dsm/translations/lb.json +++ b/homeassistant/components/synology_dsm/translations/lb.json @@ -20,7 +20,6 @@ }, "link": { "data": { - "api_version": "DSM Versioun", "password": "Passwuert", "port": "Port (Optionell)", "ssl": "Benotz SSL/TLS fir d'Verbindung mam NAS", @@ -31,7 +30,6 @@ }, "user": { "data": { - "api_version": "DSM Versioun", "host": "Apparat", "password": "Passwuert", "port": "Port (Optionell)", diff --git a/homeassistant/components/synology_dsm/translations/nl.json b/homeassistant/components/synology_dsm/translations/nl.json index 8b69639140b..5798dce567d 100644 --- a/homeassistant/components/synology_dsm/translations/nl.json +++ b/homeassistant/components/synology_dsm/translations/nl.json @@ -20,7 +20,6 @@ }, "link": { "data": { - "api_version": "DSM-versie", "password": "Wachtwoord", "port": "Poort (optioneel)", "ssl": "Gebruik SSL/TLS om verbinding te maken met uw NAS", @@ -31,7 +30,6 @@ }, "user": { "data": { - "api_version": "DSM-versie", "host": "Host", "password": "Wachtwoord", "port": "Poort (optioneel)", diff --git a/homeassistant/components/synology_dsm/translations/no.json b/homeassistant/components/synology_dsm/translations/no.json index 40d7907d61d..678484d5226 100644 --- a/homeassistant/components/synology_dsm/translations/no.json +++ b/homeassistant/components/synology_dsm/translations/no.json @@ -20,7 +20,6 @@ }, "link": { "data": { - "api_version": "DSM-versjon", "password": "Passord", "port": "Port (valgfritt)", "ssl": "Bruk SSL/TLS til \u00e5 koble til NAS-en", @@ -31,7 +30,6 @@ }, "user": { "data": { - "api_version": "DSM-versjon", "host": "Vert", "password": "Passord", "port": "Port (valgfritt)", diff --git a/homeassistant/components/synology_dsm/translations/pl.json b/homeassistant/components/synology_dsm/translations/pl.json index ab55803696f..60c7ee849f1 100644 --- a/homeassistant/components/synology_dsm/translations/pl.json +++ b/homeassistant/components/synology_dsm/translations/pl.json @@ -20,7 +20,6 @@ }, "link": { "data": { - "api_version": "Wersja DSM", "password": "Has\u0142o", "port": "Port", "ssl": "U\u017cyj SSL/TLS, aby po\u0142\u0105czy\u0107 si\u0119 z serwerem NAS", @@ -31,7 +30,6 @@ }, "user": { "data": { - "api_version": "Wersja DSM", "host": "Nazwa hosta lub adres IP", "password": "Has\u0142o", "port": "Port", diff --git a/homeassistant/components/synology_dsm/translations/pt-BR.json b/homeassistant/components/synology_dsm/translations/pt-BR.json index 61be8e0e51d..e633eb9128d 100644 --- a/homeassistant/components/synology_dsm/translations/pt-BR.json +++ b/homeassistant/components/synology_dsm/translations/pt-BR.json @@ -13,7 +13,6 @@ }, "link": { "data": { - "api_version": "Vers\u00e3o DSM", "ssl": "Use SSL/TLS para conectar-se ao seu NAS" }, "description": "Voc\u00ea quer configurar o {name} ({host})?", diff --git a/homeassistant/components/synology_dsm/translations/ru.json b/homeassistant/components/synology_dsm/translations/ru.json index 9d1ca6ca39f..c0e5da7a1f1 100644 --- a/homeassistant/components/synology_dsm/translations/ru.json +++ b/homeassistant/components/synology_dsm/translations/ru.json @@ -20,7 +20,6 @@ }, "link": { "data": { - "api_version": "\u0412\u0435\u0440\u0441\u0438\u044f DSM", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "port": "\u041f\u043e\u0440\u0442", "ssl": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c SSL / TLS \u0434\u043b\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f", @@ -31,7 +30,6 @@ }, "user": { "data": { - "api_version": "\u0412\u0435\u0440\u0441\u0438\u044f DSM", "host": "\u0425\u043e\u0441\u0442", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "port": "\u041f\u043e\u0440\u0442", diff --git a/homeassistant/components/synology_dsm/translations/sl.json b/homeassistant/components/synology_dsm/translations/sl.json index 1ad2e70a148..8f58af96960 100644 --- a/homeassistant/components/synology_dsm/translations/sl.json +++ b/homeassistant/components/synology_dsm/translations/sl.json @@ -20,7 +20,6 @@ }, "link": { "data": { - "api_version": "Razli\u010dica DSM", "password": "Geslo", "port": "Vrata (Izbirno)", "ssl": "Uporabite SSL/TLS za povezavo z va\u0161im NAS-om", @@ -31,7 +30,6 @@ }, "user": { "data": { - "api_version": "Razli\u010dica DSM", "host": "Gostitelj", "password": "Geslo", "port": "Vrata (Izbirno)", diff --git a/homeassistant/components/synology_dsm/translations/zh-Hant.json b/homeassistant/components/synology_dsm/translations/zh-Hant.json index 07d469bedd2..eec37c812f8 100644 --- a/homeassistant/components/synology_dsm/translations/zh-Hant.json +++ b/homeassistant/components/synology_dsm/translations/zh-Hant.json @@ -20,7 +20,6 @@ }, "link": { "data": { - "api_version": "DSM \u7248\u672c", "password": "\u5bc6\u78bc", "port": "\u901a\u8a0a\u57e0", "ssl": "\u4f7f\u7528 SSL/TLS \u9023\u7dda\u81f3 NAS", @@ -31,7 +30,6 @@ }, "user": { "data": { - "api_version": "DSM \u7248\u672c", "host": "\u4e3b\u6a5f\u7aef", "password": "\u5bc6\u78bc", "port": "\u901a\u8a0a\u57e0", diff --git a/homeassistant/components/tplink/translations/bg.json b/homeassistant/components/tplink/translations/bg.json index 288ae9de670..cdceae66cbf 100644 --- a/homeassistant/components/tplink/translations/bg.json +++ b/homeassistant/components/tplink/translations/bg.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "\u0418\u0441\u043a\u0430\u0442\u0435 \u043b\u0438 \u0434\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u0435 TP-Link \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430?", - "title": "TP-Link Smart Home" + "description": "\u0418\u0441\u043a\u0430\u0442\u0435 \u043b\u0438 \u0434\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u0435 TP-Link \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430?" } } } diff --git a/homeassistant/components/tplink/translations/ca.json b/homeassistant/components/tplink/translations/ca.json index 41470c2bd6a..62baf289fd4 100644 --- a/homeassistant/components/tplink/translations/ca.json +++ b/homeassistant/components/tplink/translations/ca.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Vols configurar dispositius intel\u00b7ligents TP-Link?", - "title": "TP-Link Smart Home" + "description": "Vols configurar dispositius intel\u00b7ligents TP-Link?" } } } diff --git a/homeassistant/components/tplink/translations/da.json b/homeassistant/components/tplink/translations/da.json index fa55b4ff92f..e6fc3c895a3 100644 --- a/homeassistant/components/tplink/translations/da.json +++ b/homeassistant/components/tplink/translations/da.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Vil du konfigurere TP-Link-smartenheder?", - "title": "TP-Link Smart Home" + "description": "Vil du konfigurere TP-Link-smartenheder?" } } } diff --git a/homeassistant/components/tplink/translations/de.json b/homeassistant/components/tplink/translations/de.json index bd391ed762c..64bdfc9bf77 100644 --- a/homeassistant/components/tplink/translations/de.json +++ b/homeassistant/components/tplink/translations/de.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "M\u00f6chtest du TP-Link Smart Devices einrichten?", - "title": "TP-Link Smart Home" + "description": "M\u00f6chtest du TP-Link Smart Devices einrichten?" } } } diff --git a/homeassistant/components/tplink/translations/en.json b/homeassistant/components/tplink/translations/en.json index 49c7f40d4ad..3c1f7da52e0 100644 --- a/homeassistant/components/tplink/translations/en.json +++ b/homeassistant/components/tplink/translations/en.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Do you want to setup TP-Link smart devices?", - "title": "TP-Link Smart Home" + "description": "Do you want to setup TP-Link smart devices?" } } } diff --git a/homeassistant/components/tplink/translations/es-419.json b/homeassistant/components/tplink/translations/es-419.json index c349c395733..4113e802e1a 100644 --- a/homeassistant/components/tplink/translations/es-419.json +++ b/homeassistant/components/tplink/translations/es-419.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "\u00bfDesea configurar dispositivos inteligentes TP-Link?", - "title": "TP-Link Smart Home" + "description": "\u00bfDesea configurar dispositivos inteligentes TP-Link?" } } } diff --git a/homeassistant/components/tplink/translations/es.json b/homeassistant/components/tplink/translations/es.json index 6fa2fd5fd16..408be2c1360 100644 --- a/homeassistant/components/tplink/translations/es.json +++ b/homeassistant/components/tplink/translations/es.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "\u00bfQuieres configurar dispositivos inteligentes de TP-Link?", - "title": "TP-Link Smart Home" + "description": "\u00bfQuieres configurar dispositivos inteligentes de TP-Link?" } } } diff --git a/homeassistant/components/tplink/translations/fr.json b/homeassistant/components/tplink/translations/fr.json index b67464cb97c..43ea1d1b111 100644 --- a/homeassistant/components/tplink/translations/fr.json +++ b/homeassistant/components/tplink/translations/fr.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Voulez-vous configurer TP-Link smart devices?", - "title": "TP-Link Smart Home" + "description": "Voulez-vous configurer TP-Link smart devices?" } } } diff --git a/homeassistant/components/tplink/translations/he.json b/homeassistant/components/tplink/translations/he.json index f947ce87933..55d53f6e676 100644 --- a/homeassistant/components/tplink/translations/he.json +++ b/homeassistant/components/tplink/translations/he.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "\u05d4\u05d0\u05dd \u05d0\u05ea\u05d4 \u05e8\u05d5\u05e6\u05d4 \u05dc\u05d4\u05d2\u05d3\u05d9\u05e8 \u05d4\u05ea\u05e7\u05e0\u05d9\u05dd \u05d7\u05db\u05de\u05d9\u05dd \u05e9\u05dc TP-Link ?", - "title": "\u05d1\u05d9\u05ea \u05d7\u05db\u05dd \u05e9\u05dc TP-Link" + "description": "\u05d4\u05d0\u05dd \u05d0\u05ea\u05d4 \u05e8\u05d5\u05e6\u05d4 \u05dc\u05d4\u05d2\u05d3\u05d9\u05e8 \u05d4\u05ea\u05e7\u05e0\u05d9\u05dd \u05d7\u05db\u05de\u05d9\u05dd \u05e9\u05dc TP-Link ?" } } } diff --git a/homeassistant/components/tplink/translations/it.json b/homeassistant/components/tplink/translations/it.json index fd46e40d33f..d5fc0a46a00 100644 --- a/homeassistant/components/tplink/translations/it.json +++ b/homeassistant/components/tplink/translations/it.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Vuoi configurare i dispositivi intelligenti TP-Link?", - "title": "TP-Link Smart Home" + "description": "Vuoi configurare i dispositivi intelligenti TP-Link?" } } } diff --git a/homeassistant/components/tplink/translations/ko.json b/homeassistant/components/tplink/translations/ko.json index 45e5c525e35..dc8a6a5a8fc 100644 --- a/homeassistant/components/tplink/translations/ko.json +++ b/homeassistant/components/tplink/translations/ko.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "TP-Link \uc2a4\ub9c8\ud2b8 \uae30\uae30\ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", - "title": "TP-Link Smart Home" + "description": "TP-Link \uc2a4\ub9c8\ud2b8 \uae30\uae30\ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" } } } diff --git a/homeassistant/components/tplink/translations/lb.json b/homeassistant/components/tplink/translations/lb.json index 740b1684d6e..80369964f11 100644 --- a/homeassistant/components/tplink/translations/lb.json +++ b/homeassistant/components/tplink/translations/lb.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Soll TP-Link Smart Home konfigur\u00e9iert ginn?", - "title": "TP-Link Smart Home" + "description": "Soll TP-Link Smart Home konfigur\u00e9iert ginn?" } } } diff --git a/homeassistant/components/tplink/translations/nl.json b/homeassistant/components/tplink/translations/nl.json index 0d6ace9da78..f6a8dbbe02a 100644 --- a/homeassistant/components/tplink/translations/nl.json +++ b/homeassistant/components/tplink/translations/nl.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Wil je TP-Link slimme apparaten instellen?", - "title": "TP-Link Smart Home" + "description": "Wil je TP-Link slimme apparaten instellen?" } } } diff --git a/homeassistant/components/tplink/translations/no.json b/homeassistant/components/tplink/translations/no.json index f2ba2085918..d480a5d996d 100644 --- a/homeassistant/components/tplink/translations/no.json +++ b/homeassistant/components/tplink/translations/no.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Vil du konfigurere TP-Link smart enheter?", - "title": "" + "description": "Vil du konfigurere TP-Link smart enheter?" } } } diff --git a/homeassistant/components/tplink/translations/pl.json b/homeassistant/components/tplink/translations/pl.json index 33b963e9813..924f596ca4f 100644 --- a/homeassistant/components/tplink/translations/pl.json +++ b/homeassistant/components/tplink/translations/pl.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Czy chcesz skonfigurowa\u0107 urz\u0105dzenia TP-Link smart?", - "title": "TP-Link Smart Home" + "description": "Czy chcesz skonfigurowa\u0107 urz\u0105dzenia TP-Link smart?" } } } diff --git a/homeassistant/components/tplink/translations/pt-BR.json b/homeassistant/components/tplink/translations/pt-BR.json index f86d5d5b2a2..f4852405726 100644 --- a/homeassistant/components/tplink/translations/pt-BR.json +++ b/homeassistant/components/tplink/translations/pt-BR.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Deseja configurar dispositivos inteligentes TP-Link?", - "title": "TP-Link Smart Home" + "description": "Deseja configurar dispositivos inteligentes TP-Link?" } } } diff --git a/homeassistant/components/tplink/translations/pt.json b/homeassistant/components/tplink/translations/pt.json index 27c9fd6fbb1..9df803c325e 100644 --- a/homeassistant/components/tplink/translations/pt.json +++ b/homeassistant/components/tplink/translations/pt.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Deseja configurar os dispositivos inteligentes TP-Link?", - "title": "TP-Link Smart Home" + "description": "Deseja configurar os dispositivos inteligentes TP-Link?" } } } diff --git a/homeassistant/components/tplink/translations/ru.json b/homeassistant/components/tplink/translations/ru.json index 5797fb63bd7..7f84067d9a2 100644 --- a/homeassistant/components/tplink/translations/ru.json +++ b/homeassistant/components/tplink/translations/ru.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c TP-Link Smart Home?", - "title": "TP-Link Smart Home" + "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c TP-Link Smart Home?" } } } diff --git a/homeassistant/components/tplink/translations/sl.json b/homeassistant/components/tplink/translations/sl.json index 9bbf88cae15..6c49eaf8d0a 100644 --- a/homeassistant/components/tplink/translations/sl.json +++ b/homeassistant/components/tplink/translations/sl.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "\u017delite namestiti pametne naprave TP-Link?", - "title": "TP-Link Pametni Dom" + "description": "\u017delite namestiti pametne naprave TP-Link?" } } } diff --git a/homeassistant/components/tplink/translations/sv.json b/homeassistant/components/tplink/translations/sv.json index c60162e6e31..70ab1740f0d 100644 --- a/homeassistant/components/tplink/translations/sv.json +++ b/homeassistant/components/tplink/translations/sv.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Vill du konfigurera TP-Link smart enheter?", - "title": "TP-Link Smart Home" + "description": "Vill du konfigurera TP-Link smart enheter?" } } } diff --git a/homeassistant/components/tplink/translations/zh-Hans.json b/homeassistant/components/tplink/translations/zh-Hans.json index 22ff788e9bb..710cc0fe166 100644 --- a/homeassistant/components/tplink/translations/zh-Hans.json +++ b/homeassistant/components/tplink/translations/zh-Hans.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "\u60a8\u60f3\u8981\u914d\u7f6e TP-Link \u667a\u80fd\u8bbe\u5907\u5417\uff1f", - "title": "TP-Link Smart Home" + "description": "\u60a8\u60f3\u8981\u914d\u7f6e TP-Link \u667a\u80fd\u8bbe\u5907\u5417\uff1f" } } } diff --git a/homeassistant/components/tplink/translations/zh-Hant.json b/homeassistant/components/tplink/translations/zh-Hant.json index 08a4779e593..9fafcbbce7d 100644 --- a/homeassistant/components/tplink/translations/zh-Hant.json +++ b/homeassistant/components/tplink/translations/zh-Hant.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "\u662f\u5426\u8981\u8a2d\u5b9a TP-Link \u667a\u80fd\u8a2d\u5099\uff1f", - "title": "TP-Link Smart Home" + "description": "\u662f\u5426\u8981\u8a2d\u5b9a TP-Link \u667a\u80fd\u8a2d\u5099\uff1f" } } } diff --git a/homeassistant/components/tuya/translations/ca.json b/homeassistant/components/tuya/translations/ca.json index 2cf09455b7b..89398296e9f 100644 --- a/homeassistant/components/tuya/translations/ca.json +++ b/homeassistant/components/tuya/translations/ca.json @@ -1,7 +1,6 @@ { "config": { "abort": { - "already_in_progress": "La configuraci\u00f3 de Tuya ja est\u00e0 en curs.", "auth_failed": "Autenticaci\u00f3 inv\u00e0lida", "conn_error": "No s'ha pogut connectar", "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3." @@ -15,6 +14,7 @@ "data": { "country_code": "El teu codi de pa\u00eds (per exemple, 1 per l'EUA o 86 per la Xina)", "password": "Contrasenya", + "platform": "L\u2019aplicaci\u00f3 on es registra el vostre compte", "username": "Nom d'usuari" }, "description": "Introdueix la teva credencial de Tuya.", diff --git a/homeassistant/components/tuya/translations/en.json b/homeassistant/components/tuya/translations/en.json index c3de7cbe020..c66eb2d274f 100644 --- a/homeassistant/components/tuya/translations/en.json +++ b/homeassistant/components/tuya/translations/en.json @@ -1,7 +1,6 @@ { "config": { "abort": { - "already_in_progress": "Tuya configuration is already in progress.", "auth_failed": "Invalid authentication", "conn_error": "Failed to connect", "single_instance_allowed": "Already configured. Only a single configuration possible." diff --git a/homeassistant/components/tuya/translations/es.json b/homeassistant/components/tuya/translations/es.json index 6493ca39754..f0a607331f3 100644 --- a/homeassistant/components/tuya/translations/es.json +++ b/homeassistant/components/tuya/translations/es.json @@ -1,7 +1,6 @@ { "config": { "abort": { - "already_in_progress": "La configuraci\u00f3n de Tuya ya est\u00e1 en progreso.", "auth_failed": "Autenticaci\u00f3n no v\u00e1lida", "conn_error": "No se pudo conectar", "single_instance_allowed": "Ya est\u00e1 configurado. S\u00f3lo es posible una \u00fanica configuraci\u00f3n." diff --git a/homeassistant/components/tuya/translations/fi.json b/homeassistant/components/tuya/translations/fi.json index f0c2c5e71d3..a2efee7e5ec 100644 --- a/homeassistant/components/tuya/translations/fi.json +++ b/homeassistant/components/tuya/translations/fi.json @@ -1,7 +1,6 @@ { "config": { "abort": { - "already_in_progress": "Tuya-m\u00e4\u00e4ritykset ovat jo k\u00e4ynniss\u00e4.", "conn_error": "Yhdist\u00e4minen ep\u00e4onnistui" }, "error": { diff --git a/homeassistant/components/tuya/translations/fr.json b/homeassistant/components/tuya/translations/fr.json index 9860353504b..6e181e2d646 100644 --- a/homeassistant/components/tuya/translations/fr.json +++ b/homeassistant/components/tuya/translations/fr.json @@ -1,8 +1,5 @@ { "config": { - "abort": { - "already_in_progress": "La configuration de Tuya est d\u00e9j\u00e0 en cours." - }, "flow_title": "Configuration Tuya", "step": { "user": { diff --git a/homeassistant/components/tuya/translations/it.json b/homeassistant/components/tuya/translations/it.json index 581121cc6f9..fc2c8fc49b3 100644 --- a/homeassistant/components/tuya/translations/it.json +++ b/homeassistant/components/tuya/translations/it.json @@ -1,7 +1,6 @@ { "config": { "abort": { - "already_in_progress": "La configurazione di Tuya \u00e8 gi\u00e0 in corso.", "auth_failed": "Autenticazione non valida", "conn_error": "Impossibile connettersi", "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." diff --git a/homeassistant/components/tuya/translations/ko.json b/homeassistant/components/tuya/translations/ko.json index 201e45ec357..a028fc2ed56 100644 --- a/homeassistant/components/tuya/translations/ko.json +++ b/homeassistant/components/tuya/translations/ko.json @@ -1,7 +1,6 @@ { "config": { "abort": { - "already_in_progress": "Tuya \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4.", "auth_failed": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "conn_error": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." diff --git a/homeassistant/components/tuya/translations/lb.json b/homeassistant/components/tuya/translations/lb.json index d09427d71d8..32595b961a1 100644 --- a/homeassistant/components/tuya/translations/lb.json +++ b/homeassistant/components/tuya/translations/lb.json @@ -1,7 +1,6 @@ { "config": { "abort": { - "already_in_progress": "Tuya Konfiguratioun ass schonn am gaang.", "auth_failed": "Ong\u00eblteg Authentifikatioun", "conn_error": "Feeler beim verbannen", "single_instance_allowed": "Scho konfigur\u00e9iert. N\u00ebmmen eng eenzeg Konfiguratioun ass m\u00e9iglech." diff --git a/homeassistant/components/tuya/translations/nl.json b/homeassistant/components/tuya/translations/nl.json index 9d991230731..0c1a78eb2a5 100644 --- a/homeassistant/components/tuya/translations/nl.json +++ b/homeassistant/components/tuya/translations/nl.json @@ -1,7 +1,6 @@ { "config": { "abort": { - "already_in_progress": "Tuya-configuratie is al bezig.", "auth_failed": "Verkeerde gegevens", "conn_error": "Niet gelukt om te verbinden.", "single_instance_allowed": "Al geconfigureerd. Er is maar een configuratie mogelijk." diff --git a/homeassistant/components/tuya/translations/no.json b/homeassistant/components/tuya/translations/no.json index 253401e721e..7b336c3f9c6 100644 --- a/homeassistant/components/tuya/translations/no.json +++ b/homeassistant/components/tuya/translations/no.json @@ -1,8 +1,5 @@ { "config": { - "abort": { - "already_in_progress": "Tuya konfigurasjon er allerede i gang." - }, "flow_title": "Tuya konfigurasjon", "step": { "user": { diff --git a/homeassistant/components/tuya/translations/pl.json b/homeassistant/components/tuya/translations/pl.json index 0291bef6217..7278806b5f6 100644 --- a/homeassistant/components/tuya/translations/pl.json +++ b/homeassistant/components/tuya/translations/pl.json @@ -1,7 +1,6 @@ { "config": { "abort": { - "already_in_progress": "Konfiguracja integracji Tuya jest ju\u017c w toku.", "auth_failed": "Niepoprawne uwierzytelnienie.", "conn_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia.", "single_instance_allowed": "Ju\u017c skonfigurowano. Mo\u017cliwa jest tylko jedna konfiguracja." diff --git a/homeassistant/components/tuya/translations/pt-BR.json b/homeassistant/components/tuya/translations/pt-BR.json index 7ba1ce6967c..8dc537e7549 100644 --- a/homeassistant/components/tuya/translations/pt-BR.json +++ b/homeassistant/components/tuya/translations/pt-BR.json @@ -1,8 +1,5 @@ { "config": { - "abort": { - "already_in_progress": "A configura\u00e7\u00e3o Tuya j\u00e1 est\u00e1 em andamento." - }, "flow_title": "Configura\u00e7\u00e3o Tuya", "step": { "user": { diff --git a/homeassistant/components/tuya/translations/ru.json b/homeassistant/components/tuya/translations/ru.json index c8a3a816b13..dab853f3310 100644 --- a/homeassistant/components/tuya/translations/ru.json +++ b/homeassistant/components/tuya/translations/ru.json @@ -1,7 +1,6 @@ { "config": { "abort": { - "already_in_progress": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f.", "auth_failed": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", "conn_error": "\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f.", "single_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430. \u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e." diff --git a/homeassistant/components/tuya/translations/zh-Hant.json b/homeassistant/components/tuya/translations/zh-Hant.json index 91c0936404d..2d7d2fe1004 100644 --- a/homeassistant/components/tuya/translations/zh-Hant.json +++ b/homeassistant/components/tuya/translations/zh-Hant.json @@ -1,7 +1,6 @@ { "config": { "abort": { - "already_in_progress": "Tuya \u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d\u3002", "auth_failed": "\u9a57\u8b49\u78bc\u7121\u6548", "conn_error": "\u9023\u7dda\u5931\u6557", "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u8a2d\u5099\u3002" diff --git a/homeassistant/components/unifi/translations/bg.json b/homeassistant/components/unifi/translations/bg.json index f99f5ed67e5..afe2db0cc5b 100644 --- a/homeassistant/components/unifi/translations/bg.json +++ b/homeassistant/components/unifi/translations/bg.json @@ -1,8 +1,7 @@ { "config": { "abort": { - "already_configured": "\u0421\u0430\u0439\u0442\u044a\u0442 \u0435 \u0432\u0435\u0447\u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d", - "user_privilege": "\u041f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b\u044f\u0442 \u0442\u0440\u044f\u0431\u0432\u0430 \u0434\u0430 \u0435 \u0430\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440" + "already_configured": "\u0421\u0430\u0439\u0442\u044a\u0442 \u0435 \u0432\u0435\u0447\u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d" }, "error": { "faulty_credentials": "\u041d\u0435\u0432\u0430\u043b\u0438\u0434\u043d\u0438 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u043e\u043d\u043d\u0438 \u0434\u0430\u043d\u043d\u0438", diff --git a/homeassistant/components/unifi/translations/ca.json b/homeassistant/components/unifi/translations/ca.json index 4b8c08d3c7a..82008f5c5b9 100644 --- a/homeassistant/components/unifi/translations/ca.json +++ b/homeassistant/components/unifi/translations/ca.json @@ -1,9 +1,7 @@ { "config": { "abort": { - "already_configured": "El lloc del controlador ja est\u00e0 configurat", - "no_local_user": "No s'ha trobat cap usuari local, configura un compte local al controlador i torna-ho a provar", - "user_privilege": "L'usuari ha de ser administrador" + "already_configured": "El lloc del controlador ja est\u00e0 configurat" }, "error": { "faulty_credentials": "[%key::common::config_flow::error::invalid_auth%]", @@ -29,7 +27,6 @@ "client_control": { "data": { "block_client": "Clients controlats amb acc\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.", @@ -38,6 +35,7 @@ "device_tracker": { "data": { "detection_time": "Temps (en segons) des de s'ha vist per \u00faltima vegada fins que es considera a fora", + "ignore_wired_bug": "Desactiva la l\u00f2gica d\u2019errors amb UniFi", "ssid_filter": "Selecciona els SSID's on fer-hi el seguiment de clients", "track_clients": "Segueix clients de la xarxa", "track_devices": "Segueix dispositius de la xarxa (dispositius Ubiquiti)", diff --git a/homeassistant/components/unifi/translations/cs.json b/homeassistant/components/unifi/translations/cs.json index 8fe1c060992..fdbfbc343de 100644 --- a/homeassistant/components/unifi/translations/cs.json +++ b/homeassistant/components/unifi/translations/cs.json @@ -1,8 +1,7 @@ { "config": { "abort": { - "already_configured": "\u0158adi\u010d je ji\u017e nakonfigurov\u00e1n", - "user_privilege": "U\u017eivatel mus\u00ed b\u00fdt spr\u00e1vcem" + "already_configured": "\u0158adi\u010d je ji\u017e nakonfigurov\u00e1n" }, "error": { "faulty_credentials": "Chybn\u00e9 p\u0159ihla\u0161ovac\u00ed \u00fadaje", diff --git a/homeassistant/components/unifi/translations/da.json b/homeassistant/components/unifi/translations/da.json index a15d25a283d..15ec878f1ce 100644 --- a/homeassistant/components/unifi/translations/da.json +++ b/homeassistant/components/unifi/translations/da.json @@ -1,8 +1,7 @@ { "config": { "abort": { - "already_configured": "Controller site er allerede konfigureret", - "user_privilege": "Bruger skal v\u00e6re administrator" + "already_configured": "Controller site er allerede konfigureret" }, "error": { "faulty_credentials": "Ugyldige legitimationsoplysninger", diff --git a/homeassistant/components/unifi/translations/de.json b/homeassistant/components/unifi/translations/de.json index 4ef34b3915b..133a5355deb 100644 --- a/homeassistant/components/unifi/translations/de.json +++ b/homeassistant/components/unifi/translations/de.json @@ -1,9 +1,7 @@ { "config": { "abort": { - "already_configured": "Controller-Site ist bereits konfiguriert", - "no_local_user": "Kein lokaler Benutzer gefunden, konfigurieren Sie ein lokales Konto auf dem Controller und versuchen Sie es erneut", - "user_privilege": "Der Benutzer muss Administrator sein" + "already_configured": "Controller-Site ist bereits konfiguriert" }, "error": { "faulty_credentials": "Ung\u00fcltige Anmeldeinformationen", @@ -29,7 +27,6 @@ "client_control": { "data": { "block_client": "Clients mit Netzwerkzugriffskontrolle", - "new_client": "F\u00fcgen Sie einen neuen Client f\u00fcr die Netzwerkzugangskontrolle hinzu", "poe_clients": "POE-Kontrolle von Clients zulassen" }, "description": "Konfigurieren Sie Client-Steuerelemente \n\nErstellen Sie Switches f\u00fcr Seriennummern, f\u00fcr die Sie den Netzwerkzugriff steuern m\u00f6chten.", diff --git a/homeassistant/components/unifi/translations/en.json b/homeassistant/components/unifi/translations/en.json index 279904ebd95..691f4fb6b01 100644 --- a/homeassistant/components/unifi/translations/en.json +++ b/homeassistant/components/unifi/translations/en.json @@ -1,9 +1,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" + "already_configured": "Controller site is already configured" }, "error": { "faulty_credentials": "Invalid authentication", @@ -29,7 +27,6 @@ "client_control": { "data": { "block_client": "Network access controlled clients", - "new_client": "Add new client for network access control", "poe_clients": "Allow POE control of clients" }, "description": "Configure client controls\n\nCreate switches for serial numbers you want to control network access for.", diff --git a/homeassistant/components/unifi/translations/es-419.json b/homeassistant/components/unifi/translations/es-419.json index 7f92a0b45af..0c4e9a73614 100644 --- a/homeassistant/components/unifi/translations/es-419.json +++ b/homeassistant/components/unifi/translations/es-419.json @@ -1,8 +1,7 @@ { "config": { "abort": { - "already_configured": "El sitio del controlador ya est\u00e1 configurado", - "user_privilege": "El usuario necesita ser administrador" + "already_configured": "El sitio del controlador ya est\u00e1 configurado" }, "error": { "faulty_credentials": "Credenciales de usuario incorrectas", @@ -28,7 +27,6 @@ "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.", diff --git a/homeassistant/components/unifi/translations/es.json b/homeassistant/components/unifi/translations/es.json index 1d2bb2977cb..1867bd89ffd 100644 --- a/homeassistant/components/unifi/translations/es.json +++ b/homeassistant/components/unifi/translations/es.json @@ -1,9 +1,7 @@ { "config": { "abort": { - "already_configured": "El sitio del controlador ya est\u00e1 configurado", - "no_local_user": "No se encontr\u00f3 ning\u00fan usuario local, configure una cuenta local en el controlador e int\u00e9ntelo de nuevo", - "user_privilege": "El usuario debe ser administrador" + "already_configured": "El sitio del controlador ya est\u00e1 configurado" }, "error": { "faulty_credentials": "Autenticaci\u00f3n no v\u00e1lida", @@ -29,7 +27,6 @@ "client_control": { "data": { "block_client": "Clientes con acceso controlado a la red", - "new_client": "A\u00f1adir nuevo cliente para el control de acceso a la red", "poe_clients": "Permitir control PoE de clientes" }, "description": "Configurar controles de cliente\n\nCrea conmutadores para los n\u00fameros de serie para los que deseas controlar el acceso a la red.", diff --git a/homeassistant/components/unifi/translations/fi.json b/homeassistant/components/unifi/translations/fi.json index 7fde27cb308..6b6d9abf9e5 100644 --- a/homeassistant/components/unifi/translations/fi.json +++ b/homeassistant/components/unifi/translations/fi.json @@ -1,8 +1,5 @@ { "config": { - "abort": { - "user_privilege": "K\u00e4ytt\u00e4j\u00e4n on oltava j\u00e4rjestelm\u00e4nvalvoja" - }, "error": { "faulty_credentials": "Virheellinen tunnistautuminen", "service_unavailable": "Yhdist\u00e4minen ep\u00e4onnistui" diff --git a/homeassistant/components/unifi/translations/fr.json b/homeassistant/components/unifi/translations/fr.json index a655ed46e4d..f1846177791 100644 --- a/homeassistant/components/unifi/translations/fr.json +++ b/homeassistant/components/unifi/translations/fr.json @@ -1,9 +1,7 @@ { "config": { "abort": { - "already_configured": "Le contr\u00f4leur est d\u00e9j\u00e0 configur\u00e9", - "no_local_user": "Aucun utilisateur local trouv\u00e9, configurez un compte local sur le contr\u00f4leur et r\u00e9essayez", - "user_privilege": "L'utilisateur doit \u00eatre administrateur" + "already_configured": "Le contr\u00f4leur est d\u00e9j\u00e0 configur\u00e9" }, "error": { "faulty_credentials": "Mauvaises informations d'identification de l'utilisateur", @@ -28,8 +26,7 @@ "step": { "client_control": { "data": { - "block_client": "Clients contr\u00f4l\u00e9s par acc\u00e8s r\u00e9seau", - "new_client": "Ajouter un nouveau client pour le contr\u00f4le d'acc\u00e8s au r\u00e9seau" + "block_client": "Clients contr\u00f4l\u00e9s par acc\u00e8s r\u00e9seau" }, "title": "Options UniFi 2/3" }, diff --git a/homeassistant/components/unifi/translations/hu.json b/homeassistant/components/unifi/translations/hu.json index 87055634ce0..91d031334dd 100644 --- a/homeassistant/components/unifi/translations/hu.json +++ b/homeassistant/components/unifi/translations/hu.json @@ -1,8 +1,5 @@ { "config": { - "abort": { - "user_privilege": "A felhaszn\u00e1l\u00f3nak rendszergazd\u00e1nak kell lennie" - }, "error": { "faulty_credentials": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s", "service_unavailable": "Sikertelen csatlakoz\u00e1s" diff --git a/homeassistant/components/unifi/translations/it.json b/homeassistant/components/unifi/translations/it.json index 9eddddde302..8a06ca440c5 100644 --- a/homeassistant/components/unifi/translations/it.json +++ b/homeassistant/components/unifi/translations/it.json @@ -1,9 +1,7 @@ { "config": { "abort": { - "already_configured": "Il sito del Controller \u00e8 gi\u00e0 configurato", - "no_local_user": "Nessun utente locale trovato, configura un account locale sul controller e riprova", - "user_privilege": "L'utente deve essere amministratore" + "already_configured": "Il sito del Controller \u00e8 gi\u00e0 configurato" }, "error": { "faulty_credentials": "Autenticazione non valida", @@ -29,7 +27,6 @@ "client_control": { "data": { "block_client": "Client controllati per l'accesso alla rete", - "new_client": "Aggiungere un nuovo client per il controllo dell'accesso alla rete", "poe_clients": "Consentire il controllo POE dei client" }, "description": "Configurare i controlli client \n\nCreare interruttori per i numeri di serie dei quali si desidera controllare l'accesso alla rete.", diff --git a/homeassistant/components/unifi/translations/ko.json b/homeassistant/components/unifi/translations/ko.json index 6878b537209..a3d2c8f3b69 100644 --- a/homeassistant/components/unifi/translations/ko.json +++ b/homeassistant/components/unifi/translations/ko.json @@ -1,9 +1,7 @@ { "config": { "abort": { - "already_configured": "\ucee8\ud2b8\ub864\ub7ec \uc0ac\uc774\ud2b8\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", - "no_local_user": "\ub85c\uceec \uc0ac\uc6a9\uc790\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \ucee8\ud2b8\ub864\ub7ec\uc5d0\uc11c \ub85c\uceec \uacc4\uc815\uc744 \uad6c\uc131\ud55c \ud6c4 \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", - "user_privilege": "\uc0ac\uc6a9\uc790\ub294 \uad00\ub9ac\uc790\uc5ec\uc57c \ud569\ub2c8\ub2e4" + "already_configured": "\ucee8\ud2b8\ub864\ub7ec \uc0ac\uc774\ud2b8\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { "faulty_credentials": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4", @@ -29,7 +27,6 @@ "client_control": { "data": { "block_client": "\ub124\ud2b8\uc6cc\ud06c \uc561\uc138\uc2a4 \uc81c\uc5b4 \ud074\ub77c\uc774\uc5b8\ud2b8", - "new_client": "\ub124\ud2b8\uc6cc\ud06c \uc561\uc138\uc2a4 \uc81c\uc5b4\ub97c \uc704\ud55c \uc0c8\ub85c\uc6b4 \ud074\ub77c\uc774\uc5b8\ud2b8 \ucd94\uac00", "poe_clients": "\ud074\ub77c\uc774\uc5b8\ud2b8\uc758 POE \uc81c\uc5b4 \ud5c8\uc6a9" }, "description": "\ud074\ub77c\uc774\uc5b8\ud2b8 \ucee8\ud2b8\ub864 \uad6c\uc131 \n\n\ub124\ud2b8\uc6cc\ud06c \uc561\uc138\uc2a4\ub97c \uc81c\uc5b4\ud558\ub824\ub294 \uc2dc\ub9ac\uc5bc \ubc88\ud638\uc5d0 \ub300\ud55c \uc2a4\uc704\uce58\ub97c \ub9cc\ub4ed\ub2c8\ub2e4.", diff --git a/homeassistant/components/unifi/translations/lb.json b/homeassistant/components/unifi/translations/lb.json index d93a1d8d882..83011f8cceb 100644 --- a/homeassistant/components/unifi/translations/lb.json +++ b/homeassistant/components/unifi/translations/lb.json @@ -1,9 +1,7 @@ { "config": { "abort": { - "already_configured": "Kontroller Site ass scho konfigur\u00e9iert", - "no_local_user": "Kee lokale Benotzer fonnt, erstell ee lokale Kont um Kontroller a prob\u00e9ier nach eemol", - "user_privilege": "Benotzer muss een Administrator sinn" + "already_configured": "Kontroller Site ass scho konfigur\u00e9iert" }, "error": { "faulty_credentials": "Ong\u00eblteg Login Informatioune", @@ -29,7 +27,6 @@ "client_control": { "data": { "block_client": "Netzwierk Zougang kontroll\u00e9iert Clienten", - "new_client": "Neie Client fir Netzwierk Zougang Kontroll b\u00e4isetzen", "poe_clients": "POE Kontroll vun Clienten erlaben" }, "description": "Client Kontroll konfigur\u00e9ieren\n\nErstell Schalter fir Serienummer d\u00e9i sollen fir Netzwierk Zougangs Kontroll kontroll\u00e9iert ginn.", diff --git a/homeassistant/components/unifi/translations/nl.json b/homeassistant/components/unifi/translations/nl.json index d3ea6b3eaae..5d64d73d1de 100644 --- a/homeassistant/components/unifi/translations/nl.json +++ b/homeassistant/components/unifi/translations/nl.json @@ -1,9 +1,7 @@ { "config": { "abort": { - "already_configured": "Controller site is al geconfigureerd", - "no_local_user": "Geen lokale gebruiker gevonden, configureer een lokaal account op de controller en probeer het opnieuw", - "user_privilege": "Gebruiker moet beheerder zijn" + "already_configured": "Controller site is al geconfigureerd" }, "error": { "faulty_credentials": "Foutieve gebruikersgegevens", @@ -29,7 +27,6 @@ "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.", diff --git a/homeassistant/components/unifi/translations/no.json b/homeassistant/components/unifi/translations/no.json index c244cac1696..d9260f92640 100644 --- a/homeassistant/components/unifi/translations/no.json +++ b/homeassistant/components/unifi/translations/no.json @@ -1,9 +1,7 @@ { "config": { "abort": { - "already_configured": "Kontroller nettstedet er allerede konfigurert", - "no_local_user": "Ingen lokale brukere funnet. Konfigurer en lokal konto p\u00e5 kontrolleren og pr\u00f8v igjen", - "user_privilege": "Bruker m\u00e5 v\u00e6re administrator" + "already_configured": "Kontroller nettstedet er allerede konfigurert" }, "error": { "faulty_credentials": "Ugyldig brukerlegitimasjon", @@ -29,7 +27,6 @@ "client_control": { "data": { "block_client": "Nettverkskontrollerte klienter", - "new_client": "Legg til ny klient for nettverkstilgangskontroll", "poe_clients": "Tillat POE-kontroll av klienter" }, "description": "Konfigurere klient-kontroller\n\nOpprette brytere for serienumre du \u00f8nsker \u00e5 kontrollere tilgang til nettverk for.", diff --git a/homeassistant/components/unifi/translations/pl.json b/homeassistant/components/unifi/translations/pl.json index 4c5a4ed15e7..c062d392911 100644 --- a/homeassistant/components/unifi/translations/pl.json +++ b/homeassistant/components/unifi/translations/pl.json @@ -1,9 +1,7 @@ { "config": { "abort": { - "already_configured": "Witryna kontrolera jest ju\u017c skonfigurowana.", - "no_local_user": "Nie znaleziono lokalnego u\u017cytkownika, skonfiguruj konto lokalne na kontrolerze i spr\u00f3buj ponownie.", - "user_privilege": "U\u017cytkownik musi by\u0107 administratorem" + "already_configured": "Witryna kontrolera jest ju\u017c skonfigurowana." }, "error": { "faulty_credentials": "Niepoprawne uwierzytelnienie.", @@ -29,7 +27,6 @@ "client_control": { "data": { "block_client": "Klienci z kontrol\u0105 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.", diff --git a/homeassistant/components/unifi/translations/pt-BR.json b/homeassistant/components/unifi/translations/pt-BR.json index 9816086d75a..67b39f07f66 100644 --- a/homeassistant/components/unifi/translations/pt-BR.json +++ b/homeassistant/components/unifi/translations/pt-BR.json @@ -1,8 +1,7 @@ { "config": { "abort": { - "already_configured": "O site de controle j\u00e1 est\u00e1 configurado", - "user_privilege": "O usu\u00e1rio precisa ser administrador" + "already_configured": "O site de controle j\u00e1 est\u00e1 configurado" }, "error": { "faulty_credentials": "Credenciais do usu\u00e1rio inv\u00e1lidas", @@ -27,8 +26,7 @@ "step": { "client_control": { "data": { - "block_client": "Clientes com acesso controlado \u00e0 rede", - "new_client": "Adicionar novo cliente para controle de acesso \u00e0 rede" + "block_client": "Clientes com acesso controlado \u00e0 rede" }, "description": "Configurar controles do cliente \n\nCrie comutadores para os n\u00fameros de s\u00e9rie para os quais deseja controlar o acesso \u00e0 rede.", "title": "UniFi op\u00e7\u00f5es de 2/3" diff --git a/homeassistant/components/unifi/translations/pt.json b/homeassistant/components/unifi/translations/pt.json index 123385313e9..354870a0d51 100644 --- a/homeassistant/components/unifi/translations/pt.json +++ b/homeassistant/components/unifi/translations/pt.json @@ -1,8 +1,7 @@ { "config": { "abort": { - "already_configured": "O site do controlador j\u00e1 se encontra configurado", - "user_privilege": "Utilizador tem que ser administrador" + "already_configured": "O site do controlador j\u00e1 se encontra configurado" }, "error": { "faulty_credentials": "Credenciais do utilizador erradas", diff --git a/homeassistant/components/unifi/translations/ro.json b/homeassistant/components/unifi/translations/ro.json index 090aeab1a7c..03a05564c27 100644 --- a/homeassistant/components/unifi/translations/ro.json +++ b/homeassistant/components/unifi/translations/ro.json @@ -1,8 +1,5 @@ { "config": { - "abort": { - "user_privilege": "Utilizatorul trebuie s\u0103 fie administrator" - }, "error": { "faulty_credentials": "Credentiale utilizator invalide", "service_unavailable": "Nici un serviciu disponibil" diff --git a/homeassistant/components/unifi/translations/ru.json b/homeassistant/components/unifi/translations/ru.json index f2e404a04fc..eecd5b53c56 100644 --- a/homeassistant/components/unifi/translations/ru.json +++ b/homeassistant/components/unifi/translations/ru.json @@ -1,9 +1,7 @@ { "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.", - "no_local_user": "\u041d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c, \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u0443\u044e \u0443\u0447\u0435\u0442\u043d\u0443\u044e \u0437\u0430\u043f\u0438\u0441\u044c \u043d\u0430 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440\u0435 \u0438 \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u0435 \u043f\u043e\u043f\u044b\u0442\u043a\u0443.", - "user_privilege": "\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0430\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440\u043e\u043c." + "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." }, "error": { "faulty_credentials": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", @@ -29,7 +27,6 @@ "client_control": { "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", - "new_client": "\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043d\u043e\u0432\u043e\u0433\u043e \u043a\u043b\u0438\u0435\u043d\u0442\u0430 \u0434\u043b\u044f \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044f \u0441\u0435\u0442\u0435\u0432\u043e\u0433\u043e \u0434\u043e\u0441\u0442\u0443\u043f\u0430", "poe_clients": "\u0420\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u044c POE \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044c \u043a\u043b\u0438\u0435\u043d\u0442\u043e\u0432" }, "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f.\n\n\u0421\u043e\u0437\u0434\u0430\u0439\u0442\u0435 \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0430\u0442\u0435\u043b\u0438 \u0434\u043b\u044f \u0441\u0435\u0440\u0438\u0439\u043d\u044b\u0445 \u043d\u043e\u043c\u0435\u0440\u043e\u0432, \u0434\u043b\u044f \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0412\u044b \u0445\u043e\u0442\u0438\u0442\u0435 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u0441\u0435\u0442\u0438.", @@ -38,6 +35,7 @@ "device_tracker": { "data": { "detection_time": "\u0412\u0440\u0435\u043c\u044f \u043e\u0442 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u0433\u043e \u0441\u0435\u0430\u043d\u0441\u0430 \u0441\u0432\u044f\u0437\u0438 \u0441 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u043c (\u0441\u0435\u043a.), \u043f\u043e \u0438\u0441\u0442\u0435\u0447\u0435\u043d\u0438\u044e \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u043f\u043e\u043b\u0443\u0447\u0438\u0442 \u0441\u0442\u0430\u0442\u0443\u0441 \"\u041d\u0435 \u0434\u043e\u043c\u0430\".", + "ignore_wired_bug": "\u041e\u0442\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u043b\u043e\u0433\u0438\u043a\u0443 \u043e\u0448\u0438\u0431\u043a\u0438 \u0434\u043b\u044f \u043d\u0435 \u0431\u0435\u0441\u043f\u0440\u043e\u0432\u043e\u0434\u043d\u044b\u0445 \u043a\u043b\u0438\u0435\u043d\u0442\u043e\u0432 UniFi", "ssid_filter": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 SSID \u0434\u043b\u044f \u043e\u0442\u0441\u043b\u0435\u0436\u0438\u0432\u0430\u043d\u0438\u044f \u0431\u0435\u0441\u043f\u0440\u043e\u0432\u043e\u0434\u043d\u044b\u0445 \u043a\u043b\u0438\u0435\u043d\u0442\u043e\u0432", "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)", diff --git a/homeassistant/components/unifi/translations/sl.json b/homeassistant/components/unifi/translations/sl.json index 7a5a79e252c..e0acdc13079 100644 --- a/homeassistant/components/unifi/translations/sl.json +++ b/homeassistant/components/unifi/translations/sl.json @@ -1,9 +1,7 @@ { "config": { "abort": { - "already_configured": "Nadzornik je \u017ee konfiguriran", - "no_local_user": "Nobenega lokalnega uporabnika ni mogo\u010de najti, konfigurirajte lokalni ra\u010dun na krmilniku in poskusite znova", - "user_privilege": "Uporabnik mora biti skrbnik" + "already_configured": "Nadzornik je \u017ee konfiguriran" }, "error": { "faulty_credentials": "Napa\u010dni uporabni\u0161ki podatki", @@ -29,7 +27,6 @@ "client_control": { "data": { "block_client": "Odjemalci pod nadzorom dostopa do omre\u017eja", - "new_client": "Dodajte novega odjemalca za nadzor dostopa do omre\u017eja", "poe_clients": "Dovoli POE nadzor strank" }, "description": "Konfigurirajte nadzor odjemalcev \n\n Ustvarite stikala za serijske \u0161tevilke, za katere \u017eelite nadzirati dostop do omre\u017eja.", diff --git a/homeassistant/components/unifi/translations/sv.json b/homeassistant/components/unifi/translations/sv.json index d3dd18a28e0..a0388cb7611 100644 --- a/homeassistant/components/unifi/translations/sv.json +++ b/homeassistant/components/unifi/translations/sv.json @@ -1,8 +1,7 @@ { "config": { "abort": { - "already_configured": "Controller-platsen \u00e4r redan konfigurerad", - "user_privilege": "Anv\u00e4ndaren m\u00e5ste vara administrat\u00f6r" + "already_configured": "Controller-platsen \u00e4r redan konfigurerad" }, "error": { "faulty_credentials": "Felaktiga anv\u00e4ndaruppgifter", diff --git a/homeassistant/components/unifi/translations/zh-Hans.json b/homeassistant/components/unifi/translations/zh-Hans.json index 402d8277bc7..7fe1b741bd5 100644 --- a/homeassistant/components/unifi/translations/zh-Hans.json +++ b/homeassistant/components/unifi/translations/zh-Hans.json @@ -1,8 +1,7 @@ { "config": { "abort": { - "already_configured": "\u63a7\u5236\u5668\u7ad9\u70b9\u5df2\u914d\u7f6e\u5b8c\u6210", - "user_privilege": "\u7528\u6237\u987b\u4e3a\u7ba1\u7406\u5458" + "already_configured": "\u63a7\u5236\u5668\u7ad9\u70b9\u5df2\u914d\u7f6e\u5b8c\u6210" }, "error": { "faulty_credentials": "\u9519\u8bef\u7684\u7528\u6237\u51ed\u636e", diff --git a/homeassistant/components/unifi/translations/zh-Hant.json b/homeassistant/components/unifi/translations/zh-Hant.json index c5d44c13e73..7ba51c9b621 100644 --- a/homeassistant/components/unifi/translations/zh-Hant.json +++ b/homeassistant/components/unifi/translations/zh-Hant.json @@ -1,9 +1,7 @@ { "config": { "abort": { - "already_configured": "\u63a7\u5236\u5668\u4f4d\u5740\u5df2\u7d93\u8a2d\u5b9a", - "no_local_user": "\u627e\u4e0d\u5230\u672c\u5730\u4f7f\u7528\u8005\u3001\u65bc\u63a7\u5236\u5668\u4e0a\u8a2d\u5b9a\u4e00\u7d44\u672c\u5730\u5e33\u865f\u4e26\u518d\u8a66\u4e00\u6b21", - "user_privilege": "\u4f7f\u7528\u8005\u5fc5\u9808\u70ba\u7ba1\u7406\u54e1\u8eab\u4efd" + "already_configured": "\u63a7\u5236\u5668\u4f4d\u5740\u5df2\u7d93\u8a2d\u5b9a" }, "error": { "faulty_credentials": "\u9a57\u8b49\u78bc\u7121\u6548", @@ -29,7 +27,6 @@ "client_control": { "data": { "block_client": "\u7db2\u8def\u5b58\u53d6\u63a7\u5236\u5ba2\u6236\u7aef", - "new_client": "\u65b0\u589e\u9396\u8981\u63a7\u5236\u7db2\u8def\u5b58\u53d6\u7684\u5ba2\u6236\u7aef", "poe_clients": "\u5141\u8a31 POE \u63a7\u5236\u5ba2\u6236\u7aef" }, "description": "\u8a2d\u5b9a\u5ba2\u6236\u7aef\u63a7\u5236\n\n\u65b0\u589e\u9396\u8981\u63a7\u5236\u7db2\u8def\u5b58\u53d6\u7684\u958b\u95dc\u5e8f\u865f\u3002", diff --git a/homeassistant/components/upb/translations/lb.json b/homeassistant/components/upb/translations/lb.json index 6c1a46067d1..71f05f72581 100644 --- a/homeassistant/components/upb/translations/lb.json +++ b/homeassistant/components/upb/translations/lb.json @@ -12,6 +12,7 @@ "user": { "data": { "address": "Adress (Kuck Beschr\u00e9iwung uewen)", + "file_path": "Pad a Numm vum UPStart UPB Export Fichier.", "protocol": "Protokoll" }, "description": "Verbann een Universal Bus Powerline Interface Module (UPB PIM). D'Adresse muss an der der From 'adress[:port]' fir 'tcp' sinn. De Port ass optionell an als standard op 2101 gesat. Beispill: '192.168.1.42'. Fir de serielle Protokoll muss d'Adress an der form 'tty[:baud]' sinn. Baudrate ass optionell an standardm\u00e9isseg o p 4800. Beispill: '/dev/ttyS1'.", diff --git a/homeassistant/components/upnp/translations/bg.json b/homeassistant/components/upnp/translations/bg.json index 7e85d64daa1..892d193dd6b 100644 --- a/homeassistant/components/upnp/translations/bg.json +++ b/homeassistant/components/upnp/translations/bg.json @@ -2,29 +2,12 @@ "config": { "abort": { "already_configured": "UPnP/IGD \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d", - "incomplete_device": "\u0418\u0433\u043d\u043e\u0440\u0438\u0440\u0430\u043d\u0435 \u043d\u0430 \u043d\u0435\u043f\u044a\u043b\u043d\u043e UPnP \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e", "no_devices_discovered": "\u041d\u044f\u043c\u0430 \u043e\u0442\u043a\u0440\u0438\u0442\u0438 UPnP/IGD", - "no_devices_found": "\u041d\u0435 \u0441\u0430 \u043d\u0430\u043c\u0435\u0440\u0435\u043d\u0438 UPnP/IGD \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0432 \u043c\u0440\u0435\u0436\u0430\u0442\u0430.", - "no_sensors_or_port_mapping": "\u0410\u043a\u0442\u0438\u0432\u0438\u0440\u0430\u0439\u0442\u0435 \u0441\u0435\u043d\u0437\u043e\u0440\u0438\u0442\u0435 \u0438\u043b\u0438 \u043f\u0440\u0435\u043d\u0430\u0441\u043e\u0447\u0432\u0430\u043d\u0435 \u043d\u0430 \u043f\u043e\u0440\u0442\u0430", - "single_instance_allowed": "\u041d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u0430 \u0435 \u0441\u0430\u043c\u043e \u0435\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f \u043d\u0430 UPnP/IGD." + "no_devices_found": "\u041d\u0435 \u0441\u0430 \u043d\u0430\u043c\u0435\u0440\u0435\u043d\u0438 UPnP/IGD \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0432 \u043c\u0440\u0435\u0436\u0430\u0442\u0430." }, "error": { "one": "\u0433\u0440\u0435\u0448\u043a\u0430", "other": "\u0433\u0440\u0435\u0448\u043a\u0438" - }, - "step": { - "confirm": { - "description": "\u0418\u0441\u043a\u0430\u0442\u0435 \u043b\u0438 \u0434\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u0435 UPnP/IGD?", - "title": "UPnP/IGD" - }, - "user": { - "data": { - "enable_port_mapping": "\u0410\u043a\u0442\u0438\u0432\u0438\u0440\u0430\u043d\u0435 \u043d\u0430 \u043f\u0440\u0435\u043d\u0430\u0441\u043e\u0447\u0432\u0430\u043d\u0435 \u043d\u0430 \u043f\u043e\u0440\u0442\u0430 \u0437\u0430 Home Assistant", - "enable_sensors": "\u0414\u043e\u0431\u0430\u0432\u044f\u043d\u0435 \u043d\u0430 \u0442\u0440\u0430\u0444\u0438\u0447\u043d\u0438 \u0441\u0435\u043d\u0437\u043e\u0440\u0438", - "igd": "UPnP/IGD" - }, - "title": "\u041a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u0438 \u043e\u043f\u0446\u0438\u0438 \u0437\u0430 UPnP/IGD" - } } } } \ No newline at end of file diff --git a/homeassistant/components/upnp/translations/ca.json b/homeassistant/components/upnp/translations/ca.json index 0559ee087e7..e2bf1e92d99 100644 --- a/homeassistant/components/upnp/translations/ca.json +++ b/homeassistant/components/upnp/translations/ca.json @@ -2,12 +2,9 @@ "config": { "abort": { "already_configured": "UPnP/IGD ja est\u00e0 configurat", - "incomplete_device": "Ignorant el dispositiu incomplet UPnP", "incomplete_discovery": "Descoberta incompleta", "no_devices_discovered": "No s'ha trobat cap UPnP/IGD", - "no_devices_found": "No s'han trobat dispositius UPnP/IGD a la xarxa.", - "no_sensors_or_port_mapping": "Activa, com a m\u00ednim, els sensors o l'assignaci\u00f3 de ports", - "single_instance_allowed": "Nom\u00e9s cal una sola configuraci\u00f3 de UPnP/IGD." + "no_devices_found": "No s'han trobat dispositius UPnP/IGD a la xarxa." }, "error": { "one": "un", @@ -15,22 +12,14 @@ }, "flow_title": "UPnP/IGD: {name}", "step": { - "confirm": { - "description": "Vols configurar UPnP/IGD?", - "title": "UPnP/IGD" - }, "ssdp_confirm": { "description": "Vols configurar aquest dispositiu UPnP/IGD?" }, "user": { "data": { - "enable_port_mapping": "Activa l'assignaci\u00f3 de ports per a Home Assistant", - "enable_sensors": "Afegeix sensors de tr\u00e0nsit", - "igd": "UPnP/IGD", "scan_interval": "Interval d'actualitzaci\u00f3 (en segons, m\u00ednim 30)", "usn": "Dispositiu" - }, - "title": "Opcions de configuraci\u00f3 d'UPnP/IGD" + } } } } diff --git a/homeassistant/components/upnp/translations/cs.json b/homeassistant/components/upnp/translations/cs.json index 9e3878bedd4..0c38ba9ab80 100644 --- a/homeassistant/components/upnp/translations/cs.json +++ b/homeassistant/components/upnp/translations/cs.json @@ -2,25 +2,8 @@ "config": { "abort": { "already_configured": "UPnP/IGD je ji\u017e nakonfigurov\u00e1no", - "incomplete_device": "Ignorov\u00e1n\u00ed ne\u00fapln\u00e9ho za\u0159\u00edzen\u00ed UPnP", "no_devices_discovered": "Nebyly zji\u0161t\u011bny \u017e\u00e1dn\u00e9 UPnP/IGD", - "no_devices_found": "V s\u00edti nejsou nalezena \u017e\u00e1dn\u00e1 za\u0159\u00edzen\u00ed UPnP/IGD.", - "no_sensors_or_port_mapping": "Povolte senzory nebo mapov\u00e1n\u00ed port\u016f", - "single_instance_allowed": "Povolena je pouze jedna instance UPnP/IGD." - }, - "step": { - "confirm": { - "description": "Chcete nastavit UPnP/IGD?", - "title": "UPnP/IGD" - }, - "user": { - "data": { - "enable_port_mapping": "Povolit mapov\u00e1n\u00ed port\u016f pro Home Assistant", - "enable_sensors": "P\u0159idejte dopravn\u00ed senzory", - "igd": "UPnP/IGD" - }, - "title": "Mo\u017enosti konfigurace pro UPnP/IGD" - } + "no_devices_found": "V s\u00edti nejsou nalezena \u017e\u00e1dn\u00e1 za\u0159\u00edzen\u00ed UPnP/IGD." } } } \ No newline at end of file diff --git a/homeassistant/components/upnp/translations/da.json b/homeassistant/components/upnp/translations/da.json index e45e84fffaf..f1880412006 100644 --- a/homeassistant/components/upnp/translations/da.json +++ b/homeassistant/components/upnp/translations/da.json @@ -2,29 +2,12 @@ "config": { "abort": { "already_configured": "UPnP/IGD er allerede konfigureret", - "incomplete_device": "Ignorerer ufuldst\u00e6ndig UPnP-enhed", "no_devices_discovered": "Ingen UPnP/IGD-enheder fundet.", - "no_devices_found": "Ingen UPnP/IGD enheder kunne findes p\u00e5 netv\u00e6rket.", - "no_sensors_or_port_mapping": "Aktiv\u00e9r enten sensorer eller porttilknytning", - "single_instance_allowed": "Det er kun n\u00f8dvendigt med en ops\u00e6tning af UPnP/IGD." + "no_devices_found": "Ingen UPnP/IGD enheder kunne findes p\u00e5 netv\u00e6rket." }, "error": { "one": "En", "other": "Anden" - }, - "step": { - "confirm": { - "description": "Er du sikker p\u00e5 at du vil konfigurere UPnP/IGD?", - "title": "UPnP/IGD" - }, - "user": { - "data": { - "enable_port_mapping": "Aktiv\u00e9r porttilknytning til Home Assistent", - "enable_sensors": "Tilf\u00f8j trafiksensorer", - "igd": "UPnP/IGD" - }, - "title": "Konfigurationsindstillinger for UPnP/IGD" - } } } } \ No newline at end of file diff --git a/homeassistant/components/upnp/translations/de.json b/homeassistant/components/upnp/translations/de.json index 66e43e22d46..c2defe875a3 100644 --- a/homeassistant/components/upnp/translations/de.json +++ b/homeassistant/components/upnp/translations/de.json @@ -2,12 +2,9 @@ "config": { "abort": { "already_configured": "UPnP/IGD ist bereits konfiguriert", - "incomplete_device": "Unvollst\u00e4ndiges UPnP-Ger\u00e4t wird ignoriert", "incomplete_discovery": "Unvollst\u00e4ndige Suche", "no_devices_discovered": "Keine UPnP/IGDs entdeckt", - "no_devices_found": "Keine UPnP/IGD-Ger\u00e4te im Netzwerk gefunden.", - "no_sensors_or_port_mapping": "Aktiviere mindestens Sensoren oder Port-Mapping", - "single_instance_allowed": "Es ist nur eine einzige Konfiguration von UPnP/IGD erforderlich." + "no_devices_found": "Keine UPnP/IGD-Ger\u00e4te im Netzwerk gefunden." }, "error": { "one": "Ein", @@ -15,21 +12,13 @@ }, "flow_title": "UPnP/IGD: {name}", "step": { - "confirm": { - "description": "M\u00f6chtest du UPnP/IGD einrichten?", - "title": "UPnP/IGD" - }, "ssdp_confirm": { "description": "M\u00f6chten Sie dieses UPnP/IGD-Ger\u00e4t einrichten?" }, "user": { "data": { - "enable_port_mapping": "Aktiviere Port-Mapping f\u00fcr Home Assistant", - "enable_sensors": "Verkehrssensoren hinzuf\u00fcgen", - "igd": "UPnP/IGD", "usn": "Ger\u00e4t" - }, - "title": "Konfigurations-Optionen" + } } } } diff --git a/homeassistant/components/upnp/translations/en.json b/homeassistant/components/upnp/translations/en.json index c2242031151..737954c2133 100644 --- a/homeassistant/components/upnp/translations/en.json +++ b/homeassistant/components/upnp/translations/en.json @@ -2,31 +2,20 @@ "config": { "abort": { "already_configured": "UPnP/IGD is already configured", - "incomplete_device": "Ignoring incomplete UPnP device", "incomplete_discovery": "Incomplete discovery", "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." }, "flow_title": "UPnP/IGD: {name}", "step": { - "confirm": { - "description": "Do you want to set up UPnP/IGD?", - "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", "scan_interval": "Update interval (seconds, minimal 30)", "usn": "Device" - }, - "title": "Configuration options" + } } } } diff --git a/homeassistant/components/upnp/translations/es-419.json b/homeassistant/components/upnp/translations/es-419.json index e516d978af0..8522ad16805 100644 --- a/homeassistant/components/upnp/translations/es-419.json +++ b/homeassistant/components/upnp/translations/es-419.json @@ -2,25 +2,8 @@ "config": { "abort": { "already_configured": "UPnP/IGD ya est\u00e1 configurado", - "incomplete_device": "Ignorar un dispositivo UPnP incompleto", "no_devices_discovered": "No se han descubierto UPnP/IGDs", - "no_devices_found": "No se encuentran dispositivos UPnP/IGD en la red.", - "no_sensors_or_port_mapping": "Habilitar al menos sensores o mapeo de puertos", - "single_instance_allowed": "S\u00f3lo se necesita una \u00fanica configuraci\u00f3n de UPnP/IGD." - }, - "step": { - "confirm": { - "description": "\u00bfDesea configurar UPnP/IGD?", - "title": "UPnP/IGD" - }, - "user": { - "data": { - "enable_port_mapping": "Habilitar la asignaci\u00f3n de puertos para Home Assistant", - "enable_sensors": "A\u00f1adir sensores de tr\u00e1fico", - "igd": "UPnP/IGD" - }, - "title": "Opciones de configuraci\u00f3n para UPnP/IGD" - } + "no_devices_found": "No se encuentran dispositivos UPnP/IGD en la red." } } } \ No newline at end of file diff --git a/homeassistant/components/upnp/translations/es.json b/homeassistant/components/upnp/translations/es.json index 84b5f2831e2..308b10cb6db 100644 --- a/homeassistant/components/upnp/translations/es.json +++ b/homeassistant/components/upnp/translations/es.json @@ -2,12 +2,9 @@ "config": { "abort": { "already_configured": "UPnP / IGD ya est\u00e1 configurado", - "incomplete_device": "Ignorando el dispositivo UPnP incompleto", "incomplete_discovery": "Descubrimiento incompleto", "no_devices_discovered": "No se descubrieron UPnP / IGDs", - "no_devices_found": "No se encuentran dispositivos UPnP/IGD en la red.", - "no_sensors_or_port_mapping": "Habilitar al menos sensores o mapeo de puertos", - "single_instance_allowed": "S\u00f3lo se necesita una configuraci\u00f3n de UPnP/IGD." + "no_devices_found": "No se encuentran dispositivos UPnP/IGD en la red." }, "error": { "one": "UNO", @@ -15,22 +12,14 @@ }, "flow_title": "UPnP / IGD: {name}", "step": { - "confirm": { - "description": "\u00bfDesea configurar UPnP/IGD?", - "title": "UPnP/IGD" - }, "ssdp_confirm": { "description": "\u00bfQuieres configurar este dispositivo UPnP/IGD?" }, "user": { "data": { - "enable_port_mapping": "Habilitar la asignaci\u00f3n de puertos para Home Assistant", - "enable_sensors": "A\u00f1adir sensores de tr\u00e1fico", - "igd": "UPnP / IGD", "scan_interval": "Intervalo de actualizaci\u00f3n (segundos, m\u00ednimo 30)", "usn": "Dispositivo" - }, - "title": "Opciones de configuraci\u00f3n" + } } } } diff --git a/homeassistant/components/upnp/translations/fi.json b/homeassistant/components/upnp/translations/fi.json index 0ad39900b72..dcd927ffd24 100644 --- a/homeassistant/components/upnp/translations/fi.json +++ b/homeassistant/components/upnp/translations/fi.json @@ -1,14 +1,8 @@ { "config": { "step": { - "confirm": { - "description": "Haluatko m\u00e4\u00e4ritt\u00e4\u00e4 UPnP/IGD:n?", - "title": "UPnP/IGD" - }, "user": { "data": { - "enable_sensors": "Lis\u00e4\u00e4 liikenneanturit", - "igd": "UPnP/IGD", "scan_interval": "P\u00e4ivitysv\u00e4li (sekuntia, v\u00e4hint\u00e4\u00e4n 30)", "usn": "Laite" } diff --git a/homeassistant/components/upnp/translations/fr.json b/homeassistant/components/upnp/translations/fr.json index 6baf9087b8e..e07bfbc6330 100644 --- a/homeassistant/components/upnp/translations/fr.json +++ b/homeassistant/components/upnp/translations/fr.json @@ -2,11 +2,8 @@ "config": { "abort": { "already_configured": "UPnP / IGD est d\u00e9j\u00e0 configur\u00e9", - "incomplete_device": "Ignorer un p\u00e9riph\u00e9rique UPnP incomplet", "no_devices_discovered": "Aucun UPnP / IGD d\u00e9couvert", - "no_devices_found": "Aucun p\u00e9riph\u00e9rique UPnP / IGD trouv\u00e9 sur le r\u00e9seau.", - "no_sensors_or_port_mapping": "Activer au moins les capteurs ou la cartographie des ports", - "single_instance_allowed": "Une seule configuration UPnP / IGD est n\u00e9cessaire." + "no_devices_found": "Aucun p\u00e9riph\u00e9rique UPnP / IGD trouv\u00e9 sur le r\u00e9seau." }, "error": { "one": "Vide", @@ -14,21 +11,13 @@ }, "flow_title": "UPnP/IGD: {name}", "step": { - "confirm": { - "description": "Voulez-vous configurer UPnP / IGD?", - "title": "UPnP / IGD" - }, "ssdp_confirm": { "description": "Voulez-vous configurer ce p\u00e9riph\u00e9rique UPnP/IGD?" }, "user": { "data": { - "enable_port_mapping": "Activer le mappage de port pour Home Assistant", - "enable_sensors": "Ajouter des capteurs de trafic", - "igd": "UPnP / IGD", "usn": "Appareil" - }, - "title": "Options de configuration pour UPnP / IGD" + } } } } diff --git a/homeassistant/components/upnp/translations/hu.json b/homeassistant/components/upnp/translations/hu.json index a8dc4ce854b..1baf3f0c11a 100644 --- a/homeassistant/components/upnp/translations/hu.json +++ b/homeassistant/components/upnp/translations/hu.json @@ -2,28 +2,12 @@ "config": { "abort": { "already_configured": "Az UPnP / IGD m\u00e1r konfigur\u00e1l\u00e1sra ker\u00fclt", - "incomplete_device": "A hi\u00e1nyos UPnP-eszk\u00f6z figyelmen k\u00edv\u00fcl hagy\u00e1sa", "no_devices_discovered": "Nem tal\u00e1ltam UPnP / IGD-ket", - "no_devices_found": "Nincsenek UPnPIGD eszk\u00f6z\u00f6k a h\u00e1l\u00f3zaton.", - "single_instance_allowed": "Csak egy UPnP / IGD konfigur\u00e1ci\u00f3 sz\u00fcks\u00e9ges." + "no_devices_found": "Nincsenek UPnPIGD eszk\u00f6z\u00f6k a h\u00e1l\u00f3zaton." }, "error": { "one": "hiba", "other": "" - }, - "step": { - "confirm": { - "description": "Be szeretn\u00e9d \u00e1ll\u00edtani a UPnP/IGD-t?", - "title": "UPnP/IGD" - }, - "user": { - "data": { - "enable_port_mapping": "Enged\u00e9lyezd a port mappinget a Home Assistant sz\u00e1m\u00e1ra", - "enable_sensors": "Forgalom \u00e9rz\u00e9kel\u0151k hozz\u00e1ad\u00e1sa", - "igd": "UPnP/IGD" - }, - "title": "Az UPnP/IGD be\u00e1ll\u00edt\u00e1si lehet\u0151s\u00e9gei" - } } } } \ No newline at end of file diff --git a/homeassistant/components/upnp/translations/it.json b/homeassistant/components/upnp/translations/it.json index 064f448aef7..dacb5023615 100644 --- a/homeassistant/components/upnp/translations/it.json +++ b/homeassistant/components/upnp/translations/it.json @@ -2,12 +2,9 @@ "config": { "abort": { "already_configured": "UPnP/IGD \u00e8 gi\u00e0 configurato", - "incomplete_device": "Ignorare il dispositivo UPnP incompleto", "incomplete_discovery": "Individuazione incompleta", "no_devices_discovered": "Nessun UPnP/IGD trovato", - "no_devices_found": "Nessun dispositivo UPnP/IGD trovato in rete.", - "no_sensors_or_port_mapping": "Abilita almeno i sensori o la mappatura delle porte", - "single_instance_allowed": "\u00c8 necessaria una sola configurazione di UPnP/IGD." + "no_devices_found": "Nessun dispositivo UPnP/IGD trovato in rete." }, "error": { "one": "Vuoto", @@ -15,10 +12,6 @@ }, "flow_title": "UPnP/IGD: {name}", "step": { - "confirm": { - "description": "Vuoi configurare UPnP/IGD?", - "title": "UPnP/IGD" - }, "init": { "one": "uno", "other": "altro" @@ -28,13 +21,9 @@ }, "user": { "data": { - "enable_port_mapping": "Abilita il port mapping per Home Assistant", - "enable_sensors": "Aggiungi sensori di traffico", - "igd": "UPnP/IGD", "scan_interval": "Intervallo di aggiornamento (secondi, minimo 30)", "usn": "Dispositivo" - }, - "title": "Opzioni di configurazione" + } } } } diff --git a/homeassistant/components/upnp/translations/ko.json b/homeassistant/components/upnp/translations/ko.json index 886fb4a4930..279d9f7f7ce 100644 --- a/homeassistant/components/upnp/translations/ko.json +++ b/homeassistant/components/upnp/translations/ko.json @@ -2,31 +2,20 @@ "config": { "abort": { "already_configured": "UPnP/IGD \uac00 \uc774\ubbf8 \uc124\uc815\ub41c \uc0c1\ud0dc\uc785\ub2c8\ub2e4", - "incomplete_device": "\ubd88\uc644\uc804\ud55c UPnP \uae30\uae30 \ubb34\uc2dc\ud558\uae30", "incomplete_discovery": "\uae30\uae30 \uac80\uc0c9\uc774 \uc644\uc804\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4", "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." + "no_devices_found": "UPnP/IGD \uae30\uae30\uac00 \ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \ubc1c\uacac\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4." }, "flow_title": "UPnP/IGD: {name}", "step": { - "confirm": { - "description": "UPnP/IGD \ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", - "title": "UPnP/IGD" - }, "ssdp_confirm": { "description": "\uc774 UPnP/IGD \uae30\uae30\ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" }, "user": { "data": { - "enable_port_mapping": "Home Assistant \ud3ec\ud2b8 \ub9e4\ud551 \ud65c\uc131\ud654", - "enable_sensors": "\ud2b8\ub798\ud53d \uc13c\uc11c \ucd94\uac00", - "igd": "UPnP/IGD", "scan_interval": "\uc5c5\ub370\uc774\ud2b8 \uac04\uaca9 (\ucd08, \ucd5c\uc18c\uac12 30)", "usn": "\uae30\uae30" - }, - "title": "\uc635\uc158 \uc124\uc815\ud558\uae30" + } } } } diff --git a/homeassistant/components/upnp/translations/lb.json b/homeassistant/components/upnp/translations/lb.json index 451d9fe78f0..710b788ae32 100644 --- a/homeassistant/components/upnp/translations/lb.json +++ b/homeassistant/components/upnp/translations/lb.json @@ -2,12 +2,9 @@ "config": { "abort": { "already_configured": "UPnP/IGD ass scho konfigur\u00e9iert", - "incomplete_device": "Ignor\u00e9iert onvollst\u00e4nnegen UPnP-Apparat", "incomplete_discovery": "Entdeckung net komplett", "no_devices_discovered": "Keng UPnP/IGDs entdeckt", - "no_devices_found": "Keng UPnP/IGD Apparater am Netzwierk fonnt.", - "no_sensors_or_port_mapping": "Aktiv\u00e9ier op mannst Sensoren oder Port Mapping", - "single_instance_allowed": "N\u00ebmmen eng eenzeg Konfiguratioun vun UPnP/IGD ass n\u00e9ideg." + "no_devices_found": "Keng UPnP/IGD Apparater am Netzwierk fonnt." }, "error": { "one": "Een", @@ -15,22 +12,14 @@ }, "flow_title": "UPnP/IGD: {name}", "step": { - "confirm": { - "description": "Soll UPnP/IGD konfigur\u00e9iert ginn?", - "title": "UPnP/IGD" - }, "ssdp_confirm": { "description": "Soll d\u00ebsen UPnP/IGD Apparat konfigur\u00e9iert ginn?" }, "user": { "data": { - "enable_port_mapping": "Port Mapping fir Home Assistant aktiv\u00e9ieren", - "enable_sensors": "Trafic Sensoren dob\u00e4isetzen", - "igd": "UPnP/IGD", "scan_interval": "Update Intervall (Sekonnen, minimum 30)", "usn": "Apparat" - }, - "title": "Konfiguratiouns Optiounen" + } } } } diff --git a/homeassistant/components/upnp/translations/nl.json b/homeassistant/components/upnp/translations/nl.json index 932d13434da..9b579127f8c 100644 --- a/homeassistant/components/upnp/translations/nl.json +++ b/homeassistant/components/upnp/translations/nl.json @@ -2,12 +2,9 @@ "config": { "abort": { "already_configured": "UPnP/IGD is al geconfigureerd", - "incomplete_device": "Onvolledig UPnP-apparaat negeren", "incomplete_discovery": "Onvolledige ontdekking", "no_devices_discovered": "Geen UPnP'/IGD's ontdekt", - "no_devices_found": "Geen UPnP/IGD apparaten gevonden op het netwerk.", - "no_sensors_or_port_mapping": "Schakel ten minste sensoren of poorttoewijzing in", - "single_instance_allowed": "Er is slechts \u00e9\u00e9n configuratie van UPnP/IGD nodig." + "no_devices_found": "Geen UPnP/IGD apparaten gevonden op het netwerk." }, "error": { "one": "Een", @@ -15,21 +12,13 @@ }, "flow_title": "[%%]: {naam}", "step": { - "confirm": { - "description": "Wilt u UPnP/IGD instellen?", - "title": "UPnP/IGD" - }, "ssdp_confirm": { "description": "Wilt u [%%] instellen?" }, "user": { "data": { - "enable_port_mapping": "Poorttoewijzing voor Home Assistant inschakelen", - "enable_sensors": "Voeg verkeerssensoren toe", - "igd": "UPnP/IGD", "usn": "Apparaat" - }, - "title": "Configuratiemogelijkheden voor de UPnP/IGD" + } } } } diff --git a/homeassistant/components/upnp/translations/nn.json b/homeassistant/components/upnp/translations/nn.json index f521406b087..83565f89436 100644 --- a/homeassistant/components/upnp/translations/nn.json +++ b/homeassistant/components/upnp/translations/nn.json @@ -1,21 +1,8 @@ { "config": { - "abort": { - "no_sensors_or_port_mapping": "I det minste, aktiver sensor eller portkartlegging" - }, "error": { "one": "Ein", "other": "Andre" - }, - "step": { - "confirm": { - "title": "UPnP/IGD" - }, - "user": { - "data": { - "igd": "UPnP/IGD" - } - } } } } \ No newline at end of file diff --git a/homeassistant/components/upnp/translations/no.json b/homeassistant/components/upnp/translations/no.json index 08b8ee59931..28c2e099192 100644 --- a/homeassistant/components/upnp/translations/no.json +++ b/homeassistant/components/upnp/translations/no.json @@ -2,12 +2,9 @@ "config": { "abort": { "already_configured": "UPnP / IGD er allerede konfigurert", - "incomplete_device": "Ignorerer ufullstendig UPnP-enhet", "incomplete_discovery": "Ufullstendig oppdagelse", "no_devices_discovered": "Ingen UPnP / IGDs oppdaget", - "no_devices_found": "Ingen UPnP / IGD-enheter funnet p\u00e5 nettverket.", - "no_sensors_or_port_mapping": "Aktiver minst sensorer eller port mapping", - "single_instance_allowed": "Bare en konfigurasjon av UPnP / IGD er n\u00f8dvendig." + "no_devices_found": "Ingen UPnP / IGD-enheter funnet p\u00e5 nettverket." }, "error": { "few": "f\u00e5", @@ -19,22 +16,14 @@ }, "flow_title": "UPnP/IGD: {name}", "step": { - "confirm": { - "description": "\u00d8nsker du \u00e5 sette opp UPnP / IGD?", - "title": "" - }, "ssdp_confirm": { "description": "\u00d8nsker du \u00e5 sette opp denne UPnP/IGD-enheten?" }, "user": { "data": { - "enable_port_mapping": "Aktiver port mapping for Home Assistant", - "enable_sensors": "Legg til trafikk sensorer", - "igd": "UPnP / IGD", "scan_interval": "Oppdateringsintervall (sekunder, minimum 30)", "usn": "Enhet" - }, - "title": "Konfigurasjonsalternativer" + } } } } diff --git a/homeassistant/components/upnp/translations/pl.json b/homeassistant/components/upnp/translations/pl.json index 63af10a93c7..55d0abbba48 100644 --- a/homeassistant/components/upnp/translations/pl.json +++ b/homeassistant/components/upnp/translations/pl.json @@ -2,31 +2,20 @@ "config": { "abort": { "already_configured": "UPnP/IGD jest ju\u017c skonfigurowane.", - "incomplete_device": "Ignorowanie niekompletnego urz\u0105dzenia UPnP", "incomplete_discovery": "Wykrywanie niekompletne", "no_devices_discovered": "Nie wykryto urz\u0105dze\u0144 UPnP/IGD.", - "no_devices_found": "Nie znaleziono w sieci urz\u0105dze\u0144 UPnP/IGD.", - "no_sensors_or_port_mapping": "W\u0142\u0105cz przynajmniej sensory lub mapowanie port\u00f3w", - "single_instance_allowed": "Wymagana jest tylko jedna konfiguracja UPnP/IGD." + "no_devices_found": "Nie znaleziono w sieci urz\u0105dze\u0144 UPnP/IGD." }, "flow_title": "UPnP/IGD: {name}", "step": { - "confirm": { - "description": "Czy chcesz skonfigurowa\u0107 UPnP/IGD?", - "title": "UPnP/IGD" - }, "ssdp_confirm": { "description": "Czy chcesz skonfigurowa\u0107 to urz\u0105dzenie UPnP/IGD?" }, "user": { "data": { - "enable_port_mapping": "W\u0142\u0105cz mapowanie port\u00f3w dla Home Assistanta", - "enable_sensors": "Dodaj sensor ruchu sieciowego", - "igd": "UPnP/IGD", "scan_interval": "Cz\u0119stotliwo\u015b\u0107 aktualizacji (sekundy, minimum 30)", "usn": "Urz\u0105dzenie" - }, - "title": "Opcje konfiguracji dla UPnP/IGD" + } } } } diff --git a/homeassistant/components/upnp/translations/pt-BR.json b/homeassistant/components/upnp/translations/pt-BR.json index a69a18570ea..f9c75ffec25 100644 --- a/homeassistant/components/upnp/translations/pt-BR.json +++ b/homeassistant/components/upnp/translations/pt-BR.json @@ -2,27 +2,16 @@ "config": { "abort": { "already_configured": "UPnP / IGD j\u00e1 est\u00e1 configurado", - "incomplete_device": "Ignorando o dispositivo UPnP incompleto", "incomplete_discovery": "Descoberta incompleta", "no_devices_discovered": "Nenhum UPnP/IGD descoberto", - "no_devices_found": "Nenhum dispositivo UPnP/IGD encontrado na rede.", - "no_sensors_or_port_mapping": "Ative pelo menos sensores ou mapeamento de porta", - "single_instance_allowed": "Apenas uma configura\u00e7\u00e3o do UPnP/IGD \u00e9 necess\u00e1ria." + "no_devices_found": "Nenhum dispositivo UPnP/IGD encontrado na rede." }, "step": { - "confirm": { - "description": "Deseja configurar o UPnP/IGD?", - "title": "UPnP/IGD" - }, "user": { "data": { - "enable_port_mapping": "Ativar o mapeamento de porta para o Home Assistant", - "enable_sensors": "Adicionar sensores de tr\u00e1fego", - "igd": "UPnP/IGD", "scan_interval": "Intervalo de atualiza\u00e7\u00e3o (segundos, m\u00ednimo 30)", "usn": "Dispositivo" - }, - "title": "Op\u00e7\u00f5es de configura\u00e7\u00e3o para o UPnP/IGD" + } } } } diff --git a/homeassistant/components/upnp/translations/pt.json b/homeassistant/components/upnp/translations/pt.json index ea501c2c263..cb08c0f52a6 100644 --- a/homeassistant/components/upnp/translations/pt.json +++ b/homeassistant/components/upnp/translations/pt.json @@ -2,29 +2,12 @@ "config": { "abort": { "already_configured": "UPnP/IGD j\u00e1 est\u00e1 configurado", - "incomplete_device": "Dispositivos UPnP incompletos ignorados", "no_devices_discovered": "Nenhum UPnP/IGDs descoberto", - "no_devices_found": "Nenhum dispositivo UPnP / IGD encontrado na rede.", - "no_sensors_or_port_mapping": "Ative pelo menos os sensores ou o mapeamento de porta", - "single_instance_allowed": "Apenas uma \u00fanica configura\u00e7\u00e3o do UPnP/IGD \u00e9 necess\u00e1ria." + "no_devices_found": "Nenhum dispositivo UPnP / IGD encontrado na rede." }, "error": { "one": "um", "other": "v\u00e1rios" - }, - "step": { - "confirm": { - "description": "Deseja configurar o UPnP / IGD?", - "title": "UPnP/IGD" - }, - "user": { - "data": { - "enable_port_mapping": "Ativar o mapeamento de porta para o Home Assistant", - "enable_sensors": "Adicionar sensores de tr\u00e1fego", - "igd": "UPnP/IGD" - }, - "title": "Op\u00e7\u00f5es de configura\u00e7\u00e3o para o UPnP/IGD" - } } } } \ No newline at end of file diff --git a/homeassistant/components/upnp/translations/ro.json b/homeassistant/components/upnp/translations/ro.json index 33342807995..230e285cfa4 100644 --- a/homeassistant/components/upnp/translations/ro.json +++ b/homeassistant/components/upnp/translations/ro.json @@ -8,16 +8,6 @@ "few": "", "one": "Unul", "other": "" - }, - "step": { - "user": { - "data": { - "enable_port_mapping": "Activa\u021bi maparea porturilor pentru Home Assistant", - "enable_sensors": "Ad\u0103uga\u021bi senzori de trafic", - "igd": "UPnP/IGD" - }, - "title": "Op\u021biuni de configurare pentru UPnP/IGD" - } } } } \ No newline at end of file diff --git a/homeassistant/components/upnp/translations/ru.json b/homeassistant/components/upnp/translations/ru.json index ee899c998fe..cea6b66db2b 100644 --- a/homeassistant/components/upnp/translations/ru.json +++ b/homeassistant/components/upnp/translations/ru.json @@ -2,12 +2,9 @@ "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.", - "incomplete_device": "\u0418\u0433\u043d\u043e\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043d\u0435\u043f\u043e\u043b\u043d\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 UPnP.", "incomplete_discovery": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u044c \u043f\u0440\u043e\u0446\u0435\u0441\u0441.", "no_devices_discovered": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 UPnP / IGD \u043d\u0435 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u044b.", - "no_devices_found": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 UPnP / IGD \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b \u0432 \u0441\u0435\u0442\u0438.", - "no_sensors_or_port_mapping": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u0441\u0435\u043d\u0441\u043e\u0440\u044b \u0438\u043b\u0438 \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u043f\u043e\u0440\u0442\u043e\u0432.", - "single_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + "no_devices_found": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 UPnP / IGD \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b \u0432 \u0441\u0435\u0442\u0438." }, "error": { "few": "\u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e", @@ -17,22 +14,14 @@ }, "flow_title": "UPnP/IGD: {name}", "step": { - "confirm": { - "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c UPnP / IGD?", - "title": "UPnP / IGD" - }, "ssdp_confirm": { "description": "\u0425\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u044d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e UPnP / IGD?" }, "user": { "data": { - "enable_port_mapping": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u043f\u043e\u0440\u0442\u043e\u0432 \u0434\u043b\u044f Home Assistant", - "enable_sensors": "\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0441\u0435\u043d\u0441\u043e\u0440\u044b \u0441\u0435\u0442\u0435\u0432\u043e\u0433\u043e \u0442\u0440\u0430\u0444\u0438\u043a\u0430", - "igd": "UPnP / IGD", "scan_interval": "\u0418\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445, \u043c\u0438\u043d\u0438\u043c\u0443\u043c 30)", "usn": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e" - }, - "title": "UPnP / IGD" + } } } } diff --git a/homeassistant/components/upnp/translations/sl.json b/homeassistant/components/upnp/translations/sl.json index 1813461eadd..88b339b7372 100644 --- a/homeassistant/components/upnp/translations/sl.json +++ b/homeassistant/components/upnp/translations/sl.json @@ -2,11 +2,8 @@ "config": { "abort": { "already_configured": "UPnP/IGD je \u017ee konfiguriran", - "incomplete_device": "Ignoriranje nepopolnih UPnP naprav", "no_devices_discovered": "Ni odkritih UPnP/IGD naprav", - "no_devices_found": "Naprav UPnP/IGD ni mogo\u010de najti v omre\u017eju.", - "no_sensors_or_port_mapping": "Omogo\u010dite vsaj senzorje ali preslikavo vrat (port mapping)", - "single_instance_allowed": "Potrebna je samo ena konfiguracija UPnp/IGD." + "no_devices_found": "Naprav UPnP/IGD ni mogo\u010de najti v omre\u017eju." }, "error": { "few": "nekaj", @@ -16,21 +13,13 @@ }, "flow_title": "UPnP/IGD: {name}", "step": { - "confirm": { - "description": "Ali \u017eelite nastaviti UPnp/IGD?", - "title": "UPnP/IGD" - }, "ssdp_confirm": { "description": "Ali \u017eelite nastaviti to UPnP/IGD napravo?" }, "user": { "data": { - "enable_port_mapping": "Omogo\u010dajo preslikavo vrat (port mapping) za Home Assistant-a", - "enable_sensors": "Dodaj prometne senzorje", - "igd": "UPnP/IGD", "usn": "Naprava" - }, - "title": "Mo\u017enosti konfiguracije za UPnP/IGD" + } } } } diff --git a/homeassistant/components/upnp/translations/sv.json b/homeassistant/components/upnp/translations/sv.json index f53c9294ea9..a7c95b6fc08 100644 --- a/homeassistant/components/upnp/translations/sv.json +++ b/homeassistant/components/upnp/translations/sv.json @@ -2,29 +2,18 @@ "config": { "abort": { "already_configured": "UPnP/IGD \u00e4r redan konfigurerad", - "incomplete_device": "Ignorera ofullst\u00e4ndig UPnP-enhet", "no_devices_discovered": "Inga UPnP/IGDs uppt\u00e4cktes", - "no_devices_found": "Inga UPnP/IGD-enheter hittades p\u00e5 n\u00e4tverket.", - "no_sensors_or_port_mapping": "Aktivera minst sensorer eller portmappning", - "single_instance_allowed": "Endast en enda konfiguration av UPnP/IGD \u00e4r n\u00f6dv\u00e4ndig." + "no_devices_found": "Inga UPnP/IGD-enheter hittades p\u00e5 n\u00e4tverket." }, "error": { "one": "En", "other": "Andra" }, "step": { - "confirm": { - "description": "Vill du konfigurera UPnP/IGD?", - "title": "UPnP/IGD" - }, "user": { "data": { - "enable_port_mapping": "Aktivera portmappning f\u00f6r Home Assistant", - "enable_sensors": "L\u00e4gg till trafiksensorer", - "igd": "UPnP/IGD", "usn": "Enheten" - }, - "title": "Konfigurationsalternativ f\u00f6r UPnP/IGD" + } } } } diff --git a/homeassistant/components/upnp/translations/uk.json b/homeassistant/components/upnp/translations/uk.json index 44268a5b5b5..c1a9f1fadcf 100644 --- a/homeassistant/components/upnp/translations/uk.json +++ b/homeassistant/components/upnp/translations/uk.json @@ -2,17 +2,7 @@ "config": { "abort": { "already_configured": "UPnP/IGD \u0432\u0436\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0454\u043d\u043e", - "no_devices_discovered": "\u041d\u0435 \u0432\u0438\u044f\u0432\u043b\u0435\u043d\u043e UPnP/IGD", - "no_sensors_or_port_mapping": "\u0423\u0432\u0456\u043c\u043a\u043d\u0456\u0442\u044c \u043f\u0440\u0438\u043d\u0430\u0439\u043c\u043d\u0456 \u0434\u0430\u0442\u0447\u0438\u043a\u0438 \u0430\u0431\u043e \u0432\u0456\u0434\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u043d\u044f \u043f\u043e\u0440\u0442\u0456\u0432" - }, - "step": { - "user": { - "data": { - "enable_port_mapping": "\u0423\u0432\u0456\u043c\u043a\u043d\u0443\u0442\u0438 \u0432\u0456\u0434\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u043d\u044f \u043f\u043e\u0440\u0442\u0456\u0432 \u0434\u043b\u044f Home Assistant", - "enable_sensors": "\u0414\u043e\u0434\u0430\u0442\u0438 \u0434\u0430\u0442\u0447\u0438\u043a\u0438 \u0442\u0440\u0430\u0444\u0456\u043a\u0443" - }, - "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0457\u0442\u0438 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438 UPnP/IGD" - } + "no_devices_discovered": "\u041d\u0435 \u0432\u0438\u044f\u0432\u043b\u0435\u043d\u043e UPnP/IGD" } } } \ No newline at end of file diff --git a/homeassistant/components/upnp/translations/zh-Hans.json b/homeassistant/components/upnp/translations/zh-Hans.json index 80216d7a18e..7919d0f4591 100644 --- a/homeassistant/components/upnp/translations/zh-Hans.json +++ b/homeassistant/components/upnp/translations/zh-Hans.json @@ -2,25 +2,14 @@ "config": { "abort": { "already_configured": "UPnP/IGD \u5df2\u914d\u7f6e\u5b8c\u6210", - "incomplete_device": "\u5ffd\u7565\u4e0d\u5b8c\u6574\u7684 UPnP \u8bbe\u5907", "no_devices_discovered": "\u672a\u53d1\u73b0 UPnP/IGD", - "no_devices_found": "\u6ca1\u6709\u5728\u7f51\u7edc\u4e0a\u627e\u5230 UPnP/IGD \u8bbe\u5907\u3002", - "no_sensors_or_port_mapping": "\u81f3\u5c11\u542f\u7528\u4f20\u611f\u5668\u6216\u7aef\u53e3\u6620\u5c04", - "single_instance_allowed": "UPnP/IGD \u53ea\u9700\u8981\u914d\u7f6e\u4e00\u6b21\u3002" + "no_devices_found": "\u6ca1\u6709\u5728\u7f51\u7edc\u4e0a\u627e\u5230 UPnP/IGD \u8bbe\u5907\u3002" }, "step": { - "confirm": { - "description": "\u60a8\u60f3\u8981\u914d\u7f6e UPnP/IGD \u5417\uff1f", - "title": "UPnP/IGD" - }, "user": { "data": { - "enable_port_mapping": "\u4e3a Home Assistant \u542f\u7528\u7aef\u53e3\u6620\u5c04", - "enable_sensors": "\u6dfb\u52a0\u6d41\u91cf\u4f20\u611f\u5668", - "igd": "UPnP/IGD", "usn": "\u8bbe\u5907" - }, - "title": "UPnP/IGD \u7684\u914d\u7f6e\u9009\u9879" + } } } } diff --git a/homeassistant/components/upnp/translations/zh-Hant.json b/homeassistant/components/upnp/translations/zh-Hant.json index 6895b3baa10..c89ff8af971 100644 --- a/homeassistant/components/upnp/translations/zh-Hant.json +++ b/homeassistant/components/upnp/translations/zh-Hant.json @@ -2,31 +2,20 @@ "config": { "abort": { "already_configured": "UPnP/IGD \u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", - "incomplete_device": "\u5ffd\u7565\u4e0d\u76f8\u5bb9 UPnP \u8a2d\u5099", "incomplete_discovery": "\u672a\u5b8c\u6210\u63a2\u7d22", "no_devices_discovered": "\u672a\u641c\u5c0b\u5230 UPnP/IGD", - "no_devices_found": "\u5728\u7db2\u8def\u4e0a\u627e\u4e0d\u5230 UPnP/IGD \u8a2d\u5099\u3002", - "no_sensors_or_port_mapping": "\u81f3\u5c11\u958b\u555f\u611f\u61c9\u5668\u6216\u901a\u8a0a\u57e0\u8f49\u767c", - "single_instance_allowed": "\u50c5\u9700\u8a2d\u5b9a\u4e00\u6b21 UPnP/IGD \u5373\u53ef\u3002" + "no_devices_found": "\u5728\u7db2\u8def\u4e0a\u627e\u4e0d\u5230 UPnP/IGD \u8a2d\u5099\u3002" }, "flow_title": "UPnP/IGD\uff1a{name}", "step": { - "confirm": { - "description": "\u662f\u5426\u8981\u8a2d\u5b9a UPnP/IGD\uff1f", - "title": "UPnP/IGD" - }, "ssdp_confirm": { "description": "\u662f\u5426\u8981\u8a2d\u5b9a UPnP/IGD \u8a2d\u5099\uff1f" }, "user": { "data": { - "enable_port_mapping": "\u958b\u555f Home Assistant \u901a\u8a0a\u57e0\u8f49\u767c", - "enable_sensors": "\u65b0\u589e\u6d41\u91cf\u611f\u61c9\u5668", - "igd": "UPnP/IGD", "scan_interval": "\u66f4\u65b0\u9593\u9694\uff08\u79d2\u3001\u6700\u5c11 30 \u79d2\uff09", "usn": "\u8a2d\u5099" - }, - "title": "\u8a2d\u5b9a\u9078\u9805" + } } } } diff --git a/homeassistant/components/vizio/translations/ca.json b/homeassistant/components/vizio/translations/ca.json index 7061bcb176f..e5ccd1d4d6d 100644 --- a/homeassistant/components/vizio/translations/ca.json +++ b/homeassistant/components/vizio/translations/ca.json @@ -1,12 +1,11 @@ { "config": { "abort": { - "already_setup": "Aquesta entrada ja ha estat configurada.", + "already_configured_device": "El dispositiu ja est\u00e0 configurat", "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", - "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.", + "cannot_connect": "No s'ha pogut connectar", "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/da.json b/homeassistant/components/vizio/translations/da.json index 6de25c240de..71f9648cf0f 100644 --- a/homeassistant/components/vizio/translations/da.json +++ b/homeassistant/components/vizio/translations/da.json @@ -1,11 +1,9 @@ { "config": { "abort": { - "already_setup": "Denne post er allerede blevet konfigureret.", "updated_entry": "Denne post er allerede konfigureret, men navnet og/eller indstillingerne, der er defineret i konfigurationen, stemmer ikke overens med den tidligere importerede konfiguration, s\u00e5 konfigurationsposten er blevet opdateret i overensstemmelse hermed." }, "error": { - "cant_connect": "Kunne ikke oprette forbindelse til enheden. [Gennemg\u00e5 dokumentationen] (https://www.home-assistant.io/integrations/vizio/), og bekr\u00e6ft, at: \n - Enheden er t\u00e6ndt \n - Enheden er tilsluttet netv\u00e6rket \n - De angivne v\u00e6rdier er korrekte \n f\u00f8r du fors\u00f8ger at indsende igen.", "host_exists": "Vizio-enhed med den specificerede v\u00e6rt er allerede konfigureret.", "name_exists": "Vizio-enhed med det specificerede navn er allerede konfigureret." }, diff --git a/homeassistant/components/vizio/translations/de.json b/homeassistant/components/vizio/translations/de.json index b5e8a5acc60..28397d08b8f 100644 --- a/homeassistant/components/vizio/translations/de.json +++ b/homeassistant/components/vizio/translations/de.json @@ -1,12 +1,9 @@ { "config": { "abort": { - "already_setup": "Dieser Eintrag wurde bereits eingerichtet.", "updated_entry": "Dieser Eintrag wurde bereits eingerichtet, aber der Name, die Apps und / oder die in der Konfiguration definierten Optionen stimmen nicht mit der zuvor importierten Konfiguration \u00fcberein, sodass der Konfigurationseintrag entsprechend aktualisiert wurde." }, "error": { - "cant_connect": "Es konnte keine Verbindung zum Ger\u00e4t hergestellt werden. [\u00dcberpr\u00fcfen Sie die Dokumentation] (https://www.home-assistant.io/integrations/vizio/) und \u00fcberpr\u00fcfen Sie Folgendes erneut:\n- Das Ger\u00e4t ist eingeschaltet\n- Das Ger\u00e4t ist mit dem Netzwerk verbunden\n- Die von Ihnen eingegebenen Werte sind korrekt\nbevor sie versuchen, erneut zu \u00fcbermitteln.", - "complete_pairing failed": "Das Pairing kann nicht abgeschlossen werden. Stellen Sie sicher, dass die von Ihnen angegebene PIN korrekt ist und das Fernsehger\u00e4t weiterhin mit Strom versorgt und mit dem Netzwerk verbunden ist, bevor Sie es erneut versuchen.", "host_exists": "VIZIO-Ger\u00e4t mit angegebenem Host bereits konfiguriert.", "name_exists": "VIZIO-Ger\u00e4t mit angegebenem Namen bereits konfiguriert." }, diff --git a/homeassistant/components/vizio/translations/en.json b/homeassistant/components/vizio/translations/en.json index d09e29037ee..42dcfe6d96d 100644 --- a/homeassistant/components/vizio/translations/en.json +++ b/homeassistant/components/vizio/translations/en.json @@ -1,15 +1,14 @@ { "config": { "abort": { - "already_setup": "This entry has already been setup.", + "already_configured_device": "Device is already configured", "updated_entry": "This entry has already been setup but the name, apps, and/or options defined in the configuration do not match the previously imported configuration, so the configuration entry has been updated accordingly." }, "error": { - "cant_connect": "Failed to connect", - "complete_pairing failed": "Unable to complete pairing. Ensure the PIN you provided is correct and the TV is still powered and connected to the network before resubmitting.", + "cannot_connect": "Failed to connect", "complete_pairing_failed": "Unable to complete pairing. Ensure the PIN you provided is correct and the TV is still powered and connected to the network before resubmitting.", - "host_exists": "VIZIO device with specified host already configured.", - "name_exists": "VIZIO device with specified name already configured." + "host_exists": "VIZIO SmartCast Device with specified host already configured.", + "name_exists": "VIZIO SmartCast Device with specified name already configured." }, "step": { "pair_tv": { @@ -20,11 +19,11 @@ "title": "Complete Pairing Process" }, "pairing_complete": { - "description": "Your VIZIO SmartCast device is now connected to Home Assistant.", + "description": "Your VIZIO SmartCast Device is now connected to Home Assistant.", "title": "Pairing Complete" }, "pairing_complete_import": { - "description": "Your VIZIO SmartCast TV is now connected to Home Assistant.\n\nYour Access Token is '**{access_token}**'.", + "description": "Your VIZIO SmartCast Device is now connected to Home Assistant.\n\nYour Access Token is '**{access_token}**'.", "title": "Pairing Complete" }, "user": { @@ -35,7 +34,7 @@ "name": "Name" }, "description": "An Access Token is only needed for TVs. If you are configuring a TV and do not have an Access Token yet, leave it blank to go through a pairing process.", - "title": "Setup VIZIO SmartCast Device" + "title": "VIZIO SmartCast Device" } } }, @@ -48,7 +47,7 @@ "volume_step": "Volume Step Size" }, "description": "If you have a Smart TV, you can optionally filter your source list by choosing which apps to include or exclude in your source list.", - "title": "Update VIZIO SmartCast Options" + "title": "Update VIZIO SmartCast Device Options" } } } diff --git a/homeassistant/components/vizio/translations/es-419.json b/homeassistant/components/vizio/translations/es-419.json index d60f839d653..0ee2dcaaf86 100644 --- a/homeassistant/components/vizio/translations/es-419.json +++ b/homeassistant/components/vizio/translations/es-419.json @@ -1,12 +1,9 @@ { "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." }, diff --git a/homeassistant/components/vizio/translations/es.json b/homeassistant/components/vizio/translations/es.json index 41246de7531..ad496be3836 100644 --- a/homeassistant/components/vizio/translations/es.json +++ b/homeassistant/components/vizio/translations/es.json @@ -1,12 +1,11 @@ { "config": { "abort": { - "already_setup": "Esta entrada ya ha sido configurada.", + "already_configured_device": "El dispositivo ya est\u00e1 configurado", "updated_entry": "Esta entrada ya ha sido configurada pero el nombre y/o las opciones definidas en la configuraci\u00f3n no coinciden con la configuraci\u00f3n previamente importada, por lo que la entrada de la configuraci\u00f3n ha sido actualizada en consecuencia." }, "error": { - "cant_connect": "Error al conectar", - "complete_pairing failed": "No se pudo completar el emparejamiento. Aseg\u00farate de que el PIN que has proporcionado es correcto y que el televisor sigue encendido y conectado a la red antes de volver a enviarlo.", + "cannot_connect": "No se pudo conectar", "complete_pairing_failed": "No se pudo completar el emparejamiento. Aseg\u00farate de que el PIN que has proporcionado es correcto y que el televisor sigue encendido y conectado a la red antes de volver a enviarlo.", "host_exists": "El host ya est\u00e1 configurado.", "name_exists": "Nombre ya configurado." diff --git a/homeassistant/components/vizio/translations/fr.json b/homeassistant/components/vizio/translations/fr.json index b28b9881024..906f1580c35 100644 --- a/homeassistant/components/vizio/translations/fr.json +++ b/homeassistant/components/vizio/translations/fr.json @@ -1,12 +1,9 @@ { "config": { "abort": { - "already_setup": "Cette entr\u00e9e a d\u00e9j\u00e0 \u00e9t\u00e9 configur\u00e9e.", "updated_entry": "Cette entr\u00e9e a d\u00e9j\u00e0 \u00e9t\u00e9 configur\u00e9e mais le nom et/ou les options d\u00e9finis dans la configuration ne correspondent pas \u00e0 la configuration pr\u00e9c\u00e9demment import\u00e9e, de sorte que l'entr\u00e9e de configuration a \u00e9t\u00e9 mise \u00e0 jour en cons\u00e9quence." }, "error": { - "cant_connect": "Impossible de se connecter \u00e0 l'appareil. [V\u00e9rifier les documents](https://www.home-assistant.io/integrations/vizio/) et rev\u00e9rifier que:\n- L'appareil est sous tension\n- L'appareil est connect\u00e9 au r\u00e9seau\n- Les valeurs que vous avez saisies sont exactes\navant d'essayer de le soumettre \u00e0 nouveau.", - "complete_pairing failed": "Impossible de terminer l'appariement. Assurez-vous que le code PIN que vous avez fourni est correct et que le t\u00e9l\u00e9viseur est toujours aliment\u00e9 et connect\u00e9 au r\u00e9seau avant de soumettre \u00e0 nouveau.", "host_exists": "H\u00f4te d\u00e9j\u00e0 configur\u00e9.", "name_exists": "Nom d\u00e9j\u00e0 configur\u00e9." }, diff --git a/homeassistant/components/vizio/translations/hu.json b/homeassistant/components/vizio/translations/hu.json index 8dadb52211f..469649275e1 100644 --- a/homeassistant/components/vizio/translations/hu.json +++ b/homeassistant/components/vizio/translations/hu.json @@ -1,11 +1,9 @@ { "config": { "abort": { - "already_setup": "Ez a bejegyz\u00e9s m\u00e1r be van \u00e1ll\u00edtva.", "updated_entry": "Ez a bejegyz\u00e9s m\u00e1r be van \u00e1ll\u00edtva, de a konfigur\u00e1ci\u00f3ban defini\u00e1lt n\u00e9v, appok \u00e9s/vagy be\u00e1ll\u00edt\u00e1sok nem egyeznek meg a kor\u00e1bban import\u00e1lt konfigur\u00e1ci\u00f3val, \u00edgy a konfigur\u00e1ci\u00f3s bejegyz\u00e9s ennek megfelel\u0151en friss\u00fclt." }, "error": { - "cant_connect": "Sikertelen csatlakoz\u00e1s", "host_exists": "A megadott kiszolg\u00e1l\u00f3n\u00e9vvel rendelkez\u0151 Vizio-eszk\u00f6z m\u00e1r konfigur\u00e1lva van.", "name_exists": "A megadott n\u00e9vvel rendelkez\u0151 Vizio-eszk\u00f6z m\u00e1r konfigur\u00e1lva van." }, diff --git a/homeassistant/components/vizio/translations/it.json b/homeassistant/components/vizio/translations/it.json index 6ec2a7d1b84..e175becd8c9 100644 --- a/homeassistant/components/vizio/translations/it.json +++ b/homeassistant/components/vizio/translations/it.json @@ -1,12 +1,9 @@ { "config": { "abort": { - "already_setup": "Questa voce \u00e8 gi\u00e0 stata configurata.", "updated_entry": "Questa voce \u00e8 gi\u00e0 stata configurata, ma il nome, le app e/o le opzioni definite nella configurazione non corrispondono alla configurazione importata in precedenza, pertanto la voce di configurazione \u00e8 stata aggiornata di conseguenza." }, "error": { - "cant_connect": "Impossibile connettersi", - "complete_pairing failed": "Impossibile completare l'associazione. Assicurarsi che il PIN fornito sia corretto e che il televisore sia ancora alimentato e connesso alla rete prima di inviarlo di nuovo.", "complete_pairing_failed": "Impossibile completare l'associazione. Assicurarsi che il PIN fornito sia corretto e che la TV sia ancora accesa e collegata alla rete prima di inviare nuovamente.", "host_exists": "Dispositivo VIZIO con host specificato gi\u00e0 configurato.", "name_exists": "Dispositivo VIZIO con il nome specificato gi\u00e0 configurato." diff --git a/homeassistant/components/vizio/translations/ko.json b/homeassistant/components/vizio/translations/ko.json index 8b1a667b9e8..ebe6da266dc 100644 --- a/homeassistant/components/vizio/translations/ko.json +++ b/homeassistant/components/vizio/translations/ko.json @@ -1,12 +1,9 @@ { "config": { "abort": { - "already_setup": "\uc774 \ud56d\ubaa9\uc740 \uc774\ubbf8 \uc124\uc815\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", "updated_entry": "\uc774 \ud56d\ubaa9\uc740 \uc774\ubbf8 \uc124\uc815\ub418\uc5c8\uc9c0\ub9cc \uad6c\uc131\uc5d0 \uc815\uc758\ub41c \uc774\ub984, \uc571 \ud639\uc740 \uc635\uc158\uc774 \uc774\uc804\uc5d0 \uac00\uc838\uc628 \uad6c\uc131 \ub0b4\uc6a9\uacfc \uc77c\uce58\ud558\uc9c0 \uc54a\uc73c\ubbc0\ub85c \uad6c\uc131 \ud56d\ubaa9\uc774 \uadf8\uc5d0 \ub530\ub77c \uc5c5\ub370\uc774\ud2b8\ub418\uc5c8\uc2b5\ub2c8\ub2e4." }, "error": { - "cant_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", - "complete_pairing failed": "\ud398\uc5b4\ub9c1\uc744 \uc644\ub8cc\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \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.", "complete_pairing_failed": "\ud398\uc5b4\ub9c1\uc744 \uc644\ub8cc\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc81c\ucd9c\ud558\uae30 \uc804\uc5d0 \uc785\ub825\ud55c PIN \uc774 \uc62c\ubc14\ub978\uc9c0, TV \uc758 \uc804\uc6d0\uc774 \ucf1c\uc838 \uc788\uace0 \ub124\ud2b8\uc6cc\ud06c\uc5d0 \uc5f0\uacb0\ub418\uc5b4 \uc788\ub294\uc9c0 \ud655\uc778\ud574\uc8fc\uc138\uc694.", "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." diff --git a/homeassistant/components/vizio/translations/lb.json b/homeassistant/components/vizio/translations/lb.json index 8a799834b3a..1fefd98f9f7 100644 --- a/homeassistant/components/vizio/translations/lb.json +++ b/homeassistant/components/vizio/translations/lb.json @@ -1,12 +1,11 @@ { "config": { "abort": { - "already_setup": "D\u00ebs Entr\u00e9e ass scho konfigur\u00e9iert.", + "already_configured_device": "Apparat ass scho konfigur\u00e9iert", "updated_entry": "D\u00ebs Entr\u00e9e ass scho konfigur\u00e9iert mee d\u00e9i defin\u00e9ierten Numm an/oder Optiounen an der Konfiguratioun st\u00ebmmen net mat deene virdrun import\u00e9ierten Optiounen iwwereneen, esou gouf d'Entr\u00e9e deementspriechend aktualis\u00e9iert." }, "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.", + "cannot_connect": "Feeler beim verbannen", "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." diff --git a/homeassistant/components/vizio/translations/nl.json b/homeassistant/components/vizio/translations/nl.json index 33777175c3e..42a69dc8d78 100644 --- a/homeassistant/components/vizio/translations/nl.json +++ b/homeassistant/components/vizio/translations/nl.json @@ -1,11 +1,9 @@ { "config": { "abort": { - "already_setup": "Dit item is al ingesteld.", "updated_entry": "Dit item is al ingesteld, maar de naam en/of opties die zijn gedefinieerd in de configuratie komen niet overeen met de eerder ge\u00efmporteerde configuratie, dus het configuratie-item is dienovereenkomstig bijgewerkt." }, "error": { - "cant_connect": "Kan geen verbinding maken met het apparaat. [Bekijk de documenten] (https://www.home-assistant.io/integrations/vizio/) en controleer of:\n- Het apparaat is ingeschakeld\n- Het apparaat is aangesloten op het netwerk\n- De waarden die u ingevuld correct zijn\nvoordat u weer probeert om opnieuw in te dienen.", "host_exists": "Vizio apparaat met opgegeven host al geconfigureerd.", "name_exists": "Vizio apparaat met opgegeven naam al geconfigureerd." }, diff --git a/homeassistant/components/vizio/translations/no.json b/homeassistant/components/vizio/translations/no.json index d7446a415b6..ab585dcdcf3 100644 --- a/homeassistant/components/vizio/translations/no.json +++ b/homeassistant/components/vizio/translations/no.json @@ -1,12 +1,9 @@ { "config": { "abort": { - "already_setup": "Denne oppf\u00f8ringen er allerede konfigurert.", "updated_entry": "Dette innlegget har allerede v\u00e6rt oppsett, men navnet, apps, og/eller alternativer som er definert i konfigurasjon som ikke stemmer med det som tidligere er importert konfigurasjon, s\u00e5 konfigurasjonen innlegget har blitt oppdatert i henhold til dette." }, "error": { - "cant_connect": "Kunne ikke koble til enheten. [Se gjennom dokumentene](https://www.home-assistant.io/integrations/vizio/) og bekreft at: \n - Enheten er sl\u00e5tt p\u00e5 \n - Enheten er koblet til nettverket \n - Verdiene du fylte ut er n\u00f8yaktige \n f\u00f8r du pr\u00f8ver \u00e5 sende inn p\u00e5 nytt.", - "complete_pairing failed": "Kan ikke fullf\u00f8re sammenkoblingen. Forsikre deg om at PIN-koden du oppga er riktig, og at TV-en fortsatt er p\u00e5 og tilkoblet nettverket f\u00f8r du sender inn p\u00e5 nytt.", "complete_pairing_failed": "Kan ikke fullf\u00f8re sammenkoblingen. Forsikre deg om at PIN-koden du oppga er riktig, og at TV-en fortsatt er p\u00e5 og tilkoblet nettverket f\u00f8r du sender inn p\u00e5 nytt.", "host_exists": "VIZIO-enhet med spesifisert vert allerede konfigurert.", "name_exists": "VIZIO-enhet med spesifisert navn allerede konfigurert." @@ -20,11 +17,9 @@ "title": "Fullf\u00f8r sammenkoblingsprosess" }, "pairing_complete": { - "description": "Din VIZIO SmartCast enheten er n\u00e5 koblet til Home Assistant.", "title": "Sammenkoblingen fullf\u00f8rt" }, "pairing_complete_import": { - "description": "VIZIO SmartCast TV er n\u00e5 koblet til Home Assistant\n\nTilgangstokenet er '**{access_token}**'.", "title": "Sammenkoblingen fullf\u00f8rt" }, "user": { @@ -34,8 +29,7 @@ "host": "Vert", "name": "Navn" }, - "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" + "title": "VIZIO SmartCast-enhet" } } }, @@ -47,8 +41,7 @@ "include_or_exclude": "Inkluder eller ekskludere apper?", "volume_step": "St\u00f8rrelse p\u00e5 volum trinn" }, - "description": "Hvis du har en Smart-TV, kan du eventuelt filtrere kildelisten ved \u00e5 velge hvilke apper som skal inkluderes eller utelates i kildelisten.", - "title": "Oppdater VIZIO SmartCast-alternativer" + "description": "Hvis du har en Smart-TV, kan du eventuelt filtrere kildelisten ved \u00e5 velge hvilke apper som skal inkluderes eller utelates i kildelisten." } } } diff --git a/homeassistant/components/vizio/translations/pl.json b/homeassistant/components/vizio/translations/pl.json index 7e150f234ce..cc94dabaa12 100644 --- a/homeassistant/components/vizio/translations/pl.json +++ b/homeassistant/components/vizio/translations/pl.json @@ -1,12 +1,9 @@ { "config": { "abort": { - "already_setup": "Ten komponent jest ju\u017c skonfigurowany.", "updated_entry": "Ten wpis zosta\u0142 ju\u017c skonfigurowany, ale nazwa i/lub opcje zdefiniowane w konfiguracji nie pasuj\u0105 do wcze\u015bniej zaimportowanych warto\u015bci, wi\u0119c wpis konfiguracji zosta\u0142 odpowiednio zaktualizowany." }, "error": { - "cant_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia.", - "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.", "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." diff --git a/homeassistant/components/vizio/translations/pt-BR.json b/homeassistant/components/vizio/translations/pt-BR.json index 78f72cd05eb..425d1b91f5e 100644 --- a/homeassistant/components/vizio/translations/pt-BR.json +++ b/homeassistant/components/vizio/translations/pt-BR.json @@ -1,7 +1,6 @@ { "config": { "error": { - "complete_pairing failed": "N\u00e3o foi poss\u00edvel concluir o pareamento. Verifique se o PIN que voc\u00ea forneceu est\u00e1 correto e a TV ainda est\u00e1 ligada e conectada \u00e0 internet antes de reenviar.", "complete_pairing_failed": "N\u00e3o foi poss\u00edvel concluir o pareamento. Verifique se o PIN que voc\u00ea forneceu est\u00e1 correto e a TV ainda est\u00e1 ligada e conectada \u00e0 internet antes de reenviar." }, "step": { diff --git a/homeassistant/components/vizio/translations/ru.json b/homeassistant/components/vizio/translations/ru.json index 20d051813c5..d21b0c39615 100644 --- a/homeassistant/components/vizio/translations/ru.json +++ b/homeassistant/components/vizio/translations/ru.json @@ -1,12 +1,11 @@ { "config": { "abort": { - "already_setup": "\u042d\u0442\u0430 \u0437\u0430\u043f\u0438\u0441\u044c \u0443\u0436\u0435 \u0431\u044b\u043b\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d\u0430.", + "already_configured_device": "\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.", "updated_entry": "\u042d\u0442\u0430 \u0437\u0430\u043f\u0438\u0441\u044c \u0443\u0436\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d\u0430, \u043d\u043e \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b, \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u0435 \u0432 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438, \u043d\u0435 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0442 \u0440\u0430\u043d\u0435\u0435 \u0438\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u043c \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f\u043c, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0437\u0430\u043f\u0438\u0441\u044c \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 \u0431\u044b\u043b\u0430 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0430." }, "error": { - "cant_connect": "\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f.", - "complete_pairing failed": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u044c \u0441\u043e\u043f\u0440\u044f\u0436\u0435\u043d\u0438\u0435. \u041f\u0440\u0435\u0436\u0434\u0435 \u0447\u0435\u043c \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u044c \u043f\u043e\u043f\u044b\u0442\u043a\u0443, \u0443\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044c, \u0447\u0442\u043e \u0432\u0432\u0435\u0434\u0435\u043d\u043d\u044b\u0439 \u0412\u0430\u043c\u0438 PIN-\u043a\u043e\u0434 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u044b\u0439, \u0430 \u0442\u0435\u043b\u0435\u0432\u0438\u0437\u043e\u0440 \u0432\u043a\u043b\u044e\u0447\u0435\u043d \u0438 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d \u043a \u0441\u0435\u0442\u0438.", + "cannot_connect": "\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f.", "complete_pairing_failed": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u044c \u0441\u043e\u043f\u0440\u044f\u0436\u0435\u043d\u0438\u0435. \u041f\u0440\u0435\u0436\u0434\u0435 \u0447\u0435\u043c \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u044c \u043f\u043e\u043f\u044b\u0442\u043a\u0443, \u0443\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044c, \u0447\u0442\u043e \u0432\u0432\u0435\u0434\u0435\u043d\u043d\u044b\u0439 \u0412\u0430\u043c\u0438 PIN-\u043a\u043e\u0434 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u044b\u0439, \u0430 \u0442\u0435\u043b\u0435\u0432\u0438\u0437\u043e\u0440 \u0432\u043a\u043b\u044e\u0447\u0435\u043d \u0438 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d \u043a \u0441\u0435\u0442\u0438.", "host_exists": "\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.", "name_exists": "\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 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." @@ -24,7 +23,7 @@ "title": "\u0421\u043e\u043f\u0440\u044f\u0436\u0435\u043d\u0438\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u043e" }, "pairing_complete_import": { - "description": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e VIZIO SmartCast TV \u0442\u0435\u043f\u0435\u0440\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043e \u043a Home Assistant. \n\n\u0412\u0430\u0448 \u0422\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0430 - '**{access_token}**'.", + "description": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e VIZIO SmartCast \u0442\u0435\u043f\u0435\u0440\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043e \u043a Home Assistant. \n\n\u0412\u0430\u0448 \u0422\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0430 - '**{access_token}**'.", "title": "\u0421\u043e\u043f\u0440\u044f\u0436\u0435\u043d\u0438\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u043e" }, "user": { diff --git a/homeassistant/components/vizio/translations/sl.json b/homeassistant/components/vizio/translations/sl.json index e64c3b6cc86..e1f11c9a13e 100644 --- a/homeassistant/components/vizio/translations/sl.json +++ b/homeassistant/components/vizio/translations/sl.json @@ -1,12 +1,9 @@ { "config": { "abort": { - "already_setup": "Ta vnos je \u017ee nastavljen.", "updated_entry": "Ta vnos je bil \u017ee nastavljen, vendar se ime, aplikacije in/ali mo\u017enosti, dolo\u010dene v konfiguraciji, ne ujemajo s predhodno uvo\u017eeno konfiguracijo, zato je bil konfiguracijski vnos ustrezno posodobljen." }, "error": { - "cant_connect": "Ni bilo mogo\u010de povezati z napravo. [Preglejte dokumente] (https://www.home-assistant.io/integrations/vizio/) in ponovno preverite, ali: \n \u2013 Naprava je vklopljena \n \u2013 Naprava je povezana z omre\u017ejem \n \u2013 Vrednosti, ki ste jih izpolnili, so to\u010dne \nnato poskusite ponovno.", - "complete_pairing failed": "Seznanjanja ni mogo\u010de dokon\u010dati. Zagotovite, da je PIN, ki ste ga vnesli, pravilen in da je televizor \u0161e vedno vklopljen in priklju\u010den na omre\u017eje, preden ponovno poizkusite.", "host_exists": "Naprava Vizio z dolo\u010denim gostiteljem je \u017ee konfigurirana.", "name_exists": "Naprava Vizio z navedenim imenom je \u017ee konfigurirana." }, diff --git a/homeassistant/components/vizio/translations/sv.json b/homeassistant/components/vizio/translations/sv.json index 5c5a25dd129..0762066c8b4 100644 --- a/homeassistant/components/vizio/translations/sv.json +++ b/homeassistant/components/vizio/translations/sv.json @@ -1,11 +1,9 @@ { "config": { "abort": { - "already_setup": "Den h\u00e4r posten har redan st\u00e4llts in.", "updated_entry": "Den h\u00e4r posten har redan konfigurerats, men namnet och/eller alternativen som definierats i konfigurationen matchar inte den tidigare importerade konfigurationen och d\u00e4rf\u00f6r har konfigureringsposten uppdaterats i enlighet med detta." }, "error": { - "cant_connect": "Det gick inte att ansluta till enheten. [Granska dokumentationen] (https://www.home-assistant.io/integrations/vizio/) och p\u00e5 nytt kontrollera att\n- Enheten \u00e4r p\u00e5slagen\n- Enheten \u00e4r ansluten till n\u00e4tverket\n- De v\u00e4rden du fyllt i \u00e4r korrekta\ninnan du f\u00f6rs\u00f6ker skicka in igen.", "host_exists": "Vizio-enheten med angivet v\u00e4rdnamn \u00e4r redan konfigurerad.", "name_exists": "Vizio-enheten med angivet namn \u00e4r redan konfigurerad." }, diff --git a/homeassistant/components/vizio/translations/zh-Hant.json b/homeassistant/components/vizio/translations/zh-Hant.json index a3508481a1b..0ddf8f8b88f 100644 --- a/homeassistant/components/vizio/translations/zh-Hant.json +++ b/homeassistant/components/vizio/translations/zh-Hant.json @@ -1,15 +1,14 @@ { "config": { "abort": { - "already_setup": "\u6b64\u7269\u4ef6\u5df2\u8a2d\u5b9a\u904e\u3002", + "already_configured_device": "\u8a2d\u5099\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", "updated_entry": "\u6b64\u7269\u4ef6\u5df2\u7d93\u8a2d\u5b9a\uff0c\u4f46\u8a2d\u5b9a\u4e4b\u540d\u7a31\u3001App \u53ca/\u6216\u9078\u9805\u8207\u5148\u524d\u532f\u5165\u7684\u7269\u4ef6\u9078\u9805\u503c\u4e0d\u5408\uff0c\u56e0\u6b64\u8a2d\u5b9a\u5c07\u6703\u8ddf\u8457\u66f4\u65b0\u3002" }, "error": { - "cant_connect": "\u9023\u7dda\u5931\u6557", - "complete_pairing failed": "\u7121\u6cd5\u5b8c\u6210\u914d\u5c0d\uff0c\u50b3\u9001\u524d\u3001\u8acb\u78ba\u5b9a\u6240\u8f38\u5165\u7684 PIN \u78bc\u3001\u540c\u6642\u96fb\u8996\u5df2\u7d93\u958b\u555f\u4e26\u9023\u7dda\u81f3\u7db2\u8def\u3002", + "cannot_connect": "\u9023\u7dda\u5931\u6557", "complete_pairing_failed": "\u7121\u6cd5\u5b8c\u6210\u914d\u5c0d\uff0c\u50b3\u9001\u524d\u3001\u8acb\u78ba\u5b9a\u6240\u8f38\u5165\u7684 PIN \u78bc\u3001\u540c\u6642\u96fb\u8996\u5df2\u7d93\u958b\u555f\u4e26\u9023\u7dda\u81f3\u7db2\u8def\u3002", - "host_exists": "\u4f9d\u4e3b\u6a5f\u7aef\u4e4b VIZIO \u5143\u4ef6\u8a2d\u5b9a\u5df2\u8a2d\u5b9a\u5b8c\u6210\u3002", - "name_exists": "\u4f9d\u540d\u7a31\u4e4b VIZIO \u5143\u4ef6\u8a2d\u5b9a\u5df2\u8a2d\u5b9a\u5b8c\u6210\u3002" + "host_exists": "\u4f9d\u4e3b\u6a5f\u7aef\u4e4b VIZIO SmartCast \u8a2d\u5099 \u8a2d\u5b9a\u5df2\u8a2d\u5b9a\u5b8c\u6210\u3002", + "name_exists": "\u4f9d\u540d\u7a31\u4e4b VIZIO SmartCast \u8a2d\u5099 \u8a2d\u5b9a\u5df2\u8a2d\u5b9a\u5b8c\u6210\u3002" }, "step": { "pair_tv": { @@ -20,11 +19,11 @@ "title": "\u5b8c\u6210\u914d\u5c0d\u904e\u7a0b" }, "pairing_complete": { - "description": "VIZIO SmartCast \u8a2d\u5099\u5df2\u7d93\u9023\u7dda\u81f3 Home Assistant\u3002", + "description": "VIZIO SmartCast \u8a2d\u5099 \u5df2\u7d93\u9023\u7dda\u81f3 Home Assistant\u3002", "title": "\u914d\u5c0d\u5b8c\u6210" }, "pairing_complete_import": { - "description": "VIZIO SmartCast TV \u8a2d\u5099\u5df2\u9023\u7dda\u81f3 Home Assistant\u3002\n\n\u5b58\u53d6\u5bc6\u9470\u70ba '**{access_token}**'\u3002", + "description": "VIZIO SmartCast \u8a2d\u5099 \u5df2\u9023\u7dda\u81f3 Home Assistant\u3002\n\n\u5b58\u53d6\u5bc6\u9470\u70ba '**{access_token}**'\u3002", "title": "\u914d\u5c0d\u5b8c\u6210" }, "user": { @@ -35,7 +34,7 @@ "name": "\u540d\u7a31" }, "description": "\u6b64\u96fb\u8996\u50c5\u9700\u5b58\u53d6\u5bc6\u9470\u5047\u5982\u60a8\u6b63\u5728\u8a2d\u5b9a\u96fb\u8996\u3001\u5c1a\u672a\u53d6\u5f97\u5b58\u53d6\u5bc6\u9470 \uff0c\u4fdd\u6301\u7a7a\u767d\u4ee5\u9032\u884c\u914d\u5c0d\u904e\u7a0b\u3002", - "title": "\u8a2d\u5b9a VIZIO SmartCast \u8a2d\u5099" + "title": "VIZIO SmartCast \u8a2d\u5099" } } }, @@ -48,7 +47,7 @@ "volume_step": "\u97f3\u91cf\u5927\u5c0f" }, "description": "\u5047\u5982\u60a8\u64c1\u6709 Smart TV\u3001\u53ef\u7531\u4f86\u6e90\u5217\u8868\u4e2d\u9078\u64c7\u6240\u8981\u904e\u6ffe\u5305\u542b\u6216\u6392\u9664\u7684 App\u3002\u3002", - "title": "\u66f4\u65b0 VIZIO SmartCast \u9078\u9805" + "title": "\u66f4\u65b0 VIZIO SmartCast \u8a2d\u5099 \u9078\u9805" } } } diff --git a/homeassistant/components/wemo/translations/bg.json b/homeassistant/components/wemo/translations/bg.json index 19f2121f42a..d532f7088f1 100644 --- a/homeassistant/components/wemo/translations/bg.json +++ b/homeassistant/components/wemo/translations/bg.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "\u0418\u0441\u043a\u0430\u0442\u0435 \u043b\u0438 \u0434\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u0435 Wemo?", - "title": "Wemo" + "description": "\u0418\u0441\u043a\u0430\u0442\u0435 \u043b\u0438 \u0434\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u0435 Wemo?" } } } diff --git a/homeassistant/components/wemo/translations/ca.json b/homeassistant/components/wemo/translations/ca.json index 36ce6153796..007252042cc 100644 --- a/homeassistant/components/wemo/translations/ca.json +++ b/homeassistant/components/wemo/translations/ca.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Vols configurar Wemo?", - "title": "Wemo" + "description": "Vols configurar Wemo?" } } } diff --git a/homeassistant/components/wemo/translations/da.json b/homeassistant/components/wemo/translations/da.json index d2192d85f4b..4094128de25 100644 --- a/homeassistant/components/wemo/translations/da.json +++ b/homeassistant/components/wemo/translations/da.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Vil du konfigurere Wemo?", - "title": "Wemo" + "description": "Vil du konfigurere Wemo?" } } } diff --git a/homeassistant/components/wemo/translations/de.json b/homeassistant/components/wemo/translations/de.json index a0d4465796d..f20ad5598ab 100644 --- a/homeassistant/components/wemo/translations/de.json +++ b/homeassistant/components/wemo/translations/de.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "M\u00f6chtest du Wemo einrichten?", - "title": "Wemo" + "description": "M\u00f6chtest du Wemo einrichten?" } } } diff --git a/homeassistant/components/wemo/translations/en.json b/homeassistant/components/wemo/translations/en.json index ef86613ea79..32ef4cc4cf5 100644 --- a/homeassistant/components/wemo/translations/en.json +++ b/homeassistant/components/wemo/translations/en.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Do you want to set up Wemo?", - "title": "Wemo" + "description": "Do you want to set up Wemo?" } } } diff --git a/homeassistant/components/wemo/translations/es-419.json b/homeassistant/components/wemo/translations/es-419.json index 3010cfed63b..56bcfd9249e 100644 --- a/homeassistant/components/wemo/translations/es-419.json +++ b/homeassistant/components/wemo/translations/es-419.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "\u00bfDesea configurar Wemo?", - "title": "Wemo" + "description": "\u00bfDesea configurar Wemo?" } } } diff --git a/homeassistant/components/wemo/translations/es.json b/homeassistant/components/wemo/translations/es.json index c8c45bb1083..1c7088a5280 100644 --- a/homeassistant/components/wemo/translations/es.json +++ b/homeassistant/components/wemo/translations/es.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "\u00bfQuieres configurar Wemo?", - "title": "Wemo" + "description": "\u00bfQuieres configurar Wemo?" } } } diff --git a/homeassistant/components/wemo/translations/fr.json b/homeassistant/components/wemo/translations/fr.json index 47b7d0e6b74..ccf2ac6ef21 100644 --- a/homeassistant/components/wemo/translations/fr.json +++ b/homeassistant/components/wemo/translations/fr.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Voulez-vous configurer Wemo?", - "title": "Wemo" + "description": "Voulez-vous configurer Wemo?" } } } diff --git a/homeassistant/components/wemo/translations/it.json b/homeassistant/components/wemo/translations/it.json index a14a47a459b..46204dc6057 100644 --- a/homeassistant/components/wemo/translations/it.json +++ b/homeassistant/components/wemo/translations/it.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Vuoi configurare Wemo?", - "title": "Wemo" + "description": "Vuoi configurare Wemo?" } } } diff --git a/homeassistant/components/wemo/translations/ko.json b/homeassistant/components/wemo/translations/ko.json index 41de2e3aaeb..a262f7ebd3e 100644 --- a/homeassistant/components/wemo/translations/ko.json +++ b/homeassistant/components/wemo/translations/ko.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Wemo \ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", - "title": "Wemo" + "description": "Wemo \ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" } } } diff --git a/homeassistant/components/wemo/translations/lb.json b/homeassistant/components/wemo/translations/lb.json index 8602a1adcbf..9ec38088d1b 100644 --- a/homeassistant/components/wemo/translations/lb.json +++ b/homeassistant/components/wemo/translations/lb.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Soll Wemo konfigur\u00e9iert ginn?", - "title": "Wemo" + "description": "Soll Wemo konfigur\u00e9iert ginn?" } } } diff --git a/homeassistant/components/wemo/translations/nl.json b/homeassistant/components/wemo/translations/nl.json index dc40fd0d66b..6146072623a 100644 --- a/homeassistant/components/wemo/translations/nl.json +++ b/homeassistant/components/wemo/translations/nl.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Wilt u Wemo instellen?", - "title": "Wemo" + "description": "Wilt u Wemo instellen?" } } } diff --git a/homeassistant/components/wemo/translations/no.json b/homeassistant/components/wemo/translations/no.json index 13476f63ec2..59ff68d5790 100644 --- a/homeassistant/components/wemo/translations/no.json +++ b/homeassistant/components/wemo/translations/no.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "\u00d8nsker du \u00e5 sette opp Wemo?", - "title": "" + "description": "\u00d8nsker du \u00e5 sette opp Wemo?" } } } diff --git a/homeassistant/components/wemo/translations/pl.json b/homeassistant/components/wemo/translations/pl.json index c1c7d9746d5..456dca40d4f 100644 --- a/homeassistant/components/wemo/translations/pl.json +++ b/homeassistant/components/wemo/translations/pl.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Czy chcesz skonfigurowa\u0107 Wemo?", - "title": "Wemo" + "description": "Czy chcesz skonfigurowa\u0107 Wemo?" } } } diff --git a/homeassistant/components/wemo/translations/pt-BR.json b/homeassistant/components/wemo/translations/pt-BR.json index a4fb8960af9..c14cb64bf4e 100644 --- a/homeassistant/components/wemo/translations/pt-BR.json +++ b/homeassistant/components/wemo/translations/pt-BR.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Voc\u00ea quer configurar o Wemo?", - "title": "Wemo" + "description": "Voc\u00ea quer configurar o Wemo?" } } } diff --git a/homeassistant/components/wemo/translations/ru.json b/homeassistant/components/wemo/translations/ru.json index 5f5c4380586..7123ac1b601 100644 --- a/homeassistant/components/wemo/translations/ru.json +++ b/homeassistant/components/wemo/translations/ru.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c Wemo?", - "title": "Wemo" + "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c Wemo?" } } } diff --git a/homeassistant/components/wemo/translations/sl.json b/homeassistant/components/wemo/translations/sl.json index 8f754b309be..e499e7eb787 100644 --- a/homeassistant/components/wemo/translations/sl.json +++ b/homeassistant/components/wemo/translations/sl.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Ali \u017eelite nastaviti Wemo?", - "title": "Wemo" + "description": "Ali \u017eelite nastaviti Wemo?" } } } diff --git a/homeassistant/components/wemo/translations/sv.json b/homeassistant/components/wemo/translations/sv.json index 8476f17077c..be479aa1333 100644 --- a/homeassistant/components/wemo/translations/sv.json +++ b/homeassistant/components/wemo/translations/sv.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "Vill du konfigurera Wemo?", - "title": "Wemo" + "description": "Vill du konfigurera Wemo?" } } } diff --git a/homeassistant/components/wemo/translations/zh-Hant.json b/homeassistant/components/wemo/translations/zh-Hant.json index b3845e0df57..29167a13480 100644 --- a/homeassistant/components/wemo/translations/zh-Hant.json +++ b/homeassistant/components/wemo/translations/zh-Hant.json @@ -6,8 +6,7 @@ }, "step": { "confirm": { - "description": "\u662f\u5426\u8981\u8a2d\u5b9a Wemo\uff1f", - "title": "Wemo" + "description": "\u662f\u5426\u8981\u8a2d\u5b9a Wemo\uff1f" } } } diff --git a/homeassistant/components/wled/translations/af.json b/homeassistant/components/wled/translations/af.json index a9107341e37..77eafbdb9bd 100644 --- a/homeassistant/components/wled/translations/af.json +++ b/homeassistant/components/wled/translations/af.json @@ -10,8 +10,7 @@ "flow_title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", "step": { "user": { - "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" + "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" }, "zeroconf_confirm": { "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", diff --git a/homeassistant/components/wled/translations/ar.json b/homeassistant/components/wled/translations/ar.json index a9107341e37..77eafbdb9bd 100644 --- a/homeassistant/components/wled/translations/ar.json +++ b/homeassistant/components/wled/translations/ar.json @@ -10,8 +10,7 @@ "flow_title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", "step": { "user": { - "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" + "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" }, "zeroconf_confirm": { "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", diff --git a/homeassistant/components/wled/translations/bg.json b/homeassistant/components/wled/translations/bg.json index 8e091a7eace..98c2c33f618 100644 --- a/homeassistant/components/wled/translations/bg.json +++ b/homeassistant/components/wled/translations/bg.json @@ -13,8 +13,7 @@ "data": { "host": "\u0410\u0434\u0440\u0435\u0441" }, - "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" + "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" }, "zeroconf_confirm": { "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", diff --git a/homeassistant/components/wled/translations/bs.json b/homeassistant/components/wled/translations/bs.json index a9107341e37..77eafbdb9bd 100644 --- a/homeassistant/components/wled/translations/bs.json +++ b/homeassistant/components/wled/translations/bs.json @@ -10,8 +10,7 @@ "flow_title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", "step": { "user": { - "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" + "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" }, "zeroconf_confirm": { "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", diff --git a/homeassistant/components/wled/translations/ca.json b/homeassistant/components/wled/translations/ca.json index 764c644994f..72a8fc519b4 100644 --- a/homeassistant/components/wled/translations/ca.json +++ b/homeassistant/components/wled/translations/ca.json @@ -13,8 +13,7 @@ "data": { "host": "Amfitri\u00f3" }, - "description": "Configura el teu WLED per integrar-lo amb Home Assistant.", - "title": "Enlla\u00e7 amb WLED" + "description": "Configura el teu WLED per integrar-lo amb Home Assistant." }, "zeroconf_confirm": { "description": "Vols afegir el WLED `{name}` a Home Assistant?", diff --git a/homeassistant/components/wled/translations/cs.json b/homeassistant/components/wled/translations/cs.json index a9107341e37..77eafbdb9bd 100644 --- a/homeassistant/components/wled/translations/cs.json +++ b/homeassistant/components/wled/translations/cs.json @@ -10,8 +10,7 @@ "flow_title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", "step": { "user": { - "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" + "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" }, "zeroconf_confirm": { "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", diff --git a/homeassistant/components/wled/translations/cy.json b/homeassistant/components/wled/translations/cy.json index a9107341e37..77eafbdb9bd 100644 --- a/homeassistant/components/wled/translations/cy.json +++ b/homeassistant/components/wled/translations/cy.json @@ -10,8 +10,7 @@ "flow_title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", "step": { "user": { - "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" + "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" }, "zeroconf_confirm": { "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", diff --git a/homeassistant/components/wled/translations/da.json b/homeassistant/components/wled/translations/da.json index 739a9befa82..ec45027f3ab 100644 --- a/homeassistant/components/wled/translations/da.json +++ b/homeassistant/components/wled/translations/da.json @@ -13,8 +13,7 @@ "data": { "host": "V\u00e6rt eller IP-adresse" }, - "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" + "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" }, "zeroconf_confirm": { "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", diff --git a/homeassistant/components/wled/translations/de.json b/homeassistant/components/wled/translations/de.json index 697fa1acf52..bfae4dd47da 100644 --- a/homeassistant/components/wled/translations/de.json +++ b/homeassistant/components/wled/translations/de.json @@ -13,8 +13,7 @@ "data": { "host": "Hostname oder IP-Adresse" }, - "description": "Richten Sie Ihren WLED f\u00fcr die Integration mit Home Assistant ein.", - "title": "Verkn\u00fcpfen Sie Ihre WLED" + "description": "Richten Sie Ihren WLED f\u00fcr die Integration mit Home Assistant ein." }, "zeroconf_confirm": { "description": "M\u00f6chten Sie die WLED mit dem Namen `{name}` zu Home Assistant hinzuf\u00fcgen?", diff --git a/homeassistant/components/wled/translations/el.json b/homeassistant/components/wled/translations/el.json index a9107341e37..77eafbdb9bd 100644 --- a/homeassistant/components/wled/translations/el.json +++ b/homeassistant/components/wled/translations/el.json @@ -10,8 +10,7 @@ "flow_title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", "step": { "user": { - "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" + "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" }, "zeroconf_confirm": { "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", diff --git a/homeassistant/components/wled/translations/en.json b/homeassistant/components/wled/translations/en.json index 362798be5bd..2576270808d 100644 --- a/homeassistant/components/wled/translations/en.json +++ b/homeassistant/components/wled/translations/en.json @@ -13,8 +13,7 @@ "data": { "host": "Host" }, - "description": "Set up your WLED to integrate with Home Assistant.", - "title": "Link your WLED" + "description": "Set up your WLED to integrate with Home Assistant." }, "zeroconf_confirm": { "description": "Do you want to add the WLED named `{name}` to Home Assistant?", diff --git a/homeassistant/components/wled/translations/eo.json b/homeassistant/components/wled/translations/eo.json index a9107341e37..77eafbdb9bd 100644 --- a/homeassistant/components/wled/translations/eo.json +++ b/homeassistant/components/wled/translations/eo.json @@ -10,8 +10,7 @@ "flow_title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", "step": { "user": { - "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" + "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" }, "zeroconf_confirm": { "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", diff --git a/homeassistant/components/wled/translations/es-419.json b/homeassistant/components/wled/translations/es-419.json index b5973638373..c6dd845b620 100644 --- a/homeassistant/components/wled/translations/es-419.json +++ b/homeassistant/components/wled/translations/es-419.json @@ -13,8 +13,7 @@ "data": { "host": "Host o direcci\u00f3n IP" }, - "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" + "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" }, "zeroconf_confirm": { "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", diff --git a/homeassistant/components/wled/translations/es.json b/homeassistant/components/wled/translations/es.json index 6b2c46f25e0..aab9752d8b6 100644 --- a/homeassistant/components/wled/translations/es.json +++ b/homeassistant/components/wled/translations/es.json @@ -13,8 +13,7 @@ "data": { "host": "Host o direcci\u00f3n IP" }, - "description": "Configura el WLED para integrarlo con Home Assistant.", - "title": "Vincula tu WLED" + "description": "Configura el WLED para integrarlo con Home Assistant." }, "zeroconf_confirm": { "description": "\u00bfQuieres a\u00f1adir el WLED `{name}` a Home Assistant?", diff --git a/homeassistant/components/wled/translations/et.json b/homeassistant/components/wled/translations/et.json index a9107341e37..77eafbdb9bd 100644 --- a/homeassistant/components/wled/translations/et.json +++ b/homeassistant/components/wled/translations/et.json @@ -10,8 +10,7 @@ "flow_title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", "step": { "user": { - "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" + "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" }, "zeroconf_confirm": { "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", diff --git a/homeassistant/components/wled/translations/eu.json b/homeassistant/components/wled/translations/eu.json index a9107341e37..77eafbdb9bd 100644 --- a/homeassistant/components/wled/translations/eu.json +++ b/homeassistant/components/wled/translations/eu.json @@ -10,8 +10,7 @@ "flow_title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", "step": { "user": { - "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" + "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" }, "zeroconf_confirm": { "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", diff --git a/homeassistant/components/wled/translations/fa.json b/homeassistant/components/wled/translations/fa.json index a9107341e37..77eafbdb9bd 100644 --- a/homeassistant/components/wled/translations/fa.json +++ b/homeassistant/components/wled/translations/fa.json @@ -10,8 +10,7 @@ "flow_title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", "step": { "user": { - "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" + "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" }, "zeroconf_confirm": { "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", diff --git a/homeassistant/components/wled/translations/fi.json b/homeassistant/components/wled/translations/fi.json index a9107341e37..77eafbdb9bd 100644 --- a/homeassistant/components/wled/translations/fi.json +++ b/homeassistant/components/wled/translations/fi.json @@ -10,8 +10,7 @@ "flow_title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", "step": { "user": { - "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" + "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" }, "zeroconf_confirm": { "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", diff --git a/homeassistant/components/wled/translations/fr.json b/homeassistant/components/wled/translations/fr.json index ded950bdf69..a48c466c10a 100644 --- a/homeassistant/components/wled/translations/fr.json +++ b/homeassistant/components/wled/translations/fr.json @@ -13,8 +13,7 @@ "data": { "host": "H\u00f4te ou adresse IP" }, - "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" + "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" }, "zeroconf_confirm": { "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", diff --git a/homeassistant/components/wled/translations/gsw.json b/homeassistant/components/wled/translations/gsw.json index a9107341e37..77eafbdb9bd 100644 --- a/homeassistant/components/wled/translations/gsw.json +++ b/homeassistant/components/wled/translations/gsw.json @@ -10,8 +10,7 @@ "flow_title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", "step": { "user": { - "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" + "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" }, "zeroconf_confirm": { "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", diff --git a/homeassistant/components/wled/translations/he.json b/homeassistant/components/wled/translations/he.json index a9107341e37..77eafbdb9bd 100644 --- a/homeassistant/components/wled/translations/he.json +++ b/homeassistant/components/wled/translations/he.json @@ -10,8 +10,7 @@ "flow_title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", "step": { "user": { - "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" + "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" }, "zeroconf_confirm": { "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", diff --git a/homeassistant/components/wled/translations/hi.json b/homeassistant/components/wled/translations/hi.json index a9107341e37..77eafbdb9bd 100644 --- a/homeassistant/components/wled/translations/hi.json +++ b/homeassistant/components/wled/translations/hi.json @@ -10,8 +10,7 @@ "flow_title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", "step": { "user": { - "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" + "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" }, "zeroconf_confirm": { "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", diff --git a/homeassistant/components/wled/translations/hr.json b/homeassistant/components/wled/translations/hr.json index a9107341e37..77eafbdb9bd 100644 --- a/homeassistant/components/wled/translations/hr.json +++ b/homeassistant/components/wled/translations/hr.json @@ -10,8 +10,7 @@ "flow_title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", "step": { "user": { - "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" + "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" }, "zeroconf_confirm": { "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", diff --git a/homeassistant/components/wled/translations/hu.json b/homeassistant/components/wled/translations/hu.json index 3565db0376c..f86a02da7c9 100644 --- a/homeassistant/components/wled/translations/hu.json +++ b/homeassistant/components/wled/translations/hu.json @@ -13,8 +13,7 @@ "data": { "host": "Hoszt" }, - "description": "\u00c1ll\u00edtsd be a WLED-et a Home Assistant-ba val\u00f3 integr\u00e1l\u00e1shoz.", - "title": "Csatlakoztasd a WLED-et" + "description": "\u00c1ll\u00edtsd be a WLED-et a Home Assistant-ba val\u00f3 integr\u00e1l\u00e1shoz." }, "zeroconf_confirm": { "description": "Szeretn\u00e9d hozz\u00e1adni a(z) `{name}` WLED-et a Home Assistant-hoz?", diff --git a/homeassistant/components/wled/translations/id.json b/homeassistant/components/wled/translations/id.json index a9107341e37..77eafbdb9bd 100644 --- a/homeassistant/components/wled/translations/id.json +++ b/homeassistant/components/wled/translations/id.json @@ -10,8 +10,7 @@ "flow_title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", "step": { "user": { - "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" + "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" }, "zeroconf_confirm": { "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", diff --git a/homeassistant/components/wled/translations/is.json b/homeassistant/components/wled/translations/is.json index a9107341e37..77eafbdb9bd 100644 --- a/homeassistant/components/wled/translations/is.json +++ b/homeassistant/components/wled/translations/is.json @@ -10,8 +10,7 @@ "flow_title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", "step": { "user": { - "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" + "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" }, "zeroconf_confirm": { "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", diff --git a/homeassistant/components/wled/translations/it.json b/homeassistant/components/wled/translations/it.json index 0c1d505dc6d..897c04539f0 100644 --- a/homeassistant/components/wled/translations/it.json +++ b/homeassistant/components/wled/translations/it.json @@ -13,8 +13,7 @@ "data": { "host": "Host" }, - "description": "Configura WLED per l'integrazione con Home Assistant.", - "title": "Collega il tuo WLED" + "description": "Configura WLED per l'integrazione con Home Assistant." }, "zeroconf_confirm": { "description": "Vuoi aggiungere il WLED chiamato `{name}` a Home Assistant?", diff --git a/homeassistant/components/wled/translations/ja.json b/homeassistant/components/wled/translations/ja.json index a9107341e37..77eafbdb9bd 100644 --- a/homeassistant/components/wled/translations/ja.json +++ b/homeassistant/components/wled/translations/ja.json @@ -10,8 +10,7 @@ "flow_title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", "step": { "user": { - "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" + "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" }, "zeroconf_confirm": { "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", diff --git a/homeassistant/components/wled/translations/ko.json b/homeassistant/components/wled/translations/ko.json index 12d964f25c6..684f0879b55 100644 --- a/homeassistant/components/wled/translations/ko.json +++ b/homeassistant/components/wled/translations/ko.json @@ -13,8 +13,7 @@ "data": { "host": "\ud638\uc2a4\ud2b8" }, - "description": "Home Assistant \uc5d0 WLED \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud569\ub2c8\ub2e4.", - "title": "WLED \uc5f0\uacb0\ud558\uae30" + "description": "Home Assistant \uc5d0 WLED \ud1b5\ud569 \uad6c\uc131\uc694\uc18c\ub97c \uc124\uc815\ud569\ub2c8\ub2e4." }, "zeroconf_confirm": { "description": "Home Assistant \uc5d0 WLED `{name}` \uc744(\ub97c) \ucd94\uac00\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", diff --git a/homeassistant/components/wled/translations/lb.json b/homeassistant/components/wled/translations/lb.json index 7657e1a12cc..8a5069a0e1b 100644 --- a/homeassistant/components/wled/translations/lb.json +++ b/homeassistant/components/wled/translations/lb.json @@ -13,8 +13,7 @@ "data": { "host": "Numm oder IP Adresse" }, - "description": "D\u00e4in WLED als Integratioun mam Home Assistant ariichten.", - "title": "D\u00e4in WLED verbannen" + "description": "D\u00e4in WLED als Integratioun mam Home Assistant ariichten." }, "zeroconf_confirm": { "description": "Soll de WLED mam Numm `{name}` am Home Assistant dob\u00e4i gesaat ginn?", diff --git a/homeassistant/components/wled/translations/lt.json b/homeassistant/components/wled/translations/lt.json index a9107341e37..77eafbdb9bd 100644 --- a/homeassistant/components/wled/translations/lt.json +++ b/homeassistant/components/wled/translations/lt.json @@ -10,8 +10,7 @@ "flow_title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", "step": { "user": { - "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" + "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" }, "zeroconf_confirm": { "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", diff --git a/homeassistant/components/wled/translations/lv.json b/homeassistant/components/wled/translations/lv.json index a9107341e37..77eafbdb9bd 100644 --- a/homeassistant/components/wled/translations/lv.json +++ b/homeassistant/components/wled/translations/lv.json @@ -10,8 +10,7 @@ "flow_title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", "step": { "user": { - "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" + "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" }, "zeroconf_confirm": { "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", diff --git a/homeassistant/components/wled/translations/nl.json b/homeassistant/components/wled/translations/nl.json index 5b78cbd791b..fd6f2ff68f8 100644 --- a/homeassistant/components/wled/translations/nl.json +++ b/homeassistant/components/wled/translations/nl.json @@ -13,8 +13,7 @@ "data": { "host": "Hostnaam of IP-adres" }, - "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" + "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" }, "zeroconf_confirm": { "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", diff --git a/homeassistant/components/wled/translations/nn.json b/homeassistant/components/wled/translations/nn.json index a9107341e37..77eafbdb9bd 100644 --- a/homeassistant/components/wled/translations/nn.json +++ b/homeassistant/components/wled/translations/nn.json @@ -10,8 +10,7 @@ "flow_title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", "step": { "user": { - "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" + "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" }, "zeroconf_confirm": { "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", diff --git a/homeassistant/components/wled/translations/no.json b/homeassistant/components/wled/translations/no.json index fd3b5f94cbe..9f9c2b40e6e 100644 --- a/homeassistant/components/wled/translations/no.json +++ b/homeassistant/components/wled/translations/no.json @@ -13,8 +13,7 @@ "data": { "host": "Vert eller IP-adresse" }, - "description": "Sett opp WLED til \u00e5 integreres med Home Assistant.", - "title": "Linken din WLED" + "description": "Sett opp WLED til \u00e5 integreres med Home Assistant." }, "zeroconf_confirm": { "description": "Vil du legge til WLED med navnet ' {name} ' i Home Assistant?", diff --git a/homeassistant/components/wled/translations/pl.json b/homeassistant/components/wled/translations/pl.json index 8a912fb6a19..6f68055d385 100644 --- a/homeassistant/components/wled/translations/pl.json +++ b/homeassistant/components/wled/translations/pl.json @@ -13,8 +13,7 @@ "data": { "host": "Nazwa hosta lub adres IP" }, - "description": "Konfiguracja WLED w celu integracji z Home Assistantem.", - "title": "Po\u0142\u0105czenie z WLED" + "description": "Konfiguracja WLED w celu integracji z Home Assistantem." }, "zeroconf_confirm": { "description": "Czy chcesz doda\u0107 WLED o nazwie `{name}` do Home Assistanta?", diff --git a/homeassistant/components/wled/translations/pt-BR.json b/homeassistant/components/wled/translations/pt-BR.json index a9107341e37..77eafbdb9bd 100644 --- a/homeassistant/components/wled/translations/pt-BR.json +++ b/homeassistant/components/wled/translations/pt-BR.json @@ -10,8 +10,7 @@ "flow_title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", "step": { "user": { - "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" + "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" }, "zeroconf_confirm": { "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", diff --git a/homeassistant/components/wled/translations/pt.json b/homeassistant/components/wled/translations/pt.json index 356c842839e..4738ca85983 100644 --- a/homeassistant/components/wled/translations/pt.json +++ b/homeassistant/components/wled/translations/pt.json @@ -13,8 +13,7 @@ "data": { "host": "Nome servidor ou endere\u00e7o IP" }, - "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" + "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" }, "zeroconf_confirm": { "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", diff --git a/homeassistant/components/wled/translations/ro.json b/homeassistant/components/wled/translations/ro.json index a9107341e37..77eafbdb9bd 100644 --- a/homeassistant/components/wled/translations/ro.json +++ b/homeassistant/components/wled/translations/ro.json @@ -10,8 +10,7 @@ "flow_title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", "step": { "user": { - "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" + "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" }, "zeroconf_confirm": { "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", diff --git a/homeassistant/components/wled/translations/ru.json b/homeassistant/components/wled/translations/ru.json index 867b2ca2ab6..5d50b75b94f 100644 --- a/homeassistant/components/wled/translations/ru.json +++ b/homeassistant/components/wled/translations/ru.json @@ -13,8 +13,7 @@ "data": { "host": "\u0425\u043e\u0441\u0442" }, - "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0441 WLED.", - "title": "WLED" + "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." }, "zeroconf_confirm": { "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c WLED `{name}`?", diff --git a/homeassistant/components/wled/translations/sk.json b/homeassistant/components/wled/translations/sk.json index a9107341e37..77eafbdb9bd 100644 --- a/homeassistant/components/wled/translations/sk.json +++ b/homeassistant/components/wled/translations/sk.json @@ -10,8 +10,7 @@ "flow_title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", "step": { "user": { - "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" + "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" }, "zeroconf_confirm": { "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", diff --git a/homeassistant/components/wled/translations/sl.json b/homeassistant/components/wled/translations/sl.json index e0f4b187e54..086e8d111ab 100644 --- a/homeassistant/components/wled/translations/sl.json +++ b/homeassistant/components/wled/translations/sl.json @@ -13,8 +13,7 @@ "data": { "host": "Gostitelj ali IP naslov" }, - "description": "Nastavite svoj WLED za integracijo s Home Assistant.", - "title": "Pove\u017eite svoj WLED" + "description": "Nastavite svoj WLED za integracijo s Home Assistant." }, "zeroconf_confirm": { "description": "Ali \u017eelite dodati WLED z imenom `{name}` v Home Assistant?", diff --git a/homeassistant/components/wled/translations/sr-Latn.json b/homeassistant/components/wled/translations/sr-Latn.json index a9107341e37..77eafbdb9bd 100644 --- a/homeassistant/components/wled/translations/sr-Latn.json +++ b/homeassistant/components/wled/translations/sr-Latn.json @@ -10,8 +10,7 @@ "flow_title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", "step": { "user": { - "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" + "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" }, "zeroconf_confirm": { "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", diff --git a/homeassistant/components/wled/translations/sr.json b/homeassistant/components/wled/translations/sr.json index a9107341e37..77eafbdb9bd 100644 --- a/homeassistant/components/wled/translations/sr.json +++ b/homeassistant/components/wled/translations/sr.json @@ -10,8 +10,7 @@ "flow_title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", "step": { "user": { - "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" + "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" }, "zeroconf_confirm": { "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", diff --git a/homeassistant/components/wled/translations/sv.json b/homeassistant/components/wled/translations/sv.json index a348c0d5572..f0be486f0c3 100644 --- a/homeassistant/components/wled/translations/sv.json +++ b/homeassistant/components/wled/translations/sv.json @@ -13,8 +13,7 @@ "data": { "host": "V\u00e4rd eller IP-adress" }, - "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" + "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" }, "zeroconf_confirm": { "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", diff --git a/homeassistant/components/wled/translations/ta.json b/homeassistant/components/wled/translations/ta.json index a9107341e37..77eafbdb9bd 100644 --- a/homeassistant/components/wled/translations/ta.json +++ b/homeassistant/components/wled/translations/ta.json @@ -10,8 +10,7 @@ "flow_title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", "step": { "user": { - "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" + "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" }, "zeroconf_confirm": { "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", diff --git a/homeassistant/components/wled/translations/te.json b/homeassistant/components/wled/translations/te.json index a9107341e37..77eafbdb9bd 100644 --- a/homeassistant/components/wled/translations/te.json +++ b/homeassistant/components/wled/translations/te.json @@ -10,8 +10,7 @@ "flow_title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", "step": { "user": { - "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" + "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" }, "zeroconf_confirm": { "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", diff --git a/homeassistant/components/wled/translations/th.json b/homeassistant/components/wled/translations/th.json index a9107341e37..77eafbdb9bd 100644 --- a/homeassistant/components/wled/translations/th.json +++ b/homeassistant/components/wled/translations/th.json @@ -10,8 +10,7 @@ "flow_title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", "step": { "user": { - "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" + "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" }, "zeroconf_confirm": { "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", diff --git a/homeassistant/components/wled/translations/tr.json b/homeassistant/components/wled/translations/tr.json index a9107341e37..77eafbdb9bd 100644 --- a/homeassistant/components/wled/translations/tr.json +++ b/homeassistant/components/wled/translations/tr.json @@ -10,8 +10,7 @@ "flow_title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", "step": { "user": { - "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" + "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" }, "zeroconf_confirm": { "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", diff --git a/homeassistant/components/wled/translations/uk.json b/homeassistant/components/wled/translations/uk.json index a9107341e37..77eafbdb9bd 100644 --- a/homeassistant/components/wled/translations/uk.json +++ b/homeassistant/components/wled/translations/uk.json @@ -10,8 +10,7 @@ "flow_title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", "step": { "user": { - "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" + "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" }, "zeroconf_confirm": { "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", diff --git a/homeassistant/components/wled/translations/ur.json b/homeassistant/components/wled/translations/ur.json index a9107341e37..77eafbdb9bd 100644 --- a/homeassistant/components/wled/translations/ur.json +++ b/homeassistant/components/wled/translations/ur.json @@ -10,8 +10,7 @@ "flow_title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", "step": { "user": { - "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" + "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" }, "zeroconf_confirm": { "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", diff --git a/homeassistant/components/wled/translations/vi.json b/homeassistant/components/wled/translations/vi.json index a9107341e37..77eafbdb9bd 100644 --- a/homeassistant/components/wled/translations/vi.json +++ b/homeassistant/components/wled/translations/vi.json @@ -10,8 +10,7 @@ "flow_title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", "step": { "user": { - "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" + "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" }, "zeroconf_confirm": { "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", diff --git a/homeassistant/components/wled/translations/zh-Hans.json b/homeassistant/components/wled/translations/zh-Hans.json index a9107341e37..77eafbdb9bd 100644 --- a/homeassistant/components/wled/translations/zh-Hans.json +++ b/homeassistant/components/wled/translations/zh-Hans.json @@ -10,8 +10,7 @@ "flow_title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", "step": { "user": { - "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" + "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" }, "zeroconf_confirm": { "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", diff --git a/homeassistant/components/wled/translations/zh-Hant.json b/homeassistant/components/wled/translations/zh-Hant.json index c712a3682ad..87490dc4595 100644 --- a/homeassistant/components/wled/translations/zh-Hant.json +++ b/homeassistant/components/wled/translations/zh-Hant.json @@ -13,8 +13,7 @@ "data": { "host": "\u4e3b\u6a5f\u7aef" }, - "description": "\u8a2d\u5b9a WLED \u4ee5\u6574\u5408\u81f3 Home Assistant\u3002", - "title": "\u9023\u7d50 WLED" + "description": "\u8a2d\u5b9a WLED \u4ee5\u6574\u5408\u81f3 Home Assistant\u3002" }, "zeroconf_confirm": { "description": "\u662f\u5426\u8981\u65b0\u589e WLED \u540d\u7a31\u300c{name}\u300d\u8a2d\u5099\u81f3 Home Assistant\uff1f", diff --git a/homeassistant/components/zerproc/translations/lb.json b/homeassistant/components/zerproc/translations/lb.json index cdfd3890fb8..e1d2e73d527 100644 --- a/homeassistant/components/zerproc/translations/lb.json +++ b/homeassistant/components/zerproc/translations/lb.json @@ -1,3 +1,8 @@ { + "config": { + "abort": { + "no_devices_found": "Keng Apparater am Netzwierk fonnt" + } + }, "title": "Zerproc" } \ No newline at end of file diff --git a/homeassistant/components/zha/translations/bg.json b/homeassistant/components/zha/translations/bg.json index a84ef9b4abd..c0bd15fd462 100644 --- a/homeassistant/components/zha/translations/bg.json +++ b/homeassistant/components/zha/translations/bg.json @@ -8,9 +8,6 @@ }, "step": { "user": { - "data": { - "radio_type": "\u0422\u0438\u043f \u0440\u0430\u0434\u0438\u043e" - }, "title": "ZHA" } } diff --git a/homeassistant/components/zha/translations/ca.json b/homeassistant/components/zha/translations/ca.json index d4e8b7997b0..8b2e9a9aed0 100644 --- a/homeassistant/components/zha/translations/ca.json +++ b/homeassistant/components/zha/translations/ca.json @@ -25,9 +25,7 @@ }, "user": { "data": { - "path": "Ruta del port s\u00e8rie al dispositiu", - "radio_type": "Tipus de r\u00e0dio", - "usb_path": "[%key::common::config_flow::data::usb_path%]" + "path": "Ruta del port s\u00e8rie al dispositiu" }, "description": "Selecciona el port s\u00e8rie per a la r\u00e0dio Zigbee", "title": "ZHA" diff --git a/homeassistant/components/zha/translations/da.json b/homeassistant/components/zha/translations/da.json index 2ebf02c5455..6a6e34c2581 100644 --- a/homeassistant/components/zha/translations/da.json +++ b/homeassistant/components/zha/translations/da.json @@ -25,9 +25,7 @@ }, "user": { "data": { - "path": "Stien til seriel enhed", - "radio_type": "Radio-type", - "usb_path": "USB-enheds sti" + "path": "Stien til seriel enhed" }, "description": "V\u00e6lg seriel port til Zigbee-radio", "title": "ZHA" diff --git a/homeassistant/components/zha/translations/de.json b/homeassistant/components/zha/translations/de.json index d25894e338d..592450fcfbc 100644 --- a/homeassistant/components/zha/translations/de.json +++ b/homeassistant/components/zha/translations/de.json @@ -25,9 +25,7 @@ }, "user": { "data": { - "path": "Serieller Ger\u00e4tepfad", - "radio_type": "Radio-Type", - "usb_path": "USB-Ger\u00e4te-Pfad" + "path": "Serieller Ger\u00e4tepfad" }, "description": "W\u00e4hlen Sie die serielle Schnittstelle f\u00fcr den ZigBee-Funk", "title": "ZHA" diff --git a/homeassistant/components/zha/translations/en.json b/homeassistant/components/zha/translations/en.json index 760492552a7..6a1eb4bac8e 100644 --- a/homeassistant/components/zha/translations/en.json +++ b/homeassistant/components/zha/translations/en.json @@ -25,9 +25,7 @@ }, "user": { "data": { - "path": "Serial Device Path", - "radio_type": "Radio Type", - "usb_path": "USB Device Path" + "path": "Serial Device Path" }, "description": "Select serial port for Zigbee radio", "title": "ZHA" diff --git a/homeassistant/components/zha/translations/es-419.json b/homeassistant/components/zha/translations/es-419.json index 36dadce7087..f2aaf80c78a 100644 --- a/homeassistant/components/zha/translations/es-419.json +++ b/homeassistant/components/zha/translations/es-419.json @@ -8,9 +8,6 @@ }, "step": { "user": { - "data": { - "radio_type": "Tipo de radio" - }, "title": "ZHA" } } diff --git a/homeassistant/components/zha/translations/es.json b/homeassistant/components/zha/translations/es.json index 97bbc5ce033..fd06722a445 100644 --- a/homeassistant/components/zha/translations/es.json +++ b/homeassistant/components/zha/translations/es.json @@ -25,9 +25,7 @@ }, "user": { "data": { - "path": "Ruta del Dispositivo Serie", - "radio_type": "Tipo de radio", - "usb_path": "Ruta del Dispositivo USB" + "path": "Ruta del Dispositivo Serie" }, "description": "Selecciona puerto serie para radio Zigbee", "title": "ZHA" diff --git a/homeassistant/components/zha/translations/fi.json b/homeassistant/components/zha/translations/fi.json index 5e008b35ddf..e22705142d4 100644 --- a/homeassistant/components/zha/translations/fi.json +++ b/homeassistant/components/zha/translations/fi.json @@ -21,9 +21,6 @@ "title": "Asetukset" }, "user": { - "data": { - "radio_type": "Radiotyyppi" - }, "title": "ZHA" } } diff --git a/homeassistant/components/zha/translations/fr.json b/homeassistant/components/zha/translations/fr.json index 41cff30b9e5..11e58f7be31 100644 --- a/homeassistant/components/zha/translations/fr.json +++ b/homeassistant/components/zha/translations/fr.json @@ -25,8 +25,7 @@ }, "user": { "data": { - "path": "Chemin du p\u00e9riph\u00e9rique s\u00e9rie", - "radio_type": "Type de radio" + "path": "Chemin du p\u00e9riph\u00e9rique s\u00e9rie" }, "description": "S\u00e9lectionnez le port s\u00e9rie de la radio Zigbee", "title": "ZHA" diff --git a/homeassistant/components/zha/translations/hu.json b/homeassistant/components/zha/translations/hu.json index 36f1cf0ceed..935663ed9e4 100644 --- a/homeassistant/components/zha/translations/hu.json +++ b/homeassistant/components/zha/translations/hu.json @@ -14,10 +14,6 @@ "title": "Be\u00e1ll\u00edt\u00e1sok" }, "user": { - "data": { - "radio_type": "R\u00e1di\u00f3 t\u00edpusa", - "usb_path": "USB eszk\u00f6z el\u00e9r\u00e9si \u00fat" - }, "title": "ZHA" } } diff --git a/homeassistant/components/zha/translations/it.json b/homeassistant/components/zha/translations/it.json index be5da92433c..7b2531c1290 100644 --- a/homeassistant/components/zha/translations/it.json +++ b/homeassistant/components/zha/translations/it.json @@ -25,9 +25,7 @@ }, "user": { "data": { - "path": "Percorso del dispositivo seriale", - "radio_type": "Tipo di Radio", - "usb_path": "Percorso del dispositivo USB" + "path": "Percorso del dispositivo seriale" }, "description": "Selezionare la porta seriale per la radio Zigbee", "title": "ZHA" diff --git a/homeassistant/components/zha/translations/ko.json b/homeassistant/components/zha/translations/ko.json index f1190f590d4..93582cc9202 100644 --- a/homeassistant/components/zha/translations/ko.json +++ b/homeassistant/components/zha/translations/ko.json @@ -25,9 +25,7 @@ }, "user": { "data": { - "path": "\uc2dc\ub9ac\uc5bc \uc7a5\uce58 \uacbd\ub85c", - "radio_type": "\ubb34\uc120 \uc720\ud615", - "usb_path": "USB \uc7a5\uce58 \uacbd\ub85c" + "path": "\uc2dc\ub9ac\uc5bc \uc7a5\uce58 \uacbd\ub85c" }, "description": "Zigbee \ubb34\uc120 \uc7a5\uce58\uc758 \uc2dc\ub9ac\uc5bc \ud3ec\ud2b8\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694", "title": "ZHA" diff --git a/homeassistant/components/zha/translations/lb.json b/homeassistant/components/zha/translations/lb.json index 5f72f64b099..62498a7dfe2 100644 --- a/homeassistant/components/zha/translations/lb.json +++ b/homeassistant/components/zha/translations/lb.json @@ -25,9 +25,7 @@ }, "user": { "data": { - "path": "Pad zum seriellen Apparat", - "radio_type": "Typ vun Radio", - "usb_path": "Pad zum USB Apparat" + "path": "Pad zum seriellen Apparat" }, "description": "Serielle Port fir Zigbee Radio auswielen", "title": "ZHA" diff --git a/homeassistant/components/zha/translations/nl.json b/homeassistant/components/zha/translations/nl.json index d72d560aafc..ca8d18dae12 100644 --- a/homeassistant/components/zha/translations/nl.json +++ b/homeassistant/components/zha/translations/nl.json @@ -24,8 +24,7 @@ }, "user": { "data": { - "path": "Serieel apparaatpad", - "radio_type": "Radio Type" + "path": "Serieel apparaatpad" }, "description": "Selecteer seri\u00eble poort voor Zigbee-radio", "title": "ZHA" diff --git a/homeassistant/components/zha/translations/no.json b/homeassistant/components/zha/translations/no.json index fe5714d5359..9699a7219ad 100644 --- a/homeassistant/components/zha/translations/no.json +++ b/homeassistant/components/zha/translations/no.json @@ -25,9 +25,7 @@ }, "user": { "data": { - "path": "Seriell enhetsbane", - "radio_type": "Radio type", - "usb_path": "USB enhetsbane" + "path": "Seriell enhetsbane" }, "description": "Velg seriell port for Zigbee radio", "title": "" diff --git a/homeassistant/components/zha/translations/pl.json b/homeassistant/components/zha/translations/pl.json index 69ea79c71a2..b9780d8427f 100644 --- a/homeassistant/components/zha/translations/pl.json +++ b/homeassistant/components/zha/translations/pl.json @@ -25,9 +25,7 @@ }, "user": { "data": { - "path": "\u015acie\u017cka urz\u0105dzenia szeregowego", - "radio_type": "Typ radia", - "usb_path": "\u015acie\u017cka urz\u0105dzenia USB" + "path": "\u015acie\u017cka urz\u0105dzenia szeregowego" }, "description": "Wyb\u00f3r portu szeregowego dla radia Zigbee", "title": "ZHA" diff --git a/homeassistant/components/zha/translations/pt-BR.json b/homeassistant/components/zha/translations/pt-BR.json index 3f5c67ae038..4b943032d28 100644 --- a/homeassistant/components/zha/translations/pt-BR.json +++ b/homeassistant/components/zha/translations/pt-BR.json @@ -21,9 +21,6 @@ "title": "Configura\u00e7\u00f5es" }, "user": { - "data": { - "radio_type": "Tipo de r\u00e1dio" - }, "title": "ZHA" } } diff --git a/homeassistant/components/zha/translations/pt.json b/homeassistant/components/zha/translations/pt.json index 1b9073b21f0..233791e63f1 100644 --- a/homeassistant/components/zha/translations/pt.json +++ b/homeassistant/components/zha/translations/pt.json @@ -8,9 +8,6 @@ }, "step": { "user": { - "data": { - "radio_type": "Tipo de r\u00e1dio" - }, "title": "ZHA" } } diff --git a/homeassistant/components/zha/translations/ru.json b/homeassistant/components/zha/translations/ru.json index 557b17ebe92..d535e9dd588 100644 --- a/homeassistant/components/zha/translations/ru.json +++ b/homeassistant/components/zha/translations/ru.json @@ -25,9 +25,7 @@ }, "user": { "data": { - "path": "\u041f\u0443\u0442\u044c \u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443", - "radio_type": "\u0422\u0438\u043f \u0420\u0430\u0434\u0438\u043e", - "usb_path": "\u041f\u0443\u0442\u044c \u043a USB-\u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443" + "path": "\u041f\u0443\u0442\u044c \u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443" }, "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u043f\u043e\u0440\u0442 \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u043e\u0440\u0430 \u0441\u0435\u0442\u0438 Zigbee", "title": "Zigbee Home Automation" diff --git a/homeassistant/components/zha/translations/sl.json b/homeassistant/components/zha/translations/sl.json index bfaf0757ea4..54d87c087bb 100644 --- a/homeassistant/components/zha/translations/sl.json +++ b/homeassistant/components/zha/translations/sl.json @@ -8,10 +8,6 @@ }, "step": { "user": { - "data": { - "radio_type": "Vrsta radia", - "usb_path": "USB Pot" - }, "title": "ZHA" } } diff --git a/homeassistant/components/zha/translations/sv.json b/homeassistant/components/zha/translations/sv.json index 00358fb1e23..7bbfe8dcfb1 100644 --- a/homeassistant/components/zha/translations/sv.json +++ b/homeassistant/components/zha/translations/sv.json @@ -25,8 +25,7 @@ }, "user": { "data": { - "path": "Seriell enhetsv\u00e4g", - "radio_type": "Typ av radio" + "path": "Seriell enhetsv\u00e4g" }, "title": "ZHA" } diff --git a/homeassistant/components/zha/translations/zh-Hans.json b/homeassistant/components/zha/translations/zh-Hans.json index d5c25573b7b..7a841edbb3b 100644 --- a/homeassistant/components/zha/translations/zh-Hans.json +++ b/homeassistant/components/zha/translations/zh-Hans.json @@ -8,9 +8,6 @@ }, "step": { "user": { - "data": { - "radio_type": "\u65e0\u7ebf\u7535\u7c7b\u578b" - }, "title": "ZHA" } } diff --git a/homeassistant/components/zha/translations/zh-Hant.json b/homeassistant/components/zha/translations/zh-Hant.json index ecfd8ff9e51..cbe4d8fbedd 100644 --- a/homeassistant/components/zha/translations/zh-Hant.json +++ b/homeassistant/components/zha/translations/zh-Hant.json @@ -25,9 +25,7 @@ }, "user": { "data": { - "path": "\u5e8f\u5217\u8a2d\u5099\u8def\u5f91", - "radio_type": "\u7121\u7dda\u96fb\u985e\u578b", - "usb_path": "USB \u8a2d\u5099\u8def\u5f91" + "path": "\u5e8f\u5217\u8a2d\u5099\u8def\u5f91" }, "description": "\u9078\u64c7 Zigbee \u7121\u7dda\u96fb\u5e8f\u5217\u57e0", "title": "ZHA" From 9c45115468a99a2ecee1d84b1eb73e78a53ffae8 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 28 May 2020 21:06:09 -0700 Subject: [PATCH 243/406] Upgrade translations download to use Lokalise CLI v2 (#36240) --- script/translations/const.py | 3 ++- script/translations/download.py | 19 ++++++++++++------- script/translations/upload.py | 4 ++-- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/script/translations/const.py b/script/translations/const.py index ddb753ef1ca..ba4d7b39177 100644 --- a/script/translations/const.py +++ b/script/translations/const.py @@ -3,6 +3,7 @@ import pathlib CORE_PROJECT_ID = "130246255a974bd3b5e8a1.51616605" FRONTEND_PROJECT_ID = "3420425759f6d6d241f598.13594006" -DOCKER_IMAGE = "b8329d20280263cad04f65b843e54b9e8e6909a348a678eac959550b5ef5c75f" +CLI_1_DOCKER_IMAGE = "b8329d20280263cad04f65b843e54b9e8e6909a348a678eac959550b5ef5c75f" +CLI_2_DOCKER_IMAGE = "v2.3.0" INTEGRATIONS_DIR = pathlib.Path("homeassistant/components") FRONTEND_DIR = pathlib.Path("../frontend") diff --git a/script/translations/download.py b/script/translations/download.py index 364f309b644..8f17e057080 100755 --- a/script/translations/download.py +++ b/script/translations/download.py @@ -7,7 +7,7 @@ import re import subprocess from typing import Dict, List, Union -from .const import CORE_PROJECT_ID, DOCKER_IMAGE +from .const import CLI_2_DOCKER_IMAGE, CORE_PROJECT_ID from .error import ExitApp from .util import get_lokalise_token @@ -25,18 +25,23 @@ def run_download_docker(): "-v", f"{DOWNLOAD_DIR}:/opt/dest/locale", "--rm", - f"lokalise/lokalise-cli@sha256:{DOCKER_IMAGE}", + f"lokalise/lokalise-cli-2:{CLI_2_DOCKER_IMAGE}", # Lokalise command - "lokalise", + "lokalise2", "--token", get_lokalise_token(), - "export", + "--project-id", CORE_PROJECT_ID, - "--export_empty", + "file", + "download", + CORE_PROJECT_ID, + "--original-filenames=false", + "--replace-breaks=false", + "--export-empty-as", "skip", - "--type", + "--format", "json", - "--unzip_to", + "--unzip-to", "/opt/dest", ] ) diff --git a/script/translations/upload.py b/script/translations/upload.py index 844c706d064..a9dfa38f6c3 100755 --- a/script/translations/upload.py +++ b/script/translations/upload.py @@ -6,7 +6,7 @@ import pathlib import re import subprocess -from .const import CORE_PROJECT_ID, DOCKER_IMAGE, INTEGRATIONS_DIR +from .const import CLI_1_DOCKER_IMAGE, CORE_PROJECT_ID, INTEGRATIONS_DIR from .error import ExitApp from .util import get_current_branch, get_lokalise_token @@ -26,7 +26,7 @@ def run_upload_docker(): "-v", f"{LOCAL_FILE}:{CONTAINER_FILE}", "--rm", - f"lokalise/lokalise-cli@sha256:{DOCKER_IMAGE}", + f"lokalise/lokalise-cli@sha256:{CLI_1_DOCKER_IMAGE}", # Lokalise command "lokalise", "--token", From fbe7b4ddfa813e300f99175d254ec509c8d65e33 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 28 May 2020 23:09:07 -0500 Subject: [PATCH 244/406] Ensure frontend is available if integrations fail to start - Part 1 of 2 (#36093) Co-authored-by: Paulus Schoutsen --- homeassistant/bootstrap.py | 21 ++++++- homeassistant/components/http/__init__.py | 49 +++++++++------ homeassistant/components/http/view.py | 6 +- .../components/websocket_api/decorators.py | 4 +- .../components/websocket_api/http.py | 4 +- homeassistant/core.py | 7 +++ tests/components/hassio/test_discovery.py | 5 +- tests/components/http/test_data_validator.py | 2 +- tests/components/http/test_view.py | 16 ++++- tests/components/logbook/test_init.py | 3 +- tests/components/panel_iframe/test_init.py | 63 ++++++++++--------- tests/test_bootstrap.py | 51 +++++++++++++-- tests/test_core.py | 5 +- 13 files changed, 173 insertions(+), 63 deletions(-) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 1a4a7de4c18..43a10b7315e 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -29,6 +29,9 @@ _LOGGER = logging.getLogger(__name__) ERROR_LOG_FILENAME = "home-assistant.log" +# How long to wait until things that run on bootstrap have to finish. +TIMEOUT_EVENT_BOOTSTRAP = 15 + # hass.data key for logging information. DATA_LOGGING = "logging" @@ -44,6 +47,13 @@ STAGE_1_INTEGRATIONS = { "mqtt_eventstream", # To provide account link implementations "cloud", + # Ensure supervisor is available + "hassio", + # Get the frontend up and running as soon + # as possible so problem integrations can + # be removed + "frontend", + "config", } @@ -399,6 +409,8 @@ async def _async_set_up_integrations( ) if stage_1_domains: + _LOGGER.info("Setting up %s", stage_1_domains) + await async_setup_multi_components(stage_1_domains) # Load all integrations @@ -442,4 +454,11 @@ async def _async_set_up_integrations( # Wrap up startup _LOGGER.debug("Waiting for startup to wrap up") - await hass.async_block_till_done() + try: + async with timeout(TIMEOUT_EVENT_BOOTSTRAP): + await hass.async_block_till_done() + except asyncio.TimeoutError: + _LOGGER.warning( + "Something is blocking Home Assistant from wrapping up the " + "bootstrap phase. We're going to continue anyway." + ) diff --git a/homeassistant/components/http/__init__.py b/homeassistant/components/http/__init__.py index 069fc42c884..7f14e8703d3 100644 --- a/homeassistant/components/http/__init__.py +++ b/homeassistant/components/http/__init__.py @@ -4,7 +4,7 @@ import logging import os import ssl from traceback import extract_stack -from typing import Optional, cast +from typing import Dict, Optional, cast from aiohttp import web from aiohttp.web_exceptions import HTTPMovedPermanently @@ -15,7 +15,7 @@ from homeassistant.const import ( EVENT_HOMEASSISTANT_STOP, SERVER_PORT, ) -from homeassistant.core import HomeAssistant +from homeassistant.core import Event, HomeAssistant from homeassistant.helpers import storage import homeassistant.helpers.config_validation as cv from homeassistant.loader import bind_hass @@ -216,29 +216,25 @@ async def async_setup(hass, config): ssl_profile=ssl_profile, ) - async def stop_server(event): + startup_listeners = [] + + async def stop_server(event: Event) -> None: """Stop the server.""" await server.stop() - async def start_server(event): + async def start_server(event: Event) -> None: """Start the server.""" + + for listener in startup_listeners: + listener() + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_server) - await server.start() - # If we are set up successful, we store the HTTP settings for safe mode. - store = storage.Store(hass, STORAGE_VERSION, STORAGE_KEY) + await start_http_server_and_save_config(hass, dict(conf), server) - if CONF_TRUSTED_PROXIES in conf: - conf_to_save = dict(conf) - conf_to_save[CONF_TRUSTED_PROXIES] = [ - str(ip.network_address) for ip in conf_to_save[CONF_TRUSTED_PROXIES] - ] - else: - conf_to_save = conf - - await store.async_save(conf_to_save) - - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, start_server) + startup_listeners.append( + hass.bus.async_listen(EVENT_HOMEASSISTANT_START, start_server) + ) hass.http = server @@ -418,3 +414,20 @@ class HomeAssistantHTTP: """Stop the aiohttp server.""" await self.site.stop() await self.runner.cleanup() + + +async def start_http_server_and_save_config( + hass: HomeAssistant, conf: Dict, server: HomeAssistantHTTP +) -> None: + """Startup the http server and save the config.""" + await server.start() # type: ignore + + # If we are set up successful, we store the HTTP settings for safe mode. + store = storage.Store(hass, STORAGE_VERSION, STORAGE_KEY) + + if CONF_TRUSTED_PROXIES in conf: + conf[CONF_TRUSTED_PROXIES] = [ + str(ip.network_address) for ip in conf[CONF_TRUSTED_PROXIES] + ] + + await store.async_save(conf) diff --git a/homeassistant/components/http/view.py b/homeassistant/components/http/view.py index 701c497d88c..eb6c757384e 100644 --- a/homeassistant/components/http/view.py +++ b/homeassistant/components/http/view.py @@ -14,7 +14,7 @@ from aiohttp.web_exceptions import ( import voluptuous as vol from homeassistant import exceptions -from homeassistant.const import CONTENT_TYPE_JSON, HTTP_OK +from homeassistant.const import CONTENT_TYPE_JSON, HTTP_OK, HTTP_SERVICE_UNAVAILABLE from homeassistant.core import Context, is_callback from homeassistant.helpers.json import JSONEncoder @@ -107,8 +107,8 @@ def request_handler_factory(view: HomeAssistantView, handler: Callable) -> Calla async def handle(request: web.Request) -> web.StreamResponse: """Handle incoming request.""" - if not request.app[KEY_HASS].is_running: - return web.Response(status=503) + if request.app[KEY_HASS].is_stopping: + return web.Response(status=HTTP_SERVICE_UNAVAILABLE) authenticated = request.get(KEY_AUTHENTICATED, False) diff --git a/homeassistant/components/websocket_api/decorators.py b/homeassistant/components/websocket_api/decorators.py index 87b5d5baf92..e042fb9d197 100644 --- a/homeassistant/components/websocket_api/decorators.py +++ b/homeassistant/components/websocket_api/decorators.py @@ -31,7 +31,9 @@ def async_response( @wraps(func) def schedule_handler(hass, connection, msg): """Schedule the handler.""" - hass.async_create_task(_handle_async_response(func, hass, connection, msg)) + # As the webserver is now started before the start + # event we do not want to block for websocket responders + hass.loop.create_task(_handle_async_response(func, hass, connection, msg)) return schedule_handler diff --git a/homeassistant/components/websocket_api/http.py b/homeassistant/components/websocket_api/http.py index e20e53d139a..248ea3597ad 100644 --- a/homeassistant/components/websocket_api/http.py +++ b/homeassistant/components/websocket_api/http.py @@ -165,7 +165,9 @@ class WebSocketHandler: EVENT_HOMEASSISTANT_STOP, handle_hass_stop ) - self._writer_task = self.hass.async_create_task(self._writer()) + # As the webserver is now started before the start + # event we do not want to block for websocket responses + self._writer_task = self.hass.loop.create_task(self._writer()) auth = AuthPhase(self._logger, self.hass, self._send_message, request) connection = None diff --git a/homeassistant/core.py b/homeassistant/core.py index 34df648a4df..eb7457daecb 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -209,6 +209,11 @@ class HomeAssistant: """Return if Home Assistant is running.""" return self.state in (CoreState.starting, CoreState.running) + @property + def is_stopping(self) -> bool: + """Return if Home Assistant is stopping.""" + return self.state in (CoreState.stopping, CoreState.final_write) + def start(self) -> int: """Start Home Assistant. @@ -260,6 +265,7 @@ class HomeAssistant: setattr(self.loop, "_thread_ident", threading.get_ident()) self.bus.async_fire(EVENT_HOMEASSISTANT_START) + self.bus.async_fire(EVENT_CORE_CONFIG_UPDATE) try: # Only block for EVENT_HOMEASSISTANT_START listener @@ -1391,6 +1397,7 @@ class Config: "version": __version__, "config_source": self.config_source, "safe_mode": self.safe_mode, + "state": self.hass.state.value, "external_url": self.external_url, "internal_url": self.internal_url, } diff --git a/tests/components/hassio/test_discovery.py b/tests/components/hassio/test_discovery.py index 845c60c2f85..9d148745f18 100644 --- a/tests/components/hassio/test_discovery.py +++ b/tests/components/hassio/test_discovery.py @@ -60,6 +60,9 @@ async def test_hassio_discovery_startup(hass, aioclient_mock, hassio_client): async def test_hassio_discovery_startup_done(hass, aioclient_mock, hassio_client): """Test startup and discovery with hass discovery.""" + aioclient_mock.post( + "http://127.0.0.1/supervisor/options", json={"result": "ok", "data": {}}, + ) aioclient_mock.get( "http://127.0.0.1/discovery", json={ @@ -101,7 +104,7 @@ async def test_hassio_discovery_startup_done(hass, aioclient_mock, hassio_client await async_setup_component(hass, "hassio", {}) await hass.async_block_till_done() - assert aioclient_mock.call_count == 2 + assert aioclient_mock.call_count == 3 assert mock_mqtt.called mock_mqtt.assert_called_with( { diff --git a/tests/components/http/test_data_validator.py b/tests/components/http/test_data_validator.py index b0a14a31bc5..c7b5ed42ccd 100644 --- a/tests/components/http/test_data_validator.py +++ b/tests/components/http/test_data_validator.py @@ -11,7 +11,7 @@ from tests.async_mock import Mock async def get_client(aiohttp_client, validator): """Generate a client that hits a view decorated with validator.""" app = web.Application() - app["hass"] = Mock(is_running=True) + app["hass"] = Mock(is_stopping=False) class TestView(HomeAssistantView): url = "/" diff --git a/tests/components/http/test_view.py b/tests/components/http/test_view.py index a6e4bdc12c8..045f0837983 100644 --- a/tests/components/http/test_view.py +++ b/tests/components/http/test_view.py @@ -19,7 +19,13 @@ from tests.async_mock import AsyncMock, Mock @pytest.fixture def mock_request(): """Mock a request.""" - return Mock(app={"hass": Mock(is_running=True)}, match_info={}) + return Mock(app={"hass": Mock(is_stopping=False)}, match_info={}) + + +@pytest.fixture +def mock_request_with_stopping(): + """Mock a request.""" + return Mock(app={"hass": Mock(is_stopping=True)}, match_info={}) async def test_invalid_json(caplog): @@ -55,3 +61,11 @@ async def test_handling_service_not_found(mock_request): Mock(requires_auth=False), AsyncMock(side_effect=ServiceNotFound("test", "test")), )(mock_request) + + +async def test_not_running(mock_request_with_stopping): + """Test we get a 503 when not running.""" + response = await request_handler_factory( + Mock(requires_auth=False), AsyncMock(side_effect=Unauthorized) + )(mock_request_with_stopping) + assert response.status == 503 diff --git a/tests/components/logbook/test_init.py b/tests/components/logbook/test_init.py index abe6f6ec515..d86800b143b 100644 --- a/tests/components/logbook/test_init.py +++ b/tests/components/logbook/test_init.py @@ -43,7 +43,8 @@ class TestComponentLogbook(unittest.TestCase): """Set up things to be run when tests are started.""" self.hass = get_test_home_assistant() init_recorder_component(self.hass) # Force an in memory DB - assert setup_component(self.hass, logbook.DOMAIN, self.EMPTY_CONFIG) + with patch("homeassistant.components.http.start_http_server_and_save_config"): + assert setup_component(self.hass, logbook.DOMAIN, self.EMPTY_CONFIG) def tearDown(self): """Stop everything that was started.""" diff --git a/tests/components/panel_iframe/test_init.py b/tests/components/panel_iframe/test_init.py index d586c4c199e..b38f3c4b1fa 100644 --- a/tests/components/panel_iframe/test_init.py +++ b/tests/components/panel_iframe/test_init.py @@ -4,6 +4,7 @@ import unittest from homeassistant import setup from homeassistant.components import frontend +from tests.async_mock import patch from tests.common import get_test_home_assistant @@ -26,38 +27,42 @@ class TestPanelIframe(unittest.TestCase): ] for conf in to_try: - assert not setup.setup_component( - self.hass, "panel_iframe", {"panel_iframe": conf} - ) + with patch( + "homeassistant.components.http.start_http_server_and_save_config" + ): + assert not setup.setup_component( + self.hass, "panel_iframe", {"panel_iframe": conf} + ) def test_correct_config(self): """Test correct config.""" - assert setup.setup_component( - self.hass, - "panel_iframe", - { - "panel_iframe": { - "router": { - "icon": "mdi:network-wireless", - "title": "Router", - "url": "http://192.168.1.1", - "require_admin": True, - }, - "weather": { - "icon": "mdi:weather", - "title": "Weather", - "url": "https://www.wunderground.com/us/ca/san-diego", - "require_admin": True, - }, - "api": {"icon": "mdi:weather", "title": "Api", "url": "/api"}, - "ftp": { - "icon": "mdi:weather", - "title": "FTP", - "url": "ftp://some/ftp", - }, - } - }, - ) + with patch("homeassistant.components.http.start_http_server_and_save_config"): + assert setup.setup_component( + self.hass, + "panel_iframe", + { + "panel_iframe": { + "router": { + "icon": "mdi:network-wireless", + "title": "Router", + "url": "http://192.168.1.1", + "require_admin": True, + }, + "weather": { + "icon": "mdi:weather", + "title": "Weather", + "url": "https://www.wunderground.com/us/ca/san-diego", + "require_admin": True, + }, + "api": {"icon": "mdi:weather", "title": "Api", "url": "/api"}, + "ftp": { + "icon": "mdi:weather", + "title": "FTP", + "url": "ftp://some/ftp", + }, + } + }, + ) panels = self.hass.data[frontend.DATA_PANELS] diff --git a/tests/test_bootstrap.py b/tests/test_bootstrap.py index 9597dfa60b8..2be29e55814 100644 --- a/tests/test_bootstrap.py +++ b/tests/test_bootstrap.py @@ -201,6 +201,43 @@ async def test_setup_after_deps_not_present(hass, caplog): assert order == ["root", "second_dep"] +async def test_setup_continues_if_blocked(hass, caplog): + """Test we continue after timeout if blocked.""" + caplog.set_level(logging.DEBUG) + order = [] + + def gen_domain_setup(domain): + async def async_setup(hass, config): + order.append(domain) + return True + + return async_setup + + mock_integration( + hass, MockModule(domain="root", async_setup=gen_domain_setup("root")) + ) + mock_integration( + hass, + MockModule( + domain="second_dep", + async_setup=gen_domain_setup("second_dep"), + partial_manifest={"after_dependencies": ["first_dep"]}, + ), + ) + + with patch.object(bootstrap, "TIMEOUT_EVENT_BOOTSTRAP", 0): + hass.async_create_task(asyncio.sleep(2)) + await bootstrap._async_set_up_integrations( + hass, {"root": {}, "first_dep": {}, "second_dep": {}} + ) + + assert "root" in hass.config.components + assert "first_dep" not in hass.config.components + assert "second_dep" in hass.config.components + assert order == ["root", "second_dep"] + assert "blocking Home Assistant from wrapping up" in caplog.text + + @pytest.fixture def mock_is_virtual_env(): """Mock enable logging.""" @@ -261,7 +298,9 @@ async def test_setup_hass( with patch( "homeassistant.config.async_hass_config_yaml", return_value={"browser": {}, "frontend": {}}, - ), patch.object(bootstrap, "LOG_SLOW_STARTUP_INTERVAL", 5000): + ), patch.object(bootstrap, "LOG_SLOW_STARTUP_INTERVAL", 5000), patch( + "homeassistant.components.http.start_http_server_and_save_config" + ): hass = await bootstrap.async_setup_hass( config_dir=get_test_config_dir(), verbose=verbose, @@ -338,7 +377,7 @@ async def test_setup_hass_invalid_yaml( """Test it works.""" with patch( "homeassistant.config.async_hass_config_yaml", side_effect=HomeAssistantError - ): + ), patch("homeassistant.components.http.start_http_server_and_save_config"): hass = await bootstrap.async_setup_hass( config_dir=get_test_config_dir(), verbose=False, @@ -391,7 +430,9 @@ async def test_setup_hass_safe_mode( hass.config_entries._async_schedule_save() await flush_store(hass.config_entries._store) - with patch("homeassistant.components.browser.setup") as browser_setup: + with patch("homeassistant.components.browser.setup") as browser_setup, patch( + "homeassistant.components.http.start_http_server_and_save_config" + ): hass = await bootstrap.async_setup_hass( config_dir=get_test_config_dir(), verbose=False, @@ -421,7 +462,7 @@ async def test_setup_hass_invalid_core_config( with patch( "homeassistant.config.async_hass_config_yaml", return_value={"homeassistant": {"non-existing": 1}}, - ): + ), patch("homeassistant.components.http.start_http_server_and_save_config"): hass = await bootstrap.async_setup_hass( config_dir=get_test_config_dir(), verbose=False, @@ -451,7 +492,7 @@ async def test_setup_safe_mode_if_no_frontend( with patch( "homeassistant.config.async_hass_config_yaml", return_value={"map": {}, "person": {"invalid": True}}, - ): + ), patch("homeassistant.components.http.start_http_server_and_save_config"): hass = await bootstrap.async_setup_hass( config_dir=get_test_config_dir(), verbose=verbose, diff --git a/tests/test_core.py b/tests/test_core.py index 3bc001b78b6..9fc257eaf2d 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -35,7 +35,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, Mock, patch +from tests.async_mock import MagicMock, Mock, PropertyMock, patch from tests.common import async_mock_service, get_test_home_assistant PST = pytz.timezone("America/Los_Angeles") @@ -901,6 +901,8 @@ class TestConfig(unittest.TestCase): def test_as_dict(self): """Test as dict.""" self.config.config_dir = "/test/ha-config" + self.config.hass = MagicMock() + type(self.config.hass.state).value = PropertyMock(return_value="RUNNING") expected = { "latitude": 0, "longitude": 0, @@ -914,6 +916,7 @@ class TestConfig(unittest.TestCase): "version": __version__, "config_source": "default", "safe_mode": False, + "state": "RUNNING", "external_url": None, "internal_url": None, } From dcea238661c1345e7004f6c60b9c9c02a68753dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20H=C3=B8yer=20Iversen?= Date: Fri, 29 May 2020 08:00:20 +0200 Subject: [PATCH 245/406] Fix some mistakes in documentation (#36246) --- homeassistant/components/hitron_coda/device_tracker.py | 2 +- homeassistant/components/keenetic_ndms2/device_tracker.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/hitron_coda/device_tracker.py b/homeassistant/components/hitron_coda/device_tracker.py index a49e3cc6d21..ace6540fe71 100644 --- a/homeassistant/components/hitron_coda/device_tracker.py +++ b/homeassistant/components/hitron_coda/device_tracker.py @@ -34,7 +34,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( def get_scanner(_hass, config): - """Validate the configuration and return a Nmap scanner.""" + """Validate the configuration and return a Hitron CODA-4582U scanner.""" scanner = HitronCODADeviceScanner(config[DOMAIN]) return scanner if scanner.success_init else None diff --git a/homeassistant/components/keenetic_ndms2/device_tracker.py b/homeassistant/components/keenetic_ndms2/device_tracker.py index 598e29cf583..d98806dfc05 100644 --- a/homeassistant/components/keenetic_ndms2/device_tracker.py +++ b/homeassistant/components/keenetic_ndms2/device_tracker.py @@ -35,7 +35,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( def get_scanner(_hass, config): - """Validate the configuration and return a Nmap scanner.""" + """Validate the configuration and return a Keenetic NDMS2 scanner.""" scanner = KeeneticNDMS2DeviceScanner(config[DOMAIN]) return scanner if scanner.success_init else None From 6f9770c0670452cf57871f4a7698a6c08e532a7d Mon Sep 17 00:00:00 2001 From: Josef Schlehofer Date: Fri, 29 May 2020 09:16:35 +0200 Subject: [PATCH 246/406] Upgrade youtube_dl to version 2020.05.29 (#36239) --- 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 95cb1db65a8..b70729a7435 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.05.08"], + "requirements": ["youtube_dl==2020.05.29"], "dependencies": ["media_player"], "codeowners": [], "quality_scale": "internal" diff --git a/requirements_all.txt b/requirements_all.txt index 11f5d9a79e1..3f548646a8c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2236,7 +2236,7 @@ yeelight==0.5.2 yeelightsunflower==0.0.10 # homeassistant.components.media_extractor -youtube_dl==2020.05.08 +youtube_dl==2020.05.29 # homeassistant.components.zengge zengge==0.2 From 08f2714e57caa017451cb4d555bfc29b8bb088f7 Mon Sep 17 00:00:00 2001 From: Markus Bong Date: Fri, 29 May 2020 09:59:44 +0200 Subject: [PATCH 247/406] Change devolo HomeControl SwitchDevice to SwitchEntity (#36248) --- homeassistant/components/devolo_home_control/switch.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/devolo_home_control/switch.py b/homeassistant/components/devolo_home_control/switch.py index e5210262268..e70474a8d5d 100644 --- a/homeassistant/components/devolo_home_control/switch.py +++ b/homeassistant/components/devolo_home_control/switch.py @@ -1,7 +1,7 @@ -"""Platform for light integration.""" +"""Platform for switch integration.""" import logging -from homeassistant.components.switch import SwitchDevice +from homeassistant.components.switch import SwitchEntity from homeassistant.config_entries import ConfigEntry from homeassistant.helpers.typing import HomeAssistantType @@ -29,8 +29,8 @@ async def async_setup_entry( async_add_entities(entities) -class DevoloSwitch(SwitchDevice): - """Representation of an Awesome Light.""" +class DevoloSwitch(SwitchEntity): + """Representation of a switch.""" def __init__(self, homecontrol, device_instance, element_uid): """Initialize an devolo Switch.""" From ed014e3c96547451f8bd07033b3cb5d0fb7575e9 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Fri, 29 May 2020 10:18:39 +0200 Subject: [PATCH 248/406] Revert "Ensure frontend is available if integrations fail to start - Part 1 of 2 (#36093)" (#36251) This reverts commit fbe7b4ddfa813e300f99175d254ec509c8d65e33. --- homeassistant/bootstrap.py | 21 +------ homeassistant/components/http/__init__.py | 49 ++++++--------- homeassistant/components/http/view.py | 6 +- .../components/websocket_api/decorators.py | 4 +- .../components/websocket_api/http.py | 4 +- homeassistant/core.py | 7 --- tests/components/hassio/test_discovery.py | 5 +- tests/components/http/test_data_validator.py | 2 +- tests/components/http/test_view.py | 16 +---- tests/components/logbook/test_init.py | 3 +- tests/components/panel_iframe/test_init.py | 63 +++++++++---------- tests/test_bootstrap.py | 51 ++------------- tests/test_core.py | 5 +- 13 files changed, 63 insertions(+), 173 deletions(-) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 43a10b7315e..1a4a7de4c18 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -29,9 +29,6 @@ _LOGGER = logging.getLogger(__name__) ERROR_LOG_FILENAME = "home-assistant.log" -# How long to wait until things that run on bootstrap have to finish. -TIMEOUT_EVENT_BOOTSTRAP = 15 - # hass.data key for logging information. DATA_LOGGING = "logging" @@ -47,13 +44,6 @@ STAGE_1_INTEGRATIONS = { "mqtt_eventstream", # To provide account link implementations "cloud", - # Ensure supervisor is available - "hassio", - # Get the frontend up and running as soon - # as possible so problem integrations can - # be removed - "frontend", - "config", } @@ -409,8 +399,6 @@ async def _async_set_up_integrations( ) if stage_1_domains: - _LOGGER.info("Setting up %s", stage_1_domains) - await async_setup_multi_components(stage_1_domains) # Load all integrations @@ -454,11 +442,4 @@ async def _async_set_up_integrations( # Wrap up startup _LOGGER.debug("Waiting for startup to wrap up") - try: - async with timeout(TIMEOUT_EVENT_BOOTSTRAP): - await hass.async_block_till_done() - except asyncio.TimeoutError: - _LOGGER.warning( - "Something is blocking Home Assistant from wrapping up the " - "bootstrap phase. We're going to continue anyway." - ) + await hass.async_block_till_done() diff --git a/homeassistant/components/http/__init__.py b/homeassistant/components/http/__init__.py index 7f14e8703d3..069fc42c884 100644 --- a/homeassistant/components/http/__init__.py +++ b/homeassistant/components/http/__init__.py @@ -4,7 +4,7 @@ import logging import os import ssl from traceback import extract_stack -from typing import Dict, Optional, cast +from typing import Optional, cast from aiohttp import web from aiohttp.web_exceptions import HTTPMovedPermanently @@ -15,7 +15,7 @@ from homeassistant.const import ( EVENT_HOMEASSISTANT_STOP, SERVER_PORT, ) -from homeassistant.core import Event, HomeAssistant +from homeassistant.core import HomeAssistant from homeassistant.helpers import storage import homeassistant.helpers.config_validation as cv from homeassistant.loader import bind_hass @@ -216,25 +216,29 @@ async def async_setup(hass, config): ssl_profile=ssl_profile, ) - startup_listeners = [] - - async def stop_server(event: Event) -> None: + async def stop_server(event): """Stop the server.""" await server.stop() - async def start_server(event: Event) -> None: + async def start_server(event): """Start the server.""" - - for listener in startup_listeners: - listener() - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_server) + await server.start() - await start_http_server_and_save_config(hass, dict(conf), server) + # If we are set up successful, we store the HTTP settings for safe mode. + store = storage.Store(hass, STORAGE_VERSION, STORAGE_KEY) - startup_listeners.append( - hass.bus.async_listen(EVENT_HOMEASSISTANT_START, start_server) - ) + if CONF_TRUSTED_PROXIES in conf: + conf_to_save = dict(conf) + conf_to_save[CONF_TRUSTED_PROXIES] = [ + str(ip.network_address) for ip in conf_to_save[CONF_TRUSTED_PROXIES] + ] + else: + conf_to_save = conf + + await store.async_save(conf_to_save) + + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, start_server) hass.http = server @@ -414,20 +418,3 @@ class HomeAssistantHTTP: """Stop the aiohttp server.""" await self.site.stop() await self.runner.cleanup() - - -async def start_http_server_and_save_config( - hass: HomeAssistant, conf: Dict, server: HomeAssistantHTTP -) -> None: - """Startup the http server and save the config.""" - await server.start() # type: ignore - - # If we are set up successful, we store the HTTP settings for safe mode. - store = storage.Store(hass, STORAGE_VERSION, STORAGE_KEY) - - if CONF_TRUSTED_PROXIES in conf: - conf[CONF_TRUSTED_PROXIES] = [ - str(ip.network_address) for ip in conf[CONF_TRUSTED_PROXIES] - ] - - await store.async_save(conf) diff --git a/homeassistant/components/http/view.py b/homeassistant/components/http/view.py index eb6c757384e..701c497d88c 100644 --- a/homeassistant/components/http/view.py +++ b/homeassistant/components/http/view.py @@ -14,7 +14,7 @@ from aiohttp.web_exceptions import ( import voluptuous as vol from homeassistant import exceptions -from homeassistant.const import CONTENT_TYPE_JSON, HTTP_OK, HTTP_SERVICE_UNAVAILABLE +from homeassistant.const import CONTENT_TYPE_JSON, HTTP_OK from homeassistant.core import Context, is_callback from homeassistant.helpers.json import JSONEncoder @@ -107,8 +107,8 @@ def request_handler_factory(view: HomeAssistantView, handler: Callable) -> Calla async def handle(request: web.Request) -> web.StreamResponse: """Handle incoming request.""" - if request.app[KEY_HASS].is_stopping: - return web.Response(status=HTTP_SERVICE_UNAVAILABLE) + if not request.app[KEY_HASS].is_running: + return web.Response(status=503) authenticated = request.get(KEY_AUTHENTICATED, False) diff --git a/homeassistant/components/websocket_api/decorators.py b/homeassistant/components/websocket_api/decorators.py index e042fb9d197..87b5d5baf92 100644 --- a/homeassistant/components/websocket_api/decorators.py +++ b/homeassistant/components/websocket_api/decorators.py @@ -31,9 +31,7 @@ def async_response( @wraps(func) def schedule_handler(hass, connection, msg): """Schedule the handler.""" - # As the webserver is now started before the start - # event we do not want to block for websocket responders - hass.loop.create_task(_handle_async_response(func, hass, connection, msg)) + hass.async_create_task(_handle_async_response(func, hass, connection, msg)) return schedule_handler diff --git a/homeassistant/components/websocket_api/http.py b/homeassistant/components/websocket_api/http.py index 248ea3597ad..e20e53d139a 100644 --- a/homeassistant/components/websocket_api/http.py +++ b/homeassistant/components/websocket_api/http.py @@ -165,9 +165,7 @@ class WebSocketHandler: EVENT_HOMEASSISTANT_STOP, handle_hass_stop ) - # As the webserver is now started before the start - # event we do not want to block for websocket responses - self._writer_task = self.hass.loop.create_task(self._writer()) + self._writer_task = self.hass.async_create_task(self._writer()) auth = AuthPhase(self._logger, self.hass, self._send_message, request) connection = None diff --git a/homeassistant/core.py b/homeassistant/core.py index eb7457daecb..34df648a4df 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -209,11 +209,6 @@ class HomeAssistant: """Return if Home Assistant is running.""" return self.state in (CoreState.starting, CoreState.running) - @property - def is_stopping(self) -> bool: - """Return if Home Assistant is stopping.""" - return self.state in (CoreState.stopping, CoreState.final_write) - def start(self) -> int: """Start Home Assistant. @@ -265,7 +260,6 @@ class HomeAssistant: setattr(self.loop, "_thread_ident", threading.get_ident()) self.bus.async_fire(EVENT_HOMEASSISTANT_START) - self.bus.async_fire(EVENT_CORE_CONFIG_UPDATE) try: # Only block for EVENT_HOMEASSISTANT_START listener @@ -1397,7 +1391,6 @@ class Config: "version": __version__, "config_source": self.config_source, "safe_mode": self.safe_mode, - "state": self.hass.state.value, "external_url": self.external_url, "internal_url": self.internal_url, } diff --git a/tests/components/hassio/test_discovery.py b/tests/components/hassio/test_discovery.py index 9d148745f18..845c60c2f85 100644 --- a/tests/components/hassio/test_discovery.py +++ b/tests/components/hassio/test_discovery.py @@ -60,9 +60,6 @@ async def test_hassio_discovery_startup(hass, aioclient_mock, hassio_client): async def test_hassio_discovery_startup_done(hass, aioclient_mock, hassio_client): """Test startup and discovery with hass discovery.""" - aioclient_mock.post( - "http://127.0.0.1/supervisor/options", json={"result": "ok", "data": {}}, - ) aioclient_mock.get( "http://127.0.0.1/discovery", json={ @@ -104,7 +101,7 @@ async def test_hassio_discovery_startup_done(hass, aioclient_mock, hassio_client await async_setup_component(hass, "hassio", {}) await hass.async_block_till_done() - assert aioclient_mock.call_count == 3 + assert aioclient_mock.call_count == 2 assert mock_mqtt.called mock_mqtt.assert_called_with( { diff --git a/tests/components/http/test_data_validator.py b/tests/components/http/test_data_validator.py index c7b5ed42ccd..b0a14a31bc5 100644 --- a/tests/components/http/test_data_validator.py +++ b/tests/components/http/test_data_validator.py @@ -11,7 +11,7 @@ from tests.async_mock import Mock async def get_client(aiohttp_client, validator): """Generate a client that hits a view decorated with validator.""" app = web.Application() - app["hass"] = Mock(is_stopping=False) + app["hass"] = Mock(is_running=True) class TestView(HomeAssistantView): url = "/" diff --git a/tests/components/http/test_view.py b/tests/components/http/test_view.py index 045f0837983..a6e4bdc12c8 100644 --- a/tests/components/http/test_view.py +++ b/tests/components/http/test_view.py @@ -19,13 +19,7 @@ from tests.async_mock import AsyncMock, Mock @pytest.fixture def mock_request(): """Mock a request.""" - return Mock(app={"hass": Mock(is_stopping=False)}, match_info={}) - - -@pytest.fixture -def mock_request_with_stopping(): - """Mock a request.""" - return Mock(app={"hass": Mock(is_stopping=True)}, match_info={}) + return Mock(app={"hass": Mock(is_running=True)}, match_info={}) async def test_invalid_json(caplog): @@ -61,11 +55,3 @@ async def test_handling_service_not_found(mock_request): Mock(requires_auth=False), AsyncMock(side_effect=ServiceNotFound("test", "test")), )(mock_request) - - -async def test_not_running(mock_request_with_stopping): - """Test we get a 503 when not running.""" - response = await request_handler_factory( - Mock(requires_auth=False), AsyncMock(side_effect=Unauthorized) - )(mock_request_with_stopping) - assert response.status == 503 diff --git a/tests/components/logbook/test_init.py b/tests/components/logbook/test_init.py index d86800b143b..abe6f6ec515 100644 --- a/tests/components/logbook/test_init.py +++ b/tests/components/logbook/test_init.py @@ -43,8 +43,7 @@ class TestComponentLogbook(unittest.TestCase): """Set up things to be run when tests are started.""" self.hass = get_test_home_assistant() init_recorder_component(self.hass) # Force an in memory DB - with patch("homeassistant.components.http.start_http_server_and_save_config"): - assert setup_component(self.hass, logbook.DOMAIN, self.EMPTY_CONFIG) + assert setup_component(self.hass, logbook.DOMAIN, self.EMPTY_CONFIG) def tearDown(self): """Stop everything that was started.""" diff --git a/tests/components/panel_iframe/test_init.py b/tests/components/panel_iframe/test_init.py index b38f3c4b1fa..d586c4c199e 100644 --- a/tests/components/panel_iframe/test_init.py +++ b/tests/components/panel_iframe/test_init.py @@ -4,7 +4,6 @@ import unittest from homeassistant import setup from homeassistant.components import frontend -from tests.async_mock import patch from tests.common import get_test_home_assistant @@ -27,42 +26,38 @@ class TestPanelIframe(unittest.TestCase): ] for conf in to_try: - with patch( - "homeassistant.components.http.start_http_server_and_save_config" - ): - assert not setup.setup_component( - self.hass, "panel_iframe", {"panel_iframe": conf} - ) + assert not setup.setup_component( + self.hass, "panel_iframe", {"panel_iframe": conf} + ) def test_correct_config(self): """Test correct config.""" - with patch("homeassistant.components.http.start_http_server_and_save_config"): - assert setup.setup_component( - self.hass, - "panel_iframe", - { - "panel_iframe": { - "router": { - "icon": "mdi:network-wireless", - "title": "Router", - "url": "http://192.168.1.1", - "require_admin": True, - }, - "weather": { - "icon": "mdi:weather", - "title": "Weather", - "url": "https://www.wunderground.com/us/ca/san-diego", - "require_admin": True, - }, - "api": {"icon": "mdi:weather", "title": "Api", "url": "/api"}, - "ftp": { - "icon": "mdi:weather", - "title": "FTP", - "url": "ftp://some/ftp", - }, - } - }, - ) + assert setup.setup_component( + self.hass, + "panel_iframe", + { + "panel_iframe": { + "router": { + "icon": "mdi:network-wireless", + "title": "Router", + "url": "http://192.168.1.1", + "require_admin": True, + }, + "weather": { + "icon": "mdi:weather", + "title": "Weather", + "url": "https://www.wunderground.com/us/ca/san-diego", + "require_admin": True, + }, + "api": {"icon": "mdi:weather", "title": "Api", "url": "/api"}, + "ftp": { + "icon": "mdi:weather", + "title": "FTP", + "url": "ftp://some/ftp", + }, + } + }, + ) panels = self.hass.data[frontend.DATA_PANELS] diff --git a/tests/test_bootstrap.py b/tests/test_bootstrap.py index 2be29e55814..9597dfa60b8 100644 --- a/tests/test_bootstrap.py +++ b/tests/test_bootstrap.py @@ -201,43 +201,6 @@ async def test_setup_after_deps_not_present(hass, caplog): assert order == ["root", "second_dep"] -async def test_setup_continues_if_blocked(hass, caplog): - """Test we continue after timeout if blocked.""" - caplog.set_level(logging.DEBUG) - order = [] - - def gen_domain_setup(domain): - async def async_setup(hass, config): - order.append(domain) - return True - - return async_setup - - mock_integration( - hass, MockModule(domain="root", async_setup=gen_domain_setup("root")) - ) - mock_integration( - hass, - MockModule( - domain="second_dep", - async_setup=gen_domain_setup("second_dep"), - partial_manifest={"after_dependencies": ["first_dep"]}, - ), - ) - - with patch.object(bootstrap, "TIMEOUT_EVENT_BOOTSTRAP", 0): - hass.async_create_task(asyncio.sleep(2)) - await bootstrap._async_set_up_integrations( - hass, {"root": {}, "first_dep": {}, "second_dep": {}} - ) - - assert "root" in hass.config.components - assert "first_dep" not in hass.config.components - assert "second_dep" in hass.config.components - assert order == ["root", "second_dep"] - assert "blocking Home Assistant from wrapping up" in caplog.text - - @pytest.fixture def mock_is_virtual_env(): """Mock enable logging.""" @@ -298,9 +261,7 @@ async def test_setup_hass( with patch( "homeassistant.config.async_hass_config_yaml", return_value={"browser": {}, "frontend": {}}, - ), patch.object(bootstrap, "LOG_SLOW_STARTUP_INTERVAL", 5000), patch( - "homeassistant.components.http.start_http_server_and_save_config" - ): + ), patch.object(bootstrap, "LOG_SLOW_STARTUP_INTERVAL", 5000): hass = await bootstrap.async_setup_hass( config_dir=get_test_config_dir(), verbose=verbose, @@ -377,7 +338,7 @@ async def test_setup_hass_invalid_yaml( """Test it works.""" with patch( "homeassistant.config.async_hass_config_yaml", side_effect=HomeAssistantError - ), patch("homeassistant.components.http.start_http_server_and_save_config"): + ): hass = await bootstrap.async_setup_hass( config_dir=get_test_config_dir(), verbose=False, @@ -430,9 +391,7 @@ async def test_setup_hass_safe_mode( hass.config_entries._async_schedule_save() await flush_store(hass.config_entries._store) - with patch("homeassistant.components.browser.setup") as browser_setup, patch( - "homeassistant.components.http.start_http_server_and_save_config" - ): + with patch("homeassistant.components.browser.setup") as browser_setup: hass = await bootstrap.async_setup_hass( config_dir=get_test_config_dir(), verbose=False, @@ -462,7 +421,7 @@ async def test_setup_hass_invalid_core_config( with patch( "homeassistant.config.async_hass_config_yaml", return_value={"homeassistant": {"non-existing": 1}}, - ), patch("homeassistant.components.http.start_http_server_and_save_config"): + ): hass = await bootstrap.async_setup_hass( config_dir=get_test_config_dir(), verbose=False, @@ -492,7 +451,7 @@ async def test_setup_safe_mode_if_no_frontend( with patch( "homeassistant.config.async_hass_config_yaml", return_value={"map": {}, "person": {"invalid": True}}, - ), patch("homeassistant.components.http.start_http_server_and_save_config"): + ): hass = await bootstrap.async_setup_hass( config_dir=get_test_config_dir(), verbose=verbose, diff --git a/tests/test_core.py b/tests/test_core.py index 9fc257eaf2d..3bc001b78b6 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -35,7 +35,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, Mock, PropertyMock, patch +from tests.async_mock import MagicMock, Mock, patch from tests.common import async_mock_service, get_test_home_assistant PST = pytz.timezone("America/Los_Angeles") @@ -901,8 +901,6 @@ class TestConfig(unittest.TestCase): def test_as_dict(self): """Test as dict.""" self.config.config_dir = "/test/ha-config" - self.config.hass = MagicMock() - type(self.config.hass.state).value = PropertyMock(return_value="RUNNING") expected = { "latitude": 0, "longitude": 0, @@ -916,7 +914,6 @@ class TestConfig(unittest.TestCase): "version": __version__, "config_source": "default", "safe_mode": False, - "state": "RUNNING", "external_url": None, "internal_url": None, } From 26fae7c629b4bd67f0e1c8a50dbf9e9f68677aac Mon Sep 17 00:00:00 2001 From: Tom Brien Date: Fri, 29 May 2020 11:51:43 +0100 Subject: [PATCH 249/406] Fix weather entity copy and paste error (#36227) --- homeassistant/components/weather/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/weather/__init__.py b/homeassistant/components/weather/__init__.py index 01ca8ed6790..8efb8519636 100644 --- a/homeassistant/components/weather/__init__.py +++ b/homeassistant/components/weather/__init__.py @@ -114,7 +114,7 @@ class WeatherEntity(Entity): @property def precision(self): - """Return the forecast.""" + """Return the precision of the temperature value.""" return ( PRECISION_TENTHS if self.temperature_unit == TEMP_CELSIUS From ff9de687c07e8df3ec25dc72e9fb11dbcd2ef0f5 Mon Sep 17 00:00:00 2001 From: jjlawren Date: Fri, 29 May 2020 06:24:35 -0500 Subject: [PATCH 250/406] Bump plexwebsocket to 0.0.10 (#36226) --- homeassistant/components/plex/manifest.json | 6 +++++- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/plex/manifest.json b/homeassistant/components/plex/manifest.json index b864cae3557..7e78cdda4b2 100644 --- a/homeassistant/components/plex/manifest.json +++ b/homeassistant/components/plex/manifest.json @@ -3,7 +3,11 @@ "name": "Plex Media Server", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/plex", - "requirements": ["plexapi==3.6.0", "plexauth==0.0.5", "plexwebsocket==0.0.8"], + "requirements": [ + "plexapi==3.6.0", + "plexauth==0.0.5", + "plexwebsocket==0.0.10" + ], "dependencies": ["http"], "after_dependencies": ["sonos"], "codeowners": ["@jjlawren"] diff --git a/requirements_all.txt b/requirements_all.txt index 3f548646a8c..9381750ab56 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1087,7 +1087,7 @@ plexapi==3.6.0 plexauth==0.0.5 # homeassistant.components.plex -plexwebsocket==0.0.8 +plexwebsocket==0.0.10 # homeassistant.components.plum_lightpad plumlightpad==0.0.11 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 4c62ea89b63..466d0ea99ed 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -459,7 +459,7 @@ plexapi==3.6.0 plexauth==0.0.5 # homeassistant.components.plex -plexwebsocket==0.0.8 +plexwebsocket==0.0.10 # homeassistant.components.mhz19 # homeassistant.components.serial_pm From 35c00fed6df3a6246deca03fbc8d52e7144c1f09 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 29 May 2020 11:43:48 -0700 Subject: [PATCH 251/406] Migrate translations upload to use Lokalise CLI 2 (#36247) --- script/translations/const.py | 1 - script/translations/upload.py | 18 +++++++++--------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/script/translations/const.py b/script/translations/const.py index ba4d7b39177..d282c9c2915 100644 --- a/script/translations/const.py +++ b/script/translations/const.py @@ -3,7 +3,6 @@ import pathlib CORE_PROJECT_ID = "130246255a974bd3b5e8a1.51616605" FRONTEND_PROJECT_ID = "3420425759f6d6d241f598.13594006" -CLI_1_DOCKER_IMAGE = "b8329d20280263cad04f65b843e54b9e8e6909a348a678eac959550b5ef5c75f" CLI_2_DOCKER_IMAGE = "v2.3.0" INTEGRATIONS_DIR = pathlib.Path("homeassistant/components") FRONTEND_DIR = pathlib.Path("../frontend") diff --git a/script/translations/upload.py b/script/translations/upload.py index a9dfa38f6c3..02d964a94c9 100755 --- a/script/translations/upload.py +++ b/script/translations/upload.py @@ -6,7 +6,7 @@ import pathlib import re import subprocess -from .const import CLI_1_DOCKER_IMAGE, CORE_PROJECT_ID, INTEGRATIONS_DIR +from .const import CLI_2_DOCKER_IMAGE, CORE_PROJECT_ID, INTEGRATIONS_DIR from .error import ExitApp from .util import get_current_branch, get_lokalise_token @@ -26,21 +26,21 @@ def run_upload_docker(): "-v", f"{LOCAL_FILE}:{CONTAINER_FILE}", "--rm", - f"lokalise/lokalise-cli@sha256:{CLI_1_DOCKER_IMAGE}", + f"lokalise/lokalise-cli-2:{CLI_2_DOCKER_IMAGE}", # Lokalise command - "lokalise", + "lokalise2", "--token", get_lokalise_token(), - "import", + "--project-id", CORE_PROJECT_ID, + "file", + "upload", "--file", CONTAINER_FILE, - "--lang_iso", + "--lang-iso", LANG_ISO, - "--convert_placeholders", - "0", - "--replace", - "1", + "--convert-placeholders=false", + "--replace-modified", ], ) print() From 67f7a4bb572f0f183138a64edf25a7fa19c280f2 Mon Sep 17 00:00:00 2001 From: foxy82 Date: Fri, 29 May 2020 22:39:17 +0100 Subject: [PATCH 252/406] Add more alexa doorbell event locales (#36252) --- homeassistant/components/alexa/capabilities.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/alexa/capabilities.py b/homeassistant/components/alexa/capabilities.py index 7451d15eb1c..c8eb932a9e0 100644 --- a/homeassistant/components/alexa/capabilities.py +++ b/homeassistant/components/alexa/capabilities.py @@ -1661,7 +1661,21 @@ class AlexaDoorbellEventSource(AlexaCapability): https://developer.amazon.com/docs/device-apis/alexa-doorbelleventsource.html """ - supported_locales = {"en-US"} + supported_locales = { + "en-US", + "de-DE", + "en-AU", + "en-CA", + "en-GB", + "en-IN", + "en-US", + "es-ES", + "es-MX", + "fr-CA", + "fr-FR", + "it-IT", + "ja-JP", + } def name(self): """Return the Alexa API name of this interface.""" From fa00f3e49b6bf8dcdf9deeb4ba2c2f50cd77b6d2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 29 May 2020 17:13:58 -0500 Subject: [PATCH 253/406] s/hass.loop.create_task/asyncio.create_task/g (#36262) --- homeassistant/bootstrap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 1a4a7de4c18..596b0250de6 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -346,7 +346,7 @@ async def _async_set_up_integrations( domain: hass.async_create_task(async_setup_component(hass, domain, config)) for domain in domains } - log_task = hass.loop.create_task(_async_log_pending_setups()) + log_task = asyncio.create_task(_async_log_pending_setups()) await asyncio.wait(futures.values()) log_task.cancel() errors = [domain for domain in domains if futures[domain].exception()] From f4a518edccba80282f4c183f198b20b5b2e6c836 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Sat, 30 May 2020 00:03:23 +0000 Subject: [PATCH 254/406] [ci skip] Translation update --- .../components/airvisual/translations/ca.json | 4 +-- .../components/airvisual/translations/es.json | 2 +- .../ambiclimate/translations/it.json | 2 +- .../components/atag/translations/ca.json | 2 +- .../components/atag/translations/es.json | 4 +-- .../components/atag/translations/it.json | 5 +-- .../components/atag/translations/nl.json | 3 +- .../components/auth/translations/it.json | 2 +- .../components/axis/translations/ca.json | 2 +- .../binary_sensor/translations/ca.json | 14 ++++---- .../binary_sensor/translations/it.json | 2 +- .../components/braviatv/translations/it.json | 3 +- .../components/braviatv/translations/nl.json | 3 +- .../components/bsblan/translations/es.json | 2 +- .../components/calendar/translations/ca.json | 4 +-- .../components/climate/translations/ca.json | 2 +- .../components/cover/translations/ca.json | 4 +-- .../components/daikin/translations/es.json | 4 +-- .../dialogflow/translations/it.json | 2 +- .../components/elgato/translations/it.json | 2 +- .../emulated_roku/translations/ca.json | 2 +- .../components/esphome/translations/it.json | 2 +- .../components/geofency/translations/it.json | 2 +- .../components/gpslogger/translations/it.json | 2 +- .../components/guardian/translations/it.json | 22 ++++++++++++ .../components/guardian/translations/nl.json | 22 ++++++++++++ .../homekit_controller/translations/ca.json | 6 ++-- .../huawei_lte/translations/ca.json | 2 +- .../components/ifttt/translations/it.json | 2 +- .../components/ipp/translations/ca.json | 2 +- .../components/locative/translations/it.json | 2 +- .../logi_circle/translations/it.json | 2 +- .../media_player/translations/ca.json | 2 +- .../components/mikrotik/translations/it.json | 2 +- .../components/nest/translations/it.json | 2 +- .../components/openuv/translations/ca.json | 2 +- .../components/openuv/translations/it.json | 3 ++ .../components/openuv/translations/nl.json | 3 ++ .../components/plaato/translations/it.json | 2 +- .../components/plant/translations/ca.json | 2 +- .../components/plex/translations/ca.json | 4 +-- .../components/plex/translations/es.json | 2 +- .../components/plugwise/translations/it.json | 22 ++++++++++++ .../components/plugwise/translations/nl.json | 36 +++++++++---------- .../components/plugwise/translations/ru.json | 22 ++++++++++++ .../plugwise/translations/zh-Hant.json | 22 ++++++++++++ .../components/point/translations/it.json | 2 +- .../components/ps4/translations/it.json | 4 +-- .../components/sensor/translations/ca.json | 34 +++++++++--------- .../tellduslive/translations/it.json | 2 +- .../components/toon/translations/it.json | 2 +- .../components/twilio/translations/it.json | 2 +- .../components/unifi/translations/ca.json | 2 +- .../components/vacuum/translations/ca.json | 4 +-- .../components/vizio/translations/ca.json | 14 ++++---- .../components/vizio/translations/it.json | 14 ++++---- .../components/vizio/translations/nl.json | 2 ++ .../components/withings/translations/ca.json | 2 +- .../components/wled/translations/bg.json | 15 +------- .../components/wled/translations/da.json | 15 +------- .../components/wled/translations/es-419.json | 15 +------- .../components/wled/translations/fr.json | 15 +------- .../components/wled/translations/nl.json | 15 +------- .../components/wled/translations/pt.json | 15 +------- .../components/wled/translations/sv.json | 15 +------- 65 files changed, 242 insertions(+), 209 deletions(-) create mode 100644 homeassistant/components/guardian/translations/it.json create mode 100644 homeassistant/components/guardian/translations/nl.json create mode 100644 homeassistant/components/plugwise/translations/it.json create mode 100644 homeassistant/components/plugwise/translations/ru.json create mode 100644 homeassistant/components/plugwise/translations/zh-Hant.json diff --git a/homeassistant/components/airvisual/translations/ca.json b/homeassistant/components/airvisual/translations/ca.json index 31c3dfaf1ab..045be812dc2 100644 --- a/homeassistant/components/airvisual/translations/ca.json +++ b/homeassistant/components/airvisual/translations/ca.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Aquesta clau API ja est\u00e0 sent utilitzada." + "already_configured": "Aquestes coordenades o Node/Pro ID ja estan registrades." }, "error": { "general_error": "S'ha produ\u00eft un error desconegut.", @@ -32,7 +32,7 @@ "node_pro": "AirVisual Node Pro", "type": "Tipus d'integraci\u00f3" }, - "description": "Monitoritzaci\u00f3 de la qualitat de l'aire per ubicaci\u00f3 geogr\u00e0fica.", + "description": "Tria quin tipus de dades d'AirVisual vols monitoritzar.", "title": "Configura AirVisual" } } diff --git a/homeassistant/components/airvisual/translations/es.json b/homeassistant/components/airvisual/translations/es.json index 4872f736915..dbb44ed4abe 100644 --- a/homeassistant/components/airvisual/translations/es.json +++ b/homeassistant/components/airvisual/translations/es.json @@ -21,7 +21,7 @@ "node_pro": { "data": { "ip_address": "Direcci\u00f3n IP/Nombre de host de la Unidad", - "password": "Contrase\u00f1a de la Unidad" + "password": "Contrase\u00f1a" }, "description": "Monitorizar una unidad personal AirVisual. La contrase\u00f1a puede ser recuperada desde la interfaz de la unidad.", "title": "Configurar un AirVisual Node/Pro" diff --git a/homeassistant/components/ambiclimate/translations/it.json b/homeassistant/components/ambiclimate/translations/it.json index 2bda20def83..427aa0ab445 100644 --- a/homeassistant/components/ambiclimate/translations/it.json +++ b/homeassistant/components/ambiclimate/translations/it.json @@ -3,7 +3,7 @@ "abort": { "access_token": "Errore sconosciuto durante la generazione di un token di accesso.", "already_setup": "L'account Ambiclimate \u00e8 configurato.", - "no_config": "\u00c8 necessario configurare Ambiclimate prima di poter eseguire l'autenticazione con esso. [Leggere le istruzioni] (https://www.home-assistant.io/components/ambiclimate/)." + "no_config": "\u00c8 necessario configurare Ambiclimate prima di poter eseguire l'autenticazione con esso. [Leggere le istruzioni](https://www.home-assistant.io/components/ambiclimate/)." }, "create_entry": { "default": "Autenticato con successo con Ambiclimate" diff --git a/homeassistant/components/atag/translations/ca.json b/homeassistant/components/atag/translations/ca.json index 0ccafe15f97..ac9959bca39 100644 --- a/homeassistant/components/atag/translations/ca.json +++ b/homeassistant/components/atag/translations/ca.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Nom\u00e9s es pot afegir un sol dispositiu Atag a Home Assistant" + "already_configured": "Aquest dispositiu ja ha estat afegit a Home Assistant" }, "error": { "connection_error": "No s'ha pogut connectar, torna-ho a provar", diff --git a/homeassistant/components/atag/translations/es.json b/homeassistant/components/atag/translations/es.json index b3d6fee9114..c60a8476bdf 100644 --- a/homeassistant/components/atag/translations/es.json +++ b/homeassistant/components/atag/translations/es.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "S\u00f3lo se puede a\u00f1adir un dispositivo Atag a Home Assistant" + "already_configured": "Este dispositivo ya ha sido a\u00f1adido a HomeAssistant" }, "error": { "connection_error": "No se pudo conectar, por favor, int\u00e9ntalo de nuevo", @@ -12,7 +12,7 @@ "data": { "email": "Correo electr\u00f3nico (Opcional)", "host": "Host", - "port": "Puerto (10000)" + "port": "Puerto" }, "title": "Conectarse al dispositivo" } diff --git a/homeassistant/components/atag/translations/it.json b/homeassistant/components/atag/translations/it.json index 84d07585e73..cbef4fab268 100644 --- a/homeassistant/components/atag/translations/it.json +++ b/homeassistant/components/atag/translations/it.json @@ -1,10 +1,11 @@ { "config": { "abort": { - "already_configured": "\u00c8 possibile aggiungere un solo dispositivo Atag ad Home Assistant" + "already_configured": "Questo dispositivo \u00e8 gi\u00e0 stato aggiunto a HomeAssistant" }, "error": { - "connection_error": "Impossibile connettersi, si prega di riprovare" + "connection_error": "Impossibile connettersi, si prega di riprovare", + "unauthorized": "Associazione negata, controllare il dispositivo per la richiesta di autenticazione" }, "step": { "user": { diff --git a/homeassistant/components/atag/translations/nl.json b/homeassistant/components/atag/translations/nl.json index 049e363cf92..077beb65871 100644 --- a/homeassistant/components/atag/translations/nl.json +++ b/homeassistant/components/atag/translations/nl.json @@ -4,7 +4,8 @@ "already_configured": "Er kan slechts \u00e9\u00e9n Atag-apparaat worden toegevoegd aan Home Assistant " }, "error": { - "connection_error": "Verbinding mislukt, probeer het opnieuw" + "connection_error": "Verbinding mislukt, probeer het opnieuw", + "unauthorized": "Koppelen geweigerd, controleer apparaat op autorisatieverzoek" }, "step": { "user": { diff --git a/homeassistant/components/auth/translations/it.json b/homeassistant/components/auth/translations/it.json index dbfe4acd615..34d404f0bc6 100644 --- a/homeassistant/components/auth/translations/it.json +++ b/homeassistant/components/auth/translations/it.json @@ -25,7 +25,7 @@ }, "step": { "init": { - "description": "Per attivare l'autenticazione a due fattori utilizzando le password monouso basate sul tempo, eseguire la scansione del codice QR con l'app di autenticazione. Se non ne hai uno, ti consigliamo [Google Authenticator] (https://support.google.com/accounts/answer/1066447) o [Authy] (https://authy.com/). \n\n {qr_code} \n \nDopo la scansione, inserisci il codice a sei cifre dalla tua app per verificare la configurazione. Se riscontri problemi con la scansione del codice QR, esegui una configurazione manuale con il codice ** ` {code} ` **.", + "description": "Per attivare l'autenticazione a due fattori utilizzando le password monouso basate sul tempo, eseguire la scansione del codice QR con l'app di autenticazione. Se non ne hai uno, ti consigliamo [Google Authenticator](https://support.google.com/accounts/answer/1066447) o [Authy](https://authy.com/). \n\n {qr_code} \n \nDopo la scansione, inserisci il codice a sei cifre dalla tua app per verificare la configurazione. Se riscontri problemi con la scansione del codice QR, esegui una configurazione manuale con il codice **`{code}`**.", "title": "Imposta l'autenticazione a due fattori usando TOTP" } }, diff --git a/homeassistant/components/axis/translations/ca.json b/homeassistant/components/axis/translations/ca.json index a636c3c40c8..449feb28fc7 100644 --- a/homeassistant/components/axis/translations/ca.json +++ b/homeassistant/components/axis/translations/ca.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "El dispositiu ja est\u00e0 configurat", - "bad_config_file": "Dades incorrectes del fitxer de configuraci\u00f3", + "bad_config_file": "Dades del fitxer de configuraci\u00f3 incorrectes", "link_local_address": "L'enlla\u00e7 d'adreces locals no est\u00e0 disponible", "not_axis_device": "El dispositiu descobert no \u00e9s un dispositiu Axis" }, diff --git a/homeassistant/components/binary_sensor/translations/ca.json b/homeassistant/components/binary_sensor/translations/ca.json index 2abd811d944..047420b051e 100644 --- a/homeassistant/components/binary_sensor/translations/ca.json +++ b/homeassistant/components/binary_sensor/translations/ca.json @@ -107,12 +107,12 @@ "on": "Connectat" }, "door": { - "off": "Tancada", - "on": "Oberta" + "off": "Tancat/da", + "on": "Obert/a" }, "garage_door": { - "off": "Tancada", - "on": "Oberta" + "off": "Tancat/da", + "on": "Obert/a" }, "gas": { "off": "Lliure", @@ -139,7 +139,7 @@ "on": "Detectat" }, "opening": { - "off": "Tancat", + "off": "Tancat/da", "on": "Obert" }, "presence": { @@ -167,8 +167,8 @@ "on": "Detectat" }, "window": { - "off": "Tancada", - "on": "Oberta" + "off": "Tancat/da", + "on": "Obert/a" } }, "title": "Sensor binari" diff --git a/homeassistant/components/binary_sensor/translations/it.json b/homeassistant/components/binary_sensor/translations/it.json index 95a9778345e..590ab87f142 100644 --- a/homeassistant/components/binary_sensor/translations/it.json +++ b/homeassistant/components/binary_sensor/translations/it.json @@ -128,7 +128,7 @@ }, "moisture": { "off": "Asciutto", - "on": "Bagnato" + "on": "Umido" }, "motion": { "off": "Assenza", diff --git a/homeassistant/components/braviatv/translations/it.json b/homeassistant/components/braviatv/translations/it.json index 1b20c51009b..46cb5fca7a4 100644 --- a/homeassistant/components/braviatv/translations/it.json +++ b/homeassistant/components/braviatv/translations/it.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Questo televisore \u00e8 gi\u00e0 configurato." + "already_configured": "Questo televisore \u00e8 gi\u00e0 configurato.", + "no_ip_control": "Il controllo IP \u00e8 disabilitato sulla TV o la TV non \u00e8 supportata." }, "error": { "cannot_connect": "Connessione non riuscita, host o codice PIN non valido.", diff --git a/homeassistant/components/braviatv/translations/nl.json b/homeassistant/components/braviatv/translations/nl.json index ba09fbca3a3..b35d7de45cf 100644 --- a/homeassistant/components/braviatv/translations/nl.json +++ b/homeassistant/components/braviatv/translations/nl.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Deze tv is al geconfigureerd." + "already_configured": "Deze tv is al geconfigureerd.", + "no_ip_control": "IP-besturing is uitgeschakeld op uw tv of de tv wordt niet ondersteund." }, "error": { "cannot_connect": "Geen verbinding, ongeldige host of PIN-code.", diff --git a/homeassistant/components/bsblan/translations/es.json b/homeassistant/components/bsblan/translations/es.json index 77c7c51e68b..9d90f95e3d8 100644 --- a/homeassistant/components/bsblan/translations/es.json +++ b/homeassistant/components/bsblan/translations/es.json @@ -12,7 +12,7 @@ "data": { "host": "Host o direcci\u00f3n IP", "passkey": "Clave de acceso", - "port": "N\u00famero de puerto" + "port": "Puerto" }, "description": "Configura tu dispositivo BSB-Lan para integrarse con Home Assistant.", "title": "Conectar con el dispositivo BSB-Lan" diff --git a/homeassistant/components/calendar/translations/ca.json b/homeassistant/components/calendar/translations/ca.json index 5e842769c51..f1b3279a4cb 100644 --- a/homeassistant/components/calendar/translations/ca.json +++ b/homeassistant/components/calendar/translations/ca.json @@ -1,8 +1,8 @@ { "state": { "_": { - "off": "Desactivat", - "on": "Activat" + "off": "OFF", + "on": "ON" } }, "title": "Calendari" diff --git a/homeassistant/components/climate/translations/ca.json b/homeassistant/components/climate/translations/ca.json index e2f3a58e2eb..5cc7233864e 100644 --- a/homeassistant/components/climate/translations/ca.json +++ b/homeassistant/components/climate/translations/ca.json @@ -22,7 +22,7 @@ "fan_only": "Nom\u00e9s ventilador", "heat": "Escalfar", "heat_cool": "Escalfar/Refredar", - "off": "Apagat" + "off": "OFF" } }, "title": "Climatitzaci\u00f3" diff --git a/homeassistant/components/cover/translations/ca.json b/homeassistant/components/cover/translations/ca.json index 024a2a67b25..970661be215 100644 --- a/homeassistant/components/cover/translations/ca.json +++ b/homeassistant/components/cover/translations/ca.json @@ -13,8 +13,8 @@ "is_closing": "{entity_name} est\u00e0 tancant-se", "is_open": "{entity_name} est\u00e0 obert/a", "is_opening": "{entity_name} s'est\u00e0 obrint", - "is_position": "La posici\u00f3 de {entity_name} \u00e9s", - "is_tilt_position": "La posici\u00f3 d'inclinaci\u00f3 de {entity_name} \u00e9s" + "is_position": "La posici\u00f3 actual de {entity_name} \u00e9s", + "is_tilt_position": "La inclinaci\u00f3 actual de {entity_name} \u00e9s" }, "trigger_type": { "closed": "{entity_name} tancat/da", diff --git a/homeassistant/components/daikin/translations/es.json b/homeassistant/components/daikin/translations/es.json index 3e667fa65cf..42b58e38438 100644 --- a/homeassistant/components/daikin/translations/es.json +++ b/homeassistant/components/daikin/translations/es.json @@ -12,8 +12,8 @@ "user": { "data": { "host": "Host", - "key": "Clave de autenticaci\u00f3n (s\u00f3lo utilizada por dispositivos BRP072C/Zena)", - "password": "Contrase\u00f1a del dispositivo (s\u00f3lo utilizada por dispositivos SKYFi)" + "key": "Clave API", + "password": "Contrase\u00f1a" }, "description": "Introduce la IP de tu aire acondicionado Daikin", "title": "Configurar aire acondicionado Daikin" diff --git a/homeassistant/components/dialogflow/translations/it.json b/homeassistant/components/dialogflow/translations/it.json index fe31d88e5c4..2c933d09a52 100644 --- a/homeassistant/components/dialogflow/translations/it.json +++ b/homeassistant/components/dialogflow/translations/it.json @@ -5,7 +5,7 @@ "one_instance_allowed": "\u00c8 necessaria una sola istanza." }, "create_entry": { - "default": "Per inviare eventi a Home Assistant, dovrai configurare [l'integrazione webhook di Dialogflow]({dialogflow_url})\n\n Compila le seguenti informazioni: \n\n - URL: ` {webhook_url} ` \n - Method: POST \n - Content Type: application/json \n\n Vedi [la documentazione]({docs_url}) for ulteriori dettagli." + "default": "Per inviare eventi a Home Assistant, dovrai configurare [l'integrazione webhook di Dialogflow]({dialogflow_url})\n\n Compila le seguenti informazioni: \n\n - URL: `{webhook_url}` \n - Method: POST \n - Content Type: application/json \n\n Vedi [la documentazione]({docs_url}) for ulteriori dettagli." }, "step": { "user": { diff --git a/homeassistant/components/elgato/translations/it.json b/homeassistant/components/elgato/translations/it.json index 77e6e7e827e..3985bcf6f59 100644 --- a/homeassistant/components/elgato/translations/it.json +++ b/homeassistant/components/elgato/translations/it.json @@ -17,7 +17,7 @@ "description": "Configura Elgato Key Light per l'integrazione con Home Assistant." }, "zeroconf_confirm": { - "description": "Vuoi aggiungere il dispositivo Elgato Key Light con il numero di serie {serial_number} a Home Assistant?", + "description": "Vuoi aggiungere il dispositivo Elgato Key Light con il numero di serie `{serial_number}` a Home Assistant?", "title": "Dispositivo Elgato Key Light rilevato" } } diff --git a/homeassistant/components/emulated_roku/translations/ca.json b/homeassistant/components/emulated_roku/translations/ca.json index cccea2cb77b..e54c91b1a48 100644 --- a/homeassistant/components/emulated_roku/translations/ca.json +++ b/homeassistant/components/emulated_roku/translations/ca.json @@ -17,5 +17,5 @@ } } }, - "title": "EmulatedRoku" + "title": "Emulated Roku" } \ No newline at end of file diff --git a/homeassistant/components/esphome/translations/it.json b/homeassistant/components/esphome/translations/it.json index 58f6dda968a..19ff26493a8 100644 --- a/homeassistant/components/esphome/translations/it.json +++ b/homeassistant/components/esphome/translations/it.json @@ -26,7 +26,7 @@ "host": "Host", "port": "Porta" }, - "description": "Inserisci le impostazioni di connessione del tuo nodo [ESPHome] (https://esphomelib.com/)." + "description": "Inserisci le impostazioni di connessione del tuo nodo [ESPHome](https://esphomelib.com/)." } } } diff --git a/homeassistant/components/geofency/translations/it.json b/homeassistant/components/geofency/translations/it.json index 0640d351e53..4a74e58e5ca 100644 --- a/homeassistant/components/geofency/translations/it.json +++ b/homeassistant/components/geofency/translations/it.json @@ -5,7 +5,7 @@ "one_instance_allowed": "\u00c8 necessaria una sola istanza." }, "create_entry": { - "default": "Per inviare eventi a Home Assistant, dovrai configurare la funzionalit\u00e0 webhook in Geofency.\n\n Compila le seguenti informazioni: \n\n - URL: ` {webhook_url} ` \n - Method: POST \n\n Vedi [la documentazione]({docs_url}) for ulteriori dettagli." + "default": "Per inviare eventi a Home Assistant, dovrai configurare la funzionalit\u00e0 webhook in Geofency.\n\n Compila le seguenti informazioni: \n\n - URL: `{webhook_url}` \n - Method: POST \n\n Vedi [la documentazione]({docs_url}) for ulteriori dettagli." }, "step": { "user": { diff --git a/homeassistant/components/gpslogger/translations/it.json b/homeassistant/components/gpslogger/translations/it.json index 8c26f19ac5b..32e6b574096 100644 --- a/homeassistant/components/gpslogger/translations/it.json +++ b/homeassistant/components/gpslogger/translations/it.json @@ -5,7 +5,7 @@ "one_instance_allowed": "\u00c8 necessaria una sola istanza." }, "create_entry": { - "default": "Per inviare eventi a Home Assistant, dovrai configurare la funzionalit\u00e0 webhook in GPSLogger.\n\n Compila le seguenti informazioni: \n\n - URL: ` {webhook_url} ` \n - Method: POST \n\n Vedi [la documentazione]({docs_url}) for ulteriori dettagli." + "default": "Per inviare eventi a Home Assistant, dovrai configurare la funzionalit\u00e0 webhook in GPSLogger.\n\n Compila le seguenti informazioni: \n\n - URL: `{webhook_url}` \n - Method: POST \n\n Vedi [la documentazione]({docs_url}) for ulteriori dettagli." }, "step": { "user": { diff --git a/homeassistant/components/guardian/translations/it.json b/homeassistant/components/guardian/translations/it.json new file mode 100644 index 00000000000..3f1999be761 --- /dev/null +++ b/homeassistant/components/guardian/translations/it.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Questo dispositivo Guardian \u00e8 gi\u00e0 stato configurato", + "already_in_progress": "La configurazione del dispositivo Guardian \u00e8 gi\u00e0 in corso.", + "connection_error": "Impossibile connettersi al dispositivo Guardian." + }, + "step": { + "user": { + "data": { + "ip_address": "Indirizzo IP", + "port": "Porta" + }, + "description": "Configurare un dispositivo Elexa Guardian locale." + }, + "zeroconf_confirm": { + "description": "Vuoi configurare questo dispositivo Guardian?" + } + } + }, + "title": "Elexa Guardian" +} \ No newline at end of file diff --git a/homeassistant/components/guardian/translations/nl.json b/homeassistant/components/guardian/translations/nl.json new file mode 100644 index 00000000000..a1cd1def87a --- /dev/null +++ b/homeassistant/components/guardian/translations/nl.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Dit Guardian-apparaat is al geconfigureerd.", + "already_in_progress": "De configuratie van het Guardian-apparaat is al bezig.", + "connection_error": "Kan geen verbinding maken met het Guardian-apparaat." + }, + "step": { + "user": { + "data": { + "ip_address": "IP-adres", + "port": "Poort" + }, + "description": "Configureer een lokaal Elexa Guardian-apparaat." + }, + "zeroconf_confirm": { + "description": "Wilt u dit Guardian-apparaat instellen?" + } + } + }, + "title": "Elexa Guardian" +} \ 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 3407d93c63f..bf19b9af672 100644 --- a/homeassistant/components/homekit_controller/translations/ca.json +++ b/homeassistant/components/homekit_controller/translations/ca.json @@ -6,7 +6,7 @@ "already_in_progress": "El flux de dades de configuraci\u00f3 pel dispositiu ja est\u00e0 en curs.", "already_paired": "Aquest accessori ja est\u00e0 vinculat amb un altre dispositiu. Reinicia l'accessori i torna-ho a provar.", "ignored_model": "La disponibilitat de HomeKit per aquest model est\u00e0 bloquejada ja que, de moment, no hi ha una integraci\u00f3 nativa completa.", - "invalid_config_entry": "Aquest dispositiu s'est\u00e0 mostrant com a llest per a ser vinculat per\u00f2, hi ha una entrada de configuraci\u00f3 conflictiva a Home Assistant que s'ha d'eliminar primer.", + "invalid_config_entry": "Aquest dispositiu s'est\u00e0 mostrant com a llest per a ser vinculat per\u00f2 ja hi ha una entrada de configuraci\u00f3 conflictiva a Home Assistant que s'ha d'eliminar primer.", "no_devices": "No s'han trobat dispositius desvinculats." }, "error": { @@ -14,7 +14,7 @@ "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'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.", + "pairing_failed": "S'ha produ\u00eft un error mentre s'intentava la vinculaci\u00f3 amb aquest dispositiu. Pot ser que sigui un error temporal o pot ser que el teu dispositiu encara no sigui compatible.", "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." }, @@ -36,5 +36,5 @@ } } }, - "title": "Accessori HomeKit" + "title": "Controlador HomeKit" } \ No newline at end of file diff --git a/homeassistant/components/huawei_lte/translations/ca.json b/homeassistant/components/huawei_lte/translations/ca.json index cb8a4331a57..a8f4f9584c2 100644 --- a/homeassistant/components/huawei_lte/translations/ca.json +++ b/homeassistant/components/huawei_lte/translations/ca.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Aquest dispositiu ja est\u00e0 configurat", + "already_configured": "Aquest dispositiu ja ha estat configurat", "already_in_progress": "Aquest dispositiu ja s'est\u00e0 configurant", "not_huawei_lte": "No \u00e9s un dispositiu Huawei LTE" }, diff --git a/homeassistant/components/ifttt/translations/it.json b/homeassistant/components/ifttt/translations/it.json index ce037f1c5c3..1989efd733c 100644 --- a/homeassistant/components/ifttt/translations/it.json +++ b/homeassistant/components/ifttt/translations/it.json @@ -5,7 +5,7 @@ "one_instance_allowed": "\u00c8 necessaria una sola istanza." }, "create_entry": { - "default": "Per inviare eventi a Home Assistant, dovrai utilizzare l'azione \"Esegui una richiesta web\" dall'applet [Weblet di IFTTT] ( {applet_url} ). \n\n Compila le seguenti informazioni: \n\n - URL: ` {webhook_url} ` \n - Metodo: POST \n - Tipo di contenuto: application / json \n\n Vedi [la documentazione] ( {docs_url} ) su come configurare le automazioni per gestire i dati in arrivo." + "default": "Per inviare eventi a Home Assistant, \u00e8 necessario utilizzare l'azione \"Crea una richiesta web\" dall'[applet IFTTT Webhook]({applet_url}). \n\n Compilare le seguenti informazioni: \n\n - URL: `{webhook_url}` \n - Metodo: POST \n - Tipo di contenuto: application/json \n\nVedere [la documentazione]({docs_url}) su come configurare le automazioni per gestire i dati in arrivo." }, "step": { "user": { diff --git a/homeassistant/components/ipp/translations/ca.json b/homeassistant/components/ipp/translations/ca.json index 7d372548e22..cd619583e52 100644 --- a/homeassistant/components/ipp/translations/ca.json +++ b/homeassistant/components/ipp/translations/ca.json @@ -27,7 +27,7 @@ "title": "Enlla\u00e7 d'impressora" }, "zeroconf_confirm": { - "description": "Vols afegir la impressora {name} a Home Assistant?", + "description": "Vols configurar {name}?", "title": "Impressora descoberta" } } diff --git a/homeassistant/components/locative/translations/it.json b/homeassistant/components/locative/translations/it.json index c023cd832b9..210fbfe4c28 100644 --- a/homeassistant/components/locative/translations/it.json +++ b/homeassistant/components/locative/translations/it.json @@ -5,7 +5,7 @@ "one_instance_allowed": "\u00c8 necessaria una sola istanza." }, "create_entry": { - "default": "Per inviare localit\u00e0 a Home Assistant, dovrai configurare la funzionalit\u00e0 Webhook nell'app Locative.\n\n Compila le seguenti informazioni: \n\n - URL: ` {webhook_url} ` \n - Method: POST \n\n Vedi [la documentazione]({docs_url}) for ulteriori dettagli." + "default": "Per inviare localit\u00e0 a Home Assistant, dovrai configurare la funzionalit\u00e0 Webhook nell'app Locative.\n\n Compila le seguenti informazioni: \n\n - URL: `{webhook_url}` \n - Method: POST \n\n Vedi [la documentazione]({docs_url}) for ulteriori dettagli." }, "step": { "user": { diff --git a/homeassistant/components/logi_circle/translations/it.json b/homeassistant/components/logi_circle/translations/it.json index 2d6e7621281..6bb23254cda 100644 --- a/homeassistant/components/logi_circle/translations/it.json +++ b/homeassistant/components/logi_circle/translations/it.json @@ -4,7 +4,7 @@ "already_setup": "\u00c8 possibile configurare solo un singolo account Logi Circle.", "external_error": "Si \u00e8 verificata un'eccezione da un altro flusso.", "external_setup": "Logi Circle configurato con successo da un altro flusso.", - "no_flows": "Devi configurare Logi Circle prima di poter eseguire l'autenticazione. [Si prega di leggere le istruzioni] (https://www.home-assistant.io/components/logi_circle/)." + "no_flows": "Devi configurare Logi Circle prima di poter eseguire l'autenticazione. [Si prega di leggere le istruzioni](https://www.home-assistant.io/components/logi_circle/)." }, "create_entry": { "default": "Autenticato con successo con Logi Circle." diff --git a/homeassistant/components/media_player/translations/ca.json b/homeassistant/components/media_player/translations/ca.json index afec96fe14f..1a1c161e5ec 100644 --- a/homeassistant/components/media_player/translations/ca.json +++ b/homeassistant/components/media_player/translations/ca.json @@ -13,7 +13,7 @@ "idle": "Inactiu", "off": "OFF", "on": "ON", - "paused": "Pausat", + "paused": "Pausat/da", "playing": "Reproduint", "standby": "En espera" } diff --git a/homeassistant/components/mikrotik/translations/it.json b/homeassistant/components/mikrotik/translations/it.json index 69cdadb4dfc..104392236b2 100644 --- a/homeassistant/components/mikrotik/translations/it.json +++ b/homeassistant/components/mikrotik/translations/it.json @@ -27,7 +27,7 @@ "device_tracker": { "data": { "arp_ping": "Attivare il ping ARP", - "detection_time": "Considerare l'intervallo di casa", + "detection_time": "Considerare in casa nell'intervallo di", "force_dhcp": "Scansione forzata con DHCP" } } diff --git a/homeassistant/components/nest/translations/it.json b/homeassistant/components/nest/translations/it.json index 2e567a27ce1..b525781fdeb 100644 --- a/homeassistant/components/nest/translations/it.json +++ b/homeassistant/components/nest/translations/it.json @@ -4,7 +4,7 @@ "already_setup": "\u00c8 possibile configurare un solo account Nest.", "authorize_url_fail": "Errore sconosciuto nel generare l'url di autorizzazione", "authorize_url_timeout": "Tempo scaduto nel generare l'url di autorizzazione", - "no_flows": "Devi configurare Nest prima di poter eseguire l'autenticazione. [Si prega di leggere le istruzioni] (https://www.home-assistant.io/components/nest/)." + "no_flows": "Devi configurare Nest prima di poter eseguire l'autenticazione. [Si prega di leggere le istruzioni](https://www.home-assistant.io/components/nest/)." }, "error": { "internal_error": "Errore interno nella convalida del codice", diff --git a/homeassistant/components/openuv/translations/ca.json b/homeassistant/components/openuv/translations/ca.json index 973148e86bd..2bc06623cc2 100644 --- a/homeassistant/components/openuv/translations/ca.json +++ b/homeassistant/components/openuv/translations/ca.json @@ -5,7 +5,7 @@ }, "error": { "identifier_exists": "Les coordenades ja estan registrades", - "invalid_api_key": "Clau API no v\u00e0lida" + "invalid_api_key": "Clau API inv\u00e0lida" }, "step": { "user": { diff --git a/homeassistant/components/openuv/translations/it.json b/homeassistant/components/openuv/translations/it.json index 8a07b3c3175..e6113500fbe 100644 --- a/homeassistant/components/openuv/translations/it.json +++ b/homeassistant/components/openuv/translations/it.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "Queste coordinate sono gi\u00e0 registrate." + }, "error": { "identifier_exists": "Coordinate gi\u00e0 registrate", "invalid_api_key": "Chiave API non valida" diff --git a/homeassistant/components/openuv/translations/nl.json b/homeassistant/components/openuv/translations/nl.json index c4f7834ff89..e3badcb796a 100644 --- a/homeassistant/components/openuv/translations/nl.json +++ b/homeassistant/components/openuv/translations/nl.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "Deze co\u00f6rdinaten zijn al geregistreerd." + }, "error": { "identifier_exists": "Co\u00f6rdinaten al geregistreerd", "invalid_api_key": "Ongeldige API-sleutel" diff --git a/homeassistant/components/plaato/translations/it.json b/homeassistant/components/plaato/translations/it.json index 2c6235cf17c..d0f04bca9d9 100644 --- a/homeassistant/components/plaato/translations/it.json +++ b/homeassistant/components/plaato/translations/it.json @@ -5,7 +5,7 @@ "one_instance_allowed": "\u00c8 necessaria solo una singola istanza." }, "create_entry": { - "default": "Per inviare eventi a Home Assistant, dovrai impostare la funzione webhook in Plaato Airlock. \n\n Inserisci le seguenti informazioni: \n\n - URL: ` {webhook_url} ` \n - Metodo: POST \n\n Vedi [la documentazione]({docs_url}) per ulteriori dettagli." + "default": "Per inviare eventi a Home Assistant, dovrai impostare la funzione webhook in Plaato Airlock. \n\n Inserisci le seguenti informazioni: \n\n - URL: `{webhook_url}` \n - Metodo: POST \n\n Vedi [la documentazione]({docs_url}) per ulteriori dettagli." }, "step": { "user": { diff --git a/homeassistant/components/plant/translations/ca.json b/homeassistant/components/plant/translations/ca.json index 7a2a8d4f616..b338a20ae41 100644 --- a/homeassistant/components/plant/translations/ca.json +++ b/homeassistant/components/plant/translations/ca.json @@ -5,5 +5,5 @@ "problem": "Problema" } }, - "title": "Planta" + "title": "Monitoritzaci\u00f3 de planta" } \ No newline at end of file diff --git a/homeassistant/components/plex/translations/ca.json b/homeassistant/components/plex/translations/ca.json index 49d7b5d6278..fe78770ed9b 100644 --- a/homeassistant/components/plex/translations/ca.json +++ b/homeassistant/components/plex/translations/ca.json @@ -10,9 +10,9 @@ "unknown": "Ha fallat per motiu desconegut" }, "error": { - "faulty_credentials": "Ha fallat l'autoritzaci\u00f3", + "faulty_credentials": "Ha fallat l'autoritzaci\u00f3, comprova el Token", "host_or_token": "Has de proporcionar almenys o un amfitri\u00f3 (host) o un token", - "no_servers": "No hi ha servidors enlla\u00e7ats amb el compte", + "no_servers": "No hi ha servidors vinculats amb el compte de Plex", "not_found": "No s'ha trobat el servidor Plex", "ssl_error": "Problema amb el certificat SSL" }, diff --git a/homeassistant/components/plex/translations/es.json b/homeassistant/components/plex/translations/es.json index b28d6279ade..6cdecd52408 100644 --- a/homeassistant/components/plex/translations/es.json +++ b/homeassistant/components/plex/translations/es.json @@ -20,7 +20,7 @@ "step": { "manual_setup": { "data": { - "host": "Host (Opcional si se proporciona Token)", + "host": "Host", "port": "Puerto", "ssl": "Usar SSL", "token": "Token (Opcional)", diff --git a/homeassistant/components/plugwise/translations/it.json b/homeassistant/components/plugwise/translations/it.json new file mode 100644 index 00000000000..96eceaaf7b2 --- /dev/null +++ b/homeassistant/components/plugwise/translations/it.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Smile gi\u00e0 configurato" + }, + "error": { + "cannot_connect": "Impossibile connettersi, si prega di riprovare", + "invalid_auth": "Autenticazione non valida. Controllare gli 8 caratterei dell'ID Smile", + "unknown": "Errore imprevisto" + }, + "step": { + "user": { + "data": { + "host": "Indirizzo IP Smile", + "password": "ID Smile" + }, + "description": "Dettagli", + "title": "Connettersi al dispositivo" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/plugwise/translations/nl.json b/homeassistant/components/plugwise/translations/nl.json index 7665136a58e..964675e0c63 100644 --- a/homeassistant/components/plugwise/translations/nl.json +++ b/homeassistant/components/plugwise/translations/nl.json @@ -1,22 +1,22 @@ { - "config": { - "step": { - "user": { - "title": "Verbinden met de Smile", - "description": "Gegevens", - "data": { - "host": "IP adres van de Smile", - "password": "Smile ID" + "config": { + "abort": { + "already_configured": "Deze Smile is al geconfigureerd" + }, + "error": { + "cannot_connect": "Verbinding mislukt, probeer het opnieuw", + "invalid_auth": "Ongeldige authenticatie, controleer de 8 karakters van uw Smile-ID", + "unknown": "Onverwachte fout" + }, + "step": { + "user": { + "data": { + "host": "Smile IP-adres", + "password": "Smile-ID" + }, + "description": "Details", + "title": "Maak verbinding met de Smile" + } } - } - }, - "error": { - "cannot_connect": "Verbinding mislukt, probeer het opnieuw", - "invalid_auth": "Authenticatie mislukt, voer de 8 karakters van je Smile goed in", - "unknown": "Overwachte fout" - }, - "abort": { - "already_configured": "Deze Smile is al geconfigureerd" } - } } \ No newline at end of file diff --git a/homeassistant/components/plugwise/translations/ru.json b/homeassistant/components/plugwise/translations/ru.json new file mode 100644 index 00000000000..b500247cfdf --- /dev/null +++ b/homeassistant/components/plugwise/translations/ru.json @@ -0,0 +1,22 @@ +{ + "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.", + "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f. \u041f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 ID \u0438 \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0441\u043d\u043e\u0432\u0430.", + "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." + }, + "step": { + "user": { + "data": { + "host": "IP-\u0430\u0434\u0440\u0435\u0441", + "password": "ID \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430" + }, + "description": "\u041f\u043e\u0434\u0440\u043e\u0431\u043d\u043e\u0441\u0442\u0438", + "title": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/plugwise/translations/zh-Hant.json b/homeassistant/components/plugwise/translations/zh-Hant.json new file mode 100644 index 00000000000..b35fcea1508 --- /dev/null +++ b/homeassistant/components/plugwise/translations/zh-Hant.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Smile \u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" + }, + "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557\uff0c\u8acb\u518d\u8a66\u4e00\u6b21", + "invalid_auth": "\u9a57\u8b49\u7121\u6548\uff0c\u8acb\u6aa2\u67e5\u6240\u8f38\u5165\u7684 Smile ID 8 \u4f4d\u5b57\u5143", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + }, + "step": { + "user": { + "data": { + "host": "Smile IP \u4f4d\u5740", + "password": "Smile ID" + }, + "description": "\u8a73\u7d30\u8cc7\u8a0a", + "title": "\u9023\u7dda\u81f3 Smile" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/point/translations/it.json b/homeassistant/components/point/translations/it.json index ec38d1a16f3..8f2b5f94c4b 100644 --- a/homeassistant/components/point/translations/it.json +++ b/homeassistant/components/point/translations/it.json @@ -16,7 +16,7 @@ }, "step": { "auth": { - "description": "Segui il link qui sotto e ** Accetta** l'accesso al tuo account Minut, quindi torna indietro e premi **Invia** qui sotto. \n\n [Link]({authorization_url})", + "description": "Segui il link qui sotto e **Accetta** l'accesso al tuo account Minut, quindi torna indietro e premi **Invia** qui sotto. \n\n [Link]({authorization_url})", "title": "Autenticare Point" }, "user": { diff --git a/homeassistant/components/ps4/translations/it.json b/homeassistant/components/ps4/translations/it.json index dc73362280e..d010c2e1f45 100644 --- a/homeassistant/components/ps4/translations/it.json +++ b/homeassistant/components/ps4/translations/it.json @@ -4,8 +4,8 @@ "credential_error": "Errore nel recupero delle credenziali.", "devices_configured": "Tutti i dispositivi trovati sono gi\u00e0 configurati.", "no_devices_found": "Nessun dispositivo PlayStation 4 trovato in rete.", - "port_987_bind_error": "Impossibile collegarsi alla porta 987. Per ulteriori informazioni, consultare la [documentazione] (https://www.home-assistant.io/components/ps4/) per ulteriori informazioni.", - "port_997_bind_error": "Impossibile collegarsi alla porta 997. Consultare la [documentazione] (https://www.home-assistant.io/components/ps4/) per ulteriori informazioni." + "port_987_bind_error": "Impossibile collegarsi alla porta 987. Fare riferimento alla [documentazione](https://www.home-assistant.io/components/ps4/) per ulteriori informazioni.", + "port_997_bind_error": "Impossibile collegarsi alla porta 997. Consultare la [documentazione](https://www.home-assistant.io/components/ps4/) per ulteriori informazioni." }, "error": { "credential_timeout": "Servizio credenziali scaduto. Premi Invia per riavviare.", diff --git a/homeassistant/components/sensor/translations/ca.json b/homeassistant/components/sensor/translations/ca.json index 7b6defd869b..7f8f7e56d6d 100644 --- a/homeassistant/components/sensor/translations/ca.json +++ b/homeassistant/components/sensor/translations/ca.json @@ -2,25 +2,25 @@ "device_automation": { "condition_type": { "is_battery_level": "Nivell de bateria actual de {entity_name}", - "is_humidity": "Humitat de {entity_name}", - "is_illuminance": "Il\u00b7luminaci\u00f3 de {entity_name}", - "is_power": "Pot\u00e8ncia de {entity_name}", - "is_pressure": "Pressi\u00f3 de {entity_name}", - "is_signal_strength": "For\u00e7a del senyal de {entity_name}", - "is_temperature": "Temperatura de {entity_name}", - "is_timestamp": "Marca de temps de {entity_name}", - "is_value": "Valor de {entity_name}" + "is_humidity": "Humitat actual de {entity_name}", + "is_illuminance": "Il\u00b7luminaci\u00f3 actual de {entity_name}", + "is_power": "Pot\u00e8ncia actual de {entity_name}", + "is_pressure": "Pressi\u00f3 actual de {entity_name}", + "is_signal_strength": "Pot\u00e8ncia de senyal actual de {entity_name}", + "is_temperature": "Temperatura actual de {entity_name}", + "is_timestamp": "Marca temporal actual de {entity_name}", + "is_value": "Valor actual de {entity_name}" }, "trigger_type": { - "battery_level": "Nivell de bateria de {entity_name}", - "humidity": "Humitat de {entity_name}", - "illuminance": "Il\u00b7luminaci\u00f3 de {entity_name}", - "power": "Pot\u00e8ncia de {entity_name}", - "pressure": "Pressi\u00f3 de {entity_name}", - "signal_strength": "For\u00e7a del senyal de {entity_name}", - "temperature": "Temperatura de {entity_name}", - "timestamp": "Marca de temps de {entity_name}", - "value": "Valor de {entity_name}" + "battery_level": "Canvia el nivell de bateria de {entity_name}", + "humidity": "Canvia la humitat de {entity_name}", + "illuminance": "Canvia la il\u00b7luminaci\u00f3 de {entity_name}", + "power": "Canvia la pot\u00e8ncia de {entity_name}", + "pressure": "Canvia la pressi\u00f3 de {entity_name}", + "signal_strength": "Canvia la pot\u00e8ncia de senyal de {entity_name}", + "temperature": "Canvia la temperatura de {entity_name}", + "timestamp": "Canvia la marca temporal de {entity_name}", + "value": "Canvia el valor de {entity_name}" } }, "state": { diff --git a/homeassistant/components/tellduslive/translations/it.json b/homeassistant/components/tellduslive/translations/it.json index 7776c23dbaa..177c22e5813 100644 --- a/homeassistant/components/tellduslive/translations/it.json +++ b/homeassistant/components/tellduslive/translations/it.json @@ -11,7 +11,7 @@ }, "step": { "auth": { - "description": "Per collegare il tuo account TelldusLive:\n 1. Clicca sul link sottostante\n 2. Accedi a Telldus Live\n 3. Autorizzare **{app_name}**** (cliccare **S\u00ec**).\n 4. Torna qui e clicca su **SUBMIT**.\n\n [Collega account TelldusLive]({auth_url})", + "description": "Per collegare il tuo account TelldusLive:\n 1. Clicca sul link sottostante\n 2. Accedi a Telldus Live\n 3. Autorizzare **{app_name}** (cliccare **S\u00ec**).\n 4. Torna qui e clicca su **SUBMIT**.\n\n [Link per account TelldusLive]({auth_url})", "title": "Autenticati con TelldusLive" }, "user": { diff --git a/homeassistant/components/toon/translations/it.json b/homeassistant/components/toon/translations/it.json index 62f4031660d..af7d61d0ef9 100644 --- a/homeassistant/components/toon/translations/it.json +++ b/homeassistant/components/toon/translations/it.json @@ -4,7 +4,7 @@ "client_id": "L'ID client dalla configurazione non \u00e8 valido.", "client_secret": "Il client segreto della configurazione non \u00e8 valido.", "no_agreements": "Questo account non ha display Toon.", - "no_app": "\u00c8 necessario configurare Toon prima di poter eseguire l'autenticazione con esso. [Si prega di leggere le istruzioni] (https://www.home-assistant.io/components/toon/).", + "no_app": "\u00c8 necessario configurare Toon prima di poter eseguire l'autenticazione con esso. [Si prega di leggere le istruzioni](https://www.home-assistant.io/components/toon/).", "unknown_auth_fail": "Si \u00e8 verificato un errore imprevisto durante l'autenticazione." }, "error": { diff --git a/homeassistant/components/twilio/translations/it.json b/homeassistant/components/twilio/translations/it.json index b50d82bfeb1..8bf92bf60a2 100644 --- a/homeassistant/components/twilio/translations/it.json +++ b/homeassistant/components/twilio/translations/it.json @@ -5,7 +5,7 @@ "one_instance_allowed": "\u00c8 necessaria una sola istanza." }, "create_entry": { - "default": "Per inviare eventi a Home Assistant, dovrai configurare [Webhooks con Twilio]({twilio_url})\n\n Compila le seguenti informazioni: \n\n - URL: ` {webhook_url} ` \n - Method: POST \n - Content Type: application/x-www-form-urlencoded\n\n Vedi [la documentazione]({docs_url}) su come configurare le automazioni per gestire i dati in arrivo." + "default": "Per inviare eventi a Home Assistant, dovrai configurare [Webhooks con Twilio]({twilio_url})\n\n Compila le seguenti informazioni: \n\n - URL: `{webhook_url}` \n - Method: POST \n - Content Type: application/x-www-form-urlencoded\n\n Vedi [la documentazione]({docs_url}) su come configurare le automazioni per gestire i dati in arrivo." }, "step": { "user": { diff --git a/homeassistant/components/unifi/translations/ca.json b/homeassistant/components/unifi/translations/ca.json index 82008f5c5b9..aaeecd9d51e 100644 --- a/homeassistant/components/unifi/translations/ca.json +++ b/homeassistant/components/unifi/translations/ca.json @@ -54,7 +54,7 @@ }, "statistics_sensors": { "data": { - "allow_bandwidth_sensors": "Crea sensors d'\u00fas d'ample de banda per a clients de la xarxa" + "allow_bandwidth_sensors": "Sensors d'utilitzaci\u00f3 d'ample de banda per a clients de la xarxa" }, "description": "Configuraci\u00f3 dels sensors d'estad\u00edstiques", "title": "Opcions d'UniFi 3/3" diff --git a/homeassistant/components/vacuum/translations/ca.json b/homeassistant/components/vacuum/translations/ca.json index 19147380fa0..5f8f234d808 100644 --- a/homeassistant/components/vacuum/translations/ca.json +++ b/homeassistant/components/vacuum/translations/ca.json @@ -21,8 +21,8 @@ "idle": "Inactiu", "off": "OFF", "on": "ON", - "paused": "Pausat", - "returning": "Retornant a la base" + "paused": "Pausat/da", + "returning": "Retornant a base" } }, "title": "Aspirador" diff --git a/homeassistant/components/vizio/translations/ca.json b/homeassistant/components/vizio/translations/ca.json index e5ccd1d4d6d..42d363c1d41 100644 --- a/homeassistant/components/vizio/translations/ca.json +++ b/homeassistant/components/vizio/translations/ca.json @@ -2,13 +2,13 @@ "config": { "abort": { "already_configured_device": "El dispositiu ja est\u00e0 configurat", - "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." + "updated_entry": "Aquesta entrada ja s'ha configurat per\u00f2 el nom, les aplicacions i/o les opcions definides a la configuraci\u00f3 no coincideixen amb els valors importats anteriorment, en conseq\u00fc\u00e8ncia, l'entrada de configuraci\u00f3 s'ha actualitzat." }, "error": { "cannot_connect": "No s'ha pogut connectar", "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." + "host_exists": "Dispositiu VIZIO SmartCast amb aquest nom d'amfitri\u00f3 ja configurat.", + "name_exists": "Dispositiu VIZIO SmartCast amb aquest nom ja configurat." }, "step": { "pair_tv": { @@ -19,11 +19,11 @@ "title": "Proc\u00e9s d'aparellament complet" }, "pairing_complete": { - "description": "El dispositiu Vizio SmartCast est\u00e0 connectat a Home Assistant.", + "description": "El Dispositiu VIZIO SmartCast est\u00e0 connectat a Home Assistant.", "title": "Emparellament completat" }, "pairing_complete_import": { - "description": "El dispositiu VIZIO SmartCast TV est\u00e0 connectat a Home Assistant.\n\nEl teu [%key::common::config_flow::data::access_token%] \u00e9s '**{access_token}**'.", + "description": "El Dispositiu VIZIO SmartCast est\u00e0 connectat a Home Assistant.\n\nEl teu [%key::common::config_flow::data::access_token%] \u00e9s '**{access_token}**'.", "title": "Emparellament completat" }, "user": { @@ -34,7 +34,7 @@ "name": "Nom" }, "description": "Nom\u00e9s es necessita el [%key::common::config_flow::data::access_token%] per als televisors. Si est\u00e0s configurant un televisor i encara no tens un [%key::common::config_flow::data::access_token%], deixa-ho en blanc per poder fer el proc\u00e9s d'emparellament.", - "title": "Configuraci\u00f3 del client de Vizio SmartCast" + "title": "Dispositiu VIZIO SmartCast" } } }, @@ -47,7 +47,7 @@ "volume_step": "Mida del pas de volum" }, "description": "Si tens una Smart TV, pots filtrar de manera opcional la teva llista de canals escollint quines aplicacions vols incloure o excloure de la llista.", - "title": "Actualitzaci\u00f3 de les opcions de Vizo SmartCast" + "title": "Actualitzaci\u00f3 de les opcions del Dispositiu VIZIO SmartCast" } } } diff --git a/homeassistant/components/vizio/translations/it.json b/homeassistant/components/vizio/translations/it.json index e175becd8c9..64f3d8ddd93 100644 --- a/homeassistant/components/vizio/translations/it.json +++ b/homeassistant/components/vizio/translations/it.json @@ -1,12 +1,14 @@ { "config": { "abort": { + "already_configured_device": "Dispositivo gi\u00e0 configurato", "updated_entry": "Questa voce \u00e8 gi\u00e0 stata configurata, ma il nome, le app e/o le opzioni definite nella configurazione non corrispondono alla configurazione importata in precedenza, pertanto la voce di configurazione \u00e8 stata aggiornata di conseguenza." }, "error": { + "cannot_connect": "Impossibile connettersi", "complete_pairing_failed": "Impossibile completare l'associazione. Assicurarsi che il PIN fornito sia corretto e che la TV sia ancora accesa e collegata alla rete prima di inviare nuovamente.", - "host_exists": "Dispositivo VIZIO con host specificato gi\u00e0 configurato.", - "name_exists": "Dispositivo VIZIO con il nome specificato gi\u00e0 configurato." + "host_exists": "Il Dispositivo SmartCast VIZIO con host specificato \u00e8 gi\u00e0 configurato.", + "name_exists": "Il Dispositivo SmartCast VIZIO con il nome specificato \u00e8 gi\u00e0 configurato." }, "step": { "pair_tv": { @@ -17,11 +19,11 @@ "title": "Processo di associazione completo" }, "pairing_complete": { - "description": "Il dispositivo VIZIO SmartCast \u00e8 ora connesso a Home Assistant.", + "description": "Il tuo Dispositivo SmartCast VIZIO \u00e8 ora connesso a Home Assistant.", "title": "Associazione completata" }, "pairing_complete_import": { - "description": "Il dispositivo VIZIO SmartCast TV \u00e8 ora connesso a Home Assistant. \n\nIl tuo Token di accesso \u00e8 '**{access_token}**'.", + "description": "Il tuo Dispositivo SmartCast VIZIO \u00e8 ora connesso a Home Assistant.\n\nIl tuo Token di accesso \u00e8 '**{access_token}**'.", "title": "Associazione completata" }, "user": { @@ -32,7 +34,7 @@ "name": "Nome" }, "description": "Un Token di accesso \u00e8 necessario solo per i televisori. Se si sta configurando un televisore e non si dispone ancora di un Token di accesso, lasciarlo vuoto per passare attraverso un processo di associazione.", - "title": "Configurazione del dispositivo SmartCast VIZIO" + "title": "Dispositivo SmartCast VIZIO" } } }, @@ -45,7 +47,7 @@ "volume_step": "Dimensione del passo del volume" }, "description": "Se si dispone di una Smart TV, \u00e8 possibile filtrare l'elenco di origine scegliendo le app da includere o escludere in esso.", - "title": "Aggiornamento delle opzioni di VIZIO SmartCast" + "title": "Aggiornamento delle Opzioni del Dispositivo SmartCast VIZIO" } } } diff --git a/homeassistant/components/vizio/translations/nl.json b/homeassistant/components/vizio/translations/nl.json index 42a69dc8d78..476f8265102 100644 --- a/homeassistant/components/vizio/translations/nl.json +++ b/homeassistant/components/vizio/translations/nl.json @@ -1,9 +1,11 @@ { "config": { "abort": { + "already_configured_device": "Dit apparaat is al geconfigureerd", "updated_entry": "Dit item is al ingesteld, maar de naam en/of opties die zijn gedefinieerd in de configuratie komen niet overeen met de eerder ge\u00efmporteerde configuratie, dus het configuratie-item is dienovereenkomstig bijgewerkt." }, "error": { + "cannot_connect": "Verbinding mislukt", "host_exists": "Vizio apparaat met opgegeven host al geconfigureerd.", "name_exists": "Vizio apparaat met opgegeven naam al geconfigureerd." }, diff --git a/homeassistant/components/withings/translations/ca.json b/homeassistant/components/withings/translations/ca.json index e98426446fc..40896dd7931 100644 --- a/homeassistant/components/withings/translations/ca.json +++ b/homeassistant/components/withings/translations/ca.json @@ -5,7 +5,7 @@ "missing_configuration": "La integraci\u00f3 Withings no est\u00e0 configurada. Mira'n la documentaci\u00f3." }, "create_entry": { - "default": "Autenticaci\u00f3 exitosa amb Withings per al perfil seleccionat." + "default": "Autenticaci\u00f3 exitosa amb Withings." }, "step": { "pick_implementation": { diff --git a/homeassistant/components/wled/translations/bg.json b/homeassistant/components/wled/translations/bg.json index 98c2c33f618..beb1bc0d6e6 100644 --- a/homeassistant/components/wled/translations/bg.json +++ b/homeassistant/components/wled/translations/bg.json @@ -1,23 +1,10 @@ { "config": { - "abort": { - "already_configured": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "connection_error": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" - }, - "error": { - "connection_error": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" - }, - "flow_title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", "step": { "user": { "data": { "host": "\u0410\u0434\u0440\u0435\u0441" - }, - "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" - }, - "zeroconf_confirm": { - "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" + } } } } diff --git a/homeassistant/components/wled/translations/da.json b/homeassistant/components/wled/translations/da.json index ec45027f3ab..4cf5ce622fa 100644 --- a/homeassistant/components/wled/translations/da.json +++ b/homeassistant/components/wled/translations/da.json @@ -1,23 +1,10 @@ { "config": { - "abort": { - "already_configured": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "connection_error": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" - }, - "error": { - "connection_error": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" - }, - "flow_title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", "step": { "user": { "data": { "host": "V\u00e6rt eller IP-adresse" - }, - "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" - }, - "zeroconf_confirm": { - "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" + } } } } diff --git a/homeassistant/components/wled/translations/es-419.json b/homeassistant/components/wled/translations/es-419.json index c6dd845b620..04a20091f50 100644 --- a/homeassistant/components/wled/translations/es-419.json +++ b/homeassistant/components/wled/translations/es-419.json @@ -1,23 +1,10 @@ { "config": { - "abort": { - "already_configured": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "connection_error": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" - }, - "error": { - "connection_error": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" - }, - "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%]" - }, - "zeroconf_confirm": { - "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" + } } } } diff --git a/homeassistant/components/wled/translations/fr.json b/homeassistant/components/wled/translations/fr.json index a48c466c10a..12e539cf755 100644 --- a/homeassistant/components/wled/translations/fr.json +++ b/homeassistant/components/wled/translations/fr.json @@ -1,23 +1,10 @@ { "config": { - "abort": { - "already_configured": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "connection_error": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" - }, - "error": { - "connection_error": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" - }, - "flow_title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", "step": { "user": { "data": { "host": "H\u00f4te ou adresse IP" - }, - "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" - }, - "zeroconf_confirm": { - "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" + } } } } diff --git a/homeassistant/components/wled/translations/nl.json b/homeassistant/components/wled/translations/nl.json index fd6f2ff68f8..794f68256a7 100644 --- a/homeassistant/components/wled/translations/nl.json +++ b/homeassistant/components/wled/translations/nl.json @@ -1,23 +1,10 @@ { "config": { - "abort": { - "already_configured": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "connection_error": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" - }, - "error": { - "connection_error": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" - }, - "flow_title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", "step": { "user": { "data": { "host": "Hostnaam of IP-adres" - }, - "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" - }, - "zeroconf_confirm": { - "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" + } } } } diff --git a/homeassistant/components/wled/translations/pt.json b/homeassistant/components/wled/translations/pt.json index 4738ca85983..a6e5cd46cbb 100644 --- a/homeassistant/components/wled/translations/pt.json +++ b/homeassistant/components/wled/translations/pt.json @@ -1,23 +1,10 @@ { "config": { - "abort": { - "already_configured": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "connection_error": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" - }, - "error": { - "connection_error": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" - }, - "flow_title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", "step": { "user": { "data": { "host": "Nome servidor ou endere\u00e7o IP" - }, - "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" - }, - "zeroconf_confirm": { - "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" + } } } } diff --git a/homeassistant/components/wled/translations/sv.json b/homeassistant/components/wled/translations/sv.json index f0be486f0c3..3c802a87007 100644 --- a/homeassistant/components/wled/translations/sv.json +++ b/homeassistant/components/wled/translations/sv.json @@ -1,23 +1,10 @@ { "config": { - "abort": { - "already_configured": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "connection_error": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" - }, - "error": { - "connection_error": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" - }, - "flow_title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", "step": { "user": { "data": { "host": "V\u00e4rd eller IP-adress" - }, - "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" - }, - "zeroconf_confirm": { - "description": "Wykryto urz\u0105dzenie [%key:component::wled::title%]", - "title": "Wykryto urz\u0105dzenie [%key:component::wled::title%]" + } } } } From 940249f45e4f5bf93161e167f0bd2573a259c385 Mon Sep 17 00:00:00 2001 From: Chris Talkington Date: Fri, 29 May 2020 19:08:05 -0500 Subject: [PATCH 255/406] Refactor Sonarr Integration (#33859) Co-authored-by: Paulus Schoutsen --- .coveragerc | 1 - homeassistant/components/sonarr/__init__.py | 165 +++- .../components/sonarr/config_flow.py | 145 +++ homeassistant/components/sonarr/const.py | 29 + homeassistant/components/sonarr/manifest.json | 5 +- homeassistant/components/sonarr/sensor.py | 627 +++++++++---- homeassistant/components/sonarr/strings.json | 37 + .../components/sonarr/translations/en.json | 37 + homeassistant/generated/config_flows.py | 1 + requirements_all.txt | 3 + requirements_test_all.txt | 3 + tests/components/sonarr/__init__.py | 218 ++++- tests/components/sonarr/test_config_flow.py | 183 ++++ tests/components/sonarr/test_init.py | 36 + tests/components/sonarr/test_sensor.py | 865 ++++-------------- tests/fixtures/sonarr/calendar.json | 116 +++ tests/fixtures/sonarr/command.json | 36 + tests/fixtures/sonarr/diskspace.json | 8 + tests/fixtures/sonarr/queue.json | 129 +++ tests/fixtures/sonarr/series.json | 163 ++++ tests/fixtures/sonarr/system-status.json | 18 + tests/fixtures/sonarr/wanted-missing.json | 253 +++++ 22 files changed, 2179 insertions(+), 899 deletions(-) create mode 100644 homeassistant/components/sonarr/config_flow.py create mode 100644 homeassistant/components/sonarr/const.py create mode 100644 homeassistant/components/sonarr/strings.json create mode 100644 homeassistant/components/sonarr/translations/en.json create mode 100644 tests/components/sonarr/test_config_flow.py create mode 100644 tests/components/sonarr/test_init.py create mode 100644 tests/fixtures/sonarr/calendar.json create mode 100644 tests/fixtures/sonarr/command.json create mode 100644 tests/fixtures/sonarr/diskspace.json create mode 100644 tests/fixtures/sonarr/queue.json create mode 100644 tests/fixtures/sonarr/series.json create mode 100644 tests/fixtures/sonarr/system-status.json create mode 100644 tests/fixtures/sonarr/wanted-missing.json diff --git a/.coveragerc b/.coveragerc index bd09465f643..7e0269784a4 100644 --- a/.coveragerc +++ b/.coveragerc @@ -726,7 +726,6 @@ omit = homeassistant/components/soma/__init__.py homeassistant/components/somfy/* homeassistant/components/somfy_mylink/* - homeassistant/components/sonarr/sensor.py homeassistant/components/sonos/* homeassistant/components/sony_projector/switch.py homeassistant/components/spc/* diff --git a/homeassistant/components/sonarr/__init__.py b/homeassistant/components/sonarr/__init__.py index 63c194cc969..4228d6c8400 100644 --- a/homeassistant/components/sonarr/__init__.py +++ b/homeassistant/components/sonarr/__init__.py @@ -1 +1,164 @@ -"""The sonarr component.""" +"""The Sonarr component.""" +import asyncio +from datetime import timedelta +from typing import Any, Dict + +from sonarr import Sonarr, SonarrError + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import ( + ATTR_NAME, + CONF_API_KEY, + CONF_HOST, + CONF_PORT, + CONF_SSL, + CONF_VERIFY_SSL, +) +from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.helpers.dispatcher import async_dispatcher_send +from homeassistant.helpers.entity import Entity +from homeassistant.helpers.typing import HomeAssistantType + +from .const import ( + ATTR_IDENTIFIERS, + ATTR_MANUFACTURER, + ATTR_SOFTWARE_VERSION, + CONF_BASE_PATH, + CONF_UPCOMING_DAYS, + CONF_WANTED_MAX_ITEMS, + DATA_SONARR, + DATA_UNDO_UPDATE_LISTENER, + DEFAULT_UPCOMING_DAYS, + DEFAULT_WANTED_MAX_ITEMS, + DOMAIN, +) + +PLATFORMS = ["sensor"] +SCAN_INTERVAL = timedelta(seconds=30) + + +async def async_setup(hass: HomeAssistantType, config: Dict) -> bool: + """Set up the Sonarr component.""" + hass.data.setdefault(DOMAIN, {}) + return True + + +async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool: + """Set up Sonarr from a config entry.""" + if not entry.options: + options = { + CONF_UPCOMING_DAYS: entry.data.get( + CONF_UPCOMING_DAYS, DEFAULT_UPCOMING_DAYS + ), + CONF_WANTED_MAX_ITEMS: entry.data.get( + CONF_WANTED_MAX_ITEMS, DEFAULT_WANTED_MAX_ITEMS + ), + } + hass.config_entries.async_update_entry(entry, options=options) + + sonarr = Sonarr( + host=entry.data[CONF_HOST], + port=entry.data[CONF_PORT], + api_key=entry.data[CONF_API_KEY], + base_path=entry.data[CONF_BASE_PATH], + session=async_get_clientsession(hass), + tls=entry.data[CONF_SSL], + verify_ssl=entry.data[CONF_VERIFY_SSL], + ) + + try: + await sonarr.update() + except SonarrError: + raise ConfigEntryNotReady + + undo_listener = entry.add_update_listener(_async_update_listener) + + hass.data[DOMAIN][entry.entry_id] = { + DATA_SONARR: sonarr, + DATA_UNDO_UPDATE_LISTENER: undo_listener, + } + + for component in PLATFORMS: + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(entry, component) + ) + + return True + + +async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool: + """Unload a config entry.""" + unload_ok = all( + await asyncio.gather( + *[ + hass.config_entries.async_forward_entry_unload(entry, component) + for component in PLATFORMS + ] + ) + ) + + hass.data[DOMAIN][entry.entry_id][DATA_UNDO_UPDATE_LISTENER]() + + if unload_ok: + hass.data[DOMAIN].pop(entry.entry_id) + + return unload_ok + + +async def _async_update_listener(hass: HomeAssistantType, entry: ConfigEntry) -> None: + """Handle options update.""" + async_dispatcher_send( + hass, f"sonarr.{entry.entry_id}.entry_options_update", entry.options + ) + + +class SonarrEntity(Entity): + """Defines a base Sonarr entity.""" + + def __init__( + self, + *, + sonarr: Sonarr, + entry_id: str, + device_id: str, + name: str, + icon: str, + enabled_default: bool = True, + ) -> None: + """Initialize the Sonar entity.""" + self._entry_id = entry_id + self._device_id = device_id + self._enabled_default = enabled_default + self._icon = icon + self._name = name + self.sonarr = sonarr + + @property + def name(self) -> str: + """Return the name of the entity.""" + return self._name + + @property + def icon(self) -> str: + """Return the mdi icon of the entity.""" + return self._icon + + @property + def entity_registry_enabled_default(self) -> bool: + """Return if the entity should be enabled when first added to the entity registry.""" + return self._enabled_default + + @property + def device_info(self) -> Dict[str, Any]: + """Return device information about the application.""" + if self._device_id is None: + return None + + return { + ATTR_IDENTIFIERS: {(DOMAIN, self._device_id)}, + ATTR_NAME: "Activity Sensor", + ATTR_MANUFACTURER: "Sonarr", + ATTR_SOFTWARE_VERSION: self.sonarr.app.info.version, + "entry_type": "service", + } diff --git a/homeassistant/components/sonarr/config_flow.py b/homeassistant/components/sonarr/config_flow.py new file mode 100644 index 00000000000..e82ecb49fda --- /dev/null +++ b/homeassistant/components/sonarr/config_flow.py @@ -0,0 +1,145 @@ +"""Config flow for Sonarr.""" +import logging +from typing import Any, Dict, Optional + +from sonarr import Sonarr, SonarrAccessRestricted, SonarrError +import voluptuous as vol + +from homeassistant.config_entries import CONN_CLASS_LOCAL_POLL, ConfigFlow, OptionsFlow +from homeassistant.const import ( + CONF_API_KEY, + CONF_HOST, + CONF_PORT, + CONF_SSL, + CONF_VERIFY_SSL, +) +from homeassistant.core import callback +from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.helpers.typing import ConfigType, HomeAssistantType + +from .const import ( + CONF_BASE_PATH, + CONF_UPCOMING_DAYS, + CONF_WANTED_MAX_ITEMS, + DEFAULT_BASE_PATH, + DEFAULT_PORT, + DEFAULT_SSL, + DEFAULT_UPCOMING_DAYS, + DEFAULT_VERIFY_SSL, + DEFAULT_WANTED_MAX_ITEMS, +) +from .const import DOMAIN # pylint: disable=unused-import + +_LOGGER = logging.getLogger(__name__) + + +async def validate_input(hass: HomeAssistantType, data: dict) -> Dict[str, Any]: + """Validate the user input allows us to connect. + + Data has the keys from DATA_SCHEMA with values provided by the user. + """ + session = async_get_clientsession(hass) + + sonarr = Sonarr( + host=data[CONF_HOST], + port=data[CONF_PORT], + api_key=data[CONF_API_KEY], + base_path=data[CONF_BASE_PATH], + tls=data[CONF_SSL], + verify_ssl=data[CONF_VERIFY_SSL], + session=session, + ) + + await sonarr.update() + + return True + + +class SonarrConfigFlow(ConfigFlow, domain=DOMAIN): + """Handle a config flow for Sonarr.""" + + VERSION = 1 + CONNECTION_CLASS = CONN_CLASS_LOCAL_POLL + + @staticmethod + @callback + def async_get_options_flow(config_entry): + """Get the options flow for this handler.""" + return SonarrOptionsFlowHandler(config_entry) + + async def async_step_import( + self, user_input: Optional[ConfigType] = None + ) -> Dict[str, Any]: + """Handle a flow initiated by configuration file.""" + return await self.async_step_user(user_input) + + async def async_step_user( + self, user_input: Optional[ConfigType] = None + ) -> Dict[str, Any]: + """Handle a flow initiated by the user.""" + if user_input is None: + return self._show_setup_form() + + if CONF_VERIFY_SSL not in user_input: + user_input[CONF_VERIFY_SSL] = DEFAULT_VERIFY_SSL + + try: + await validate_input(self.hass, user_input) + except SonarrAccessRestricted: + return self._show_setup_form({"base": "invalid_auth"}) + except SonarrError: + return self._show_setup_form({"base": "cannot_connect"}) + except Exception: # pylint: disable=broad-except + _LOGGER.exception("Unexpected exception") + return self.async_abort(reason="unknown") + + return self.async_create_entry(title=user_input[CONF_HOST], data=user_input) + + def _show_setup_form(self, errors: Optional[Dict] = None) -> Dict[str, Any]: + """Show the setup form to the user.""" + data_schema = { + vol.Required(CONF_HOST): str, + vol.Required(CONF_API_KEY): str, + vol.Optional(CONF_BASE_PATH, default=DEFAULT_BASE_PATH): str, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): int, + vol.Optional(CONF_SSL, default=DEFAULT_SSL): bool, + } + + if self.show_advanced_options: + data_schema[ + vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL) + ] = bool + + return self.async_show_form( + step_id="user", data_schema=vol.Schema(data_schema), errors=errors or {}, + ) + + +class SonarrOptionsFlowHandler(OptionsFlow): + """Handle Sonarr client options.""" + + def __init__(self, config_entry): + """Initialize options flow.""" + self.config_entry = config_entry + + async def async_step_init(self, user_input: Optional[ConfigType] = None): + """Manage Sonarr options.""" + if user_input is not None: + return self.async_create_entry(title="", data=user_input) + + options = { + vol.Optional( + CONF_UPCOMING_DAYS, + default=self.config_entry.options.get( + CONF_UPCOMING_DAYS, DEFAULT_UPCOMING_DAYS + ), + ): int, + vol.Optional( + CONF_WANTED_MAX_ITEMS, + default=self.config_entry.options.get( + CONF_WANTED_MAX_ITEMS, DEFAULT_WANTED_MAX_ITEMS + ), + ): int, + } + + return self.async_show_form(step_id="init", data_schema=vol.Schema(options)) diff --git a/homeassistant/components/sonarr/const.py b/homeassistant/components/sonarr/const.py new file mode 100644 index 00000000000..52079a9416c --- /dev/null +++ b/homeassistant/components/sonarr/const.py @@ -0,0 +1,29 @@ +"""Constants for Sonarr.""" +DOMAIN = "sonarr" + +# Attributes +ATTR_IDENTIFIERS = "identifiers" +ATTR_MANUFACTURER = "manufacturer" +ATTR_SOFTWARE_VERSION = "sw_version" + +# Config Keys +CONF_BASE_PATH = "base_path" +CONF_DAYS = "days" +CONF_INCLUDED = "include_paths" +CONF_UNIT = "unit" +CONF_UPCOMING_DAYS = "upcoming_days" +CONF_URLBASE = "urlbase" +CONF_WANTED_MAX_ITEMS = "wanted_max_items" + +# Data +DATA_SONARR = "sonarr" +DATA_UNDO_UPDATE_LISTENER = "undo_update_listener" + +# Defaults +DEFAULT_BASE_PATH = "/api" +DEFAULT_HOST = "localhost" +DEFAULT_PORT = 8989 +DEFAULT_SSL = False +DEFAULT_UPCOMING_DAYS = 1 +DEFAULT_VERIFY_SSL = False +DEFAULT_WANTED_MAX_ITEMS = 50 diff --git a/homeassistant/components/sonarr/manifest.json b/homeassistant/components/sonarr/manifest.json index 2fe375fb1df..61c30102e34 100644 --- a/homeassistant/components/sonarr/manifest.json +++ b/homeassistant/components/sonarr/manifest.json @@ -2,5 +2,8 @@ "domain": "sonarr", "name": "Sonarr", "documentation": "https://www.home-assistant.io/integrations/sonarr", - "codeowners": ["@ctalkington"] + "codeowners": ["@ctalkington"], + "requirements": ["sonarr==0.2.1"], + "config_flow": true, + "quality_scale": "silver" } diff --git a/homeassistant/components/sonarr/sensor.py b/homeassistant/components/sonarr/sensor.py index 65513db3571..f1945f26836 100644 --- a/homeassistant/components/sonarr/sensor.py +++ b/homeassistant/components/sonarr/sensor.py @@ -1,17 +1,20 @@ -"""Support for Sonarr.""" +"""Support for Sonarr sensors.""" from datetime import timedelta import logging +from typing import Any, Callable, Dict, List, Optional, Union -import requests +from sonarr import Sonarr, SonarrConnectionError, SonarrError import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import ( CONF_API_KEY, CONF_HOST, CONF_MONITORED_CONDITIONS, CONF_PORT, CONF_SSL, + CONF_VERIFY_SSL, DATA_BYTES, DATA_EXABYTES, DATA_GIGABYTES, @@ -21,46 +24,32 @@ from homeassistant.const import ( DATA_TERABYTES, DATA_YOTTABYTES, DATA_ZETTABYTES, - HTTP_OK, ) import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import Entity +from homeassistant.helpers.typing import ConfigType, HomeAssistantType import homeassistant.util.dt as dt_util +from . import SonarrEntity +from .const import ( + CONF_BASE_PATH, + CONF_DAYS, + CONF_INCLUDED, + CONF_UNIT, + CONF_UPCOMING_DAYS, + CONF_URLBASE, + CONF_WANTED_MAX_ITEMS, + DATA_SONARR, + DEFAULT_BASE_PATH, + DEFAULT_HOST, + DEFAULT_PORT, + DEFAULT_SSL, + DOMAIN, +) + _LOGGER = logging.getLogger(__name__) -CONF_DAYS = "days" -CONF_INCLUDED = "include_paths" -CONF_UNIT = "unit" -CONF_URLBASE = "urlbase" - -DEFAULT_HOST = "localhost" -DEFAULT_PORT = 8989 -DEFAULT_URLBASE = "" -DEFAULT_DAYS = "1" -DEFAULT_UNIT = DATA_GIGABYTES - -SENSOR_TYPES = { - "diskspace": ["Disk Space", DATA_GIGABYTES, "mdi:harddisk"], - "queue": ["Queue", "Episodes", "mdi:download"], - "upcoming": ["Upcoming", "Episodes", "mdi:television"], - "wanted": ["Wanted", "Episodes", "mdi:television"], - "series": ["Series", "Shows", "mdi:television"], - "commands": ["Commands", "Commands", "mdi:code-braces"], - "status": ["Status", "Status", "mdi:information"], -} - -ENDPOINTS = { - "diskspace": "{0}://{1}:{2}/{3}api/diskspace", - "queue": "{0}://{1}:{2}/{3}api/queue", - "upcoming": "{0}://{1}:{2}/{3}api/calendar?start={4}&end={5}", - "wanted": "{0}://{1}:{2}/{3}api/wanted/missing", - "series": "{0}://{1}:{2}/{3}api/series", - "commands": "{0}://{1}:{2}/{3}api/command", - "status": "{0}://{1}:{2}/{3}api/system/status", -} - -# Support to Yottabytes for the future, why not BYTE_SIZES = [ DATA_BYTES, DATA_KILOBYTES, @@ -72,198 +61,430 @@ BYTE_SIZES = [ DATA_ZETTABYTES, DATA_YOTTABYTES, ] -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( - { - vol.Required(CONF_API_KEY): cv.string, - vol.Optional(CONF_DAYS, default=DEFAULT_DAYS): cv.string, - vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, - vol.Optional(CONF_INCLUDED, default=[]): cv.ensure_list, - vol.Optional(CONF_MONITORED_CONDITIONS, default=["upcoming"]): vol.All( - cv.ensure_list, [vol.In(list(SENSOR_TYPES))] - ), - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, - vol.Optional(CONF_SSL, default=False): cv.boolean, - vol.Optional(CONF_UNIT, default=DEFAULT_UNIT): vol.In(BYTE_SIZES), - vol.Optional(CONF_URLBASE, default=DEFAULT_URLBASE): cv.string, - } + +DEFAULT_URLBASE = "" +DEFAULT_DAYS = "1" +DEFAULT_UNIT = DATA_GIGABYTES + +PLATFORM_SCHEMA = vol.All( + cv.deprecated(CONF_INCLUDED, invalidation_version="0.112"), + cv.deprecated(CONF_MONITORED_CONDITIONS, invalidation_version="0.112"), + cv.deprecated(CONF_UNIT, invalidation_version="0.112"), + PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_API_KEY): cv.string, + vol.Optional(CONF_DAYS, default=DEFAULT_DAYS): cv.string, + vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, + vol.Optional(CONF_INCLUDED, default=[]): cv.ensure_list, + vol.Optional(CONF_MONITORED_CONDITIONS, default=[]): cv.ensure_list, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean, + vol.Optional(CONF_UNIT, default=DEFAULT_UNIT): vol.In(BYTE_SIZES), + vol.Optional(CONF_URLBASE, default=DEFAULT_URLBASE): cv.string, + } + ), ) -def setup_platform(hass, config, add_entities, discovery_info=None): - """Set up the Sonarr platform.""" - conditions = config.get(CONF_MONITORED_CONDITIONS) - add_entities([SonarrSensor(config, sensor) for sensor in conditions], True) +async def async_setup_platform( + hass: HomeAssistantType, + config: ConfigType, + async_add_entities: Callable[[List[Entity], bool], None], + discovery_info: Any = None, +) -> None: + """Import the platform into a config entry.""" + if len(hass.config_entries.async_entries(DOMAIN)) > 0: + return True + + config[CONF_BASE_PATH] = f"{config[CONF_URLBASE]}{DEFAULT_BASE_PATH}" + config[CONF_UPCOMING_DAYS] = int(config[CONF_DAYS]) + config[CONF_VERIFY_SSL] = False + + del config[CONF_DAYS] + del config[CONF_INCLUDED] + del config[CONF_MONITORED_CONDITIONS] + del config[CONF_URLBASE] + + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_IMPORT}, data=config + ) + ) -class SonarrSensor(Entity): +async def async_setup_entry( + hass: HomeAssistantType, + entry: ConfigEntry, + async_add_entities: Callable[[List[Entity], bool], None], +) -> None: + """Set up Sonarr sensors based on a config entry.""" + options = entry.options + sonarr = hass.data[DOMAIN][entry.entry_id][DATA_SONARR] + + entities = [ + SonarrCommandsSensor(sonarr, entry.entry_id), + SonarrDiskspaceSensor(sonarr, entry.entry_id), + SonarrQueueSensor(sonarr, entry.entry_id), + SonarrSeriesSensor(sonarr, entry.entry_id), + SonarrUpcomingSensor(sonarr, entry.entry_id, days=options[CONF_UPCOMING_DAYS]), + SonarrWantedSensor( + sonarr, entry.entry_id, max_items=options[CONF_WANTED_MAX_ITEMS] + ), + ] + + async_add_entities(entities, True) + + +def sonarr_exception_handler(func): + """Decorate Sonarr calls to handle Sonarr exceptions. + + A decorator that wraps the passed in function, catches Sonarr errors, + and handles the availability of the entity. + """ + + async def handler(self, *args, **kwargs): + try: + await func(self, *args, **kwargs) + self.last_update_success = True + except SonarrConnectionError as error: + if self.available: + _LOGGER.error("Error communicating with API: %s", error) + self.last_update_success = False + except SonarrError as error: + if self.available: + _LOGGER.error("Invalid response from API: %s", error) + self.last_update_success = False + + return handler + + +class SonarrSensor(SonarrEntity): """Implementation of the Sonarr sensor.""" - def __init__(self, conf, sensor_type): - """Create Sonarr entity.""" + def __init__( + self, + *, + sonarr: Sonarr, + entry_id: str, + enabled_default: bool = True, + icon: str, + key: str, + name: str, + unit_of_measurement: Optional[str] = None, + ) -> None: + """Initialize Sonarr sensor.""" + self._unit_of_measurement = unit_of_measurement + self._key = key + self._unique_id = f"{entry_id}_{key}" + self.last_update_success = False - self.conf = conf - self.host = conf.get(CONF_HOST) - self.port = conf.get(CONF_PORT) - self.urlbase = conf.get(CONF_URLBASE) - if self.urlbase: - self.urlbase = "{}/".format(self.urlbase.strip("/")) - self.apikey = conf.get(CONF_API_KEY) - self.included = conf.get(CONF_INCLUDED) - self.days = int(conf.get(CONF_DAYS)) - self.ssl = "https" if conf.get(CONF_SSL) else "http" - self._state = None - self.data = [] - self.type = sensor_type - self._name = SENSOR_TYPES[self.type][0] - if self.type == "diskspace": - self._unit = conf.get(CONF_UNIT) - else: - self._unit = SENSOR_TYPES[self.type][1] - self._icon = SENSOR_TYPES[self.type][2] - self._available = False + super().__init__( + sonarr=sonarr, + entry_id=entry_id, + device_id=entry_id, + name=name, + icon=icon, + enabled_default=enabled_default, + ) @property - def name(self): - """Return the name of the sensor.""" - return "{} {}".format("Sonarr", self._name) + def unique_id(self) -> str: + """Return the unique ID for this sensor.""" + return self._unique_id @property - def state(self): - """Return sensor state.""" - return self._state - - @property - def available(self): + def available(self) -> bool: """Return sensor availability.""" - return self._available + return self.last_update_success @property - def unit_of_measurement(self): - """Return the unit of the sensor.""" - return self._unit + def unit_of_measurement(self) -> str: + """Return the unit this state is expressed in.""" + return self._unit_of_measurement + + +class SonarrCommandsSensor(SonarrSensor): + """Defines a Sonarr Commands sensor.""" + + def __init__(self, sonarr: Sonarr, entry_id: str) -> None: + """Initialize Sonarr Commands sensor.""" + self._commands = [] + + super().__init__( + sonarr=sonarr, + entry_id=entry_id, + icon="mdi:code-braces", + key="commands", + name=f"{sonarr.app.info.app_name} Commands", + unit_of_measurement="Commands", + enabled_default=False, + ) + + @sonarr_exception_handler + async def async_update(self) -> None: + """Update entity.""" + self._commands = await self.sonarr.commands() @property - def device_state_attributes(self): - """Return the state attributes of the sensor.""" - attributes = {} - if self.type == "upcoming": - for show in self.data: - if show["series"]["title"] in attributes: - continue + def device_state_attributes(self) -> Optional[Dict[str, Any]]: + """Return the state attributes of the entity.""" + attrs = {} - attributes[show["series"]["title"]] = "S{:02d}E{:02d}".format( - show["seasonNumber"], show["episodeNumber"] - ) - elif self.type == "queue": - for show in self.data: - remaining = 1 if show["size"] == 0 else show["sizeleft"] / show["size"] - attributes[ - show["series"]["title"] - + " S{:02d}E{:02d}".format( - show["episode"]["seasonNumber"], - show["episode"]["episodeNumber"], - ) - ] = "{:.2f}%".format(100 * (1 - (remaining))) - elif self.type == "wanted": - for show in self.data: - attributes[ - show["series"]["title"] - + " S{:02d}E{:02d}".format( - show["seasonNumber"], show["episodeNumber"] - ) - ] = show["airDate"] - elif self.type == "commands": - for command in self.data: - attributes[command["name"]] = command["state"] - elif self.type == "diskspace": - for data in self.data: - attributes[data["path"]] = "{:.2f}/{:.2f}{} ({:.2f}%)".format( - to_unit(data["freeSpace"], self._unit), - to_unit(data["totalSpace"], self._unit), - self._unit, - ( - to_unit(data["freeSpace"], self._unit) - / to_unit(data["totalSpace"], self._unit) - * 100 - ), - ) - elif self.type == "series": - for show in self.data: - if "episodeFileCount" not in show or "episodeCount" not in show: - attributes[show["title"]] = "N/A" - else: - attributes[show["title"]] = "{}/{} Episodes".format( - show["episodeFileCount"], show["episodeCount"] - ) - elif self.type == "status": - attributes = self.data - return attributes + for command in self._commands: + attrs[command.name] = command.state + + return attrs @property - def icon(self): - """Return the icon of the sensor.""" - return self._icon + def state(self) -> Union[None, str, int, float]: + """Return the state of the sensor.""" + return len(self._commands) - def update(self): - """Update the data for the sensor.""" + +class SonarrDiskspaceSensor(SonarrSensor): + """Defines a Sonarr Disk Space sensor.""" + + def __init__(self, sonarr: Sonarr, entry_id: str) -> None: + """Initialize Sonarr Disk Space sensor.""" + self._disks = [] + self._total_free = 0 + + super().__init__( + sonarr=sonarr, + entry_id=entry_id, + icon="mdi:harddisk", + key="diskspace", + name=f"{sonarr.app.info.app_name} Disk Space", + unit_of_measurement=DATA_GIGABYTES, + enabled_default=False, + ) + + def _to_unit(self, value): + """Return a value converted to unit of measurement.""" + return value / 1024 ** BYTE_SIZES.index(self._unit_of_measurement) + + @sonarr_exception_handler + async def async_update(self) -> None: + """Update entity.""" + app = await self.sonarr.update() + self._disks = app.disks + self._total_free = sum([disk.free for disk in self._disks]) + + @property + def device_state_attributes(self) -> Optional[Dict[str, Any]]: + """Return the state attributes of the entity.""" + attrs = {} + + for disk in self._disks: + free = self._to_unit(disk.free) + total = self._to_unit(disk.total) + usage = free / total * 100 + + attrs[ + disk.path + ] = f"{free:.2f}/{total:.2f}{self._unit_of_measurement} ({usage:.2f}%)" + + return attrs + + @property + def state(self) -> Union[None, str, int, float]: + """Return the state of the sensor.""" + free = self._to_unit(self._total_free) + return f"{free:.2f}" + + +class SonarrQueueSensor(SonarrSensor): + """Defines a Sonarr Queue sensor.""" + + def __init__(self, sonarr: Sonarr, entry_id: str) -> None: + """Initialize Sonarr Queue sensor.""" + self._queue = [] + + super().__init__( + sonarr=sonarr, + entry_id=entry_id, + icon="mdi:download", + key="queue", + name=f"{sonarr.app.info.app_name} Queue", + unit_of_measurement="Episodes", + enabled_default=False, + ) + + @sonarr_exception_handler + async def async_update(self) -> None: + """Update entity.""" + self._queue = await self.sonarr.queue() + + @property + def device_state_attributes(self) -> Optional[Dict[str, Any]]: + """Return the state attributes of the entity.""" + attrs = {} + + for item in self._queue: + remaining = 1 if item.size == 0 else item.size_remaining / item.size + remaining_pct = 100 * (1 - remaining) + name = f"{item.episode.series.title} {item.episode.identifier}" + attrs[name] = f"{remaining_pct:.2f}%" + + return attrs + + @property + def state(self) -> Union[None, str, int, float]: + """Return the state of the sensor.""" + return len(self._queue) + + +class SonarrSeriesSensor(SonarrSensor): + """Defines a Sonarr Series sensor.""" + + def __init__(self, sonarr: Sonarr, entry_id: str) -> None: + """Initialize Sonarr Series sensor.""" + self._items = [] + + super().__init__( + sonarr=sonarr, + entry_id=entry_id, + icon="mdi:television", + key="series", + name=f"{sonarr.app.info.app_name} Shows", + unit_of_measurement="Series", + enabled_default=False, + ) + + @sonarr_exception_handler + async def async_update(self) -> None: + """Update entity.""" + self._items = await self.sonarr.series() + + @property + def device_state_attributes(self) -> Optional[Dict[str, Any]]: + """Return the state attributes of the entity.""" + attrs = {} + + for item in self._items: + attrs[item.series.title] = f"{item.downloaded}/{item.episodes} Episodes" + + return attrs + + @property + def state(self) -> Union[None, str, int, float]: + """Return the state of the sensor.""" + return len(self._items) + + +class SonarrUpcomingSensor(SonarrSensor): + """Defines a Sonarr Upcoming sensor.""" + + def __init__(self, sonarr: Sonarr, entry_id: str, days: int = 1) -> None: + """Initialize Sonarr Upcoming sensor.""" + self._days = days + self._upcoming = [] + + super().__init__( + sonarr=sonarr, + entry_id=entry_id, + icon="mdi:television", + key="upcoming", + name=f"{sonarr.app.info.app_name} Upcoming", + unit_of_measurement="Episodes", + ) + + async def async_added_to_hass(self): + """Listen for signals.""" + await super().async_added_to_hass() + + self.async_on_remove( + async_dispatcher_connect( + self.hass, + f"sonarr.{self._entry_id}.entry_options_update", + self.async_update_entry_options, + ) + ) + + @sonarr_exception_handler + async def async_update(self) -> None: + """Update entity.""" 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( - ENDPOINTS[self.type].format( - self.ssl, - self.host, - self.port, - self.urlbase, - start.isoformat().replace("+00:00", "Z"), - end.isoformat().replace("+00:00", "Z"), - ), - headers={"X-Api-Key": self.apikey}, - timeout=10, + end = start + timedelta(days=self._days) + self._upcoming = await self.sonarr.calendar( + start=start.isoformat(), end=end.isoformat() + ) + + async def async_update_entry_options(self, options: dict) -> None: + """Update sensor settings when config entry options are update.""" + self._days = options[CONF_UPCOMING_DAYS] + + @property + def device_state_attributes(self) -> Optional[Dict[str, Any]]: + """Return the state attributes of the entity.""" + attrs = {} + + for episode in self._upcoming: + attrs[episode.series.title] = episode.identifier + + return attrs + + @property + def state(self) -> Union[None, str, int, float]: + """Return the state of the sensor.""" + return len(self._upcoming) + + +class SonarrWantedSensor(SonarrSensor): + """Defines a Sonarr Wanted sensor.""" + + def __init__(self, sonarr: Sonarr, entry_id: str, max_items: int = 10) -> None: + """Initialize Sonarr Wanted sensor.""" + self._max_items = max_items + self._results = None + self._total = None + + super().__init__( + sonarr=sonarr, + entry_id=entry_id, + icon="mdi:television", + key="wanted", + name=f"{sonarr.app.info.app_name} Wanted", + unit_of_measurement="Episodes", + enabled_default=False, + ) + + async def async_added_to_hass(self): + """Listen for signals.""" + await super().async_added_to_hass() + + self.async_on_remove( + async_dispatcher_connect( + self.hass, + f"sonarr.{self._entry_id}.entry_options_update", + self.async_update_entry_options, ) - except OSError: - _LOGGER.warning("Host %s is not available", self.host) - self._available = False - self._state = None - return + ) - if res.status_code == HTTP_OK: - if self.type in ["upcoming", "queue", "series", "commands"]: - self.data = res.json() - self._state = len(self.data) - elif self.type == "wanted": - data = res.json() - res = requests.get( - "{}?pageSize={}".format( - ENDPOINTS[self.type].format( - self.ssl, self.host, self.port, self.urlbase - ), - data["totalRecords"], - ), - headers={"X-Api-Key": self.apikey}, - timeout=10, - ) - self.data = res.json()["records"] - self._state = len(self.data) - elif self.type == "diskspace": - # If included paths are not provided, use all data - if self.included == []: - self.data = res.json() - else: - # Filter to only show lists that are included - self.data = list( - filter(lambda x: x["path"] in self.included, res.json()) - ) - self._state = "{:.2f}".format( - to_unit(sum([data["freeSpace"] for data in self.data]), self._unit) - ) - elif self.type == "status": - self.data = res.json() - self._state = self.data["version"] - self._available = True + @sonarr_exception_handler + async def async_update(self) -> None: + """Update entity.""" + self._results = await self.sonarr.wanted(page_size=self._max_items) + self._total = self._results.total + async def async_update_entry_options(self, options: dict) -> None: + """Update sensor settings when config entry options are update.""" + self._max_items = options[CONF_WANTED_MAX_ITEMS] -def to_unit(value, unit): - """Convert bytes to give unit.""" - return value / 1024 ** BYTE_SIZES.index(unit) + @property + def device_state_attributes(self) -> Optional[Dict[str, Any]]: + """Return the state attributes of the entity.""" + attrs = {} + + if self._results is not None: + for episode in self._results.episodes: + name = f"{episode.series.title} {episode.identifier}" + attrs[name] = episode.airdate + + return attrs + + @property + def state(self) -> Union[None, str, int, float]: + """Return the state of the sensor.""" + return self._total diff --git a/homeassistant/components/sonarr/strings.json b/homeassistant/components/sonarr/strings.json new file mode 100644 index 00000000000..481a3d381f0 --- /dev/null +++ b/homeassistant/components/sonarr/strings.json @@ -0,0 +1,37 @@ +{ + "title": "Sonarr", + "config": { + "flow_title": "Sonarr: {name}", + "step": { + "user": { + "title": "Connect to Sonarr", + "data": { + "host": "[%key:common::config_flow::data::host%]", + "api_key": "[%key:common::config_flow::data::api_key%]", + "base_path": "Path to API", + "port": "[%key:common::config_flow::data::port%]", + "ssl": "Sonarr uses a SSL certificate", + "verify_ssl": "Sonarr uses a proper certificate" + } + } + }, + "error": { + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]" + }, + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_service%]", + "unknown": "[%key:common::config_flow::error::unknown%]" + } + }, + "options": { + "step": { + "init": { + "data": { + "upcoming_days": "Number of upcoming days to display", + "wanted_max_items": "Max number of wanted items to display" + } + } + } + } +} diff --git a/homeassistant/components/sonarr/translations/en.json b/homeassistant/components/sonarr/translations/en.json new file mode 100644 index 00000000000..d60565ee19d --- /dev/null +++ b/homeassistant/components/sonarr/translations/en.json @@ -0,0 +1,37 @@ +{ + "config": { + "abort": { + "already_configured": "Sonarr is already configured", + "unknown": "Unexpected error" + }, + "error": { + "cannot_connect": "Failed to connect, please try again", + "invalid_auth": "Invalid authentication" + }, + "flow_title": "Sonarr: {name}", + "step": { + "user": { + "data": { + "api_key": "API Key", + "base_path": "Path to API", + "host": "Host or IP address", + "port": "Port", + "ssl": "Sonarr uses a SSL certificate", + "verify_ssl": "Sonarr uses a proper certificate" + }, + "title": "Connect to Sonarr" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "upcoming_days": "Number of upcoming days to display", + "wanted_max_items": "Max number of wanted items to display" + } + } + } + }, + "title": "Sonarr" +} \ No newline at end of file diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index b0309482205..65bfd461bc1 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -135,6 +135,7 @@ FLOWS = [ "solarlog", "soma", "somfy", + "sonarr", "songpal", "sonos", "spotify", diff --git a/requirements_all.txt b/requirements_all.txt index 9381750ab56..bbccd9de4aa 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1992,6 +1992,9 @@ somecomfort==0.5.2 # homeassistant.components.somfy_mylink somfy-mylink-synergy==1.0.6 +# homeassistant.components.sonarr +sonarr==0.2.1 + # homeassistant.components.marytts speak2mary==1.4.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 466d0ea99ed..436374f28a7 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -809,6 +809,9 @@ solaredge==0.0.2 # homeassistant.components.honeywell somecomfort==0.5.2 +# homeassistant.components.sonarr +sonarr==0.2.1 + # homeassistant.components.marytts speak2mary==1.4.0 diff --git a/tests/components/sonarr/__init__.py b/tests/components/sonarr/__init__.py index 573575cedc0..49a092c97e7 100644 --- a/tests/components/sonarr/__init__.py +++ b/tests/components/sonarr/__init__.py @@ -1 +1,217 @@ -"""Tests for the sonarr component.""" +"""Tests for the Sonarr component.""" +from socket import gaierror as SocketGIAError + +from homeassistant.components.sonarr.const import ( + CONF_BASE_PATH, + CONF_UPCOMING_DAYS, + CONF_WANTED_MAX_ITEMS, + DEFAULT_UPCOMING_DAYS, + DEFAULT_WANTED_MAX_ITEMS, + DOMAIN, +) +from homeassistant.const import ( + CONF_API_KEY, + CONF_HOST, + CONF_PORT, + CONF_SSL, + CONF_VERIFY_SSL, +) +from homeassistant.helpers.typing import HomeAssistantType + +from tests.common import MockConfigEntry, load_fixture +from tests.test_util.aiohttp import AiohttpClientMocker + +HOST = "192.168.1.189" +PORT = 8989 +BASE_PATH = "/api" +API_KEY = "MOCK_API_KEY" + +MOCK_SENSOR_CONFIG = { + "platform": DOMAIN, + "host": HOST, + "api_key": API_KEY, + "days": 3, +} + +MOCK_USER_INPUT = { + CONF_HOST: HOST, + CONF_PORT: PORT, + CONF_BASE_PATH: BASE_PATH, + CONF_SSL: False, + CONF_API_KEY: API_KEY, +} + + +def mock_connection( + aioclient_mock: AiohttpClientMocker, + host: str = HOST, + port: str = PORT, + base_path: str = BASE_PATH, + error: bool = False, + invalid_auth: bool = False, + server_error: bool = False, +) -> None: + """Mock Sonarr connection.""" + if error: + mock_connection_error( + aioclient_mock, host=host, port=port, base_path=base_path, + ) + return + + if invalid_auth: + mock_connection_invalid_auth( + aioclient_mock, host=host, port=port, base_path=base_path, + ) + return + + if server_error: + mock_connection_server_error( + aioclient_mock, host=host, port=port, base_path=base_path, + ) + return + + sonarr_url = f"http://{host}:{port}{base_path}" + + aioclient_mock.get( + f"{sonarr_url}/system/status", + text=load_fixture(f"sonarr/system-status.json"), + headers={"Content-Type": "application/json"}, + ) + + aioclient_mock.get( + f"{sonarr_url}/diskspace", + text=load_fixture(f"sonarr/diskspace.json"), + headers={"Content-Type": "application/json"}, + ) + + aioclient_mock.get( + f"{sonarr_url}/calendar", + text=load_fixture(f"sonarr/calendar.json"), + headers={"Content-Type": "application/json"}, + ) + + aioclient_mock.get( + f"{sonarr_url}/command", + text=load_fixture(f"sonarr/command.json"), + headers={"Content-Type": "application/json"}, + ) + + aioclient_mock.get( + f"{sonarr_url}/queue", + text=load_fixture(f"sonarr/queue.json"), + headers={"Content-Type": "application/json"}, + ) + + aioclient_mock.get( + f"{sonarr_url}/series", + text=load_fixture(f"sonarr/series.json"), + headers={"Content-Type": "application/json"}, + ) + + aioclient_mock.get( + f"{sonarr_url}/wanted/missing", + text=load_fixture(f"sonarr/wanted-missing.json"), + headers={"Content-Type": "application/json"}, + ) + + +def mock_connection_error( + aioclient_mock: AiohttpClientMocker, + host: str = HOST, + port: str = PORT, + base_path: str = BASE_PATH, +) -> None: + """Mock Sonarr connection errors.""" + sonarr_url = f"http://{host}:{port}{base_path}" + + aioclient_mock.get(f"{sonarr_url}/system/status", exc=SocketGIAError) + aioclient_mock.get(f"{sonarr_url}/diskspace", exc=SocketGIAError) + aioclient_mock.get(f"{sonarr_url}/calendar", exc=SocketGIAError) + aioclient_mock.get(f"{sonarr_url}/command", exc=SocketGIAError) + aioclient_mock.get(f"{sonarr_url}/queue", exc=SocketGIAError) + aioclient_mock.get(f"{sonarr_url}/series", exc=SocketGIAError) + aioclient_mock.get(f"{sonarr_url}/missing/wanted", exc=SocketGIAError) + + +def mock_connection_invalid_auth( + aioclient_mock: AiohttpClientMocker, + host: str = HOST, + port: str = PORT, + base_path: str = BASE_PATH, +) -> None: + """Mock Sonarr invalid auth errors.""" + sonarr_url = f"http://{host}:{port}{base_path}" + + aioclient_mock.get(f"{sonarr_url}/system/status", status=403) + aioclient_mock.get(f"{sonarr_url}/diskspace", status=403) + aioclient_mock.get(f"{sonarr_url}/calendar", status=403) + aioclient_mock.get(f"{sonarr_url}/command", status=403) + aioclient_mock.get(f"{sonarr_url}/queue", status=403) + aioclient_mock.get(f"{sonarr_url}/series", status=403) + aioclient_mock.get(f"{sonarr_url}/missing/wanted", status=403) + + +def mock_connection_server_error( + aioclient_mock: AiohttpClientMocker, + host: str = HOST, + port: str = PORT, + base_path: str = BASE_PATH, +) -> None: + """Mock Sonarr server errors.""" + sonarr_url = f"http://{host}:{port}{base_path}" + + aioclient_mock.get(f"{sonarr_url}/system/status", status=500) + aioclient_mock.get(f"{sonarr_url}/diskspace", status=500) + aioclient_mock.get(f"{sonarr_url}/calendar", status=500) + aioclient_mock.get(f"{sonarr_url}/command", status=500) + aioclient_mock.get(f"{sonarr_url}/queue", status=500) + aioclient_mock.get(f"{sonarr_url}/series", status=500) + aioclient_mock.get(f"{sonarr_url}/missing/wanted", status=500) + + +async def setup_integration( + hass: HomeAssistantType, + aioclient_mock: AiohttpClientMocker, + host: str = HOST, + port: str = PORT, + base_path: str = BASE_PATH, + api_key: str = API_KEY, + unique_id: str = None, + skip_entry_setup: bool = False, + connection_error: bool = False, + invalid_auth: bool = False, + server_error: bool = False, +) -> MockConfigEntry: + """Set up the Sonarr integration in Home Assistant.""" + entry = MockConfigEntry( + domain=DOMAIN, + unique_id=unique_id, + data={ + CONF_HOST: host, + CONF_PORT: port, + CONF_BASE_PATH: base_path, + CONF_SSL: False, + CONF_VERIFY_SSL: False, + CONF_API_KEY: api_key, + CONF_UPCOMING_DAYS: DEFAULT_UPCOMING_DAYS, + CONF_WANTED_MAX_ITEMS: DEFAULT_WANTED_MAX_ITEMS, + }, + ) + + entry.add_to_hass(hass) + + mock_connection( + aioclient_mock, + host=host, + port=port, + base_path=base_path, + error=connection_error, + invalid_auth=invalid_auth, + server_error=server_error, + ) + + if not skip_entry_setup: + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + return entry diff --git a/tests/components/sonarr/test_config_flow.py b/tests/components/sonarr/test_config_flow.py new file mode 100644 index 00000000000..cf5f1d15d55 --- /dev/null +++ b/tests/components/sonarr/test_config_flow.py @@ -0,0 +1,183 @@ +"""Test the Sonarr config flow.""" +from homeassistant.components.sonarr.const import ( + CONF_UPCOMING_DAYS, + CONF_WANTED_MAX_ITEMS, + DEFAULT_UPCOMING_DAYS, + DEFAULT_WANTED_MAX_ITEMS, + DOMAIN, +) +from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER +from homeassistant.const import CONF_HOST, CONF_SOURCE, CONF_VERIFY_SSL +from homeassistant.data_entry_flow import ( + RESULT_TYPE_ABORT, + RESULT_TYPE_CREATE_ENTRY, + RESULT_TYPE_FORM, +) +from homeassistant.helpers.typing import HomeAssistantType + +from tests.async_mock import patch +from tests.components.sonarr import ( + HOST, + MOCK_USER_INPUT, + mock_connection, + mock_connection_error, + mock_connection_invalid_auth, + setup_integration, +) +from tests.test_util.aiohttp import AiohttpClientMocker + + +async def test_options(hass, aioclient_mock: AiohttpClientMocker): + """Test updating options.""" + entry = await setup_integration(hass, aioclient_mock) + assert entry.options[CONF_UPCOMING_DAYS] == DEFAULT_UPCOMING_DAYS + assert entry.options[CONF_WANTED_MAX_ITEMS] == DEFAULT_WANTED_MAX_ITEMS + + result = await hass.config_entries.options.async_init(entry.entry_id) + + assert result["type"] == RESULT_TYPE_FORM + assert result["step_id"] == "init" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={CONF_UPCOMING_DAYS: 2, CONF_WANTED_MAX_ITEMS: 100}, + ) + + assert result["type"] == RESULT_TYPE_CREATE_ENTRY + assert result["data"][CONF_UPCOMING_DAYS] == 2 + assert result["data"][CONF_WANTED_MAX_ITEMS] == 100 + + +async def test_show_user_form(hass: HomeAssistantType) -> None: + """Test that the user set up form is served.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={CONF_SOURCE: SOURCE_USER}, + ) + + assert result["step_id"] == "user" + assert result["type"] == RESULT_TYPE_FORM + + +async def test_cannot_connect( + hass: HomeAssistantType, aioclient_mock: AiohttpClientMocker +) -> None: + """Test we show user form on connection error.""" + mock_connection_error(aioclient_mock) + + user_input = MOCK_USER_INPUT.copy() + result = await hass.config_entries.flow.async_init( + DOMAIN, context={CONF_SOURCE: SOURCE_USER}, data=user_input, + ) + + assert result["type"] == RESULT_TYPE_FORM + assert result["step_id"] == "user" + assert result["errors"] == {"base": "cannot_connect"} + + +async def test_invalid_auth( + hass: HomeAssistantType, aioclient_mock: AiohttpClientMocker +) -> None: + """Test we show user form on invalid auth.""" + mock_connection_invalid_auth(aioclient_mock) + + user_input = MOCK_USER_INPUT.copy() + result = await hass.config_entries.flow.async_init( + DOMAIN, context={CONF_SOURCE: SOURCE_USER}, data=user_input, + ) + + assert result["type"] == RESULT_TYPE_FORM + assert result["step_id"] == "user" + assert result["errors"] == {"base": "invalid_auth"} + + +async def test_unknown_error( + hass: HomeAssistantType, aioclient_mock: AiohttpClientMocker +) -> None: + """Test we show user form on unknown error.""" + user_input = MOCK_USER_INPUT.copy() + with patch( + "homeassistant.components.sonarr.config_flow.Sonarr.update", + side_effect=Exception, + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={CONF_SOURCE: SOURCE_USER}, data=user_input, + ) + + assert result["type"] == RESULT_TYPE_ABORT + assert result["reason"] == "unknown" + + +async def test_full_import_flow_implementation( + hass: HomeAssistantType, aioclient_mock: AiohttpClientMocker +) -> None: + """Test the full manual user flow from start to finish.""" + mock_connection(aioclient_mock) + + user_input = MOCK_USER_INPUT.copy() + result = await hass.config_entries.flow.async_init( + DOMAIN, context={CONF_SOURCE: SOURCE_IMPORT}, data=user_input, + ) + + assert result["type"] == RESULT_TYPE_CREATE_ENTRY + assert result["title"] == HOST + + assert result["data"] + assert result["data"][CONF_HOST] == HOST + + assert result["result"] + assert result["result"].options[CONF_UPCOMING_DAYS] == DEFAULT_UPCOMING_DAYS + assert result["result"].options[CONF_WANTED_MAX_ITEMS] == DEFAULT_WANTED_MAX_ITEMS + + +async def test_full_user_flow_implementation( + hass: HomeAssistantType, aioclient_mock: AiohttpClientMocker +) -> None: + """Test the full manual user flow from start to finish.""" + mock_connection(aioclient_mock) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={CONF_SOURCE: SOURCE_USER}, + ) + + assert result["type"] == RESULT_TYPE_FORM + assert result["step_id"] == "user" + + user_input = MOCK_USER_INPUT.copy() + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input=user_input, + ) + + assert result["type"] == RESULT_TYPE_CREATE_ENTRY + assert result["title"] == HOST + + assert result["data"] + assert result["data"][CONF_HOST] == HOST + + +async def test_full_user_flow_advanced_options( + hass: HomeAssistantType, aioclient_mock: AiohttpClientMocker +) -> None: + """Test the full manual user flow with advanced options.""" + mock_connection(aioclient_mock) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={CONF_SOURCE: SOURCE_USER, "show_advanced_options": True} + ) + + assert result["type"] == RESULT_TYPE_FORM + assert result["step_id"] == "user" + + user_input = { + **MOCK_USER_INPUT, + CONF_VERIFY_SSL: True, + } + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input=user_input, + ) + + assert result["type"] == RESULT_TYPE_CREATE_ENTRY + assert result["title"] == HOST + + assert result["data"] + assert result["data"][CONF_HOST] == HOST + assert result["data"][CONF_VERIFY_SSL] diff --git a/tests/components/sonarr/test_init.py b/tests/components/sonarr/test_init.py new file mode 100644 index 00000000000..852befcb31c --- /dev/null +++ b/tests/components/sonarr/test_init.py @@ -0,0 +1,36 @@ +"""Tests for the Sonsrr integration.""" +from homeassistant.components.sonarr.const import DOMAIN +from homeassistant.config_entries import ( + ENTRY_STATE_LOADED, + ENTRY_STATE_NOT_LOADED, + ENTRY_STATE_SETUP_RETRY, +) +from homeassistant.core import HomeAssistant + +from tests.components.sonarr import setup_integration +from tests.test_util.aiohttp import AiohttpClientMocker + + +async def test_config_entry_not_ready( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: + """Test the configuration entry not ready.""" + entry = await setup_integration(hass, aioclient_mock, connection_error=True) + assert entry.state == ENTRY_STATE_SETUP_RETRY + + +async def test_unload_config_entry( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: + """Test the configuration entry unloading.""" + entry = await setup_integration(hass, aioclient_mock) + + assert hass.data[DOMAIN] + assert entry.entry_id in hass.data[DOMAIN] + assert entry.state == ENTRY_STATE_LOADED + + await hass.config_entries.async_unload(entry.entry_id) + await hass.async_block_till_done() + + assert entry.entry_id not in hass.data[DOMAIN] + assert entry.state == ENTRY_STATE_NOT_LOADED diff --git a/tests/components/sonarr/test_sensor.py b/tests/components/sonarr/test_sensor.py index 96585f87068..8fbca025404 100644 --- a/tests/components/sonarr/test_sensor.py +++ b/tests/components/sonarr/test_sensor.py @@ -1,722 +1,203 @@ -"""The tests for the Sonarr platform.""" -from datetime import datetime -import time -import unittest +"""Tests for the Sonarr sensor platform.""" +from datetime import timedelta import pytest -import homeassistant.components.sonarr.sensor as sonarr -from homeassistant.const import DATA_GIGABYTES, UNIT_PERCENTAGE +from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN +from homeassistant.components.sonarr.const import ( + CONF_BASE_PATH, + CONF_UPCOMING_DAYS, + DOMAIN, +) +from homeassistant.const import ( + ATTR_ICON, + ATTR_UNIT_OF_MEASUREMENT, + DATA_GIGABYTES, + STATE_UNAVAILABLE, +) +from homeassistant.helpers.typing import HomeAssistantType +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 get_test_home_assistant +from tests.common import async_fire_time_changed +from tests.components.sonarr import ( + MOCK_SENSOR_CONFIG, + mock_connection, + setup_integration, +) +from tests.test_util.aiohttp import AiohttpClientMocker + +UPCOMING_ENTITY_ID = f"{SENSOR_DOMAIN}.sonarr_upcoming" -def mocked_exception(*args, **kwargs): - """Mock exception thrown by requests.get.""" - raise OSError +async def test_import_from_sensor_component( + hass: HomeAssistantType, aioclient_mock: AiohttpClientMocker +) -> None: + """Test import from sensor platform.""" + mock_connection(aioclient_mock) + assert await async_setup_component( + hass, SENSOR_DOMAIN, {SENSOR_DOMAIN: MOCK_SENSOR_CONFIG} + ) + await hass.async_block_till_done() + + entries = hass.config_entries.async_entries(DOMAIN) + assert len(entries) == 1 + + assert entries[0].data[CONF_BASE_PATH] == "/api" + assert entries[0].options[CONF_UPCOMING_DAYS] == 3 + + assert hass.states.get(UPCOMING_ENTITY_ID) -def mocked_requests_get(*args, **kwargs): - """Mock requests.get invocations.""" +async def test_sensors( + hass: HomeAssistantType, aioclient_mock: AiohttpClientMocker +) -> None: + """Test the creation and values of the sensors.""" + entry = await setup_integration(hass, aioclient_mock, skip_entry_setup=True) + registry = await hass.helpers.entity_registry.async_get_registry() - class MockResponse: - """Class to represent a mocked response.""" + # Pre-create registry entries for disabled by default sensors + sensors = { + "commands": "sonarr_commands", + "diskspace": "sonarr_disk_space", + "queue": "sonarr_queue", + "series": "sonarr_shows", + "wanted": "sonarr_wanted", + } - def __init__(self, json_data, status_code): - """Initialize the mock response class.""" - self.json_data = json_data - self.status_code = status_code - - def json(self): - """Return the json of the response.""" - return self.json_data - - today = datetime.date(datetime.fromtimestamp(time.time())) - url = str(args[0]) - if "api/calendar" in url: - return MockResponse( - [ - { - "seriesId": 3, - "episodeFileId": 0, - "seasonNumber": 4, - "episodeNumber": 11, - "title": "Easy Com-mercial, Easy Go-mercial", - "airDate": str(today), - "airDateUtc": "2014-01-27T01:30:00Z", - "overview": "To compete with fellow “restaurateur,” Ji...", - "hasFile": "false", - "monitored": "true", - "sceneEpisodeNumber": 0, - "sceneSeasonNumber": 0, - "tvDbEpisodeId": 0, - "series": { - "tvdbId": 194031, - "tvRageId": 24607, - "imdbId": "tt1561755", - "title": "Bob's Burgers", - "cleanTitle": "bobsburgers", - "status": "continuing", - "overview": "Bob's Burgers follows a third-generation ...", - "airTime": "5:30pm", - "monitored": "true", - "qualityProfileId": 1, - "seasonFolder": "true", - "lastInfoSync": "2014-01-26T19:25:55.4555946Z", - "runtime": 30, - "images": [ - { - "coverType": "banner", - "url": "http://slurm.trakt.us/images/bann.jpg", - }, - { - "coverType": "poster", - "url": "http://slurm.trakt.us/images/poster00.jpg", - }, - { - "coverType": "fanart", - "url": "http://slurm.trakt.us/images/fan6.jpg", - }, - ], - "seriesType": "standard", - "network": "FOX", - "useSceneNumbering": "false", - "titleSlug": "bobs-burgers", - "path": "T:\\Bob's Burgers", - "year": 0, - "firstAired": "2011-01-10T01:30:00Z", - "qualityProfile": { - "value": { - "name": "SD", - "allowed": [ - {"id": 1, "name": "SDTV", "weight": 1}, - {"id": 8, "name": "WEBDL-480p", "weight": 2}, - {"id": 2, "name": "DVD", "weight": 3}, - ], - "cutoff": {"id": 1, "name": "SDTV", "weight": 1}, - "id": 1, - }, - "isLoaded": "true", - }, - "seasons": [ - {"seasonNumber": 4, "monitored": "true"}, - {"seasonNumber": 3, "monitored": "true"}, - {"seasonNumber": 2, "monitored": "true"}, - {"seasonNumber": 1, "monitored": "true"}, - {"seasonNumber": 0, "monitored": "false"}, - ], - "id": 66, - }, - "downloading": "false", - "id": 14402, - } - ], - 200, + for (unique, oid) in sensors.items(): + registry.async_get_or_create( + SENSOR_DOMAIN, + DOMAIN, + f"{entry.entry_id}_{unique}", + suggested_object_id=oid, + disabled_by=None, ) - if "api/command" in url: - return MockResponse( - [ - { - "name": "RescanSeries", - "startedOn": "0001-01-01T00:00:00Z", - "stateChangeTime": "2014-02-05T05:09:09.2366139Z", - "sendUpdatesToClient": "true", - "state": "pending", - "id": 24, - } - ], - 200, - ) - if "api/wanted/missing" in url or "totalRecords" in url: - return MockResponse( - { - "page": 1, - "pageSize": 15, - "sortKey": "airDateUtc", - "sortDirection": "descending", - "totalRecords": 1, - "records": [ - { - "seriesId": 1, - "episodeFileId": 0, - "seasonNumber": 5, - "episodeNumber": 4, - "title": "Archer Vice: House Call", - "airDate": "2014-02-03", - "airDateUtc": "2014-02-04T03:00:00Z", - "overview": "Archer has to stage an that ... ", - "hasFile": "false", - "monitored": "true", - "sceneEpisodeNumber": 0, - "sceneSeasonNumber": 0, - "tvDbEpisodeId": 0, - "absoluteEpisodeNumber": 50, - "series": { - "tvdbId": 110381, - "tvRageId": 23354, - "imdbId": "tt1486217", - "title": "Archer (2009)", - "cleanTitle": "archer2009", - "status": "continuing", - "overview": "At ISIS, an international spy ...", - "airTime": "7:00pm", - "monitored": "true", - "qualityProfileId": 1, - "seasonFolder": "true", - "lastInfoSync": "2014-02-05T04:39:28.550495Z", - "runtime": 30, - "images": [ - { - "coverType": "banner", - "url": "http://slurm.trakt.us//57.12.jpg", - }, - { - "coverType": "poster", - "url": "http://slurm.trakt.u/57.12-300.jpg", - }, - { - "coverType": "fanart", - "url": "http://slurm.trakt.us/image.12.jpg", - }, - ], - "seriesType": "standard", - "network": "FX", - "useSceneNumbering": "false", - "titleSlug": "archer-2009", - "path": "E:\\Test\\TV\\Archer (2009)", - "year": 2009, - "firstAired": "2009-09-18T02:00:00Z", - "qualityProfile": { - "value": { - "name": "SD", - "cutoff": {"id": 1, "name": "SDTV"}, - "items": [ - { - "quality": {"id": 1, "name": "SDTV"}, - "allowed": "true", - }, - { - "quality": {"id": 8, "name": "WEBDL-480p"}, - "allowed": "true", - }, - { - "quality": {"id": 2, "name": "DVD"}, - "allowed": "true", - }, - { - "quality": {"id": 4, "name": "HDTV-720p"}, - "allowed": "false", - }, - { - "quality": {"id": 9, "name": "HDTV-1080p"}, - "allowed": "false", - }, - { - "quality": {"id": 10, "name": "Raw-HD"}, - "allowed": "false", - }, - { - "quality": {"id": 5, "name": "WEBDL-720p"}, - "allowed": "false", - }, - { - "quality": {"id": 6, "name": "Bluray-720p"}, - "allowed": "false", - }, - { - "quality": {"id": 3, "name": "WEBDL-1080p"}, - "allowed": "false", - }, - { - "quality": { - "id": 7, - "name": "Bluray-1080p", - }, - "allowed": "false", - }, - ], - "id": 1, - }, - "isLoaded": "true", - }, - "seasons": [ - {"seasonNumber": 5, "monitored": "true"}, - {"seasonNumber": 4, "monitored": "true"}, - {"seasonNumber": 3, "monitored": "true"}, - {"seasonNumber": 2, "monitored": "true"}, - {"seasonNumber": 1, "monitored": "true"}, - {"seasonNumber": 0, "monitored": "false"}, - ], - "id": 1, - }, - "downloading": "false", - "id": 55, - } - ], - }, - 200, - ) - if "api/queue" in url: - return MockResponse( - [ - { - "series": { - "title": "Game of Thrones", - "sortTitle": "game thrones", - "seasonCount": 6, - "status": "continuing", - "overview": "Seven noble families fight for land ...", - "network": "HBO", - "airTime": "21:00", - "images": [ - { - "coverType": "fanart", - "url": "http://thetvdb.com/banners/fanart/-83.jpg", - }, - { - "coverType": "banner", - "url": "http://thetvdb.com/banners/-g19.jpg", - }, - { - "coverType": "poster", - "url": "http://thetvdb.com/banners/posters-34.jpg", - }, - ], - "seasons": [ - {"seasonNumber": 0, "monitored": "false"}, - {"seasonNumber": 1, "monitored": "false"}, - {"seasonNumber": 2, "monitored": "true"}, - {"seasonNumber": 3, "monitored": "false"}, - {"seasonNumber": 4, "monitored": "false"}, - {"seasonNumber": 5, "monitored": "true"}, - {"seasonNumber": 6, "monitored": "true"}, - ], - "year": 2011, - "path": "/Volumes/Media/Shows/Game of Thrones", - "profileId": 5, - "seasonFolder": "true", - "monitored": "true", - "useSceneNumbering": "false", - "runtime": 60, - "tvdbId": 121361, - "tvRageId": 24493, - "tvMazeId": 82, - "firstAired": "2011-04-16T23:00:00Z", - "lastInfoSync": "2016-02-05T16:40:11.614176Z", - "seriesType": "standard", - "cleanTitle": "gamethrones", - "imdbId": "tt0944947", - "titleSlug": "game-of-thrones", - "certification": "TV-MA", - "genres": ["Adventure", "Drama", "Fantasy"], - "tags": [], - "added": "2015-12-28T13:44:24.204583Z", - "ratings": {"votes": 1128, "value": 9.4}, - "qualityProfileId": 5, - "id": 17, - }, - "episode": { - "seriesId": 17, - "episodeFileId": 0, - "seasonNumber": 3, - "episodeNumber": 8, - "title": "Second Sons", - "airDate": "2013-05-19", - "airDateUtc": "2013-05-20T01:00:00Z", - "overview": "King’s Landing hosts a wedding, and ...", - "hasFile": "false", - "monitored": "false", - "absoluteEpisodeNumber": 28, - "unverifiedSceneNumbering": "false", - "id": 889, - }, - "quality": { - "quality": {"id": 7, "name": "Bluray-1080p"}, - "revision": {"version": 1, "real": 0}, - }, - "size": 4472186820, - "title": "Game.of.Thrones.S03E08.Second.Sons.2013.1080p.", - "sizeleft": 0, - "timeleft": "00:00:00", - "estimatedCompletionTime": "2016-02-05T22:46:52.440104Z", - "status": "Downloading", - "trackedDownloadStatus": "Ok", - "statusMessages": [], - "downloadId": "SABnzbd_nzo_Mq2f_b", - "protocol": "usenet", - "id": 1503378561, - } - ], - 200, - ) - if "api/series" in url: - return MockResponse( - [ - { - "title": "Marvel's Daredevil", - "alternateTitles": [{"title": "Daredevil", "seasonNumber": -1}], - "sortTitle": "marvels daredevil", - "seasonCount": 2, - "totalEpisodeCount": 26, - "episodeCount": 26, - "episodeFileCount": 26, - "sizeOnDisk": 79282273693, - "status": "continuing", - "overview": "Matt Murdock was blinded in a tragic accident...", - "previousAiring": "2016-03-18T04:01:00Z", - "network": "Netflix", - "airTime": "00:01", - "images": [ - { - "coverType": "fanart", - "url": "/sonarr/MediaCover/7/fanart.jpg?lastWrite=", - }, - { - "coverType": "banner", - "url": "/sonarr/MediaCover/7/banner.jpg?lastWrite=", - }, - { - "coverType": "poster", - "url": "/sonarr/MediaCover/7/poster.jpg?lastWrite=", - }, - ], - "seasons": [ - { - "seasonNumber": 1, - "monitored": "false", - "statistics": { - "previousAiring": "2015-04-10T04:01:00Z", - "episodeFileCount": 13, - "episodeCount": 13, - "totalEpisodeCount": 13, - "sizeOnDisk": 22738179333, - "percentOfEpisodes": 100, - }, - }, - { - "seasonNumber": 2, - "monitored": "false", - "statistics": { - "previousAiring": "2016-03-18T04:01:00Z", - "episodeFileCount": 13, - "episodeCount": 13, - "totalEpisodeCount": 13, - "sizeOnDisk": 56544094360, - "percentOfEpisodes": 100, - }, - }, - ], - "year": 2015, - "path": "F:\\TV_Shows\\Marvels Daredevil", - "profileId": 6, - "seasonFolder": "true", - "monitored": "true", - "useSceneNumbering": "false", - "runtime": 55, - "tvdbId": 281662, - "tvRageId": 38796, - "tvMazeId": 1369, - "firstAired": "2015-04-10T04:00:00Z", - "lastInfoSync": "2016-09-09T09:02:49.4402575Z", - "seriesType": "standard", - "cleanTitle": "marvelsdaredevil", - "imdbId": "tt3322312", - "titleSlug": "marvels-daredevil", - "certification": "TV-MA", - "genres": ["Action", "Crime", "Drama"], - "tags": [], - "added": "2015-05-15T00:20:32.7892744Z", - "ratings": {"votes": 461, "value": 8.9}, - "qualityProfileId": 6, - "id": 7, - } - ], - 200, - ) - if "api/diskspace" in url: - return MockResponse( - [ - { - "path": "/data", - "label": "", - "freeSpace": 282500067328, - "totalSpace": 499738734592, - } - ], - 200, - ) - if "api/system/status" in url: - return MockResponse( - { - "version": "2.0.0.1121", - "buildTime": "2014-02-08T20:49:36.5560392Z", - "isDebug": "false", - "isProduction": "true", - "isAdmin": "true", - "isUserInteractive": "false", - "startupPath": "C:\\ProgramData\\NzbDrone\\bin", - "appData": "C:\\ProgramData\\NzbDrone", - "osVersion": "6.2.9200.0", - "isMono": "false", - "isLinux": "false", - "isWindows": "true", - "branch": "develop", - "authentication": "false", - "startOfWeek": 0, - "urlBase": "", - }, - 200, - ) - return MockResponse({"error": "Unauthorized"}, 401) + + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + for (unique, oid) in sensors.items(): + entity = registry.async_get(f"sensor.{oid}") + assert entity + assert entity.unique_id == f"{entry.entry_id}_{unique}" + + state = hass.states.get("sensor.sonarr_commands") + assert state + assert state.attributes.get(ATTR_ICON) == "mdi:code-braces" + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == "Commands" + assert state.state == "2" + + state = hass.states.get("sensor.sonarr_disk_space") + assert state + assert state.attributes.get(ATTR_ICON) == "mdi:harddisk" + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == DATA_GIGABYTES + assert state.state == "263.10" + + state = hass.states.get("sensor.sonarr_queue") + assert state + assert state.attributes.get(ATTR_ICON) == "mdi:download" + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == "Episodes" + assert state.state == "1" + + state = hass.states.get("sensor.sonarr_shows") + assert state + assert state.attributes.get(ATTR_ICON) == "mdi:television" + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == "Series" + assert state.state == "1" + + state = hass.states.get("sensor.sonarr_upcoming") + assert state + assert state.attributes.get(ATTR_ICON) == "mdi:television" + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == "Episodes" + assert state.state == "1" + + state = hass.states.get("sensor.sonarr_wanted") + assert state + assert state.attributes.get(ATTR_ICON) == "mdi:television" + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == "Episodes" + assert state.state == "2" -class TestSonarrSetup(unittest.TestCase): - """Test the Sonarr platform.""" +@pytest.mark.parametrize( + "entity_id", + ( + "sensor.sonarr_commands", + "sensor.sonarr_disk_space", + "sensor.sonarr_queue", + "sensor.sonarr_shows", + "sensor.sonarr_wanted", + ), +) +async def test_disabled_by_default_sensors( + hass: HomeAssistantType, aioclient_mock: AiohttpClientMocker, entity_id: str +) -> None: + """Test the disabled by default sensors.""" + await setup_integration(hass, aioclient_mock) + registry = await hass.helpers.entity_registry.async_get_registry() + print(registry.entities) - # pylint: disable=invalid-name - DEVICES = [] + state = hass.states.get(entity_id) + assert state is None - def add_entities(self, devices, update): - """Mock add devices.""" - for device in devices: - self.DEVICES.append(device) + entry = registry.async_get(entity_id) + assert entry + assert entry.disabled + assert entry.disabled_by == "integration" - def setUp(self): - """Initialize values for this testcase class.""" - self.DEVICES = [] - self.hass = get_test_home_assistant() - self.hass.config.time_zone = "America/Los_Angeles" - def tearDown(self): # pylint: disable=invalid-name - """Stop everything that was started.""" - self.hass.stop() +async def test_availability( + hass: HomeAssistantType, aioclient_mock: AiohttpClientMocker +) -> None: + """Test entity availability.""" + now = dt_util.utcnow() - @patch("requests.get", side_effect=mocked_requests_get) - def test_diskspace_no_paths(self, req_mock): - """Test getting all disk space.""" - config = { - "platform": "sonarr", - "api_key": "foo", - "days": "2", - "unit": DATA_GIGABYTES, - "include_paths": [], - "monitored_conditions": ["diskspace"], - } - sonarr.setup_platform(self.hass, config, self.add_entities, None) - for device in self.DEVICES: - device.update() - assert "263.10" == device.state - assert "mdi:harddisk" == device.icon - assert DATA_GIGABYTES == device.unit_of_measurement - assert "Sonarr Disk Space" == device.name - assert "263.10/465.42GB (56.53%)" == device.device_state_attributes["/data"] + with patch("homeassistant.util.dt.utcnow", return_value=now): + await setup_integration(hass, aioclient_mock) - @patch("requests.get", side_effect=mocked_requests_get) - def test_diskspace_paths(self, req_mock): - """Test getting diskspace for included paths.""" - config = { - "platform": "sonarr", - "api_key": "foo", - "days": "2", - "unit": DATA_GIGABYTES, - "include_paths": ["/data"], - "monitored_conditions": ["diskspace"], - } - sonarr.setup_platform(self.hass, config, self.add_entities, None) - for device in self.DEVICES: - device.update() - assert "263.10" == device.state - assert "mdi:harddisk" == device.icon - assert DATA_GIGABYTES == device.unit_of_measurement - assert "Sonarr Disk Space" == device.name - assert "263.10/465.42GB (56.53%)" == device.device_state_attributes["/data"] + assert hass.states.get(UPCOMING_ENTITY_ID).state == "1" - @patch("requests.get", side_effect=mocked_requests_get) - def test_commands(self, req_mock): - """Test getting running commands.""" - config = { - "platform": "sonarr", - "api_key": "foo", - "days": "2", - "unit": DATA_GIGABYTES, - "include_paths": ["/data"], - "monitored_conditions": ["commands"], - } - sonarr.setup_platform(self.hass, config, self.add_entities, None) - for device in self.DEVICES: - device.update() - assert 1 == device.state - assert "mdi:code-braces" == device.icon - assert "Commands" == device.unit_of_measurement - assert "Sonarr Commands" == device.name - assert "pending" == device.device_state_attributes["RescanSeries"] + # state to unavailable + aioclient_mock.clear_requests() + mock_connection(aioclient_mock, error=True) - @patch("requests.get", side_effect=mocked_requests_get) - def test_queue(self, req_mock): - """Test getting downloads in the queue.""" - config = { - "platform": "sonarr", - "api_key": "foo", - "days": "2", - "unit": DATA_GIGABYTES, - "include_paths": ["/data"], - "monitored_conditions": ["queue"], - } - sonarr.setup_platform(self.hass, config, self.add_entities, None) - for device in self.DEVICES: - device.update() - assert 1 == device.state - assert "mdi:download" == device.icon - assert "Episodes" == device.unit_of_measurement - assert "Sonarr Queue" == device.name - assert ( - f"100.00{UNIT_PERCENTAGE}" - == device.device_state_attributes["Game of Thrones S03E08"] - ) + future = now + timedelta(minutes=1) + with patch("homeassistant.util.dt.utcnow", return_value=future): + async_fire_time_changed(hass, future) + await hass.async_block_till_done() - @patch("requests.get", side_effect=mocked_requests_get) - def test_series(self, req_mock): - """Test getting the number of series.""" - config = { - "platform": "sonarr", - "api_key": "foo", - "days": "2", - "unit": DATA_GIGABYTES, - "include_paths": ["/data"], - "monitored_conditions": ["series"], - } - sonarr.setup_platform(self.hass, config, self.add_entities, None) - for device in self.DEVICES: - device.update() - assert 1 == device.state - assert "mdi:television" == device.icon - assert "Shows" == device.unit_of_measurement - assert "Sonarr Series" == device.name - assert ( - "26/26 Episodes" == device.device_state_attributes["Marvel's Daredevil"] - ) + assert hass.states.get(UPCOMING_ENTITY_ID).state == STATE_UNAVAILABLE - @patch("requests.get", side_effect=mocked_requests_get) - def test_wanted(self, req_mock): - """Test getting wanted episodes.""" - config = { - "platform": "sonarr", - "api_key": "foo", - "days": "2", - "unit": DATA_GIGABYTES, - "include_paths": ["/data"], - "monitored_conditions": ["wanted"], - } - sonarr.setup_platform(self.hass, config, self.add_entities, None) - for device in self.DEVICES: - device.update() - assert 1 == device.state - assert "mdi:television" == device.icon - assert "Episodes" == device.unit_of_measurement - assert "Sonarr Wanted" == device.name - assert ( - "2014-02-03" == device.device_state_attributes["Archer (2009) S05E04"] - ) + # state to available + aioclient_mock.clear_requests() + mock_connection(aioclient_mock) - @patch("requests.get", side_effect=mocked_requests_get) - def test_upcoming_multiple_days(self, req_mock): - """Test the upcoming episodes for multiple days.""" - config = { - "platform": "sonarr", - "api_key": "foo", - "days": "2", - "unit": DATA_GIGABYTES, - "include_paths": ["/data"], - "monitored_conditions": ["upcoming"], - } - sonarr.setup_platform(self.hass, config, self.add_entities, None) - for device in self.DEVICES: - device.update() - assert 1 == device.state - assert "mdi:television" == device.icon - assert "Episodes" == device.unit_of_measurement - assert "Sonarr Upcoming" == device.name - assert "S04E11" == device.device_state_attributes["Bob's Burgers"] + future += timedelta(minutes=1) + with patch("homeassistant.util.dt.utcnow", return_value=future): + async_fire_time_changed(hass, future) + await hass.async_block_till_done() - @pytest.mark.skip - @patch("requests.get", side_effect=mocked_requests_get) - def test_upcoming_today(self, req_mock): - """Test filtering for a single day. + assert hass.states.get(UPCOMING_ENTITY_ID).state == "1" - Sonarr needs to respond with at least 2 days - """ - config = { - "platform": "sonarr", - "api_key": "foo", - "days": "1", - "unit": DATA_GIGABYTES, - "include_paths": ["/data"], - "monitored_conditions": ["upcoming"], - } - sonarr.setup_platform(self.hass, config, self.add_entities, None) - for device in self.DEVICES: - device.update() - assert 1 == device.state - assert "mdi:television" == device.icon - assert "Episodes" == device.unit_of_measurement - assert "Sonarr Upcoming" == device.name - assert "S04E11" == device.device_state_attributes["Bob's Burgers"] + # state to unavailable + aioclient_mock.clear_requests() + mock_connection(aioclient_mock, invalid_auth=True) - @patch("requests.get", side_effect=mocked_requests_get) - def test_system_status(self, req_mock): - """Test getting system status.""" - config = { - "platform": "sonarr", - "api_key": "foo", - "days": "2", - "unit": DATA_GIGABYTES, - "include_paths": ["/data"], - "monitored_conditions": ["status"], - } - sonarr.setup_platform(self.hass, config, self.add_entities, None) - for device in self.DEVICES: - device.update() - assert "2.0.0.1121" == device.state - assert "mdi:information" == device.icon - assert "Sonarr Status" == device.name - assert "6.2.9200.0" == device.device_state_attributes["osVersion"] + future += timedelta(minutes=1) + with patch("homeassistant.util.dt.utcnow", return_value=future): + async_fire_time_changed(hass, future) + await hass.async_block_till_done() - @pytest.mark.skip - @patch("requests.get", side_effect=mocked_requests_get) - def test_ssl(self, req_mock): - """Test SSL being enabled.""" - config = { - "platform": "sonarr", - "api_key": "foo", - "days": "1", - "unit": DATA_GIGABYTES, - "include_paths": ["/data"], - "monitored_conditions": ["upcoming"], - "ssl": "true", - } - sonarr.setup_platform(self.hass, config, self.add_entities, None) - for device in self.DEVICES: - device.update() - assert 1 == device.state - assert "s" == device.ssl - assert "mdi:television" == device.icon - assert "Episodes" == device.unit_of_measurement - assert "Sonarr Upcoming" == device.name - assert "S04E11" == device.device_state_attributes["Bob's Burgers"] + assert hass.states.get(UPCOMING_ENTITY_ID).state == STATE_UNAVAILABLE - @patch("requests.get", side_effect=mocked_exception) - def test_exception_handling(self, req_mock): - """Test exception being handled.""" - config = { - "platform": "sonarr", - "api_key": "foo", - "days": "1", - "unit": DATA_GIGABYTES, - "include_paths": ["/data"], - "monitored_conditions": ["upcoming"], - } - sonarr.setup_platform(self.hass, config, self.add_entities, None) - for device in self.DEVICES: - device.update() - assert device.state is None + # state to available + aioclient_mock.clear_requests() + mock_connection(aioclient_mock) + + future += timedelta(minutes=1) + with patch("homeassistant.util.dt.utcnow", return_value=future): + async_fire_time_changed(hass, future) + await hass.async_block_till_done() + + assert hass.states.get(UPCOMING_ENTITY_ID).state == "1" diff --git a/tests/fixtures/sonarr/calendar.json b/tests/fixtures/sonarr/calendar.json new file mode 100644 index 00000000000..e24a48d227a --- /dev/null +++ b/tests/fixtures/sonarr/calendar.json @@ -0,0 +1,116 @@ +[ + { + "seriesId": 3, + "episodeFileId": 0, + "seasonNumber": 4, + "episodeNumber": 11, + "title": "Easy Com-mercial, Easy Go-mercial", + "airDate": "2014-01-26", + "airDateUtc": "2014-01-27T01:30:00Z", + "overview": "To compete with fellow \"restaurateur,\" Jimmy Pesto, and his blowout Super Bowl event, Bob is determined to create a Bob's Burgers commercial to air during the \"big game.\" In an effort to outshine Pesto, the Belchers recruit Randy, a documentarian, to assist with the filmmaking and hire on former pro football star Connie Frye to be the celebrity endorser.", + "hasFile": false, + "monitored": true, + "sceneEpisodeNumber": 0, + "sceneSeasonNumber": 0, + "tvDbEpisodeId": 0, + "series": { + "tvdbId": 194031, + "tvRageId": 24607, + "imdbId": "tt1561755", + "title": "Bob's Burgers", + "sortTitle": "bob burgers", + "cleanTitle": "bobsburgers", + "seasonCount": 4, + "status": "continuing", + "overview": "Bob's Burgers follows a third-generation restaurateur, Bob, as he runs Bob's Burgers with the help of his wife and their three kids. Bob and his quirky family have big ideas about burgers, but fall short on service and sophistication. Despite the greasy counters, lousy location and a dearth of customers, Bob and his family are determined to make Bob's Burgers \"grand re-re-re-opening\" a success.", + "airTime": "17:30", + "monitored": true, + "qualityProfileId": 1, + "seasonFolder": true, + "lastInfoSync": "2014-01-26T19:25:55.455594Z", + "runtime": 30, + "images": [ + { + "coverType": "banner", + "url": "http://slurm.trakt.us/images/banners/1387.6.jpg" + }, + { + "coverType": "poster", + "url": "http://slurm.trakt.us/images/posters/1387.6-300.jpg" + }, + { + "coverType": "fanart", + "url": "http://slurm.trakt.us/images/fanart/1387.6.jpg" + } + ], + "seriesType": "standard", + "network": "FOX", + "useSceneNumbering": false, + "titleSlug": "bobs-burgers", + "certification": "TV-14", + "path": "T:\\Bob's Burgers", + "year": 2011, + "firstAired": "2011-01-10T01:30:00Z", + "genres": [ + "Animation", + "Comedy" + ], + "tags": [], + "added": "2011-01-26T19:25:55.455594Z", + "qualityProfile": { + "value": { + "name": "SD", + "allowed": [ + { + "id": 1, + "name": "SDTV", + "weight": 1 + }, + { + "id": 8, + "name": "WEBDL-480p", + "weight": 2 + }, + { + "id": 2, + "name": "DVD", + "weight": 3 + } + ], + "cutoff": { + "id": 1, + "name": "SDTV", + "weight": 1 + }, + "id": 1 + }, + "isLoaded": true + }, + "seasons": [ + { + "seasonNumber": 4, + "monitored": true + }, + { + "seasonNumber": 3, + "monitored": true + }, + { + "seasonNumber": 2, + "monitored": true + }, + { + "seasonNumber": 1, + "monitored": true + }, + { + "seasonNumber": 0, + "monitored": false + } + ], + "id": 66 + }, + "downloading": false, + "id": 14402 + } +] diff --git a/tests/fixtures/sonarr/command.json b/tests/fixtures/sonarr/command.json new file mode 100644 index 00000000000..97acc2f9f82 --- /dev/null +++ b/tests/fixtures/sonarr/command.json @@ -0,0 +1,36 @@ +[ + { + "name": "RefreshSeries", + "body": { + "isNewSeries": false, + "sendUpdatesToClient": true, + "updateScheduledTask": true, + "completionMessage": "Completed", + "requiresDiskAccess": false, + "isExclusive": false, + "name": "RefreshSeries", + "trigger": "manual", + "suppressMessages": false + }, + "priority": "normal", + "status": "started", + "queued": "2020-04-06T16:54:06.41945Z", + "started": "2020-04-06T16:54:06.421322Z", + "trigger": "manual", + "state": "started", + "manual": true, + "startedOn": "2020-04-06T16:54:06.41945Z", + "stateChangeTime": "2020-04-06T16:54:06.421322Z", + "sendUpdatesToClient": true, + "updateScheduledTask": true, + "id": 368621 + }, + { + "name": "RefreshSeries", + "state": "started", + "startedOn": "2020-04-06T16:57:51.406504Z", + "stateChangeTime": "2020-04-06T16:57:51.417931Z", + "sendUpdatesToClient": true, + "id": 368629 + } +] diff --git a/tests/fixtures/sonarr/diskspace.json b/tests/fixtures/sonarr/diskspace.json new file mode 100644 index 00000000000..bc867cf21e5 --- /dev/null +++ b/tests/fixtures/sonarr/diskspace.json @@ -0,0 +1,8 @@ +[ + { + "path": "C:\\", + "label": "", + "freeSpace": 282500067328, + "totalSpace": 499738734592 + } +] diff --git a/tests/fixtures/sonarr/queue.json b/tests/fixtures/sonarr/queue.json new file mode 100644 index 00000000000..1a8eb0924c3 --- /dev/null +++ b/tests/fixtures/sonarr/queue.json @@ -0,0 +1,129 @@ +[ + { + "series": { + "title": "The Andy Griffith Show", + "sortTitle": "andy griffith show", + "seasonCount": 8, + "status": "ended", + "overview": "Down-home humor and an endearing cast of characters helped make The Andy Griffith Show one of the most beloved comedies in the history of TV. The show centered around widower Andy Taylor, who divided his time between raising his young son Opie, and his job as sheriff of the sleepy North Carolina town, Mayberry. Andy and Opie live with Andy's Aunt Bee, who serves as a surrogate mother to both father and son. Andy's nervous cousin, Barney Fife, is his deputy sheriff whose incompetence is tolerated because Mayberry is virtually crime-free.", + "network": "CBS", + "airTime": "21:30", + "images": [ + { + "coverType": "fanart", + "url": "https://artworks.thetvdb.com/banners/fanart/original/77754-5.jpg" + }, + { + "coverType": "banner", + "url": "https://artworks.thetvdb.com/banners/graphical/77754-g.jpg" + }, + { + "coverType": "poster", + "url": "https://artworks.thetvdb.com/banners/posters/77754-4.jpg" + } + ], + "seasons": [ + { + "seasonNumber": 0, + "monitored": false + }, + { + "seasonNumber": 1, + "monitored": false + }, + { + "seasonNumber": 2, + "monitored": true + }, + { + "seasonNumber": 3, + "monitored": false + }, + { + "seasonNumber": 4, + "monitored": false + }, + { + "seasonNumber": 5, + "monitored": true + }, + { + "seasonNumber": 6, + "monitored": true + }, + { + "seasonNumber": 7, + "monitored": true + }, + { + "seasonNumber": 8, + "monitored": true + } + ], + "year": 1960, + "path": "F:\\The Andy Griffith Show", + "profileId": 5, + "seasonFolder": true, + "monitored": true, + "useSceneNumbering": false, + "runtime": 25, + "tvdbId": 77754, + "tvRageId": 5574, + "tvMazeId": 3853, + "firstAired": "1960-02-15T06:00:00Z", + "lastInfoSync": "2016-02-05T16:40:11.614176Z", + "seriesType": "standard", + "cleanTitle": "theandygriffithshow", + "imdbId": "", + "titleSlug": "the-andy-griffith-show", + "certification": "TV-G", + "genres": [ + "Comedy" + ], + "tags": [], + "added": "2008-02-04T13:44:24.204583Z", + "ratings": { + "votes": 547, + "value": 8.6 + }, + "qualityProfileId": 5, + "id": 17 + }, + "episode": { + "seriesId": 17, + "episodeFileId": 0, + "seasonNumber": 1, + "episodeNumber": 1, + "title": "The New Housekeeper", + "airDate": "1960-10-03", + "airDateUtc": "1960-10-03T01:00:00Z", + "overview": "Sheriff Andy Taylor and his young son Opie are in need of a new housekeeper. Andy's Aunt Bee looks like the perfect candidate and moves in, but her presence causes friction with Opie.", + "hasFile": false, + "monitored": false, + "absoluteEpisodeNumber": 1, + "unverifiedSceneNumbering": false, + "id": 889 + }, + "quality": { + "quality": { + "id": 7, + "name": "SD" + }, + "revision": { + "version": 1, + "real": 0 + } + }, + "size": 4472186820, + "title": "The.Andy.Griffith.Show.S01E01.x264-GROUP", + "sizeleft": 0, + "timeleft": "00:00:00", + "estimatedCompletionTime": "2016-02-05T22:46:52.440104Z", + "status": "Downloading", + "trackedDownloadStatus": "Ok", + "statusMessages": [], + "downloadId": "SABnzbd_nzo_Mq2f_b", + "protocol": "usenet", + "id": 1503378561 + } +] diff --git a/tests/fixtures/sonarr/series.json b/tests/fixtures/sonarr/series.json new file mode 100644 index 00000000000..ea727c14a97 --- /dev/null +++ b/tests/fixtures/sonarr/series.json @@ -0,0 +1,163 @@ +[ + { + "title": "The Andy Griffith Show", + "alternateTitles": [], + "sortTitle": "andy griffith show", + "seasonCount": 8, + "totalEpisodeCount": 253, + "episodeCount": 0, + "episodeFileCount": 0, + "sizeOnDisk": 0, + "status": "ended", + "overview": "Down-home humor and an endearing cast of characters helped make The Andy Griffith Show one of the most beloved comedies in the history of TV. The show centered around widower Andy Taylor, who divided his time between raising his young son Opie, and his job as sheriff of the sleepy North Carolina town, Mayberry. Andy and Opie live with Andy's Aunt Bee, who serves as a surrogate mother to both father and son. Andy's nervous cousin, Barney Fife, is his deputy sheriff whose incompetence is tolerated because Mayberry is virtually crime-free.", + "network": "CBS", + "airTime": "21:30", + "images": [ + { + "coverType": "fanart", + "url": "/MediaCover/105/fanart.jpg?lastWrite=637217160281262470", + "remoteUrl": "https://artworks.thetvdb.com/banners/fanart/original/77754-5.jpg" + }, + { + "coverType": "banner", + "url": "/MediaCover/105/banner.jpg?lastWrite=637217160301222320", + "remoteUrl": "https://artworks.thetvdb.com/banners/graphical/77754-g.jpg" + }, + { + "coverType": "poster", + "url": "/MediaCover/105/poster.jpg?lastWrite=637217160322182160", + "remoteUrl": "https://artworks.thetvdb.com/banners/posters/77754-1.jpg" + } + ], + "seasons": [ + { + "seasonNumber": 0, + "monitored": false, + "statistics": { + "episodeFileCount": 0, + "episodeCount": 0, + "totalEpisodeCount": 4, + "sizeOnDisk": 0, + "percentOfEpisodes": 0.0 + } + }, + { + "seasonNumber": 1, + "monitored": false, + "statistics": { + "episodeFileCount": 0, + "episodeCount": 0, + "totalEpisodeCount": 32, + "sizeOnDisk": 0, + "percentOfEpisodes": 0.0 + } + }, + { + "seasonNumber": 2, + "monitored": false, + "statistics": { + "episodeFileCount": 0, + "episodeCount": 0, + "totalEpisodeCount": 31, + "sizeOnDisk": 0, + "percentOfEpisodes": 0.0 + } + }, + { + "seasonNumber": 3, + "monitored": false, + "statistics": { + "episodeFileCount": 8, + "episodeCount": 8, + "totalEpisodeCount": 32, + "sizeOnDisk": 8000000000, + "percentOfEpisodes": 100.0 + } + }, + { + "seasonNumber": 4, + "monitored": false, + "statistics": { + "episodeFileCount": 0, + "episodeCount": 0, + "totalEpisodeCount": 32, + "sizeOnDisk": 0, + "percentOfEpisodes": 0.0 + } + }, + { + "seasonNumber": 5, + "monitored": false, + "statistics": { + "episodeFileCount": 0, + "episodeCount": 0, + "totalEpisodeCount": 32, + "sizeOnDisk": 0, + "percentOfEpisodes": 0.0 + } + }, + { + "seasonNumber": 6, + "monitored": false, + "statistics": { + "episodeFileCount": 0, + "episodeCount": 0, + "totalEpisodeCount": 30, + "sizeOnDisk": 0, + "percentOfEpisodes": 0.0 + } + }, + { + "seasonNumber": 7, + "monitored": false, + "statistics": { + "episodeFileCount": 0, + "episodeCount": 0, + "totalEpisodeCount": 30, + "sizeOnDisk": 0, + "percentOfEpisodes": 0.0 + } + }, + { + "seasonNumber": 8, + "monitored": true, + "statistics": { + "episodeFileCount": 0, + "episodeCount": 0, + "totalEpisodeCount": 30, + "sizeOnDisk": 0, + "percentOfEpisodes": 0.0 + } + } + ], + "year": 1960, + "path": "F:\\The Andy Griffith Show", + "profileId": 2, + "languageProfileId": 1, + "seasonFolder": true, + "monitored": true, + "useSceneNumbering": false, + "runtime": 25, + "tvdbId": 77754, + "tvRageId": 5574, + "tvMazeId": 3853, + "firstAired": "1960-02-15T06:00:00Z", + "lastInfoSync": "2020-04-05T20:40:21.545669Z", + "seriesType": "standard", + "cleanTitle": "theandygriffithshow", + "imdbId": "tt0053479", + "titleSlug": "the-andy-griffith-show", + "certification": "TV-G", + "genres": [ + "Comedy" + ], + "tags": [], + "added": "2020-04-05T20:40:20.050044Z", + "ratings": { + "votes": 547, + "value": 8.6 + }, + "qualityProfileId": 2, + "id": 105 + } +] diff --git a/tests/fixtures/sonarr/system-status.json b/tests/fixtures/sonarr/system-status.json new file mode 100644 index 00000000000..c3969df08fe --- /dev/null +++ b/tests/fixtures/sonarr/system-status.json @@ -0,0 +1,18 @@ +{ + "version": "2.0.0.1121", + "buildTime": "2014-02-08T20:49:36.5560392Z", + "isDebug": false, + "isProduction": true, + "isAdmin": true, + "isUserInteractive": false, + "startupPath": "C:\\ProgramData\\NzbDrone\\bin", + "appData": "C:\\ProgramData\\NzbDrone", + "osVersion": "6.2.9200.0", + "isMono": false, + "isLinux": false, + "isWindows": true, + "branch": "develop", + "authentication": false, + "startOfWeek": 0, + "urlBase": "" +} diff --git a/tests/fixtures/sonarr/wanted-missing.json b/tests/fixtures/sonarr/wanted-missing.json new file mode 100644 index 00000000000..5db7c52f469 --- /dev/null +++ b/tests/fixtures/sonarr/wanted-missing.json @@ -0,0 +1,253 @@ +{ + "page": 1, + "pageSize": 10, + "sortKey": "airDateUtc", + "sortDirection": "descending", + "totalRecords": 2, + "records": [ + { + "seriesId": 3, + "episodeFileId": 0, + "seasonNumber": 4, + "episodeNumber": 11, + "title": "Easy Com-mercial, Easy Go-mercial", + "airDate": "2014-01-26", + "airDateUtc": "2014-01-27T01:30:00Z", + "overview": "To compete with fellow \"restaurateur,\" Jimmy Pesto, and his blowout Super Bowl event, Bob is determined to create a Bob's Burgers commercial to air during the \"big game.\" In an effort to outshine Pesto, the Belchers recruit Randy, a documentarian, to assist with the filmmaking and hire on former pro football star Connie Frye to be the celebrity endorser.", + "hasFile": false, + "monitored": true, + "sceneEpisodeNumber": 0, + "sceneSeasonNumber": 0, + "tvDbEpisodeId": 0, + "series": { + "tvdbId": 194031, + "tvRageId": 24607, + "imdbId": "tt1561755", + "title": "Bob's Burgers", + "sortTitle": "bob burgers", + "cleanTitle": "bobsburgers", + "seasonCount": 4, + "status": "continuing", + "overview": "Bob's Burgers follows a third-generation restaurateur, Bob, as he runs Bob's Burgers with the help of his wife and their three kids. Bob and his quirky family have big ideas about burgers, but fall short on service and sophistication. Despite the greasy counters, lousy location and a dearth of customers, Bob and his family are determined to make Bob's Burgers \"grand re-re-re-opening\" a success.", + "airTime": "17:30", + "monitored": true, + "qualityProfileId": 1, + "seasonFolder": true, + "lastInfoSync": "2014-01-26T19:25:55.455594Z", + "runtime": 30, + "images": [ + { + "coverType": "banner", + "url": "http://slurm.trakt.us/images/banners/1387.6.jpg" + }, + { + "coverType": "poster", + "url": "http://slurm.trakt.us/images/posters/1387.6-300.jpg" + }, + { + "coverType": "fanart", + "url": "http://slurm.trakt.us/images/fanart/1387.6.jpg" + } + ], + "seriesType": "standard", + "network": "FOX", + "useSceneNumbering": false, + "titleSlug": "bobs-burgers", + "certification": "TV-14", + "path": "T:\\Bob's Burgers", + "year": 2011, + "firstAired": "2011-01-10T01:30:00Z", + "genres": [ + "Animation", + "Comedy" + ], + "tags": [], + "added": "2011-01-26T19:25:55.455594Z", + "qualityProfile": { + "value": { + "name": "SD", + "allowed": [ + { + "id": 1, + "name": "SDTV", + "weight": 1 + }, + { + "id": 8, + "name": "WEBDL-480p", + "weight": 2 + }, + { + "id": 2, + "name": "DVD", + "weight": 3 + } + ], + "cutoff": { + "id": 1, + "name": "SDTV", + "weight": 1 + }, + "id": 1 + }, + "isLoaded": true + }, + "seasons": [ + { + "seasonNumber": 4, + "monitored": true + }, + { + "seasonNumber": 3, + "monitored": true + }, + { + "seasonNumber": 2, + "monitored": true + }, + { + "seasonNumber": 1, + "monitored": true + }, + { + "seasonNumber": 0, + "monitored": false + } + ], + "id": 66 + }, + "downloading": false, + "id": 14402 + }, + { + "seriesId": 17, + "episodeFileId": 0, + "seasonNumber": 1, + "episodeNumber": 1, + "title": "The New Housekeeper", + "airDate": "1960-10-03", + "airDateUtc": "1960-10-03T01:00:00Z", + "overview": "Sheriff Andy Taylor and his young son Opie are in need of a new housekeeper. Andy's Aunt Bee looks like the perfect candidate and moves in, but her presence causes friction with Opie.", + "hasFile": false, + "monitored": true, + "sceneEpisodeNumber": 0, + "sceneSeasonNumber": 0, + "tvDbEpisodeId": 0, + "series": { + "imdbId": "", + "tvdbId": 77754, + "tvRageId": 5574, + "tvMazeId": 3853, + "title": "The Andy Griffith Show", + "sortTitle": "andy griffith show", + "cleanTitle": "theandygriffithshow", + "seasonCount": 8, + "status": "ended", + "overview": "Down-home humor and an endearing cast of characters helped make The Andy Griffith Show one of the most beloved comedies in the history of TV. The show centered around widower Andy Taylor, who divided his time between raising his young son Opie, and his job as sheriff of the sleepy North Carolina town, Mayberry. Andy and Opie live with Andy's Aunt Bee, who serves as a surrogate mother to both father and son. Andy's nervous cousin, Barney Fife, is his deputy sheriff whose incompetence is tolerated because Mayberry is virtually crime-free.", + "airTime": "21:30", + "monitored": true, + "qualityProfileId": 1, + "seasonFolder": true, + "lastInfoSync": "2016-02-05T16:40:11.614176Z", + "runtime": 25, + "images": [ + { + "coverType": "fanart", + "url": "https://artworks.thetvdb.com/banners/fanart/original/77754-5.jpg" + }, + { + "coverType": "banner", + "url": "https://artworks.thetvdb.com/banners/graphical/77754-g.jpg" + }, + { + "coverType": "poster", + "url": "https://artworks.thetvdb.com/banners/posters/77754-4.jpg" + } + ], + "seriesType": "standard", + "network": "CBS", + "useSceneNumbering": false, + "titleSlug": "the-andy-griffith-show", + "certification": "TV-G", + "path": "F:\\The Andy Griffith Show", + "year": 1960, + "firstAired": "1960-02-15T06:00:00Z", + "genres": [ + "Comedy" + ], + "tags": [], + "added": "2008-02-04T13:44:24.204583Z", + "qualityProfile": { + "value": { + "name": "SD", + "allowed": [ + { + "id": 1, + "name": "SDTV", + "weight": 1 + }, + { + "id": 8, + "name": "WEBDL-480p", + "weight": 2 + }, + { + "id": 2, + "name": "DVD", + "weight": 3 + } + ], + "cutoff": { + "id": 1, + "name": "SDTV", + "weight": 1 + }, + "id": 1 + }, + "isLoaded": true + }, + "seasons": [ + { + "seasonNumber": 0, + "monitored": false + }, + { + "seasonNumber": 1, + "monitored": false + }, + { + "seasonNumber": 2, + "monitored": true + }, + { + "seasonNumber": 3, + "monitored": false + }, + { + "seasonNumber": 4, + "monitored": false + }, + { + "seasonNumber": 5, + "monitored": true + }, + { + "seasonNumber": 6, + "monitored": true + }, + { + "seasonNumber": 7, + "monitored": true + }, + { + "seasonNumber": 8, + "monitored": true + } + ], + "id": 17 + }, + "downloading": false, + "id": 889 + } + ] +} From 86582ad1ba0acd53baab7d50578a232556d2d110 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 29 May 2020 17:32:14 -0700 Subject: [PATCH 256/406] Fix lint on sonarr --- tests/components/sonarr/__init__.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/components/sonarr/__init__.py b/tests/components/sonarr/__init__.py index 49a092c97e7..c7f31c67742 100644 --- a/tests/components/sonarr/__init__.py +++ b/tests/components/sonarr/__init__.py @@ -74,43 +74,43 @@ def mock_connection( aioclient_mock.get( f"{sonarr_url}/system/status", - text=load_fixture(f"sonarr/system-status.json"), + text=load_fixture("sonarr/system-status.json"), headers={"Content-Type": "application/json"}, ) aioclient_mock.get( f"{sonarr_url}/diskspace", - text=load_fixture(f"sonarr/diskspace.json"), + text=load_fixture("sonarr/diskspace.json"), headers={"Content-Type": "application/json"}, ) aioclient_mock.get( f"{sonarr_url}/calendar", - text=load_fixture(f"sonarr/calendar.json"), + text=load_fixture("sonarr/calendar.json"), headers={"Content-Type": "application/json"}, ) aioclient_mock.get( f"{sonarr_url}/command", - text=load_fixture(f"sonarr/command.json"), + text=load_fixture("sonarr/command.json"), headers={"Content-Type": "application/json"}, ) aioclient_mock.get( f"{sonarr_url}/queue", - text=load_fixture(f"sonarr/queue.json"), + text=load_fixture("sonarr/queue.json"), headers={"Content-Type": "application/json"}, ) aioclient_mock.get( f"{sonarr_url}/series", - text=load_fixture(f"sonarr/series.json"), + text=load_fixture("sonarr/series.json"), headers={"Content-Type": "application/json"}, ) aioclient_mock.get( f"{sonarr_url}/wanted/missing", - text=load_fixture(f"sonarr/wanted-missing.json"), + text=load_fixture("sonarr/wanted-missing.json"), headers={"Content-Type": "application/json"}, ) From e1c6f010475f59b71c92a57b9cb0f62b91a3683f Mon Sep 17 00:00:00 2001 From: mlemainque Date: Sat, 30 May 2020 04:15:54 +0200 Subject: [PATCH 257/406] Fix reworded properties in 2.1.0 pydaikin release (#36257) --- homeassistant/components/daikin/sensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/daikin/sensor.py b/homeassistant/components/daikin/sensor.py index eeaa162c2d8..7ff79338a79 100644 --- a/homeassistant/components/daikin/sensor.py +++ b/homeassistant/components/daikin/sensor.py @@ -129,7 +129,7 @@ class DaikinPowerSensor(DaikinSensor): if self._device_attribute == ATTR_TOTAL_POWER: return round(self._api.device.current_total_power_consumption, 3) if self._device_attribute == ATTR_COOL_ENERGY: - return round(self._api.device.last_hour_cool_power_consumption, 3) + return round(self._api.device.last_hour_cool_energy_consumption, 3) if self._device_attribute == ATTR_HEAT_ENERGY: - return round(self._api.device.last_hour_heat_power_consumption, 3) + return round(self._api.device.last_hour_heat_energy_consumption, 3) return None From efec62d98ea3338c181593ebc46aac12b2f35f54 Mon Sep 17 00:00:00 2001 From: Fredrik Erlandsson Date: Sat, 30 May 2020 09:50:18 +0200 Subject: [PATCH 258/406] Add KEY_HOSTNAME for Daikin zeroconf (#36253) * Add KEY_HOSTNAME for Daikin zeroconf * Update tests and use CONF_HOST as hostname --- homeassistant/components/daikin/config_flow.py | 6 +++--- homeassistant/components/daikin/const.py | 1 + tests/components/daikin/test_config_flow.py | 8 ++++++-- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/daikin/config_flow.py b/homeassistant/components/daikin/config_flow.py index f999a5376b1..5f2a8a0c0b1 100644 --- a/homeassistant/components/daikin/config_flow.py +++ b/homeassistant/components/daikin/config_flow.py @@ -11,7 +11,7 @@ import voluptuous as vol from homeassistant import config_entries from homeassistant.const import CONF_HOST, CONF_PASSWORD -from .const import CONF_KEY, CONF_UUID, KEY_IP, KEY_MAC, TIMEOUT +from .const import CONF_KEY, CONF_UUID, KEY_HOSTNAME, KEY_IP, KEY_MAC, TIMEOUT _LOGGER = logging.getLogger(__name__) @@ -128,7 +128,7 @@ class FlowHandler(config_entries.ConfigFlow): async def async_step_zeroconf(self, discovery_info): """Prepare configuration for a discovered Daikin device.""" _LOGGER.debug("Zeroconf discovery_info: %s", discovery_info) - await self.async_set_unique_id(discovery_info.get(CONF_HOST)) + await self.async_set_unique_id(discovery_info[KEY_HOSTNAME]) self._abort_if_unique_id_configured() - self.host = discovery_info.get(CONF_HOST) + self.host = discovery_info[CONF_HOST] return await self.async_step_user() diff --git a/homeassistant/components/daikin/const.py b/homeassistant/components/daikin/const.py index 30d34b898d3..3e24325e5b1 100644 --- a/homeassistant/components/daikin/const.py +++ b/homeassistant/components/daikin/const.py @@ -64,5 +64,6 @@ CONF_UUID = "uuid" KEY_MAC = "mac" KEY_IP = "ip" +KEY_HOSTNAME = "hostname" TIMEOUT = 60 diff --git a/tests/components/daikin/test_config_flow.py b/tests/components/daikin/test_config_flow.py index 989064afe6c..67affc3d501 100644 --- a/tests/components/daikin/test_config_flow.py +++ b/tests/components/daikin/test_config_flow.py @@ -6,7 +6,7 @@ from aiohttp import ClientError from aiohttp.web_exceptions import HTTPForbidden import pytest -from homeassistant.components.daikin.const import KEY_IP, KEY_MAC +from homeassistant.components.daikin.const import KEY_HOSTNAME, KEY_IP, KEY_MAC from homeassistant.config_entries import ( SOURCE_DISCOVERY, SOURCE_IMPORT, @@ -112,7 +112,11 @@ async def test_device_abort(hass, mock_daikin, s_effect, reason): "source, data, unique_id", [ (SOURCE_DISCOVERY, {KEY_IP: HOST, KEY_MAC: MAC}, MAC), - (SOURCE_ZEROCONF, {CONF_HOST: HOST}, HOST), + ( + SOURCE_ZEROCONF, + {CONF_HOST: HOST, KEY_HOSTNAME: "DaikinUNIQE.local"}, + "DaikinUNIQE.local", + ), ], ) async def test_discovery_zeroconf(hass, mock_daikin, source, data, unique_id): From b6407f77dae2c28ba16e8bc976268d2bee2c84bf Mon Sep 17 00:00:00 2001 From: Barry Williams Date: Sat, 30 May 2020 13:40:35 +0100 Subject: [PATCH 259/406] Add service to openhome to invoke a pin (#31119) Setup platform async Use entity services Store UUID in default data rather than entity --- .coveragerc | 2 ++ homeassistant/components/openhome/const.py | 5 +++ .../components/openhome/manifest.json | 2 +- .../components/openhome/media_player.py | 35 +++++++++++++------ .../components/openhome/services.yaml | 11 ++++++ requirements_all.txt | 2 +- 6 files changed, 45 insertions(+), 12 deletions(-) create mode 100644 homeassistant/components/openhome/const.py create mode 100644 homeassistant/components/openhome/services.yaml diff --git a/.coveragerc b/.coveragerc index 7e0269784a4..5003e179b80 100644 --- a/.coveragerc +++ b/.coveragerc @@ -564,7 +564,9 @@ omit = homeassistant/components/openevse/sensor.py homeassistant/components/openexchangerates/sensor.py homeassistant/components/opengarage/cover.py + homeassistant/components/openhome/__init__.py homeassistant/components/openhome/media_player.py + homeassistant/components/openhome/const.py homeassistant/components/opensensemap/air_quality.py homeassistant/components/opensky/sensor.py homeassistant/components/opentherm_gw/__init__.py diff --git a/homeassistant/components/openhome/const.py b/homeassistant/components/openhome/const.py new file mode 100644 index 00000000000..09fcd2ef0e2 --- /dev/null +++ b/homeassistant/components/openhome/const.py @@ -0,0 +1,5 @@ +"""Constants for the Openhome component.""" +DOMAIN = "openhome" +SERVICE_INVOKE_PIN = "invoke_pin" +ATTR_PIN_INDEX = "pin" +DATA_OPENHOME = "openhome" diff --git a/homeassistant/components/openhome/manifest.json b/homeassistant/components/openhome/manifest.json index 8105c01dfc5..3a94215a7b1 100644 --- a/homeassistant/components/openhome/manifest.json +++ b/homeassistant/components/openhome/manifest.json @@ -2,6 +2,6 @@ "domain": "openhome", "name": "Linn / OpenHome", "documentation": "https://www.home-assistant.io/integrations/openhome", - "requirements": ["openhomedevice==0.6.3"], + "requirements": ["openhomedevice==0.7.2"], "codeowners": [] } diff --git a/homeassistant/components/openhome/media_player.py b/homeassistant/components/openhome/media_player.py index 4225228e271..b4258a88347 100644 --- a/homeassistant/components/openhome/media_player.py +++ b/homeassistant/components/openhome/media_player.py @@ -2,6 +2,7 @@ import logging from openhomedevice.Device import Device +import voluptuous as vol from homeassistant.components.media_player import MediaPlayerEntity from homeassistant.components.media_player.const import ( @@ -20,35 +21,45 @@ from homeassistant.components.media_player.const import ( SUPPORT_VOLUME_STEP, ) from homeassistant.const import STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING +from homeassistant.helpers import config_validation as cv, entity_platform + +from .const import ATTR_PIN_INDEX, DATA_OPENHOME, SERVICE_INVOKE_PIN SUPPORT_OPENHOME = SUPPORT_SELECT_SOURCE | SUPPORT_TURN_OFF | SUPPORT_TURN_ON _LOGGER = logging.getLogger(__name__) -DEVICES = [] - -def setup_platform(hass, config, add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the Openhome platform.""" if not discovery_info: - return True + return + + openhome_data = hass.data.setdefault(DATA_OPENHOME, set()) name = discovery_info.get("name") description = discovery_info.get("ssdp_description") + _LOGGER.info("Openhome device found: %s", name) - device = Device(description) + device = await hass.async_add_executor_job(Device, description) # if device has already been discovered - if device.Uuid() in [x.unique_id for x in DEVICES]: + if device.Uuid() in openhome_data: return True - device = OpenhomeDevice(hass, device) + entity = OpenhomeDevice(hass, device) - add_entities([device], True) - DEVICES.append(device) + async_add_entities([entity]) + openhome_data.add(device.Uuid()) - return True + platform = entity_platform.current_platform.get() + + platform.async_register_entity_service( + SERVICE_INVOKE_PIN, + {vol.Required(ATTR_PIN_INDEX): cv.positive_int}, + "invoke_pin", + ) class OpenhomeDevice(MediaPlayerEntity): @@ -162,6 +173,10 @@ class OpenhomeDevice(MediaPlayerEntity): """Select input source.""" self._device.SetSource(self._source_index[source]) + def invoke_pin(self, pin): + """Invoke pin.""" + self._device.InvokePin(pin) + @property def name(self): """Return the name of the device.""" diff --git a/homeassistant/components/openhome/services.yaml b/homeassistant/components/openhome/services.yaml new file mode 100644 index 00000000000..e8ae5fb55da --- /dev/null +++ b/homeassistant/components/openhome/services.yaml @@ -0,0 +1,11 @@ +# Describes the format for available openhome services + +invoke_pin: + description: Invoke a pin on the specified device. + fields: + entity_id: + description: The name of the openhome device to invoke the pin on + example: media_player.main_room + pin: + description: Which pin to invoke + example: 4 diff --git a/requirements_all.txt b/requirements_all.txt index bbccd9de4aa..d0811ef659c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1012,7 +1012,7 @@ openerz-api==0.1.0 openevsewifi==0.4 # homeassistant.components.openhome -openhomedevice==0.6.3 +openhomedevice==0.7.2 # homeassistant.components.opensensemap opensensemap-api==0.1.5 From 1855c9198831973b436591c40b3b937e89274bb4 Mon Sep 17 00:00:00 2001 From: Quentame Date: Sat, 30 May 2020 17:27:20 +0200 Subject: [PATCH 260/406] Use CONF_CLIENT_ID & CONF_CLIENT_SECRET from homeassistant.const (#36233) * Use CONF_CLIENT_ID & CONF_CLIENT_SECRET from homeassistant.const * Fix pylint * Use in tests * Search for "client_id" * Fix tests * Fix test * Fix test --- homeassistant/components/alexa/__init__.py | 4 +- homeassistant/components/alexa/auth.py | 10 +-- homeassistant/components/alexa/const.py | 2 - .../components/alexa/smart_home_http.py | 10 +-- homeassistant/components/almond/__init__.py | 11 ++- .../components/ambiclimate/__init__.py | 3 +- .../components/ambiclimate/climate.py | 10 ++- .../components/ambiclimate/config_flow.py | 3 +- homeassistant/components/ambiclimate/const.py | 7 +- .../components/cppm_tracker/device_tracker.py | 8 +- homeassistant/components/fitbit/sensor.py | 23 +++--- .../components/fleetgo/device_tracker.py | 9 ++- homeassistant/components/flume/__init__.py | 9 ++- homeassistant/components/flume/config_flow.py | 9 ++- homeassistant/components/flume/const.py | 2 - homeassistant/components/flume/sensor.py | 10 ++- homeassistant/components/google/__init__.py | 3 +- homeassistant/components/lametric/__init__.py | 4 +- .../components/logi_circle/__init__.py | 4 +- .../components/logi_circle/config_flow.py | 14 ++-- homeassistant/components/logi_circle/const.py | 7 +- homeassistant/components/lyft/sensor.py | 4 +- homeassistant/components/mastodon/notify.py | 4 +- homeassistant/components/mqtt/__init__.py | 2 +- homeassistant/components/nest/__init__.py | 4 +- homeassistant/components/plex/config_flow.py | 4 +- homeassistant/components/plex/const.py | 1 - homeassistant/components/plex/server.py | 7 +- homeassistant/components/point/__init__.py | 12 +-- homeassistant/components/point/config_flow.py | 19 ++--- homeassistant/components/point/const.py | 3 - homeassistant/components/reddit/sensor.py | 10 ++- homeassistant/components/smappee/__init__.py | 10 ++- .../components/smartthings/__init__.py | 16 ++-- .../components/smartthings/config_flow.py | 18 +++-- homeassistant/components/smartthings/const.py | 13 ++- homeassistant/components/somfy/__init__.py | 5 +- homeassistant/components/spotify/__init__.py | 11 +-- homeassistant/components/spotify/const.py | 3 - homeassistant/components/toon/__init__.py | 10 ++- homeassistant/components/toon/config_flow.py | 18 ++--- homeassistant/components/toon/const.py | 2 - homeassistant/components/twitch/sensor.py | 3 +- homeassistant/components/wink/__init__.py | 41 +++++----- homeassistant/components/withings/__init__.py | 28 +++---- .../components/withings/config_flow.py | 2 +- homeassistant/components/withings/const.py | 8 +- .../components/wunderlist/__init__.py | 4 +- tests/components/alexa/test_auth.py | 13 +-- tests/components/almond/test_config_flow.py | 19 ++--- .../ambiclimate/test_config_flow.py | 5 +- tests/components/flume/test_config_flow.py | 54 +++++++------ tests/components/google/test_init.py | 7 +- .../home_connect/test_config_flow.py | 6 +- tests/components/netatmo/test_config_flow.py | 3 +- tests/components/plex/const.py | 3 +- tests/components/point/test_config_flow.py | 5 +- tests/components/reddit/test_sensor.py | 20 +++-- tests/components/smartthings/conftest.py | 13 +-- .../smartthings/test_config_flow.py | 28 +++---- tests/components/somfy/test_config_flow.py | 20 ++--- tests/components/spotify/test_config_flow.py | 7 +- tests/components/toon/test_config_flow.py | 13 ++- tests/components/twitch/test_twitch.py | 5 +- tests/components/withings/common.py | 13 ++- tests/components/withings/test_init.py | 80 +++++++++---------- 66 files changed, 386 insertions(+), 352 deletions(-) diff --git a/homeassistant/components/alexa/__init__.py b/homeassistant/components/alexa/__init__.py index de5a67087ca..81b0f670058 100644 --- a/homeassistant/components/alexa/__init__.py +++ b/homeassistant/components/alexa/__init__.py @@ -3,15 +3,13 @@ import logging import voluptuous as vol -from homeassistant.const import CONF_NAME +from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET, CONF_NAME from homeassistant.core import callback from homeassistant.helpers import config_validation as cv, entityfilter from . import flash_briefings, intent, smart_home_http from .const import ( CONF_AUDIO, - CONF_CLIENT_ID, - CONF_CLIENT_SECRET, CONF_DESCRIPTION, CONF_DISPLAY_CATEGORIES, CONF_DISPLAY_URL, diff --git a/homeassistant/components/alexa/auth.py b/homeassistant/components/alexa/auth.py index 3b7984f56d3..090481876da 100644 --- a/homeassistant/components/alexa/auth.py +++ b/homeassistant/components/alexa/auth.py @@ -7,7 +7,7 @@ import logging import aiohttp import async_timeout -from homeassistant.const import HTTP_OK +from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET, HTTP_OK from homeassistant.core import callback from homeassistant.helpers import aiohttp_client from homeassistant.util import dt @@ -48,8 +48,8 @@ class Auth: lwa_params = { "grant_type": "authorization_code", "code": accept_grant_code, - "client_id": self.client_id, - "client_secret": self.client_secret, + CONF_CLIENT_ID: self.client_id, + CONF_CLIENT_SECRET: self.client_secret, } _LOGGER.debug( "Calling LWA to get the access token (first time), with: %s", @@ -80,8 +80,8 @@ class Auth: lwa_params = { "grant_type": "refresh_token", "refresh_token": self._prefs[STORAGE_REFRESH_TOKEN], - "client_id": self.client_id, - "client_secret": self.client_secret, + CONF_CLIENT_ID: self.client_id, + CONF_CLIENT_SECRET: self.client_secret, } _LOGGER.debug("Calling LWA to refresh the access token.") diff --git a/homeassistant/components/alexa/const.py b/homeassistant/components/alexa/const.py index 3dd50ce3b3e..50e3edb475c 100644 --- a/homeassistant/components/alexa/const.py +++ b/homeassistant/components/alexa/const.py @@ -18,8 +18,6 @@ CONF_DISPLAY_URL = "display_url" CONF_FILTER = "filter" CONF_ENTITY_CONFIG = "entity_config" CONF_ENDPOINT = "endpoint" -CONF_CLIENT_ID = "client_id" -CONF_CLIENT_SECRET = "client_secret" CONF_LOCALE = "locale" ATTR_UID = "uid" diff --git a/homeassistant/components/alexa/smart_home_http.py b/homeassistant/components/alexa/smart_home_http.py index 7c745f8afdd..41ebfb340eb 100644 --- a/homeassistant/components/alexa/smart_home_http.py +++ b/homeassistant/components/alexa/smart_home_http.py @@ -3,17 +3,11 @@ import logging from homeassistant import core from homeassistant.components.http.view import HomeAssistantView +from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET from .auth import Auth from .config import AbstractConfig -from .const import ( - CONF_CLIENT_ID, - CONF_CLIENT_SECRET, - CONF_ENDPOINT, - CONF_ENTITY_CONFIG, - CONF_FILTER, - CONF_LOCALE, -) +from .const import CONF_ENDPOINT, CONF_ENTITY_CONFIG, CONF_FILTER, CONF_LOCALE from .smart_home import async_handle_message from .state_report import async_enable_proactive_mode diff --git a/homeassistant/components/almond/__init__.py b/homeassistant/components/almond/__init__.py index 07daa3e4781..3710ff14b1a 100644 --- a/homeassistant/components/almond/__init__.py +++ b/homeassistant/components/almond/__init__.py @@ -13,7 +13,13 @@ import voluptuous as vol from homeassistant import config_entries from homeassistant.auth.const import GROUP_ID_ADMIN from homeassistant.components import conversation -from homeassistant.const import CONF_HOST, CONF_TYPE, EVENT_HOMEASSISTANT_START +from homeassistant.const import ( + CONF_CLIENT_ID, + CONF_CLIENT_SECRET, + CONF_HOST, + CONF_TYPE, + EVENT_HOMEASSISTANT_START, +) from homeassistant.core import Context, CoreState, HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import ( @@ -29,9 +35,6 @@ from homeassistant.helpers import ( from . import config_flow from .const import DOMAIN, TYPE_LOCAL, TYPE_OAUTH2 -CONF_CLIENT_ID = "client_id" -CONF_CLIENT_SECRET = "client_secret" - STORAGE_VERSION = 1 STORAGE_KEY = DOMAIN diff --git a/homeassistant/components/ambiclimate/__init__.py b/homeassistant/components/ambiclimate/__init__.py index e15f6dea2ec..490c41255bf 100644 --- a/homeassistant/components/ambiclimate/__init__.py +++ b/homeassistant/components/ambiclimate/__init__.py @@ -3,10 +3,11 @@ import logging import voluptuous as vol +from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET from homeassistant.helpers import config_validation as cv from . import config_flow -from .const import CONF_CLIENT_ID, CONF_CLIENT_SECRET, DOMAIN +from .const import DOMAIN _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/ambiclimate/climate.py b/homeassistant/components/ambiclimate/climate.py index cb19d1329ca..93b38974464 100644 --- a/homeassistant/components/ambiclimate/climate.py +++ b/homeassistant/components/ambiclimate/climate.py @@ -11,14 +11,18 @@ from homeassistant.components.climate.const import ( HVAC_MODE_OFF, SUPPORT_TARGET_TEMPERATURE, ) -from homeassistant.const import ATTR_NAME, ATTR_TEMPERATURE, TEMP_CELSIUS +from homeassistant.const import ( + ATTR_NAME, + ATTR_TEMPERATURE, + CONF_CLIENT_ID, + CONF_CLIENT_SECRET, + TEMP_CELSIUS, +) from homeassistant.helpers import config_validation as cv from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import ( ATTR_VALUE, - CONF_CLIENT_ID, - CONF_CLIENT_SECRET, DOMAIN, SERVICE_COMFORT_FEEDBACK, SERVICE_COMFORT_MODE, diff --git a/homeassistant/components/ambiclimate/config_flow.py b/homeassistant/components/ambiclimate/config_flow.py index 99d4aa3c944..2b88e7ab91e 100644 --- a/homeassistant/components/ambiclimate/config_flow.py +++ b/homeassistant/components/ambiclimate/config_flow.py @@ -5,6 +5,7 @@ import ambiclimate from homeassistant import config_entries from homeassistant.components.http import HomeAssistantView +from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET from homeassistant.core import callback from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.network import get_url @@ -12,8 +13,6 @@ from homeassistant.helpers.network import get_url from .const import ( AUTH_CALLBACK_NAME, AUTH_CALLBACK_PATH, - CONF_CLIENT_ID, - CONF_CLIENT_SECRET, DOMAIN, STORAGE_KEY, STORAGE_VERSION, diff --git a/homeassistant/components/ambiclimate/const.py b/homeassistant/components/ambiclimate/const.py index 833fef303f5..6393e97569a 100644 --- a/homeassistant/components/ambiclimate/const.py +++ b/homeassistant/components/ambiclimate/const.py @@ -1,12 +1,13 @@ """Constants used by the Ambiclimate component.""" -ATTR_VALUE = "value" -CONF_CLIENT_ID = "client_id" -CONF_CLIENT_SECRET = "client_secret" DOMAIN = "ambiclimate" + +ATTR_VALUE = "value" + SERVICE_COMFORT_FEEDBACK = "send_comfort_feedback" SERVICE_COMFORT_MODE = "set_comfort_mode" SERVICE_TEMPERATURE_MODE = "set_temperature_mode" + STORAGE_KEY = "ambiclimate_auth" STORAGE_VERSION = 1 diff --git a/homeassistant/components/cppm_tracker/device_tracker.py b/homeassistant/components/cppm_tracker/device_tracker.py index 1bb723091d4..a784fbd2f89 100644 --- a/homeassistant/components/cppm_tracker/device_tracker.py +++ b/homeassistant/components/cppm_tracker/device_tracker.py @@ -10,19 +10,17 @@ from homeassistant.components.device_tracker import ( PLATFORM_SCHEMA, DeviceScanner, ) -from homeassistant.const import CONF_API_KEY, CONF_HOST +from homeassistant.const import CONF_API_KEY, CONF_CLIENT_ID, CONF_HOST import homeassistant.helpers.config_validation as cv SCAN_INTERVAL = timedelta(seconds=120) -CLIENT_ID = "client_id" - GRANT_TYPE = "client_credentials" PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { vol.Required(CONF_HOST): cv.string, - vol.Required(CLIENT_ID): cv.string, + vol.Required(CONF_CLIENT_ID): cv.string, vol.Required(CONF_API_KEY): cv.string, } ) @@ -37,7 +35,7 @@ def get_scanner(hass, config): "server": config[DOMAIN][CONF_HOST], "grant_type": GRANT_TYPE, "secret": config[DOMAIN][CONF_API_KEY], - "client": config[DOMAIN][CLIENT_ID], + "client": config[DOMAIN][CONF_CLIENT_ID], } cppm = ClearPass(data) if cppm.access_token is None: diff --git a/homeassistant/components/fitbit/sensor.py b/homeassistant/components/fitbit/sensor.py index 7390603c8fa..7e713505a0e 100644 --- a/homeassistant/components/fitbit/sensor.py +++ b/homeassistant/components/fitbit/sensor.py @@ -13,6 +13,8 @@ from homeassistant.components.http import HomeAssistantView from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( ATTR_ATTRIBUTION, + CONF_CLIENT_ID, + CONF_CLIENT_SECRET, CONF_UNIT_SYSTEM, MASS_KILOGRAMS, MASS_MILLIGRAMS, @@ -32,8 +34,6 @@ _LOGGER = logging.getLogger(__name__) ATTR_ACCESS_TOKEN = "access_token" ATTR_REFRESH_TOKEN = "refresh_token" -ATTR_CLIENT_ID = "client_id" -ATTR_CLIENT_SECRET = "client_secret" ATTR_LAST_SAVED_AT = "last_saved_at" CONF_MONITORED_RESOURCES = "monitored_resources" @@ -47,7 +47,10 @@ FITBIT_DEFAULT_RESOURCES = ["activities/steps"] SCAN_INTERVAL = datetime.timedelta(minutes=30) -DEFAULT_CONFIG = {"client_id": "CLIENT_ID_HERE", "client_secret": "CLIENT_SECRET_HERE"} +DEFAULT_CONFIG = { + CONF_CLIENT_ID: "CLIENT_ID_HERE", + CONF_CLIENT_SECRET: "CLIENT_SECRET_HERE", +} FITBIT_RESOURCES_LIST = { "activities/activityCalories": ["Activity Calories", "cal", "fire"], @@ -251,8 +254,8 @@ def setup_platform(hass, config, add_entities, discovery_info=None): expires_at = config_file.get(ATTR_LAST_SAVED_AT) if None not in (access_token, refresh_token): authd_client = Fitbit( - config_file.get(ATTR_CLIENT_ID), - config_file.get(ATTR_CLIENT_SECRET), + config_file.get(CONF_CLIENT_ID), + config_file.get(CONF_CLIENT_SECRET), access_token=access_token, refresh_token=refresh_token, expires_at=expires_at, @@ -305,7 +308,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): else: oauth = FitbitOauth2Client( - config_file.get(ATTR_CLIENT_ID), config_file.get(ATTR_CLIENT_SECRET) + config_file.get(CONF_CLIENT_ID), config_file.get(CONF_CLIENT_SECRET) ) redirect_uri = f"{get_url(hass)}{FITBIT_AUTH_CALLBACK_PATH}" @@ -388,8 +391,8 @@ class FitbitAuthCallbackView(HomeAssistantView): config_contents = { ATTR_ACCESS_TOKEN: result.get("access_token"), ATTR_REFRESH_TOKEN: result.get("refresh_token"), - ATTR_CLIENT_ID: self.oauth.client_id, - ATTR_CLIENT_SECRET: self.oauth.client_secret, + CONF_CLIENT_ID: self.oauth.client_id, + CONF_CLIENT_SECRET: self.oauth.client_secret, ATTR_LAST_SAVED_AT: int(time.time()), } save_json(hass.config.path(FITBIT_CONFIG_FILE), config_contents) @@ -514,8 +517,8 @@ class FitbitSensor(Entity): config_contents = { ATTR_ACCESS_TOKEN: token.get("access_token"), ATTR_REFRESH_TOKEN: token.get("refresh_token"), - ATTR_CLIENT_ID: self.client.client.client_id, - ATTR_CLIENT_SECRET: self.client.client.client_secret, + CONF_CLIENT_ID: self.client.client.client_id, + CONF_CLIENT_SECRET: self.client.client.client_secret, ATTR_LAST_SAVED_AT: int(time.time()), } save_json(self.config_path, config_contents) diff --git a/homeassistant/components/fleetgo/device_tracker.py b/homeassistant/components/fleetgo/device_tracker.py index 5a922ed4b92..d46bbc9c85b 100644 --- a/homeassistant/components/fleetgo/device_tracker.py +++ b/homeassistant/components/fleetgo/device_tracker.py @@ -6,14 +6,17 @@ from ritassist import API import voluptuous as vol from homeassistant.components.device_tracker import PLATFORM_SCHEMA -from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +from homeassistant.const import ( + CONF_CLIENT_ID, + CONF_CLIENT_SECRET, + CONF_PASSWORD, + CONF_USERNAME, +) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import track_utc_time_change _LOGGER = logging.getLogger(__name__) -CONF_CLIENT_ID = "client_id" -CONF_CLIENT_SECRET = "client_secret" CONF_INCLUDE = "include" PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( diff --git a/homeassistant/components/flume/__init__.py b/homeassistant/components/flume/__init__.py index 2c18864194e..cc618f64b4e 100644 --- a/homeassistant/components/flume/__init__.py +++ b/homeassistant/components/flume/__init__.py @@ -8,14 +8,17 @@ from requests import Session from requests.exceptions import RequestException from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +from homeassistant.const import ( + CONF_CLIENT_ID, + CONF_CLIENT_SECRET, + CONF_PASSWORD, + CONF_USERNAME, +) from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady from .const import ( BASE_TOKEN_FILENAME, - CONF_CLIENT_ID, - CONF_CLIENT_SECRET, DOMAIN, FLUME_AUTH, FLUME_DEVICES, diff --git a/homeassistant/components/flume/config_flow.py b/homeassistant/components/flume/config_flow.py index 3232245a4a9..f26be5f1e1d 100644 --- a/homeassistant/components/flume/config_flow.py +++ b/homeassistant/components/flume/config_flow.py @@ -7,9 +7,14 @@ from requests.exceptions import RequestException import voluptuous as vol from homeassistant import config_entries, core, exceptions -from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +from homeassistant.const import ( + CONF_CLIENT_ID, + CONF_CLIENT_SECRET, + CONF_PASSWORD, + CONF_USERNAME, +) -from .const import BASE_TOKEN_FILENAME, CONF_CLIENT_ID, CONF_CLIENT_SECRET +from .const import BASE_TOKEN_FILENAME from .const import DOMAIN # pylint:disable=unused-import _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/flume/const.py b/homeassistant/components/flume/const.py index 17bbb60edb0..4f05b93ea22 100644 --- a/homeassistant/components/flume/const.py +++ b/homeassistant/components/flume/const.py @@ -5,8 +5,6 @@ PLATFORMS = ["sensor"] DEFAULT_NAME = "Flume Sensor" -CONF_CLIENT_ID = "client_id" -CONF_CLIENT_SECRET = "client_secret" FLUME_TYPE_SENSOR = 2 FLUME_AUTH = "flume_auth" diff --git a/homeassistant/components/flume/sensor.py b/homeassistant/components/flume/sensor.py index 21a19a3a56c..9ec54ca1a8c 100644 --- a/homeassistant/components/flume/sensor.py +++ b/homeassistant/components/flume/sensor.py @@ -7,14 +7,18 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.config_entries import SOURCE_IMPORT -from homeassistant.const import CONF_NAME, CONF_PASSWORD, CONF_USERNAME +from homeassistant.const import ( + CONF_CLIENT_ID, + CONF_CLIENT_SECRET, + CONF_NAME, + CONF_PASSWORD, + CONF_USERNAME, +) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle from .const import ( - CONF_CLIENT_ID, - CONF_CLIENT_SECRET, DEFAULT_NAME, DOMAIN, FLUME_AUTH, diff --git a/homeassistant/components/google/__init__.py b/homeassistant/components/google/__init__.py index 93afb43cf52..4f1accec4e0 100644 --- a/homeassistant/components/google/__init__.py +++ b/homeassistant/components/google/__init__.py @@ -15,6 +15,7 @@ import voluptuous as vol from voluptuous.error import Error as VoluptuousError import yaml +from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import generate_entity_id @@ -26,8 +27,6 @@ _LOGGER = logging.getLogger(__name__) DOMAIN = "google" ENTITY_ID_FORMAT = DOMAIN + ".{}" -CONF_CLIENT_ID = "client_id" -CONF_CLIENT_SECRET = "client_secret" CONF_TRACK_NEW = "track_new_calendar" CONF_CAL_ID = "cal_id" diff --git a/homeassistant/components/lametric/__init__.py b/homeassistant/components/lametric/__init__.py index 9281affa492..797f0982f00 100644 --- a/homeassistant/components/lametric/__init__.py +++ b/homeassistant/components/lametric/__init__.py @@ -4,14 +4,14 @@ import logging from lmnotify import LaMetricManager import voluptuous as vol +from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -CONF_CLIENT_ID = "client_id" -CONF_CLIENT_SECRET = "client_secret" DOMAIN = "lametric" + LAMETRIC_DEVICES = "LAMETRIC_DEVICES" CONFIG_SCHEMA = vol.Schema( diff --git a/homeassistant/components/logi_circle/__init__.py b/homeassistant/components/logi_circle/__init__.py index 0a6d471889f..87ee2e6e384 100644 --- a/homeassistant/components/logi_circle/__init__.py +++ b/homeassistant/components/logi_circle/__init__.py @@ -12,6 +12,8 @@ from homeassistant import config_entries from homeassistant.components.camera import ATTR_FILENAME, CAMERA_SERVICE_SCHEMA from homeassistant.const import ( ATTR_MODE, + CONF_CLIENT_ID, + CONF_CLIENT_SECRET, CONF_MONITORED_CONDITIONS, CONF_SENSORS, EVENT_HOMEASSISTANT_STOP, @@ -22,8 +24,6 @@ from homeassistant.helpers.dispatcher import async_dispatcher_send from . import config_flow from .const import ( CONF_API_KEY, - CONF_CLIENT_ID, - CONF_CLIENT_SECRET, CONF_REDIRECT_URI, DATA_LOGI, DEFAULT_CACHEDB, diff --git a/homeassistant/components/logi_circle/config_flow.py b/homeassistant/components/logi_circle/config_flow.py index 527353eebf6..55b617c0748 100644 --- a/homeassistant/components/logi_circle/config_flow.py +++ b/homeassistant/components/logi_circle/config_flow.py @@ -9,17 +9,15 @@ import voluptuous as vol from homeassistant import config_entries from homeassistant.components.http import HomeAssistantView -from homeassistant.const import CONF_SENSORS, HTTP_BAD_REQUEST -from homeassistant.core import callback - -from .const import ( - CONF_API_KEY, +from homeassistant.const import ( CONF_CLIENT_ID, CONF_CLIENT_SECRET, - CONF_REDIRECT_URI, - DEFAULT_CACHEDB, - DOMAIN, + CONF_SENSORS, + HTTP_BAD_REQUEST, ) +from homeassistant.core import callback + +from .const import CONF_API_KEY, CONF_REDIRECT_URI, DEFAULT_CACHEDB, DOMAIN _TIMEOUT = 15 # seconds diff --git a/homeassistant/components/logi_circle/const.py b/homeassistant/components/logi_circle/const.py index 333a85e9b77..a0905aee63e 100644 --- a/homeassistant/components/logi_circle/const.py +++ b/homeassistant/components/logi_circle/const.py @@ -1,15 +1,14 @@ """Constants in Logi Circle component.""" from homeassistant.const import UNIT_PERCENTAGE -CONF_CLIENT_ID = "client_id" -CONF_CLIENT_SECRET = "client_secret" +DOMAIN = "logi_circle" +DATA_LOGI = DOMAIN + CONF_API_KEY = "api_key" CONF_REDIRECT_URI = "redirect_uri" DEFAULT_CACHEDB = ".logi_cache.pickle" -DOMAIN = "logi_circle" -DATA_LOGI = DOMAIN LED_MODE_KEY = "LED" RECORDING_MODE_KEY = "RECORDING_MODE" diff --git a/homeassistant/components/lyft/sensor.py b/homeassistant/components/lyft/sensor.py index 5e8555f857d..98084b28f0c 100644 --- a/homeassistant/components/lyft/sensor.py +++ b/homeassistant/components/lyft/sensor.py @@ -8,15 +8,13 @@ from lyft_rides.errors import APIError import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import TIME_MINUTES +from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET, TIME_MINUTES import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) -CONF_CLIENT_ID = "client_id" -CONF_CLIENT_SECRET = "client_secret" CONF_END_LATITUDE = "end_latitude" CONF_END_LONGITUDE = "end_longitude" CONF_PRODUCT_IDS = "product_ids" diff --git a/homeassistant/components/mastodon/notify.py b/homeassistant/components/mastodon/notify.py index 88716de5773..058137393f5 100644 --- a/homeassistant/components/mastodon/notify.py +++ b/homeassistant/components/mastodon/notify.py @@ -6,14 +6,12 @@ from mastodon.Mastodon import MastodonAPIError, MastodonUnauthorizedError import voluptuous as vol from homeassistant.components.notify import PLATFORM_SCHEMA, BaseNotificationService -from homeassistant.const import CONF_ACCESS_TOKEN +from homeassistant.const import CONF_ACCESS_TOKEN, CONF_CLIENT_ID, CONF_CLIENT_SECRET import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) CONF_BASE_URL = "base_url" -CONF_CLIENT_ID = "client_id" -CONF_CLIENT_SECRET = "client_secret" DEFAULT_URL = "https://mastodon.social" diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index a1eaf2f3a2a..2be31895979 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -18,6 +18,7 @@ import voluptuous as vol from homeassistant import config_entries from homeassistant.components import websocket_api from homeassistant.const import ( + CONF_CLIENT_ID, CONF_DEVICE, CONF_NAME, CONF_PASSWORD, @@ -70,7 +71,6 @@ SERVICE_DUMP = "dump" CONF_EMBEDDED = "embedded" -CONF_CLIENT_ID = "client_id" CONF_DISCOVERY_PREFIX = "discovery_prefix" CONF_KEEPALIVE = "keepalive" CONF_CERTIFICATE = "certificate" diff --git a/homeassistant/components/nest/__init__.py b/homeassistant/components/nest/__init__.py index f92f6466156..8ddd6da6dcf 100644 --- a/homeassistant/components/nest/__init__.py +++ b/homeassistant/components/nest/__init__.py @@ -10,6 +10,8 @@ import voluptuous as vol from homeassistant import config_entries from homeassistant.const import ( CONF_BINARY_SENSORS, + CONF_CLIENT_ID, + CONF_CLIENT_SECRET, CONF_FILENAME, CONF_MONITORED_CONDITIONS, CONF_SENSORS, @@ -37,8 +39,6 @@ DATA_NEST_CONFIG = "nest_config" SIGNAL_NEST_UPDATE = "nest_update" NEST_CONFIG_FILE = "nest.conf" -CONF_CLIENT_ID = "client_id" -CONF_CLIENT_SECRET = "client_secret" ATTR_ETA = "eta" ATTR_ETA_WINDOW = "eta_window" diff --git a/homeassistant/components/plex/config_flow.py b/homeassistant/components/plex/config_flow.py index e4d3751f661..a4e2bb0a589 100644 --- a/homeassistant/components/plex/config_flow.py +++ b/homeassistant/components/plex/config_flow.py @@ -13,6 +13,7 @@ from homeassistant import config_entries from homeassistant.components.http.view import HomeAssistantView from homeassistant.components.media_player import DOMAIN as MP_DOMAIN from homeassistant.const import ( + CONF_CLIENT_ID, CONF_HOST, CONF_PORT, CONF_SSL, @@ -29,7 +30,6 @@ from .const import ( # pylint: disable=unused-import AUTH_CALLBACK_NAME, AUTH_CALLBACK_PATH, AUTOMATIC_SETUP_STRING, - CONF_CLIENT_IDENTIFIER, CONF_IGNORE_NEW_SHARED_USERS, CONF_IGNORE_PLEX_WEB_CLIENTS, CONF_MONITORED_USERS, @@ -227,7 +227,7 @@ class PlexFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): entry_config = {CONF_URL: url} if self.client_id: - entry_config[CONF_CLIENT_IDENTIFIER] = self.client_id + entry_config[CONF_CLIENT_ID] = self.client_id if token: entry_config[CONF_TOKEN] = token if url.startswith("https"): diff --git a/homeassistant/components/plex/const.py b/homeassistant/components/plex/const.py index d17710c4436..0ab76e10f50 100644 --- a/homeassistant/components/plex/const.py +++ b/homeassistant/components/plex/const.py @@ -24,7 +24,6 @@ PLEX_UPDATE_MEDIA_PLAYER_SIGNAL = "plex_update_mp_signal.{}" PLEX_UPDATE_PLATFORMS_SIGNAL = "plex_update_platforms_signal.{}" PLEX_UPDATE_SENSOR_SIGNAL = "plex_update_sensor_signal.{}" -CONF_CLIENT_IDENTIFIER = "client_id" CONF_SERVER = "server" CONF_SERVER_IDENTIFIER = "server_id" CONF_USE_EPISODE_ART = "use_episode_art" diff --git a/homeassistant/components/plex/server.py b/homeassistant/components/plex/server.py index 88b4b533f88..cf0a90e2b0a 100644 --- a/homeassistant/components/plex/server.py +++ b/homeassistant/components/plex/server.py @@ -17,13 +17,12 @@ from homeassistant.components.media_player.const import ( MEDIA_TYPE_PLAYLIST, MEDIA_TYPE_VIDEO, ) -from homeassistant.const import CONF_TOKEN, CONF_URL, CONF_VERIFY_SSL +from homeassistant.const import CONF_CLIENT_ID, CONF_TOKEN, CONF_URL, CONF_VERIFY_SSL from homeassistant.core import callback from homeassistant.helpers.debounce import Debouncer from homeassistant.helpers.dispatcher import async_dispatcher_send from .const import ( - CONF_CLIENT_IDENTIFIER, CONF_IGNORE_NEW_SHARED_USERS, CONF_IGNORE_PLEX_WEB_CLIENTS, CONF_MONITORED_USERS, @@ -81,8 +80,8 @@ class PlexServer: ).async_call # Header conditionally added as it is not available in config entry v1 - if CONF_CLIENT_IDENTIFIER in server_config: - plexapi.X_PLEX_IDENTIFIER = server_config[CONF_CLIENT_IDENTIFIER] + if CONF_CLIENT_ID in server_config: + plexapi.X_PLEX_IDENTIFIER = server_config[CONF_CLIENT_ID] plexapi.myplex.BASE_HEADERS = plexapi.reset_base_headers() plexapi.server.BASE_HEADERS = plexapi.reset_base_headers() diff --git a/homeassistant/components/point/__init__.py b/homeassistant/components/point/__init__.py index 2d6f39feb11..ba6c621dbd6 100644 --- a/homeassistant/components/point/__init__.py +++ b/homeassistant/components/point/__init__.py @@ -7,7 +7,12 @@ import voluptuous as vol from homeassistant import config_entries from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_TOKEN, CONF_WEBHOOK_ID +from homeassistant.const import ( + CONF_CLIENT_ID, + CONF_CLIENT_SECRET, + CONF_TOKEN, + CONF_WEBHOOK_ID, +) from homeassistant.helpers import config_validation as cv from homeassistant.helpers.dispatcher import ( async_dispatcher_connect, @@ -31,9 +36,6 @@ from .const import ( _LOGGER = logging.getLogger(__name__) -CONF_CLIENT_ID = "client_id" -CONF_CLIENT_SECRET = "client_secret" - DATA_CONFIG_ENTRY_LOCK = "point_config_entry_lock" CONFIG_ENTRY_IS_SETUP = "point_config_entry_is_setup" @@ -82,7 +84,7 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry): # Force token update. entry.data[CONF_TOKEN]["expires_in"] = -1 session = PointSession( - entry.data["refresh_args"]["client_id"], + entry.data["refresh_args"][CONF_CLIENT_ID], token=entry.data[CONF_TOKEN], auto_refresh_kwargs=entry.data["refresh_args"], token_saver=token_saver, diff --git a/homeassistant/components/point/config_flow.py b/homeassistant/components/point/config_flow.py index 3312931085e..5a343f276a7 100644 --- a/homeassistant/components/point/config_flow.py +++ b/homeassistant/components/point/config_flow.py @@ -9,9 +9,10 @@ import voluptuous as vol from homeassistant import config_entries from homeassistant.components.http import HomeAssistantView +from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET from homeassistant.core import callback -from .const import CLIENT_ID, CLIENT_SECRET, DOMAIN +from .const import DOMAIN AUTH_CALLBACK_PATH = "/api/minut" AUTH_CALLBACK_NAME = "api:minut" @@ -34,8 +35,8 @@ def register_flow_implementation(hass, domain, client_id, client_secret): hass.data[DATA_FLOW_IMPL] = OrderedDict() hass.data[DATA_FLOW_IMPL][domain] = { - CLIENT_ID: client_id, - CLIENT_SECRET: client_secret, + CONF_CLIENT_ID: client_id, + CONF_CLIENT_SECRET: client_secret, } @@ -112,8 +113,8 @@ class PointFlowHandler(config_entries.ConfigFlow): """Create Minut Point session and get authorization url.""" flow = self.hass.data[DATA_FLOW_IMPL][self.flow_impl] - client_id = flow[CLIENT_ID] - client_secret = flow[CLIENT_SECRET] + client_id = flow[CONF_CLIENT_ID] + client_secret = flow[CONF_CLIENT_SECRET] point_session = PointSession(client_id, client_secret=client_secret) self.hass.http.register_view(MinutAuthCallbackView()) @@ -140,8 +141,8 @@ class PointFlowHandler(config_entries.ConfigFlow): """Create point session and entries.""" flow = self.hass.data[DATA_FLOW_IMPL][DOMAIN] - client_id = flow[CLIENT_ID] - client_secret = flow[CLIENT_SECRET] + client_id = flow[CONF_CLIENT_ID] + client_secret = flow[CONF_CLIENT_SECRET] point_session = PointSession(client_id, client_secret=client_secret) token = await self.hass.async_add_executor_job( point_session.get_access_token, code @@ -159,8 +160,8 @@ class PointFlowHandler(config_entries.ConfigFlow): data={ "token": token, "refresh_args": { - "client_id": client_id, - "client_secret": client_secret, + CONF_CLIENT_ID: client_id, + CONF_CLIENT_SECRET: client_secret, }, }, ) diff --git a/homeassistant/components/point/const.py b/homeassistant/components/point/const.py index 5e78f7ae24e..c21971185f9 100644 --- a/homeassistant/components/point/const.py +++ b/homeassistant/components/point/const.py @@ -2,9 +2,6 @@ from datetime import timedelta DOMAIN = "point" -CLIENT_ID = "client_id" -CLIENT_SECRET = "client_secret" - SCAN_INTERVAL = timedelta(minutes=1) diff --git a/homeassistant/components/reddit/sensor.py b/homeassistant/components/reddit/sensor.py index ed24dfe47df..0fe4e87f863 100644 --- a/homeassistant/components/reddit/sensor.py +++ b/homeassistant/components/reddit/sensor.py @@ -6,14 +6,18 @@ import praw import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import CONF_MAXIMUM, CONF_PASSWORD, CONF_USERNAME +from homeassistant.const import ( + CONF_CLIENT_ID, + CONF_CLIENT_SECRET, + CONF_MAXIMUM, + CONF_PASSWORD, + CONF_USERNAME, +) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) -CONF_CLIENT_ID = "client_id" -CONF_CLIENT_SECRET = "client_secret" CONF_SORT_BY = "sort_by" CONF_SUBREDDITS = "subreddits" diff --git a/homeassistant/components/smappee/__init__.py b/homeassistant/components/smappee/__init__.py index c31ab97cb95..d230661a9f2 100644 --- a/homeassistant/components/smappee/__init__.py +++ b/homeassistant/components/smappee/__init__.py @@ -7,7 +7,13 @@ from requests.exceptions import RequestException import smappy import voluptuous as vol -from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME +from homeassistant.const import ( + CONF_CLIENT_ID, + CONF_CLIENT_SECRET, + CONF_HOST, + CONF_PASSWORD, + CONF_USERNAME, +) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.discovery import load_platform from homeassistant.util import Throttle @@ -17,8 +23,6 @@ _LOGGER = logging.getLogger(__name__) DEFAULT_NAME = "Smappee" DEFAULT_HOST_PASSWORD = "admin" -CONF_CLIENT_ID = "client_id" -CONF_CLIENT_SECRET = "client_secret" CONF_HOST_PASSWORD = "host_password" DOMAIN = "smappee" diff --git a/homeassistant/components/smartthings/__init__.py b/homeassistant/components/smartthings/__init__.py index e4d720c94e5..479df05fbb4 100644 --- a/homeassistant/components/smartthings/__init__.py +++ b/homeassistant/components/smartthings/__init__.py @@ -9,7 +9,12 @@ from pysmartapp.event import EVENT_TYPE_DEVICE from pysmartthings import Attribute, Capability, SmartThings from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_ACCESS_TOKEN, HTTP_FORBIDDEN +from homeassistant.const import ( + CONF_ACCESS_TOKEN, + CONF_CLIENT_ID, + CONF_CLIENT_SECRET, + HTTP_FORBIDDEN, +) from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.dispatcher import ( @@ -25,8 +30,6 @@ from .const import ( CONF_APP_ID, CONF_INSTALLED_APP_ID, CONF_LOCATION_ID, - CONF_OAUTH_CLIENT_ID, - CONF_OAUTH_CLIENT_SECRET, CONF_REFRESH_TOKEN, DATA_BROKERS, DATA_MANAGER, @@ -115,8 +118,8 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry): # Get SmartApp token to sync subscriptions token = await api.generate_tokens( - entry.data[CONF_OAUTH_CLIENT_ID], - entry.data[CONF_OAUTH_CLIENT_SECRET], + entry.data[CONF_CLIENT_ID], + entry.data[CONF_CLIENT_SECRET], entry.data[CONF_REFRESH_TOKEN], ) hass.config_entries.async_update_entry( @@ -312,8 +315,7 @@ class DeviceBroker: async def regenerate_refresh_token(now): """Generate a new refresh token and update the config entry.""" await self._token.refresh( - self._entry.data[CONF_OAUTH_CLIENT_ID], - self._entry.data[CONF_OAUTH_CLIENT_SECRET], + self._entry.data[CONF_CLIENT_ID], self._entry.data[CONF_CLIENT_SECRET], ) self._hass.config_entries.async_update_entry( self._entry, diff --git a/homeassistant/components/smartthings/config_flow.py b/homeassistant/components/smartthings/config_flow.py index c03ade4d8b1..f8746b597de 100644 --- a/homeassistant/components/smartthings/config_flow.py +++ b/homeassistant/components/smartthings/config_flow.py @@ -7,7 +7,13 @@ 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, HTTP_UNAUTHORIZED +from homeassistant.const import ( + CONF_ACCESS_TOKEN, + CONF_CLIENT_ID, + CONF_CLIENT_SECRET, + HTTP_FORBIDDEN, + HTTP_UNAUTHORIZED, +) from homeassistant.helpers.aiohttp_client import async_get_clientsession # pylint: disable=unused-import @@ -17,8 +23,6 @@ from .const import ( CONF_APP_ID, CONF_INSTALLED_APP_ID, CONF_LOCATION_ID, - CONF_OAUTH_CLIENT_ID, - CONF_OAUTH_CLIENT_SECRET, CONF_REFRESH_TOKEN, DOMAIN, VAL_UID_MATCHER, @@ -112,8 +116,8 @@ class SmartThingsFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): None, ) if existing: - self.oauth_client_id = existing.data[CONF_OAUTH_CLIENT_ID] - self.oauth_client_secret = existing.data[CONF_OAUTH_CLIENT_SECRET] + self.oauth_client_id = existing.data[CONF_CLIENT_ID] + self.oauth_client_secret = existing.data[CONF_CLIENT_SECRET] else: # Get oauth client id/secret by regenerating it app_oauth = AppOAuth(app.app_id) @@ -227,8 +231,8 @@ class SmartThingsFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): data = { CONF_ACCESS_TOKEN: self.access_token, CONF_REFRESH_TOKEN: self.refresh_token, - CONF_OAUTH_CLIENT_ID: self.oauth_client_id, - CONF_OAUTH_CLIENT_SECRET: self.oauth_client_secret, + CONF_CLIENT_ID: self.oauth_client_id, + CONF_CLIENT_SECRET: self.oauth_client_secret, CONF_LOCATION_ID: self.location_id, CONF_APP_ID: self.app_id, CONF_INSTALLED_APP_ID: self.installed_app_id, diff --git a/homeassistant/components/smartthings/const.py b/homeassistant/components/smartthings/const.py index 8e323c0a715..9d779bf9d5b 100644 --- a/homeassistant/components/smartthings/const.py +++ b/homeassistant/components/smartthings/const.py @@ -2,26 +2,31 @@ from datetime import timedelta import re +DOMAIN = "smartthings" + APP_OAUTH_CLIENT_NAME = "Home Assistant" APP_OAUTH_SCOPES = ["r:devices:*"] APP_NAME_PREFIX = "homeassistant." + CONF_APP_ID = "app_id" CONF_CLOUDHOOK_URL = "cloudhook_url" CONF_INSTALLED_APP_ID = "installed_app_id" CONF_INSTANCE_ID = "instance_id" CONF_LOCATION_ID = "location_id" -CONF_OAUTH_CLIENT_ID = "client_id" -CONF_OAUTH_CLIENT_SECRET = "client_secret" CONF_REFRESH_TOKEN = "refresh_token" + DATA_MANAGER = "manager" DATA_BROKERS = "brokers" -DOMAIN = "smartthings" EVENT_BUTTON = "smartthings.button" + SIGNAL_SMARTTHINGS_UPDATE = "smartthings_update" SIGNAL_SMARTAPP_PREFIX = "smartthings_smartap_" + SETTINGS_INSTANCE_ID = "hassInstanceId" + STORAGE_KEY = DOMAIN STORAGE_VERSION = 1 + # Ordered 'specific to least-specific platform' in order for capabilities # to be drawn-down and represented by the most appropriate platform. SUPPORTED_PLATFORMS = [ @@ -35,6 +40,8 @@ SUPPORTED_PLATFORMS = [ "sensor", "scene", ] + TOKEN_REFRESH_INTERVAL = timedelta(days=14) + VAL_UID = "^(?:([0-9a-fA-F]{32})|([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}))$" VAL_UID_MATCHER = re.compile(VAL_UID) diff --git a/homeassistant/components/somfy/__init__.py b/homeassistant/components/somfy/__init__.py index 40692b9d459..fb20fcd6683 100644 --- a/homeassistant/components/somfy/__init__.py +++ b/homeassistant/components/somfy/__init__.py @@ -9,6 +9,7 @@ import voluptuous as vol from homeassistant.components.somfy import config_flow from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET from homeassistant.helpers import ( config_entry_oauth2_flow, config_validation as cv, @@ -19,6 +20,7 @@ from homeassistant.helpers.typing import HomeAssistantType from homeassistant.util import Throttle from . import api +from .const import DOMAIN API = "api" @@ -28,10 +30,7 @@ _LOGGER = logging.getLogger(__name__) SCAN_INTERVAL = timedelta(seconds=30) -DOMAIN = "somfy" -CONF_CLIENT_ID = "client_id" -CONF_CLIENT_SECRET = "client_secret" CONF_OPTIMISTIC = "optimistic" SOMFY_AUTH_CALLBACK_PATH = "/auth/somfy/callback" diff --git a/homeassistant/components/spotify/__init__.py b/homeassistant/components/spotify/__init__.py index 9e5feb1c582..619bcdb471f 100644 --- a/homeassistant/components/spotify/__init__.py +++ b/homeassistant/components/spotify/__init__.py @@ -6,7 +6,7 @@ import voluptuous as vol from homeassistant.components.media_player import DOMAIN as MEDIA_PLAYER_DOMAIN from homeassistant.components.spotify import config_flow from homeassistant.config_entries import ConfigEntry -from homeassistant.const import ATTR_CREDENTIALS +from homeassistant.const import ATTR_CREDENTIALS, CONF_CLIENT_ID, CONF_CLIENT_SECRET from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import config_entry_oauth2_flow, config_validation as cv @@ -16,14 +16,7 @@ from homeassistant.helpers.config_entry_oauth2_flow import ( ) from homeassistant.helpers.typing import ConfigType -from .const import ( - CONF_CLIENT_ID, - CONF_CLIENT_SECRET, - DATA_SPOTIFY_CLIENT, - DATA_SPOTIFY_ME, - DATA_SPOTIFY_SESSION, - DOMAIN, -) +from .const import DATA_SPOTIFY_CLIENT, DATA_SPOTIFY_ME, DATA_SPOTIFY_SESSION, DOMAIN CONFIG_SCHEMA = vol.Schema( { diff --git a/homeassistant/components/spotify/const.py b/homeassistant/components/spotify/const.py index 37bd1a2bf81..f508c9b2938 100644 --- a/homeassistant/components/spotify/const.py +++ b/homeassistant/components/spotify/const.py @@ -2,9 +2,6 @@ DOMAIN = "spotify" -CONF_CLIENT_ID = "client_id" -CONF_CLIENT_SECRET = "client_secret" - DATA_SPOTIFY_CLIENT = "spotify_client" DATA_SPOTIFY_ME = "spotify_me" DATA_SPOTIFY_SESSION = "spotify_session" diff --git a/homeassistant/components/toon/__init__.py b/homeassistant/components/toon/__init__.py index 595d3cc1ede..b970ed2221b 100644 --- a/homeassistant/components/toon/__init__.py +++ b/homeassistant/components/toon/__init__.py @@ -6,7 +6,13 @@ from typing import Any, Dict from toonapilib import Toon import voluptuous as vol -from homeassistant.const import CONF_PASSWORD, CONF_SCAN_INTERVAL, CONF_USERNAME +from homeassistant.const import ( + CONF_CLIENT_ID, + CONF_CLIENT_SECRET, + CONF_PASSWORD, + CONF_SCAN_INTERVAL, + CONF_USERNAME, +) from homeassistant.core import callback from homeassistant.helpers import config_validation as cv, device_registry as dr from homeassistant.helpers.dispatcher import async_dispatcher_connect, dispatcher_send @@ -16,8 +22,6 @@ from homeassistant.helpers.typing import ConfigType, HomeAssistantType from . import config_flow # noqa: F401 from .const import ( - CONF_CLIENT_ID, - CONF_CLIENT_SECRET, CONF_DISPLAY, CONF_TENANT, DATA_TOON, diff --git a/homeassistant/components/toon/config_flow.py b/homeassistant/components/toon/config_flow.py index c8b4b537853..b584b7bd6cb 100644 --- a/homeassistant/components/toon/config_flow.py +++ b/homeassistant/components/toon/config_flow.py @@ -13,17 +13,15 @@ from toonapilib.toonapilibexceptions import ( import voluptuous as vol from homeassistant import config_entries -from homeassistant.const import CONF_PASSWORD, CONF_USERNAME -from homeassistant.core import callback - -from .const import ( +from homeassistant.const import ( CONF_CLIENT_ID, CONF_CLIENT_SECRET, - CONF_DISPLAY, - CONF_TENANT, - DATA_TOON_CONFIG, - DOMAIN, + CONF_PASSWORD, + CONF_USERNAME, ) +from homeassistant.core import callback + +from .const import CONF_DISPLAY, CONF_TENANT, DATA_TOON_CONFIG, DOMAIN _LOGGER = logging.getLogger(__name__) @@ -94,10 +92,10 @@ class ToonFlowHandler(config_entries.ConfigFlow): displays = toon.display_names except InvalidConsumerKey: - return self.async_abort(reason="client_id") + return self.async_abort(reason=CONF_CLIENT_ID) except InvalidConsumerSecret: - return self.async_abort(reason="client_secret") + return self.async_abort(reason=CONF_CLIENT_SECRET) except InvalidCredentials: return await self._show_authenticaticate_form({"base": "credentials"}) diff --git a/homeassistant/components/toon/const.py b/homeassistant/components/toon/const.py index 239359c1fdf..5f26035065e 100644 --- a/homeassistant/components/toon/const.py +++ b/homeassistant/components/toon/const.py @@ -8,8 +8,6 @@ DATA_TOON_CLIENT = "toon_client" DATA_TOON_CONFIG = "toon_config" DATA_TOON_UPDATED = "toon_updated" -CONF_CLIENT_ID = "client_id" -CONF_CLIENT_SECRET = "client_secret" CONF_DISPLAY = "display" CONF_TENANT = "tenant" diff --git a/homeassistant/components/twitch/sensor.py b/homeassistant/components/twitch/sensor.py index 68b7d5dce21..52212fca060 100644 --- a/homeassistant/components/twitch/sensor.py +++ b/homeassistant/components/twitch/sensor.py @@ -6,7 +6,7 @@ from twitch import TwitchClient import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import CONF_TOKEN +from homeassistant.const import CONF_CLIENT_ID, CONF_TOKEN import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity @@ -23,7 +23,6 @@ ATTR_FOLLOWING = "followers" ATTR_VIEWS = "views" CONF_CHANNELS = "channels" -CONF_CLIENT_ID = "client_id" ICON = "mdi:twitch" diff --git a/homeassistant/components/wink/__init__.py b/homeassistant/components/wink/__init__.py index 1763a34fd87..26666bf4b15 100644 --- a/homeassistant/components/wink/__init__.py +++ b/homeassistant/components/wink/__init__.py @@ -14,6 +14,8 @@ from homeassistant.components.http import HomeAssistantView from homeassistant.const import ( ATTR_BATTERY_LEVEL, ATTR_NAME, + CONF_CLIENT_ID, + CONF_CLIENT_SECRET, CONF_EMAIL, CONF_PASSWORD, EVENT_HOMEASSISTANT_START, @@ -38,8 +40,6 @@ DOMAIN = "wink" SUBSCRIPTION_HANDLER = None -CONF_CLIENT_ID = "client_id" -CONF_CLIENT_SECRET = "client_secret" CONF_USER_AGENT = "user_agent" CONF_OAUTH = "oauth" CONF_LOCAL_CONTROL = "local_control" @@ -47,8 +47,6 @@ CONF_MISSING_OAUTH_MSG = "Missing oauth2 credentials." ATTR_ACCESS_TOKEN = "access_token" ATTR_REFRESH_TOKEN = "refresh_token" -ATTR_CLIENT_ID = "client_id" -ATTR_CLIENT_SECRET = "client_secret" ATTR_PAIRING_MODE = "pairing_mode" ATTR_KIDDE_RADIO_CODE = "kidde_radio_code" ATTR_HUB_NAME = "hub_name" @@ -58,7 +56,10 @@ WINK_AUTH_START = "/auth/wink" WINK_CONFIG_FILE = ".wink.conf" USER_AGENT = f"Manufacturer/Home-Assistant{__version__} python/3 Wink/3" -DEFAULT_CONFIG = {"client_id": "CLIENT_ID_HERE", "client_secret": "CLIENT_SECRET_HERE"} +DEFAULT_CONFIG = { + CONF_CLIENT_ID: "CLIENT_ID_HERE", + CONF_CLIENT_SECRET: "CLIENT_SECRET_HERE", +} SERVICE_ADD_NEW_DEVICES = "pull_newly_added_devices_from_wink" SERVICE_REFRESH_STATES = "refresh_state_from_wink" @@ -219,12 +220,12 @@ def _request_app_setup(hass, config): setup(hass, config) return - client_id = callback_data.get("client_id").strip() - client_secret = callback_data.get("client_secret").strip() + client_id = callback_data.get(CONF_CLIENT_ID).strip() + client_secret = callback_data.get(CONF_CLIENT_SECRET).strip() if None not in (client_id, client_secret): save_json( _config_path, - {ATTR_CLIENT_ID: client_id, ATTR_CLIENT_SECRET: client_secret}, + {CONF_CLIENT_ID: client_id, CONF_CLIENT_SECRET: client_secret}, ) setup(hass, config) return @@ -249,8 +250,8 @@ def _request_app_setup(hass, config): submit_caption="submit", description_image="/static/images/config_wink.png", fields=[ - {"id": "client_id", "name": "Client ID", "type": "string"}, - {"id": "client_secret", "name": "Client secret", "type": "string"}, + {"id": CONF_CLIENT_ID, "name": "Client ID", "type": "string"}, + {"id": CONF_CLIENT_SECRET, "name": "Client secret", "type": "string"}, ], ) @@ -293,8 +294,8 @@ def setup(hass, config): } if config.get(DOMAIN) is not None: - client_id = config[DOMAIN].get(ATTR_CLIENT_ID) - client_secret = config[DOMAIN].get(ATTR_CLIENT_SECRET) + client_id = config[DOMAIN].get(CONF_CLIENT_ID) + client_secret = config[DOMAIN].get(CONF_CLIENT_SECRET) email = config[DOMAIN].get(CONF_EMAIL) password = config[DOMAIN].get(CONF_PASSWORD) local_control = config[DOMAIN].get(CONF_LOCAL_CONTROL) @@ -309,8 +310,8 @@ def setup(hass, config): _LOGGER.info("Using legacy OAuth authentication") if not local_control: pywink.disable_local_control() - hass.data[DOMAIN]["oauth"]["client_id"] = client_id - hass.data[DOMAIN]["oauth"]["client_secret"] = client_secret + hass.data[DOMAIN]["oauth"][CONF_CLIENT_ID] = client_id + hass.data[DOMAIN]["oauth"][CONF_CLIENT_SECRET] = client_secret hass.data[DOMAIN]["oauth"]["email"] = email hass.data[DOMAIN]["oauth"]["password"] = password pywink.legacy_set_wink_credentials(email, password, client_id, client_secret) @@ -341,8 +342,8 @@ def setup(hass, config): # This will be called after authorizing Home-Assistant if None not in (access_token, refresh_token): pywink.set_wink_credentials( - config_file.get(ATTR_CLIENT_ID), - config_file.get(ATTR_CLIENT_SECRET), + config_file.get(CONF_CLIENT_ID), + config_file.get(CONF_CLIENT_SECRET), access_token=access_token, refresh_token=refresh_token, ) @@ -353,7 +354,7 @@ def setup(hass, config): redirect_uri = f"{get_url(hass)}{WINK_AUTH_CALLBACK_PATH}" wink_auth_start_url = pywink.get_authorization_url( - config_file.get(ATTR_CLIENT_ID), redirect_uri + config_file.get(CONF_CLIENT_ID), redirect_uri ) hass.http.register_redirect(WINK_AUTH_START, wink_auth_start_url) hass.http.register_view( @@ -698,14 +699,14 @@ class WinkAuthCallbackView(HomeAssistantView): if data.get("code") is not None: response = self.request_token( - data.get("code"), self.config_file["client_secret"] + data.get("code"), self.config_file[CONF_CLIENT_SECRET] ) config_contents = { ATTR_ACCESS_TOKEN: response["access_token"], ATTR_REFRESH_TOKEN: response["refresh_token"], - ATTR_CLIENT_ID: self.config_file["client_id"], - ATTR_CLIENT_SECRET: self.config_file["client_secret"], + CONF_CLIENT_ID: self.config_file[CONF_CLIENT_ID], + CONF_CLIENT_SECRET: self.config_file[CONF_CLIENT_SECRET], } save_json(hass.config.path(WINK_CONFIG_FILE), config_contents) diff --git a/homeassistant/components/withings/__init__.py b/homeassistant/components/withings/__init__.py index 92c3f2ae155..75cf96a37cf 100644 --- a/homeassistant/components/withings/__init__.py +++ b/homeassistant/components/withings/__init__.py @@ -7,23 +7,21 @@ import voluptuous as vol from withings_api import WithingsAuth from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET from homeassistant.helpers import config_entry_oauth2_flow, config_validation as cv from homeassistant.helpers.typing import ConfigType, HomeAssistantType -from . import config_flow, const +from . import config_flow from .common import _LOGGER, NotAuthenticatedError, get_data_manager - -DOMAIN = const.DOMAIN +from .const import CONF_PROFILES, CONFIG, CREDENTIALS, DOMAIN CONFIG_SCHEMA = vol.Schema( { DOMAIN: vol.Schema( { - vol.Required(const.CLIENT_ID): vol.All(cv.string, vol.Length(min=1)), - vol.Required(const.CLIENT_SECRET): vol.All( - cv.string, vol.Length(min=1) - ), - vol.Required(const.PROFILES): vol.All( + vol.Required(CONF_CLIENT_ID): vol.All(cv.string, vol.Length(min=1)), + vol.Required(CONF_CLIENT_SECRET): vol.All(cv.string, vol.Length(min=1)), + vol.Required(CONF_PROFILES): vol.All( cv.ensure_list, vol.Unique(), vol.Length(min=1), @@ -42,15 +40,15 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: if not conf: return True - hass.data[DOMAIN] = {const.CONFIG: conf} + hass.data[DOMAIN] = {CONFIG: conf} config_flow.WithingsFlowHandler.async_register_implementation( hass, config_entry_oauth2_flow.LocalOAuth2Implementation( hass, - const.DOMAIN, - conf[const.CLIENT_ID], - conf[const.CLIENT_SECRET], + DOMAIN, + conf[CONF_CLIENT_ID], + conf[CONF_CLIENT_SECRET], f"{WithingsAuth.URL}/oauth2_user/authorize2", f"{WithingsAuth.URL}/oauth2/token", ), @@ -65,12 +63,12 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool if "auth_implementation" not in entry.data: _LOGGER.debug("Upgrading existing config entry") data = entry.data - creds = data.get(const.CREDENTIALS, {}) + creds = data.get(CREDENTIALS, {}) hass.config_entries.async_update_entry( entry, data={ - "auth_implementation": const.DOMAIN, - "implementation": const.DOMAIN, + "auth_implementation": DOMAIN, + "implementation": DOMAIN, "profile": data.get("profile"), "token": { "access_token": creds.get("access_token"), diff --git a/homeassistant/components/withings/config_flow.py b/homeassistant/components/withings/config_flow.py index cd1e4e4485d..e18a4b0337a 100644 --- a/homeassistant/components/withings/config_flow.py +++ b/homeassistant/components/withings/config_flow.py @@ -51,7 +51,7 @@ class WithingsFlowHandler(config_entry_oauth2_flow.AbstractOAuth2FlowHandler): self._current_data = None return await self.async_step_finish(new_data) - profiles = self.hass.data[const.DOMAIN][const.CONFIG][const.PROFILES] + profiles = self.hass.data[const.DOMAIN][const.CONFIG][const.CONF_PROFILES] return self.async_show_form( step_id="profile", data_schema=vol.Schema({vol.Required(const.PROFILE): vol.In(profiles)}), diff --git a/homeassistant/components/withings/const.py b/homeassistant/components/withings/const.py index 172a17d2914..f2a29cfa3ca 100644 --- a/homeassistant/components/withings/const.py +++ b/homeassistant/components/withings/const.py @@ -1,19 +1,19 @@ """Constants used by the Withings component.""" import homeassistant.const as const +DOMAIN = "withings" + +CONF_PROFILES = "profiles" + DATA_MANAGER = "data_manager" BASE_URL = "base_url" -CLIENT_ID = "client_id" -CLIENT_SECRET = "client_secret" CODE = "code" CONFIG = "config" CREDENTIALS = "credentials" -DOMAIN = "withings" LOG_NAMESPACE = "homeassistant.components.withings" MEASURES = "measures" PROFILE = "profile" -PROFILES = "profiles" AUTH_CALLBACK_PATH = "/api/withings/authorize" AUTH_CALLBACK_NAME = "withings:authorize" diff --git a/homeassistant/components/wunderlist/__init__.py b/homeassistant/components/wunderlist/__init__.py index 954088e4b21..30d665091b6 100644 --- a/homeassistant/components/wunderlist/__init__.py +++ b/homeassistant/components/wunderlist/__init__.py @@ -4,13 +4,13 @@ import logging import voluptuous as vol from wunderpy2 import WunderApi -from homeassistant.const import CONF_ACCESS_TOKEN, CONF_NAME +from homeassistant.const import CONF_ACCESS_TOKEN, CONF_CLIENT_ID, CONF_NAME import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) DOMAIN = "wunderlist" -CONF_CLIENT_ID = "client_id" + CONF_LIST_NAME = "list_name" CONF_STARRED = "starred" diff --git a/tests/components/alexa/test_auth.py b/tests/components/alexa/test_auth.py index 9d14fffe7e4..ae28052efc9 100644 --- a/tests/components/alexa/test_auth.py +++ b/tests/components/alexa/test_auth.py @@ -1,5 +1,6 @@ """Test Alexa auth endpoints.""" from homeassistant.components.alexa.auth import Auth +from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET from . import TEST_TOKEN_URL @@ -53,13 +54,13 @@ async def test_auth_get_access_token_expired(hass, aioclient_mock): assert auth_call_json["grant_type"] == "authorization_code" assert auth_call_json["code"] == accept_grant_code - assert auth_call_json["client_id"] == client_id - assert auth_call_json["client_secret"] == client_secret + assert auth_call_json[CONF_CLIENT_ID] == client_id + assert auth_call_json[CONF_CLIENT_SECRET] == client_secret assert token_call_json["grant_type"] == "refresh_token" assert token_call_json["refresh_token"] == refresh_token - assert token_call_json["client_id"] == client_id - assert token_call_json["client_secret"] == client_secret + assert token_call_json[CONF_CLIENT_ID] == client_id + assert token_call_json[CONF_CLIENT_SECRET] == client_secret async def test_auth_get_access_token_not_expired(hass, aioclient_mock): @@ -86,5 +87,5 @@ async def test_auth_get_access_token_not_expired(hass, aioclient_mock): assert auth_call_json["grant_type"] == "authorization_code" assert auth_call_json["code"] == accept_grant_code - assert auth_call_json["client_id"] == client_id - assert auth_call_json["client_secret"] == client_secret + assert auth_call_json[CONF_CLIENT_ID] == client_id + assert auth_call_json[CONF_CLIENT_SECRET] == client_secret diff --git a/tests/components/almond/test_config_flow.py b/tests/components/almond/test_config_flow.py index d1403c017aa..959846bd017 100644 --- a/tests/components/almond/test_config_flow.py +++ b/tests/components/almond/test_config_flow.py @@ -4,6 +4,7 @@ import asyncio 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.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET from homeassistant.helpers import config_entry_oauth2_flow from tests.async_mock import patch @@ -17,9 +18,7 @@ async def test_import(hass): """Test that we can import a config entry.""" with patch("pyalmond.WebAlmondAPI.async_list_apps"): assert await setup.async_setup_component( - hass, - "almond", - {"almond": {"type": "local", "host": "http://localhost:3000"}}, + hass, DOMAIN, {DOMAIN: {"type": "local", "host": "http://localhost:3000"}}, ) await hass.async_block_till_done() @@ -35,9 +34,7 @@ async def test_import_cannot_connect(hass): "pyalmond.WebAlmondAPI.async_list_apps", side_effect=asyncio.TimeoutError ): assert await setup.async_setup_component( - hass, - "almond", - {"almond": {"type": "local", "host": "http://localhost:3000"}}, + hass, DOMAIN, {DOMAIN: {"type": "local", "host": "http://localhost:3000"}}, ) await hass.async_block_till_done() @@ -94,19 +91,19 @@ async def test_full_flow(hass, aiohttp_client, aioclient_mock): """Check full flow.""" assert await setup.async_setup_component( hass, - "almond", + DOMAIN, { - "almond": { + DOMAIN: { "type": "oauth2", - "client_id": CLIENT_ID_VALUE, - "client_secret": CLIENT_SECRET_VALUE, + CONF_CLIENT_ID: CLIENT_ID_VALUE, + CONF_CLIENT_SECRET: CLIENT_SECRET_VALUE, }, "http": {"base_url": "https://example.com"}, }, ) result = await hass.config_entries.flow.async_init( - "almond", context={"source": config_entries.SOURCE_USER} + DOMAIN, context={"source": config_entries.SOURCE_USER} ) state = config_entry_oauth2_flow._encode_jwt(hass, {"flow_id": result["flow_id"]}) diff --git a/tests/components/ambiclimate/test_config_flow.py b/tests/components/ambiclimate/test_config_flow.py index 6dee15c27f9..2ff7942f8dd 100644 --- a/tests/components/ambiclimate/test_config_flow.py +++ b/tests/components/ambiclimate/test_config_flow.py @@ -3,6 +3,7 @@ import ambiclimate from homeassistant import data_entry_flow from homeassistant.components.ambiclimate import config_flow +from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET from homeassistant.setup import async_setup_component from homeassistant.util import aiohttp @@ -71,8 +72,8 @@ async def test_full_flow_implementation(hass): assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result["title"] == "Ambiclimate" assert result["data"]["callback_url"] == "https://hass.com/api/ambiclimate" - assert result["data"]["client_secret"] == "secret" - assert result["data"]["client_id"] == "id" + assert result["data"][CONF_CLIENT_SECRET] == "secret" + assert result["data"][CONF_CLIENT_ID] == "id" with patch("ambiclimate.AmbiclimateOAuth.get_access_token", return_value=None): result = await flow.async_step_code("123ABC") diff --git a/tests/components/flume/test_config_flow.py b/tests/components/flume/test_config_flow.py index a408a652f32..6f2c8d2ed04 100644 --- a/tests/components/flume/test_config_flow.py +++ b/tests/components/flume/test_config_flow.py @@ -3,6 +3,12 @@ import requests.exceptions from homeassistant import config_entries, setup from homeassistant.components.flume.const import DOMAIN +from homeassistant.const import ( + CONF_CLIENT_ID, + CONF_CLIENT_SECRET, + CONF_PASSWORD, + CONF_USERNAME, +) from tests.async_mock import MagicMock, patch @@ -37,20 +43,20 @@ async def test_form(hass): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { - "username": "test-username", - "password": "test-password", - "client_id": "client_id", - "client_secret": "client_secret", + CONF_USERNAME: "test-username", + CONF_PASSWORD: "test-password", + CONF_CLIENT_ID: "client_id", + CONF_CLIENT_SECRET: "client_secret", }, ) assert result2["type"] == "create_entry" assert result2["title"] == "test-username" assert result2["data"] == { - "username": "test-username", - "password": "test-password", - "client_id": "client_id", - "client_secret": "client_secret", + CONF_USERNAME: "test-username", + CONF_PASSWORD: "test-password", + CONF_CLIENT_ID: "client_id", + CONF_CLIENT_SECRET: "client_secret", } await hass.async_block_till_done() assert len(mock_setup.mock_calls) == 1 @@ -76,20 +82,20 @@ async def test_form_import(hass): DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data={ - "username": "test-username", - "password": "test-password", - "client_id": "client_id", - "client_secret": "client_secret", + CONF_USERNAME: "test-username", + CONF_PASSWORD: "test-password", + CONF_CLIENT_ID: "client_id", + CONF_CLIENT_SECRET: "client_secret", }, ) assert result["type"] == "create_entry" assert result["title"] == "test-username" assert result["data"] == { - "username": "test-username", - "password": "test-password", - "client_id": "client_id", - "client_secret": "client_secret", + CONF_USERNAME: "test-username", + CONF_PASSWORD: "test-password", + CONF_CLIENT_ID: "client_id", + CONF_CLIENT_SECRET: "client_secret", } await hass.async_block_till_done() assert len(mock_setup.mock_calls) == 1 @@ -111,10 +117,10 @@ async def test_form_invalid_auth(hass): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { - "username": "test-username", - "password": "test-password", - "client_id": "client_id", - "client_secret": "client_secret", + CONF_USERNAME: "test-username", + CONF_PASSWORD: "test-password", + CONF_CLIENT_ID: "client_id", + CONF_CLIENT_SECRET: "client_secret", }, ) @@ -136,10 +142,10 @@ async def test_form_cannot_connect(hass): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], { - "username": "test-username", - "password": "test-password", - "client_id": "client_id", - "client_secret": "client_secret", + CONF_USERNAME: "test-username", + CONF_PASSWORD: "test-password", + 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 6f7ce74ce62..e3412a01f5e 100644 --- a/tests/components/google/test_init.py +++ b/tests/components/google/test_init.py @@ -2,6 +2,7 @@ import pytest import homeassistant.components.google as google +from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET from homeassistant.setup import async_setup_component from tests.async_mock import patch @@ -24,7 +25,7 @@ def mock_google_setup(hass): async def test_setup_component(hass, google_setup): """Test setup component.""" - config = {"google": {"client_id": "id", "client_secret": "secret"}} + config = {"google": {CONF_CLIENT_ID: "id", CONF_CLIENT_SECRET: "secret"}} assert await async_setup_component(hass, "google", config) @@ -51,8 +52,8 @@ async def test_found_calendar(hass, google_setup, mock_next_event, test_calendar """Test when a calendar is found.""" config = { "google": { - "client_id": "id", - "client_secret": "secret", + CONF_CLIENT_ID: "id", + CONF_CLIENT_SECRET: "secret", "track_new_calendar": True, } } diff --git a/tests/components/home_connect/test_config_flow.py b/tests/components/home_connect/test_config_flow.py index be6c21fe0a7..d6d936fe16e 100644 --- a/tests/components/home_connect/test_config_flow.py +++ b/tests/components/home_connect/test_config_flow.py @@ -5,6 +5,7 @@ from homeassistant.components.home_connect.const import ( OAUTH2_AUTHORIZE, OAUTH2_TOKEN, ) +from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET from homeassistant.helpers import config_entry_oauth2_flow CLIENT_ID = "1234" @@ -17,7 +18,10 @@ async def test_full_flow(hass, aiohttp_client, aioclient_mock): hass, "home_connect", { - "home_connect": {"client_id": CLIENT_ID, "client_secret": CLIENT_SECRET}, + "home_connect": { + CONF_CLIENT_ID: CLIENT_ID, + CONF_CLIENT_SECRET: CLIENT_SECRET, + }, "http": {"base_url": "https://example.com"}, }, ) diff --git a/tests/components/netatmo/test_config_flow.py b/tests/components/netatmo/test_config_flow.py index 047dd4c0c40..b497df93bf5 100644 --- a/tests/components/netatmo/test_config_flow.py +++ b/tests/components/netatmo/test_config_flow.py @@ -6,6 +6,7 @@ from homeassistant.components.netatmo.const import ( OAUTH2_AUTHORIZE, OAUTH2_TOKEN, ) +from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET from homeassistant.helpers import config_entry_oauth2_flow from tests.async_mock import patch @@ -43,7 +44,7 @@ async def test_full_flow(hass, aiohttp_client, aioclient_mock): hass, "netatmo", { - "netatmo": {"client_id": CLIENT_ID, "client_secret": CLIENT_SECRET}, + "netatmo": {CONF_CLIENT_ID: CLIENT_ID, CONF_CLIENT_SECRET: CLIENT_SECRET}, "http": {"base_url": "https://example.com"}, }, ) diff --git a/tests/components/plex/const.py b/tests/components/plex/const.py index 0f91a9da23f..bfc4b8ef78e 100644 --- a/tests/components/plex/const.py +++ b/tests/components/plex/const.py @@ -2,6 +2,7 @@ from homeassistant.components.media_player import DOMAIN as MP_DOMAIN from homeassistant.components.plex import const from homeassistant.const import ( + CONF_CLIENT_ID, CONF_HOST, CONF_PORT, CONF_TOKEN, @@ -35,7 +36,7 @@ MOCK_TOKEN = "secret_token" DEFAULT_DATA = { const.CONF_SERVER: MOCK_SERVERS[0][const.CONF_SERVER], const.PLEX_SERVER_CONFIG: { - const.CONF_CLIENT_IDENTIFIER: "00000000-0000-0000-0000-000000000000", + CONF_CLIENT_ID: "00000000-0000-0000-0000-000000000000", CONF_TOKEN: MOCK_TOKEN, CONF_URL: f"https://{MOCK_SERVERS[0][CONF_HOST]}:{MOCK_SERVERS[0][CONF_PORT]}", CONF_VERIFY_SSL: True, diff --git a/tests/components/point/test_config_flow.py b/tests/components/point/test_config_flow.py index 1714dd5a352..d1f8688da24 100644 --- a/tests/components/point/test_config_flow.py +++ b/tests/components/point/test_config_flow.py @@ -5,6 +5,7 @@ import pytest from homeassistant import data_entry_flow from homeassistant.components.point import DOMAIN, config_flow +from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET from tests.async_mock import AsyncMock, patch @@ -86,8 +87,8 @@ async def test_full_flow_implementation( result = await flow.async_step_code("123ABC") assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result["data"]["refresh_args"] == { - "client_id": "id", - "client_secret": "secret", + CONF_CLIENT_ID: "id", + CONF_CLIENT_SECRET: "secret", } assert result["title"] == "john.doe@example.com" assert result["data"]["token"] == {"access_token": "boo"} diff --git a/tests/components/reddit/test_sensor.py b/tests/components/reddit/test_sensor.py index c2620aa906d..4ec2a3ba452 100644 --- a/tests/components/reddit/test_sensor.py +++ b/tests/components/reddit/test_sensor.py @@ -15,7 +15,13 @@ from homeassistant.components.reddit.sensor import ( CONF_SORT_BY, DOMAIN, ) -from homeassistant.const import CONF_MAXIMUM, CONF_PASSWORD, CONF_USERNAME +from homeassistant.const import ( + CONF_CLIENT_ID, + CONF_CLIENT_SECRET, + CONF_MAXIMUM, + CONF_PASSWORD, + CONF_USERNAME, +) from homeassistant.setup import setup_component from tests.async_mock import patch @@ -24,8 +30,8 @@ from tests.common import get_test_home_assistant VALID_CONFIG = { "sensor": { "platform": DOMAIN, - "client_id": "test_client_id", - "client_secret": "test_client_secret", + CONF_CLIENT_ID: "test_client_id", + CONF_CLIENT_SECRET: "test_client_secret", CONF_USERNAME: "test_username", CONF_PASSWORD: "test_password", "subreddits": ["worldnews", "news"], @@ -35,8 +41,8 @@ VALID_CONFIG = { VALID_LIMITED_CONFIG = { "sensor": { "platform": DOMAIN, - "client_id": "test_client_id", - "client_secret": "test_client_secret", + CONF_CLIENT_ID: "test_client_id", + CONF_CLIENT_SECRET: "test_client_secret", CONF_USERNAME: "test_username", CONF_PASSWORD: "test_password", "subreddits": ["worldnews", "news"], @@ -48,8 +54,8 @@ VALID_LIMITED_CONFIG = { INVALID_SORT_BY_CONFIG = { "sensor": { "platform": DOMAIN, - "client_id": "test_client_id", - "client_secret": "test_client_secret", + CONF_CLIENT_ID: "test_client_id", + CONF_CLIENT_SECRET: "test_client_secret", CONF_USERNAME: "test_username", CONF_PASSWORD: "test_password", "subreddits": ["worldnews", "news"], diff --git a/tests/components/smartthings/conftest.py b/tests/components/smartthings/conftest.py index 83f8e4dfca1..a7ca9a4744c 100644 --- a/tests/components/smartthings/conftest.py +++ b/tests/components/smartthings/conftest.py @@ -28,8 +28,6 @@ from homeassistant.components.smartthings.const import ( CONF_INSTALLED_APP_ID, CONF_INSTANCE_ID, CONF_LOCATION_ID, - CONF_OAUTH_CLIENT_ID, - CONF_OAUTH_CLIENT_SECRET, CONF_REFRESH_TOKEN, DATA_BROKERS, DOMAIN, @@ -39,7 +37,12 @@ from homeassistant.components.smartthings.const import ( ) from homeassistant.config import async_process_ha_core_config from homeassistant.config_entries import CONN_CLASS_CLOUD_PUSH, SOURCE_USER, ConfigEntry -from homeassistant.const import CONF_ACCESS_TOKEN, CONF_WEBHOOK_ID +from homeassistant.const import ( + CONF_ACCESS_TOKEN, + CONF_CLIENT_ID, + CONF_CLIENT_SECRET, + CONF_WEBHOOK_ID, +) from homeassistant.setup import async_setup_component from tests.async_mock import Mock, patch @@ -217,8 +220,8 @@ def config_entry_fixture(hass, installed_app, location): CONF_APP_ID: installed_app.app_id, CONF_LOCATION_ID: location.location_id, CONF_REFRESH_TOKEN: str(uuid4()), - CONF_OAUTH_CLIENT_ID: str(uuid4()), - CONF_OAUTH_CLIENT_SECRET: str(uuid4()), + CONF_CLIENT_ID: str(uuid4()), + CONF_CLIENT_SECRET: str(uuid4()), } return MockConfigEntry( domain=DOMAIN, diff --git a/tests/components/smartthings/test_config_flow.py b/tests/components/smartthings/test_config_flow.py index 47726bfe270..91bba9ab405 100644 --- a/tests/components/smartthings/test_config_flow.py +++ b/tests/components/smartthings/test_config_flow.py @@ -11,13 +11,13 @@ from homeassistant.components.smartthings.const import ( CONF_APP_ID, CONF_INSTALLED_APP_ID, CONF_LOCATION_ID, - CONF_OAUTH_CLIENT_ID, - CONF_OAUTH_CLIENT_SECRET, DOMAIN, ) from homeassistant.config import async_process_ha_core_config from homeassistant.const import ( CONF_ACCESS_TOKEN, + CONF_CLIENT_ID, + CONF_CLIENT_SECRET, HTTP_FORBIDDEN, HTTP_NOT_FOUND, HTTP_UNAUTHORIZED, @@ -97,8 +97,8 @@ async def test_entry_created(hass, app, app_oauth_client, location, smartthings_ 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["data"][CONF_CLIENT_SECRET] == app_oauth_client.client_secret + assert result["data"][CONF_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( @@ -165,8 +165,8 @@ async def test_entry_created_from_update_event( 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["data"][CONF_CLIENT_SECRET] == app_oauth_client.client_secret + assert result["data"][CONF_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( @@ -233,8 +233,8 @@ async def test_entry_created_existing_app_new_oauth_client( 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["data"][CONF_CLIENT_SECRET] == app_oauth_client.client_secret + assert result["data"][CONF_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( @@ -262,8 +262,8 @@ async def test_entry_created_existing_app_copies_oauth_client( domain=DOMAIN, data={ CONF_APP_ID: app.app_id, - CONF_OAUTH_CLIENT_ID: oauth_client_id, - CONF_OAUTH_CLIENT_SECRET: oauth_client_secret, + CONF_CLIENT_ID: oauth_client_id, + CONF_CLIENT_SECRET: oauth_client_secret, CONF_LOCATION_ID: str(uuid4()), CONF_INSTALLED_APP_ID: str(uuid4()), CONF_ACCESS_TOKEN: token, @@ -316,8 +316,8 @@ async def test_entry_created_existing_app_copies_oauth_client( 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["data"][CONF_CLIENT_SECRET] == oauth_client_secret + assert result["data"][CONF_CLIENT_ID] == oauth_client_id assert result["title"] == location.name entry = next( ( @@ -405,8 +405,8 @@ async def test_entry_created_with_cloudhook( 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["data"][CONF_CLIENT_SECRET] == app_oauth_client.client_secret + assert result["data"][CONF_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, diff --git a/tests/components/somfy/test_config_flow.py b/tests/components/somfy/test_config_flow.py index 1823cb3c3ab..f5cf97bacff 100644 --- a/tests/components/somfy/test_config_flow.py +++ b/tests/components/somfy/test_config_flow.py @@ -5,14 +5,14 @@ import pytest from homeassistant import config_entries, data_entry_flow, setup from homeassistant.components.somfy import DOMAIN, config_flow +from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET from homeassistant.helpers import config_entry_oauth2_flow from tests.async_mock import patch from tests.common import MockConfigEntry -CLIENT_SECRET_VALUE = "5678" - CLIENT_ID_VALUE = "1234" +CLIENT_SECRET_VALUE = "5678" @pytest.fixture() @@ -56,18 +56,18 @@ async def test_full_flow(hass, aiohttp_client, aioclient_mock): """Check full flow.""" assert await setup.async_setup_component( hass, - "somfy", + DOMAIN, { - "somfy": { - "client_id": CLIENT_ID_VALUE, - "client_secret": CLIENT_SECRET_VALUE, + DOMAIN: { + CONF_CLIENT_ID: CLIENT_ID_VALUE, + CONF_CLIENT_SECRET: CLIENT_SECRET_VALUE, }, "http": {"base_url": "https://example.com"}, }, ) result = await hass.config_entries.flow.async_init( - "somfy", context={"source": config_entries.SOURCE_USER} + DOMAIN, context={"source": config_entries.SOURCE_USER} ) state = config_entry_oauth2_flow._encode_jwt(hass, {"flow_id": result["flow_id"]}) @@ -97,7 +97,7 @@ async def test_full_flow(hass, aiohttp_client, aioclient_mock): with patch("homeassistant.components.somfy.api.ConfigEntrySomfyApi"): result = await hass.config_entries.flow.async_configure(result["flow_id"]) - assert result["data"]["auth_implementation"] == "somfy" + assert result["data"]["auth_implementation"] == DOMAIN result["data"]["token"].pop("expires_at") assert result["data"]["token"] == { @@ -107,8 +107,8 @@ async def test_full_flow(hass, aiohttp_client, aioclient_mock): "expires_in": 60, } - assert "somfy" in hass.config.components - entry = hass.config_entries.async_entries("somfy")[0] + assert DOMAIN in hass.config.components + entry = hass.config_entries.async_entries(DOMAIN)[0] assert entry.state == config_entries.ENTRY_STATE_LOADED assert await hass.config_entries.async_unload(entry.entry_id) diff --git a/tests/components/spotify/test_config_flow.py b/tests/components/spotify/test_config_flow.py index 7115151451f..860840c477f 100644 --- a/tests/components/spotify/test_config_flow.py +++ b/tests/components/spotify/test_config_flow.py @@ -2,12 +2,9 @@ from spotipy import SpotifyException from homeassistant import data_entry_flow, setup -from homeassistant.components.spotify.const import ( - CONF_CLIENT_ID, - CONF_CLIENT_SECRET, - DOMAIN, -) +from homeassistant.components.spotify.const import DOMAIN from homeassistant.config_entries import SOURCE_USER, SOURCE_ZEROCONF +from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET from homeassistant.helpers import config_entry_oauth2_flow from tests.async_mock import patch diff --git a/tests/components/toon/test_config_flow.py b/tests/components/toon/test_config_flow.py index fdf97243a3a..4ba74245876 100644 --- a/tests/components/toon/test_config_flow.py +++ b/tests/components/toon/test_config_flow.py @@ -10,14 +10,13 @@ from toonapilib.toonapilibexceptions import ( from homeassistant import data_entry_flow from homeassistant.components.toon import config_flow -from homeassistant.components.toon.const import ( +from homeassistant.components.toon.const import CONF_DISPLAY, CONF_TENANT, DOMAIN +from homeassistant.const import ( CONF_CLIENT_ID, CONF_CLIENT_SECRET, - CONF_DISPLAY, - CONF_TENANT, - DOMAIN, + CONF_PASSWORD, + CONF_USERNAME, ) -from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.setup import async_setup_component from tests.async_mock import patch @@ -76,8 +75,8 @@ async def test_show_authenticate_form(hass): @pytest.mark.parametrize( "side_effect,reason", [ - (InvalidConsumerKey, "client_id"), - (InvalidConsumerSecret, "client_secret"), + (InvalidConsumerKey, CONF_CLIENT_ID), + (InvalidConsumerSecret, CONF_CLIENT_SECRET), (AgreementsRetrievalError, "no_agreements"), (Exception, "unknown_auth_fail"), ], diff --git a/tests/components/twitch/test_twitch.py b/tests/components/twitch/test_twitch.py index 3e777fa3d03..1d2675184fc 100644 --- a/tests/components/twitch/test_twitch.py +++ b/tests/components/twitch/test_twitch.py @@ -3,6 +3,7 @@ from requests import HTTPError from twitch.resources import Channel, Follow, Stream, Subscription, User from homeassistant.components import sensor +from homeassistant.const import CONF_CLIENT_ID from homeassistant.setup import async_setup_component from tests.async_mock import MagicMock, patch @@ -11,14 +12,14 @@ ENTITY_ID = "sensor.channel123" CONFIG = { sensor.DOMAIN: { "platform": "twitch", - "client_id": "1234", + CONF_CLIENT_ID: "1234", "channels": ["channel123"], } } CONFIG_WITH_OAUTH = { sensor.DOMAIN: { "platform": "twitch", - "client_id": "1234", + CONF_CLIENT_ID: "1234", "channels": ["channel123"], "token": "9876", } diff --git a/tests/components/withings/common.py b/tests/components/withings/common.py index fc30820d5d1..0bb7966da53 100644 --- a/tests/components/withings/common.py +++ b/tests/components/withings/common.py @@ -19,7 +19,12 @@ import homeassistant.components.http as http import homeassistant.components.withings.const as const from homeassistant.config import async_process_ha_core_config from homeassistant.config_entries import SOURCE_USER -from homeassistant.const import CONF_UNIT_SYSTEM, CONF_UNIT_SYSTEM_METRIC +from homeassistant.const import ( + CONF_CLIENT_ID, + CONF_CLIENT_SECRET, + CONF_UNIT_SYSTEM, + CONF_UNIT_SYSTEM_METRIC, +) from homeassistant.core import HomeAssistant from homeassistant.helpers import config_entry_oauth2_flow from homeassistant.setup import async_setup_component @@ -55,9 +60,9 @@ async def setup_hass(hass: HomeAssistant) -> dict: api.DOMAIN: {"base_url": "http://localhost/"}, http.DOMAIN: {"server_port": 8080}, const.DOMAIN: { - const.CLIENT_ID: "my_client_id", - const.CLIENT_SECRET: "my_client_secret", - const.PROFILES: profiles, + CONF_CLIENT_ID: "my_client_id", + CONF_CLIENT_SECRET: "my_client_secret", + const.CONF_PROFILES: profiles, }, } diff --git a/tests/components/withings/test_init.py b/tests/components/withings/test_init.py index 7d61c74c50a..b65e175913d 100644 --- a/tests/components/withings/test_init.py +++ b/tests/components/withings/test_init.py @@ -15,7 +15,7 @@ from homeassistant.components.withings import ( const, ) from homeassistant.config import async_process_ha_core_config -from homeassistant.const import STATE_UNKNOWN +from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET, STATE_UNKNOWN from homeassistant.core import HomeAssistant from .common import ( @@ -55,9 +55,9 @@ def test_config_schema_basic_config() -> None: """Test schema.""" config_schema_validate( { - const.CLIENT_ID: "my_client_id", - const.CLIENT_SECRET: "my_client_secret", - const.PROFILES: ["Person 1", "Person 2"], + CONF_CLIENT_ID: "my_client_id", + CONF_CLIENT_SECRET: "my_client_secret", + const.CONF_PROFILES: ["Person 1", "Person 2"], } ) @@ -66,22 +66,22 @@ def test_config_schema_client_id() -> None: """Test schema.""" config_schema_assert_fail( { - const.CLIENT_SECRET: "my_client_secret", - const.PROFILES: ["Person 1", "Person 2"], + CONF_CLIENT_SECRET: "my_client_secret", + const.CONF_PROFILES: ["Person 1", "Person 2"], } ) config_schema_assert_fail( { - const.CLIENT_SECRET: "my_client_secret", - const.CLIENT_ID: "", - const.PROFILES: ["Person 1"], + CONF_CLIENT_SECRET: "my_client_secret", + CONF_CLIENT_ID: "", + const.CONF_PROFILES: ["Person 1"], } ) config_schema_validate( { - const.CLIENT_SECRET: "my_client_secret", - const.CLIENT_ID: "my_client_id", - const.PROFILES: ["Person 1"], + CONF_CLIENT_SECRET: "my_client_secret", + CONF_CLIENT_ID: "my_client_id", + const.CONF_PROFILES: ["Person 1"], } ) @@ -89,20 +89,20 @@ def test_config_schema_client_id() -> None: def test_config_schema_client_secret() -> None: """Test schema.""" config_schema_assert_fail( - {const.CLIENT_ID: "my_client_id", const.PROFILES: ["Person 1"]} + {CONF_CLIENT_ID: "my_client_id", const.CONF_PROFILES: ["Person 1"]} ) config_schema_assert_fail( { - const.CLIENT_ID: "my_client_id", - const.CLIENT_SECRET: "", - const.PROFILES: ["Person 1"], + CONF_CLIENT_ID: "my_client_id", + CONF_CLIENT_SECRET: "", + const.CONF_PROFILES: ["Person 1"], } ) config_schema_validate( { - const.CLIENT_ID: "my_client_id", - const.CLIENT_SECRET: "my_client_secret", - const.PROFILES: ["Person 1"], + CONF_CLIENT_ID: "my_client_id", + CONF_CLIENT_SECRET: "my_client_secret", + const.CONF_PROFILES: ["Person 1"], } ) @@ -110,41 +110,41 @@ def test_config_schema_client_secret() -> None: def test_config_schema_profiles() -> None: """Test schema.""" config_schema_assert_fail( - {const.CLIENT_ID: "my_client_id", const.CLIENT_SECRET: "my_client_secret"} + {CONF_CLIENT_ID: "my_client_id", CONF_CLIENT_SECRET: "my_client_secret"} ) config_schema_assert_fail( { - const.CLIENT_ID: "my_client_id", - const.CLIENT_SECRET: "my_client_secret", - const.PROFILES: "", + CONF_CLIENT_ID: "my_client_id", + CONF_CLIENT_SECRET: "my_client_secret", + const.CONF_PROFILES: "", } ) config_schema_assert_fail( { - const.CLIENT_ID: "my_client_id", - const.CLIENT_SECRET: "my_client_secret", - const.PROFILES: [], + CONF_CLIENT_ID: "my_client_id", + CONF_CLIENT_SECRET: "my_client_secret", + const.CONF_PROFILES: [], } ) config_schema_assert_fail( { - const.CLIENT_ID: "my_client_id", - const.CLIENT_SECRET: "my_client_secret", - const.PROFILES: ["Person 1", "Person 1"], + CONF_CLIENT_ID: "my_client_id", + CONF_CLIENT_SECRET: "my_client_secret", + const.CONF_PROFILES: ["Person 1", "Person 1"], } ) config_schema_validate( { - const.CLIENT_ID: "my_client_id", - const.CLIENT_SECRET: "my_client_secret", - const.PROFILES: ["Person 1"], + CONF_CLIENT_ID: "my_client_id", + CONF_CLIENT_SECRET: "my_client_secret", + const.CONF_PROFILES: ["Person 1"], } ) config_schema_validate( { - const.CLIENT_ID: "my_client_id", - const.CLIENT_SECRET: "my_client_secret", - const.PROFILES: ["Person 1", "Person 2"], + CONF_CLIENT_ID: "my_client_id", + CONF_CLIENT_SECRET: "my_client_secret", + const.CONF_PROFILES: ["Person 1", "Person 2"], } ) @@ -163,7 +163,7 @@ async def test_upgrade_token( ) -> None: """Test upgrading from old config data format to new one.""" config = await setup_hass(hass) - profiles = config[const.DOMAIN][const.PROFILES] + profiles = config[const.DOMAIN][const.CONF_PROFILES] await async_process_ha_core_config( hass, {"internal_url": "http://example.local"}, @@ -197,7 +197,7 @@ async def test_upgrade_token( "token_expiry": token.get("expires_at"), "token_type": token.get("type"), "userid": token.get("userid"), - "client_id": token.get("my_client_id"), + CONF_CLIENT_ID: token.get("my_client_id"), "consumer_secret": token.get("my_consumer_secret"), }, }, @@ -228,7 +228,7 @@ async def test_upgrade_token( assert token.get("expires_at") > time.time() assert token.get("type") == "Bearer" assert token.get("userid") == "myuserid" - assert not token.get("client_id") + assert not token.get(CONF_CLIENT_ID) assert not token.get("consumer_secret") @@ -237,7 +237,7 @@ async def test_auth_failure( ) -> None: """Test auth failure.""" config = await setup_hass(hass) - profiles = config[const.DOMAIN][const.PROFILES] + profiles = config[const.DOMAIN][const.CONF_PROFILES] await async_process_ha_core_config( hass, {"internal_url": "http://example.local"}, @@ -276,7 +276,7 @@ async def test_auth_failure( async def test_full_setup(hass: HomeAssistant, aiohttp_client, aioclient_mock) -> None: """Test the whole component lifecycle.""" config = await setup_hass(hass) - profiles = config[const.DOMAIN][const.PROFILES] + profiles = config[const.DOMAIN][const.CONF_PROFILES] await async_process_ha_core_config( hass, {"internal_url": "http://example.local"}, From 22a1452946f76cd1a1a7f79f0f7d669e9cbc03a0 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Sat, 30 May 2020 14:07:56 -0400 Subject: [PATCH 261/406] Bump pyvizio version to 0.1.48 (#36275) --- homeassistant/components/vizio/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/vizio/manifest.json b/homeassistant/components/vizio/manifest.json index 9c1f1960d74..2f50e26f310 100644 --- a/homeassistant/components/vizio/manifest.json +++ b/homeassistant/components/vizio/manifest.json @@ -2,7 +2,7 @@ "domain": "vizio", "name": "VIZIO SmartCast", "documentation": "https://www.home-assistant.io/integrations/vizio", - "requirements": ["pyvizio==0.1.47"], + "requirements": ["pyvizio==0.1.48"], "codeowners": ["@raman325"], "config_flow": true, "zeroconf": ["_viziocast._tcp.local."], diff --git a/requirements_all.txt b/requirements_all.txt index d0811ef659c..e0831b0caa4 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1802,7 +1802,7 @@ pyversasense==0.0.6 pyvesync==1.1.0 # homeassistant.components.vizio -pyvizio==0.1.47 +pyvizio==0.1.48 # homeassistant.components.velux pyvlx==0.2.16 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 436374f28a7..da7a7f4fe58 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -747,7 +747,7 @@ pyvera==0.3.7 pyvesync==1.1.0 # homeassistant.components.vizio -pyvizio==0.1.47 +pyvizio==0.1.48 # homeassistant.components.html5 pywebpush==1.9.2 From a062da05b42761061630b6d29f3f1efbe676ea4f Mon Sep 17 00:00:00 2001 From: Luke Pomfrey Date: Sat, 30 May 2020 18:17:15 +0000 Subject: [PATCH 262/406] Keep all OpenCV classifier results (#36294) --- homeassistant/components/opencv/image_processing.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/opencv/image_processing.py b/homeassistant/components/opencv/image_processing.py index f35d063de50..6b5e4e85513 100644 --- a/homeassistant/components/opencv/image_processing.py +++ b/homeassistant/components/opencv/image_processing.py @@ -160,6 +160,9 @@ class OpenCVImageProcessor(ImageProcessingEntity): """Process the image.""" cv_image = cv2.imdecode(numpy.asarray(bytearray(image)), cv2.IMREAD_UNCHANGED) + matches = {} + total_matches = 0 + for name, classifier in self._classifiers.items(): scale = DEFAULT_SCALE neighbors = DEFAULT_NEIGHBORS @@ -177,8 +180,6 @@ class OpenCVImageProcessor(ImageProcessingEntity): detections = cascade.detectMultiScale( cv_image, scaleFactor=scale, minNeighbors=neighbors, minSize=min_size ) - matches = {} - total_matches = 0 regions = [] # pylint: disable=invalid-name for (x, y, w, h) in detections: From fb754186bd38987448bf491179af3d6e06244937 Mon Sep 17 00:00:00 2001 From: Tom Harris Date: Sat, 30 May 2020 15:08:36 -0400 Subject: [PATCH 263/406] Bump pyinsteon to 1.0.2 (#36297) --- homeassistant/components/insteon/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/insteon/manifest.json b/homeassistant/components/insteon/manifest.json index f342cd02291..0f0ccf842ff 100644 --- a/homeassistant/components/insteon/manifest.json +++ b/homeassistant/components/insteon/manifest.json @@ -2,6 +2,6 @@ "domain": "insteon", "name": "Insteon", "documentation": "https://www.home-assistant.io/integrations/insteon", - "requirements": ["pyinsteon==1.0.1"], + "requirements": ["pyinsteon==1.0.2"], "codeowners": ["@teharris1"] } \ No newline at end of file diff --git a/requirements_all.txt b/requirements_all.txt index e0831b0caa4..62ed538235a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1378,7 +1378,7 @@ pyialarm==0.3 pyicloud==0.9.7 # homeassistant.components.insteon -pyinsteon==1.0.1 +pyinsteon==1.0.2 # homeassistant.components.intesishome pyintesishome==1.7.4 From db9900cf504c5a0d3c0d7751a61bfe5670ba0d5d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 30 May 2020 17:37:25 -0500 Subject: [PATCH 264/406] Reduce hunterdouglas_powerview parallel updates (#36299) The powerview hub gets overwhelmed with just a few requests so we now set PARALLEL_UPDATES to 1. --- homeassistant/components/hunterdouglas_powerview/__init__.py | 3 +++ homeassistant/components/hunterdouglas_powerview/cover.py | 2 ++ 2 files changed, 5 insertions(+) diff --git a/homeassistant/components/hunterdouglas_powerview/__init__.py b/homeassistant/components/hunterdouglas_powerview/__init__.py index 935410d9351..3df895c94ce 100644 --- a/homeassistant/components/hunterdouglas_powerview/__init__.py +++ b/homeassistant/components/hunterdouglas_powerview/__init__.py @@ -56,6 +56,9 @@ from .const import ( USER_DATA, ) +PARALLEL_UPDATES = 1 + + DEVICE_SCHEMA = vol.Schema( {DOMAIN: vol.Schema({vol.Required(CONF_HOST): cv.string})}, extra=vol.ALLOW_EXTRA ) diff --git a/homeassistant/components/hunterdouglas_powerview/cover.py b/homeassistant/components/hunterdouglas_powerview/cover.py index ea56d56352a..871413b9f5b 100644 --- a/homeassistant/components/hunterdouglas_powerview/cover.py +++ b/homeassistant/components/hunterdouglas_powerview/cover.py @@ -45,6 +45,8 @@ _LOGGER = logging.getLogger(__name__) # from one state to another TRANSITION_COMPLETE_DURATION = 30 +PARALLEL_UPDATES = 1 + async def async_setup_entry(hass, entry, async_add_entities): """Set up the hunter douglas shades.""" From 39f2d4cb5a7a3327339c903b5389ddf5f92c6f45 Mon Sep 17 00:00:00 2001 From: Kumar Gala Date: Sat, 30 May 2020 17:48:38 -0500 Subject: [PATCH 265/406] Add unique_id support to Somfy MyLink (#36232) --- homeassistant/components/somfy_mylink/cover.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/somfy_mylink/cover.py b/homeassistant/components/somfy_mylink/cover.py index 767abda2fd7..b849a490940 100644 --- a/homeassistant/components/somfy_mylink/cover.py +++ b/homeassistant/components/somfy_mylink/cover.py @@ -46,7 +46,7 @@ class SomfyShade(CoverEntity): def __init__( self, somfy_mylink, - target_id="AABBCC", + target_id, name="SomfyShade", reverse=False, device_class="window", @@ -58,6 +58,11 @@ class SomfyShade(CoverEntity): self._reverse = reverse self._device_class = device_class + @property + def unique_id(self): + """Return the unique ID of this cover.""" + return self._target_id + @property def name(self): """Return the name of the cover.""" From 5920f3379c0610b80572e6a5bd082bda58338d6e Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Sun, 31 May 2020 00:02:42 +0000 Subject: [PATCH 266/406] [ci skip] Translation update --- .../components/bsblan/translations/pl.json | 2 +- .../flick_electric/translations/pl.json | 2 +- .../components/guardian/translations/pl.json | 14 +++++++ .../lutron_caseta/translations/pl.json | 6 ++- .../components/plugwise/translations/pl.json | 22 +++++++++++ .../components/sonarr/translations/ca.json | 37 +++++++++++++++++++ .../components/sonarr/translations/en.json | 6 +-- .../components/sonarr/translations/fr.json | 3 ++ .../components/sonarr/translations/no.json | 13 +++++++ .../components/sonarr/translations/pl.json | 37 +++++++++++++++++++ .../components/sonarr/translations/sv.json | 9 +++++ .../sonarr/translations/zh-Hant.json | 37 +++++++++++++++++++ .../components/unifi/translations/ca.json | 2 +- .../components/upb/translations/pl.json | 1 + .../components/vizio/translations/pl.json | 2 + .../components/zwave/translations/pl.json | 2 +- 16 files changed, 187 insertions(+), 8 deletions(-) create mode 100644 homeassistant/components/guardian/translations/pl.json create mode 100644 homeassistant/components/plugwise/translations/pl.json create mode 100644 homeassistant/components/sonarr/translations/ca.json create mode 100644 homeassistant/components/sonarr/translations/fr.json create mode 100644 homeassistant/components/sonarr/translations/no.json create mode 100644 homeassistant/components/sonarr/translations/pl.json create mode 100644 homeassistant/components/sonarr/translations/sv.json create mode 100644 homeassistant/components/sonarr/translations/zh-Hant.json diff --git a/homeassistant/components/bsblan/translations/pl.json b/homeassistant/components/bsblan/translations/pl.json index 81ed1374f7f..21b42085761 100644 --- a/homeassistant/components/bsblan/translations/pl.json +++ b/homeassistant/components/bsblan/translations/pl.json @@ -4,7 +4,7 @@ "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane." }, "error": { - "connection_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia." + "connection_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia z urz\u0105dzeniem BSB_LAN." }, "flow_title": "BSB-Lan: {name}", "step": { diff --git a/homeassistant/components/flick_electric/translations/pl.json b/homeassistant/components/flick_electric/translations/pl.json index 20e2aa21902..fb6554d00d8 100644 --- a/homeassistant/components/flick_electric/translations/pl.json +++ b/homeassistant/components/flick_electric/translations/pl.json @@ -4,7 +4,7 @@ "already_configured": "Konto jest ju\u017c skonfigurowane." }, "error": { - "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia.", + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie.", "invalid_auth": "Niepoprawne uwierzytelnienie.", "unknown": "Nieoczekiwany b\u0142\u0105d." }, diff --git a/homeassistant/components/guardian/translations/pl.json b/homeassistant/components/guardian/translations/pl.json new file mode 100644 index 00000000000..a49582f814e --- /dev/null +++ b/homeassistant/components/guardian/translations/pl.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "connection_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia z urz\u0105dzeniem Guardian, spr\u00f3buj ponownie." + }, + "step": { + "user": { + "data": { + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/translations/pl.json b/homeassistant/components/lutron_caseta/translations/pl.json index 970d722fe4c..9559e7f0e83 100644 --- a/homeassistant/components/lutron_caseta/translations/pl.json +++ b/homeassistant/components/lutron_caseta/translations/pl.json @@ -1,3 +1,7 @@ { - "title": "Lutron Cas\u00e9ta" + "config": { + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia mostkiem Cas\u00e9ta, sprawd\u017a konfiguracj\u0119 hosta i certyfikatu." + } + } } \ No newline at end of file diff --git a/homeassistant/components/plugwise/translations/pl.json b/homeassistant/components/plugwise/translations/pl.json new file mode 100644 index 00000000000..8fc4d27abbf --- /dev/null +++ b/homeassistant/components/plugwise/translations/pl.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "TenSmile jest ju\u017c skonfigurowany." + }, + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie.", + "invalid_auth": "Nieudane uwierzytelnienie, sprawd\u017a Smile ID", + "unknown": "Nieoczekiwany b\u0142\u0105d." + }, + "step": { + "user": { + "data": { + "host": "Adres IP Smile", + "password": "Smile ID" + }, + "description": "Szczeg\u00f3\u0142y", + "title": "Po\u0142\u0105cz si\u0119 ze Smile" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sonarr/translations/ca.json b/homeassistant/components/sonarr/translations/ca.json new file mode 100644 index 00000000000..955d94e9202 --- /dev/null +++ b/homeassistant/components/sonarr/translations/ca.json @@ -0,0 +1,37 @@ +{ + "config": { + "abort": { + "already_configured": "El servei ja est\u00e0 configurat", + "unknown": "Error inesperat" + }, + "error": { + "cannot_connect": "No s'ha pogut connectar", + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida" + }, + "flow_title": "Sonarr: {name}", + "step": { + "user": { + "data": { + "api_key": "Clau API", + "base_path": "Ruta a l'API", + "host": "Amfitri\u00f3", + "port": "Port", + "ssl": "Sonarr utilitza un certificat SSL", + "verify_ssl": "Sonarr utilitza un certificat adequat" + }, + "title": "Connexi\u00f3 amb Sonarr" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "upcoming_days": "Nombre de dies propers a mostrar", + "wanted_max_items": "Nombre m\u00e0xim d'elements a mostrar" + } + } + } + }, + "title": "Sonarr" +} \ No newline at end of file diff --git a/homeassistant/components/sonarr/translations/en.json b/homeassistant/components/sonarr/translations/en.json index d60565ee19d..9e62ea16d77 100644 --- a/homeassistant/components/sonarr/translations/en.json +++ b/homeassistant/components/sonarr/translations/en.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "Sonarr is already configured", + "already_configured": "Service is already configured", "unknown": "Unexpected error" }, "error": { - "cannot_connect": "Failed to connect, please try again", + "cannot_connect": "Failed to connect", "invalid_auth": "Invalid authentication" }, "flow_title": "Sonarr: {name}", @@ -14,7 +14,7 @@ "data": { "api_key": "API Key", "base_path": "Path to API", - "host": "Host or IP address", + "host": "Host", "port": "Port", "ssl": "Sonarr uses a SSL certificate", "verify_ssl": "Sonarr uses a proper certificate" diff --git a/homeassistant/components/sonarr/translations/fr.json b/homeassistant/components/sonarr/translations/fr.json new file mode 100644 index 00000000000..f613debcfbc --- /dev/null +++ b/homeassistant/components/sonarr/translations/fr.json @@ -0,0 +1,3 @@ +{ + "title": "Sonarr" +} \ No newline at end of file diff --git a/homeassistant/components/sonarr/translations/no.json b/homeassistant/components/sonarr/translations/no.json new file mode 100644 index 00000000000..af05fe620ee --- /dev/null +++ b/homeassistant/components/sonarr/translations/no.json @@ -0,0 +1,13 @@ +{ + "config": { + "flow_title": "", + "step": { + "user": { + "data": { + "port": "" + } + } + } + }, + "title": "" +} \ No newline at end of file diff --git a/homeassistant/components/sonarr/translations/pl.json b/homeassistant/components/sonarr/translations/pl.json new file mode 100644 index 00000000000..e2c60427b7e --- /dev/null +++ b/homeassistant/components/sonarr/translations/pl.json @@ -0,0 +1,37 @@ +{ + "config": { + "abort": { + "already_configured": "Us\u0142uga jest ju\u017c skonfigurowana.", + "unknown": "Nieoczekiwany b\u0142\u0105d." + }, + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia.", + "invalid_auth": "Niepoprawne uwierzytelnienie." + }, + "flow_title": "Sonarr: {name}", + "step": { + "user": { + "data": { + "api_key": "Klucz API", + "base_path": "\u015acie\u017cka do API", + "host": "Nazwa hosta lub adres IP", + "port": "Port", + "ssl": "Sonarr u\u017cywa certyfikatu SSL", + "verify_ssl": "Sonarr u\u017cywa prawid\u0142owego certyfikatu" + }, + "title": "Po\u0142\u0105cz z Sonarr" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "upcoming_days": "Liczba nadchodz\u0105cych dni do wy\u015bwietlenia", + "wanted_max_items": "Maksymalna liczba wy\u015bwietlanych element\u00f3w" + } + } + } + }, + "title": "Sonarr" +} \ No newline at end of file diff --git a/homeassistant/components/sonarr/translations/sv.json b/homeassistant/components/sonarr/translations/sv.json new file mode 100644 index 00000000000..bbdafea6798 --- /dev/null +++ b/homeassistant/components/sonarr/translations/sv.json @@ -0,0 +1,9 @@ +{ + "config": { + "step": { + "user": { + "title": "Anslut till Sonarr" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sonarr/translations/zh-Hant.json b/homeassistant/components/sonarr/translations/zh-Hant.json new file mode 100644 index 00000000000..dc03a007099 --- /dev/null +++ b/homeassistant/components/sonarr/translations/zh-Hant.json @@ -0,0 +1,37 @@ +{ + "config": { + "abort": { + "already_configured": "\u670d\u52d9\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + }, + "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548" + }, + "flow_title": "Sonarr\uff1a{name}", + "step": { + "user": { + "data": { + "api_key": "API \u5bc6\u9470", + "base_path": "API \u8def\u5f91", + "host": "\u4e3b\u6a5f\u7aef", + "port": "\u901a\u8a0a\u57e0", + "ssl": "Sonarr \u4f7f\u7528 SSL \u8a8d\u8b49", + "verify_ssl": "Sonarr \u4f7f\u7528\u5c0d\u61c9\u8a8d\u8b49" + }, + "title": "\u9023\u7dda\u81f3 Sonarr" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "upcoming_days": "\u5373\u5c07\u5230\u4f86\u986f\u793a\u5929\u6578", + "wanted_max_items": "\u9700\u6c42\u9805\u76ee\u6700\u5927\u986f\u793a\u6578" + } + } + } + }, + "title": "Sonarr" +} \ No newline at end of file diff --git a/homeassistant/components/unifi/translations/ca.json b/homeassistant/components/unifi/translations/ca.json index aaeecd9d51e..de2a8ffc562 100644 --- a/homeassistant/components/unifi/translations/ca.json +++ b/homeassistant/components/unifi/translations/ca.json @@ -35,7 +35,7 @@ "device_tracker": { "data": { "detection_time": "Temps (en segons) des de s'ha vist per \u00faltima vegada fins que es considera a fora", - "ignore_wired_bug": "Desactiva la l\u00f2gica d\u2019errors amb UniFi", + "ignore_wired_bug": "Desactiva la l\u00f2gica d'errors amb UniFi", "ssid_filter": "Selecciona els SSID's on fer-hi el seguiment de clients", "track_clients": "Segueix clients de la xarxa", "track_devices": "Segueix dispositius de la xarxa (dispositius Ubiquiti)", diff --git a/homeassistant/components/upb/translations/pl.json b/homeassistant/components/upb/translations/pl.json index 0ec840b87f9..abbda657dc5 100644 --- a/homeassistant/components/upb/translations/pl.json +++ b/homeassistant/components/upb/translations/pl.json @@ -1,6 +1,7 @@ { "config": { "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia z UPB PIM, spr\u00f3buj ponownie.", "unknown": "Nieoczekiwany b\u0142\u0105d." } } diff --git a/homeassistant/components/vizio/translations/pl.json b/homeassistant/components/vizio/translations/pl.json index cc94dabaa12..9d22796ea44 100644 --- a/homeassistant/components/vizio/translations/pl.json +++ b/homeassistant/components/vizio/translations/pl.json @@ -1,9 +1,11 @@ { "config": { "abort": { + "already_configured_device": "Urz\u0105dzenie jest ju\u017c skonfigurowane.", "updated_entry": "Ten wpis zosta\u0142 ju\u017c skonfigurowany, ale nazwa i/lub opcje zdefiniowane w konfiguracji nie pasuj\u0105 do wcze\u015bniej zaimportowanych warto\u015bci, wi\u0119c wpis konfiguracji zosta\u0142 odpowiednio zaktualizowany." }, "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia.", "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." diff --git a/homeassistant/components/zwave/translations/pl.json b/homeassistant/components/zwave/translations/pl.json index 3ff6d9cd194..f871ab928b6 100644 --- a/homeassistant/components/zwave/translations/pl.json +++ b/homeassistant/components/zwave/translations/pl.json @@ -5,7 +5,7 @@ "one_instance_only": "Komponent obs\u0142uguje tylko jedn\u0105 instancj\u0119 Z-Wave" }, "error": { - "option_error": "Walidacja Z-Wave nie powiod\u0142a si\u0119. Czy \u015bcie\u017cka do kontrolera Z-Wave USB jest prawid\u0142owa?" + "option_error": "Walidacja Z-Wave si\u0119 nie powiod\u0142a. Czy \u015bcie\u017cka do kontrolera Z-Wave USB jest prawid\u0142owa?" }, "step": { "user": { From 2a86d52dba1ad63c0671f1f528f2c374aeb87aa2 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 30 May 2020 21:42:40 -0700 Subject: [PATCH 267/406] Alexa media player only include equalizer if supported sound mode (#36285) --- .../components/alexa/capabilities.py | 44 +++++++++++-------- homeassistant/components/alexa/entities.py | 6 ++- tests/components/alexa/test_smart_home.py | 11 +++-- 3 files changed, 38 insertions(+), 23 deletions(-) diff --git a/homeassistant/components/alexa/capabilities.py b/homeassistant/components/alexa/capabilities.py index c8eb932a9e0..c11e974310c 100644 --- a/homeassistant/components/alexa/capabilities.py +++ b/homeassistant/components/alexa/capabilities.py @@ -1803,6 +1803,13 @@ class AlexaEqualizerController(AlexaCapability): """ supported_locales = {"en-US"} + VALID_SOUND_MODES = { + "MOVIE", + "MUSIC", + "NIGHT", + "SPORT", + "TV", + } def name(self): """Return the Alexa API name of this interface.""" @@ -1821,35 +1828,34 @@ class AlexaEqualizerController(AlexaCapability): raise UnsupportedProperty(name) sound_mode = self.entity.attributes.get(media_player.ATTR_SOUND_MODE) - if sound_mode and sound_mode.upper() in ( - "MOVIE", - "MUSIC", - "NIGHT", - "SPORT", - "TV", - ): + if sound_mode and sound_mode.upper() in self.VALID_SOUND_MODES: return sound_mode.upper() return None def configurations(self): - """Return the sound modes supported in the configurations object. - - Valid Values for modes are: MOVIE, MUSIC, NIGHT, SPORT, TV. - """ + """Return the sound modes supported in the configurations object.""" configurations = None - sound_mode_list = self.entity.attributes.get(media_player.ATTR_SOUND_MODE_LIST) - if sound_mode_list: - supported_sound_modes = [ - {"name": sound_mode.upper()} - for sound_mode in sound_mode_list - if sound_mode.upper() in ("MOVIE", "MUSIC", "NIGHT", "SPORT", "TV") - ] - + supported_sound_modes = self.get_valid_inputs( + self.entity.attributes.get(media_player.ATTR_SOUND_MODE_LIST, []) + ) + if supported_sound_modes: configurations = {"modes": {"supported": supported_sound_modes}} return configurations + @classmethod + def get_valid_inputs(cls, sound_mode_list): + """Return list of supported inputs.""" + input_list = [] + for sound_mode in sound_mode_list: + sound_mode = sound_mode.upper() + + if sound_mode in cls.VALID_SOUND_MODES: + input_list.append({"name": sound_mode}) + + return input_list + class AlexaTimeHoldController(AlexaCapability): """Implements Alexa.TimeHoldController. diff --git a/homeassistant/components/alexa/entities.py b/homeassistant/components/alexa/entities.py index e74722b89e8..972df08bd1e 100644 --- a/homeassistant/components/alexa/entities.py +++ b/homeassistant/components/alexa/entities.py @@ -547,7 +547,11 @@ class MediaPlayerCapabilities(AlexaEntity): yield AlexaChannelController(self.entity) if supported & media_player.const.SUPPORT_SELECT_SOUND_MODE: - yield AlexaEqualizerController(self.entity) + inputs = AlexaInputController.get_valid_inputs( + self.entity.attributes.get(media_player.const.ATTR_SOUND_MODE_LIST, []) + ) + if len(inputs) > 0: + yield AlexaEqualizerController(self.entity) yield AlexaEndpointHealth(self.hass, self.entity) yield Alexa(self.hass) diff --git a/tests/components/alexa/test_smart_home.py b/tests/components/alexa/test_smart_home.py index 591d200ef90..a72099da600 100644 --- a/tests/components/alexa/test_smart_home.py +++ b/tests/components/alexa/test_smart_home.py @@ -3297,8 +3297,8 @@ async def test_media_player_eq_modes(hass): assert call.data["sound_mode"] == mode.lower() -async def test_media_player_sound_mode_list_none(hass): - """Test EqualizerController bands directive not supported.""" +async def test_media_player_sound_mode_list_unsupported(hass): + """Test EqualizerController with unsupported sound modes.""" device = ( "media_player.test", "on", @@ -3306,13 +3306,18 @@ async def test_media_player_sound_mode_list_none(hass): "friendly_name": "Test media player", "supported_features": SUPPORT_SELECT_SOUND_MODE, "sound_mode": "unknown", - "sound_mode_list": None, + "sound_mode_list": ["unsupported", "non-existing"], }, ) appliance = await discovery_test(device, hass) assert appliance["endpointId"] == "media_player#test" assert appliance["friendlyName"] == "Test media player" + # Test equalizer controller is not there + assert_endpoint_capabilities( + appliance, "Alexa", "Alexa.PowerController", "Alexa.EndpointHealth", + ) + async def test_media_player_eq_bands_not_supported(hass): """Test EqualizerController bands directive not supported.""" From cd054a9579b3025ff37b83527e87f8dca960d54c Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Sun, 31 May 2020 04:03:11 -0400 Subject: [PATCH 268/406] clean up code to improve consistency and revert previous change (#35752) --- homeassistant/components/vizio/const.py | 1 + .../components/vizio/media_player.py | 21 ++++++++++--------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/vizio/const.py b/homeassistant/components/vizio/const.py index 43cb993cec3..72bd9b6b08a 100644 --- a/homeassistant/components/vizio/const.py +++ b/homeassistant/components/vizio/const.py @@ -65,6 +65,7 @@ SUPPORTED_COMMANDS = { VIZIO_SOUND_MODE = "eq" VIZIO_AUDIO_SETTINGS = "audio" +VIZIO_MUTE_ON = "on" # Since Vizio component relies on device class, this dict will ensure that changes to # the values of DEVICE_CLASS_SPEAKER or DEVICE_CLASS_TV don't require changes to pyvizio. diff --git a/homeassistant/components/vizio/media_player.py b/homeassistant/components/vizio/media_player.py index 5a191b22cd4..942411b0536 100644 --- a/homeassistant/components/vizio/media_player.py +++ b/homeassistant/components/vizio/media_player.py @@ -44,6 +44,7 @@ from .const import ( SUPPORTED_COMMANDS, VIZIO_AUDIO_SETTINGS, VIZIO_DEVICE_CLASSES, + VIZIO_MUTE_ON, VIZIO_SOUND_MODE, ) @@ -202,10 +203,10 @@ class VizioDevice(MediaPlayerEntity): audio_settings = await self._device.get_all_settings( VIZIO_AUDIO_SETTINGS, log_api_exception=False ) - if audio_settings is not None: + if audio_settings: self._volume_level = float(audio_settings["volume"]) / self._max_volume if "mute" in audio_settings: - self._is_volume_muted = audio_settings["mute"].lower() == "on" + self._is_volume_muted = audio_settings["mute"].lower() == VIZIO_MUTE_ON else: self._is_volume_muted = None @@ -217,20 +218,20 @@ class VizioDevice(MediaPlayerEntity): VIZIO_AUDIO_SETTINGS, VIZIO_SOUND_MODE ) else: - self._supported_commands ^= SUPPORT_SELECT_SOUND_MODE + # Explicitly remove SUPPORT_SELECT_SOUND_MODE from supported features + self._supported_commands &= ~SUPPORT_SELECT_SOUND_MODE input_ = await self._device.get_current_input(log_api_exception=False) - if input_ is not None: + if input_: self._current_input = input_ - if not self._available_inputs: - inputs = await self._device.get_inputs_list(log_api_exception=False) + inputs = await self._device.get_inputs_list(log_api_exception=False) - # If no inputs returned, end update - if not inputs: - return + # If no inputs returned, end update + if not inputs: + return - self._available_inputs = [input_.name for input_ in inputs] + self._available_inputs = [input_.name for input_ in inputs] # Return before setting app variables if INPUT_APPS isn't in available inputs if self._device_class == DEVICE_CLASS_SPEAKER or not any( From 0ae23fa166308272bb4f1ff0e949f152fa42e15e Mon Sep 17 00:00:00 2001 From: Emilv2 Date: Sun, 31 May 2020 17:02:56 +0200 Subject: [PATCH 269/406] Remove duplicate information in delijn sensor (#36276) --- CODEOWNERS | 2 +- homeassistant/components/delijn/manifest.json | 2 +- homeassistant/components/delijn/sensor.py | 2 -- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 26d9c1f58b9..c51b087c031 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -87,7 +87,7 @@ homeassistant/components/cups/* @fabaff homeassistant/components/daikin/* @fredrike homeassistant/components/darksky/* @fabaff homeassistant/components/deconz/* @Kane610 -homeassistant/components/delijn/* @bollewolle +homeassistant/components/delijn/* @bollewolle @Emilv2 homeassistant/components/demo/* @home-assistant/core homeassistant/components/denonavr/* @scarface-4711 @starkillerOG homeassistant/components/derivative/* @afaucogney diff --git a/homeassistant/components/delijn/manifest.json b/homeassistant/components/delijn/manifest.json index 8727dd25139..e3ab4f29512 100644 --- a/homeassistant/components/delijn/manifest.json +++ b/homeassistant/components/delijn/manifest.json @@ -2,6 +2,6 @@ "domain": "delijn", "name": "De Lijn", "documentation": "https://www.home-assistant.io/integrations/delijn", - "codeowners": ["@bollewolle"], + "codeowners": ["@bollewolle", "@Emilv2"], "requirements": ["pydelijn==0.6.0"] } diff --git a/homeassistant/components/delijn/sensor.py b/homeassistant/components/delijn/sensor.py index 2c7eec1691c..7673e473e43 100644 --- a/homeassistant/components/delijn/sensor.py +++ b/homeassistant/components/delijn/sensor.py @@ -81,8 +81,6 @@ class DeLijnPublicTransportSensor(Entity): return self._attributes["stopname"] = self._name - for passage in self.line.passages: - passage["stopname"] = self._name try: first = self.line.passages[0] From 7197ef76a67a720ef682c765d6d8c24afce557e8 Mon Sep 17 00:00:00 2001 From: Brian Rogers Date: Sun, 31 May 2020 11:26:56 -0400 Subject: [PATCH 270/406] Add Rachio rain sensor status (#36229) * Add Rachio rain sensor status * Remove summary * Fix and re-add zone webhook summary * Add slope to const --- .../components/rachio/binary_sensor.py | 85 +++++++++++++++---- homeassistant/components/rachio/const.py | 12 ++- homeassistant/components/rachio/switch.py | 26 ++++-- homeassistant/components/rachio/webhooks.py | 10 ++- 4 files changed, 107 insertions(+), 26 deletions(-) diff --git a/homeassistant/components/rachio/binary_sensor.py b/homeassistant/components/rachio/binary_sensor.py index 49f46578f76..495001e39b9 100644 --- a/homeassistant/components/rachio/binary_sensor.py +++ b/homeassistant/components/rachio/binary_sensor.py @@ -4,6 +4,7 @@ import logging from homeassistant.components.binary_sensor import ( DEVICE_CLASS_CONNECTIVITY, + DEVICE_CLASS_MOISTURE, BinarySensorEntity, ) from homeassistant.core import callback @@ -12,13 +13,21 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect from .const import ( DOMAIN as DOMAIN_RACHIO, KEY_DEVICE_ID, + KEY_RAIN_SENSOR_TRIPPED, KEY_STATUS, KEY_SUBTYPE, SIGNAL_RACHIO_CONTROLLER_UPDATE, + SIGNAL_RACHIO_RAIN_SENSOR_UPDATE, STATUS_ONLINE, ) from .entity import RachioDevice -from .webhooks import SUBTYPE_COLD_REBOOT, SUBTYPE_OFFLINE, SUBTYPE_ONLINE +from .webhooks import ( + SUBTYPE_COLD_REBOOT, + SUBTYPE_OFFLINE, + SUBTYPE_ONLINE, + SUBTYPE_RAIN_SENSOR_DETECTION_OFF, + SUBTYPE_RAIN_SENSOR_DETECTION_ON, +) _LOGGER = logging.getLogger(__name__) @@ -34,6 +43,7 @@ def _create_entities(hass, config_entry): entities = [] for controller in hass.data[DOMAIN_RACHIO][config_entry.entry_id].controllers: entities.append(RachioControllerOnlineBinarySensor(controller)) + entities.append(RachioRainSensor(controller)) return entities @@ -64,16 +74,6 @@ class RachioControllerBinarySensor(RachioDevice, BinarySensorEntity): def _async_handle_update(self, *args, **kwargs) -> None: """Handle an update to the state of this sensor.""" - async def async_added_to_hass(self): - """Subscribe to updates.""" - self.async_on_remove( - async_dispatcher_connect( - self.hass, - SIGNAL_RACHIO_CONTROLLER_UPDATE, - self._async_handle_any_update, - ) - ) - class RachioControllerOnlineBinarySensor(RachioControllerBinarySensor): """Represent a binary sensor that reflects if the controller is online.""" @@ -98,11 +98,6 @@ 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" - 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: """Handle an update to the state of this sensor.""" @@ -115,3 +110,61 @@ class RachioControllerOnlineBinarySensor(RachioControllerBinarySensor): self._state = False self.async_write_ha_state() + + async def async_added_to_hass(self): + """Subscribe to updates.""" + self._state = self._controller.init_data[KEY_STATUS] == STATUS_ONLINE + + self.async_on_remove( + async_dispatcher_connect( + self.hass, + SIGNAL_RACHIO_CONTROLLER_UPDATE, + self._async_handle_any_update, + ) + ) + + +class RachioRainSensor(RachioControllerBinarySensor): + """Represent a binary sensor that reflects the status of the rain sensor.""" + + @property + def name(self) -> str: + """Return the name of this sensor including the controller name.""" + return f"{self._controller.name} rain sensor" + + @property + def unique_id(self) -> str: + """Return a unique id for this entity.""" + return f"{self._controller.controller_id}-rain_sensor" + + @property + def device_class(self) -> str: + """Return the class of this device.""" + return DEVICE_CLASS_MOISTURE + + @property + def icon(self) -> str: + """Return the icon for this sensor.""" + return "mdi:water" if self.is_on else "mdi:water-off" + + @callback + def _async_handle_update(self, *args, **kwargs) -> None: + """Handle an update to the state of this sensor.""" + if args[0][0][KEY_SUBTYPE] == SUBTYPE_RAIN_SENSOR_DETECTION_ON: + self._state = True + elif args[0][0][KEY_SUBTYPE] == SUBTYPE_RAIN_SENSOR_DETECTION_OFF: + self._state = False + + self.async_write_ha_state() + + async def async_added_to_hass(self): + """Subscribe to updates.""" + self._state = self._controller.init_data[KEY_RAIN_SENSOR_TRIPPED] + + self.async_on_remove( + async_dispatcher_connect( + self.hass, + SIGNAL_RACHIO_RAIN_SENSOR_UPDATE, + self._async_handle_any_update, + ) + ) diff --git a/homeassistant/components/rachio/const.py b/homeassistant/components/rachio/const.py index 016218906f4..7f8111bd5e5 100644 --- a/homeassistant/components/rachio/const.py +++ b/homeassistant/components/rachio/const.py @@ -12,6 +12,12 @@ CONF_CUSTOM_URL = "hass_url_override" CONF_MANUAL_RUN_MINS = "manual_run_mins" DEFAULT_MANUAL_RUN_MINS = 10 +# Slope constants +SLOPE_FLAT = "ZERO_THREE" +SLOPE_SLIGHT = "FOUR_SIX" +SLOPE_MODERATE = "SEVEN_TWELVE" +SLOPE_STEEP = "OVER_TWELVE" + # Keys used in the API JSON KEY_DEVICE_ID = "deviceId" KEY_IMAGE_URL = "imageUrl" @@ -24,6 +30,7 @@ KEY_MODEL = "model" KEY_ON = "on" KEY_DURATION = "totalDuration" KEY_RAIN_DELAY = "rainDelayExpirationDate" +KEY_RAIN_SENSOR_TRIPPED = "rainSensorTripped" KEY_STATUS = "status" KEY_SUBTYPE = "subType" KEY_SUMMARY = "summary" @@ -40,9 +47,7 @@ KEY_FLEX_SCHEDULES = "flexScheduleRules" KEY_SCHEDULE_ID = "scheduleId" KEY_CUSTOM_SHADE = "customShade" KEY_CUSTOM_CROP = "customCrop" - -ATTR_ZONE_TYPE = "type" -ATTR_ZONE_SHADE = "shade" +KEY_CUSTOM_SLOPE = "customSlope" # Yes we really do get all these exceptions (hopefully rachiopy switches to requests) RACHIO_API_EXCEPTIONS = ( @@ -57,6 +62,7 @@ STATUS_ONLINE = "ONLINE" 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_RAIN_SENSOR_UPDATE = f"{SIGNAL_RACHIO_UPDATE}_rain_sensor" 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 95f01d31518..b16e3ce529e 100644 --- a/homeassistant/components/rachio/switch.py +++ b/homeassistant/components/rachio/switch.py @@ -9,13 +9,12 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.util.dt import as_timestamp, now from .const import ( - ATTR_ZONE_SHADE, - ATTR_ZONE_TYPE, CONF_MANUAL_RUN_MINS, DEFAULT_MANUAL_RUN_MINS, DOMAIN as DOMAIN_RACHIO, KEY_CUSTOM_CROP, KEY_CUSTOM_SHADE, + KEY_CUSTOM_SLOPE, KEY_DEVICE_ID, KEY_DURATION, KEY_ENABLED, @@ -33,6 +32,10 @@ from .const import ( SIGNAL_RACHIO_RAIN_DELAY_UPDATE, SIGNAL_RACHIO_SCHEDULE_UPDATE, SIGNAL_RACHIO_ZONE_UPDATE, + SLOPE_FLAT, + SLOPE_MODERATE, + SLOPE_SLIGHT, + SLOPE_STEEP, ) from .entity import RachioDevice from .webhooks import ( @@ -50,11 +53,14 @@ from .webhooks import ( _LOGGER = logging.getLogger(__name__) -ATTR_ZONE_SUMMARY = "Summary" -ATTR_ZONE_NUMBER = "Zone number" ATTR_SCHEDULE_SUMMARY = "Summary" ATTR_SCHEDULE_ENABLED = "Enabled" ATTR_SCHEDULE_DURATION = "Duration" +ATTR_ZONE_NUMBER = "Zone number" +ATTR_ZONE_SHADE = "Shade" +ATTR_ZONE_SLOPE = "Slope" +ATTR_ZONE_SUMMARY = "Summary" +ATTR_ZONE_TYPE = "Type" async def async_setup_entry(hass, config_entry, async_add_entities): @@ -235,6 +241,7 @@ class RachioZone(RachioSwitch): self._person = person self._shade_type = data.get(KEY_CUSTOM_SHADE, {}).get(KEY_NAME) self._zone_type = data.get(KEY_CUSTOM_CROP, {}).get(KEY_NAME) + self._slope_type = data.get(KEY_CUSTOM_SLOPE, {}).get(KEY_NAME) self._summary = "" self._current_schedule = current_schedule super().__init__(controller) @@ -281,6 +288,15 @@ class RachioZone(RachioSwitch): props[ATTR_ZONE_SHADE] = self._shade_type if self._zone_type: props[ATTR_ZONE_TYPE] = self._zone_type + if self._slope_type: + if self._slope_type == SLOPE_FLAT: + props[ATTR_ZONE_SLOPE] = "Flat" + elif self._slope_type == SLOPE_SLIGHT: + props[ATTR_ZONE_SLOPE] = "Slight" + elif self._slope_type == SLOPE_MODERATE: + props[ATTR_ZONE_SLOPE] = "Moderate" + elif self._slope_type == SLOPE_STEEP: + props[ATTR_ZONE_SLOPE] = "Steep" return props def turn_on(self, **kwargs) -> None: @@ -312,7 +328,7 @@ class RachioZone(RachioSwitch): if args[0][KEY_ZONE_ID] != self.zone_id: return - self._summary = kwargs.get(KEY_SUMMARY, "") + self._summary = args[0][KEY_SUMMARY] if args[0][KEY_SUBTYPE] == SUBTYPE_ZONE_STARTED: self._state = True diff --git a/homeassistant/components/rachio/webhooks.py b/homeassistant/components/rachio/webhooks.py index a3f95d5a5f3..5daf7852725 100644 --- a/homeassistant/components/rachio/webhooks.py +++ b/homeassistant/components/rachio/webhooks.py @@ -16,6 +16,7 @@ from .const import ( KEY_TYPE, SIGNAL_RACHIO_CONTROLLER_UPDATE, SIGNAL_RACHIO_RAIN_DELAY_UPDATE, + SIGNAL_RACHIO_RAIN_SENSOR_UPDATE, SIGNAL_RACHIO_SCHEDULE_UPDATE, SIGNAL_RACHIO_ZONE_UPDATE, ) @@ -29,14 +30,17 @@ SUBTYPE_COLD_REBOOT = "COLD_REBOOT" SUBTYPE_SLEEP_MODE_ON = "SLEEP_MODE_ON" 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" +# Rain sensor values +TYPE_RAIN_SENSOR_STATUS = "RAIN_SENSOR_DETECTION" +SUBTYPE_RAIN_SENSOR_DETECTION_ON = "RAIN_SENSOR_DETECTION_ON" +SUBTYPE_RAIN_SENSOR_DETECTION_OFF = "RAIN_SENSOR_DETECTION_OFF" + # Schedule webhook values TYPE_SCHEDULE_STATUS = "SCHEDULE_STATUS" SUBTYPE_SCHEDULE_STARTED = "SCHEDULE_STARTED" @@ -60,6 +64,7 @@ LISTEN_EVENT_TYPES = [ "DEVICE_STATUS_EVENT", "ZONE_STATUS_EVENT", "RAIN_DELAY_EVENT", + "RAIN_SENSOR_DETECTION_EVENT", "SCHEDULE_STATUS_EVENT", ] WEBHOOK_CONST_ID = "homeassistant.rachio:" @@ -68,6 +73,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_RAIN_SENSOR_STATUS: SIGNAL_RACHIO_RAIN_SENSOR_UPDATE, TYPE_SCHEDULE_STATUS: SIGNAL_RACHIO_SCHEDULE_UPDATE, TYPE_ZONE_STATUS: SIGNAL_RACHIO_ZONE_UPDATE, } From 6ed68d8ced66c2c91e82f6881c7e954c2035dd5a Mon Sep 17 00:00:00 2001 From: Igor Date: Sun, 31 May 2020 20:58:02 +0300 Subject: [PATCH 271/406] Do not show graphite warnings if no new_state in event (#36292) It is not correct to show warning about "unexpected event type" if EVENT_STATE_CHANGED have no new_state field. We should show this warning only if it is real unexpected event type. Run task_done() before continue, because we should tell the queue that the processing on the task is complete after get(). --- homeassistant/components/graphite/__init__.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/graphite/__init__.py b/homeassistant/components/graphite/__init__.py index 00dabd59d8f..327e8293be7 100644 --- a/homeassistant/components/graphite/__init__.py +++ b/homeassistant/components/graphite/__init__.py @@ -139,7 +139,16 @@ class GraphiteFeeder(threading.Thread): _LOGGER.debug("Event processing thread stopped") self._queue.task_done() return - if event.event_type == EVENT_STATE_CHANGED and event.data.get("new_state"): + if event.event_type == EVENT_STATE_CHANGED: + if not event.data.get("new_state"): + _LOGGER.debug( + "Skipping %s without new_state for %s", + event.event_type, + event.data["entity_id"], + ) + self._queue.task_done() + continue + _LOGGER.debug( "Processing STATE_CHANGED event for %s", event.data["entity_id"] ) From 01d936629907b05940379dcc65bdaf71c33c1281 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Sun, 31 May 2020 20:00:15 +0200 Subject: [PATCH 272/406] Improve Axis integration (#36205) * Improve configuration * Read new properties for basic device information Improve tests by mocking less improving stability of tests * Clean up in device tests * Support new port management api * Improve initializing data * Bump dependency to v28 --- homeassistant/components/axis/__init__.py | 2 +- homeassistant/components/axis/config_flow.py | 7 +- homeassistant/components/axis/device.py | 19 +-- homeassistant/components/axis/manifest.json | 2 +- homeassistant/components/axis/switch.py | 5 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/axis/test_config_flow.py | 101 ++++-------- tests/components/axis/test_device.py | 162 +++++++++++++------ tests/components/axis/test_switch.py | 65 ++++++-- 10 files changed, 215 insertions(+), 152 deletions(-) diff --git a/homeassistant/components/axis/__init__.py b/homeassistant/components/axis/__init__.py index 4b9cb7d20cf..eedeac01366 100644 --- a/homeassistant/components/axis/__init__.py +++ b/homeassistant/components/axis/__init__.py @@ -27,7 +27,7 @@ async def async_setup_entry(hass, config_entry): # 0.104 introduced config entry unique id, this makes upgrading possible if config_entry.unique_id is None: hass.config_entries.async_update_entry( - config_entry, unique_id=device.api.vapix.params.system_serialnumber + config_entry, unique_id=device.api.vapix.serial_number ) hass.data[AXIS_DOMAIN][config_entry.unique_id] = device diff --git a/homeassistant/components/axis/config_flow.py b/homeassistant/components/axis/config_flow.py index 37141d6017a..508bee6aff5 100644 --- a/homeassistant/components/axis/config_flow.py +++ b/homeassistant/components/axis/config_flow.py @@ -61,8 +61,7 @@ class AxisFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): password=user_input[CONF_PASSWORD], ) - serial_number = device.vapix.params.system_serialnumber - await self.async_set_unique_id(serial_number) + await self.async_set_unique_id(device.vapix.serial_number) self._abort_if_unique_id_configured( updates={ @@ -76,8 +75,8 @@ class AxisFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): CONF_PORT: user_input[CONF_PORT], CONF_USERNAME: user_input[CONF_USERNAME], CONF_PASSWORD: user_input[CONF_PASSWORD], - CONF_MAC: serial_number, - CONF_MODEL: device.vapix.params.prodnbr, + CONF_MAC: device.vapix.serial_number, + CONF_MODEL: device.vapix.product_number, } return await self._create_entry() diff --git a/homeassistant/components/axis/device.py b/homeassistant/components/axis/device.py index cc327cb3b65..54e534068a8 100644 --- a/homeassistant/components/axis/device.py +++ b/homeassistant/components/axis/device.py @@ -4,6 +4,7 @@ import asyncio import async_timeout import axis +from axis.configuration import Configuration from axis.event_stream import OPERATION_INITIALIZED from axis.mqtt import mqtt_json_to_event from axis.streammanager import SIGNAL_PLAYING, STATE_STOPPED @@ -185,8 +186,8 @@ class AxisNetworkDevice: LOGGER.error("Unknown error connecting with Axis device on %s", self.host) return False - self.fw_version = self.api.vapix.params.firmware_version - self.product_type = self.api.vapix.params.prodtype + self.fw_version = self.api.vapix.firmware_version + self.product_type = self.api.vapix.product_type async def start_platforms(): await asyncio.gather( @@ -254,22 +255,12 @@ async def get_device(hass, host, port, username, password): """Create a Axis device.""" device = axis.AxisDevice( - host=host, port=port, username=username, password=password, web_proto="http", + Configuration(host, port=port, username=username, password=password) ) - device.vapix.initialize_params(preload_data=False) - device.vapix.initialize_ports() - try: with async_timeout.timeout(15): - - for vapix_call in ( - device.vapix.initialize_api_discovery, - device.vapix.params.update_brand, - device.vapix.params.update_properties, - device.vapix.ports.update, - ): - await hass.async_add_executor_job(vapix_call) + await hass.async_add_executor_job(device.vapix.initialize) return device diff --git a/homeassistant/components/axis/manifest.json b/homeassistant/components/axis/manifest.json index dd33cdbf1ea..417e1781c7c 100644 --- a/homeassistant/components/axis/manifest.json +++ b/homeassistant/components/axis/manifest.json @@ -3,7 +3,7 @@ "name": "Axis", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/axis", - "requirements": ["axis==27"], + "requirements": ["axis==28"], "zeroconf": ["_axis-video._tcp.local."], "after_dependencies": ["mqtt"], "codeowners": ["@Kane610"] diff --git a/homeassistant/components/axis/switch.py b/homeassistant/components/axis/switch.py index db91115484f..256a22db114 100644 --- a/homeassistant/components/axis/switch.py +++ b/homeassistant/components/axis/switch.py @@ -1,7 +1,6 @@ """Support for Axis switches.""" from axis.event_stream import CLASS_OUTPUT -from axis.port_cgi import ACTION_HIGH, ACTION_LOW from homeassistant.components.switch import SwitchEntity from homeassistant.core import callback @@ -39,13 +38,13 @@ class AxisSwitch(AxisEventBase, SwitchEntity): async def async_turn_on(self, **kwargs): """Turn on switch.""" await self.hass.async_add_executor_job( - self.device.api.vapix.ports[self.event.id].action, ACTION_HIGH + self.device.api.vapix.ports[self.event.id].close ) async def async_turn_off(self, **kwargs): """Turn off switch.""" await self.hass.async_add_executor_job( - self.device.api.vapix.ports[self.event.id].action, ACTION_LOW + self.device.api.vapix.ports[self.event.id].open ) @property diff --git a/requirements_all.txt b/requirements_all.txt index 62ed538235a..98759a6a077 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -306,7 +306,7 @@ avea==1.4 avri-api==0.1.7 # homeassistant.components.axis -axis==27 +axis==28 # homeassistant.components.azure_event_hub azure-eventhub==1.3.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index da7a7f4fe58..44b069ab2ca 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -147,7 +147,7 @@ async-upnp-client==0.14.13 av==8.0.1 # homeassistant.components.axis -axis==27 +axis==28 # homeassistant.components.homekit base36==0.1.1 diff --git a/tests/components/axis/test_config_flow.py b/tests/components/axis/test_config_flow.py index 7cc0e3e535b..ab3516873fa 100644 --- a/tests/components/axis/test_config_flow.py +++ b/tests/components/axis/test_config_flow.py @@ -1,5 +1,4 @@ """Test Axis config flow.""" -from homeassistant.components import axis from homeassistant.components.axis import config_flow from homeassistant.components.axis.const import CONF_MODEL, DOMAIN as AXIS_DOMAIN from homeassistant.const import ( @@ -11,30 +10,12 @@ from homeassistant.const import ( CONF_USERNAME, ) -from .test_device import MAC, MODEL, NAME, setup_axis_integration +from .test_device import MAC, MODEL, NAME, setup_axis_integration, vapix_session_request -from tests.async_mock import Mock, patch +from tests.async_mock import patch from tests.common import MockConfigEntry -def setup_mock_axis_device(mock_device): - """Prepare mock axis device.""" - - def mock_constructor(host, username, password, port, web_proto): - """Fake the controller constructor.""" - mock_device.host = host - mock_device.username = username - mock_device.password = password - mock_device.port = port - return mock_device - - mock_device.side_effect = mock_constructor - mock_device.vapix.params.system_serialnumber = MAC - mock_device.vapix.params.prodnbr = "prodnbr" - mock_device.vapix.params.prodtype = "prodtype" - mock_device.vapix.params.firmware_version = "firmware_version" - - async def test_flow_manual_configuration(hass): """Test that config flow works.""" result = await hass.config_entries.flow.async_init( @@ -44,10 +25,7 @@ async def test_flow_manual_configuration(hass): assert result["type"] == "form" assert result["step_id"] == "user" - with patch("axis.AxisDevice") as mock_device: - - setup_mock_axis_device(mock_device) - + with patch("axis.vapix.session_request", new=vapix_session_request): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={ @@ -59,15 +37,15 @@ async def test_flow_manual_configuration(hass): ) assert result["type"] == "create_entry" - assert result["title"] == f"prodnbr - {MAC}" + assert result["title"] == f"M1065-LW - {MAC}" assert result["data"] == { CONF_HOST: "1.2.3.4", CONF_USERNAME: "user", CONF_PASSWORD: "pass", CONF_PORT: 80, CONF_MAC: MAC, - CONF_MODEL: "prodnbr", - CONF_NAME: "prodnbr 0", + CONF_MODEL: "M1065-LW", + CONF_NAME: "M1065-LW 0", } @@ -82,13 +60,7 @@ async def test_manual_configuration_update_configuration(hass): assert result["type"] == "form" assert result["step_id"] == "user" - mock_device = Mock() - mock_device.vapix.params.system_serialnumber = MAC - - with patch( - "homeassistant.components.axis.config_flow.get_device", - return_value=mock_device, - ): + with patch("axis.vapix.session_request", new=vapix_session_request): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={ @@ -115,13 +87,7 @@ async def test_flow_fails_already_configured(hass): assert result["type"] == "form" assert result["step_id"] == "user" - mock_device = Mock() - mock_device.vapix.params.system_serialnumber = MAC - - with patch( - "homeassistant.components.axis.config_flow.get_device", - return_value=mock_device, - ): + with patch("axis.vapix.session_request", new=vapix_session_request): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={ @@ -191,11 +157,11 @@ async def test_flow_fails_device_unavailable(hass): async def test_flow_create_entry_multiple_existing_entries_of_same_model(hass): """Test that create entry can generate a name with other entries.""" entry = MockConfigEntry( - domain=AXIS_DOMAIN, data={CONF_NAME: "prodnbr 0", CONF_MODEL: "prodnbr"}, + domain=AXIS_DOMAIN, data={CONF_NAME: "M1065-LW 0", CONF_MODEL: "M1065-LW"}, ) entry.add_to_hass(hass) entry2 = MockConfigEntry( - domain=AXIS_DOMAIN, data={CONF_NAME: "prodnbr 1", CONF_MODEL: "prodnbr"}, + domain=AXIS_DOMAIN, data={CONF_NAME: "M1065-LW 1", CONF_MODEL: "M1065-LW"}, ) entry2.add_to_hass(hass) @@ -206,10 +172,7 @@ async def test_flow_create_entry_multiple_existing_entries_of_same_model(hass): assert result["type"] == "form" assert result["step_id"] == "user" - with patch("axis.AxisDevice") as mock_device: - - setup_mock_axis_device(mock_device) - + with patch("axis.vapix.session_request", new=vapix_session_request): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={ @@ -221,41 +184,37 @@ async def test_flow_create_entry_multiple_existing_entries_of_same_model(hass): ) assert result["type"] == "create_entry" - assert result["title"] == f"prodnbr - {MAC}" + assert result["title"] == f"M1065-LW - {MAC}" assert result["data"] == { CONF_HOST: "1.2.3.4", CONF_USERNAME: "user", CONF_PASSWORD: "pass", CONF_PORT: 80, CONF_MAC: MAC, - CONF_MODEL: "prodnbr", - CONF_NAME: "prodnbr 2", + CONF_MODEL: "M1065-LW", + CONF_NAME: "M1065-LW 2", } - assert result["data"][CONF_NAME] == "prodnbr 2" + assert result["data"][CONF_NAME] == "M1065-LW 2" async def test_zeroconf_flow(hass): """Test that zeroconf discovery for new devices work.""" - with patch.object(axis.device, "get_device", return_value=Mock()): - result = await hass.config_entries.flow.async_init( - AXIS_DOMAIN, - data={ - CONF_HOST: "1.2.3.4", - CONF_PORT: 80, - "hostname": "name", - "properties": {"macaddress": MAC}, - }, - context={"source": "zeroconf"}, - ) + result = await hass.config_entries.flow.async_init( + AXIS_DOMAIN, + data={ + CONF_HOST: "1.2.3.4", + CONF_PORT: 80, + "hostname": "name", + "properties": {"macaddress": MAC}, + }, + context={"source": "zeroconf"}, + ) assert result["type"] == "form" assert result["step_id"] == "user" - with patch("axis.AxisDevice") as mock_device: - - setup_mock_axis_device(mock_device) - + with patch("axis.vapix.session_request", new=vapix_session_request): result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input={ @@ -267,18 +226,18 @@ async def test_zeroconf_flow(hass): ) assert result["type"] == "create_entry" - assert result["title"] == f"prodnbr - {MAC}" + assert result["title"] == f"M1065-LW - {MAC}" assert result["data"] == { CONF_HOST: "1.2.3.4", CONF_USERNAME: "user", CONF_PASSWORD: "pass", CONF_PORT: 80, CONF_MAC: MAC, - CONF_MODEL: "prodnbr", - CONF_NAME: "prodnbr 0", + CONF_MODEL: "M1065-LW", + CONF_NAME: "M1065-LW 0", } - assert result["data"][CONF_NAME] == "prodnbr 0" + assert result["data"][CONF_NAME] == "M1065-LW 0" async def test_zeroconf_flow_already_configured(hass): diff --git a/tests/components/axis/test_device.py b/tests/components/axis/test_device.py index a61176d84a7..c96d13659d1 100644 --- a/tests/components/axis/test_device.py +++ b/tests/components/axis/test_device.py @@ -1,9 +1,22 @@ """Test Axis device.""" from copy import deepcopy +import json from unittest import mock import axis as axislib +from axis.api_discovery import URL as API_DISCOVERY_URL +from axis.basic_device_info import URL as BASIC_DEVICE_INFO_URL from axis.event_stream import OPERATION_INITIALIZED +from axis.mqtt import URL_CLIENT as MQTT_CLIENT_URL +from axis.param_cgi import ( + BRAND as BRAND_URL, + INPUT as INPUT_URL, + IOPORT as IOPORT_URL, + OUTPUT as OUTPUT_URL, + PROPERTIES as PROPERTIES_URL, + STREAM_PROFILES as STREAM_PROFILES_URL, +) +from axis.port_management import URL as PORT_MANAGEMENT_URL import pytest from homeassistant import config_entries @@ -47,7 +60,7 @@ ENTRY_CONFIG = { CONF_NAME: NAME, } -DEFAULT_API_DISCOVERY = { +API_DISCOVERY_RESPONSE = { "method": "getApiList", "apiVersion": "1.0", "data": { @@ -58,7 +71,58 @@ DEFAULT_API_DISCOVERY = { }, } -DEFAULT_BRAND = """root.Brand.Brand=AXIS +API_DISCOVERY_BASIC_DEVICE_INFO = { + "id": "basic-device-info", + "version": "1.1", + "name": "Basic Device Information", +} +API_DISCOVERY_MQTT = {"id": "mqtt-client", "version": "1.0", "name": "MQTT Client API"} +API_DISCOVERY_PORT_MANAGEMENT = { + "id": "io-port-management", + "version": "1.0", + "name": "IO Port Management", +} + + +BASIC_DEVICE_INFO_RESPONSE = { + "apiVersion": "1.1", + "data": { + "propertyList": { + "ProdNbr": "M1065-LW", + "ProdType": "Network Camera", + "SerialNumber": "00408C12345", + "Version": "9.80.1", + } + }, +} + +MQTT_CLIENT_RESPONSE = { + "apiVersion": "1.0", + "context": "some context", + "method": "getClientStatus", + "data": {"status": {"state": "active", "connectionStatus": "Connected"}}, +} + +PORT_MANAGEMENT_RESPONSE = { + "apiVersion": "1.0", + "method": "getPorts", + "data": { + "numberOfPorts": 1, + "items": [ + { + "port": "0", + "configurable": False, + "usage": "", + "name": "PIR sensor", + "direction": "input", + "state": "open", + "normalState": "open", + } + ], + }, +} + +BRAND_RESPONSE = """root.Brand.Brand=AXIS root.Brand.ProdFullName=AXIS M1065-LW Network Camera root.Brand.ProdNbr=M1065-LW root.Brand.ProdShortName=AXIS M1065-LW @@ -67,7 +131,7 @@ root.Brand.ProdVariant= root.Brand.WebURL=http://www.axis.com """ -DEFAULT_PORTS = """root.Input.NbrOfInputs=1 +PORTS_RESPONSE = """root.Input.NbrOfInputs=1 root.IOPort.I0.Configurable=no root.IOPort.I0.Direction=input root.IOPort.I0.Input.Name=PIR sensor @@ -75,7 +139,7 @@ root.IOPort.I0.Input.Trig=closed root.Output.NbrOfOutputs=0 """ -DEFAULT_PROPERTIES = """root.Properties.API.HTTP.Version=3 +PROPERTIES_RESPONSE = """root.Properties.API.HTTP.Version=3 root.Properties.API.Metadata.Metadata=yes root.Properties.API.Metadata.Version=1.0 root.Properties.Firmware.BuildDate=Feb 15 2019 09:42 @@ -89,15 +153,27 @@ root.Properties.System.SerialNumber=00408C12345 """ -async def setup_axis_integration( - hass, - config=ENTRY_CONFIG, - options=ENTRY_OPTIONS, - api_discovery=DEFAULT_API_DISCOVERY, - brand=DEFAULT_BRAND, - ports=DEFAULT_PORTS, - properties=DEFAULT_PROPERTIES, -): +def vapix_session_request(session, url, **kwargs): + """Return data based on url.""" + if API_DISCOVERY_URL in url: + return json.dumps(API_DISCOVERY_RESPONSE) + if BASIC_DEVICE_INFO_URL in url: + return json.dumps(BASIC_DEVICE_INFO_RESPONSE) + if MQTT_CLIENT_URL in url: + return json.dumps(MQTT_CLIENT_RESPONSE) + if PORT_MANAGEMENT_URL in url: + return json.dumps(PORT_MANAGEMENT_RESPONSE) + if BRAND_URL in url: + return BRAND_RESPONSE + if IOPORT_URL in url or INPUT_URL in url or OUTPUT_URL in url: + return PORTS_RESPONSE + if PROPERTIES_URL in url: + return PROPERTIES_RESPONSE + if STREAM_PROFILES_URL in url: + return "" + + +async def setup_axis_integration(hass, config=ENTRY_CONFIG, options=ENTRY_OPTIONS): """Create the Axis device.""" config_entry = MockConfigEntry( domain=AXIS_DOMAIN, @@ -109,25 +185,7 @@ async def setup_axis_integration( ) config_entry.add_to_hass(hass) - def mock_update_api_discovery(self): - self.process_raw(api_discovery) - - def mock_update_brand(self): - self.process_raw(brand) - - def mock_update_ports(self): - self.process_raw(ports) - - def mock_update_properties(self): - self.process_raw(properties) - - with patch( - "axis.api_discovery.ApiDiscovery.update", new=mock_update_api_discovery - ), patch("axis.param_cgi.Brand.update_brand", new=mock_update_brand), patch( - "axis.param_cgi.Ports.update_ports", new=mock_update_ports - ), patch( - "axis.param_cgi.Properties.update_properties", new=mock_update_properties - ), patch( + with patch("axis.vapix.session_request", new=vapix_session_request), patch( "axis.rtsp.RTSPClient.start", return_value=True, ): await hass.config_entries.async_setup(config_entry.entry_id) @@ -144,7 +202,12 @@ async def test_device_setup(hass): ) as forward_entry_setup: device = await setup_axis_integration(hass) - entry = device.config_entry + assert device.api.vapix.firmware_version == "9.10.1" + assert device.api.vapix.product_number == "M1065-LW" + assert device.api.vapix.product_type == "Network Camera" + assert device.api.vapix.serial_number == "00408C12345" + + entry = device.config_entry assert len(forward_entry_setup.mock_calls) == 3 assert forward_entry_setup.mock_calls[0][1] == (entry, "binary_sensor") @@ -157,20 +220,29 @@ async def test_device_setup(hass): assert device.serial == ENTRY_CONFIG[CONF_MAC] +async def test_device_info(hass): + """Verify other path of device information works.""" + api_discovery = deepcopy(API_DISCOVERY_RESPONSE) + api_discovery["data"]["apiList"].append(API_DISCOVERY_BASIC_DEVICE_INFO) + + with patch.dict(API_DISCOVERY_RESPONSE, api_discovery): + device = await setup_axis_integration(hass) + + assert device.api.vapix.firmware_version == "9.80.1" + assert device.api.vapix.product_number == "M1065-LW" + assert device.api.vapix.product_type == "Network Camera" + assert device.api.vapix.serial_number == "00408C12345" + + async def test_device_support_mqtt(hass): """Successful setup.""" - api_discovery = deepcopy(DEFAULT_API_DISCOVERY) - api_discovery["data"]["apiList"].append( - {"id": "mqtt-client", "version": "1.0", "name": "MQTT Client API"} - ) - get_client_status = {"data": {"status": {"state": "active"}}} + api_discovery = deepcopy(API_DISCOVERY_RESPONSE) + api_discovery["data"]["apiList"].append(API_DISCOVERY_MQTT) mock_mqtt = await async_mock_mqtt_component(hass) - with patch( - "axis.mqtt.MqttClient.get_client_status", return_value=get_client_status - ): - await setup_axis_integration(hass, api_discovery=api_discovery) + with patch.dict(API_DISCOVERY_RESPONSE, api_discovery): + await setup_axis_integration(hass) mock_mqtt.async_subscribe.assert_called_with(f"{MAC}/#", mock.ANY, 0, "utf-8") @@ -267,7 +339,7 @@ async def test_shutdown(): async def test_get_device_fails(hass): """Device unauthorized yields authentication required error.""" with patch( - "axis.api_discovery.ApiDiscovery.update", side_effect=axislib.Unauthorized + "axis.vapix.session_request", side_effect=axislib.Unauthorized ), pytest.raises(axis.errors.AuthenticationRequired): await axis.device.get_device(hass, host="", port="", username="", password="") @@ -275,7 +347,7 @@ async def test_get_device_fails(hass): async def test_get_device_device_unavailable(hass): """Device unavailable yields cannot connect error.""" with patch( - "axis.api_discovery.ApiDiscovery.update", side_effect=axislib.RequestError + "axis.vapix.session_request", side_effect=axislib.RequestError ), pytest.raises(axis.errors.CannotConnect): await axis.device.get_device(hass, host="", port="", username="", password="") @@ -283,6 +355,6 @@ async def test_get_device_device_unavailable(hass): async def test_get_device_unknown_error(hass): """Device yield unknown error.""" with patch( - "axis.api_discovery.ApiDiscovery.update", side_effect=axislib.AxisException + "axis.vapix.session_request", side_effect=axislib.AxisException ), pytest.raises(axis.errors.AuthenticationRequired): await axis.device.get_device(hass, host="", port="", username="", password="") diff --git a/tests/components/axis/test_switch.py b/tests/components/axis/test_switch.py index 98ca5141a81..f00c17784d2 100644 --- a/tests/components/axis/test_switch.py +++ b/tests/components/axis/test_switch.py @@ -1,14 +1,19 @@ """Axis switch platform tests.""" -from axis.port_cgi import ACTION_HIGH, ACTION_LOW +from copy import deepcopy from homeassistant.components.axis.const import DOMAIN as AXIS_DOMAIN from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.setup import async_setup_component -from .test_device import NAME, setup_axis_integration +from .test_device import ( + API_DISCOVERY_PORT_MANAGEMENT, + API_DISCOVERY_RESPONSE, + NAME, + setup_axis_integration, +) -from tests.async_mock import Mock, call as mock_call +from tests.async_mock import Mock, patch EVENTS = [ { @@ -46,8 +51,8 @@ async def test_no_switches(hass): assert not hass.states.async_entity_ids(SWITCH_DOMAIN) -async def test_switches(hass): - """Test that switches are loaded properly.""" +async def test_switches_with_port_cgi(hass): + """Test that switches are loaded properly using port.cgi.""" device = await setup_axis_integration(hass) device.api.vapix.ports = {"0": Mock(), "1": Mock()} @@ -68,14 +73,13 @@ async def test_switches(hass): assert relay_1.state == "on" assert relay_1.name == f"{NAME} Relay 1" - device.api.vapix.ports["0"].action = Mock() - await hass.services.async_call( SWITCH_DOMAIN, "turn_on", {"entity_id": f"switch.{NAME}_doorbell"}, blocking=True, ) + device.api.vapix.ports["0"].close.assert_called_once() await hass.services.async_call( SWITCH_DOMAIN, @@ -83,8 +87,47 @@ async def test_switches(hass): {"entity_id": f"switch.{NAME}_doorbell"}, blocking=True, ) + device.api.vapix.ports["0"].open.assert_called_once() - assert device.api.vapix.ports["0"].action.call_args_list == [ - mock_call(ACTION_HIGH), - mock_call(ACTION_LOW), - ] + +async def test_switches_with_port_management(hass): + """Test that switches are loaded properly using port management.""" + api_discovery = deepcopy(API_DISCOVERY_RESPONSE) + api_discovery["data"]["apiList"].append(API_DISCOVERY_PORT_MANAGEMENT) + + with patch.dict(API_DISCOVERY_RESPONSE, api_discovery): + device = await setup_axis_integration(hass) + + device.api.vapix.ports = {"0": Mock(), "1": Mock()} + device.api.vapix.ports["0"].name = "Doorbell" + device.api.vapix.ports["1"].name = "" + + for event in EVENTS: + device.api.event.process_event(event) + await hass.async_block_till_done() + + assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 2 + + relay_0 = hass.states.get(f"switch.{NAME}_doorbell") + assert relay_0.state == "off" + assert relay_0.name == f"{NAME} Doorbell" + + relay_1 = hass.states.get(f"switch.{NAME}_relay_1") + assert relay_1.state == "on" + assert relay_1.name == f"{NAME} Relay 1" + + await hass.services.async_call( + SWITCH_DOMAIN, + "turn_on", + {"entity_id": f"switch.{NAME}_doorbell"}, + blocking=True, + ) + device.api.vapix.ports["0"].close.assert_called_once() + + await hass.services.async_call( + SWITCH_DOMAIN, + "turn_off", + {"entity_id": f"switch.{NAME}_doorbell"}, + blocking=True, + ) + device.api.vapix.ports["0"].open.assert_called_once() From d488c779fc536a666babf7e32ccac497b45b9455 Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Mon, 1 Jun 2020 00:03:28 +0000 Subject: [PATCH 273/406] [ci skip] Translation update --- .../components/atag/translations/ko.json | 3 +- .../binary_sensor/translations/ca.json | 4 +- .../components/braviatv/translations/ko.json | 3 +- .../components/climate/translations/ca.json | 8 ++-- .../configurator/translations/ca.json | 2 +- .../components/fan/translations/ca.json | 2 +- .../components/group/translations/ca.json | 2 +- .../components/guardian/translations/ko.json | 22 +++++++++++ .../components/light/translations/ca.json | 2 +- .../components/lock/translations/ca.json | 2 +- .../components/nut/translations/ca.json | 2 +- .../components/openuv/translations/ko.json | 3 ++ .../components/plant/translations/ca.json | 2 +- .../components/plugwise/translations/ko.json | 22 +++++++++++ .../components/remote/translations/ca.json | 2 +- .../components/scene/translations/ca.json | 2 +- .../components/script/translations/ca.json | 2 +- .../components/sensor/translations/ca.json | 2 +- .../smartthings/translations/ca.json | 2 +- .../components/sonarr/translations/es.json | 37 +++++++++++++++++++ .../components/sonarr/translations/ko.json | 37 +++++++++++++++++++ .../components/sonarr/translations/ru.json | 37 +++++++++++++++++++ .../components/switch/translations/ca.json | 2 +- .../components/vizio/translations/ko.json | 2 + 24 files changed, 183 insertions(+), 21 deletions(-) create mode 100644 homeassistant/components/guardian/translations/ko.json create mode 100644 homeassistant/components/plugwise/translations/ko.json create mode 100644 homeassistant/components/sonarr/translations/es.json create mode 100644 homeassistant/components/sonarr/translations/ko.json create mode 100644 homeassistant/components/sonarr/translations/ru.json diff --git a/homeassistant/components/atag/translations/ko.json b/homeassistant/components/atag/translations/ko.json index 97558185815..3ca689a488c 100644 --- a/homeassistant/components/atag/translations/ko.json +++ b/homeassistant/components/atag/translations/ko.json @@ -4,7 +4,8 @@ "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." + "connection_error": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", + "unauthorized": "\ud398\uc5b4\ub9c1\uc774 \uac70\ubd80\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \uc778\uc99d \uc694\uccad \uae30\uae30\ub97c \ud655\uc778\ud574\uc8fc\uc138\uc694" }, "step": { "user": { diff --git a/homeassistant/components/binary_sensor/translations/ca.json b/homeassistant/components/binary_sensor/translations/ca.json index 047420b051e..d145344ed37 100644 --- a/homeassistant/components/binary_sensor/translations/ca.json +++ b/homeassistant/components/binary_sensor/translations/ca.json @@ -140,14 +140,14 @@ }, "opening": { "off": "Tancat/da", - "on": "Obert" + "on": "Obert/a" }, "presence": { "off": "Lliure", "on": "Detectat" }, "problem": { - "off": "Correcte", + "off": "OK", "on": "Problema" }, "safety": { diff --git a/homeassistant/components/braviatv/translations/ko.json b/homeassistant/components/braviatv/translations/ko.json index 593fd997709..e8c433ccbfc 100644 --- a/homeassistant/components/braviatv/translations/ko.json +++ b/homeassistant/components/braviatv/translations/ko.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "\uc774 TV \ub294 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + "already_configured": "\uc774 TV \ub294 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", + "no_ip_control": "TV \uc5d0\uc11c IP \uc81c\uc5b4\uac00 \ube44\ud65c\uc131\ud654\ub418\uc5c8\uac70\ub098 TV \uac00 \uc9c0\uc6d0\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4." }, "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.", diff --git a/homeassistant/components/climate/translations/ca.json b/homeassistant/components/climate/translations/ca.json index 5cc7233864e..3eb99744751 100644 --- a/homeassistant/components/climate/translations/ca.json +++ b/homeassistant/components/climate/translations/ca.json @@ -17,11 +17,11 @@ "state": { "_": { "auto": "Autom\u00e0tic", - "cool": "Refredar", - "dry": "Assecar", + "cool": "Refreda", + "dry": "Asseca", "fan_only": "Nom\u00e9s ventilador", - "heat": "Escalfar", - "heat_cool": "Escalfar/Refredar", + "heat": "Escalfa", + "heat_cool": "Escalfa/Refreda", "off": "OFF" } }, diff --git a/homeassistant/components/configurator/translations/ca.json b/homeassistant/components/configurator/translations/ca.json index 0a4ea1ab6fa..4c3ffe3b5c5 100644 --- a/homeassistant/components/configurator/translations/ca.json +++ b/homeassistant/components/configurator/translations/ca.json @@ -1,7 +1,7 @@ { "state": { "_": { - "configure": "Configurar", + "configure": "Configura", "configured": "Configurat" } }, diff --git a/homeassistant/components/fan/translations/ca.json b/homeassistant/components/fan/translations/ca.json index c7d668ebc0b..7c1789aeb24 100644 --- a/homeassistant/components/fan/translations/ca.json +++ b/homeassistant/components/fan/translations/ca.json @@ -19,5 +19,5 @@ "on": "ON" } }, - "title": "Ventiladors" + "title": "Ventilador" } \ No newline at end of file diff --git a/homeassistant/components/group/translations/ca.json b/homeassistant/components/group/translations/ca.json index ec1116f8759..21b6361589c 100644 --- a/homeassistant/components/group/translations/ca.json +++ b/homeassistant/components/group/translations/ca.json @@ -6,7 +6,7 @@ "locked": "Bloquejat", "not_home": "Fora", "off": "OFF", - "ok": "Correcte", + "ok": "OK", "on": "ON", "open": "Obert/a", "problem": "Problema", diff --git a/homeassistant/components/guardian/translations/ko.json b/homeassistant/components/guardian/translations/ko.json new file mode 100644 index 00000000000..4580fba76d1 --- /dev/null +++ b/homeassistant/components/guardian/translations/ko.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "\uc774 Guardian \uae30\uae30\ub294 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", + "already_in_progress": "Guardian \uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4.", + "connection_error": "Guardian \uae30\uae30\uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4." + }, + "step": { + "user": { + "data": { + "ip_address": "IP \uc8fc\uc18c", + "port": "\ud3ec\ud2b8" + }, + "description": "\ub85c\uceec Elexa Guardian \uae30\uae30\ub97c \uad6c\uc131\ud574\uc8fc\uc138\uc694." + }, + "zeroconf_confirm": { + "description": "\uc774 Guardian \uae30\uae30\ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" + } + } + }, + "title": "Elexa Guardian" +} \ No newline at end of file diff --git a/homeassistant/components/light/translations/ca.json b/homeassistant/components/light/translations/ca.json index 9200bbf73db..1e91f5005ce 100644 --- a/homeassistant/components/light/translations/ca.json +++ b/homeassistant/components/light/translations/ca.json @@ -23,5 +23,5 @@ "on": "ON" } }, - "title": "Llums" + "title": "Llum" } \ No newline at end of file diff --git a/homeassistant/components/lock/translations/ca.json b/homeassistant/components/lock/translations/ca.json index a59841298fa..bd8beb73f85 100644 --- a/homeassistant/components/lock/translations/ca.json +++ b/homeassistant/components/lock/translations/ca.json @@ -20,5 +20,5 @@ "unlocked": "Desbloquejat" } }, - "title": "Panys" + "title": "Pany" } \ No newline at end of file diff --git a/homeassistant/components/nut/translations/ca.json b/homeassistant/components/nut/translations/ca.json index d00f3d08185..13c1bde3173 100644 --- a/homeassistant/components/nut/translations/ca.json +++ b/homeassistant/components/nut/translations/ca.json @@ -39,7 +39,7 @@ "resources": "Recursos", "scan_interval": "Interval d'escaneig (segons)" }, - "description": "Selecciona els recursos del sensor" + "description": "Selecciona els recursos del sensor." } } } diff --git a/homeassistant/components/openuv/translations/ko.json b/homeassistant/components/openuv/translations/ko.json index 46c0e2f0526..9d252f9a945 100644 --- a/homeassistant/components/openuv/translations/ko.json +++ b/homeassistant/components/openuv/translations/ko.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "\uc88c\ud45c\uac12\uc774 \uc774\ubbf8 \ub4f1\ub85d\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, "error": { "identifier_exists": "\uc88c\ud45c\uac12\uc774 \uc774\ubbf8 \ub4f1\ub85d\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "invalid_api_key": "\uc798\ubabb\ub41c API \ud0a4" diff --git a/homeassistant/components/plant/translations/ca.json b/homeassistant/components/plant/translations/ca.json index b338a20ae41..942297a6157 100644 --- a/homeassistant/components/plant/translations/ca.json +++ b/homeassistant/components/plant/translations/ca.json @@ -1,7 +1,7 @@ { "state": { "_": { - "ok": "Correcte", + "ok": "OK", "problem": "Problema" } }, diff --git a/homeassistant/components/plugwise/translations/ko.json b/homeassistant/components/plugwise/translations/ko.json new file mode 100644 index 00000000000..fdc189ab38f --- /dev/null +++ b/homeassistant/components/plugwise/translations/ko.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "\uc774 Smile \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. 8\uc790\uc758 Smile ID \ub97c \ud655\uc778\ud574\uc8fc\uc138\uc694", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, + "step": { + "user": { + "data": { + "host": "Smile IP \uc8fc\uc18c", + "password": "Smile ID" + }, + "description": "\uc138\ubd80 \uc815\ubcf4", + "title": "Smile \uc5d0 \uc5f0\uacb0\ud558\uae30" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/remote/translations/ca.json b/homeassistant/components/remote/translations/ca.json index ccf6b2c2640..94ff71f6d92 100644 --- a/homeassistant/components/remote/translations/ca.json +++ b/homeassistant/components/remote/translations/ca.json @@ -5,5 +5,5 @@ "on": "ON" } }, - "title": "Comandaments" + "title": "Comandament" } \ No newline at end of file diff --git a/homeassistant/components/scene/translations/ca.json b/homeassistant/components/scene/translations/ca.json index 744d02a54b2..92aa6caa099 100644 --- a/homeassistant/components/scene/translations/ca.json +++ b/homeassistant/components/scene/translations/ca.json @@ -1,3 +1,3 @@ { - "title": "Escenes" + "title": "Escena" } \ No newline at end of file diff --git a/homeassistant/components/script/translations/ca.json b/homeassistant/components/script/translations/ca.json index e736926da5e..4369856606f 100644 --- a/homeassistant/components/script/translations/ca.json +++ b/homeassistant/components/script/translations/ca.json @@ -5,5 +5,5 @@ "on": "ON" } }, - "title": "Programes (scripts)" + "title": "Programa (script)" } \ No newline at end of file diff --git a/homeassistant/components/sensor/translations/ca.json b/homeassistant/components/sensor/translations/ca.json index 7f8f7e56d6d..d80fd795698 100644 --- a/homeassistant/components/sensor/translations/ca.json +++ b/homeassistant/components/sensor/translations/ca.json @@ -29,5 +29,5 @@ "on": "ON" } }, - "title": "Sensors" + "title": "Sensor" } \ No newline at end of file diff --git a/homeassistant/components/smartthings/translations/ca.json b/homeassistant/components/smartthings/translations/ca.json index 0baa8147efe..7765eec4c13 100644 --- a/homeassistant/components/smartthings/translations/ca.json +++ b/homeassistant/components/smartthings/translations/ca.json @@ -9,7 +9,7 @@ "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." + "webhook_error": "SmartThings no ha pogut validar l'URL webhook. Comprova que l'URL pot ser accedit des d'Internet i torna-ho a provar." }, "step": { "authorize": { diff --git a/homeassistant/components/sonarr/translations/es.json b/homeassistant/components/sonarr/translations/es.json new file mode 100644 index 00000000000..29db7cfbd77 --- /dev/null +++ b/homeassistant/components/sonarr/translations/es.json @@ -0,0 +1,37 @@ +{ + "config": { + "abort": { + "already_configured": "El servicio ya est\u00e1 configurado", + "unknown": "Error inesperado" + }, + "error": { + "cannot_connect": "No se pudo conectar", + "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida" + }, + "flow_title": "Sonarr: {name}", + "step": { + "user": { + "data": { + "api_key": "Clave API", + "base_path": "Ruta a la API", + "host": "Host", + "port": "Puerto", + "ssl": "Sonarr usa un certificado SSL", + "verify_ssl": "Sonarr usa un certificado adecuado" + }, + "title": "Conectar a Sonarr" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "upcoming_days": "N\u00famero de d\u00edas pr\u00f3ximos a mostrar", + "wanted_max_items": "N\u00famero m\u00e1ximo de elementos a mostrar" + } + } + } + }, + "title": "Sonarr" +} \ No newline at end of file diff --git a/homeassistant/components/sonarr/translations/ko.json b/homeassistant/components/sonarr/translations/ko.json new file mode 100644 index 00000000000..d08f188303a --- /dev/null +++ b/homeassistant/components/sonarr/translations/ko.json @@ -0,0 +1,37 @@ +{ + "config": { + "abort": { + "already_configured": "\uc11c\ube44\uc2a4\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" + }, + "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", + "invalid_auth": "\uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "flow_title": "Sonarr: {name}", + "step": { + "user": { + "data": { + "api_key": "API \ud0a4", + "base_path": "API \uacbd\ub85c", + "host": "\ud638\uc2a4\ud2b8", + "port": "\ud3ec\ud2b8", + "ssl": "Sonarr \ub294 SSL \uc778\uc99d\uc11c\ub97c \uc0ac\uc6a9\ud558\uace0 \uc788\uc2b5\ub2c8\ub2e4", + "verify_ssl": "Sonarr \ub294 \uc62c\ubc14\ub978 \uc778\uc99d\uc11c\ub97c \uc0ac\uc6a9\ud558\uace0 \uc788\uc2b5\ub2c8\ub2e4" + }, + "title": "Sonarr \uc5d0 \uc5f0\uacb0\ud558\uae30" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "upcoming_days": "\ud45c\uc2dc\ud560 \uc608\uc815\uc77c \uc218", + "wanted_max_items": "\ud45c\uc2dc\ud560 Wanted \ud56d\ubaa9 \uc218" + } + } + } + }, + "title": "Sonarr" +} \ No newline at end of file diff --git a/homeassistant/components/sonarr/translations/ru.json b/homeassistant/components/sonarr/translations/ru.json new file mode 100644 index 00000000000..d7ffd002981 --- /dev/null +++ b/homeassistant/components/sonarr/translations/ru.json @@ -0,0 +1,37 @@ +{ + "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.", + "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." + }, + "error": { + "cannot_connect": "\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f.", + "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f." + }, + "flow_title": "Sonarr: {name}", + "step": { + "user": { + "data": { + "api_key": "\u041a\u043b\u044e\u0447 API", + "base_path": "\u041f\u0443\u0442\u044c \u043a API", + "host": "\u0425\u043e\u0441\u0442", + "port": "\u041f\u043e\u0440\u0442", + "ssl": "Sonarr \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 SSL", + "verify_ssl": "Sonarr \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" + }, + "title": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a Sonarr" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "upcoming_days": "\u041a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043f\u0440\u0435\u0434\u0441\u0442\u043e\u044f\u0449\u0438\u0445 \u0434\u043d\u0435\u0439 \u0434\u043b\u044f \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f", + "wanted_max_items": "\u041c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u043e\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432 \u0434\u043b\u044f \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f" + } + } + } + }, + "title": "Sonarr" +} \ No newline at end of file diff --git a/homeassistant/components/switch/translations/ca.json b/homeassistant/components/switch/translations/ca.json index 0904d4d08dd..e39386a680f 100644 --- a/homeassistant/components/switch/translations/ca.json +++ b/homeassistant/components/switch/translations/ca.json @@ -20,5 +20,5 @@ "on": "ON" } }, - "title": "Interruptors" + "title": "Interruptor" } \ No newline at end of file diff --git a/homeassistant/components/vizio/translations/ko.json b/homeassistant/components/vizio/translations/ko.json index ebe6da266dc..8b84c96b102 100644 --- a/homeassistant/components/vizio/translations/ko.json +++ b/homeassistant/components/vizio/translations/ko.json @@ -1,9 +1,11 @@ { "config": { "abort": { + "already_configured_device": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4", "updated_entry": "\uc774 \ud56d\ubaa9\uc740 \uc774\ubbf8 \uc124\uc815\ub418\uc5c8\uc9c0\ub9cc \uad6c\uc131\uc5d0 \uc815\uc758\ub41c \uc774\ub984, \uc571 \ud639\uc740 \uc635\uc158\uc774 \uc774\uc804\uc5d0 \uac00\uc838\uc628 \uad6c\uc131 \ub0b4\uc6a9\uacfc \uc77c\uce58\ud558\uc9c0 \uc54a\uc73c\ubbc0\ub85c \uad6c\uc131 \ud56d\ubaa9\uc774 \uadf8\uc5d0 \ub530\ub77c \uc5c5\ub370\uc774\ud2b8\ub418\uc5c8\uc2b5\ub2c8\ub2e4." }, "error": { + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "complete_pairing_failed": "\ud398\uc5b4\ub9c1\uc744 \uc644\ub8cc\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc81c\ucd9c\ud558\uae30 \uc804\uc5d0 \uc785\ub825\ud55c PIN \uc774 \uc62c\ubc14\ub978\uc9c0, TV \uc758 \uc804\uc6d0\uc774 \ucf1c\uc838 \uc788\uace0 \ub124\ud2b8\uc6cc\ud06c\uc5d0 \uc5f0\uacb0\ub418\uc5b4 \uc788\ub294\uc9c0 \ud655\uc778\ud574\uc8fc\uc138\uc694.", "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." From 276f3afb00d05fe83dd045a15f9596d2f505c8b8 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 31 May 2020 22:18:30 -0700 Subject: [PATCH 274/406] Do async_setup_platform in background (#36244) Co-authored-by: J. Nick Koston --- homeassistant/components/camera/__init__.py | 6 +- .../device_sun_light_trigger/__init__.py | 39 ++- .../components/websocket_api/sensor.py | 3 +- .../components/xiaomi_miio/vacuum.py | 2 +- homeassistant/helpers/entity_component.py | 8 +- homeassistant/helpers/entity_platform.py | 11 +- .../air_quality/test_air_quality.py | 2 + .../components/airvisual/test_config_flow.py | 5 +- tests/components/alexa/test_smart_home.py | 14 +- .../components/androidtv/test_media_player.py | 19 ++ .../components/bayesian/test_binary_sensor.py | 9 + .../binary_sensor/test_device_condition.py | 2 + .../binary_sensor/test_device_trigger.py | 2 + tests/components/bom/test_sensor.py | 2 + tests/components/buienradar/test_sensor.py | 1 + tests/components/buienradar/test_weather.py | 1 + tests/components/calendar/test_init.py | 2 + tests/components/camera/test_init.py | 13 +- tests/components/coinmarketcap/test_sensor.py | 1 + tests/components/command_line/test_cover.py | 1 + tests/components/command_line/test_switch.py | 5 +- .../components/cover/test_device_condition.py | 2 + tests/components/cover/test_device_trigger.py | 2 + tests/components/darksky/test_sensor.py | 6 + tests/components/darksky/test_weather.py | 2 + tests/components/demo/test_camera.py | 9 +- tests/components/demo/test_climate.py | 1 + tests/components/demo/test_cover.py | 1 + tests/components/demo/test_fan.py | 7 +- tests/components/demo/test_light.py | 7 +- tests/components/demo/test_lock.py | 7 +- tests/components/demo/test_media_player.py | 10 + tests/components/demo/test_remote.py | 1 + tests/components/demo/test_stt.py | 7 +- tests/components/demo/test_vacuum.py | 1 + tests/components/demo/test_water_heater.py | 9 +- .../components/device_automation/test_init.py | 2 +- .../device_sun_light_trigger/test_init.py | 18 + tests/components/dsmr/test_sensor.py | 6 + .../dte_energy_bridge/test_sensor.py | 3 + tests/components/efergy/test_sensor.py | 2 + .../facebox/test_image_processing.py | 6 + tests/components/fido/test_sensor.py | 1 + tests/components/filesize/test_sensor.py | 1 + tests/components/filter/test_sensor.py | 4 + tests/components/flux/test_switch.py | 21 +- tests/components/folder/test_sensor.py | 1 + tests/components/foobot/test_sensor.py | 1 + tests/components/gdacs/test_geo_location.py | 2 + .../generic_thermostat/test_climate.py | 318 +++++++++--------- .../geo_json_events/test_geo_location.py | 3 + .../geonetnz_quakes/test_geo_location.py | 2 + .../google_assistant/test_smart_home.py | 1 + tests/components/group/test_cover.py | 2 + tests/components/hddtemp/test_sensor.py | 7 + .../here_travel_time/test_sensor.py | 36 +- tests/components/history_stats/test_sensor.py | 1 + tests/components/homeassistant/test_scene.py | 7 + tests/components/homekit/test_type_cameras.py | 8 + .../ign_sismologia/test_geo_location.py | 2 + .../components/image_processing/test_init.py | 3 + tests/components/ipma/test_weather.py | 6 +- tests/components/lastfm/test_sensor.py | 2 + tests/components/light/test_device_action.py | 1 + .../components/light/test_device_condition.py | 2 + tests/components/light/test_device_trigger.py | 2 + tests/components/light/test_init.py | 32 +- tests/components/local_file/test_camera.py | 4 + tests/components/london_air/test_sensor.py | 1 + .../manual/test_alarm_control_panel.py | 43 +++ .../manual_mqtt/test_alarm_control_panel.py | 40 +++ tests/components/media_player/test_init.py | 2 + .../meteo_france/test_config_flow.py | 11 + tests/components/mfi/test_sensor.py | 4 + tests/components/mfi/test_switch.py | 1 + .../test_image_processing.py | 3 + .../test_image_processing.py | 3 + .../mobile_app/test_device_tracker.py | 1 + tests/components/modbus/conftest.py | 1 + .../components/mold_indicator/test_sensor.py | 7 +- tests/components/moon/test_sensor.py | 2 + .../mqtt/test_alarm_control_panel.py | 17 + tests/components/mqtt/test_binary_sensor.py | 10 + tests/components/mqtt/test_camera.py | 1 + tests/components/mqtt/test_climate.py | 35 ++ tests/components/mqtt/test_common.py | 11 +- tests/components/mqtt/test_cover.py | 34 ++ tests/components/mqtt/test_fan.py | 9 + tests/components/mqtt/test_legacy_vacuum.py | 21 ++ tests/components/mqtt/test_light.py | 25 ++ tests/components/mqtt/test_light_json.py | 15 + tests/components/mqtt/test_light_template.py | 13 + tests/components/mqtt/test_lock.py | 6 + tests/components/mqtt/test_sensor.py | 6 + tests/components/mqtt/test_state_vacuum.py | 6 + tests/components/mqtt/test_switch.py | 4 + tests/components/mqtt_room/test_sensor.py | 1 + tests/components/nextbus/test_sensor.py | 1 + .../nsw_fuel_station/test_sensor.py | 2 + .../test_geo_location.py | 2 + .../openalpr_cloud/test_image_processing.py | 3 + .../openalpr_local/test_image_processing.py | 4 + .../openhardwaremonitor/test_sensor.py | 1 + tests/components/openuv/test_config_flow.py | 12 + tests/components/pilight/test_sensor.py | 3 + tests/components/prometheus/test_init.py | 1 + .../qld_bushfire/test_geo_location.py | 2 + tests/components/random/test_binary_sensor.py | 2 + tests/components/random/test_sensor.py | 1 + tests/components/reddit/test_sensor.py | 1 + tests/components/rest/test_binary_sensor.py | 3 + tests/components/rest/test_sensor.py | 6 + tests/components/rmvtransport/test_sensor.py | 5 + tests/components/scene/test_init.py | 3 + tests/components/season/test_sensor.py | 4 + .../sensor/test_device_condition.py | 7 + .../components/sensor/test_device_trigger.py | 8 + .../components/seventeentrack/test_sensor.py | 4 +- tests/components/sigfox/test_sensor.py | 2 + .../sighthound/test_image_processing.py | 6 + tests/components/sma/test_sensor.py | 1 + tests/components/smhi/test_weather.py | 1 + tests/components/sql/test_sensor.py | 2 + tests/components/startca/test_sensor.py | 2 + tests/components/statistics/test_sensor.py | 9 + tests/components/switch/test_device_action.py | 1 + .../switch/test_device_condition.py | 2 + .../components/switch/test_device_trigger.py | 2 + tests/components/switch/test_init.py | 1 + tests/components/teksavvy/test_sensor.py | 2 + .../template/test_alarm_control_panel.py | 12 + .../components/template/test_binary_sensor.py | 11 +- tests/components/template/test_cover.py | 23 ++ tests/components/template/test_fan.py | 11 + tests/components/template/test_light.py | 25 ++ tests/components/template/test_lock.py | 12 + tests/components/template/test_sensor.py | 16 + tests/components/template/test_switch.py | 16 + tests/components/template/test_vacuum.py | 11 + tests/components/tod/test_binary_sensor.py | 19 +- tests/components/tradfri/test_light.py | 1 + tests/components/transport_nsw/test_sensor.py | 1 + tests/components/trend/test_binary_sensor.py | 11 + tests/components/twitch/test_twitch.py | 18 +- tests/components/uk_transport/test_sensor.py | 2 + .../test_geo_location.py | 2 + tests/components/wake_on_lan/test_switch.py | 5 + tests/components/weather/test_weather.py | 1 + tests/components/websocket_api/conftest.py | 2 + tests/components/websocket_api/test_auth.py | 65 ++-- tests/components/websocket_api/test_sensor.py | 29 +- .../components/workday/test_binary_sensor.py | 6 + tests/components/worldclock/test_sensor.py | 1 + tests/components/wunderground/test_sensor.py | 3 + tests/components/yamaha/test_media_player.py | 1 + .../test_yandex_transport_sensor.py | 1 + tests/components/yr/test_sensor.py | 3 + tests/helpers/test_entity_component.py | 4 +- tests/helpers/test_entity_platform.py | 6 + tests/helpers/test_translation.py | 1 + tests/test_setup.py | 4 + 161 files changed, 1184 insertions(+), 305 deletions(-) diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py index 7681ee67c29..fb33bef7d52 100644 --- a/homeassistant/components/camera/__init__.py +++ b/homeassistant/components/camera/__init__.py @@ -34,6 +34,7 @@ from homeassistant.components.stream.const import ( from homeassistant.const import ( ATTR_ENTITY_ID, CONF_FILENAME, + EVENT_HOMEASSISTANT_START, SERVICE_TURN_OFF, SERVICE_TURN_ON, ) @@ -48,7 +49,6 @@ from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.network import get_url from homeassistant.loader import bind_hass -from homeassistant.setup import async_when_setup from .const import DATA_CAMERA_PREFS, DOMAIN from .prefs import CameraPreferences @@ -259,7 +259,7 @@ async def async_setup(hass, config): await component.async_setup(config) - async def preload_stream(hass, _): + async def preload_stream(_): for camera in component.entities: camera_prefs = prefs.get(camera.entity_id) if not camera_prefs.preload_stream: @@ -273,7 +273,7 @@ async def async_setup(hass, config): request_stream(hass, source, keepalive=True, options=camera.stream_options) - async_when_setup(hass, DOMAIN_STREAM, preload_stream) + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, preload_stream) @callback def update_tokens(time): diff --git a/homeassistant/components/device_sun_light_trigger/__init__.py b/homeassistant/components/device_sun_light_trigger/__init__.py index fcf61dfe097..b1fc37e3ae3 100644 --- a/homeassistant/components/device_sun_light_trigger/__init__.py +++ b/homeassistant/components/device_sun_light_trigger/__init__.py @@ -11,6 +11,7 @@ from homeassistant.components.light import ( ) from homeassistant.const import ( ATTR_ENTITY_ID, + EVENT_HOMEASSISTANT_START, SERVICE_TURN_OFF, SERVICE_TURN_ON, STATE_HOME, @@ -59,17 +60,35 @@ CONFIG_SCHEMA = vol.Schema( async def async_setup(hass, config): """Set up the triggers to control lights based on device presence.""" + conf = config[DOMAIN] + disable_turn_off = conf[CONF_DISABLE_TURN_OFF] + light_group = conf.get(CONF_LIGHT_GROUP) + light_profile = conf[CONF_LIGHT_PROFILE] + device_group = conf.get(CONF_DEVICE_GROUP) + + async def activate_on_start(_): + """Activate automation.""" + await activate_automation( + hass, device_group, light_group, light_profile, disable_turn_off + ) + + if hass.is_running: + await activate_on_start(None) + else: + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, activate_on_start) + + return True + + +async def activate_automation( + hass, device_group, light_group, light_profile, disable_turn_off +): + """Activate the automation.""" logger = logging.getLogger(__name__) device_tracker = hass.components.device_tracker group = hass.components.group light = hass.components.light person = hass.components.person - conf = config[DOMAIN] - disable_turn_off = conf[CONF_DISABLE_TURN_OFF] - light_group = conf.get(CONF_LIGHT_GROUP) - light_profile = conf[CONF_LIGHT_PROFILE] - - device_group = conf.get(CONF_DEVICE_GROUP) if device_group is None: device_entity_ids = hass.states.async_entity_ids(device_tracker.DOMAIN) @@ -79,7 +98,7 @@ async def async_setup(hass, config): if not device_entity_ids: logger.error("No devices found to track") - return False + return # Get the light IDs from the specified group if light_group is None: @@ -89,7 +108,7 @@ async def async_setup(hass, config): if not light_ids: logger.error("No lights found to turn on") - return False + return @callback def anyone_home(): @@ -219,7 +238,7 @@ async def async_setup(hass, config): ) if disable_turn_off: - return True + return @callback def turn_off_lights_when_all_leave(entity, old_state, new_state): @@ -247,4 +266,4 @@ async def async_setup(hass, config): STATE_NOT_HOME, ) - return True + return diff --git a/homeassistant/components/websocket_api/sensor.py b/homeassistant/components/websocket_api/sensor.py index 6be07dfb1f4..c026978634f 100644 --- a/homeassistant/components/websocket_api/sensor.py +++ b/homeassistant/components/websocket_api/sensor.py @@ -24,7 +24,7 @@ class APICount(Entity): def __init__(self): """Initialize the API count.""" - self.count = None + self.count = 0 async def async_added_to_hass(self): """Added to hass.""" @@ -38,7 +38,6 @@ class APICount(Entity): SIGNAL_WEBSOCKET_DISCONNECTED, self._update_count ) ) - self._update_count() @property def name(self): diff --git a/homeassistant/components/xiaomi_miio/vacuum.py b/homeassistant/components/xiaomi_miio/vacuum.py index fd144e1edc7..f37c22a38aa 100644 --- a/homeassistant/components/xiaomi_miio/vacuum.py +++ b/homeassistant/components/xiaomi_miio/vacuum.py @@ -291,7 +291,7 @@ class MiroboVacuum(StateVacuumEntity): @property def fan_speed_list(self): """Get the list of available fan speed steps of the vacuum cleaner.""" - return list(self._fan_speeds) + return list(self._fan_speeds) if self._fan_speeds else [] @property def device_state_attributes(self): diff --git a/homeassistant/helpers/entity_component.py b/homeassistant/helpers/entity_component.py index fb7762fb9ae..e651d2e8cc7 100644 --- a/homeassistant/helpers/entity_component.py +++ b/homeassistant/helpers/entity_component.py @@ -123,15 +123,11 @@ class EntityComponent: self.config = config # Look in config for Domain, Domain 2, Domain 3 etc and load them - tasks = [] for p_type, p_config in config_per_platform(config, self.domain): - tasks.append(self.async_setup_platform(p_type, p_config)) - - if tasks: - await asyncio.gather(*tasks) + self.hass.async_create_task(self.async_setup_platform(p_type, p_config)) # Generic discovery listener for loading platform dynamically - # Refer to: homeassistant.components.discovery.load_platform() + # Refer to: homeassistant.helpers.discovery.async_load_platform() async def component_platform_discovered( platform: str, info: Optional[Dict[str, Any]] ) -> None: diff --git a/homeassistant/helpers/entity_platform.py b/homeassistant/helpers/entity_platform.py index 30b07c98252..5eb5b213732 100644 --- a/homeassistant/helpers/entity_platform.py +++ b/homeassistant/helpers/entity_platform.py @@ -4,7 +4,7 @@ from contextvars import ContextVar from datetime import datetime, timedelta from logging import Logger from types import ModuleType -from typing import TYPE_CHECKING, Dict, Iterable, List, Optional, cast +from typing import TYPE_CHECKING, Dict, Iterable, List, Optional from homeassistant.const import DEVICE_DEFAULT_NAME from homeassistant.core import CALLBACK_TYPE, callback, split_entity_id, valid_entity_id @@ -240,12 +240,9 @@ class EntityPlatform: ) -> None: """Schedule adding entities for a single platform async.""" self._tasks.append( - cast( - asyncio.Future, - self.hass.async_add_job( - self.async_add_entities( # type: ignore - new_entities, update_before_add=update_before_add - ), + self.hass.async_create_task( + self.async_add_entities( + new_entities, update_before_add=update_before_add ), ) ) diff --git a/tests/components/air_quality/test_air_quality.py b/tests/components/air_quality/test_air_quality.py index f457ebb3d2f..e0692931e1c 100644 --- a/tests/components/air_quality/test_air_quality.py +++ b/tests/components/air_quality/test_air_quality.py @@ -17,6 +17,7 @@ async def test_state(hass): config = {"air_quality": {"platform": "demo"}} assert await async_setup_component(hass, "air_quality", config) + await hass.async_block_till_done() state = hass.states.get("air_quality.demo_air_quality_home") assert state is not None @@ -29,6 +30,7 @@ async def test_attributes(hass): config = {"air_quality": {"platform": "demo"}} assert await async_setup_component(hass, "air_quality", config) + await hass.async_block_till_done() state = hass.states.get("air_quality.demo_air_quality_office") assert state is not None diff --git a/tests/components/airvisual/test_config_flow.py b/tests/components/airvisual/test_config_flow.py index fcb2360b6c6..6741731e0e5 100644 --- a/tests/components/airvisual/test_config_flow.py +++ b/tests/components/airvisual/test_config_flow.py @@ -111,8 +111,11 @@ async def test_migration(hass): assert len(hass.config_entries.async_entries(DOMAIN)) == 1 - with patch("pyairvisual.api.API.nearest_city"): + with patch("pyairvisual.api.API.nearest_city"), patch.object( + hass.config_entries, "async_forward_entry_setup" + ): assert await async_setup_component(hass, DOMAIN, {DOMAIN: conf}) + await hass.async_block_till_done() config_entries = hass.config_entries.async_entries(DOMAIN) diff --git a/tests/components/alexa/test_smart_home.py b/tests/components/alexa/test_smart_home.py index a72099da600..f2777ab00f8 100644 --- a/tests/components/alexa/test_smart_home.py +++ b/tests/components/alexa/test_smart_home.py @@ -55,19 +55,19 @@ def events(hass): @pytest.fixture -def mock_camera(hass): +async def mock_camera(hass): """Initialize a demo camera platform.""" - assert hass.loop.run_until_complete( - async_setup_component(hass, "camera", {camera.DOMAIN: {"platform": "demo"}}) + assert await async_setup_component( + hass, "camera", {camera.DOMAIN: {"platform": "demo"}} ) + await hass.async_block_till_done() @pytest.fixture -def mock_stream(hass): +async def mock_stream(hass): """Initialize a demo camera platform with streaming.""" - assert hass.loop.run_until_complete( - async_setup_component(hass, "stream", {"stream": {}}) - ) + assert await async_setup_component(hass, "stream", {"stream": {}}) + await hass.async_block_till_done() def test_create_api_message_defaults(hass): diff --git a/tests/components/androidtv/test_media_player.py b/tests/components/androidtv/test_media_player.py index fa4f6ffbed6..85e4a75acd0 100644 --- a/tests/components/androidtv/test_media_player.py +++ b/tests/components/androidtv/test_media_player.py @@ -116,6 +116,7 @@ async def _test_reconnect(hass, caplog, config): patch_key ], patchers.PATCH_KEYGEN, patchers.PATCH_ANDROIDTV_OPEN, patchers.PATCH_SIGNER: assert await async_setup_component(hass, DOMAIN, config) + await hass.async_block_till_done() await hass.helpers.entity_component.async_update_entity(entity_id) state = hass.states.get(entity_id) @@ -187,6 +188,7 @@ async def _test_adb_shell_returns_none(hass, config): patch_key ], patchers.PATCH_KEYGEN, patchers.PATCH_ANDROIDTV_OPEN, patchers.PATCH_SIGNER: assert await async_setup_component(hass, DOMAIN, config) + await hass.async_block_till_done() await hass.helpers.entity_component.async_update_entity(entity_id) state = hass.states.get(entity_id) assert state is not None @@ -295,6 +297,7 @@ async def test_setup_with_adbkey(hass): patch_key ], patchers.PATCH_ANDROIDTV_OPEN, patchers.PATCH_SIGNER, patchers.PATCH_ISFILE, patchers.PATCH_ACCESS: assert await async_setup_component(hass, DOMAIN, config) + await hass.async_block_till_done() await hass.helpers.entity_component.async_update_entity(entity_id) state = hass.states.get(entity_id) assert state is not None @@ -315,6 +318,7 @@ async def _test_sources(hass, config0): patch_key ], patchers.patch_shell("")[patch_key]: assert await async_setup_component(hass, DOMAIN, config) + await hass.async_block_till_done() await hass.helpers.entity_component.async_update_entity(entity_id) state = hass.states.get(entity_id) assert state is not None @@ -395,6 +399,7 @@ async def _test_exclude_sources(hass, config0, expected_sources): patch_key ], patchers.patch_shell("")[patch_key]: assert await async_setup_component(hass, DOMAIN, config) + await hass.async_block_till_done() await hass.helpers.entity_component.async_update_entity(entity_id) state = hass.states.get(entity_id) assert state is not None @@ -463,6 +468,7 @@ async def _test_select_source(hass, config0, source, expected_arg, method_patch) patch_key ], patchers.patch_shell("")[patch_key]: assert await async_setup_component(hass, DOMAIN, config) + await hass.async_block_till_done() await hass.helpers.entity_component.async_update_entity(entity_id) state = hass.states.get(entity_id) assert state is not None @@ -666,6 +672,7 @@ async def _test_setup_fail(hass, config): patch_key ], patchers.PATCH_KEYGEN, patchers.PATCH_ANDROIDTV_OPEN, patchers.PATCH_SIGNER: assert await async_setup_component(hass, DOMAIN, config) + await hass.async_block_till_done() await hass.helpers.entity_component.async_update_entity(entity_id) state = hass.states.get(entity_id) assert state is None @@ -698,6 +705,7 @@ async def test_setup_two_devices(hass): patch_key ], patchers.patch_shell("")[patch_key]: assert await async_setup_component(hass, DOMAIN, config) + await hass.async_block_till_done() for entity_id in ["media_player.android_tv", "media_player.fire_tv"]: await hass.helpers.entity_component.async_update_entity(entity_id) @@ -714,6 +722,7 @@ async def test_setup_same_device_twice(hass): patch_key ], patchers.patch_shell("")[patch_key]: assert await async_setup_component(hass, DOMAIN, CONFIG_ANDROIDTV_ADB_SERVER) + await hass.async_block_till_done() state = hass.states.get(entity_id) assert state is not None @@ -723,6 +732,7 @@ async def test_setup_same_device_twice(hass): patch_key ], patchers.patch_shell("")[patch_key]: assert await async_setup_component(hass, DOMAIN, CONFIG_ANDROIDTV_ADB_SERVER) + await hass.async_block_till_done() async def test_adb_command(hass): @@ -735,6 +745,7 @@ async def test_adb_command(hass): patch_key ], patchers.patch_shell("")[patch_key]: assert await async_setup_component(hass, DOMAIN, CONFIG_ANDROIDTV_ADB_SERVER) + await hass.async_block_till_done() with patch( "androidtv.basetv.BaseTV.adb_shell", return_value=response @@ -762,6 +773,7 @@ async def test_adb_command_unicode_decode_error(hass): patch_key ], patchers.patch_shell("")[patch_key]: assert await async_setup_component(hass, DOMAIN, CONFIG_ANDROIDTV_ADB_SERVER) + await hass.async_block_till_done() with patch( "androidtv.basetv.BaseTV.adb_shell", @@ -791,6 +803,7 @@ async def test_adb_command_key(hass): patch_key ], patchers.patch_shell("")[patch_key]: assert await async_setup_component(hass, DOMAIN, CONFIG_ANDROIDTV_ADB_SERVER) + await hass.async_block_till_done() with patch( "androidtv.basetv.BaseTV.adb_shell", return_value=response @@ -819,6 +832,7 @@ async def test_adb_command_get_properties(hass): patch_key ], patchers.patch_shell("")[patch_key]: assert await async_setup_component(hass, DOMAIN, CONFIG_ANDROIDTV_ADB_SERVER) + await hass.async_block_till_done() with patch( "androidtv.androidtv.AndroidTV.get_properties_dict", return_value=response @@ -844,6 +858,7 @@ async def test_update_lock_not_acquired(hass): patch_key ], patchers.patch_shell("")[patch_key]: assert await async_setup_component(hass, DOMAIN, CONFIG_ANDROIDTV_ADB_SERVER) + await hass.async_block_till_done() with patchers.patch_shell("")[patch_key]: await hass.helpers.entity_component.async_update_entity(entity_id) @@ -877,6 +892,7 @@ async def test_download(hass): patch_key ], patchers.patch_shell("")[patch_key]: assert await async_setup_component(hass, DOMAIN, CONFIG_ANDROIDTV_ADB_SERVER) + await hass.async_block_till_done() # Failed download because path is not whitelisted with patch("androidtv.basetv.BaseTV.adb_pull") as patch_pull: @@ -919,6 +935,7 @@ async def test_upload(hass): patch_key ], patchers.patch_shell("")[patch_key]: assert await async_setup_component(hass, DOMAIN, CONFIG_ANDROIDTV_ADB_SERVER) + await hass.async_block_till_done() # Failed upload because path is not whitelisted with patch("androidtv.basetv.BaseTV.adb_push") as patch_push: @@ -959,6 +976,7 @@ async def test_androidtv_volume_set(hass): patch_key ], patchers.patch_shell("")[patch_key]: assert await async_setup_component(hass, DOMAIN, CONFIG_ANDROIDTV_ADB_SERVER) + await hass.async_block_till_done() with patch( "androidtv.basetv.BaseTV.set_volume_level", return_value=0.5 @@ -984,6 +1002,7 @@ async def test_get_image(hass, hass_ws_client): patch_key ], patchers.patch_shell("")[patch_key]: assert await async_setup_component(hass, DOMAIN, CONFIG_ANDROIDTV_ADB_SERVER) + await hass.async_block_till_done() with patchers.patch_shell("11")[patch_key]: await hass.helpers.entity_component.async_update_entity(entity_id) diff --git a/tests/components/bayesian/test_binary_sensor.py b/tests/components/bayesian/test_binary_sensor.py index 6b13ef58521..7bbb9eeda27 100644 --- a/tests/components/bayesian/test_binary_sensor.py +++ b/tests/components/bayesian/test_binary_sensor.py @@ -45,6 +45,7 @@ class TestBayesianBinarySensor(unittest.TestCase): self.hass.block_till_done() assert setup_component(self.hass, "binary_sensor", config) + self.hass.block_till_done() state = self.hass.states.get("binary_sensor.test_binary") assert state.attributes.get("observations")[0]["prob_given_true"] == 0.8 @@ -75,6 +76,7 @@ class TestBayesianBinarySensor(unittest.TestCase): self.hass.block_till_done() assert setup_component(self.hass, "binary_sensor", config) + self.hass.block_till_done() state = self.hass.states.get("binary_sensor.test_binary") assert state.attributes.get("observations") == [] @@ -107,6 +109,7 @@ class TestBayesianBinarySensor(unittest.TestCase): } assert setup_component(self.hass, "binary_sensor", config) + self.hass.block_till_done() self.hass.states.set("sensor.test_monitored", 4) self.hass.block_till_done() @@ -173,6 +176,7 @@ class TestBayesianBinarySensor(unittest.TestCase): } assert setup_component(self.hass, "binary_sensor", config) + self.hass.block_till_done() self.hass.states.set("sensor.test_monitored", "on") @@ -227,6 +231,7 @@ class TestBayesianBinarySensor(unittest.TestCase): } assert setup_component(self.hass, "binary_sensor", config) + self.hass.block_till_done() self.hass.states.set("sensor.test_monitored", "on") @@ -281,6 +286,7 @@ class TestBayesianBinarySensor(unittest.TestCase): } assert setup_component(self.hass, "binary_sensor", config) + self.hass.block_till_done() self.hass.states.set("sensor.test_monitored", "on") self.hass.block_till_done() @@ -318,6 +324,7 @@ class TestBayesianBinarySensor(unittest.TestCase): } assert setup_component(self.hass, "binary_sensor", config) + self.hass.block_till_done() self.hass.states.set("sensor.test_monitored", "off") @@ -401,6 +408,7 @@ class TestBayesianBinarySensor(unittest.TestCase): } assert setup_component(self.hass, "binary_sensor", config) + self.hass.block_till_done() self.hass.states.set("sensor.test_monitored", "on") self.hass.block_till_done() @@ -452,6 +460,7 @@ class TestBayesianBinarySensor(unittest.TestCase): } assert setup_component(self.hass, "binary_sensor", config) + self.hass.block_till_done() self.hass.states.set("sensor.test_monitored", "on") self.hass.block_till_done() diff --git a/tests/components/binary_sensor/test_device_condition.py b/tests/components/binary_sensor/test_device_condition.py index 968b54b7892..fef12c88f49 100644 --- a/tests/components/binary_sensor/test_device_condition.py +++ b/tests/components/binary_sensor/test_device_condition.py @@ -104,6 +104,7 @@ async def test_if_state(hass, calls): platform.init() assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) + await hass.async_block_till_done() sensor1 = platform.ENTITIES["battery"] @@ -180,6 +181,7 @@ async def test_if_fires_on_for_condition(hass, calls): platform.init() assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) + await hass.async_block_till_done() sensor1 = platform.ENTITIES["battery"] diff --git a/tests/components/binary_sensor/test_device_trigger.py b/tests/components/binary_sensor/test_device_trigger.py index 6234d464f52..92fbce27bc1 100644 --- a/tests/components/binary_sensor/test_device_trigger.py +++ b/tests/components/binary_sensor/test_device_trigger.py @@ -103,6 +103,7 @@ async def test_if_fires_on_state_change(hass, calls): platform = getattr(hass.components, f"test.{DOMAIN}") platform.init() assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) + await hass.async_block_till_done() sensor1 = platform.ENTITIES["battery"] @@ -187,6 +188,7 @@ async def test_if_fires_on_state_change_with_for(hass, calls): platform.init() assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) + await hass.async_block_till_done() sensor1 = platform.ENTITIES["battery"] diff --git a/tests/components/bom/test_sensor.py b/tests/components/bom/test_sensor.py index 6e85dbca1cd..8c647bbf6cf 100644 --- a/tests/components/bom/test_sensor.py +++ b/tests/components/bom/test_sensor.py @@ -70,6 +70,7 @@ class TestBOMWeatherSensor(unittest.TestCase): """Test the setup with custom settings.""" with assert_setup_component(1, sensor.DOMAIN): assert setup_component(self.hass, sensor.DOMAIN, {"sensor": VALID_CONFIG}) + self.hass.block_till_done() fake_entities = [ "bom_fake_feels_like_c", @@ -85,6 +86,7 @@ class TestBOMWeatherSensor(unittest.TestCase): def test_sensor_values(self, mock_get): """Test retrieval of sensor values.""" assert setup_component(self.hass, sensor.DOMAIN, {"sensor": VALID_CONFIG}) + self.hass.block_till_done() weather = self.hass.states.get("sensor.bom_fake_weather").state assert "Fine" == weather diff --git a/tests/components/buienradar/test_sensor.py b/tests/components/buienradar/test_sensor.py index 0c1cbd2a158..801f5706a08 100644 --- a/tests/components/buienradar/test_sensor.py +++ b/tests/components/buienradar/test_sensor.py @@ -19,6 +19,7 @@ BASE_CONFIG = { async def test_smoke_test_setup_component(hass): """Smoke test for successfully set-up with default config.""" assert await async_setup_component(hass, sensor.DOMAIN, BASE_CONFIG) + await hass.async_block_till_done() for cond in CONDITIONS: state = hass.states.get(f"sensor.volkel_{cond}") diff --git a/tests/components/buienradar/test_weather.py b/tests/components/buienradar/test_weather.py index 081616a1406..db0a6ce3984 100644 --- a/tests/components/buienradar/test_weather.py +++ b/tests/components/buienradar/test_weather.py @@ -19,6 +19,7 @@ BASE_CONFIG = { async def test_smoke_test_setup_component(hass): """Smoke test for successfully set-up with default config.""" assert await async_setup_component(hass, weather.DOMAIN, BASE_CONFIG) + await hass.async_block_till_done() state = hass.states.get("weather.volkel") assert state.state == "unknown" diff --git a/tests/components/calendar/test_init.py b/tests/components/calendar/test_init.py index ede5479e407..1f87c38c6bf 100644 --- a/tests/components/calendar/test_init.py +++ b/tests/components/calendar/test_init.py @@ -8,6 +8,7 @@ import homeassistant.util.dt as dt_util async def test_events_http_api(hass, hass_client): """Test the calendar demo view.""" await async_setup_component(hass, "calendar", {"calendar": {"platform": "demo"}}) + await hass.async_block_till_done() client = await hass_client() response = await client.get("/api/calendars/calendar.calendar_2") assert response.status == 400 @@ -27,6 +28,7 @@ async def test_events_http_api(hass, hass_client): async def test_calendars_http_api(hass, hass_client): """Test the calendar demo view.""" await async_setup_component(hass, "calendar", {"calendar": {"platform": "demo"}}) + await hass.async_block_till_done() client = await hass_client() response = await client.get("/api/calendars") assert response.status == 200 diff --git a/tests/components/camera/test_init.py b/tests/components/camera/test_init.py index 81f215a145d..8a79cb39ec9 100644 --- a/tests/components/camera/test_init.py +++ b/tests/components/camera/test_init.py @@ -19,11 +19,12 @@ from tests.components.camera import common @pytest.fixture(name="mock_camera") -def mock_camera_fixture(hass): +async def mock_camera_fixture(hass): """Initialize a demo camera platform.""" - assert hass.loop.run_until_complete( - async_setup_component(hass, "camera", {camera.DOMAIN: {"platform": "demo"}}) + assert await async_setup_component( + hass, "camera", {camera.DOMAIN: {"platform": "demo"}} ) + await hass.async_block_till_done() with patch( "homeassistant.components.demo.camera.Path.read_bytes", return_value=b"Test", @@ -51,6 +52,7 @@ async def image_mock_url_fixture(hass): await async_setup_component( hass, camera.DOMAIN, {camera.DOMAIN: {"platform": "demo"}} ) + await hass.async_block_till_done() async def test_get_image_from_camera(hass, image_mock_url): @@ -311,7 +313,10 @@ async def test_preload_stream(hass, mock_stream): "homeassistant.components.demo.camera.DemoCamera.stream_source", return_value="http://example.com", ): - await async_setup_component(hass, "camera", {DOMAIN: {"platform": "demo"}}) + assert await async_setup_component( + hass, "camera", {DOMAIN: {"platform": "demo"}} + ) + await hass.async_block_till_done() hass.bus.async_fire(EVENT_HOMEASSISTANT_START) await hass.async_block_till_done() assert mock_request_stream.called diff --git a/tests/components/coinmarketcap/test_sensor.py b/tests/components/coinmarketcap/test_sensor.py index 8997bc4a5d6..f53a92a3d86 100644 --- a/tests/components/coinmarketcap/test_sensor.py +++ b/tests/components/coinmarketcap/test_sensor.py @@ -36,6 +36,7 @@ class TestCoinMarketCapSensor(unittest.TestCase): """Test the setup with custom settings.""" with assert_setup_component(1, sensor.DOMAIN): assert setup_component(self.hass, sensor.DOMAIN, {"sensor": VALID_CONFIG}) + self.hass.block_till_done() state = self.hass.states.get("sensor.ethereum") assert state is not None diff --git a/tests/components/command_line/test_cover.py b/tests/components/command_line/test_cover.py index fc359d49b26..c4f45ed9704 100644 --- a/tests/components/command_line/test_cover.py +++ b/tests/components/command_line/test_cover.py @@ -68,6 +68,7 @@ async def test_state_value(hass): ) is True ) + await hass.async_block_till_done() assert "unknown" == hass.states.get("cover.test").state diff --git a/tests/components/command_line/test_switch.py b/tests/components/command_line/test_switch.py index acdb5b23b10..ab5d0044f73 100644 --- a/tests/components/command_line/test_switch.py +++ b/tests/components/command_line/test_switch.py @@ -43,6 +43,7 @@ class TestCommandSwitch(unittest.TestCase): } }, ) + self.hass.block_till_done() state = self.hass.states.get("switch.test") assert STATE_OFF == state.state @@ -79,6 +80,7 @@ class TestCommandSwitch(unittest.TestCase): } }, ) + self.hass.block_till_done() state = self.hass.states.get("switch.test") assert STATE_OFF == state.state @@ -117,6 +119,7 @@ class TestCommandSwitch(unittest.TestCase): } }, ) + self.hass.block_till_done() state = self.hass.states.get("switch.test") assert STATE_OFF == state.state @@ -152,7 +155,7 @@ class TestCommandSwitch(unittest.TestCase): } }, ) - + self.hass.block_till_done() state = self.hass.states.get("switch.test") assert STATE_OFF == state.state diff --git a/tests/components/cover/test_device_condition.py b/tests/components/cover/test_device_condition.py index b355053ad36..511c7ced898 100644 --- a/tests/components/cover/test_device_condition.py +++ b/tests/components/cover/test_device_condition.py @@ -454,6 +454,7 @@ async def test_if_position(hass, calls): platform.init() ent = platform.ENTITIES[1] assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) + await hass.async_block_till_done() assert await async_setup_component( hass, @@ -557,6 +558,7 @@ async def test_if_tilt_position(hass, calls): platform.init() ent = platform.ENTITIES[2] assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) + await hass.async_block_till_done() assert await async_setup_component( hass, diff --git a/tests/components/cover/test_device_trigger.py b/tests/components/cover/test_device_trigger.py index 50738e2c549..1996cf9d6df 100644 --- a/tests/components/cover/test_device_trigger.py +++ b/tests/components/cover/test_device_trigger.py @@ -464,6 +464,7 @@ async def test_if_fires_on_position(hass, calls): platform.init() ent = platform.ENTITIES[1] assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) + await hass.async_block_till_done() assert await async_setup_component( hass, @@ -586,6 +587,7 @@ async def test_if_fires_on_tilt_position(hass, calls): platform.init() ent = platform.ENTITIES[1] assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) + await hass.async_block_till_done() assert await async_setup_component( hass, diff --git a/tests/components/darksky/test_sensor.py b/tests/components/darksky/test_sensor.py index 8c4038f91c6..9a48e4f1cce 100644 --- a/tests/components/darksky/test_sensor.py +++ b/tests/components/darksky/test_sensor.py @@ -117,6 +117,7 @@ class TestDarkSkySetup(unittest.TestCase): def test_setup_with_config(self): """Test the platform setup with configuration.""" setup_component(self.hass, "sensor", VALID_CONFIG_MINIMAL) + self.hass.block_till_done() state = self.hass.states.get("sensor.dark_sky_summary") assert state is not None @@ -124,6 +125,7 @@ class TestDarkSkySetup(unittest.TestCase): def test_setup_with_invalid_config(self): """Test the platform setup with invalid configuration.""" setup_component(self.hass, "sensor", INVALID_CONFIG_MINIMAL) + self.hass.block_till_done() state = self.hass.states.get("sensor.dark_sky_summary") assert state is None @@ -135,6 +137,7 @@ class TestDarkSkySetup(unittest.TestCase): def test_setup_with_language_config(self): """Test the platform setup with language configuration.""" setup_component(self.hass, "sensor", VALID_CONFIG_LANG_DE) + self.hass.block_till_done() state = self.hass.states.get("sensor.dark_sky_summary") assert state is not None @@ -142,6 +145,7 @@ class TestDarkSkySetup(unittest.TestCase): def test_setup_with_invalid_language_config(self): """Test the platform setup with language configuration.""" setup_component(self.hass, "sensor", INVALID_CONFIG_LANG) + self.hass.block_till_done() state = self.hass.states.get("sensor.dark_sky_summary") assert state is None @@ -169,6 +173,7 @@ class TestDarkSkySetup(unittest.TestCase): def test_setup_with_alerts_config(self): """Test the platform setup with alert configuration.""" setup_component(self.hass, "sensor", VALID_CONFIG_ALERTS) + self.hass.block_till_done() state = self.hass.states.get("sensor.dark_sky_alerts") assert state.state == "0" @@ -184,6 +189,7 @@ class TestDarkSkySetup(unittest.TestCase): mock_req.get(re.compile(uri), text=load_fixture("darksky.json")) assert setup_component(self.hass, "sensor", VALID_CONFIG_MINIMAL) + self.hass.block_till_done() assert mock_get_forecast.called assert mock_get_forecast.call_count == 1 diff --git a/tests/components/darksky/test_weather.py b/tests/components/darksky/test_weather.py index f871d424db6..9f43534d7cd 100644 --- a/tests/components/darksky/test_weather.py +++ b/tests/components/darksky/test_weather.py @@ -43,6 +43,7 @@ class TestDarkSky(unittest.TestCase): weather.DOMAIN, {"weather": {"name": "test", "platform": "darksky", "api_key": "foo"}}, ) + self.hass.block_till_done() assert mock_get_forecast.called assert mock_get_forecast.call_count == 1 @@ -59,6 +60,7 @@ class TestDarkSky(unittest.TestCase): weather.DOMAIN, {"weather": {"name": "test", "platform": "darksky", "api_key": "foo"}}, ) + self.hass.block_till_done() state = self.hass.states.get("weather.test") assert state.state == "unavailable" diff --git a/tests/components/demo/test_camera.py b/tests/components/demo/test_camera.py index 49b8e017f1a..e62d6db0464 100644 --- a/tests/components/demo/test_camera.py +++ b/tests/components/demo/test_camera.py @@ -22,13 +22,12 @@ ENTITY_CAMERA = "camera.demo_camera" @pytest.fixture(autouse=True) -def demo_camera(hass): +async def demo_camera(hass): """Initialize a demo camera platform.""" - hass.loop.run_until_complete( - async_setup_component( - hass, CAMERA_DOMAIN, {CAMERA_DOMAIN: {"platform": DOMAIN}} - ) + assert await async_setup_component( + hass, CAMERA_DOMAIN, {CAMERA_DOMAIN: {"platform": DOMAIN}} ) + await hass.async_block_till_done() async def test_init_state_is_streaming(hass): diff --git a/tests/components/demo/test_climate.py b/tests/components/demo/test_climate.py index efdab74304a..91e14247834 100644 --- a/tests/components/demo/test_climate.py +++ b/tests/components/demo/test_climate.py @@ -43,6 +43,7 @@ async def setup_demo_climate(hass): """Initialize setup demo climate.""" hass.config.units = METRIC_SYSTEM assert await async_setup_component(hass, DOMAIN, {"climate": {"platform": "demo"}}) + await hass.async_block_till_done() def test_setup_params(hass): diff --git a/tests/components/demo/test_cover.py b/tests/components/demo/test_cover.py index e2478f64f3a..8d561f328a5 100644 --- a/tests/components/demo/test_cover.py +++ b/tests/components/demo/test_cover.py @@ -42,6 +42,7 @@ async def setup_comp(hass): """Set up demo cover component.""" with assert_setup_component(1, DOMAIN): await async_setup_component(hass, DOMAIN, CONFIG) + await hass.async_block_till_done() async def test_supported_features(hass, setup_comp): diff --git a/tests/components/demo/test_fan.py b/tests/components/demo/test_fan.py index 71ec5c385dc..d9c9ac77dc8 100644 --- a/tests/components/demo/test_fan.py +++ b/tests/components/demo/test_fan.py @@ -16,11 +16,10 @@ def get_entity(hass): @pytest.fixture(autouse=True) -def setup_comp(hass): +async def setup_comp(hass): """Initialize components.""" - hass.loop.run_until_complete( - async_setup_component(hass, fan.DOMAIN, {"fan": {"platform": "demo"}}) - ) + assert await async_setup_component(hass, fan.DOMAIN, {"fan": {"platform": "demo"}}) + await hass.async_block_till_done() async def test_turn_on(hass): diff --git a/tests/components/demo/test_light.py b/tests/components/demo/test_light.py index fd17d7bdb0e..4e7f58811d9 100644 --- a/tests/components/demo/test_light.py +++ b/tests/components/demo/test_light.py @@ -24,11 +24,12 @@ ENTITY_LIGHT = "light.bed_light" @pytest.fixture(autouse=True) -def setup_comp(hass): +async def setup_comp(hass): """Set up demo component.""" - hass.loop.run_until_complete( - async_setup_component(hass, LIGHT_DOMAIN, {LIGHT_DOMAIN: {"platform": DOMAIN}}) + assert await async_setup_component( + hass, LIGHT_DOMAIN, {LIGHT_DOMAIN: {"platform": DOMAIN}} ) + await hass.async_block_till_done() async def test_state_attributes(hass): diff --git a/tests/components/demo/test_lock.py b/tests/components/demo/test_lock.py index 68d5a884cf0..bf8c0ddb63d 100644 --- a/tests/components/demo/test_lock.py +++ b/tests/components/demo/test_lock.py @@ -21,11 +21,12 @@ OPENABLE_LOCK = "lock.openable_lock" @pytest.fixture(autouse=True) -def setup_comp(hass): +async def setup_comp(hass): """Set up demo component.""" - hass.loop.run_until_complete( - async_setup_component(hass, LOCK_DOMAIN, {LOCK_DOMAIN: {"platform": DOMAIN}}) + assert await async_setup_component( + hass, LOCK_DOMAIN, {LOCK_DOMAIN: {"platform": DOMAIN}} ) + await hass.async_block_till_done() async def test_locking(hass): diff --git a/tests/components/demo/test_media_player.py b/tests/components/demo/test_media_player.py index e663600f84f..1ab8195c4db 100644 --- a/tests/components/demo/test_media_player.py +++ b/tests/components/demo/test_media_player.py @@ -29,6 +29,7 @@ async def test_source_select(hass): assert await async_setup_component( hass, mp.DOMAIN, {"media_player": {"platform": "demo"}} ) + await hass.async_block_till_done() state = hass.states.get(entity_id) assert state.attributes.get("source") == "dvd" @@ -47,6 +48,7 @@ async def test_clear_playlist(hass): assert await async_setup_component( hass, mp.DOMAIN, {"media_player": {"platform": "demo"}} ) + await hass.async_block_till_done() assert hass.states.is_state(TEST_ENTITY_ID, "playing") await common.async_clear_playlist(hass, TEST_ENTITY_ID) @@ -58,6 +60,7 @@ async def test_volume_services(hass): assert await async_setup_component( hass, mp.DOMAIN, {"media_player": {"platform": "demo"}} ) + await hass.async_block_till_done() state = hass.states.get(TEST_ENTITY_ID) assert state.attributes.get("volume_level") == 1.0 @@ -95,6 +98,7 @@ async def test_turning_off_and_on(hass): assert await async_setup_component( hass, mp.DOMAIN, {"media_player": {"platform": "demo"}} ) + await hass.async_block_till_done() assert hass.states.is_state(TEST_ENTITY_ID, "playing") await common.async_turn_off(hass, TEST_ENTITY_ID) @@ -114,6 +118,7 @@ async def test_playing_pausing(hass): assert await async_setup_component( hass, mp.DOMAIN, {"media_player": {"platform": "demo"}} ) + await hass.async_block_till_done() assert hass.states.is_state(TEST_ENTITY_ID, "playing") await common.async_media_pause(hass, TEST_ENTITY_ID) @@ -134,6 +139,7 @@ async def test_prev_next_track(hass): assert await async_setup_component( hass, mp.DOMAIN, {"media_player": {"platform": "demo"}} ) + await hass.async_block_till_done() state = hass.states.get(TEST_ENTITY_ID) assert state.attributes.get("media_track") == 1 @@ -152,6 +158,7 @@ async def test_prev_next_track(hass): assert await async_setup_component( hass, mp.DOMAIN, {"media_player": {"platform": "demo"}} ) + await hass.async_block_till_done() ent_id = "media_player.lounge_room" state = hass.states.get(ent_id) assert state.attributes.get("media_episode") == 1 @@ -170,6 +177,7 @@ async def test_play_media(hass): assert await async_setup_component( hass, mp.DOMAIN, {"media_player": {"platform": "demo"}} ) + await hass.async_block_till_done() ent_id = "media_player.living_room" state = hass.states.get(ent_id) assert mp.SUPPORT_PLAY_MEDIA & state.attributes.get("supported_features") > 0 @@ -192,6 +200,7 @@ async def test_seek(hass, mock_media_seek): assert await async_setup_component( hass, mp.DOMAIN, {"media_player": {"platform": "demo"}} ) + await hass.async_block_till_done() ent_id = "media_player.living_room" state = hass.states.get(ent_id) assert state.attributes["supported_features"] & mp.SUPPORT_SEEK @@ -209,6 +218,7 @@ async def test_media_image_proxy(hass, hass_client): assert await async_setup_component( hass, mp.DOMAIN, {"media_player": {"platform": "demo"}} ) + await hass.async_block_till_done() fake_picture_data = "test.test" diff --git a/tests/components/demo/test_remote.py b/tests/components/demo/test_remote.py index b83eaca4c9c..7ea31fbeb69 100644 --- a/tests/components/demo/test_remote.py +++ b/tests/components/demo/test_remote.py @@ -22,6 +22,7 @@ class TestDemoRemote(unittest.TestCase): assert setup_component( self.hass, remote.DOMAIN, {"remote": {"platform": "demo"}} ) + self.hass.block_till_done() # pylint: disable=invalid-name def tearDown(self): diff --git a/tests/components/demo/test_stt.py b/tests/components/demo/test_stt.py index 3fe4e223961..f749d6288a7 100644 --- a/tests/components/demo/test_stt.py +++ b/tests/components/demo/test_stt.py @@ -6,11 +6,10 @@ from homeassistant.setup import async_setup_component @pytest.fixture(autouse=True) -def setup_comp(hass): +async def setup_comp(hass): """Set up demo component.""" - hass.loop.run_until_complete( - async_setup_component(hass, stt.DOMAIN, {"stt": {"platform": "demo"}}) - ) + assert await async_setup_component(hass, stt.DOMAIN, {"stt": {"platform": "demo"}}) + await hass.async_block_till_done() async def test_demo_settings(hass_client): diff --git a/tests/components/demo/test_vacuum.py b/tests/components/demo/test_vacuum.py index 5c9ac4205fe..d07d7b3d4b2 100644 --- a/tests/components/demo/test_vacuum.py +++ b/tests/components/demo/test_vacuum.py @@ -51,6 +51,7 @@ ENTITY_VACUUM_STATE = f"{DOMAIN}.{DEMO_VACUUM_STATE}".lower() async def setup_demo_vacuum(hass): """Initialize setup demo vacuum.""" assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "demo"}}) + await hass.async_block_till_done() async def test_supported_features(hass): diff --git a/tests/components/demo/test_water_heater.py b/tests/components/demo/test_water_heater.py index b8474e978c4..a6b4d999ddb 100644 --- a/tests/components/demo/test_water_heater.py +++ b/tests/components/demo/test_water_heater.py @@ -13,14 +13,13 @@ ENTITY_WATER_HEATER_CELSIUS = "water_heater.demo_water_heater_celsius" @pytest.fixture(autouse=True) -def setup_comp(hass): +async def setup_comp(hass): """Set up demo component.""" hass.config.units = IMPERIAL_SYSTEM - hass.loop.run_until_complete( - async_setup_component( - hass, water_heater.DOMAIN, {"water_heater": {"platform": "demo"}} - ) + assert await async_setup_component( + hass, water_heater.DOMAIN, {"water_heater": {"platform": "demo"}} ) + await hass.async_block_till_done() async def test_setup_params(hass): diff --git a/tests/components/device_automation/test_init.py b/tests/components/device_automation/test_init.py index 48426e2640e..d6a9fda00bc 100644 --- a/tests/components/device_automation/test_init.py +++ b/tests/components/device_automation/test_init.py @@ -621,7 +621,7 @@ async def test_automation_with_sub_condition(hass, calls): platform.init() assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) - + await hass.async_block_till_done() ent1, ent2, ent3 = platform.ENTITIES assert await async_setup_component( diff --git a/tests/components/device_sun_light_trigger/test_init.py b/tests/components/device_sun_light_trigger/test_init.py index 23e400adac4..2663018fc09 100644 --- a/tests/components/device_sun_light_trigger/test_init.py +++ b/tests/components/device_sun_light_trigger/test_init.py @@ -14,11 +14,13 @@ from homeassistant.components.device_tracker.const import DOMAIN from homeassistant.const import ( ATTR_ENTITY_ID, CONF_PLATFORM, + EVENT_HOMEASSISTANT_START, STATE_HOME, STATE_NOT_HOME, STATE_OFF, STATE_ON, ) +from homeassistant.core import CoreState from homeassistant.setup import async_setup_component from homeassistant.util import dt as dt_util @@ -214,3 +216,19 @@ async def test_lights_turn_on_when_coming_home_after_sun_set_person(hass, scanne assert hass.states.get(device_1).state == "home" assert hass.states.get(device_2).state == "home" assert hass.states.get("person.me").state == "home" + + +async def test_initialize_start(hass): + """Test we initialize when HA starts.""" + hass.state = CoreState.not_running + assert await async_setup_component( + hass, device_sun_light_trigger.DOMAIN, {device_sun_light_trigger.DOMAIN: {}}, + ) + + with patch( + "homeassistant.components.device_sun_light_trigger.activate_automation" + ) as mock_activate: + hass.bus.fire(EVENT_HOMEASSISTANT_START) + await hass.async_block_till_done() + + assert len(mock_activate.mock_calls) == 1 diff --git a/tests/components/dsmr/test_sensor.py b/tests/components/dsmr/test_sensor.py index 895a95bef7b..6e6cc8f55c6 100644 --- a/tests/components/dsmr/test_sensor.py +++ b/tests/components/dsmr/test_sensor.py @@ -75,6 +75,7 @@ async def test_default_setup(hass, mock_connection_factory): with assert_setup_component(1): await async_setup_component(hass, "sensor", {"sensor": config}) + await hass.async_block_till_done() telegram_callback = connection_factory.call_args_list[0][0][2] @@ -171,6 +172,7 @@ async def test_v4_meter(hass, mock_connection_factory): with assert_setup_component(1): await async_setup_component(hass, "sensor", {"sensor": config}) + await hass.async_block_till_done() telegram_callback = connection_factory.call_args_list[0][0][2] @@ -215,6 +217,7 @@ async def test_v5_meter(hass, mock_connection_factory): with assert_setup_component(1): await async_setup_component(hass, "sensor", {"sensor": config}) + await hass.async_block_till_done() telegram_callback = connection_factory.call_args_list[0][0][2] @@ -259,6 +262,7 @@ async def test_belgian_meter(hass, mock_connection_factory): with assert_setup_component(1): await async_setup_component(hass, "sensor", {"sensor": config}) + await hass.async_block_till_done() telegram_callback = connection_factory.call_args_list[0][0][2] @@ -292,6 +296,7 @@ async def test_belgian_meter_low(hass, mock_connection_factory): with assert_setup_component(1): await async_setup_component(hass, "sensor", {"sensor": config}) + await hass.async_block_till_done() telegram_callback = connection_factory.call_args_list[0][0][2] @@ -315,6 +320,7 @@ async def test_tcp(hass, mock_connection_factory): with assert_setup_component(1): await async_setup_component(hass, "sensor", {"sensor": config}) + await hass.async_block_till_done() assert connection_factory.call_args_list[0][0][0] == "localhost" assert connection_factory.call_args_list[0][0][1] == "1234" diff --git a/tests/components/dte_energy_bridge/test_sensor.py b/tests/components/dte_energy_bridge/test_sensor.py index d95bc31f066..34f0a0a28c3 100644 --- a/tests/components/dte_energy_bridge/test_sensor.py +++ b/tests/components/dte_energy_bridge/test_sensor.py @@ -38,6 +38,7 @@ class TestDteEnergyBridgeSetup(unittest.TestCase): assert setup_component( self.hass, "sensor", {"sensor": DTE_ENERGY_BRIDGE_CONFIG} ) + self.hass.block_till_done() assert "0.411" == self.hass.states.get("sensor.current_energy_usage").state @requests_mock.Mocker() @@ -50,6 +51,7 @@ class TestDteEnergyBridgeSetup(unittest.TestCase): assert setup_component( self.hass, "sensor", {"sensor": DTE_ENERGY_BRIDGE_CONFIG} ) + self.hass.block_till_done() assert "0.411" == self.hass.states.get("sensor.current_energy_usage").state @requests_mock.Mocker() @@ -62,4 +64,5 @@ class TestDteEnergyBridgeSetup(unittest.TestCase): assert setup_component( self.hass, "sensor", {"sensor": DTE_ENERGY_BRIDGE_CONFIG} ) + self.hass.block_till_done() assert "unknown" == self.hass.states.get("sensor.current_energy_usage").state diff --git a/tests/components/efergy/test_sensor.py b/tests/components/efergy/test_sensor.py index 166325ee242..252669233e5 100644 --- a/tests/components/efergy/test_sensor.py +++ b/tests/components/efergy/test_sensor.py @@ -85,6 +85,7 @@ class TestEfergySensor(unittest.TestCase): """Test for successfully setting up the Efergy platform.""" mock_responses(mock) assert setup_component(self.hass, "sensor", {"sensor": ONE_SENSOR_CONFIG}) + self.hass.block_till_done() assert "38.21" == self.hass.states.get("sensor.energy_consumed").state assert "1580" == self.hass.states.get("sensor.energy_usage").state @@ -97,6 +98,7 @@ class TestEfergySensor(unittest.TestCase): """Test for multiple sensors in one household.""" mock_responses(mock) assert setup_component(self.hass, "sensor", {"sensor": MULTI_SENSOR_CONFIG}) + self.hass.block_till_done() assert "218" == self.hass.states.get("sensor.efergy_728386").state assert "1808" == self.hass.states.get("sensor.efergy_0").state diff --git a/tests/components/facebox/test_image_processing.py b/tests/components/facebox/test_image_processing.py index b40211d5a91..00ce7af0d01 100644 --- a/tests/components/facebox/test_image_processing.py +++ b/tests/components/facebox/test_image_processing.py @@ -156,6 +156,7 @@ def test_valid_file_path(): async def test_setup_platform(hass, mock_healthybox): """Set up platform with one entity.""" await async_setup_component(hass, ip.DOMAIN, VALID_CONFIG) + await hass.async_block_till_done() assert hass.states.get(VALID_ENTITY_ID) @@ -166,12 +167,14 @@ async def test_setup_platform_with_auth(hass, mock_healthybox): valid_config_auth[ip.DOMAIN][CONF_PASSWORD] = MOCK_PASSWORD await async_setup_component(hass, ip.DOMAIN, valid_config_auth) + await hass.async_block_till_done() assert hass.states.get(VALID_ENTITY_ID) async def test_process_image(hass, mock_healthybox, mock_image): """Test successful processing of an image.""" await async_setup_component(hass, ip.DOMAIN, VALID_CONFIG) + await hass.async_block_till_done() assert hass.states.get(VALID_ENTITY_ID) face_events = [] @@ -215,6 +218,7 @@ async def test_process_image(hass, mock_healthybox, mock_image): async def test_process_image_errors(hass, mock_healthybox, mock_image, caplog): """Test process_image errors.""" await async_setup_component(hass, ip.DOMAIN, VALID_CONFIG) + await hass.async_block_till_done() assert hass.states.get(VALID_ENTITY_ID) # Test connection error. @@ -246,6 +250,7 @@ async def test_teach_service( ): """Test teaching of facebox.""" await async_setup_component(hass, ip.DOMAIN, VALID_CONFIG) + await hass.async_block_till_done() assert hass.states.get(VALID_ENTITY_ID) # Patch out 'is_allowed_path' as the mock files aren't allowed @@ -319,6 +324,7 @@ async def test_setup_platform_with_name(hass, mock_healthybox): valid_config_named[ip.DOMAIN][ip.CONF_SOURCE][ip.CONF_NAME] = MOCK_NAME await async_setup_component(hass, ip.DOMAIN, valid_config_named) + await hass.async_block_till_done() assert hass.states.get(named_entity_id) state = hass.states.get(named_entity_id) assert state.attributes.get(CONF_FRIENDLY_NAME) == MOCK_NAME diff --git a/tests/components/fido/test_sensor.py b/tests/components/fido/test_sensor.py index b57232d25ad..c96e6d0ab58 100644 --- a/tests/components/fido/test_sensor.py +++ b/tests/components/fido/test_sensor.py @@ -76,6 +76,7 @@ async def test_fido_sensor(loop, hass): } with assert_setup_component(1): await async_setup_component(hass, "sensor", config) + await hass.async_block_till_done() state = hass.states.get("sensor.fido_1112223344_balance") assert state.state == "160.12" assert state.attributes.get("number") == "1112223344" diff --git a/tests/components/filesize/test_sensor.py b/tests/components/filesize/test_sensor.py index b2552f58c6a..11709296375 100644 --- a/tests/components/filesize/test_sensor.py +++ b/tests/components/filesize/test_sensor.py @@ -42,6 +42,7 @@ class TestFileSensor(unittest.TestCase): create_file(TEST_FILE) config = {"sensor": {"platform": "filesize", CONF_FILE_PATHS: [TEST_FILE]}} assert setup_component(self.hass, "sensor", config) + self.hass.block_till_done() assert len(self.hass.states.entity_ids()) == 1 state = self.hass.states.get("sensor.mock_file_test_filesize_txt") assert state.state == "0.0" diff --git a/tests/components/filter/test_sensor.py b/tests/components/filter/test_sensor.py index 238cb366f73..f6eae30c653 100644 --- a/tests/components/filter/test_sensor.py +++ b/tests/components/filter/test_sensor.py @@ -59,6 +59,7 @@ class TestFilterSensor(unittest.TestCase): } with assert_setup_component(0): assert setup_component(self.hass, "sensor", config) + self.hass.block_till_done() def test_chain(self): """Test if filter chaining works.""" @@ -77,6 +78,7 @@ class TestFilterSensor(unittest.TestCase): with assert_setup_component(1, "sensor"): assert setup_component(self.hass, "sensor", config) + self.hass.block_till_done() for value in self.values: self.hass.states.set(config["sensor"]["entity_id"], value.state) @@ -128,6 +130,7 @@ class TestFilterSensor(unittest.TestCase): ): with assert_setup_component(1, "sensor"): assert setup_component(self.hass, "sensor", config) + self.hass.block_till_done() for value in self.values: self.hass.states.set(config["sensor"]["entity_id"], value.state) @@ -176,6 +179,7 @@ class TestFilterSensor(unittest.TestCase): ): with assert_setup_component(1, "sensor"): assert setup_component(self.hass, "sensor", config) + self.hass.block_till_done() self.hass.block_till_done() state = self.hass.states.get("sensor.test") diff --git a/tests/components/flux/test_switch.py b/tests/components/flux/test_switch.py index ed16e94a283..0eada7da667 100644 --- a/tests/components/flux/test_switch.py +++ b/tests/components/flux/test_switch.py @@ -36,7 +36,7 @@ async def test_valid_config(hass): } }, ) - + await hass.async_block_till_done() state = hass.states.get("switch.flux") assert state assert state.state == "off" @@ -57,6 +57,7 @@ async def test_restore_state_last_on(hass): } }, ) + await hass.async_block_till_done() state = hass.states.get("switch.flux") assert state @@ -78,6 +79,7 @@ async def test_restore_state_last_off(hass): } }, ) + await hass.async_block_till_done() state = hass.states.get("switch.flux") assert state @@ -129,6 +131,7 @@ async def test_flux_when_switch_is_off(hass): assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} ) + await hass.async_block_till_done() ent1 = platform.ENTITIES[0] @@ -178,6 +181,7 @@ async def test_flux_before_sunrise(hass): assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} ) + await hass.async_block_till_done() ent1 = platform.ENTITIES[0] @@ -231,6 +235,7 @@ async def test_flux_before_sunrise_known_location(hass): assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} ) + await hass.async_block_till_done() ent1 = platform.ENTITIES[0] @@ -284,6 +289,7 @@ async def test_flux_after_sunrise_before_sunset(hass): assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} ) + await hass.async_block_till_done() ent1 = platform.ENTITIES[0] @@ -337,6 +343,7 @@ async def test_flux_after_sunset_before_stop(hass): assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} ) + await hass.async_block_till_done() ent1 = platform.ENTITIES[0] @@ -391,6 +398,7 @@ async def test_flux_after_stop_before_sunrise(hass): assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} ) + await hass.async_block_till_done() ent1 = platform.ENTITIES[0] @@ -444,6 +452,7 @@ async def test_flux_with_custom_start_stop_times(hass): assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} ) + await hass.async_block_till_done() ent1 = platform.ENTITIES[0] @@ -501,6 +510,7 @@ async def test_flux_before_sunrise_stop_next_day(hass): assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} ) + await hass.async_block_till_done() ent1 = platform.ENTITIES[0] @@ -559,6 +569,7 @@ async def test_flux_after_sunrise_before_sunset_stop_next_day(hass): assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} ) + await hass.async_block_till_done() ent1 = platform.ENTITIES[0] @@ -617,6 +628,7 @@ async def test_flux_after_sunset_before_midnight_stop_next_day(hass, x): assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} ) + await hass.async_block_till_done() ent1 = platform.ENTITIES[0] @@ -674,6 +686,7 @@ async def test_flux_after_sunset_after_midnight_stop_next_day(hass): assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} ) + await hass.async_block_till_done() ent1 = platform.ENTITIES[0] @@ -731,6 +744,7 @@ async def test_flux_after_stop_before_sunrise_stop_next_day(hass): assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} ) + await hass.async_block_till_done() ent1 = platform.ENTITIES[0] @@ -785,6 +799,7 @@ async def test_flux_with_custom_colortemps(hass): assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} ) + await hass.async_block_till_done() ent1 = platform.ENTITIES[0] @@ -841,6 +856,7 @@ async def test_flux_with_custom_brightness(hass): assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} ) + await hass.async_block_till_done() ent1 = platform.ENTITIES[0] @@ -895,6 +911,7 @@ async def test_flux_with_multiple_lights(hass): assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} ) + await hass.async_block_till_done() ent1, ent2, ent3 = platform.ENTITIES @@ -972,6 +989,7 @@ async def test_flux_with_mired(hass): assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} ) + await hass.async_block_till_done() ent1 = platform.ENTITIES[0] @@ -1023,6 +1041,7 @@ async def test_flux_with_rgb(hass): assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} ) + await hass.async_block_till_done() ent1 = platform.ENTITIES[0] diff --git a/tests/components/folder/test_sensor.py b/tests/components/folder/test_sensor.py index 1c465c6a864..9578eadc915 100644 --- a/tests/components/folder/test_sensor.py +++ b/tests/components/folder/test_sensor.py @@ -48,6 +48,7 @@ class TestFolderSensor(unittest.TestCase): create_file(TEST_FILE) config = {"sensor": {"platform": "folder", CONF_FOLDER_PATHS: TEST_DIR}} assert setup_component(self.hass, "sensor", config) + self.hass.block_till_done() assert len(self.hass.states.entity_ids()) == 1 state = self.hass.states.get("sensor.test_folder") assert state.state == "0.0" diff --git a/tests/components/foobot/test_sensor.py b/tests/components/foobot/test_sensor.py index 0a145c88479..d2f55fbda16 100644 --- a/tests/components/foobot/test_sensor.py +++ b/tests/components/foobot/test_sensor.py @@ -39,6 +39,7 @@ async def test_default_setup(hass, aioclient_mock): re.compile("api.foobot.io/v2/device/.*"), text=load_fixture("foobot_data.json") ) assert await async_setup_component(hass, sensor.DOMAIN, {"sensor": VALID_CONFIG}) + await hass.async_block_till_done() metrics = { "co2": ["1232.0", CONCENTRATION_PARTS_PER_MILLION], diff --git a/tests/components/gdacs/test_geo_location.py b/tests/components/gdacs/test_geo_location.py index 2162340154f..ccffd77a658 100644 --- a/tests/components/gdacs/test_geo_location.py +++ b/tests/components/gdacs/test_geo_location.py @@ -91,6 +91,7 @@ async def test_setup(hass): ) 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, gdacs.DOMAIN, CONFIG) + await hass.async_block_till_done() # Artificially trigger update and collect events. hass.bus.async_fire(EVENT_HOMEASSISTANT_START) await hass.async_block_till_done() @@ -213,6 +214,7 @@ async def test_setup_imperial(hass): ): mock_feed_update.return_value = "OK", [mock_entry_1] assert await async_setup_component(hass, gdacs.DOMAIN, CONFIG) + await hass.async_block_till_done() # Artificially trigger update and collect events. hass.bus.async_fire(EVENT_HOMEASSISTANT_START) await hass.async_block_till_done() diff --git a/tests/components/generic_thermostat/test_climate.py b/tests/components/generic_thermostat/test_climate.py index f0b29539ed5..5e144c3684a 100644 --- a/tests/components/generic_thermostat/test_climate.py +++ b/tests/components/generic_thermostat/test_climate.py @@ -76,12 +76,11 @@ async def test_valid_conf(hass): @pytest.fixture -def setup_comp_1(hass): +async def setup_comp_1(hass): """Initialize components.""" hass.config.units = METRIC_SYSTEM - assert hass.loop.run_until_complete( - async_setup_component(hass, "homeassistant", {}) - ) + assert await async_setup_component(hass, "homeassistant", {}) + await hass.async_block_till_done() async def test_heater_input_boolean(hass, setup_comp_1): @@ -104,6 +103,7 @@ async def test_heater_input_boolean(hass, setup_comp_1): } }, ) + await hass.async_block_till_done() assert STATE_OFF == hass.states.get(heater_switch).state @@ -122,6 +122,7 @@ async def test_heater_switch(hass, setup_comp_1): assert await async_setup_component( hass, switch.DOMAIN, {"switch": {"platform": "test"}} ) + await hass.async_block_till_done() heater_switch = switch_1.entity_id assert await async_setup_component( @@ -154,27 +155,26 @@ def _setup_sensor(hass, temp): @pytest.fixture -def setup_comp_2(hass): +async def setup_comp_2(hass): """Initialize components.""" hass.config.units = METRIC_SYSTEM - assert hass.loop.run_until_complete( - async_setup_component( - hass, - DOMAIN, - { - "climate": { - "platform": "generic_thermostat", - "name": "test", - "cold_tolerance": 2, - "hot_tolerance": 4, - "heater": ENT_SWITCH, - "target_sensor": ENT_SENSOR, - "away_temp": 16, - "initial_hvac_mode": HVAC_MODE_HEAT, - } - }, - ) + assert await async_setup_component( + hass, + DOMAIN, + { + "climate": { + "platform": "generic_thermostat", + "name": "test", + "cold_tolerance": 2, + "hot_tolerance": 4, + "heater": ENT_SWITCH, + "target_sensor": ENT_SENSOR, + "away_temp": 16, + "initial_hvac_mode": HVAC_MODE_HEAT, + } + }, ) + await hass.async_block_till_done() async def test_setup_defaults_to_unknown(hass): @@ -195,6 +195,7 @@ async def test_setup_defaults_to_unknown(hass): } }, ) + await hass.async_block_till_done() assert HVAC_MODE_OFF == hass.states.get(ENTITY).state @@ -288,6 +289,7 @@ async def test_sensor_unknown(hass): } }, ) + await hass.async_block_till_done() state = hass.states.get("climate.unknown") assert state.attributes.get("current_temperature") is None @@ -307,6 +309,7 @@ async def test_sensor_unavailable(hass): } }, ) + await hass.async_block_till_done() state = hass.states.get("climate.unavailable") assert state.attributes.get("current_temperature") is None @@ -438,28 +441,27 @@ def _setup_switch(hass, is_on): @pytest.fixture -def setup_comp_3(hass): +async def setup_comp_3(hass): """Initialize components.""" hass.config.temperature_unit = TEMP_CELSIUS - assert hass.loop.run_until_complete( - async_setup_component( - hass, - DOMAIN, - { - "climate": { - "platform": "generic_thermostat", - "name": "test", - "cold_tolerance": 2, - "hot_tolerance": 4, - "away_temp": 30, - "heater": ENT_SWITCH, - "target_sensor": ENT_SENSOR, - "ac_mode": True, - "initial_hvac_mode": HVAC_MODE_COOL, - } - }, - ) + assert await async_setup_component( + hass, + DOMAIN, + { + "climate": { + "platform": "generic_thermostat", + "name": "test", + "cold_tolerance": 2, + "hot_tolerance": 4, + "away_temp": 30, + "heater": ENT_SWITCH, + "target_sensor": ENT_SENSOR, + "ac_mode": True, + "initial_hvac_mode": HVAC_MODE_COOL, + } + }, ) + await hass.async_block_till_done() async def test_set_target_temp_ac_off(hass, setup_comp_3): @@ -584,28 +586,27 @@ async def test_no_state_change_when_operation_mode_off_2(hass, setup_comp_3): @pytest.fixture -def setup_comp_4(hass): +async def setup_comp_4(hass): """Initialize components.""" hass.config.temperature_unit = TEMP_CELSIUS - assert hass.loop.run_until_complete( - async_setup_component( - hass, - DOMAIN, - { - "climate": { - "platform": "generic_thermostat", - "name": "test", - "cold_tolerance": 0.3, - "hot_tolerance": 0.3, - "heater": ENT_SWITCH, - "target_sensor": ENT_SENSOR, - "ac_mode": True, - "min_cycle_duration": datetime.timedelta(minutes=10), - "initial_hvac_mode": HVAC_MODE_COOL, - } - }, - ) + assert await async_setup_component( + hass, + DOMAIN, + { + "climate": { + "platform": "generic_thermostat", + "name": "test", + "cold_tolerance": 0.3, + "hot_tolerance": 0.3, + "heater": ENT_SWITCH, + "target_sensor": ENT_SENSOR, + "ac_mode": True, + "min_cycle_duration": datetime.timedelta(minutes=10), + "initial_hvac_mode": HVAC_MODE_COOL, + } + }, ) + await hass.async_block_till_done() async def test_temp_change_ac_trigger_on_not_long_enough(hass, setup_comp_4): @@ -695,28 +696,27 @@ async def test_mode_change_ac_trigger_on_not_long_enough(hass, setup_comp_4): @pytest.fixture -def setup_comp_5(hass): +async def setup_comp_5(hass): """Initialize components.""" hass.config.temperature_unit = TEMP_CELSIUS - assert hass.loop.run_until_complete( - async_setup_component( - hass, - DOMAIN, - { - "climate": { - "platform": "generic_thermostat", - "name": "test", - "cold_tolerance": 0.3, - "hot_tolerance": 0.3, - "heater": ENT_SWITCH, - "target_sensor": ENT_SENSOR, - "ac_mode": True, - "min_cycle_duration": datetime.timedelta(minutes=10), - "initial_hvac_mode": HVAC_MODE_COOL, - } - }, - ) + assert await async_setup_component( + hass, + DOMAIN, + { + "climate": { + "platform": "generic_thermostat", + "name": "test", + "cold_tolerance": 0.3, + "hot_tolerance": 0.3, + "heater": ENT_SWITCH, + "target_sensor": ENT_SENSOR, + "ac_mode": True, + "min_cycle_duration": datetime.timedelta(minutes=10), + "initial_hvac_mode": HVAC_MODE_COOL, + } + }, ) + await hass.async_block_till_done() async def test_temp_change_ac_trigger_on_not_long_enough_2(hass, setup_comp_5): @@ -806,27 +806,26 @@ async def test_mode_change_ac_trigger_on_not_long_enough_2(hass, setup_comp_5): @pytest.fixture -def setup_comp_6(hass): +async def setup_comp_6(hass): """Initialize components.""" hass.config.temperature_unit = TEMP_CELSIUS - assert hass.loop.run_until_complete( - async_setup_component( - hass, - DOMAIN, - { - "climate": { - "platform": "generic_thermostat", - "name": "test", - "cold_tolerance": 0.3, - "hot_tolerance": 0.3, - "heater": ENT_SWITCH, - "target_sensor": ENT_SENSOR, - "min_cycle_duration": datetime.timedelta(minutes=10), - "initial_hvac_mode": HVAC_MODE_HEAT, - } - }, - ) + assert await async_setup_component( + hass, + DOMAIN, + { + "climate": { + "platform": "generic_thermostat", + "name": "test", + "cold_tolerance": 0.3, + "hot_tolerance": 0.3, + "heater": ENT_SWITCH, + "target_sensor": ENT_SENSOR, + "min_cycle_duration": datetime.timedelta(minutes=10), + "initial_hvac_mode": HVAC_MODE_HEAT, + } + }, ) + await hass.async_block_till_done() async def test_temp_change_heater_trigger_off_not_long_enough(hass, setup_comp_6): @@ -916,31 +915,31 @@ async def test_mode_change_heater_trigger_on_not_long_enough(hass, setup_comp_6) @pytest.fixture -def setup_comp_7(hass): +async def setup_comp_7(hass): """Initialize components.""" hass.config.temperature_unit = TEMP_CELSIUS - assert hass.loop.run_until_complete( - async_setup_component( - hass, - DOMAIN, - { - "climate": { - "platform": "generic_thermostat", - "name": "test", - "cold_tolerance": 0.3, - "hot_tolerance": 0.3, - "heater": ENT_SWITCH, - "target_temp": 25, - "target_sensor": ENT_SENSOR, - "ac_mode": True, - "min_cycle_duration": datetime.timedelta(minutes=15), - "keep_alive": datetime.timedelta(minutes=10), - "initial_hvac_mode": HVAC_MODE_COOL, - } - }, - ) + assert await async_setup_component( + hass, + DOMAIN, + { + "climate": { + "platform": "generic_thermostat", + "name": "test", + "cold_tolerance": 0.3, + "hot_tolerance": 0.3, + "heater": ENT_SWITCH, + "target_temp": 25, + "target_sensor": ENT_SENSOR, + "ac_mode": True, + "min_cycle_duration": datetime.timedelta(minutes=15), + "keep_alive": datetime.timedelta(minutes=10), + "initial_hvac_mode": HVAC_MODE_COOL, + } + }, ) + await hass.async_block_till_done() + async def test_temp_change_ac_trigger_on_long_enough_3(hass, setup_comp_7): """Test if turn on signal is sent at keep-alive intervals.""" @@ -994,29 +993,28 @@ def _send_time_changed(hass, now): @pytest.fixture -def setup_comp_8(hass): +async def setup_comp_8(hass): """Initialize components.""" hass.config.temperature_unit = TEMP_CELSIUS - assert hass.loop.run_until_complete( - async_setup_component( - hass, - DOMAIN, - { - "climate": { - "platform": "generic_thermostat", - "name": "test", - "cold_tolerance": 0.3, - "hot_tolerance": 0.3, - "target_temp": 25, - "heater": ENT_SWITCH, - "target_sensor": ENT_SENSOR, - "min_cycle_duration": datetime.timedelta(minutes=15), - "keep_alive": datetime.timedelta(minutes=10), - "initial_hvac_mode": HVAC_MODE_HEAT, - } - }, - ) + assert await async_setup_component( + hass, + DOMAIN, + { + "climate": { + "platform": "generic_thermostat", + "name": "test", + "cold_tolerance": 0.3, + "hot_tolerance": 0.3, + "target_temp": 25, + "heater": ENT_SWITCH, + "target_sensor": ENT_SENSOR, + "min_cycle_duration": datetime.timedelta(minutes=15), + "keep_alive": datetime.timedelta(minutes=10), + "initial_hvac_mode": HVAC_MODE_HEAT, + } + }, ) + await hass.async_block_till_done() async def test_temp_change_heater_trigger_on_long_enough_2(hass, setup_comp_8): @@ -1066,29 +1064,28 @@ async def test_temp_change_heater_trigger_off_long_enough_2(hass, setup_comp_8): @pytest.fixture -def setup_comp_9(hass): +async def setup_comp_9(hass): """Initialize components.""" hass.config.temperature_unit = TEMP_FAHRENHEIT - assert hass.loop.run_until_complete( - async_setup_component( - hass, - DOMAIN, - { - "climate": { - "platform": "generic_thermostat", - "name": "test", - "cold_tolerance": 0.3, - "hot_tolerance": 0.3, - "target_temp": 25, - "heater": ENT_SWITCH, - "target_sensor": ENT_SENSOR, - "min_cycle_duration": datetime.timedelta(minutes=15), - "keep_alive": datetime.timedelta(minutes=10), - "precision": 0.1, - } - }, - ) + assert await async_setup_component( + hass, + DOMAIN, + { + "climate": { + "platform": "generic_thermostat", + "name": "test", + "cold_tolerance": 0.3, + "hot_tolerance": 0.3, + "target_temp": 25, + "heater": ENT_SWITCH, + "target_sensor": ENT_SENSOR, + "min_cycle_duration": datetime.timedelta(minutes=15), + "keep_alive": datetime.timedelta(minutes=10), + "precision": 0.1, + } + }, ) + await hass.async_block_till_done() async def test_precision(hass, setup_comp_9): @@ -1116,6 +1113,7 @@ async def test_custom_setup_params(hass): }, ) assert result + await hass.async_block_till_done() state = hass.states.get(ENTITY) assert state.attributes.get("min_temp") == MIN_TEMP assert state.attributes.get("max_temp") == MAX_TEMP @@ -1150,7 +1148,7 @@ async def test_restore_state(hass): } }, ) - + await hass.async_block_till_done() state = hass.states.get("climate.test_thermostat") assert state.attributes[ATTR_TEMPERATURE] == 20 assert state.attributes[ATTR_PRESET_MODE] == PRESET_AWAY @@ -1188,7 +1186,7 @@ async def test_no_restore_state(hass): } }, ) - + await hass.async_block_till_done() state = hass.states.get("climate.test_thermostat") assert state.attributes[ATTR_TEMPERATURE] == 22 assert state.state == HVAC_MODE_OFF diff --git a/tests/components/geo_json_events/test_geo_location.py b/tests/components/geo_json_events/test_geo_location.py index d79fa3cb18d..f73eaa11e79 100644 --- a/tests/components/geo_json_events/test_geo_location.py +++ b/tests/components/geo_json_events/test_geo_location.py @@ -73,6 +73,7 @@ async def test_setup(hass): ) with assert_setup_component(1, geo_location.DOMAIN): assert await async_setup_component(hass, geo_location.DOMAIN, CONFIG) + await hass.async_block_till_done() # Artificially trigger update. hass.bus.async_fire(EVENT_HOMEASSISTANT_START) # Collect events. @@ -162,6 +163,7 @@ async def test_setup_with_custom_location(hass): assert await async_setup_component( hass, geo_location.DOMAIN, CONFIG_WITH_CUSTOM_LOCATION ) + await hass.async_block_till_done() # Artificially trigger update. hass.bus.async_fire(EVENT_HOMEASSISTANT_START) @@ -198,6 +200,7 @@ async def test_setup_race_condition(hass): ) as mock_feed: with assert_setup_component(1, geo_location.DOMAIN): assert await async_setup_component(hass, geo_location.DOMAIN, CONFIG) + await hass.async_block_till_done() mock_feed.return_value.update.return_value = "OK", [mock_entry_1] diff --git a/tests/components/geonetnz_quakes/test_geo_location.py b/tests/components/geonetnz_quakes/test_geo_location.py index 89644445ef1..2622cd100b3 100644 --- a/tests/components/geonetnz_quakes/test_geo_location.py +++ b/tests/components/geonetnz_quakes/test_geo_location.py @@ -67,6 +67,7 @@ async def test_setup(hass): ) 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_quakes.DOMAIN, CONFIG) + await hass.async_block_till_done() # Artificially trigger update and collect events. hass.bus.async_fire(EVENT_HOMEASSISTANT_START) await hass.async_block_till_done() @@ -172,6 +173,7 @@ async def test_setup_imperial(hass): ): mock_feed_update.return_value = "OK", [mock_entry_1] assert await async_setup_component(hass, geonetnz_quakes.DOMAIN, CONFIG) + await hass.async_block_till_done() # Artificially trigger update and collect events. hass.bus.async_fire(EVENT_HOMEASSISTANT_START) 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 c9fc159bdf5..cce8f5a6194 100644 --- a/tests/components/google_assistant/test_smart_home.py +++ b/tests/components/google_assistant/test_smart_home.py @@ -289,6 +289,7 @@ async def test_query_message(hass): async def test_execute(hass): """Test an execute command.""" await async_setup_component(hass, "light", {"light": {"platform": "demo"}}) + await hass.async_block_till_done() await hass.services.async_call( "light", "turn_off", {"entity_id": "light.ceiling_lights"}, blocking=True diff --git a/tests/components/group/test_cover.py b/tests/components/group/test_cover.py index 42345536120..ac338dd8a80 100644 --- a/tests/components/group/test_cover.py +++ b/tests/components/group/test_cover.py @@ -57,6 +57,7 @@ async def setup_comp(hass): """Set up group cover component.""" with assert_setup_component(2, DOMAIN): await async_setup_component(hass, DOMAIN, CONFIG) + await hass.async_block_till_done() async def test_attributes(hass): @@ -70,6 +71,7 @@ async def test_attributes(hass): with assert_setup_component(1, DOMAIN): await async_setup_component(hass, DOMAIN, config) + await hass.async_block_till_done() state = hass.states.get(COVER_GROUP) assert state.state == STATE_CLOSED diff --git a/tests/components/hddtemp/test_sensor.py b/tests/components/hddtemp/test_sensor.py index 4583079829a..84315afb476 100644 --- a/tests/components/hddtemp/test_sensor.py +++ b/tests/components/hddtemp/test_sensor.py @@ -96,6 +96,7 @@ class TestHDDTempSensor(unittest.TestCase): def test_hddtemp_min_config(self): """Test minimal hddtemp configuration.""" assert setup_component(self.hass, "sensor", VALID_CONFIG_MINIMAL) + self.hass.block_till_done() entity = self.hass.states.all()[0].entity_id state = self.hass.states.get(entity) @@ -118,6 +119,7 @@ class TestHDDTempSensor(unittest.TestCase): def test_hddtemp_rename_config(self): """Test hddtemp configuration with different name.""" assert setup_component(self.hass, "sensor", VALID_CONFIG_NAME) + self.hass.block_till_done() entity = self.hass.states.all()[0].entity_id state = self.hass.states.get(entity) @@ -130,6 +132,7 @@ class TestHDDTempSensor(unittest.TestCase): def test_hddtemp_one_disk(self): """Test hddtemp one disk configuration.""" assert setup_component(self.hass, "sensor", VALID_CONFIG_ONE_DISK) + self.hass.block_till_done() state = self.hass.states.get("sensor.hd_temperature_dev_sdd1") @@ -151,6 +154,7 @@ class TestHDDTempSensor(unittest.TestCase): def test_hddtemp_wrong_disk(self): """Test hddtemp wrong disk configuration.""" assert setup_component(self.hass, "sensor", VALID_CONFIG_WRONG_DISK) + self.hass.block_till_done() assert len(self.hass.states.all()) == 1 state = self.hass.states.get("sensor.hd_temperature_dev_sdx1") @@ -160,6 +164,7 @@ class TestHDDTempSensor(unittest.TestCase): def test_hddtemp_multiple_disks(self): """Test hddtemp multiple disk configuration.""" assert setup_component(self.hass, "sensor", VALID_CONFIG_MULTIPLE_DISKS) + self.hass.block_till_done() for sensor in [ "sensor.hd_temperature_dev_sda1", @@ -187,10 +192,12 @@ class TestHDDTempSensor(unittest.TestCase): def test_hddtemp_host_refused(self): """Test hddtemp if host unreachable.""" assert setup_component(self.hass, "sensor", VALID_CONFIG_HOST) + self.hass.block_till_done() assert len(self.hass.states.all()) == 0 @patch("telnetlib.Telnet", new=TelnetMock) def test_hddtemp_host_unreachable(self): """Test hddtemp if host unreachable.""" assert setup_component(self.hass, "sensor", VALID_CONFIG_HOST_UNREACHABLE) + self.hass.block_till_done() assert len(self.hass.states.all()) == 0 diff --git a/tests/components/here_travel_time/test_sensor.py b/tests/components/here_travel_time/test_sensor.py index fd2922e94fe..c6240749aa3 100644 --- a/tests/components/here_travel_time/test_sensor.py +++ b/tests/components/here_travel_time/test_sensor.py @@ -175,6 +175,7 @@ async def test_car(hass, requests_mock_car_disabled_response): } } assert await async_setup_component(hass, DOMAIN, config) + await hass.async_block_till_done() hass.bus.async_fire(EVENT_HOMEASSISTANT_START) await hass.async_block_till_done() @@ -236,6 +237,7 @@ async def test_traffic_mode_enabled(hass, requests_mock_credentials_check): } } assert await async_setup_component(hass, DOMAIN, config) + await hass.async_block_till_done() hass.bus.async_fire(EVENT_HOMEASSISTANT_START) await hass.async_block_till_done() @@ -262,6 +264,7 @@ async def test_imperial(hass, requests_mock_car_disabled_response): } } assert await async_setup_component(hass, DOMAIN, config) + await hass.async_block_till_done() hass.bus.async_fire(EVENT_HOMEASSISTANT_START) await hass.async_block_till_done() @@ -293,6 +296,7 @@ async def test_route_mode_shortest(hass, requests_mock_credentials_check): } } assert await async_setup_component(hass, DOMAIN, config) + await hass.async_block_till_done() hass.bus.async_fire(EVENT_HOMEASSISTANT_START) await hass.async_block_till_done() @@ -324,6 +328,7 @@ async def test_route_mode_fastest(hass, requests_mock_credentials_check): } } assert await async_setup_component(hass, DOMAIN, config) + await hass.async_block_till_done() hass.bus.async_fire(EVENT_HOMEASSISTANT_START) await hass.async_block_till_done() @@ -347,6 +352,7 @@ async def test_truck(hass, requests_mock_truck_response): } } assert await async_setup_component(hass, DOMAIN, config) + await hass.async_block_till_done() hass.bus.async_fire(EVENT_HOMEASSISTANT_START) await hass.async_block_till_done() @@ -378,6 +384,7 @@ async def test_public_transport(hass, requests_mock_credentials_check): } } assert await async_setup_component(hass, DOMAIN, config) + await hass.async_block_till_done() hass.bus.async_fire(EVENT_HOMEASSISTANT_START) await hass.async_block_till_done() @@ -428,6 +435,7 @@ async def test_public_transport_time_table(hass, requests_mock_credentials_check } } assert await async_setup_component(hass, DOMAIN, config) + await hass.async_block_till_done() hass.bus.async_fire(EVENT_HOMEASSISTANT_START) await hass.async_block_till_done() @@ -478,6 +486,7 @@ async def test_pedestrian(hass, requests_mock_credentials_check): } assert await async_setup_component(hass, DOMAIN, config) + await hass.async_block_till_done() hass.bus.async_fire(EVENT_HOMEASSISTANT_START) await hass.async_block_till_done() @@ -529,6 +538,7 @@ async def test_bicycle(hass, requests_mock_credentials_check): } } assert await async_setup_component(hass, DOMAIN, config) + await hass.async_block_till_done() hass.bus.async_fire(EVENT_HOMEASSISTANT_START) await hass.async_block_till_done() @@ -592,6 +602,7 @@ async def test_location_zone(hass, requests_mock_truck_response): } assert await async_setup_component(hass, "zone", zone_config) assert await async_setup_component(hass, DOMAIN, config) + await hass.async_block_till_done() hass.bus.async_fire(EVENT_HOMEASSISTANT_START) await hass.async_block_till_done() @@ -631,6 +642,7 @@ async def test_location_sensor(hass, requests_mock_truck_response): } } assert await async_setup_component(hass, DOMAIN, config) + await hass.async_block_till_done() hass.bus.async_fire(EVENT_HOMEASSISTANT_START) await hass.async_block_till_done() @@ -679,6 +691,7 @@ async def test_location_person(hass, requests_mock_truck_response): } } assert await async_setup_component(hass, DOMAIN, config) + await hass.async_block_till_done() hass.bus.async_fire(EVENT_HOMEASSISTANT_START) await hass.async_block_till_done() @@ -727,6 +740,7 @@ async def test_location_device_tracker(hass, requests_mock_truck_response): } } assert await async_setup_component(hass, DOMAIN, config) + await hass.async_block_till_done() hass.bus.async_fire(EVENT_HOMEASSISTANT_START) await hass.async_block_till_done() @@ -761,6 +775,7 @@ async def test_location_device_tracker_added_after_update( } } assert await async_setup_component(hass, DOMAIN, config) + await hass.async_block_till_done() hass.bus.async_fire(EVENT_HOMEASSISTANT_START) await hass.async_block_till_done() @@ -829,6 +844,7 @@ async def test_location_device_tracker_in_zone( } } assert await async_setup_component(hass, DOMAIN, config) + await hass.async_block_till_done() hass.bus.async_fire(EVENT_HOMEASSISTANT_START) await hass.async_block_till_done() @@ -862,6 +878,7 @@ async def test_route_not_found(hass, requests_mock_credentials_check, caplog): } } assert await async_setup_component(hass, DOMAIN, config) + await hass.async_block_till_done() hass.bus.async_fire(EVENT_HOMEASSISTANT_START) await hass.async_block_till_done() @@ -885,7 +902,8 @@ async def test_pattern_origin(hass, caplog): } } assert await async_setup_component(hass, DOMAIN, config) - assert len(caplog.records) == 1 + await hass.async_block_till_done() + assert len(caplog.records) == 2 assert "invalid latitude" in caplog.text @@ -904,7 +922,8 @@ async def test_pattern_destination(hass, caplog): } } assert await async_setup_component(hass, DOMAIN, config) - assert len(caplog.records) == 1 + await hass.async_block_till_done() + assert len(caplog.records) == 2 assert "invalid latitude" in caplog.text @@ -935,6 +954,7 @@ async def test_invalid_credentials(hass, requests_mock, caplog): } } assert await async_setup_component(hass, DOMAIN, config) + await hass.async_block_till_done() assert len(caplog.records) == 1 assert "Invalid credentials" in caplog.text @@ -964,6 +984,7 @@ async def test_attribution(hass, requests_mock_credentials_check): } } assert await async_setup_component(hass, DOMAIN, config) + await hass.async_block_till_done() hass.bus.async_fire(EVENT_HOMEASSISTANT_START) await hass.async_block_till_done() @@ -992,6 +1013,7 @@ async def test_pattern_entity_state(hass, requests_mock_truck_response, caplog): } } assert await async_setup_component(hass, DOMAIN, config) + await hass.async_block_till_done() hass.bus.async_fire(EVENT_HOMEASSISTANT_START) await hass.async_block_till_done() @@ -1018,6 +1040,7 @@ async def test_pattern_entity_state_with_space(hass, requests_mock_truck_respons } } assert await async_setup_component(hass, DOMAIN, config) + await hass.async_block_till_done() async def test_delayed_update(hass, requests_mock_truck_response, caplog): @@ -1044,6 +1067,7 @@ async def test_delayed_update(hass, requests_mock_truck_response, caplog): } } assert await async_setup_component(hass, DOMAIN, config) + await hass.async_block_till_done() assert await async_setup_component(hass, "sensor", sensor_config) hass.states.async_set( "sensor.origin", ",".join([TRUCK_ORIGIN_LATITUDE, TRUCK_ORIGIN_LONGITUDE]) @@ -1084,6 +1108,7 @@ async def test_arrival(hass, requests_mock_credentials_check): } } assert await async_setup_component(hass, DOMAIN, config) + await hass.async_block_till_done() hass.bus.async_fire(EVENT_HOMEASSISTANT_START) await hass.async_block_till_done() @@ -1121,6 +1146,7 @@ async def test_departure(hass, requests_mock_credentials_check): } } assert await async_setup_component(hass, DOMAIN, config) + await hass.async_block_till_done() hass.bus.async_fire(EVENT_HOMEASSISTANT_START) await hass.async_block_till_done() @@ -1147,7 +1173,8 @@ async def test_arrival_only_allowed_for_timetable(hass, caplog): } } assert await async_setup_component(hass, DOMAIN, config) - assert len(caplog.records) == 1 + await hass.async_block_till_done() + assert len(caplog.records) == 2 assert "[arrival] is an invalid option" in caplog.text @@ -1171,5 +1198,6 @@ async def test_exclusive_arrival_and_departure(hass, caplog): } } assert await async_setup_component(hass, DOMAIN, config) - assert len(caplog.records) == 1 + await hass.async_block_till_done() + assert len(caplog.records) == 2 assert "two or more values in the same group of exclusion" in caplog.text diff --git a/tests/components/history_stats/test_sensor.py b/tests/components/history_stats/test_sensor.py index d9f489d20b4..900af4988e2 100644 --- a/tests/components/history_stats/test_sensor.py +++ b/tests/components/history_stats/test_sensor.py @@ -45,6 +45,7 @@ class TestHistoryStatsSensor(unittest.TestCase): } assert setup_component(self.hass, "sensor", config) + self.hass.block_till_done() state = self.hass.states.get("sensor.test") assert state.state == STATE_UNKNOWN diff --git a/tests/components/homeassistant/test_scene.py b/tests/components/homeassistant/test_scene.py index 6234d425d8d..98afbd23e22 100644 --- a/tests/components/homeassistant/test_scene.py +++ b/tests/components/homeassistant/test_scene.py @@ -39,6 +39,7 @@ async def test_apply_service(hass): """Test the apply service.""" assert await async_setup_component(hass, "scene", {}) assert await async_setup_component(hass, "light", {"light": {"platform": "demo"}}) + await hass.async_block_till_done() assert await hass.services.async_call( "scene", "apply", {"entities": {"light.bed_light": "off"}}, blocking=True @@ -83,6 +84,7 @@ async def test_create_service(hass, caplog): "scene", {"scene": {"name": "hallo_2", "entities": {"light.kitchen": "on"}}}, ) + await hass.async_block_till_done() assert hass.states.get("scene.hallo") is None assert hass.states.get("scene.hallo_2") is not None @@ -155,6 +157,7 @@ async def test_create_service(hass, caplog): async def test_snapshot_service(hass, caplog): """Test the snapshot option.""" assert await async_setup_component(hass, "scene", {"scene": {}}) + await hass.async_block_till_done() hass.states.async_set("light.my_light", "on", {"hs_color": (345, 75)}) assert hass.states.get("scene.hallo") is None @@ -212,6 +215,7 @@ async def test_snapshot_service(hass, caplog): async def test_ensure_no_intersection(hass): """Test that entities and snapshot_entities do not overlap.""" assert await async_setup_component(hass, "scene", {"scene": {}}) + await hass.async_block_till_done() with pytest.raises(vol.MultipleInvalid) as ex: assert await hass.services.async_call( @@ -245,6 +249,7 @@ async def test_scenes_with_entity(hass): ] }, ) + await hass.async_block_till_done() assert sorted(ha_scene.scenes_with_entity(hass, "light.kitchen")) == [ "scene.scene_1", @@ -268,6 +273,7 @@ async def test_entities_in_scene(hass): ] }, ) + await hass.async_block_till_done() for scene_id, entities in ( ("scene.scene_1", ["light.kitchen"]), @@ -297,6 +303,7 @@ async def test_config(hass): ] }, ) + await hass.async_block_till_done() icon = hass.states.get("scene.scene_icon") assert icon is not None diff --git a/tests/components/homekit/test_type_cameras.py b/tests/components/homekit/test_type_cameras.py index 9de08b8fc22..78e27231d19 100644 --- a/tests/components/homekit/test_type_cameras.py +++ b/tests/components/homekit/test_type_cameras.py @@ -123,6 +123,7 @@ async def test_camera_stream_source_configured(hass, run_driver, events): await async_setup_component( hass, camera.DOMAIN, {camera.DOMAIN: {"platform": "demo"}} ) + await hass.async_block_till_done() entity_id = "camera.demo_camera" @@ -227,6 +228,7 @@ async def test_camera_stream_source_configured_with_failing_ffmpeg( await async_setup_component( hass, camera.DOMAIN, {camera.DOMAIN: {"platform": "demo"}} ) + await hass.async_block_till_done() entity_id = "camera.demo_camera" @@ -271,6 +273,7 @@ async def test_camera_stream_source_found(hass, run_driver, events): await async_setup_component( hass, camera.DOMAIN, {camera.DOMAIN: {"platform": "demo"}} ) + await hass.async_block_till_done() entity_id = "camera.demo_camera" @@ -313,6 +316,7 @@ async def test_camera_stream_source_fails(hass, run_driver, events): await async_setup_component( hass, camera.DOMAIN, {camera.DOMAIN: {"platform": "demo"}} ) + await hass.async_block_till_done() entity_id = "camera.demo_camera" @@ -368,6 +372,7 @@ async def test_camera_stream_source_configured_and_copy_codec(hass, run_driver, await async_setup_component( hass, camera.DOMAIN, {camera.DOMAIN: {"platform": "demo"}} ) + await hass.async_block_till_done() entity_id = "camera.demo_camera" @@ -436,6 +441,7 @@ async def test_camera_streaming_fails_after_starting_ffmpeg(hass, run_driver, ev await async_setup_component( hass, camera.DOMAIN, {camera.DOMAIN: {"platform": "demo"}} ) + await hass.async_block_till_done() entity_id = "camera.demo_camera" @@ -505,6 +511,7 @@ async def test_camera_with_linked_motion_sensor(hass, run_driver, events): await async_setup_component( hass, camera.DOMAIN, {camera.DOMAIN: {"platform": "demo"}} ) + await hass.async_block_till_done() motion_entity_id = "binary_sensor.motion" hass.states.async_set( @@ -564,6 +571,7 @@ async def test_camera_with_a_missing_linked_motion_sensor(hass, run_driver, even await async_setup_component( hass, camera.DOMAIN, {camera.DOMAIN: {"platform": "demo"}} ) + await hass.async_block_till_done() motion_entity_id = "binary_sensor.motion" entity_id = "camera.demo_camera" hass.states.async_set(entity_id, None) diff --git a/tests/components/ign_sismologia/test_geo_location.py b/tests/components/ign_sismologia/test_geo_location.py index 8b9852ea234..5695f26c5aa 100644 --- a/tests/components/ign_sismologia/test_geo_location.py +++ b/tests/components/ign_sismologia/test_geo_location.py @@ -103,6 +103,7 @@ async def test_setup(hass): ) with assert_setup_component(1, geo_location.DOMAIN): assert await async_setup_component(hass, geo_location.DOMAIN, CONFIG) + await hass.async_block_till_done() # Artificially trigger update. hass.bus.async_fire(EVENT_HOMEASSISTANT_START) # Collect events. @@ -207,6 +208,7 @@ async def test_setup_with_custom_location(hass): assert await async_setup_component( hass, geo_location.DOMAIN, CONFIG_WITH_CUSTOM_LOCATION ) + await hass.async_block_till_done() # Artificially trigger update. hass.bus.async_fire(EVENT_HOMEASSISTANT_START) diff --git a/tests/components/image_processing/test_init.py b/tests/components/image_processing/test_init.py index cc708db75db..9763dbf9415 100644 --- a/tests/components/image_processing/test_init.py +++ b/tests/components/image_processing/test_init.py @@ -59,6 +59,7 @@ class TestImageProcessing: config = {ip.DOMAIN: {"platform": "test"}, "camera": {"platform": "demo"}} setup_component(self.hass, ip.DOMAIN, config) + self.hass.block_till_done() state = self.hass.states.get("camera.demo_camera") self.url = f"{self.hass.config.api.base_url}{state.attributes.get(ATTR_ENTITY_PICTURE)}" @@ -113,6 +114,7 @@ class TestImageProcessingAlpr: new_callable=PropertyMock(return_value=False), ): setup_component(self.hass, ip.DOMAIN, config) + self.hass.block_till_done() state = self.hass.states.get("camera.demo_camera") self.url = f"{self.hass.config.api.base_url}{state.attributes.get(ATTR_ENTITY_PICTURE)}" @@ -218,6 +220,7 @@ class TestImageProcessingFace: new_callable=PropertyMock(return_value=False), ): setup_component(self.hass, ip.DOMAIN, config) + self.hass.block_till_done() state = self.hass.states.get("camera.demo_camera") self.url = f"{self.hass.config.api.base_url}{state.attributes.get(ATTR_ENTITY_PICTURE)}" diff --git a/tests/components/ipma/test_weather.py b/tests/components/ipma/test_weather.py index e7542070d2c..c7a8bbda63f 100644 --- a/tests/components/ipma/test_weather.py +++ b/tests/components/ipma/test_weather.py @@ -135,7 +135,7 @@ async def test_setup_configuration(hass): weather.DOMAIN, {"weather": {"name": "HomeTown", "platform": "ipma", "mode": "hourly"}}, ) - await hass.async_block_till_done() + await hass.async_block_till_done() state = hass.states.get("weather.hometown") assert state.state == "rainy" @@ -182,7 +182,7 @@ async def test_daily_forecast(hass): weather.DOMAIN, {"weather": {"name": "HomeTown", "platform": "ipma", "mode": "daily"}}, ) - await hass.async_block_till_done() + await hass.async_block_till_done() state = hass.states.get("weather.hometown") assert state.state == "rainy" @@ -208,7 +208,7 @@ async def test_hourly_forecast(hass): weather.DOMAIN, {"weather": {"name": "HomeTown", "platform": "ipma", "mode": "hourly"}}, ) - await hass.async_block_till_done() + await hass.async_block_till_done() state = hass.states.get("weather.hometown") assert state.state == "rainy" diff --git a/tests/components/lastfm/test_sensor.py b/tests/components/lastfm/test_sensor.py index 7ae70a6f152..82c789415bf 100644 --- a/tests/components/lastfm/test_sensor.py +++ b/tests/components/lastfm/test_sensor.py @@ -54,6 +54,7 @@ async def test_update_not_playing(hass, lastfm_network): sensor.DOMAIN, {"sensor": {"platform": "lastfm", "api_key": "secret-key", "users": ["test"]}}, ) + await hass.async_block_till_done() entity_id = "sensor.test" @@ -74,6 +75,7 @@ async def test_update_playing(hass, lastfm_network): sensor.DOMAIN, {"sensor": {"platform": "lastfm", "api_key": "secret-key", "users": ["test"]}}, ) + await hass.async_block_till_done() entity_id = "sensor.test" diff --git a/tests/components/light/test_device_action.py b/tests/components/light/test_device_action.py index 116aff4ee78..1f3f0c22bd0 100644 --- a/tests/components/light/test_device_action.py +++ b/tests/components/light/test_device_action.py @@ -203,6 +203,7 @@ async def test_action(hass, calls): platform.init() assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) + await hass.async_block_till_done() ent1, ent2, ent3 = platform.ENTITIES diff --git a/tests/components/light/test_device_condition.py b/tests/components/light/test_device_condition.py index 998ef7851c1..2a43a0abebe 100644 --- a/tests/components/light/test_device_condition.py +++ b/tests/components/light/test_device_condition.py @@ -96,6 +96,7 @@ async def test_if_state(hass, calls): platform.init() assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) + await hass.async_block_till_done() ent1, ent2, ent3 = platform.ENTITIES @@ -173,6 +174,7 @@ async def test_if_fires_on_for_condition(hass, calls): platform.init() assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) + await hass.async_block_till_done() ent1, ent2, ent3 = platform.ENTITIES diff --git a/tests/components/light/test_device_trigger.py b/tests/components/light/test_device_trigger.py index 969b4278aeb..dd4745ac513 100644 --- a/tests/components/light/test_device_trigger.py +++ b/tests/components/light/test_device_trigger.py @@ -96,6 +96,7 @@ async def test_if_fires_on_state_change(hass, calls): platform.init() assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) + await hass.async_block_till_done() ent1, ent2, ent3 = platform.ENTITIES @@ -180,6 +181,7 @@ async def test_if_fires_on_state_change_with_for(hass, calls): platform.init() assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) + await hass.async_block_till_done() ent1, ent2, ent3 = platform.ENTITIES diff --git a/tests/components/light/test_init.py b/tests/components/light/test_init.py index b1f9327ff50..2fa22cd81dd 100644 --- a/tests/components/light/test_init.py +++ b/tests/components/light/test_init.py @@ -123,6 +123,7 @@ class TestLight(unittest.TestCase): assert setup_component( self.hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} ) + self.hass.block_till_done() ent1, ent2, ent3 = platform.ENTITIES @@ -335,6 +336,7 @@ class TestLight(unittest.TestCase): assert setup_component( self.hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} ) + self.hass.block_till_done() ent1, _, _ = platform.ENTITIES @@ -376,12 +378,13 @@ class TestLight(unittest.TestCase): return real_open(path, *args, **kwargs) profile_data = "id,x,y,brightness\ngroup.all_lights.default,.4,.6,99\n" - with mock.patch("os.path.isfile", side_effect=_mock_isfile): - with mock.patch("builtins.open", side_effect=_mock_open): - with mock_storage(): - assert setup_component( - self.hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} - ) + with mock.patch("os.path.isfile", side_effect=_mock_isfile), mock.patch( + "builtins.open", side_effect=_mock_open + ), mock_storage(): + assert setup_component( + self.hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} + ) + self.hass.block_till_done() ent, _, _ = platform.ENTITIES common.turn_on(self.hass, ent.entity_id) @@ -413,12 +416,13 @@ class TestLight(unittest.TestCase): + "group.all_lights.default,.3,.5,200\n" + "light.ceiling_2.default,.6,.6,100\n" ) - with mock.patch("os.path.isfile", side_effect=_mock_isfile): - with mock.patch("builtins.open", side_effect=_mock_open): - with mock_storage(): - assert setup_component( - self.hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} - ) + with mock.patch("os.path.isfile", side_effect=_mock_isfile), mock.patch( + "builtins.open", side_effect=_mock_open + ), mock_storage(): + assert setup_component( + self.hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}} + ) + self.hass.block_till_done() dev = next( filter(lambda x: x.entity_id == "light.ceiling_2", platform.ENTITIES) @@ -434,6 +438,7 @@ async def test_light_context(hass, hass_admin_user): platform = getattr(hass.components, "test.light") platform.init() assert await async_setup_component(hass, "light", {"light": {"platform": "test"}}) + await hass.async_block_till_done() state = hass.states.get("light.ceiling") assert state is not None @@ -457,6 +462,7 @@ async def test_light_turn_on_auth(hass, hass_admin_user): platform = getattr(hass.components, "test.light") platform.init() assert await async_setup_component(hass, "light", {"light": {"platform": "test"}}) + await hass.async_block_till_done() state = hass.states.get("light.ceiling") assert state is not None @@ -481,6 +487,7 @@ async def test_light_brightness_step(hass): entity.supported_features = light.SUPPORT_BRIGHTNESS entity.brightness = 100 assert await async_setup_component(hass, "light", {"light": {"platform": "test"}}) + await hass.async_block_till_done() state = hass.states.get(entity.entity_id) assert state is not None @@ -515,6 +522,7 @@ async def test_light_brightness_pct_conversion(hass): entity.supported_features = light.SUPPORT_BRIGHTNESS entity.brightness = 100 assert await async_setup_component(hass, "light", {"light": {"platform": "test"}}) + await hass.async_block_till_done() state = hass.states.get(entity.entity_id) assert state is not None diff --git a/tests/components/local_file/test_camera.py b/tests/components/local_file/test_camera.py index ea2b3c3252e..b1a9b04412d 100644 --- a/tests/components/local_file/test_camera.py +++ b/tests/components/local_file/test_camera.py @@ -25,6 +25,7 @@ async def test_loading_file(hass, hass_client): } }, ) + await hass.async_block_till_done() client = await hass_client() @@ -57,6 +58,7 @@ async def test_file_not_readable(hass, caplog): } }, ) + await hass.async_block_till_done() assert "Could not read" in caplog.text assert "config_test" in caplog.text @@ -91,6 +93,7 @@ async def test_camera_content_type(hass, hass_client): "camera", {"camera": [cam_config_jpg, cam_config_png, cam_config_svg, cam_config_noext]}, ) + await hass.async_block_till_done() client = await hass_client() @@ -143,6 +146,7 @@ async def test_update_file_path(hass): "file_path": "mock/path_2.jpg", } await async_setup_component(hass, "camera", {"camera": [camera_1, camera_2]}) + await hass.async_block_till_done() # Fetch state and check motion detection attribute state = hass.states.get("camera.local_file") diff --git a/tests/components/london_air/test_sensor.py b/tests/components/london_air/test_sensor.py index 83405095f2e..f596750ea7d 100644 --- a/tests/components/london_air/test_sensor.py +++ b/tests/components/london_air/test_sensor.py @@ -28,6 +28,7 @@ class TestLondonAirSensor(unittest.TestCase): """Test for operational tube_state sensor with proper attributes.""" mock_req.get(URL, text=load_fixture("london_air.json")) assert setup_component(self.hass, "sensor", {"sensor": self.config}) + self.hass.block_till_done() state = self.hass.states.get("sensor.merton") assert state.state == "Low" diff --git a/tests/components/manual/test_alarm_control_panel.py b/tests/components/manual/test_alarm_control_panel.py index d3e26597d84..c1f7fd5a7e0 100644 --- a/tests/components/manual/test_alarm_control_panel.py +++ b/tests/components/manual/test_alarm_control_panel.py @@ -47,6 +47,7 @@ async def test_arm_home_no_pending(hass): } }, ) + await hass.async_block_till_done() entity_id = "alarm_control_panel.test" @@ -73,6 +74,7 @@ async def test_arm_home_no_pending_when_code_not_req(hass): } }, ) + await hass.async_block_till_done() entity_id = "alarm_control_panel.test" @@ -98,6 +100,7 @@ async def test_arm_home_with_pending(hass): } }, ) + await hass.async_block_till_done() entity_id = "alarm_control_panel.test" @@ -137,6 +140,7 @@ async def test_arm_home_with_invalid_code(hass): } }, ) + await hass.async_block_till_done() entity_id = "alarm_control_panel.test" @@ -162,6 +166,7 @@ async def test_arm_away_no_pending(hass): } }, ) + await hass.async_block_till_done() entity_id = "alarm_control_panel.test" @@ -188,6 +193,7 @@ async def test_arm_away_no_pending_when_code_not_req(hass): } }, ) + await hass.async_block_till_done() entity_id = "alarm_control_panel.test" @@ -213,6 +219,7 @@ async def test_arm_home_with_template_code(hass): } }, ) + await hass.async_block_till_done() entity_id = "alarm_control_panel.test" @@ -239,6 +246,7 @@ async def test_arm_away_with_pending(hass): } }, ) + await hass.async_block_till_done() entity_id = "alarm_control_panel.test" @@ -278,6 +286,7 @@ async def test_arm_away_with_invalid_code(hass): } }, ) + await hass.async_block_till_done() entity_id = "alarm_control_panel.test" @@ -303,6 +312,7 @@ async def test_arm_night_no_pending(hass): } }, ) + await hass.async_block_till_done() entity_id = "alarm_control_panel.test" @@ -329,6 +339,7 @@ async def test_arm_night_no_pending_when_code_not_req(hass): } }, ) + await hass.async_block_till_done() entity_id = "alarm_control_panel.test" @@ -354,6 +365,7 @@ async def test_arm_night_with_pending(hass): } }, ) + await hass.async_block_till_done() entity_id = "alarm_control_panel.test" @@ -398,6 +410,7 @@ async def test_arm_night_with_invalid_code(hass): } }, ) + await hass.async_block_till_done() entity_id = "alarm_control_panel.test" @@ -422,6 +435,7 @@ async def test_trigger_no_pending(hass): } }, ) + await hass.async_block_till_done() entity_id = "alarm_control_panel.test" @@ -458,6 +472,7 @@ async def test_trigger_with_delay(hass): } }, ) + await hass.async_block_till_done() entity_id = "alarm_control_panel.test" @@ -500,6 +515,7 @@ async def test_trigger_zero_trigger_time(hass): } }, ) + await hass.async_block_till_done() entity_id = "alarm_control_panel.test" @@ -525,6 +541,7 @@ async def test_trigger_zero_trigger_time_with_pending(hass): } }, ) + await hass.async_block_till_done() entity_id = "alarm_control_panel.test" @@ -550,6 +567,7 @@ async def test_trigger_with_pending(hass): } }, ) + await hass.async_block_till_done() entity_id = "alarm_control_panel.test" @@ -602,6 +620,7 @@ async def test_trigger_with_unused_specific_delay(hass): } }, ) + await hass.async_block_till_done() entity_id = "alarm_control_panel.test" @@ -646,6 +665,7 @@ async def test_trigger_with_specific_delay(hass): } }, ) + await hass.async_block_till_done() entity_id = "alarm_control_panel.test" @@ -689,6 +709,7 @@ async def test_trigger_with_pending_and_delay(hass): } }, ) + await hass.async_block_till_done() entity_id = "alarm_control_panel.test" @@ -745,6 +766,7 @@ async def test_trigger_with_pending_and_specific_delay(hass): } }, ) + await hass.async_block_till_done() entity_id = "alarm_control_panel.test" @@ -798,6 +820,7 @@ async def test_armed_home_with_specific_pending(hass): } }, ) + await hass.async_block_till_done() entity_id = "alarm_control_panel.test" @@ -830,6 +853,7 @@ async def test_armed_away_with_specific_pending(hass): } }, ) + await hass.async_block_till_done() entity_id = "alarm_control_panel.test" @@ -862,6 +886,7 @@ async def test_armed_night_with_specific_pending(hass): } }, ) + await hass.async_block_till_done() entity_id = "alarm_control_panel.test" @@ -896,6 +921,7 @@ async def test_trigger_with_specific_pending(hass): } }, ) + await hass.async_block_till_done() entity_id = "alarm_control_panel.test" @@ -939,6 +965,7 @@ async def test_trigger_with_disarm_after_trigger(hass): } }, ) + await hass.async_block_till_done() entity_id = "alarm_control_panel.test" @@ -975,6 +1002,7 @@ async def test_trigger_with_zero_specific_trigger_time(hass): } }, ) + await hass.async_block_till_done() entity_id = "alarm_control_panel.test" @@ -1001,6 +1029,7 @@ async def test_trigger_with_unused_zero_specific_trigger_time(hass): } }, ) + await hass.async_block_till_done() entity_id = "alarm_control_panel.test" @@ -1036,6 +1065,7 @@ async def test_trigger_with_specific_trigger_time(hass): } }, ) + await hass.async_block_till_done() entity_id = "alarm_control_panel.test" @@ -1072,6 +1102,7 @@ async def test_trigger_with_no_disarm_after_trigger(hass): } }, ) + await hass.async_block_till_done() entity_id = "alarm_control_panel.test" @@ -1112,6 +1143,7 @@ async def test_back_to_back_trigger_with_no_disarm_after_trigger(hass): } }, ) + await hass.async_block_till_done() entity_id = "alarm_control_panel.test" @@ -1164,6 +1196,7 @@ async def test_disarm_while_pending_trigger(hass): } }, ) + await hass.async_block_till_done() entity_id = "alarm_control_panel.test" @@ -1203,6 +1236,7 @@ async def test_disarm_during_trigger_with_invalid_code(hass): } }, ) + await hass.async_block_till_done() entity_id = "alarm_control_panel.test" @@ -1242,6 +1276,7 @@ async def test_disarm_with_template_code(hass): } }, ) + await hass.async_block_till_done() entity_id = "alarm_control_panel.test" @@ -1278,6 +1313,7 @@ async def test_arm_custom_bypass_no_pending(hass): } }, ) + await hass.async_block_till_done() entity_id = "alarm_control_panel.test" @@ -1304,6 +1340,7 @@ async def test_arm_custom_bypass_no_pending_when_code_not_req(hass): } }, ) + await hass.async_block_till_done() entity_id = "alarm_control_panel.test" @@ -1329,6 +1366,7 @@ async def test_arm_custom_bypass_with_pending(hass): } }, ) + await hass.async_block_till_done() entity_id = "alarm_control_panel.test" @@ -1368,6 +1406,7 @@ async def test_arm_custom_bypass_with_invalid_code(hass): } }, ) + await hass.async_block_till_done() entity_id = "alarm_control_panel.test" @@ -1392,6 +1431,7 @@ async def test_armed_custom_bypass_with_specific_pending(hass): } }, ) + await hass.async_block_till_done() entity_id = "alarm_control_panel.test" @@ -1428,6 +1468,7 @@ async def test_arm_away_after_disabled_disarmed(hass): } }, ) + await hass.async_block_till_done() entity_id = "alarm_control_panel.test" @@ -1499,6 +1540,7 @@ async def test_restore_armed_state(hass): } }, ) + await hass.async_block_till_done() state = hass.states.get("alarm_control_panel.test") assert state @@ -1525,6 +1567,7 @@ async def test_restore_disarmed_state(hass): } }, ) + await hass.async_block_till_done() state = hass.states.get("alarm_control_panel.test") assert state diff --git a/tests/components/manual_mqtt/test_alarm_control_panel.py b/tests/components/manual_mqtt/test_alarm_control_panel.py index bff3818af56..a23382d9f78 100644 --- a/tests/components/manual_mqtt/test_alarm_control_panel.py +++ b/tests/components/manual_mqtt/test_alarm_control_panel.py @@ -86,6 +86,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): } }, ) + self.hass.block_till_done() entity_id = "alarm_control_panel.test" @@ -114,6 +115,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): } }, ) + self.hass.block_till_done() entity_id = "alarm_control_panel.test" @@ -141,6 +143,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): } }, ) + self.hass.block_till_done() entity_id = "alarm_control_panel.test" @@ -184,6 +187,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): } }, ) + self.hass.block_till_done() entity_id = "alarm_control_panel.test" @@ -211,6 +215,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): } }, ) + self.hass.block_till_done() entity_id = "alarm_control_panel.test" @@ -239,6 +244,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): } }, ) + self.hass.block_till_done() entity_id = "alarm_control_panel.test" @@ -266,6 +272,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): } }, ) + self.hass.block_till_done() entity_id = "alarm_control_panel.test" @@ -294,6 +301,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): } }, ) + self.hass.block_till_done() entity_id = "alarm_control_panel.test" @@ -337,6 +345,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): } }, ) + self.hass.block_till_done() entity_id = "alarm_control_panel.test" @@ -364,6 +373,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): } }, ) + self.hass.block_till_done() entity_id = "alarm_control_panel.test" @@ -392,6 +402,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): } }, ) + self.hass.block_till_done() entity_id = "alarm_control_panel.test" @@ -419,6 +430,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): } }, ) + self.hass.block_till_done() entity_id = "alarm_control_panel.test" @@ -468,6 +480,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): } }, ) + self.hass.block_till_done() entity_id = "alarm_control_panel.test" @@ -494,6 +507,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): } }, ) + self.hass.block_till_done() entity_id = "alarm_control_panel.test" @@ -535,6 +549,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): } }, ) + self.hass.block_till_done() entity_id = "alarm_control_panel.test" @@ -583,6 +598,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): } }, ) + self.hass.block_till_done() entity_id = "alarm_control_panel.test" @@ -610,6 +626,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): } }, ) + self.hass.block_till_done() entity_id = "alarm_control_panel.test" @@ -637,6 +654,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): } }, ) + self.hass.block_till_done() entity_id = "alarm_control_panel.test" @@ -693,6 +711,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): } }, ) + self.hass.block_till_done() entity_id = "alarm_control_panel.test" @@ -734,6 +753,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): } }, ) + self.hass.block_till_done() entity_id = "alarm_control_panel.test" @@ -762,6 +782,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): } }, ) + self.hass.block_till_done() entity_id = "alarm_control_panel.test" @@ -802,6 +823,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): } }, ) + self.hass.block_till_done() entity_id = "alarm_control_panel.test" @@ -842,6 +864,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): } }, ) + self.hass.block_till_done() entity_id = "alarm_control_panel.test" @@ -904,6 +927,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): } }, ) + self.hass.block_till_done() entity_id = "alarm_control_panel.test" @@ -949,6 +973,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): } }, ) + self.hass.block_till_done() entity_id = "alarm_control_panel.test" @@ -996,6 +1021,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): } }, ) + self.hass.block_till_done() entity_id = "alarm_control_panel.test" @@ -1046,6 +1072,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): } }, ) + self.hass.block_till_done() entity_id = "alarm_control_panel.test" @@ -1096,6 +1123,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): } }, ) + self.hass.block_till_done() entity_id = "alarm_control_panel.test" @@ -1162,6 +1190,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): } }, ) + self.hass.block_till_done() entity_id = "alarm_control_panel.test" @@ -1224,6 +1253,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): } }, ) + self.hass.block_till_done() entity_id = "alarm_control_panel.test" @@ -1261,6 +1291,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): } }, ) + self.hass.block_till_done() entity_id = "alarm_control_panel.test" @@ -1298,6 +1329,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): } }, ) + self.hass.block_till_done() entity_id = "alarm_control_panel.test" @@ -1337,6 +1369,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): } }, ) + self.hass.block_till_done() entity_id = "alarm_control_panel.test" @@ -1391,6 +1424,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): } }, ) + self.hass.block_till_done() entity_id = "alarm_control_panel.test" @@ -1465,6 +1499,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): } }, ) + self.hass.block_till_done() entity_id = "alarm_control_panel.test" @@ -1504,6 +1539,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): } }, ) + self.hass.block_till_done() entity_id = "alarm_control_panel.test" @@ -1544,6 +1580,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): } }, ) + self.hass.block_till_done() entity_id = "alarm_control_panel.test" @@ -1584,6 +1621,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): } }, ) + self.hass.block_till_done() entity_id = "alarm_control_panel.test" @@ -1624,6 +1662,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): } }, ) + self.hass.block_till_done() entity_id = "alarm_control_panel.test" @@ -1656,6 +1695,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase): } }, ) + self.hass.block_till_done() # Component should send disarmed alarm state on startup self.hass.block_till_done() diff --git a/tests/components/media_player/test_init.py b/tests/components/media_player/test_init.py index 50924af5d76..35863f71362 100644 --- a/tests/components/media_player/test_init.py +++ b/tests/components/media_player/test_init.py @@ -45,6 +45,7 @@ async def test_get_image_http(hass, aiohttp_client): await async_setup_component( hass, "media_player", {"media_player": {"platform": "demo"}} ) + await hass.async_block_till_done() state = hass.states.get("media_player.bedroom") assert "entity_picture_local" not in state.attributes @@ -72,6 +73,7 @@ async def test_get_image_http_remote(hass, aiohttp_client): await async_setup_component( hass, "media_player", {"media_player": {"platform": "demo"}} ) + await hass.async_block_till_done() state = hass.states.get("media_player.bedroom") assert "entity_picture_local" in state.attributes diff --git a/tests/components/meteo_france/test_config_flow.py b/tests/components/meteo_france/test_config_flow.py index d4381073209..8a5c734a0ed 100644 --- a/tests/components/meteo_france/test_config_flow.py +++ b/tests/components/meteo_france/test_config_flow.py @@ -27,6 +27,17 @@ def mock_controller_client_1(): yield service_mock +@pytest.fixture(autouse=True) +def mock_setup(): + """Prevent setup.""" + with patch( + "homeassistant.components.meteo_france.async_setup", return_value=True, + ), patch( + "homeassistant.components.meteo_france.async_setup_entry", return_value=True, + ): + yield + + @pytest.fixture(name="client_2") def mock_controller_client_2(): """Mock a successful client.""" diff --git a/tests/components/mfi/test_sensor.py b/tests/components/mfi/test_sensor.py index ff9c7fa7182..05e1379cfb4 100644 --- a/tests/components/mfi/test_sensor.py +++ b/tests/components/mfi/test_sensor.py @@ -64,6 +64,7 @@ class TestMfiSensorSetup(unittest.TestCase): config = dict(self.GOOD_CONFIG) del config[self.THING]["port"] assert setup_component(self.hass, self.COMPONENT.DOMAIN, config) + self.hass.block_till_done() assert mock_client.call_count == 1 assert mock_client.call_args == mock.call( "foo", "user", "pass", port=6443, use_tls=True, verify=True @@ -75,6 +76,7 @@ class TestMfiSensorSetup(unittest.TestCase): config = dict(self.GOOD_CONFIG) config[self.THING]["port"] = 6123 assert setup_component(self.hass, self.COMPONENT.DOMAIN, config) + self.hass.block_till_done() assert mock_client.call_count == 1 assert mock_client.call_args == mock.call( "foo", "user", "pass", port=6123, use_tls=True, verify=True @@ -88,6 +90,7 @@ class TestMfiSensorSetup(unittest.TestCase): config[self.THING]["ssl"] = False config[self.THING]["verify_ssl"] = False assert setup_component(self.hass, self.COMPONENT.DOMAIN, config) + self.hass.block_till_done() assert mock_client.call_count == 1 assert mock_client.call_args == mock.call( "foo", "user", "pass", port=6080, use_tls=False, verify=False @@ -105,6 +108,7 @@ class TestMfiSensorSetup(unittest.TestCase): mock.MagicMock(ports=ports) ] assert setup_component(self.hass, sensor.DOMAIN, self.GOOD_CONFIG) + self.hass.block_till_done() for ident, port in ports.items(): if ident != "bad": mock_sensor.assert_any_call(port, self.hass) diff --git a/tests/components/mfi/test_switch.py b/tests/components/mfi/test_switch.py index 414e0b8b50b..45bb3530266 100644 --- a/tests/components/mfi/test_switch.py +++ b/tests/components/mfi/test_switch.py @@ -48,6 +48,7 @@ class TestMfiSwitchSetup(unittest.TestCase): mock.MagicMock(ports=ports) ] assert setup_component(self.hass, switch.DOMAIN, self.GOOD_CONFIG) + self.hass.block_till_done() for ident, port in ports.items(): if ident != "bad": mock_switch.assert_any_call(port) diff --git a/tests/components/microsoft_face_detect/test_image_processing.py b/tests/components/microsoft_face_detect/test_image_processing.py index 3f38c07cb43..b6f828318bc 100644 --- a/tests/components/microsoft_face_detect/test_image_processing.py +++ b/tests/components/microsoft_face_detect/test_image_processing.py @@ -45,6 +45,7 @@ class TestMicrosoftFaceDetectSetup: with assert_setup_component(1, ip.DOMAIN): setup_component(self.hass, ip.DOMAIN, config) + self.hass.block_till_done() assert self.hass.states.get("image_processing.microsoftface_demo_camera") @@ -65,6 +66,7 @@ class TestMicrosoftFaceDetectSetup: with assert_setup_component(1, ip.DOMAIN): setup_component(self.hass, ip.DOMAIN, config) + self.hass.block_till_done() assert self.hass.states.get("image_processing.test_local") @@ -113,6 +115,7 @@ class TestMicrosoftFaceDetect: ) setup_component(self.hass, ip.DOMAIN, self.config) + self.hass.block_till_done() state = self.hass.states.get("camera.demo_camera") url = f"{self.hass.config.api.base_url}{state.attributes.get(ATTR_ENTITY_PICTURE)}" diff --git a/tests/components/microsoft_face_identify/test_image_processing.py b/tests/components/microsoft_face_identify/test_image_processing.py index 58a752c1887..cc45220153e 100644 --- a/tests/components/microsoft_face_identify/test_image_processing.py +++ b/tests/components/microsoft_face_identify/test_image_processing.py @@ -45,6 +45,7 @@ class TestMicrosoftFaceIdentifySetup: with assert_setup_component(1, ip.DOMAIN): setup_component(self.hass, ip.DOMAIN, config) + self.hass.block_till_done() assert self.hass.states.get("image_processing.microsoftface_demo_camera") @@ -66,6 +67,7 @@ class TestMicrosoftFaceIdentifySetup: with assert_setup_component(1, ip.DOMAIN): setup_component(self.hass, ip.DOMAIN, config) + self.hass.block_till_done() assert self.hass.states.get("image_processing.test_local") @@ -114,6 +116,7 @@ class TestMicrosoftFaceIdentify: ) setup_component(self.hass, ip.DOMAIN, self.config) + self.hass.block_till_done() state = self.hass.states.get("camera.demo_camera") url = f"{self.hass.config.api.base_url}{state.attributes.get(ATTR_ENTITY_PICTURE)}" diff --git a/tests/components/mobile_app/test_device_tracker.py b/tests/components/mobile_app/test_device_tracker.py index ab97ac3b9ac..164b90a5290 100644 --- a/tests/components/mobile_app/test_device_tracker.py +++ b/tests/components/mobile_app/test_device_tracker.py @@ -97,6 +97,7 @@ async def test_restoring_location(hass, create_registrations, webhook_client): # mobile app doesn't support unloading, so we just reload device tracker await hass.config_entries.async_forward_entry_unload(config_entry, "device_tracker") await hass.config_entries.async_forward_entry_setup(config_entry, "device_tracker") + await hass.async_block_till_done() state_2 = hass.states.get("device_tracker.test_1_2") assert state_2 is not None diff --git a/tests/components/modbus/conftest.py b/tests/components/modbus/conftest.py index 814e59e5571..885aa5fc235 100644 --- a/tests/components/modbus/conftest.py +++ b/tests/components/modbus/conftest.py @@ -69,6 +69,7 @@ async def run_test( now = dt_util.utcnow() with mock.patch("homeassistant.helpers.event.dt_util.utcnow", return_value=now): assert await async_setup_component(hass, entity_domain, config) + await hass.async_block_till_done() # Trigger update call with time_changed event now += timedelta(seconds=scan_interval + 1) diff --git a/tests/components/mold_indicator/test_sensor.py b/tests/components/mold_indicator/test_sensor.py index bad7430e9b2..5f3b223bf66 100644 --- a/tests/components/mold_indicator/test_sensor.py +++ b/tests/components/mold_indicator/test_sensor.py @@ -52,7 +52,7 @@ class TestSensorMoldIndicator(unittest.TestCase): } }, ) - + self.hass.block_till_done() moldind = self.hass.states.get("sensor.mold_indicator") assert moldind assert UNIT_PERCENTAGE == moldind.attributes.get("unit_of_measurement") @@ -82,6 +82,7 @@ class TestSensorMoldIndicator(unittest.TestCase): } }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() moldind = self.hass.states.get("sensor.mold_indicator") @@ -116,6 +117,7 @@ class TestSensorMoldIndicator(unittest.TestCase): }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() moldind = self.hass.states.get("sensor.mold_indicator") @@ -159,6 +161,7 @@ class TestSensorMoldIndicator(unittest.TestCase): } }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() moldind = self.hass.states.get("sensor.mold_indicator") @@ -196,6 +199,7 @@ class TestSensorMoldIndicator(unittest.TestCase): } }, ) + self.hass.block_till_done() self.hass.start() self.hass.states.set( @@ -268,6 +272,7 @@ class TestSensorMoldIndicator(unittest.TestCase): } }, ) + self.hass.block_till_done() self.hass.start() self.hass.states.set( diff --git a/tests/components/moon/test_sensor.py b/tests/components/moon/test_sensor.py index 89d5a8f798c..fe6e57dd9b6 100644 --- a/tests/components/moon/test_sensor.py +++ b/tests/components/moon/test_sensor.py @@ -29,6 +29,7 @@ class TestMoonSensor(unittest.TestCase): config = {"sensor": {"platform": "moon", "name": "moon_day1"}} assert setup_component(self.hass, "sensor", config) + self.hass.block_till_done() state = self.hass.states.get("sensor.moon_day1") assert state.state == "waxing_crescent" @@ -39,6 +40,7 @@ class TestMoonSensor(unittest.TestCase): config = {"sensor": {"platform": "moon", "name": "moon_day2"}} assert setup_component(self.hass, "sensor", config) + self.hass.block_till_done() state = self.hass.states.get("sensor.moon_day2") assert state.state == "waning_gibbous" diff --git a/tests/components/mqtt/test_alarm_control_panel.py b/tests/components/mqtt/test_alarm_control_panel.py index 6677122cf10..03e8133bde9 100644 --- a/tests/components/mqtt/test_alarm_control_panel.py +++ b/tests/components/mqtt/test_alarm_control_panel.py @@ -107,6 +107,7 @@ async def test_update_state_via_state_topic(hass, mqtt_mock): assert await async_setup_component( hass, alarm_control_panel.DOMAIN, DEFAULT_CONFIG, ) + await hass.async_block_till_done() entity_id = "alarm_control_panel.test" @@ -132,6 +133,7 @@ async def test_ignore_update_state_if_unknown_via_state_topic(hass, mqtt_mock): assert await async_setup_component( hass, alarm_control_panel.DOMAIN, DEFAULT_CONFIG, ) + await hass.async_block_till_done() entity_id = "alarm_control_panel.test" @@ -146,6 +148,7 @@ async def test_arm_home_publishes_mqtt(hass, mqtt_mock): assert await async_setup_component( hass, alarm_control_panel.DOMAIN, DEFAULT_CONFIG, ) + await hass.async_block_till_done() await common.async_alarm_arm_home(hass) mqtt_mock.async_publish.assert_called_once_with( @@ -175,6 +178,7 @@ async def test_arm_home_publishes_mqtt_when_code_not_req(hass, mqtt_mock): config = copy.deepcopy(DEFAULT_CONFIG_CODE) config[alarm_control_panel.DOMAIN]["code_arm_required"] = False assert await async_setup_component(hass, alarm_control_panel.DOMAIN, config,) + await hass.async_block_till_done() await common.async_alarm_arm_home(hass) mqtt_mock.async_publish.assert_called_once_with( @@ -187,6 +191,7 @@ async def test_arm_away_publishes_mqtt(hass, mqtt_mock): assert await async_setup_component( hass, alarm_control_panel.DOMAIN, DEFAULT_CONFIG, ) + await hass.async_block_till_done() await common.async_alarm_arm_away(hass) mqtt_mock.async_publish.assert_called_once_with( @@ -216,6 +221,7 @@ async def test_arm_away_publishes_mqtt_when_code_not_req(hass, mqtt_mock): config = copy.deepcopy(DEFAULT_CONFIG_CODE) config[alarm_control_panel.DOMAIN]["code_arm_required"] = False assert await async_setup_component(hass, alarm_control_panel.DOMAIN, config,) + await hass.async_block_till_done() await common.async_alarm_arm_away(hass) mqtt_mock.async_publish.assert_called_once_with( @@ -228,6 +234,7 @@ async def test_arm_night_publishes_mqtt(hass, mqtt_mock): assert await async_setup_component( hass, alarm_control_panel.DOMAIN, DEFAULT_CONFIG, ) + await hass.async_block_till_done() await common.async_alarm_arm_night(hass) mqtt_mock.async_publish.assert_called_once_with( @@ -257,6 +264,7 @@ async def test_arm_night_publishes_mqtt_when_code_not_req(hass, mqtt_mock): config = copy.deepcopy(DEFAULT_CONFIG_CODE) config[alarm_control_panel.DOMAIN]["code_arm_required"] = False assert await async_setup_component(hass, alarm_control_panel.DOMAIN, config,) + await hass.async_block_till_done() await common.async_alarm_arm_night(hass) mqtt_mock.async_publish.assert_called_once_with( @@ -278,6 +286,7 @@ async def test_arm_custom_bypass_publishes_mqtt(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() await common.async_alarm_arm_custom_bypass(hass) mqtt_mock.async_publish.assert_called_once_with( @@ -306,6 +315,7 @@ async def test_arm_custom_bypass_not_publishes_mqtt_with_invalid_code_when_req( } }, ) + await hass.async_block_till_done() call_count = mqtt_mock.async_publish.call_count await common.async_alarm_arm_custom_bypass(hass, "abcd") @@ -331,6 +341,7 @@ async def test_arm_custom_bypass_publishes_mqtt_when_code_not_req(hass, mqtt_moc } }, ) + await hass.async_block_till_done() await common.async_alarm_arm_custom_bypass(hass) mqtt_mock.async_publish.assert_called_once_with( @@ -343,6 +354,7 @@ async def test_disarm_publishes_mqtt(hass, mqtt_mock): assert await async_setup_component( hass, alarm_control_panel.DOMAIN, DEFAULT_CONFIG, ) + await hass.async_block_till_done() await common.async_alarm_disarm(hass) mqtt_mock.async_publish.assert_called_once_with("alarm/command", "DISARM", 0, False) @@ -359,6 +371,7 @@ async def test_disarm_publishes_mqtt_with_template(hass, mqtt_mock): '{"action":"{{ action }}",' '"code":"{{ code }}"}' ) assert await async_setup_component(hass, alarm_control_panel.DOMAIN, config,) + await hass.async_block_till_done() await common.async_alarm_disarm(hass, 1234) mqtt_mock.async_publish.assert_called_once_with( @@ -375,6 +388,7 @@ async def test_disarm_publishes_mqtt_when_code_not_req(hass, mqtt_mock): config[alarm_control_panel.DOMAIN]["code"] = "1234" config[alarm_control_panel.DOMAIN]["code_disarm_required"] = False assert await async_setup_component(hass, alarm_control_panel.DOMAIN, config,) + await hass.async_block_till_done() await common.async_alarm_disarm(hass) mqtt_mock.async_publish.assert_called_once_with("alarm/command", "DISARM", 0, False) @@ -414,6 +428,7 @@ async def test_update_state_via_state_topic_template(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() state = hass.states.get("alarm_control_panel.test") assert state.state == STATE_UNKNOWN @@ -430,6 +445,7 @@ async def test_attributes_code_number(hass, mqtt_mock): config[alarm_control_panel.DOMAIN]["code"] = CODE_NUMBER assert await async_setup_component(hass, alarm_control_panel.DOMAIN, config) + await hass.async_block_till_done() state = hass.states.get("alarm_control_panel.test") assert ( @@ -444,6 +460,7 @@ async def test_attributes_code_text(hass, mqtt_mock): config[alarm_control_panel.DOMAIN]["code"] = CODE_TEXT assert await async_setup_component(hass, alarm_control_panel.DOMAIN, config) + await hass.async_block_till_done() state = hass.states.get("alarm_control_panel.test") assert ( diff --git a/tests/components/mqtt/test_binary_sensor.py b/tests/components/mqtt/test_binary_sensor.py index 31acf187ad5..8c68fabf214 100644 --- a/tests/components/mqtt/test_binary_sensor.py +++ b/tests/components/mqtt/test_binary_sensor.py @@ -71,6 +71,7 @@ async def test_setting_sensor_value_expires_availability_topic(hass, mqtt_mock, } }, ) + await hass.async_block_till_done() state = hass.states.get("binary_sensor.test") assert state.state == STATE_UNAVAILABLE @@ -99,6 +100,7 @@ async def test_setting_sensor_value_expires(hass, mqtt_mock, caplog): } }, ) + await hass.async_block_till_done() # State should be unavailable since expire_after is defined and > 0 state = hass.states.get("binary_sensor.test") @@ -172,6 +174,7 @@ async def test_setting_sensor_value_via_mqtt_message(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() state = hass.states.get("binary_sensor.test") @@ -201,6 +204,7 @@ async def test_invalid_sensor_value_via_mqtt_message(hass, mqtt_mock, caplog): } }, ) + await hass.async_block_till_done() state = hass.states.get("binary_sensor.test") @@ -240,6 +244,7 @@ async def test_setting_sensor_value_via_mqtt_message_and_template(hass, mqtt_moc } }, ) + await hass.async_block_till_done() state = hass.states.get("binary_sensor.test") assert state.state == STATE_OFF @@ -267,6 +272,7 @@ async def test_valid_device_class(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() state = hass.states.get("binary_sensor.test") assert state.attributes.get("device_class") == "motion" @@ -286,6 +292,7 @@ async def test_invalid_device_class(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() state = hass.states.get("binary_sensor.test") assert state is None @@ -327,6 +334,7 @@ async def test_force_update_disabled(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() events = [] @@ -362,6 +370,7 @@ async def test_force_update_enabled(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() events = [] @@ -398,6 +407,7 @@ async def test_off_delay(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() events = [] diff --git a/tests/components/mqtt/test_camera.py b/tests/components/mqtt/test_camera.py index 5747d876b57..11b846d4c38 100644 --- a/tests/components/mqtt/test_camera.py +++ b/tests/components/mqtt/test_camera.py @@ -49,6 +49,7 @@ async def test_run_camera_setup(hass, aiohttp_client): "camera", {"camera": {"platform": "mqtt", "topic": topic, "name": "Test Camera"}}, ) + await hass.async_block_till_done() url = hass.states.get("camera.test_camera").attributes["entity_picture"] diff --git a/tests/components/mqtt/test_climate.py b/tests/components/mqtt/test_climate.py index 8c3bfebed20..30018c7c175 100644 --- a/tests/components/mqtt/test_climate.py +++ b/tests/components/mqtt/test_climate.py @@ -72,6 +72,7 @@ DEFAULT_CONFIG = { async def test_setup_params(hass, mqtt_mock): """Test the initial parameters.""" assert await async_setup_component(hass, CLIMATE_DOMAIN, DEFAULT_CONFIG) + await hass.async_block_till_done() state = hass.states.get(ENTITY_CLIMATE) assert state.attributes.get("temperature") == 21 @@ -85,6 +86,7 @@ async def test_setup_params(hass, mqtt_mock): async def test_supported_features(hass, mqtt_mock): """Test the supported_features.""" assert await async_setup_component(hass, CLIMATE_DOMAIN, DEFAULT_CONFIG) + await hass.async_block_till_done() state = hass.states.get(ENTITY_CLIMATE) support = ( @@ -102,6 +104,7 @@ async def test_supported_features(hass, mqtt_mock): async def test_get_hvac_modes(hass, mqtt_mock): """Test that the operation list returns the correct modes.""" assert await async_setup_component(hass, CLIMATE_DOMAIN, DEFAULT_CONFIG) + await hass.async_block_till_done() state = hass.states.get(ENTITY_CLIMATE) modes = state.attributes.get("hvac_modes") @@ -121,6 +124,7 @@ async def test_set_operation_bad_attr_and_state(hass, mqtt_mock, caplog): Also check the state. """ assert await async_setup_component(hass, CLIMATE_DOMAIN, DEFAULT_CONFIG) + await hass.async_block_till_done() state = hass.states.get(ENTITY_CLIMATE) assert state.state == "off" @@ -136,6 +140,7 @@ async def test_set_operation_bad_attr_and_state(hass, mqtt_mock, caplog): async def test_set_operation(hass, mqtt_mock): """Test setting of new operation mode.""" assert await async_setup_component(hass, CLIMATE_DOMAIN, DEFAULT_CONFIG) + await hass.async_block_till_done() state = hass.states.get(ENTITY_CLIMATE) assert state.state == "off" @@ -151,6 +156,7 @@ async def test_set_operation_pessimistic(hass, mqtt_mock): config = copy.deepcopy(DEFAULT_CONFIG) config["climate"]["mode_state_topic"] = "mode-state" assert await async_setup_component(hass, CLIMATE_DOMAIN, config) + await hass.async_block_till_done() state = hass.states.get(ENTITY_CLIMATE) assert state.state == "unknown" @@ -173,6 +179,7 @@ async def test_set_operation_with_power_command(hass, mqtt_mock): config = copy.deepcopy(DEFAULT_CONFIG) config["climate"]["power_command_topic"] = "power-command" assert await async_setup_component(hass, CLIMATE_DOMAIN, config) + await hass.async_block_till_done() state = hass.states.get(ENTITY_CLIMATE) assert state.state == "off" @@ -196,6 +203,7 @@ async def test_set_operation_with_power_command(hass, mqtt_mock): async def test_set_fan_mode_bad_attr(hass, mqtt_mock, caplog): """Test setting fan mode without required attribute.""" assert await async_setup_component(hass, CLIMATE_DOMAIN, DEFAULT_CONFIG) + await hass.async_block_till_done() state = hass.states.get(ENTITY_CLIMATE) assert state.attributes.get("fan_mode") == "low" @@ -213,6 +221,7 @@ async def test_set_fan_mode_pessimistic(hass, mqtt_mock): config = copy.deepcopy(DEFAULT_CONFIG) config["climate"]["fan_mode_state_topic"] = "fan-state" assert await async_setup_component(hass, CLIMATE_DOMAIN, config) + await hass.async_block_till_done() state = hass.states.get(ENTITY_CLIMATE) assert state.attributes.get("fan_mode") is None @@ -233,6 +242,7 @@ async def test_set_fan_mode_pessimistic(hass, mqtt_mock): async def test_set_fan_mode(hass, mqtt_mock): """Test setting of new fan mode.""" assert await async_setup_component(hass, CLIMATE_DOMAIN, DEFAULT_CONFIG) + await hass.async_block_till_done() state = hass.states.get(ENTITY_CLIMATE) assert state.attributes.get("fan_mode") == "low" @@ -245,6 +255,7 @@ async def test_set_fan_mode(hass, mqtt_mock): async def test_set_swing_mode_bad_attr(hass, mqtt_mock, caplog): """Test setting swing mode without required attribute.""" assert await async_setup_component(hass, CLIMATE_DOMAIN, DEFAULT_CONFIG) + await hass.async_block_till_done() state = hass.states.get(ENTITY_CLIMATE) assert state.attributes.get("swing_mode") == "off" @@ -262,6 +273,7 @@ async def test_set_swing_pessimistic(hass, mqtt_mock): config = copy.deepcopy(DEFAULT_CONFIG) config["climate"]["swing_mode_state_topic"] = "swing-state" assert await async_setup_component(hass, CLIMATE_DOMAIN, config) + await hass.async_block_till_done() state = hass.states.get(ENTITY_CLIMATE) assert state.attributes.get("swing_mode") is None @@ -282,6 +294,7 @@ async def test_set_swing_pessimistic(hass, mqtt_mock): async def test_set_swing(hass, mqtt_mock): """Test setting of new swing mode.""" assert await async_setup_component(hass, CLIMATE_DOMAIN, DEFAULT_CONFIG) + await hass.async_block_till_done() state = hass.states.get(ENTITY_CLIMATE) assert state.attributes.get("swing_mode") == "off" @@ -294,6 +307,7 @@ async def test_set_swing(hass, mqtt_mock): async def test_set_target_temperature(hass, mqtt_mock): """Test setting the target temperature.""" assert await async_setup_component(hass, CLIMATE_DOMAIN, DEFAULT_CONFIG) + await hass.async_block_till_done() state = hass.states.get(ENTITY_CLIMATE) assert state.attributes.get("temperature") == 21 @@ -326,6 +340,7 @@ async def test_set_target_temperature_pessimistic(hass, mqtt_mock): config = copy.deepcopy(DEFAULT_CONFIG) config["climate"]["temperature_state_topic"] = "temperature-state" assert await async_setup_component(hass, CLIMATE_DOMAIN, config) + await hass.async_block_till_done() state = hass.states.get(ENTITY_CLIMATE) assert state.attributes.get("temperature") is None @@ -346,6 +361,7 @@ async def test_set_target_temperature_pessimistic(hass, mqtt_mock): async def test_set_target_temperature_low_high(hass, mqtt_mock): """Test setting the low/high target temperature.""" assert await async_setup_component(hass, CLIMATE_DOMAIN, DEFAULT_CONFIG) + await hass.async_block_till_done() await common.async_set_temperature( hass, target_temp_low=20, target_temp_high=23, entity_id=ENTITY_CLIMATE @@ -363,6 +379,7 @@ async def test_set_target_temperature_low_highpessimistic(hass, mqtt_mock): config["climate"]["temperature_low_state_topic"] = "temperature-low-state" config["climate"]["temperature_high_state_topic"] = "temperature-high-state" assert await async_setup_component(hass, CLIMATE_DOMAIN, config) + await hass.async_block_till_done() state = hass.states.get(ENTITY_CLIMATE) assert state.attributes.get("target_temp_low") is None @@ -398,6 +415,7 @@ async def test_receive_mqtt_temperature(hass, mqtt_mock): config = copy.deepcopy(DEFAULT_CONFIG) config["climate"]["current_temperature_topic"] = "current_temperature" assert await async_setup_component(hass, CLIMATE_DOMAIN, config) + await hass.async_block_till_done() async_fire_mqtt_message(hass, "current_temperature", "47") state = hass.states.get(ENTITY_CLIMATE) @@ -409,6 +427,7 @@ async def test_set_away_mode_pessimistic(hass, mqtt_mock): config = copy.deepcopy(DEFAULT_CONFIG) config["climate"]["away_mode_state_topic"] = "away-state" assert await async_setup_component(hass, CLIMATE_DOMAIN, config) + await hass.async_block_till_done() state = hass.states.get(ENTITY_CLIMATE) assert state.attributes.get("preset_mode") is None @@ -437,6 +456,7 @@ async def test_set_away_mode(hass, mqtt_mock): config["climate"]["payload_off"] = "AUS" assert await async_setup_component(hass, CLIMATE_DOMAIN, config) + await hass.async_block_till_done() state = hass.states.get(ENTITY_CLIMATE) assert state.attributes.get("preset_mode") is None @@ -467,6 +487,7 @@ async def test_set_hvac_action(hass, mqtt_mock): config = copy.deepcopy(DEFAULT_CONFIG) config["climate"]["action_topic"] = "action" assert await async_setup_component(hass, CLIMATE_DOMAIN, config) + await hass.async_block_till_done() state = hass.states.get(ENTITY_CLIMATE) assert state.attributes.get("hvac_action") is None @@ -481,6 +502,7 @@ async def test_set_hold_pessimistic(hass, mqtt_mock): config = copy.deepcopy(DEFAULT_CONFIG) config["climate"]["hold_state_topic"] = "hold-state" assert await async_setup_component(hass, CLIMATE_DOMAIN, config) + await hass.async_block_till_done() state = hass.states.get(ENTITY_CLIMATE) assert state.attributes.get("hold_mode") is None @@ -501,6 +523,7 @@ async def test_set_hold_pessimistic(hass, mqtt_mock): async def test_set_hold(hass, mqtt_mock): """Test setting the hold mode.""" assert await async_setup_component(hass, CLIMATE_DOMAIN, DEFAULT_CONFIG) + await hass.async_block_till_done() state = hass.states.get(ENTITY_CLIMATE) assert state.attributes.get("preset_mode") is None @@ -525,6 +548,7 @@ async def test_set_hold(hass, mqtt_mock): async def test_set_preset_mode_twice(hass, mqtt_mock): """Test setting of the same mode twice only publishes once.""" assert await async_setup_component(hass, CLIMATE_DOMAIN, DEFAULT_CONFIG) + await hass.async_block_till_done() state = hass.states.get(ENTITY_CLIMATE) assert state.attributes.get("preset_mode") is None @@ -543,6 +567,7 @@ async def test_set_aux_pessimistic(hass, mqtt_mock): config = copy.deepcopy(DEFAULT_CONFIG) config["climate"]["aux_state_topic"] = "aux-state" assert await async_setup_component(hass, CLIMATE_DOMAIN, config) + await hass.async_block_till_done() state = hass.states.get(ENTITY_CLIMATE) assert state.attributes.get("aux_heat") == "off" @@ -567,6 +592,7 @@ async def test_set_aux_pessimistic(hass, mqtt_mock): async def test_set_aux(hass, mqtt_mock): """Test setting of the aux heating.""" assert await async_setup_component(hass, CLIMATE_DOMAIN, DEFAULT_CONFIG) + await hass.async_block_till_done() state = hass.states.get(ENTITY_CLIMATE) assert state.attributes.get("aux_heat") == "off" @@ -612,6 +638,7 @@ async def test_set_target_temperature_low_high_with_templates(hass, mqtt_mock, c config["climate"]["temperature_high_state_template"] = "{{ value_json.temp_high }}" assert await async_setup_component(hass, CLIMATE_DOMAIN, config) + await hass.async_block_till_done() state = hass.states.get(ENTITY_CLIMATE) @@ -658,6 +685,7 @@ async def test_set_with_templates(hass, mqtt_mock, caplog): config["climate"]["current_temperature_topic"] = "current-temperature" assert await async_setup_component(hass, CLIMATE_DOMAIN, config) + await hass.async_block_till_done() # Operation Mode state = hass.states.get(ENTITY_CLIMATE) @@ -745,6 +773,7 @@ async def test_min_temp_custom(hass, mqtt_mock): config["climate"]["min_temp"] = 26 assert await async_setup_component(hass, CLIMATE_DOMAIN, config) + await hass.async_block_till_done() state = hass.states.get(ENTITY_CLIMATE) min_temp = state.attributes.get("min_temp") @@ -759,6 +788,7 @@ async def test_max_temp_custom(hass, mqtt_mock): config["climate"]["max_temp"] = 60 assert await async_setup_component(hass, CLIMATE_DOMAIN, config) + await hass.async_block_till_done() state = hass.states.get(ENTITY_CLIMATE) max_temp = state.attributes.get("max_temp") @@ -773,6 +803,7 @@ async def test_temp_step_custom(hass, mqtt_mock): config["climate"]["temp_step"] = 0.01 assert await async_setup_component(hass, CLIMATE_DOMAIN, config) + await hass.async_block_till_done() state = hass.states.get(ENTITY_CLIMATE) temp_step = state.attributes.get("target_temp_step") @@ -788,6 +819,7 @@ async def test_temperature_unit(hass, mqtt_mock): config["climate"]["current_temperature_topic"] = "current_temperature" assert await async_setup_component(hass, CLIMATE_DOMAIN, config) + await hass.async_block_till_done() async_fire_mqtt_message(hass, "current_temperature", "77") @@ -945,6 +977,7 @@ async def test_entity_debug_info_message(hass, mqtt_mock): async def test_precision_default(hass, mqtt_mock): """Test that setting precision to tenths works as intended.""" assert await async_setup_component(hass, CLIMATE_DOMAIN, DEFAULT_CONFIG) + await hass.async_block_till_done() await common.async_set_temperature( hass, temperature=23.67, entity_id=ENTITY_CLIMATE @@ -959,6 +992,7 @@ async def test_precision_halves(hass, mqtt_mock): config = copy.deepcopy(DEFAULT_CONFIG) config["climate"]["precision"] = 0.5 assert await async_setup_component(hass, CLIMATE_DOMAIN, config) + await hass.async_block_till_done() await common.async_set_temperature( hass, temperature=23.67, entity_id=ENTITY_CLIMATE @@ -973,6 +1007,7 @@ async def test_precision_whole(hass, mqtt_mock): config = copy.deepcopy(DEFAULT_CONFIG) config["climate"]["precision"] = 1.0 assert await async_setup_component(hass, CLIMATE_DOMAIN, config) + await hass.async_block_till_done() await common.async_set_temperature( hass, temperature=23.67, entity_id=ENTITY_CLIMATE diff --git a/tests/components/mqtt/test_common.py b/tests/components/mqtt/test_common.py index bfd478a712e..d0ddc1d4830 100644 --- a/tests/components/mqtt/test_common.py +++ b/tests/components/mqtt/test_common.py @@ -39,6 +39,7 @@ async def help_test_availability_without_topic(hass, mqtt_mock, domain, config): """Test availability without defined availability topic.""" assert "availability_topic" not in config[domain] assert await async_setup_component(hass, domain, config) + await hass.async_block_till_done() state = hass.states.get(f"{domain}.test") assert state.state != STATE_UNAVAILABLE @@ -61,6 +62,7 @@ async def help_test_default_availability_payload( config = copy.deepcopy(config) config[domain]["availability_topic"] = "availability-topic" assert await async_setup_component(hass, domain, config,) + await hass.async_block_till_done() state = hass.states.get(f"{domain}.test") assert state.state == STATE_UNAVAILABLE @@ -108,6 +110,7 @@ async def help_test_custom_availability_payload( config[domain]["payload_available"] = "good" config[domain]["payload_not_available"] = "nogood" assert await async_setup_component(hass, domain, config,) + await hass.async_block_till_done() state = hass.states.get(f"{domain}.test") assert state.state == STATE_UNAVAILABLE @@ -147,6 +150,7 @@ async def help_test_setting_attribute_via_mqtt_json_message( config = copy.deepcopy(config) config[domain]["json_attributes_topic"] = "attr-topic" assert await async_setup_component(hass, domain, config,) + await hass.async_block_till_done() async_fire_mqtt_message(hass, "attr-topic", '{ "val": "100" }') state = hass.states.get(f"{domain}.test") @@ -164,6 +168,7 @@ async def help_test_setting_attribute_with_template(hass, mqtt_mock, domain, con config[domain]["json_attributes_topic"] = "attr-topic" config[domain]["json_attributes_template"] = "{{ value_json['Timer1'] | tojson }}" assert await async_setup_component(hass, domain, config,) + await hass.async_block_till_done() async_fire_mqtt_message( hass, "attr-topic", json.dumps({"Timer1": {"Arm": 0, "Time": "22:18"}}) @@ -185,6 +190,7 @@ async def help_test_update_with_json_attrs_not_dict( config = copy.deepcopy(config) config[domain]["json_attributes_topic"] = "attr-topic" assert await async_setup_component(hass, domain, config,) + await hass.async_block_till_done() async_fire_mqtt_message(hass, "attr-topic", '[ "list", "of", "things"]') state = hass.states.get(f"{domain}.test") @@ -204,6 +210,7 @@ async def help_test_update_with_json_attrs_bad_JSON( config = copy.deepcopy(config) config[domain]["json_attributes_topic"] = "attr-topic" assert await async_setup_component(hass, domain, config,) + await hass.async_block_till_done() async_fire_mqtt_message(hass, "attr-topic", "This is not JSON") @@ -251,7 +258,8 @@ async def help_test_discovery_update_attr(hass, mqtt_mock, caplog, domain, confi async def help_test_unique_id(hass, domain, config): """Test unique id option only creates one entity per unique_id.""" await async_mock_mqtt_component(hass) - assert await async_setup_component(hass, domain, config,) + assert await async_setup_component(hass, domain, config) + await hass.async_block_till_done() assert len(hass.states.async_entity_ids(domain)) == 1 @@ -459,6 +467,7 @@ async def help_test_entity_id_update_subscriptions( registry = mock_registry(hass, {}) mock_mqtt = await async_mock_mqtt_component(hass) assert await async_setup_component(hass, domain, config,) + await hass.async_block_till_done() state = hass.states.get(f"{domain}.test") assert state is not None diff --git a/tests/components/mqtt/test_cover.py b/tests/components/mqtt/test_cover.py index 201bb17c7a8..eb758ebf93a 100644 --- a/tests/components/mqtt/test_cover.py +++ b/tests/components/mqtt/test_cover.py @@ -76,6 +76,7 @@ async def test_state_via_state_topic(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() state = hass.states.get("cover.test") assert state.state == STATE_UNKNOWN @@ -112,6 +113,7 @@ async def test_opening_and_closing_state_via_custom_state_payload(hass, mqtt_moc } }, ) + await hass.async_block_till_done() state = hass.states.get("cover.test") assert state.state == STATE_UNKNOWN @@ -152,6 +154,7 @@ async def test_open_closed_state_from_position_optimistic(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() state = hass.states.get("cover.test") assert state.state == STATE_UNKNOWN @@ -199,6 +202,7 @@ async def test_position_via_position_topic(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() state = hass.states.get("cover.test") assert state.state == STATE_UNKNOWN @@ -236,6 +240,7 @@ async def test_state_via_template(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() state = hass.states.get("cover.test") assert state.state == STATE_UNKNOWN @@ -267,6 +272,7 @@ async def test_position_via_template(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() state = hass.states.get("cover.test") assert state.state == STATE_UNKNOWN @@ -301,6 +307,7 @@ async def test_optimistic_state_change(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() state = hass.states.get("cover.test") assert state.state == STATE_UNKNOWN @@ -358,6 +365,7 @@ async def test_optimistic_state_change_with_position(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() state = hass.states.get("cover.test") assert state.state == STATE_UNKNOWN @@ -419,6 +427,7 @@ async def test_send_open_cover_command(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() state = hass.states.get("cover.test") assert state.state == STATE_UNKNOWN @@ -447,6 +456,7 @@ async def test_send_close_cover_command(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() state = hass.states.get("cover.test") assert state.state == STATE_UNKNOWN @@ -475,6 +485,7 @@ async def test_send_stop__cover_command(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() state = hass.states.get("cover.test") assert state.state == STATE_UNKNOWN @@ -507,6 +518,7 @@ async def test_current_cover_position(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() state_attributes_dict = hass.states.get("cover.test").attributes assert not (ATTR_CURRENT_POSITION in state_attributes_dict) @@ -557,6 +569,7 @@ async def test_current_cover_position_inverted(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() state_attributes_dict = hass.states.get("cover.test").attributes assert not (ATTR_CURRENT_POSITION in state_attributes_dict) @@ -613,6 +626,7 @@ async def test_optimistic_position(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() state = hass.states.get("cover.test") assert state is None @@ -638,6 +652,7 @@ async def test_position_update(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() state_attributes_dict = hass.states.get("cover.test").attributes assert not (ATTR_CURRENT_POSITION in state_attributes_dict) @@ -675,6 +690,7 @@ async def test_set_position_templated(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() await hass.services.async_call( cover.DOMAIN, @@ -706,6 +722,7 @@ async def test_set_position_untemplated(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() await hass.services.async_call( cover.DOMAIN, @@ -737,6 +754,7 @@ async def test_set_position_untemplated_custom_percentage_range(hass, mqtt_mock) } }, ) + await hass.async_block_till_done() await hass.services.async_call( cover.DOMAIN, @@ -766,6 +784,7 @@ async def test_no_command_topic(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() assert hass.states.get("cover.test").attributes["supported_features"] == 240 @@ -787,6 +806,7 @@ async def test_no_payload_stop(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() assert hass.states.get("cover.test").attributes["supported_features"] == 3 @@ -810,6 +830,7 @@ async def test_with_command_topic_and_tilt(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() assert hass.states.get("cover.test").attributes["supported_features"] == 251 @@ -834,6 +855,7 @@ async def test_tilt_defaults(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() state_attributes_dict = hass.states.get("cover.test").attributes assert ATTR_CURRENT_TILT_POSITION in state_attributes_dict @@ -864,6 +886,7 @@ async def test_tilt_via_invocation_defaults(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() await hass.services.async_call( cover.DOMAIN, @@ -943,6 +966,7 @@ async def test_tilt_given_value(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() await hass.services.async_call( cover.DOMAIN, @@ -1023,6 +1047,7 @@ async def test_tilt_given_value_optimistic(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() await hass.services.async_call( cover.DOMAIN, @@ -1079,6 +1104,7 @@ async def test_tilt_given_value_altered_range(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() await hass.services.async_call( cover.DOMAIN, @@ -1145,6 +1171,7 @@ async def test_tilt_via_topic(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() async_fire_mqtt_message(hass, "tilt-status-topic", "0") @@ -1184,6 +1211,7 @@ async def test_tilt_via_topic_template(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() async_fire_mqtt_message(hass, "tilt-status-topic", "99") @@ -1222,6 +1250,7 @@ async def test_tilt_via_topic_altered_range(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() async_fire_mqtt_message(hass, "tilt-status-topic", "0") @@ -1270,6 +1299,7 @@ async def test_tilt_via_topic_template_altered_range(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() async_fire_mqtt_message(hass, "tilt-status-topic", "99") @@ -1313,6 +1343,7 @@ async def test_tilt_position(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() await hass.services.async_call( cover.DOMAIN, @@ -1348,6 +1379,7 @@ async def test_tilt_position_altered_range(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() await hass.services.async_call( cover.DOMAIN, @@ -1738,6 +1770,7 @@ async def test_valid_device_class(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() state = hass.states.get("cover.test") assert state.attributes.get("device_class") == "garage" @@ -1757,6 +1790,7 @@ async def test_invalid_device_class(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() state = hass.states.get("cover.test") assert state is None diff --git a/tests/components/mqtt/test_fan.py b/tests/components/mqtt/test_fan.py index d8b6ce00ee6..7f6eb79e85e 100644 --- a/tests/components/mqtt/test_fan.py +++ b/tests/components/mqtt/test_fan.py @@ -50,6 +50,7 @@ async def test_fail_setup_if_no_command_topic(hass, mqtt_mock): assert await async_setup_component( hass, fan.DOMAIN, {fan.DOMAIN: {"platform": "mqtt", "name": "test"}} ) + await hass.async_block_till_done() assert hass.states.get("fan.test") is None @@ -79,6 +80,7 @@ async def test_controlling_state_via_topic(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() state = hass.states.get("fan.test") assert state.state is STATE_OFF @@ -141,6 +143,7 @@ async def test_controlling_state_via_topic_and_json_message(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() state = hass.states.get("fan.test") assert state.state is STATE_OFF @@ -207,6 +210,7 @@ async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() state = hass.states.get("fan.test") assert state.state is STATE_OFF @@ -300,6 +304,7 @@ async def test_on_sending_mqtt_commands_and_optimistic(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() state = hass.states.get("fan.test") assert state.state is STATE_OFF @@ -352,6 +357,7 @@ async def test_sending_mqtt_commands_and_explicit_optimistic(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() state = hass.states.get("fan.test") assert state.state is STATE_OFF @@ -450,6 +456,7 @@ async def test_attributes(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() state = hass.states.get("fan.test") assert state.state is STATE_OFF @@ -537,6 +544,7 @@ async def test_custom_speed_list(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() state = hass.states.get("fan.test") assert state.state is STATE_OFF @@ -577,6 +585,7 @@ async def test_supported_features(hass, mqtt_mock): ] }, ) + await hass.async_block_till_done() state = hass.states.get("fan.test1") assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == 0 diff --git a/tests/components/mqtt/test_legacy_vacuum.py b/tests/components/mqtt/test_legacy_vacuum.py index b402c23e299..032a55edee4 100644 --- a/tests/components/mqtt/test_legacy_vacuum.py +++ b/tests/components/mqtt/test_legacy_vacuum.py @@ -76,6 +76,7 @@ async def test_default_supported_features(hass, mqtt_mock): assert await async_setup_component( hass, vacuum.DOMAIN, {vacuum.DOMAIN: DEFAULT_CONFIG} ) + await hass.async_block_till_done() entity = hass.states.get("vacuum.mqtttest") entity_features = entity.attributes.get(mqttvacuum.CONF_SUPPORTED_FEATURES, 0) assert sorted(services_to_strings(entity_features, SERVICE_TO_STRING)) == sorted( @@ -99,6 +100,7 @@ async def test_all_commands(hass, mqtt_mock): ) assert await async_setup_component(hass, vacuum.DOMAIN, {vacuum.DOMAIN: config}) + await hass.async_block_till_done() await common.async_turn_on(hass, "vacuum.mqtttest") mqtt_mock.async_publish.assert_called_once_with( @@ -178,6 +180,7 @@ async def test_commands_without_supported_features(hass, mqtt_mock): ) assert await async_setup_component(hass, vacuum.DOMAIN, {vacuum.DOMAIN: config}) + await hass.async_block_till_done() await common.async_turn_on(hass, "vacuum.mqtttest") mqtt_mock.async_publish.assert_not_called() @@ -225,6 +228,7 @@ async def test_attributes_without_supported_features(hass, mqtt_mock): ) assert await async_setup_component(hass, vacuum.DOMAIN, {vacuum.DOMAIN: config}) + await hass.async_block_till_done() message = """{ "battery_level": 54, @@ -250,6 +254,7 @@ async def test_status(hass, mqtt_mock): ) assert await async_setup_component(hass, vacuum.DOMAIN, {vacuum.DOMAIN: config}) + await hass.async_block_till_done() message = """{ "battery_level": 54, @@ -289,6 +294,7 @@ async def test_status_battery(hass, mqtt_mock): ) assert await async_setup_component(hass, vacuum.DOMAIN, {vacuum.DOMAIN: config}) + await hass.async_block_till_done() message = """{ "battery_level": 54 @@ -306,6 +312,7 @@ async def test_status_cleaning(hass, mqtt_mock): ) assert await async_setup_component(hass, vacuum.DOMAIN, {vacuum.DOMAIN: config}) + await hass.async_block_till_done() message = """{ "cleaning": true @@ -323,6 +330,7 @@ async def test_status_docked(hass, mqtt_mock): ) assert await async_setup_component(hass, vacuum.DOMAIN, {vacuum.DOMAIN: config}) + await hass.async_block_till_done() message = """{ "docked": true @@ -340,6 +348,7 @@ async def test_status_charging(hass, mqtt_mock): ) assert await async_setup_component(hass, vacuum.DOMAIN, {vacuum.DOMAIN: config}) + await hass.async_block_till_done() message = """{ "charging": true @@ -357,6 +366,7 @@ async def test_status_fan_speed(hass, mqtt_mock): ) assert await async_setup_component(hass, vacuum.DOMAIN, {vacuum.DOMAIN: config}) + await hass.async_block_till_done() message = """{ "fan_speed": "max" @@ -374,6 +384,7 @@ async def test_status_fan_speed_list(hass, mqtt_mock): ) assert await async_setup_component(hass, vacuum.DOMAIN, {vacuum.DOMAIN: config}) + await hass.async_block_till_done() state = hass.states.get("vacuum.mqtttest") assert state.attributes.get(ATTR_FAN_SPEED_LIST) == ["min", "medium", "high", "max"] @@ -391,6 +402,7 @@ async def test_status_no_fan_speed_list(hass, mqtt_mock): ) assert await async_setup_component(hass, vacuum.DOMAIN, {vacuum.DOMAIN: config}) + await hass.async_block_till_done() state = hass.states.get("vacuum.mqtttest") assert state.attributes.get(ATTR_FAN_SPEED_LIST) is None @@ -404,6 +416,7 @@ async def test_status_error(hass, mqtt_mock): ) assert await async_setup_component(hass, vacuum.DOMAIN, {vacuum.DOMAIN: config}) + await hass.async_block_till_done() message = """{ "error": "Error1" @@ -434,6 +447,7 @@ async def test_battery_template(hass, mqtt_mock): ) assert await async_setup_component(hass, vacuum.DOMAIN, {vacuum.DOMAIN: config}) + await hass.async_block_till_done() async_fire_mqtt_message(hass, "retroroomba/battery_level", "54") state = hass.states.get("vacuum.mqtttest") @@ -449,6 +463,7 @@ async def test_status_invalid_json(hass, mqtt_mock): ) assert await async_setup_component(hass, vacuum.DOMAIN, {vacuum.DOMAIN: config}) + await hass.async_block_till_done() async_fire_mqtt_message(hass, "vacuum/state", '{"asdfasas false}') state = hass.states.get("vacuum.mqtttest") @@ -462,6 +477,7 @@ async def test_missing_battery_template(hass, mqtt_mock): config.pop(mqttvacuum.CONF_BATTERY_LEVEL_TEMPLATE) assert await async_setup_component(hass, vacuum.DOMAIN, {vacuum.DOMAIN: config}) + await hass.async_block_till_done() state = hass.states.get("vacuum.mqtttest") assert state is None @@ -473,6 +489,7 @@ async def test_missing_charging_template(hass, mqtt_mock): config.pop(mqttvacuum.CONF_CHARGING_TEMPLATE) assert await async_setup_component(hass, vacuum.DOMAIN, {vacuum.DOMAIN: config}) + await hass.async_block_till_done() state = hass.states.get("vacuum.mqtttest") assert state is None @@ -484,6 +501,7 @@ async def test_missing_cleaning_template(hass, mqtt_mock): config.pop(mqttvacuum.CONF_CLEANING_TEMPLATE) assert await async_setup_component(hass, vacuum.DOMAIN, {vacuum.DOMAIN: config}) + await hass.async_block_till_done() state = hass.states.get("vacuum.mqtttest") assert state is None @@ -495,6 +513,7 @@ async def test_missing_docked_template(hass, mqtt_mock): config.pop(mqttvacuum.CONF_DOCKED_TEMPLATE) assert await async_setup_component(hass, vacuum.DOMAIN, {vacuum.DOMAIN: config}) + await hass.async_block_till_done() state = hass.states.get("vacuum.mqtttest") assert state is None @@ -506,6 +525,7 @@ async def test_missing_error_template(hass, mqtt_mock): config.pop(mqttvacuum.CONF_ERROR_TEMPLATE) assert await async_setup_component(hass, vacuum.DOMAIN, {vacuum.DOMAIN: config}) + await hass.async_block_till_done() state = hass.states.get("vacuum.mqtttest") assert state is None @@ -517,6 +537,7 @@ async def test_missing_fan_speed_template(hass, mqtt_mock): config.pop(mqttvacuum.CONF_FAN_SPEED_TEMPLATE) assert await async_setup_component(hass, vacuum.DOMAIN, {vacuum.DOMAIN: config}) + await hass.async_block_till_done() state = hass.states.get("vacuum.mqtttest") assert state is None diff --git a/tests/components/mqtt/test_light.py b/tests/components/mqtt/test_light.py index 7cf034ec4e1..f832e235915 100644 --- a/tests/components/mqtt/test_light.py +++ b/tests/components/mqtt/test_light.py @@ -201,6 +201,7 @@ async def test_fail_setup_if_no_command_topic(hass, mqtt_mock): assert await async_setup_component( hass, light.DOMAIN, {light.DOMAIN: {"platform": "mqtt", "name": "test"}} ) + await hass.async_block_till_done() assert hass.states.get("light.test") is None @@ -218,6 +219,7 @@ async def test_no_color_brightness_color_temp_hs_white_xy_if_no_topics(hass, mqt } }, ) + await hass.async_block_till_done() state = hass.states.get("light.test") assert state.state == STATE_OFF @@ -269,6 +271,7 @@ async def test_controlling_state_via_topic(hass, mqtt_mock): } assert await async_setup_component(hass, light.DOMAIN, config) + await hass.async_block_till_done() state = hass.states.get("light.test") assert state.state == STATE_OFF @@ -365,6 +368,7 @@ async def test_invalid_state_via_topic(hass, mqtt_mock, caplog): } assert await async_setup_component(hass, light.DOMAIN, config) + await hass.async_block_till_done() state = hass.states.get("light.test") assert state.state == STATE_OFF @@ -456,6 +460,7 @@ async def test_brightness_controlling_scale(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() state = hass.states.get("light.test") assert state.state == STATE_OFF @@ -501,6 +506,7 @@ async def test_brightness_from_rgb_controlling_scale(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() state = hass.states.get("light.test") assert state.state == STATE_OFF @@ -540,6 +546,7 @@ async def test_white_value_controlling_scale(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() state = hass.states.get("light.test") assert state.state == STATE_OFF @@ -599,6 +606,7 @@ async def test_controlling_state_via_topic_with_templates(hass, mqtt_mock): } assert await async_setup_component(hass, light.DOMAIN, config) + await hass.async_block_till_done() state = hass.states.get("light.test") assert state.state == STATE_OFF @@ -676,6 +684,7 @@ async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock): ): with assert_setup_component(1, light.DOMAIN): assert await async_setup_component(hass, light.DOMAIN, config) + await hass.async_block_till_done() state = hass.states.get("light.test") assert state.state == STATE_ON @@ -751,6 +760,7 @@ async def test_sending_mqtt_rgb_command_with_template(hass, mqtt_mock): } assert await async_setup_component(hass, light.DOMAIN, config) + await hass.async_block_till_done() state = hass.states.get("light.test") assert state.state == STATE_OFF @@ -786,6 +796,7 @@ async def test_sending_mqtt_color_temp_command_with_template(hass, mqtt_mock): } assert await async_setup_component(hass, light.DOMAIN, config) + await hass.async_block_till_done() state = hass.states.get("light.test") assert state.state == STATE_OFF @@ -818,6 +829,7 @@ async def test_show_brightness_if_only_command_topic(hass, mqtt_mock): } assert await async_setup_component(hass, light.DOMAIN, config) + await hass.async_block_till_done() state = hass.states.get("light.test") assert state.state == STATE_OFF @@ -843,6 +855,7 @@ async def test_show_color_temp_only_if_command_topic(hass, mqtt_mock): } assert await async_setup_component(hass, light.DOMAIN, config) + await hass.async_block_till_done() state = hass.states.get("light.test") assert state.state == STATE_OFF @@ -868,6 +881,7 @@ async def test_show_effect_only_if_command_topic(hass, mqtt_mock): } assert await async_setup_component(hass, light.DOMAIN, config) + await hass.async_block_till_done() state = hass.states.get("light.test") assert state.state == STATE_OFF @@ -893,6 +907,7 @@ async def test_show_hs_if_only_command_topic(hass, mqtt_mock): } assert await async_setup_component(hass, light.DOMAIN, config) + await hass.async_block_till_done() state = hass.states.get("light.test") assert state.state == STATE_OFF @@ -918,6 +933,7 @@ async def test_show_white_value_if_only_command_topic(hass, mqtt_mock): } assert await async_setup_component(hass, light.DOMAIN, config) + await hass.async_block_till_done() state = hass.states.get("light.test") assert state.state == STATE_OFF @@ -943,6 +959,7 @@ async def test_show_xy_if_only_command_topic(hass, mqtt_mock): } assert await async_setup_component(hass, light.DOMAIN, config) + await hass.async_block_till_done() state = hass.states.get("light.test") assert state.state == STATE_OFF @@ -968,6 +985,7 @@ async def test_on_command_first(hass, mqtt_mock): } assert await async_setup_component(hass, light.DOMAIN, config) + await hass.async_block_till_done() state = hass.states.get("light.test") assert state.state == STATE_OFF @@ -1002,6 +1020,7 @@ async def test_on_command_last(hass, mqtt_mock): } assert await async_setup_component(hass, light.DOMAIN, config) + await hass.async_block_till_done() state = hass.states.get("light.test") assert state.state == STATE_OFF @@ -1038,6 +1057,7 @@ async def test_on_command_brightness(hass, mqtt_mock): } assert await async_setup_component(hass, light.DOMAIN, config) + await hass.async_block_till_done() state = hass.states.get("light.test") assert state.state == STATE_OFF @@ -1091,6 +1111,7 @@ async def test_on_command_brightness_scaled(hass, mqtt_mock): } assert await async_setup_component(hass, light.DOMAIN, config) + await hass.async_block_till_done() state = hass.states.get("light.test") assert state.state == STATE_OFF @@ -1153,6 +1174,7 @@ async def test_on_command_rgb(hass, mqtt_mock): } assert await async_setup_component(hass, light.DOMAIN, config) + await hass.async_block_till_done() state = hass.states.get("light.test") assert state.state == STATE_OFF @@ -1243,6 +1265,7 @@ async def test_on_command_rgb_template(hass, mqtt_mock): } assert await async_setup_component(hass, light.DOMAIN, config) + await hass.async_block_till_done() state = hass.states.get("light.test") assert state.state == STATE_OFF @@ -1279,6 +1302,7 @@ async def test_effect(hass, mqtt_mock): } assert await async_setup_component(hass, light.DOMAIN, config) + await hass.async_block_till_done() state = hass.states.get("light.test") assert state.state == STATE_OFF @@ -1498,6 +1522,7 @@ async def test_max_mireds(hass, mqtt_mock): } assert await async_setup_component(hass, light.DOMAIN, config) + await hass.async_block_till_done() state = hass.states.get("light.test") assert state.attributes.get("min_mireds") == 153 diff --git a/tests/components/mqtt/test_light_json.py b/tests/components/mqtt/test_light_json.py index b98282b5288..bb9e2afb0e5 100644 --- a/tests/components/mqtt/test_light_json.py +++ b/tests/components/mqtt/test_light_json.py @@ -156,6 +156,7 @@ async def test_fail_setup_if_no_command_topic(hass, mqtt_mock): light.DOMAIN, {light.DOMAIN: {"platform": "mqtt", "schema": "json", "name": "test"}}, ) + await hass.async_block_till_done() assert hass.states.get("light.test") is None @@ -174,6 +175,7 @@ async def test_no_color_brightness_color_temp_white_val_if_no_topics(hass, mqtt_ } }, ) + await hass.async_block_till_done() state = hass.states.get("light.test") assert state.state == STATE_OFF @@ -222,6 +224,7 @@ async def test_controlling_state_via_topic(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() state = hass.states.get("light.test") assert state.state == STATE_OFF @@ -346,6 +349,7 @@ async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() state = hass.states.get("light.test") assert state.state == STATE_ON @@ -457,6 +461,7 @@ async def test_sending_hs_color(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() state = hass.states.get("light.test") assert state.state == STATE_OFF @@ -519,6 +524,7 @@ async def test_sending_rgb_color_no_brightness(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() state = hass.states.get("light.test") assert state.state == STATE_OFF @@ -572,6 +578,7 @@ async def test_sending_rgb_color_with_brightness(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() state = hass.states.get("light.test") assert state.state == STATE_OFF @@ -641,6 +648,7 @@ async def test_sending_rgb_color_with_scaled_brightness(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() state = hass.states.get("light.test") assert state.state == STATE_OFF @@ -709,6 +717,7 @@ async def test_sending_xy_color(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() state = hass.states.get("light.test") assert state.state == STATE_OFF @@ -771,6 +780,7 @@ async def test_effect(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() state = hass.states.get("light.test") assert state.state == STATE_OFF @@ -830,6 +840,7 @@ async def test_flash_short_and_long(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() state = hass.states.get("light.test") assert state.state == STATE_OFF @@ -869,6 +880,7 @@ async def test_transition(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() state = hass.states.get("light.test") assert state.state == STATE_OFF @@ -916,6 +928,7 @@ async def test_brightness_scale(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() state = hass.states.get("light.test") assert state.state == STATE_OFF @@ -959,6 +972,7 @@ async def test_invalid_values(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() state = hass.states.get("light.test") assert state.state == STATE_OFF @@ -1235,6 +1249,7 @@ async def test_max_mireds(hass, mqtt_mock): } assert await async_setup_component(hass, light.DOMAIN, config) + await hass.async_block_till_done() state = hass.states.get("light.test") assert state.attributes.get("min_mireds") == 153 diff --git a/tests/components/mqtt/test_light_template.py b/tests/components/mqtt/test_light_template.py index edb7900e0da..cb5aff40b4b 100644 --- a/tests/components/mqtt/test_light_template.py +++ b/tests/components/mqtt/test_light_template.py @@ -84,6 +84,7 @@ async def test_setup_fails(hass, mqtt_mock): light.DOMAIN, {light.DOMAIN: {"platform": "mqtt", "schema": "template", "name": "test"}}, ) + await hass.async_block_till_done() assert hass.states.get("light.test") is None with assert_setup_component(0, light.DOMAIN): @@ -99,6 +100,7 @@ async def test_setup_fails(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() assert hass.states.get("light.test") is None with assert_setup_component(0, light.DOMAIN): @@ -115,6 +117,7 @@ async def test_setup_fails(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() assert hass.states.get("light.test") is None with assert_setup_component(0, light.DOMAIN): @@ -131,6 +134,7 @@ async def test_setup_fails(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() assert hass.states.get("light.test") is None @@ -159,6 +163,7 @@ async def test_state_change_via_topic(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() state = hass.states.get("light.test") assert state.state == STATE_OFF @@ -214,6 +219,7 @@ async def test_state_brightness_color_effect_temp_white_change_via_topic( } }, ) + await hass.async_block_till_done() state = hass.states.get("light.test") assert state.state == STATE_OFF @@ -313,6 +319,7 @@ async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() state = hass.states.get("light.test") assert state.state == STATE_ON @@ -454,6 +461,7 @@ async def test_sending_mqtt_commands_non_optimistic_brightness_template( } }, ) + await hass.async_block_till_done() state = hass.states.get("light.test") assert state.state == STATE_OFF @@ -567,6 +575,7 @@ async def test_effect(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() state = hass.states.get("light.test") assert state.state == STATE_OFF @@ -617,6 +626,7 @@ async def test_flash(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() state = hass.states.get("light.test") assert state.state == STATE_OFF @@ -664,6 +674,7 @@ async def test_transition(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() state = hass.states.get("light.test") assert state.state == STATE_OFF @@ -719,6 +730,7 @@ async def test_invalid_values(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() state = hass.states.get("light.test") assert state.state == STATE_OFF @@ -994,6 +1006,7 @@ async def test_max_mireds(hass, mqtt_mock): } assert await async_setup_component(hass, light.DOMAIN, config) + await hass.async_block_till_done() state = hass.states.get("light.test") assert state.attributes.get("min_mireds") == 153 diff --git a/tests/components/mqtt/test_lock.py b/tests/components/mqtt/test_lock.py index 80ecbde3c4d..0e9a9af850f 100644 --- a/tests/components/mqtt/test_lock.py +++ b/tests/components/mqtt/test_lock.py @@ -58,6 +58,7 @@ async def test_controlling_state_via_topic(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() state = hass.states.get("lock.test") assert state.state is STATE_UNLOCKED @@ -92,6 +93,7 @@ async def test_controlling_non_default_state_via_topic(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() state = hass.states.get("lock.test") assert state.state is STATE_UNLOCKED @@ -127,6 +129,7 @@ async def test_controlling_state_via_topic_and_json_message(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() state = hass.states.get("lock.test") assert state.state is STATE_UNLOCKED @@ -163,6 +166,7 @@ async def test_controlling_non_default_state_via_topic_and_json_message( } }, ) + await hass.async_block_till_done() state = hass.states.get("lock.test") assert state.state is STATE_UNLOCKED @@ -195,6 +199,7 @@ async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() state = hass.states.get("lock.test") assert state.state is STATE_UNLOCKED @@ -240,6 +245,7 @@ async def test_sending_mqtt_commands_and_explicit_optimistic(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() state = hass.states.get("lock.test") assert state.state is STATE_UNLOCKED diff --git a/tests/components/mqtt/test_sensor.py b/tests/components/mqtt/test_sensor.py index 58c98f02484..d711b9e3bb8 100644 --- a/tests/components/mqtt/test_sensor.py +++ b/tests/components/mqtt/test_sensor.py @@ -64,6 +64,7 @@ async def test_setting_sensor_value_via_mqtt_message(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() async_fire_mqtt_message(hass, "test-topic", "100") state = hass.states.get("sensor.test") @@ -88,6 +89,7 @@ async def test_setting_sensor_value_expires(hass, mqtt_mock, caplog): } }, ) + await hass.async_block_till_done() state = hass.states.get("sensor.test") assert state.state == "unknown" @@ -155,6 +157,7 @@ async def test_setting_sensor_value_via_mqtt_json_message(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() async_fire_mqtt_message(hass, "test-topic", '{ "val": "100" }') state = hass.states.get("sensor.test") @@ -176,6 +179,7 @@ async def test_force_update_disabled(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() events = [] @@ -209,6 +213,7 @@ async def test_force_update_enabled(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() events = [] @@ -262,6 +267,7 @@ async def test_invalid_device_class(hass, mqtt_mock): } }, ) + await hass.async_block_till_done() state = hass.states.get("sensor.test") assert state is None diff --git a/tests/components/mqtt/test_state_vacuum.py b/tests/components/mqtt/test_state_vacuum.py index 15429e6bc57..f77a1a11ca1 100644 --- a/tests/components/mqtt/test_state_vacuum.py +++ b/tests/components/mqtt/test_state_vacuum.py @@ -82,6 +82,7 @@ async def test_default_supported_features(hass, mqtt_mock): assert await async_setup_component( hass, vacuum.DOMAIN, {vacuum.DOMAIN: DEFAULT_CONFIG} ) + await hass.async_block_till_done() entity = hass.states.get("vacuum.mqtttest") entity_features = entity.attributes.get(mqttvacuum.CONF_SUPPORTED_FEATURES, 0) assert sorted(services_to_strings(entity_features, SERVICE_TO_STRING)) == sorted( @@ -97,6 +98,7 @@ async def test_all_commands(hass, mqtt_mock): ) assert await async_setup_component(hass, vacuum.DOMAIN, {vacuum.DOMAIN: config}) + await hass.async_block_till_done() await hass.services.async_call( DOMAIN, SERVICE_START, {"entity_id": ENTITY_MATCH_ALL}, blocking=True @@ -168,6 +170,7 @@ async def test_commands_without_supported_features(hass, mqtt_mock): ) assert await async_setup_component(hass, vacuum.DOMAIN, {vacuum.DOMAIN: config}) + await hass.async_block_till_done() await hass.services.async_call( DOMAIN, SERVICE_START, {"entity_id": ENTITY_MATCH_ALL}, blocking=True @@ -223,6 +226,7 @@ async def test_status(hass, mqtt_mock): ) assert await async_setup_component(hass, vacuum.DOMAIN, {vacuum.DOMAIN: config}) + await hass.async_block_till_done() message = """{ "battery_level": 54, @@ -260,6 +264,7 @@ async def test_no_fan_vacuum(hass, mqtt_mock): ) assert await async_setup_component(hass, vacuum.DOMAIN, {vacuum.DOMAIN: config}) + await hass.async_block_till_done() message = """{ "battery_level": 54, @@ -309,6 +314,7 @@ async def test_status_invalid_json(hass, mqtt_mock): ) assert await async_setup_component(hass, vacuum.DOMAIN, {vacuum.DOMAIN: config}) + await hass.async_block_till_done() async_fire_mqtt_message(hass, "vacuum/state", '{"asdfasas false}') state = hass.states.get("vacuum.mqtttest") diff --git a/tests/components/mqtt/test_switch.py b/tests/components/mqtt/test_switch.py index b51812d2fa3..da66e2a7f60 100644 --- a/tests/components/mqtt/test_switch.py +++ b/tests/components/mqtt/test_switch.py @@ -59,6 +59,7 @@ async def test_controlling_state_via_topic(hass, mock_publish): } }, ) + await hass.async_block_till_done() state = hass.states.get("switch.test") assert state.state == STATE_OFF @@ -97,6 +98,7 @@ async def test_sending_mqtt_commands_and_optimistic(hass, mock_publish): } }, ) + await hass.async_block_till_done() state = hass.states.get("switch.test") assert state.state == STATE_ON @@ -137,6 +139,7 @@ async def test_controlling_state_via_topic_and_json_message(hass, mock_publish): } }, ) + await hass.async_block_till_done() state = hass.states.get("switch.test") assert state.state == STATE_OFF @@ -213,6 +216,7 @@ async def test_custom_state_payload(hass, mock_publish): } }, ) + await hass.async_block_till_done() state = hass.states.get("switch.test") assert state.state == STATE_OFF diff --git a/tests/components/mqtt_room/test_sensor.py b/tests/components/mqtt_room/test_sensor.py index f11786951f6..20aa34342d3 100644 --- a/tests/components/mqtt_room/test_sensor.py +++ b/tests/components/mqtt_room/test_sensor.py @@ -68,6 +68,7 @@ async def test_room_update(hass): } }, ) + await hass.async_block_till_done() await send_message(hass, BEDROOM_TOPIC, FAR_MESSAGE) await assert_state(hass, BEDROOM) diff --git a/tests/components/nextbus/test_sensor.py b/tests/components/nextbus/test_sensor.py index dd709618ec0..74ea6cce127 100644 --- a/tests/components/nextbus/test_sensor.py +++ b/tests/components/nextbus/test_sensor.py @@ -49,6 +49,7 @@ async def assert_setup_sensor(hass, config, count=1): """Set up the sensor and assert it's been created.""" with assert_setup_component(count): assert await async_setup_component(hass, sensor.DOMAIN, config) + await hass.async_block_till_done() @pytest.fixture diff --git a/tests/components/nsw_fuel_station/test_sensor.py b/tests/components/nsw_fuel_station/test_sensor.py index 2d348204dcc..861aa155f4f 100644 --- a/tests/components/nsw_fuel_station/test_sensor.py +++ b/tests/components/nsw_fuel_station/test_sensor.py @@ -92,6 +92,7 @@ class TestNSWFuelStation(unittest.TestCase): """Test the setup with custom settings.""" with assert_setup_component(1, sensor.DOMAIN): assert setup_component(self.hass, sensor.DOMAIN, {"sensor": VALID_CONFIG}) + self.hass.block_till_done() fake_entities = ["my_fake_station_p95", "my_fake_station_e10"] @@ -106,6 +107,7 @@ class TestNSWFuelStation(unittest.TestCase): def test_sensor_values(self): """Test retrieval of sensor values.""" assert setup_component(self.hass, sensor.DOMAIN, {"sensor": VALID_CONFIG}) + self.hass.block_till_done() assert "140.0" == self.hass.states.get("sensor.my_fake_station_e10").state assert "150.0" == self.hass.states.get("sensor.my_fake_station_p95").state 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 584616967c4..b8923d854ee 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 @@ -126,6 +126,7 @@ async def test_setup(hass): ) with assert_setup_component(1, geo_location.DOMAIN): assert await async_setup_component(hass, geo_location.DOMAIN, CONFIG) + await hass.async_block_till_done() # Artificially trigger update. hass.bus.async_fire(EVENT_HOMEASSISTANT_START) # Collect events. @@ -242,6 +243,7 @@ async def test_setup_with_custom_location(hass): assert await async_setup_component( hass, geo_location.DOMAIN, CONFIG_WITH_CUSTOM_LOCATION ) + await hass.async_block_till_done() # Artificially trigger update. hass.bus.async_fire(EVENT_HOMEASSISTANT_START) diff --git a/tests/components/openalpr_cloud/test_image_processing.py b/tests/components/openalpr_cloud/test_image_processing.py index c164f2f03a2..7f285d150b7 100644 --- a/tests/components/openalpr_cloud/test_image_processing.py +++ b/tests/components/openalpr_cloud/test_image_processing.py @@ -36,6 +36,7 @@ class TestOpenAlprCloudSetup: with assert_setup_component(1, ip.DOMAIN): setup_component(self.hass, ip.DOMAIN, config) + self.hass.block_till_done() assert self.hass.states.get("image_processing.openalpr_demo_camera") @@ -53,6 +54,7 @@ class TestOpenAlprCloudSetup: with assert_setup_component(1, ip.DOMAIN): setup_component(self.hass, ip.DOMAIN, config) + self.hass.block_till_done() assert self.hass.states.get("image_processing.test_local") @@ -108,6 +110,7 @@ class TestOpenAlprCloud: new_callable=PropertyMock(return_value=False), ): setup_component(self.hass, ip.DOMAIN, config) + self.hass.block_till_done() self.alpr_events = [] diff --git a/tests/components/openalpr_local/test_image_processing.py b/tests/components/openalpr_local/test_image_processing.py index 996d23184a2..bcf4fb8aafd 100644 --- a/tests/components/openalpr_local/test_image_processing.py +++ b/tests/components/openalpr_local/test_image_processing.py @@ -46,6 +46,7 @@ class TestOpenAlprLocalSetup: with assert_setup_component(1, ip.DOMAIN): setup_component(self.hass, ip.DOMAIN, config) + self.hass.block_till_done() assert self.hass.states.get("image_processing.openalpr_demo_camera") @@ -62,6 +63,7 @@ class TestOpenAlprLocalSetup: with assert_setup_component(1, ip.DOMAIN): setup_component(self.hass, ip.DOMAIN, config) + self.hass.block_till_done() assert self.hass.states.get("image_processing.test_local") @@ -77,6 +79,7 @@ class TestOpenAlprLocalSetup: with assert_setup_component(0, ip.DOMAIN): setup_component(self.hass, ip.DOMAIN, config) + self.hass.block_till_done() class TestOpenAlprLocal: @@ -101,6 +104,7 @@ class TestOpenAlprLocal: new_callable=PropertyMock(return_value=False), ): setup_component(self.hass, ip.DOMAIN, config) + self.hass.block_till_done() state = self.hass.states.get("camera.demo_camera") self.url = f"{self.hass.config.api.base_url}{state.attributes.get(ATTR_ENTITY_PICTURE)}" diff --git a/tests/components/openhardwaremonitor/test_sensor.py b/tests/components/openhardwaremonitor/test_sensor.py index 3fb93cb1375..db44216c535 100644 --- a/tests/components/openhardwaremonitor/test_sensor.py +++ b/tests/components/openhardwaremonitor/test_sensor.py @@ -35,6 +35,7 @@ class TestOpenHardwareMonitorSetup(unittest.TestCase): ) assert setup_component(self.hass, "sensor", self.config) + self.hass.block_till_done() entities = self.hass.states.async_entity_ids("sensor") assert len(entities) == 38 diff --git a/tests/components/openuv/test_config_flow.py b/tests/components/openuv/test_config_flow.py index b85805efa94..06eca531d2d 100644 --- a/tests/components/openuv/test_config_flow.py +++ b/tests/components/openuv/test_config_flow.py @@ -1,5 +1,6 @@ """Define tests for the OpenUV config flow.""" from pyopenuv.errors import InvalidApiKeyError +import pytest from homeassistant import data_entry_flow from homeassistant.components.openuv import DOMAIN @@ -15,6 +16,17 @@ from tests.async_mock import patch from tests.common import MockConfigEntry +@pytest.fixture(autouse=True) +def mock_setup(): + """Prevent setup.""" + with patch( + "homeassistant.components.openuv.async_setup", return_value=True, + ), patch( + "homeassistant.components.openuv.async_setup_entry", return_value=True, + ): + yield + + async def test_duplicate_error(hass): """Test that errors are shown when duplicates are added.""" conf = { diff --git a/tests/components/pilight/test_sensor.py b/tests/components/pilight/test_sensor.py index 4bc1e80b07a..54c72675bc7 100644 --- a/tests/components/pilight/test_sensor.py +++ b/tests/components/pilight/test_sensor.py @@ -48,6 +48,7 @@ def test_sensor_value_from_code(): } }, ) + HASS.block_till_done() state = HASS.states.get("sensor.test") assert state.state == "unknown" @@ -77,6 +78,7 @@ def test_disregard_wrong_payload(): } }, ) + HASS.block_till_done() # Try set value from data with incorrect payload fire_pilight_message( @@ -120,6 +122,7 @@ def test_variable_missing(caplog): } }, ) + HASS.block_till_done() # Create code without sensor variable fire_pilight_message( diff --git a/tests/components/prometheus/test_init.py b/tests/components/prometheus/test_init.py index 55c09183e63..4539948cc5a 100644 --- a/tests/components/prometheus/test_init.py +++ b/tests/components/prometheus/test_init.py @@ -26,6 +26,7 @@ async def prometheus_client(loop, hass, hass_client): await setup.async_setup_component( hass, climate.DOMAIN, {"climate": [{"platform": "demo"}]} ) + await hass.async_block_till_done() sensor1 = DemoSensor( None, "Television Energy", 74, None, ENERGY_KILO_WATT_HOUR, None diff --git a/tests/components/qld_bushfire/test_geo_location.py b/tests/components/qld_bushfire/test_geo_location.py index 15518afbc2d..743d967c953 100644 --- a/tests/components/qld_bushfire/test_geo_location.py +++ b/tests/components/qld_bushfire/test_geo_location.py @@ -98,6 +98,7 @@ async def test_setup(hass): ) with assert_setup_component(1, geo_location.DOMAIN): assert await async_setup_component(hass, geo_location.DOMAIN, CONFIG) + await hass.async_block_till_done() # Artificially trigger update. hass.bus.async_fire(EVENT_HOMEASSISTANT_START) # Collect events. @@ -201,6 +202,7 @@ async def test_setup_with_custom_location(hass): assert await async_setup_component( hass, geo_location.DOMAIN, CONFIG_WITH_CUSTOM_LOCATION ) + await hass.async_block_till_done() # Artificially trigger update. hass.bus.async_fire(EVENT_HOMEASSISTANT_START) diff --git a/tests/components/random/test_binary_sensor.py b/tests/components/random/test_binary_sensor.py index 975da102ca6..2f15243c71e 100644 --- a/tests/components/random/test_binary_sensor.py +++ b/tests/components/random/test_binary_sensor.py @@ -24,6 +24,7 @@ class TestRandomSensor(unittest.TestCase): config = {"binary_sensor": {"platform": "random", "name": "test"}} assert setup_component(self.hass, "binary_sensor", config) + self.hass.block_till_done() state = self.hass.states.get("binary_sensor.test") @@ -37,6 +38,7 @@ class TestRandomSensor(unittest.TestCase): config = {"binary_sensor": {"platform": "random", "name": "test"}} assert setup_component(self.hass, "binary_sensor", config) + self.hass.block_till_done() state = self.hass.states.get("binary_sensor.test") diff --git a/tests/components/random/test_sensor.py b/tests/components/random/test_sensor.py index a185c774ccd..657efc9a0cc 100644 --- a/tests/components/random/test_sensor.py +++ b/tests/components/random/test_sensor.py @@ -29,6 +29,7 @@ class TestRandomSensor(unittest.TestCase): } assert setup_component(self.hass, "sensor", config) + self.hass.block_till_done() state = self.hass.states.get("sensor.test") diff --git a/tests/components/reddit/test_sensor.py b/tests/components/reddit/test_sensor.py index 4ec2a3ba452..33c7fae76b0 100644 --- a/tests/components/reddit/test_sensor.py +++ b/tests/components/reddit/test_sensor.py @@ -166,6 +166,7 @@ class TestRedditSetup(unittest.TestCase): def test_setup_with_valid_config(self): """Test the platform setup with Reddit configuration.""" setup_component(self.hass, "sensor", VALID_CONFIG) + self.hass.block_till_done() state = self.hass.states.get("sensor.reddit_worldnews") assert int(state.state) == MOCK_RESULTS_LENGTH diff --git a/tests/components/rest/test_binary_sensor.py b/tests/components/rest/test_binary_sensor.py index 65ae36c3843..762c1705d77 100644 --- a/tests/components/rest/test_binary_sensor.py +++ b/tests/components/rest/test_binary_sensor.py @@ -88,6 +88,7 @@ class TestRestBinarySensorSetup(unittest.TestCase): "binary_sensor", {"binary_sensor": {"platform": "rest", "resource": "http://localhost"}}, ) + self.hass.block_till_done() assert 1 == mock_req.call_count @requests_mock.Mocker() @@ -113,6 +114,7 @@ class TestRestBinarySensorSetup(unittest.TestCase): } }, ) + self.hass.block_till_done() assert 1 == mock_req.call_count @requests_mock.Mocker() @@ -139,6 +141,7 @@ class TestRestBinarySensorSetup(unittest.TestCase): } }, ) + self.hass.block_till_done() assert 1 == mock_req.call_count diff --git a/tests/components/rest/test_sensor.py b/tests/components/rest/test_sensor.py index c3ed8cea1b9..77d88f083e4 100644 --- a/tests/components/rest/test_sensor.py +++ b/tests/components/rest/test_sensor.py @@ -76,6 +76,7 @@ class TestRestSensorSetup(unittest.TestCase): "sensor", {"sensor": {"platform": "rest", "resource": "http://localhost"}}, ) + self.hass.block_till_done() assert 2 == mock_req.call_count @requests_mock.Mocker() @@ -93,6 +94,7 @@ class TestRestSensorSetup(unittest.TestCase): } }, ) + self.hass.block_till_done() assert mock_req.call_count == 2 @requests_mock.Mocker() @@ -111,6 +113,7 @@ class TestRestSensorSetup(unittest.TestCase): } }, ) + self.hass.block_till_done() @requests_mock.Mocker() def test_setup_get(self, mock_req): @@ -137,6 +140,7 @@ class TestRestSensorSetup(unittest.TestCase): } }, ) + self.hass.block_till_done() assert 2 == mock_req.call_count @requests_mock.Mocker() @@ -165,6 +169,7 @@ class TestRestSensorSetup(unittest.TestCase): } }, ) + self.hass.block_till_done() assert 2 == mock_req.call_count @requests_mock.Mocker() @@ -192,6 +197,7 @@ class TestRestSensorSetup(unittest.TestCase): } }, ) + self.hass.block_till_done() assert 2 == mock_req.call_count diff --git a/tests/components/rmvtransport/test_sensor.py b/tests/components/rmvtransport/test_sensor.py index ece4142f8c7..419eaafc5eb 100644 --- a/tests/components/rmvtransport/test_sensor.py +++ b/tests/components/rmvtransport/test_sensor.py @@ -165,6 +165,7 @@ async def test_rmvtransport_min_config(hass): "RMVtransport.RMVtransport.get_departures", return_value=get_departures_mock(), ): assert await async_setup_component(hass, "sensor", VALID_CONFIG_MINIMAL) is True + await hass.async_block_till_done() state = hass.states.get("sensor.frankfurt_main_hauptbahnhof") assert state.state == "7" @@ -184,6 +185,7 @@ async def test_rmvtransport_name_config(hass): "RMVtransport.RMVtransport.get_departures", return_value=get_departures_mock(), ): assert await async_setup_component(hass, "sensor", VALID_CONFIG_NAME) + await hass.async_block_till_done() state = hass.states.get("sensor.my_station") assert state.attributes["friendly_name"] == "My Station" @@ -195,6 +197,7 @@ async def test_rmvtransport_misc_config(hass): "RMVtransport.RMVtransport.get_departures", return_value=get_departures_mock(), ): assert await async_setup_component(hass, "sensor", VALID_CONFIG_MISC) + await hass.async_block_till_done() state = hass.states.get("sensor.frankfurt_main_hauptbahnhof") assert state.attributes["friendly_name"] == "Frankfurt (Main) Hauptbahnhof" @@ -207,6 +210,7 @@ async def test_rmvtransport_dest_config(hass): "RMVtransport.RMVtransport.get_departures", return_value=get_departures_mock(), ): assert await async_setup_component(hass, "sensor", VALID_CONFIG_DEST) + await hass.async_block_till_done() state = hass.states.get("sensor.frankfurt_main_hauptbahnhof") assert state.state == "11" @@ -225,6 +229,7 @@ async def test_rmvtransport_no_departures(hass): return_value=get_no_departures_mock(), ): assert await async_setup_component(hass, "sensor", VALID_CONFIG_MINIMAL) + await hass.async_block_till_done() state = hass.states.get("sensor.frankfurt_main_hauptbahnhof") assert not state diff --git a/tests/components/scene/test_init.py b/tests/components/scene/test_init.py index 483ab17faa6..bff88d8e660 100644 --- a/tests/components/scene/test_init.py +++ b/tests/components/scene/test_init.py @@ -23,6 +23,7 @@ class TestScene(unittest.TestCase): assert setup_component( self.hass, light.DOMAIN, {light.DOMAIN: {"platform": "test"}} ) + self.hass.block_till_done() self.light_1, self.light_2 = test_light.ENTITIES[0:2] @@ -72,6 +73,7 @@ class TestScene(unittest.TestCase): ] }, ) + self.hass.block_till_done() common.activate(self.hass, "scene.test") self.hass.block_till_done() @@ -121,6 +123,7 @@ class TestScene(unittest.TestCase): ] }, ) + self.hass.block_till_done() common.activate(self.hass, "scene.test") self.hass.block_till_done() diff --git a/tests/components/season/test_sensor.py b/tests/components/season/test_sensor.py index 2acc5f6573f..279291d6da5 100644 --- a/tests/components/season/test_sensor.py +++ b/tests/components/season/test_sensor.py @@ -207,6 +207,7 @@ class TestSeason(unittest.TestCase): """Test platform setup of northern hemisphere.""" self.hass.config.latitude = HEMISPHERE_NORTHERN["homeassistant"]["latitude"] assert setup_component(self.hass, "sensor", HEMISPHERE_NORTHERN) + self.hass.block_till_done() assert ( self.hass.config.as_dict()["latitude"] == HEMISPHERE_NORTHERN["homeassistant"]["latitude"] @@ -218,6 +219,7 @@ class TestSeason(unittest.TestCase): """Test platform setup of southern hemisphere.""" self.hass.config.latitude = HEMISPHERE_SOUTHERN["homeassistant"]["latitude"] assert setup_component(self.hass, "sensor", HEMISPHERE_SOUTHERN) + self.hass.block_till_done() assert ( self.hass.config.as_dict()["latitude"] == HEMISPHERE_SOUTHERN["homeassistant"]["latitude"] @@ -229,6 +231,7 @@ class TestSeason(unittest.TestCase): """Test platform setup of equator.""" self.hass.config.latitude = HEMISPHERE_EQUATOR["homeassistant"]["latitude"] assert setup_component(self.hass, "sensor", HEMISPHERE_EQUATOR) + self.hass.block_till_done() assert ( self.hass.config.as_dict()["latitude"] == HEMISPHERE_EQUATOR["homeassistant"]["latitude"] @@ -240,4 +243,5 @@ class TestSeason(unittest.TestCase): """Test platform setup of missing latlong.""" self.hass.config.latitude = None assert setup_component(self.hass, "sensor", HEMISPHERE_EMPTY) + self.hass.block_till_done() assert self.hass.config.as_dict()["latitude"] is None diff --git a/tests/components/sensor/test_device_condition.py b/tests/components/sensor/test_device_condition.py index 5d25e666110..f92a28d358b 100644 --- a/tests/components/sensor/test_device_condition.py +++ b/tests/components/sensor/test_device_condition.py @@ -57,6 +57,7 @@ async def test_get_conditions(hass, device_reg, entity_reg): ) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) + await hass.async_block_till_done() expected_conditions = [ { @@ -93,6 +94,7 @@ async def test_get_condition_capabilities(hass, device_reg, entity_reg): ) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) + await hass.async_block_till_done() expected_capabilities = { "extra_fields": [ @@ -128,6 +130,7 @@ async def test_get_condition_capabilities_none(hass, device_reg, entity_reg): config_entry.add_to_hass(hass) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) + await hass.async_block_till_done() conditions = [ { @@ -160,6 +163,7 @@ async def test_if_state_not_above_below(hass, calls, caplog): platform.init() assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) + await hass.async_block_till_done() sensor1 = platform.ENTITIES["battery"] @@ -193,6 +197,7 @@ async def test_if_state_above(hass, calls): platform.init() assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) + await hass.async_block_till_done() sensor1 = platform.ENTITIES["battery"] @@ -250,6 +255,7 @@ async def test_if_state_below(hass, calls): platform.init() assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) + await hass.async_block_till_done() sensor1 = platform.ENTITIES["battery"] @@ -307,6 +313,7 @@ async def test_if_state_between(hass, calls): platform.init() assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) + await hass.async_block_till_done() sensor1 = platform.ENTITIES["battery"] diff --git a/tests/components/sensor/test_device_trigger.py b/tests/components/sensor/test_device_trigger.py index 70539871aa8..3f44e9e5e32 100644 --- a/tests/components/sensor/test_device_trigger.py +++ b/tests/components/sensor/test_device_trigger.py @@ -61,6 +61,7 @@ async def test_get_triggers(hass, device_reg, entity_reg): ) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) + await hass.async_block_till_done() expected_triggers = [ { @@ -98,6 +99,7 @@ async def test_get_trigger_capabilities(hass, device_reg, entity_reg): ) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) + await hass.async_block_till_done() expected_capabilities = { "extra_fields": [ @@ -134,6 +136,7 @@ async def test_get_trigger_capabilities_none(hass, device_reg, entity_reg): config_entry.add_to_hass(hass) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) + await hass.async_block_till_done() triggers = [ { @@ -165,6 +168,7 @@ async def test_if_fires_not_on_above_below(hass, calls, caplog): platform = getattr(hass.components, f"test.{DOMAIN}") platform.init() assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) + await hass.async_block_till_done() sensor1 = platform.ENTITIES["battery"] @@ -194,6 +198,7 @@ async def test_if_fires_on_state_above(hass, calls): platform = getattr(hass.components, f"test.{DOMAIN}") platform.init() assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) + await hass.async_block_till_done() sensor1 = platform.ENTITIES["battery"] @@ -251,6 +256,7 @@ async def test_if_fires_on_state_below(hass, calls): platform = getattr(hass.components, f"test.{DOMAIN}") platform.init() assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) + await hass.async_block_till_done() sensor1 = platform.ENTITIES["battery"] @@ -308,6 +314,7 @@ async def test_if_fires_on_state_between(hass, calls): platform = getattr(hass.components, f"test.{DOMAIN}") platform.init() assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) + await hass.async_block_till_done() sensor1 = platform.ENTITIES["battery"] @@ -378,6 +385,7 @@ async def test_if_fires_on_state_change_with_for(hass, calls): platform.init() assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) + await hass.async_block_till_done() sensor1 = platform.ENTITIES["battery"] diff --git a/tests/components/seventeentrack/test_sensor.py b/tests/components/seventeentrack/test_sensor.py index 62f93d7cd70..272f2fe7d76 100644 --- a/tests/components/seventeentrack/test_sensor.py +++ b/tests/components/seventeentrack/test_sensor.py @@ -130,6 +130,7 @@ async def _setup_seventeentrack(hass, config=None, summary_data=None): ProfileMock.summary_data = summary_data assert await async_setup_component(hass, "sensor", config) + await hass.async_block_till_done() async def _goto_future(hass, future=None): @@ -144,13 +145,14 @@ async def _goto_future(hass, future=None): async def test_full_valid_config(hass): """Ensure everything starts correctly.""" assert await async_setup_component(hass, "sensor", VALID_CONFIG_FULL) + await hass.async_block_till_done() assert len(hass.states.async_entity_ids()) == len(ProfileMock.summary_data.keys()) async def test_valid_config(hass): """Ensure everything starts correctly.""" assert await async_setup_component(hass, "sensor", VALID_CONFIG_MINIMAL) - + await hass.async_block_till_done() assert len(hass.states.async_entity_ids()) == len(ProfileMock.summary_data.keys()) diff --git a/tests/components/sigfox/test_sensor.py b/tests/components/sigfox/test_sensor.py index 35534a3a126..c4af07b5799 100644 --- a/tests/components/sigfox/test_sensor.py +++ b/tests/components/sigfox/test_sensor.py @@ -50,6 +50,7 @@ class TestSigfoxSensor(unittest.TestCase): url = re.compile(API_URL + "devicetypes") mock_req.get(url, text="{}", status_code=401) assert setup_component(self.hass, "sensor", VALID_CONFIG) + self.hass.block_till_done() assert len(self.hass.states.entity_ids()) == 0 def test_valid_credentials(self): @@ -65,6 +66,7 @@ class TestSigfoxSensor(unittest.TestCase): mock_req.get(url3, text=VALID_MESSAGE) assert setup_component(self.hass, "sensor", VALID_CONFIG) + self.hass.block_till_done() assert len(self.hass.states.entity_ids()) == 1 state = self.hass.states.get("sensor.sigfox_fake_id") diff --git a/tests/components/sighthound/test_image_processing.py b/tests/components/sighthound/test_image_processing.py index 1d73ace184e..78110303702 100644 --- a/tests/components/sighthound/test_image_processing.py +++ b/tests/components/sighthound/test_image_processing.py @@ -88,6 +88,7 @@ async def test_bad_api_key(hass, caplog): "simplehound.core.cloud.detect", side_effect=hound.SimplehoundException ): await async_setup_component(hass, ip.DOMAIN, VALID_CONFIG) + await hass.async_block_till_done() assert "Sighthound error" in caplog.text assert not hass.states.get(VALID_ENTITY_ID) @@ -95,12 +96,14 @@ async def test_bad_api_key(hass, caplog): async def test_setup_platform(hass, mock_detections): """Set up platform with one entity.""" await async_setup_component(hass, ip.DOMAIN, VALID_CONFIG) + await hass.async_block_till_done() assert hass.states.get(VALID_ENTITY_ID) async def test_process_image(hass, mock_image, mock_detections): """Process an image.""" await async_setup_component(hass, ip.DOMAIN, VALID_CONFIG) + await hass.async_block_till_done() assert hass.states.get(VALID_ENTITY_ID) person_events = [] @@ -128,6 +131,7 @@ async def test_catch_bad_image( valid_config_save_file = deepcopy(VALID_CONFIG) valid_config_save_file[ip.DOMAIN].update({sh.CONF_SAVE_FILE_FOLDER: TEST_DIR}) await async_setup_component(hass, ip.DOMAIN, valid_config_save_file) + await hass.async_block_till_done() assert hass.states.get(VALID_ENTITY_ID) data = {ATTR_ENTITY_ID: VALID_ENTITY_ID} @@ -141,6 +145,7 @@ async def test_save_image(hass, mock_image, mock_detections): valid_config_save_file = deepcopy(VALID_CONFIG) valid_config_save_file[ip.DOMAIN].update({sh.CONF_SAVE_FILE_FOLDER: TEST_DIR}) await async_setup_component(hass, ip.DOMAIN, valid_config_save_file) + await hass.async_block_till_done() assert hass.states.get(VALID_ENTITY_ID) with mock.patch( @@ -166,6 +171,7 @@ async def test_save_timestamped_image(hass, mock_image, mock_detections, mock_no valid_config_save_ts_file[ip.DOMAIN].update({sh.CONF_SAVE_FILE_FOLDER: TEST_DIR}) valid_config_save_ts_file[ip.DOMAIN].update({sh.CONF_SAVE_TIMESTAMPTED_FILE: True}) await async_setup_component(hass, ip.DOMAIN, valid_config_save_ts_file) + await hass.async_block_till_done() assert hass.states.get(VALID_ENTITY_ID) with mock.patch( diff --git a/tests/components/sma/test_sensor.py b/tests/components/sma/test_sensor.py index de465d233ba..2c317520a7d 100644 --- a/tests/components/sma/test_sensor.py +++ b/tests/components/sma/test_sensor.py @@ -24,6 +24,7 @@ async def test_sma_config(hass): assert await async_setup_component( hass, DOMAIN, {DOMAIN: dict(BASE_CFG, sensors=sensors)} ) + await hass.async_block_till_done() state = hass.states.get("sensor.current_consumption") assert state diff --git a/tests/components/smhi/test_weather.py b/tests/components/smhi/test_weather.py index 6f5fdfd2333..2e0ad01e552 100644 --- a/tests/components/smhi/test_weather.py +++ b/tests/components/smhi/test_weather.py @@ -48,6 +48,7 @@ async def test_setup_hass(hass: HomeAssistant, aioclient_mock) -> None: entry = MockConfigEntry(domain="smhi", data=TEST_CONFIG) await hass.config_entries.async_forward_entry_setup(entry, WEATHER_DOMAIN) + await hass.async_block_till_done() assert aioclient_mock.call_count == 1 # Testing the actual entity state for diff --git a/tests/components/sql/test_sensor.py b/tests/components/sql/test_sensor.py index afc7bebea09..8b8bca5e37c 100644 --- a/tests/components/sql/test_sensor.py +++ b/tests/components/sql/test_sensor.py @@ -39,6 +39,7 @@ class TestSQLSensor(unittest.TestCase): } assert setup_component(self.hass, "sensor", config) + self.hass.block_till_done() state = self.hass.states.get("sensor.count_tables") assert state.state == "5" @@ -64,6 +65,7 @@ class TestSQLSensor(unittest.TestCase): } assert setup_component(self.hass, "sensor", config) + self.hass.block_till_done() state = self.hass.states.get("sensor.count_tables") assert state.state == STATE_UNKNOWN diff --git a/tests/components/startca/test_sensor.py b/tests/components/startca/test_sensor.py index f3c6af51df5..302df0492c8 100644 --- a/tests/components/startca/test_sensor.py +++ b/tests/components/startca/test_sensor.py @@ -50,6 +50,7 @@ async def test_capped_setup(hass, aioclient_mock): ) await async_setup_component(hass, "sensor", {"sensor": config}) + await hass.async_block_till_done() state = hass.states.get("sensor.start_ca_usage_ratio") assert state.attributes.get("unit_of_measurement") == UNIT_PERCENTAGE @@ -145,6 +146,7 @@ async def test_unlimited_setup(hass, aioclient_mock): ) await async_setup_component(hass, "sensor", {"sensor": config}) + await hass.async_block_till_done() state = hass.states.get("sensor.start_ca_usage_ratio") assert state.attributes.get("unit_of_measurement") == UNIT_PERCENTAGE diff --git a/tests/components/statistics/test_sensor.py b/tests/components/statistics/test_sensor.py index 721cf71303d..ffbf4d9fcd8 100644 --- a/tests/components/statistics/test_sensor.py +++ b/tests/components/statistics/test_sensor.py @@ -57,6 +57,7 @@ class TestStatisticsSensor(unittest.TestCase): }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -82,6 +83,7 @@ class TestStatisticsSensor(unittest.TestCase): }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -121,6 +123,7 @@ class TestStatisticsSensor(unittest.TestCase): }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -150,6 +153,7 @@ class TestStatisticsSensor(unittest.TestCase): }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -197,6 +201,7 @@ class TestStatisticsSensor(unittest.TestCase): }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -238,6 +243,7 @@ class TestStatisticsSensor(unittest.TestCase): }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -291,6 +297,7 @@ class TestStatisticsSensor(unittest.TestCase): }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -342,6 +349,7 @@ class TestStatisticsSensor(unittest.TestCase): }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -407,6 +415,7 @@ class TestStatisticsSensor(unittest.TestCase): ) self.hass.block_till_done() + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() diff --git a/tests/components/switch/test_device_action.py b/tests/components/switch/test_device_action.py index fbd24fe2095..204b2370cb8 100644 --- a/tests/components/switch/test_device_action.py +++ b/tests/components/switch/test_device_action.py @@ -75,6 +75,7 @@ async def test_action(hass, calls): platform.init() assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) + await hass.async_block_till_done() ent1, ent2, ent3 = platform.ENTITIES diff --git a/tests/components/switch/test_device_condition.py b/tests/components/switch/test_device_condition.py index e4e8564f5bb..6e542ee24d1 100644 --- a/tests/components/switch/test_device_condition.py +++ b/tests/components/switch/test_device_condition.py @@ -96,6 +96,7 @@ async def test_if_state(hass, calls): platform.init() assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) + await hass.async_block_till_done() ent1, ent2, ent3 = platform.ENTITIES @@ -173,6 +174,7 @@ async def test_if_fires_on_for_condition(hass, calls): platform.init() assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) + await hass.async_block_till_done() ent1, ent2, ent3 = platform.ENTITIES diff --git a/tests/components/switch/test_device_trigger.py b/tests/components/switch/test_device_trigger.py index 73d12d0a729..512942ca71b 100644 --- a/tests/components/switch/test_device_trigger.py +++ b/tests/components/switch/test_device_trigger.py @@ -96,6 +96,7 @@ async def test_if_fires_on_state_change(hass, calls): platform.init() assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) + await hass.async_block_till_done() ent1, ent2, ent3 = platform.ENTITIES @@ -180,6 +181,7 @@ async def test_if_fires_on_state_change_with_for(hass, calls): platform.init() assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}}) + await hass.async_block_till_done() ent1, ent2, ent3 = platform.ENTITIES diff --git a/tests/components/switch/test_init.py b/tests/components/switch/test_init.py index 7a14d3ac117..6605d19d46f 100644 --- a/tests/components/switch/test_init.py +++ b/tests/components/switch/test_init.py @@ -33,6 +33,7 @@ class TestSwitch(unittest.TestCase): assert setup_component( self.hass, switch.DOMAIN, {switch.DOMAIN: {CONF_PLATFORM: "test"}} ) + self.hass.block_till_done() assert switch.is_on(self.hass, self.switch_1.entity_id) assert not switch.is_on(self.hass, self.switch_2.entity_id) assert not switch.is_on(self.hass, self.switch_3.entity_id) diff --git a/tests/components/teksavvy/test_sensor.py b/tests/components/teksavvy/test_sensor.py index 049a1ddc60c..7de8651f16b 100644 --- a/tests/components/teksavvy/test_sensor.py +++ b/tests/components/teksavvy/test_sensor.py @@ -44,6 +44,7 @@ async def test_capped_setup(hass, aioclient_mock): ) await async_setup_component(hass, "sensor", {"sensor": config}) + await hass.async_block_till_done() state = hass.states.get("sensor.teksavvy_data_limit") assert state.attributes.get("unit_of_measurement") == DATA_GIGABYTES @@ -125,6 +126,7 @@ async def test_unlimited_setup(hass, aioclient_mock): ) await async_setup_component(hass, "sensor", {"sensor": config}) + await hass.async_block_till_done() state = hass.states.get("sensor.teksavvy_data_limit") assert state.attributes.get("unit_of_measurement") == DATA_GIGABYTES diff --git a/tests/components/template/test_alarm_control_panel.py b/tests/components/template/test_alarm_control_panel.py index 6d2e4e48279..1126f6b60a1 100644 --- a/tests/components/template/test_alarm_control_panel.py +++ b/tests/components/template/test_alarm_control_panel.py @@ -54,6 +54,7 @@ async def test_template_state_text(hass): }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() @@ -136,6 +137,7 @@ async def test_optimistic_states(hass): }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() @@ -189,6 +191,7 @@ async def test_no_action_scripts(hass): }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() @@ -261,6 +264,7 @@ async def test_template_syntax_error(hass, caplog): }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() @@ -305,6 +309,7 @@ async def test_invalid_name_does_not_create(hass, caplog): }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() @@ -325,6 +330,7 @@ async def test_invalid_panel_does_not_create(hass, caplog): }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() @@ -338,6 +344,7 @@ async def test_no_panels_does_not_create(hass, caplog): hass, "alarm_control_panel", {"alarm_control_panel": {"platform": "template"}}, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() @@ -383,6 +390,7 @@ async def test_name(hass): }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() @@ -425,6 +433,7 @@ async def test_arm_home_action(hass): }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() @@ -471,6 +480,7 @@ async def test_arm_away_action(hass): }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() @@ -517,6 +527,7 @@ async def test_arm_night_action(hass): }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() @@ -563,6 +574,7 @@ async def test_disarm_action(hass): }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() diff --git a/tests/components/template/test_binary_sensor.py b/tests/components/template/test_binary_sensor.py index b024bbc311f..698d1c2ca80 100644 --- a/tests/components/template/test_binary_sensor.py +++ b/tests/components/template/test_binary_sensor.py @@ -127,6 +127,7 @@ class TestBinarySensorTemplate(unittest.TestCase): }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -161,6 +162,7 @@ class TestBinarySensorTemplate(unittest.TestCase): }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -193,6 +195,7 @@ class TestBinarySensorTemplate(unittest.TestCase): }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -224,6 +227,7 @@ class TestBinarySensorTemplate(unittest.TestCase): }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() init_calls = len(_async_render.mock_calls) @@ -280,6 +284,7 @@ class TestBinarySensorTemplate(unittest.TestCase): with assert_setup_component(1): assert setup.setup_component(self.hass, "binary_sensor", config) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -335,6 +340,7 @@ async def test_template_delay_on(hass): } } await setup.async_setup_component(hass, "binary_sensor", config) + await hass.async_block_till_done() await hass.async_start() hass.states.async_set("sensor.test_state", "on") @@ -394,6 +400,7 @@ async def test_template_delay_off(hass): } hass.states.async_set("sensor.test_state", "on") await setup.async_setup_component(hass, "binary_sensor", config) + await hass.async_block_till_done() await hass.async_start() hass.states.async_set("sensor.test_state", "off") @@ -452,6 +459,7 @@ async def test_available_without_availability_template(hass): } } await setup.async_setup_component(hass, "binary_sensor", config) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() @@ -475,6 +483,7 @@ async def test_availability_template(hass): } } await setup.async_setup_component(hass, "binary_sensor", config) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() @@ -537,7 +546,7 @@ async def test_invalid_availability_template_keeps_component_available(hass, cap } }, ) - + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() diff --git a/tests/components/template/test_cover.py b/tests/components/template/test_cover.py index 095393c8acb..56db2445f6b 100644 --- a/tests/components/template/test_cover.py +++ b/tests/components/template/test_cover.py @@ -62,6 +62,7 @@ async def test_template_state_text(hass, calls): }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() @@ -104,6 +105,7 @@ async def test_template_state_boolean(hass, calls): }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() @@ -137,6 +139,7 @@ async def test_template_position(hass, calls): }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() @@ -192,6 +195,7 @@ async def test_template_tilt(hass, calls): }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() @@ -226,6 +230,7 @@ async def test_template_out_of_bounds(hass, calls): }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() @@ -264,6 +269,7 @@ async def test_template_mutex(hass, calls): }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() @@ -310,6 +316,7 @@ async def test_template_open_and_close(hass, calls): }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() @@ -347,6 +354,7 @@ async def test_template_non_numeric(hass, calls): }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() @@ -378,6 +386,7 @@ async def test_open_action(hass, calls): }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() @@ -416,6 +425,7 @@ async def test_close_stop_action(hass, calls): }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() @@ -463,6 +473,7 @@ async def test_set_position(hass, calls): }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() @@ -537,6 +548,7 @@ async def test_set_tilt_position(hass, calls): }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() @@ -578,6 +590,7 @@ async def test_open_tilt_action(hass, calls): }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() @@ -616,6 +629,7 @@ async def test_close_tilt_action(hass, calls): }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() @@ -644,6 +658,7 @@ async def test_set_position_optimistic(hass, calls): } }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() @@ -708,6 +723,7 @@ async def test_set_tilt_position_optimistic(hass, calls): } }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() @@ -782,6 +798,7 @@ async def test_icon_template(hass, calls): }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() @@ -825,6 +842,7 @@ async def test_entity_picture_template(hass, calls): }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() @@ -866,6 +884,7 @@ async def test_availability_template(hass, calls): }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() @@ -905,6 +924,7 @@ async def test_availability_without_availability_template(hass, calls): }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() @@ -938,6 +958,7 @@ async def test_invalid_availability_template_keeps_component_available(hass, cap }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() @@ -972,6 +993,7 @@ async def test_device_class(hass, calls): }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() @@ -1006,6 +1028,7 @@ async def test_invalid_device_class(hass, calls): }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() diff --git a/tests/components/template/test_fan.py b/tests/components/template/test_fan.py index fb02e4f3227..2e44ec6f0ca 100644 --- a/tests/components/template/test_fan.py +++ b/tests/components/template/test_fan.py @@ -63,6 +63,7 @@ async def test_missing_optional_config(hass, calls): }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() @@ -88,6 +89,7 @@ async def test_missing_value_template_config(hass, calls): }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() @@ -113,6 +115,7 @@ async def test_missing_turn_on_config(hass, calls): }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() @@ -138,6 +141,7 @@ async def test_missing_turn_off_config(hass, calls): }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() @@ -161,6 +165,7 @@ async def test_invalid_config(hass, calls): }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() @@ -202,6 +207,7 @@ async def test_templates_with_entities(hass, calls): }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() @@ -241,6 +247,7 @@ async def test_availability_template_with_entities(hass, calls): }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() @@ -282,6 +289,7 @@ async def test_templates_with_valid_values(hass, calls): }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() @@ -311,6 +319,7 @@ async def test_templates_invalid_values(hass, calls): }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() @@ -342,6 +351,7 @@ async def test_invalid_availability_template_keeps_component_available(hass, cap }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() @@ -687,5 +697,6 @@ async def _register_components(hass, speed_list=None): {"fan": {"platform": "template", "fans": {"test_fan": test_fan_config}}}, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() diff --git a/tests/components/template/test_light.py b/tests/components/template/test_light.py index 9982d72326b..8e27a6ba15d 100644 --- a/tests/components/template/test_light.py +++ b/tests/components/template/test_light.py @@ -78,6 +78,7 @@ class TestTemplateLight: }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -117,6 +118,7 @@ class TestTemplateLight: }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -169,6 +171,7 @@ class TestTemplateLight: }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -208,6 +211,7 @@ class TestTemplateLight: }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -246,6 +250,7 @@ class TestTemplateLight: }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -265,6 +270,7 @@ class TestTemplateLight: }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -277,6 +283,7 @@ class TestTemplateLight: self.hass, "light", {"light": {"platform": "template"}} ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -316,6 +323,7 @@ class TestTemplateLight: del light["light"]["lights"]["light_one"][missing_key] with assert_setup_component(count, "light"): assert setup.setup_component(self.hass, "light", light) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -353,6 +361,7 @@ class TestTemplateLight: }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -395,6 +404,7 @@ class TestTemplateLight: }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -440,6 +450,7 @@ class TestTemplateLight: }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -482,6 +493,7 @@ class TestTemplateLight: }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -526,6 +538,7 @@ class TestTemplateLight: } }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -587,6 +600,7 @@ class TestTemplateLight: }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -625,6 +639,7 @@ class TestTemplateLight: } }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -685,6 +700,7 @@ class TestTemplateLight: }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -737,6 +753,7 @@ class TestTemplateLight: }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -775,6 +792,7 @@ class TestTemplateLight: } }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -825,6 +843,7 @@ class TestTemplateLight: }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -870,6 +889,7 @@ class TestTemplateLight: }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -920,6 +940,7 @@ class TestTemplateLight: }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -975,6 +996,7 @@ class TestTemplateLight: } }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -1046,6 +1068,7 @@ class TestTemplateLight: } }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() state = self.hass.states.get("light.test_template_light") @@ -1084,6 +1107,7 @@ async def test_available_template_with_entities(hass): } }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() @@ -1134,6 +1158,7 @@ async def test_invalid_availability_template_keeps_component_available(hass, cap }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() diff --git a/tests/components/template/test_lock.py b/tests/components/template/test_lock.py index 10f959efed8..1389040c4bb 100644 --- a/tests/components/template/test_lock.py +++ b/tests/components/template/test_lock.py @@ -57,6 +57,7 @@ class TestTemplateLock: }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -94,6 +95,7 @@ class TestTemplateLock: }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -122,6 +124,7 @@ class TestTemplateLock: }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -150,6 +153,7 @@ class TestTemplateLock: }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -178,6 +182,7 @@ class TestTemplateLock: }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -192,6 +197,7 @@ class TestTemplateLock: {"lock": {"platform": "template", "value_template": "Invalid"}}, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -219,6 +225,7 @@ class TestTemplateLock: }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -246,6 +253,7 @@ class TestTemplateLock: }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -281,6 +289,7 @@ class TestTemplateLock: }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -315,6 +324,7 @@ class TestTemplateLock: }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -352,6 +362,7 @@ async def test_available_template_with_entities(hass): }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() @@ -389,6 +400,7 @@ async def test_invalid_availability_template_keeps_component_available(hass, cap }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() diff --git a/tests/components/template/test_sensor.py b/tests/components/template/test_sensor.py index e9033b3dfe3..0fbce50f2a3 100644 --- a/tests/components/template/test_sensor.py +++ b/tests/components/template/test_sensor.py @@ -42,6 +42,7 @@ class TestTemplateSensor: }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -75,6 +76,7 @@ class TestTemplateSensor: }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -108,6 +110,7 @@ class TestTemplateSensor: }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -138,6 +141,7 @@ class TestTemplateSensor: }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -168,6 +172,7 @@ class TestTemplateSensor: }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -200,6 +205,7 @@ class TestTemplateSensor: }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -229,6 +235,7 @@ class TestTemplateSensor: }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() assert self.hass.states.all() == [] @@ -252,6 +259,7 @@ class TestTemplateSensor: }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -276,6 +284,7 @@ class TestTemplateSensor: }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -295,6 +304,7 @@ class TestTemplateSensor: }, ) + self.hass.block_till_done() self.hass.start() assert self.hass.states.all() == [] @@ -306,6 +316,7 @@ class TestTemplateSensor: self.hass, "sensor", {"sensor": {"platform": "template"}} ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -329,6 +340,7 @@ class TestTemplateSensor: }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -401,6 +413,7 @@ class TestTemplateSensor: }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -445,6 +458,7 @@ async def test_available_template_with_entities(hass): }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() @@ -510,6 +524,7 @@ async def test_invalid_availability_template_keeps_component_available(hass, cap }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() @@ -549,6 +564,7 @@ async def test_no_template_match_all(hass, caplog): } }, ) + await hass.async_block_till_done() assert hass.states.get("sensor.invalid_state").state == "unknown" assert hass.states.get("sensor.invalid_icon").state == "unknown" diff --git a/tests/components/template/test_switch.py b/tests/components/template/test_switch.py index a66028a318f..1ec8960a183 100644 --- a/tests/components/template/test_switch.py +++ b/tests/components/template/test_switch.py @@ -56,6 +56,7 @@ class TestTemplateSwitch: }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -97,6 +98,7 @@ class TestTemplateSwitch: }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -129,6 +131,7 @@ class TestTemplateSwitch: }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -164,6 +167,7 @@ class TestTemplateSwitch: }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -205,6 +209,7 @@ class TestTemplateSwitch: }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -243,6 +248,7 @@ class TestTemplateSwitch: }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -274,6 +280,7 @@ class TestTemplateSwitch: }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -293,6 +300,7 @@ class TestTemplateSwitch: }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -305,6 +313,7 @@ class TestTemplateSwitch: self.hass, "switch", {"switch": {"platform": "template"}} ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -336,6 +345,7 @@ class TestTemplateSwitch: }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -367,6 +377,7 @@ class TestTemplateSwitch: }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -398,6 +409,7 @@ class TestTemplateSwitch: }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -425,6 +437,7 @@ class TestTemplateSwitch: }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -461,6 +474,7 @@ class TestTemplateSwitch: }, ) + self.hass.block_till_done() self.hass.start() self.hass.block_till_done() @@ -502,6 +516,7 @@ async def test_available_template_with_entities(hass): }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() @@ -542,6 +557,7 @@ async def test_invalid_availability_template_keeps_component_available(hass, cap }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() diff --git a/tests/components/template/test_vacuum.py b/tests/components/template/test_vacuum.py index 4080b75f46a..c8ae5bdce51 100644 --- a/tests/components/template/test_vacuum.py +++ b/tests/components/template/test_vacuum.py @@ -50,6 +50,7 @@ async def test_missing_optional_config(hass, calls): }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() @@ -70,6 +71,7 @@ async def test_missing_start_config(hass, calls): }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() @@ -90,6 +92,7 @@ async def test_invalid_config(hass, calls): }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() @@ -120,6 +123,7 @@ async def test_templates_with_entities(hass, calls): }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() @@ -152,6 +156,7 @@ async def test_templates_with_valid_values(hass, calls): }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() @@ -178,6 +183,7 @@ async def test_templates_invalid_values(hass, calls): }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() @@ -205,6 +211,7 @@ async def test_invalid_templates(hass, calls): }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() @@ -230,6 +237,7 @@ async def test_available_template_with_entities(hass, calls): }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() @@ -266,6 +274,7 @@ async def test_invalid_availability_template_keeps_component_available(hass, cap }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() @@ -446,6 +455,7 @@ async def _register_basic_vacuum(hass): }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() @@ -532,5 +542,6 @@ async def _register_components(hass): }, ) + await hass.async_block_till_done() await hass.async_start() await hass.async_block_till_done() diff --git a/tests/components/tod/test_binary_sensor.py b/tests/components/tod/test_binary_sensor.py index 4febd1aa8d1..afa299ef063 100644 --- a/tests/components/tod/test_binary_sensor.py +++ b/tests/components/tod/test_binary_sensor.py @@ -79,8 +79,8 @@ class TestBinarySensorTod(unittest.TestCase): return_value=test_time, ): setup_component(self.hass, "binary_sensor", config) + self.hass.block_till_done() - self.hass.block_till_done() state = self.hass.states.get("binary_sensor.evening") assert state.state == STATE_ON @@ -97,8 +97,8 @@ class TestBinarySensorTod(unittest.TestCase): return_value=test_time, ): setup_component(self.hass, "binary_sensor", config) + self.hass.block_till_done() - self.hass.block_till_done() state = self.hass.states.get("binary_sensor.night") assert state.state == STATE_ON @@ -117,6 +117,7 @@ class TestBinarySensorTod(unittest.TestCase): return_value=test_time, ): setup_component(self.hass, "binary_sensor", config) + self.hass.block_till_done() state = self.hass.states.get("binary_sensor.night") assert state.state == STATE_OFF @@ -151,8 +152,8 @@ class TestBinarySensorTod(unittest.TestCase): return_value=test_time, ): setup_component(self.hass, "binary_sensor", config) + self.hass.block_till_done() - self.hass.block_till_done() state = self.hass.states.get("binary_sensor.night") assert state.state == STATE_OFF @@ -172,8 +173,8 @@ class TestBinarySensorTod(unittest.TestCase): return_value=test_time, ): setup_component(self.hass, "binary_sensor", config) + self.hass.block_till_done() - self.hass.block_till_done() state = self.hass.states.get("binary_sensor.night") assert state.state == STATE_OFF @@ -232,6 +233,7 @@ class TestBinarySensorTod(unittest.TestCase): return_value=testtime, ): setup_component(self.hass, "binary_sensor", config) + self.hass.block_till_done() self.hass.block_till_done() state = self.hass.states.get(entity_id) @@ -328,6 +330,7 @@ class TestBinarySensorTod(unittest.TestCase): return_value=testtime, ): setup_component(self.hass, "binary_sensor", config) + self.hass.block_till_done() self.hass.block_till_done() state = self.hass.states.get(entity_id) @@ -424,8 +427,8 @@ class TestBinarySensorTod(unittest.TestCase): return_value=testtime, ): setup_component(self.hass, "binary_sensor", config) + self.hass.block_till_done() - self.hass.block_till_done() state = self.hass.states.get(entity_id) assert state.state == STATE_OFF @@ -497,8 +500,8 @@ class TestBinarySensorTod(unittest.TestCase): return_value=testtime, ): setup_component(self.hass, "binary_sensor", config) + self.hass.block_till_done() - self.hass.block_till_done() state = self.hass.states.get(entity_id) assert state.state == STATE_OFF @@ -544,6 +547,7 @@ class TestBinarySensorTod(unittest.TestCase): return_value=testtime, ): setup_component(self.hass, "binary_sensor", config) + self.hass.block_till_done() self.hass.block_till_done() state = self.hass.states.get(entity_id) @@ -659,6 +663,7 @@ class TestBinarySensorTod(unittest.TestCase): return_value=testtime, ): setup_component(self.hass, "binary_sensor", config) + self.hass.block_till_done() self.hass.block_till_done() state = self.hass.states.get(entity_id) @@ -774,6 +779,7 @@ class TestBinarySensorTod(unittest.TestCase): return_value=testtime, ): setup_component(self.hass, "binary_sensor", config) + self.hass.block_till_done() self.hass.block_till_done() state = self.hass.states.get(entity_id) @@ -883,6 +889,7 @@ class TestBinarySensorTod(unittest.TestCase): return_value=testtime, ): setup_component(self.hass, "binary_sensor", config) + self.hass.block_till_done() self.hass.block_till_done() state = self.hass.states.get(entity_id) diff --git a/tests/components/tradfri/test_light.py b/tests/components/tradfri/test_light.py index 8ffc25aba5a..a5a5823fbf4 100644 --- a/tests/components/tradfri/test_light.py +++ b/tests/components/tradfri/test_light.py @@ -149,6 +149,7 @@ async def setup_gateway(hass, mock_gateway, mock_api): hass.data[tradfri.KEY_GATEWAY] = {entry.entry_id: mock_gateway} hass.data[tradfri.KEY_API] = {entry.entry_id: mock_api} await hass.config_entries.async_forward_entry_setup(entry, "light") + await hass.async_block_till_done() def mock_light(test_features={}, test_state={}, n=0): diff --git a/tests/components/transport_nsw/test_sensor.py b/tests/components/transport_nsw/test_sensor.py index ab66260a55e..0d12589372e 100644 --- a/tests/components/transport_nsw/test_sensor.py +++ b/tests/components/transport_nsw/test_sensor.py @@ -46,6 +46,7 @@ class TestRMVtransportSensor(unittest.TestCase): def test_transportnsw_config(self, mock_get_departures): """Test minimal TransportNSW configuration.""" assert setup_component(self.hass, "sensor", VALID_CONFIG) + self.hass.block_till_done() state = self.hass.states.get("sensor.next_bus") assert state.state == "16" assert state.attributes["stop_id"] == "209516" diff --git a/tests/components/trend/test_binary_sensor.py b/tests/components/trend/test_binary_sensor.py index fc93df0aacf..0f64afe27cd 100644 --- a/tests/components/trend/test_binary_sensor.py +++ b/tests/components/trend/test_binary_sensor.py @@ -35,6 +35,7 @@ class TestTrendBinarySensor: } }, ) + self.hass.block_till_done() self.hass.states.set("sensor.test_state", "1") self.hass.block_till_done() @@ -62,6 +63,7 @@ class TestTrendBinarySensor: } }, ) + self.hass.block_till_done() now = dt_util.utcnow() for val in [10, 0, 20, 30]: @@ -103,6 +105,7 @@ class TestTrendBinarySensor: } }, ) + self.hass.block_till_done() now = dt_util.utcnow() for val in [30, 20, 30, 10]: @@ -137,6 +140,7 @@ class TestTrendBinarySensor: } }, ) + self.hass.block_till_done() self.hass.states.set("sensor.test_state", "2") self.hass.block_till_done() @@ -162,6 +166,7 @@ class TestTrendBinarySensor: } }, ) + self.hass.block_till_done() self.hass.states.set("sensor.test_state", "1") self.hass.block_till_done() @@ -187,6 +192,7 @@ class TestTrendBinarySensor: } }, ) + self.hass.block_till_done() self.hass.states.set("sensor.test_state", "2") self.hass.block_till_done() @@ -212,6 +218,7 @@ class TestTrendBinarySensor: } }, ) + self.hass.block_till_done() self.hass.states.set("sensor.test_state", "State", {"attr": "1"}) self.hass.block_till_done() self.hass.states.set("sensor.test_state", "State", {"attr": "2"}) @@ -236,6 +243,7 @@ class TestTrendBinarySensor: } }, ) + self.hass.block_till_done() self.hass.states.set("sensor.test_state", "State", {"attr": "2"}) self.hass.block_till_done() @@ -262,6 +270,7 @@ class TestTrendBinarySensor: } }, ) + self.hass.block_till_done() for val in [0, 1, 2, 3, 2, 1]: self.hass.states.set("sensor.test_state", val) @@ -285,6 +294,7 @@ class TestTrendBinarySensor: } }, ) + self.hass.block_till_done() self.hass.states.set("sensor.test_state", "Non") self.hass.block_till_done() @@ -310,6 +320,7 @@ class TestTrendBinarySensor: } }, ) + self.hass.block_till_done() self.hass.states.set("sensor.test_state", "State", {"attr": "2"}) self.hass.block_till_done() diff --git a/tests/components/twitch/test_twitch.py b/tests/components/twitch/test_twitch.py index 1d2675184fc..f66014d6557 100644 --- a/tests/components/twitch/test_twitch.py +++ b/tests/components/twitch/test_twitch.py @@ -55,6 +55,7 @@ async def test_init(hass): "homeassistant.components.twitch.sensor.TwitchClient", return_value=twitch_mock ): assert await async_setup_component(hass, sensor.DOMAIN, CONFIG) is True + await hass.async_block_till_done() sensor_state = hass.states.get(ENTITY_ID) assert sensor_state.state == "offline" @@ -77,6 +78,7 @@ async def test_offline(hass): "homeassistant.components.twitch.sensor.TwitchClient", return_value=twitch_mock, ): assert await async_setup_component(hass, sensor.DOMAIN, CONFIG) is True + await hass.async_block_till_done() sensor_state = hass.states.get(ENTITY_ID) assert sensor_state.state == "offline" @@ -95,6 +97,7 @@ async def test_streaming(hass): "homeassistant.components.twitch.sensor.TwitchClient", return_value=twitch_mock, ): assert await async_setup_component(hass, sensor.DOMAIN, CONFIG) is True + await hass.async_block_till_done() sensor_state = hass.states.get(ENTITY_ID) assert sensor_state.state == "streaming" @@ -117,9 +120,8 @@ async def test_oauth_without_sub_and_follow(hass): with patch( "homeassistant.components.twitch.sensor.TwitchClient", return_value=twitch_mock, ): - assert ( - await async_setup_component(hass, sensor.DOMAIN, CONFIG_WITH_OAUTH) is True - ) + assert await async_setup_component(hass, sensor.DOMAIN, CONFIG_WITH_OAUTH) + await hass.async_block_till_done() sensor_state = hass.states.get(ENTITY_ID) assert sensor_state.attributes["subscribed"] is False @@ -140,9 +142,8 @@ async def test_oauth_with_sub(hass): with patch( "homeassistant.components.twitch.sensor.TwitchClient", return_value=twitch_mock, ): - assert ( - await async_setup_component(hass, sensor.DOMAIN, CONFIG_WITH_OAUTH) is True - ) + assert await async_setup_component(hass, sensor.DOMAIN, CONFIG_WITH_OAUTH) + await hass.async_block_till_done() sensor_state = hass.states.get(ENTITY_ID) assert sensor_state.attributes["subscribed"] is True @@ -165,9 +166,8 @@ async def test_oauth_with_follow(hass): with patch( "homeassistant.components.twitch.sensor.TwitchClient", return_value=twitch_mock, ): - assert ( - await async_setup_component(hass, sensor.DOMAIN, CONFIG_WITH_OAUTH) is True - ) + assert await async_setup_component(hass, sensor.DOMAIN, CONFIG_WITH_OAUTH) + await hass.async_block_till_done() sensor_state = hass.states.get(ENTITY_ID) assert sensor_state.attributes["subscribed"] is False diff --git a/tests/components/uk_transport/test_sensor.py b/tests/components/uk_transport/test_sensor.py index b8eddb9c785..7385592fe93 100644 --- a/tests/components/uk_transport/test_sensor.py +++ b/tests/components/uk_transport/test_sensor.py @@ -61,6 +61,7 @@ class TestUkTransportSensor(unittest.TestCase): uri = re.compile(UkTransportSensor.TRANSPORT_API_URL_BASE + "*") mock_req.get(uri, text=load_fixture("uk_transport_bus.json")) assert setup_component(self.hass, "sensor", {"sensor": self.config}) + self.hass.block_till_done() bus_state = self.hass.states.get("sensor.next_bus_to_wantage") @@ -85,6 +86,7 @@ class TestUkTransportSensor(unittest.TestCase): uri = re.compile(UkTransportSensor.TRANSPORT_API_URL_BASE + "*") mock_req.get(uri, text=load_fixture("uk_transport_train.json")) assert setup_component(self.hass, "sensor", {"sensor": self.config}) + self.hass.block_till_done() train_state = self.hass.states.get("sensor.next_train_to_WAT") diff --git a/tests/components/usgs_earthquakes_feed/test_geo_location.py b/tests/components/usgs_earthquakes_feed/test_geo_location.py index 9bd718d1933..cfd8ae40bcd 100644 --- a/tests/components/usgs_earthquakes_feed/test_geo_location.py +++ b/tests/components/usgs_earthquakes_feed/test_geo_location.py @@ -121,6 +121,7 @@ async def test_setup(hass): ) with assert_setup_component(1, geo_location.DOMAIN): assert await async_setup_component(hass, geo_location.DOMAIN, CONFIG) + await hass.async_block_till_done() # Artificially trigger update. hass.bus.async_fire(EVENT_HOMEASSISTANT_START) # Collect events. @@ -228,6 +229,7 @@ async def test_setup_with_custom_location(hass): assert await async_setup_component( hass, geo_location.DOMAIN, CONFIG_WITH_CUSTOM_LOCATION ) + await hass.async_block_till_done() # Artificially trigger update. hass.bus.async_fire(EVENT_HOMEASSISTANT_START) diff --git a/tests/components/wake_on_lan/test_switch.py b/tests/components/wake_on_lan/test_switch.py index ed4045bcb44..d99bc1ccb4f 100644 --- a/tests/components/wake_on_lan/test_switch.py +++ b/tests/components/wake_on_lan/test_switch.py @@ -57,6 +57,7 @@ class TestWolSwitch(unittest.TestCase): } }, ) + self.hass.block_till_done() state = self.hass.states.get("switch.wake_on_lan") assert STATE_OFF == state.state @@ -93,6 +94,7 @@ class TestWolSwitch(unittest.TestCase): } }, ) + self.hass.block_till_done() state = self.hass.states.get("switch.wake_on_lan") assert STATE_OFF == state.state @@ -130,6 +132,7 @@ class TestWolSwitch(unittest.TestCase): } }, ) + self.hass.block_till_done() state = self.hass.states.get("switch.wake_on_lan") assert STATE_OFF == state.state @@ -155,6 +158,7 @@ class TestWolSwitch(unittest.TestCase): } }, ) + self.hass.block_till_done() calls = mock_service(self.hass, "shell_command", "turn_off_target") state = self.hass.states.get("switch.wake_on_lan") @@ -196,6 +200,7 @@ class TestWolSwitch(unittest.TestCase): } }, ) + self.hass.block_till_done() state = self.hass.states.get("switch.wake_on_lan") assert STATE_OFF == state.state diff --git a/tests/components/weather/test_weather.py b/tests/components/weather/test_weather.py index fd960b594a0..d026cd6ee86 100644 --- a/tests/components/weather/test_weather.py +++ b/tests/components/weather/test_weather.py @@ -32,6 +32,7 @@ class TestWeather(unittest.TestCase): assert setup_component( self.hass, weather.DOMAIN, {"weather": {"platform": "demo"}} ) + self.hass.block_till_done() def tearDown(self): """Stop down everything that was started.""" diff --git a/tests/components/websocket_api/conftest.py b/tests/components/websocket_api/conftest.py index 93538f2b00b..016fdfebc11 100644 --- a/tests/components/websocket_api/conftest.py +++ b/tests/components/websocket_api/conftest.py @@ -16,6 +16,7 @@ async def websocket_client(hass, hass_ws_client): async def no_auth_websocket_client(hass, aiohttp_client): """Websocket connection that requires authentication.""" assert await async_setup_component(hass, "websocket_api", {}) + await hass.async_block_till_done() client = await aiohttp_client(hass.http.app) ws = await client.ws_connect(URL) @@ -23,6 +24,7 @@ async def no_auth_websocket_client(hass, aiohttp_client): auth_ok = await ws.receive_json() assert auth_ok["type"] == TYPE_AUTH_REQUIRED + ws.client = client yield ws if not ws.closed: diff --git a/tests/components/websocket_api/test_auth.py b/tests/components/websocket_api/test_auth.py index 2a0bc9f8c5a..c0313794783 100644 --- a/tests/components/websocket_api/test_auth.py +++ b/tests/components/websocket_api/test_auth.py @@ -1,6 +1,8 @@ """Test auth of websocket API.""" from unittest.mock import patch +import pytest + from homeassistant.components.websocket_api.auth import ( TYPE_AUTH, TYPE_AUTH_INVALID, @@ -12,33 +14,51 @@ from homeassistant.components.websocket_api.const import ( SIGNAL_WEBSOCKET_DISCONNECTED, URL, ) +from homeassistant.core import callback from homeassistant.setup import async_setup_component from tests.common import mock_coro -async def test_auth_events( - hass, no_auth_websocket_client, legacy_auth, hass_access_token -): - """Test authenticating.""" +@pytest.fixture +def track_connected(hass): + """Track connected and disconnected events.""" connected_evt = [] + + @callback + def track_connected(): + connected_evt.append(1) + hass.helpers.dispatcher.async_dispatcher_connect( - SIGNAL_WEBSOCKET_CONNECTED, lambda: connected_evt.append(1) + SIGNAL_WEBSOCKET_CONNECTED, track_connected ) disconnected_evt = [] + + @callback + def track_disconnected(): + disconnected_evt.append(1) + hass.helpers.dispatcher.async_dispatcher_connect( - SIGNAL_WEBSOCKET_DISCONNECTED, lambda: disconnected_evt.append(1) + SIGNAL_WEBSOCKET_DISCONNECTED, track_disconnected ) + return {"connected": connected_evt, "disconnected": disconnected_evt} + + +async def test_auth_events( + hass, no_auth_websocket_client, legacy_auth, hass_access_token, track_connected +): + """Test authenticating.""" + await test_auth_active_with_token(hass, no_auth_websocket_client, hass_access_token) - assert len(connected_evt) == 1 - assert not disconnected_evt + assert len(track_connected["connected"]) == 1 + assert not track_connected["disconnected"] await no_auth_websocket_client.close() await hass.async_block_till_done() - assert len(disconnected_evt) == 1 + assert len(track_connected["disconnected"]) == 1 async def test_auth_via_msg_incorrect_pass(no_auth_websocket_client): @@ -58,27 +78,18 @@ async def test_auth_via_msg_incorrect_pass(no_auth_websocket_client): assert msg["message"] == "Invalid access token or password" -async def test_auth_events_incorrect_pass(hass, no_auth_websocket_client): +async def test_auth_events_incorrect_pass(no_auth_websocket_client, track_connected): """Test authenticating.""" - connected_evt = [] - hass.helpers.dispatcher.async_dispatcher_connect( - SIGNAL_WEBSOCKET_CONNECTED, lambda: connected_evt.append(1) - ) - disconnected_evt = [] - hass.helpers.dispatcher.async_dispatcher_connect( - SIGNAL_WEBSOCKET_DISCONNECTED, lambda: disconnected_evt.append(1) - ) await test_auth_via_msg_incorrect_pass(no_auth_websocket_client) - assert not connected_evt - assert not disconnected_evt + assert not track_connected["connected"] + assert not track_connected["disconnected"] await no_auth_websocket_client.close() - await hass.async_block_till_done() - assert not connected_evt - assert not disconnected_evt + assert not track_connected["connected"] + assert not track_connected["disconnected"] async def test_pre_auth_only_auth_allowed(no_auth_websocket_client): @@ -102,13 +113,11 @@ async def test_auth_active_with_token( hass, no_auth_websocket_client, hass_access_token ): """Test authenticating with a token.""" - assert await async_setup_component(hass, "websocket_api", {}) - await no_auth_websocket_client.send_json( {"type": TYPE_AUTH, "access_token": hass_access_token} ) - auth_msg = await no_auth_websocket_client.receive_json() + assert auth_msg["type"] == TYPE_AUTH_OK @@ -117,6 +126,7 @@ async def test_auth_active_user_inactive(hass, aiohttp_client, hass_access_token refresh_token = await hass.auth.async_validate_access_token(hass_access_token) refresh_token.user.is_active = False assert await async_setup_component(hass, "websocket_api", {}) + await hass.async_block_till_done() client = await aiohttp_client(hass.http.app) @@ -133,6 +143,7 @@ async def test_auth_active_user_inactive(hass, aiohttp_client, hass_access_token async def test_auth_active_with_password_not_allow(hass, aiohttp_client): """Test authenticating with a token.""" assert await async_setup_component(hass, "websocket_api", {}) + await hass.async_block_till_done() client = await aiohttp_client(hass.http.app) @@ -149,6 +160,7 @@ async def test_auth_active_with_password_not_allow(hass, aiohttp_client): async def test_auth_legacy_support_with_password(hass, aiohttp_client, legacy_auth): """Test authenticating with a token.""" assert await async_setup_component(hass, "websocket_api", {}) + await hass.async_block_till_done() client = await aiohttp_client(hass.http.app) @@ -165,6 +177,7 @@ async def test_auth_legacy_support_with_password(hass, aiohttp_client, legacy_au async def test_auth_with_invalid_token(hass, aiohttp_client): """Test authenticating with a token.""" assert await async_setup_component(hass, "websocket_api", {}) + await hass.async_block_till_done() client = await aiohttp_client(hass.http.app) diff --git a/tests/components/websocket_api/test_sensor.py b/tests/components/websocket_api/test_sensor.py index 2c711737851..429876cd365 100644 --- a/tests/components/websocket_api/test_sensor.py +++ b/tests/components/websocket_api/test_sensor.py @@ -1,30 +1,37 @@ """Test cases for the API stream sensor.""" from homeassistant.bootstrap import async_setup_component +from homeassistant.components.websocket_api.auth import TYPE_AUTH_REQUIRED +from homeassistant.components.websocket_api.http import URL from .test_auth import test_auth_active_with_token -from tests.common import assert_setup_component - -async def test_websocket_api( - hass, no_auth_websocket_client, hass_access_token, legacy_auth -): +async def test_websocket_api(hass, aiohttp_client, hass_access_token, legacy_auth): """Test API streams.""" - with assert_setup_component(1): - await async_setup_component( - hass, "sensor", {"sensor": {"platform": "websocket_api"}} - ) + await async_setup_component( + hass, "sensor", {"sensor": {"platform": "websocket_api"}} + ) + await hass.async_block_till_done() + + client = await aiohttp_client(hass.http.app) + ws = await client.ws_connect(URL) + + auth_ok = await ws.receive_json() + + assert auth_ok["type"] == TYPE_AUTH_REQUIRED + + ws.client = client state = hass.states.get("sensor.connected_clients") assert state.state == "0" - await test_auth_active_with_token(hass, no_auth_websocket_client, hass_access_token) + await test_auth_active_with_token(hass, ws, hass_access_token) state = hass.states.get("sensor.connected_clients") assert state.state == "1" - await no_auth_websocket_client.close() + await ws.close() await hass.async_block_till_done() state = hass.states.get("sensor.connected_clients") diff --git a/tests/components/workday/test_binary_sensor.py b/tests/components/workday/test_binary_sensor.py index c476c9fd0e0..1c4ebb29b5a 100644 --- a/tests/components/workday/test_binary_sensor.py +++ b/tests/components/workday/test_binary_sensor.py @@ -109,6 +109,7 @@ class TestWorkdaySetup: """Set up workday component.""" with assert_setup_component(1, "binary_sensor"): setup_component(self.hass, "binary_sensor", self.config_province) + self.hass.block_till_done() entity = self.hass.states.get("binary_sensor.workday_sensor") assert entity is not None @@ -119,6 +120,7 @@ class TestWorkdaySetup: """Test if workdays are reported correctly.""" with assert_setup_component(1, "binary_sensor"): setup_component(self.hass, "binary_sensor", self.config_province) + self.hass.block_till_done() self.hass.start() @@ -131,6 +133,7 @@ class TestWorkdaySetup: """Test if weekends are reported correctly.""" with assert_setup_component(1, "binary_sensor"): setup_component(self.hass, "binary_sensor", self.config_province) + self.hass.block_till_done() self.hass.start() @@ -143,6 +146,7 @@ class TestWorkdaySetup: """Test if public holidays are reported correctly.""" with assert_setup_component(1, "binary_sensor"): setup_component(self.hass, "binary_sensor", self.config_province) + self.hass.block_till_done() self.hass.start() @@ -153,6 +157,7 @@ class TestWorkdaySetup: """Set up workday component.""" with assert_setup_component(1, "binary_sensor"): setup_component(self.hass, "binary_sensor", self.config_noprovince) + self.hass.block_till_done() entity = self.hass.states.get("binary_sensor.workday_sensor") assert entity is not None @@ -163,6 +168,7 @@ class TestWorkdaySetup: """Test if public holidays are reported correctly.""" with assert_setup_component(1, "binary_sensor"): setup_component(self.hass, "binary_sensor", self.config_noprovince) + self.hass.block_till_done() self.hass.start() diff --git a/tests/components/worldclock/test_sensor.py b/tests/components/worldclock/test_sensor.py index b0e8119035d..3d5fc7ab5a7 100644 --- a/tests/components/worldclock/test_sensor.py +++ b/tests/components/worldclock/test_sensor.py @@ -18,6 +18,7 @@ class TestWorldClockSensor(unittest.TestCase): config = {"sensor": {"platform": "worldclock", "time_zone": "America/New_York"}} assert setup_component(self.hass, "sensor", config) + self.hass.block_till_done() def tearDown(self): """Stop everything that was started.""" diff --git a/tests/components/wunderground/test_sensor.py b/tests/components/wunderground/test_sensor.py index a2f681f8e97..b4fb30d25c5 100644 --- a/tests/components/wunderground/test_sensor.py +++ b/tests/components/wunderground/test_sensor.py @@ -61,6 +61,7 @@ async def test_setup(hass, aioclient_mock): with assert_setup_component(1, "sensor"): await async_setup_component(hass, "sensor", {"sensor": VALID_CONFIG}) + await hass.async_block_till_done() async def test_setup_pws(hass, aioclient_mock): @@ -84,6 +85,7 @@ async def test_sensor(hass, aioclient_mock): aioclient_mock.get(URL, text=load_fixture("wunderground-valid.json")) await async_setup_component(hass, "sensor", {"sensor": VALID_CONFIG}) + await hass.async_block_till_done() state = hass.states.get("sensor.pws_weather") assert state.state == "Clear" @@ -136,6 +138,7 @@ async def test_invalid_data(hass, aioclient_mock): aioclient_mock.get(URL, text=load_fixture("wunderground-invalid.json")) await async_setup_component(hass, "sensor", {"sensor": VALID_CONFIG}) + await hass.async_block_till_done() for condition in VALID_CONFIG["monitored_conditions"]: state = hass.states.get(f"sensor.pws_{condition}") diff --git a/tests/components/yamaha/test_media_player.py b/tests/components/yamaha/test_media_player.py index 2f47ddc7355..c0a296bb25b 100644 --- a/tests/components/yamaha/test_media_player.py +++ b/tests/components/yamaha/test_media_player.py @@ -62,6 +62,7 @@ class TestYamahaMediaPlayer(unittest.TestCase): config = {"media_player": {"platform": "yamaha", "host": "127.0.0.1"}} assert setup_component(self.hass, mp.DOMAIN, config) + self.hass.block_till_done() @patch("rxv.RXV") def test_enable_output(self, mock_rxv): diff --git a/tests/components/yandex_transport/test_yandex_transport_sensor.py b/tests/components/yandex_transport/test_yandex_transport_sensor.py index f60e7ba5adf..3583dfa0bdf 100644 --- a/tests/components/yandex_transport/test_yandex_transport_sensor.py +++ b/tests/components/yandex_transport/test_yandex_transport_sensor.py @@ -50,6 +50,7 @@ async def assert_setup_sensor(hass, config, count=1): """Set up the sensor and assert it's been created.""" with assert_setup_component(count): assert await async_setup_component(hass, sensor.DOMAIN, config) + await hass.async_block_till_done() async def test_setup_platform_valid_config(hass, mock_requester): diff --git a/tests/components/yr/test_sensor.py b/tests/components/yr/test_sensor.py index d8dcbe367de..cb0345641b7 100644 --- a/tests/components/yr/test_sensor.py +++ b/tests/components/yr/test_sensor.py @@ -23,6 +23,7 @@ async def test_default_setup(hass, aioclient_mock): "homeassistant.components.yr.sensor.dt_util.utcnow", return_value=NOW ), assert_setup_component(1): await async_setup_component(hass, "sensor", {"sensor": config}) + await hass.async_block_till_done() state = hass.states.get("sensor.yr_symbol") @@ -53,6 +54,7 @@ async def test_custom_setup(hass, aioclient_mock): "homeassistant.components.yr.sensor.dt_util.utcnow", return_value=NOW ), assert_setup_component(1): await async_setup_component(hass, "sensor", {"sensor": config}) + await hass.async_block_till_done() state = hass.states.get("sensor.yr_pressure") assert state.attributes.get("unit_of_measurement") == "hPa" @@ -99,6 +101,7 @@ async def test_forecast_setup(hass, aioclient_mock): "homeassistant.components.yr.sensor.dt_util.utcnow", return_value=NOW ), assert_setup_component(1): await async_setup_component(hass, "sensor", {"sensor": config}) + await hass.async_block_till_done() state = hass.states.get("sensor.yr_pressure") assert state.attributes.get("unit_of_measurement") == "hPa" diff --git a/tests/helpers/test_entity_component.py b/tests/helpers/test_entity_component.py index 625f21d9d9f..8935b25830e 100644 --- a/tests/helpers/test_entity_component.py +++ b/tests/helpers/test_entity_component.py @@ -180,7 +180,7 @@ async def test_platform_not_ready(hass): component = EntityComponent(_LOGGER, DOMAIN, hass) await component.async_setup({DOMAIN: {"platform": "mod1"}}) - + await hass.async_block_till_done() assert len(platform1_setup.mock_calls) == 1 assert "test_domain.mod1" not in hass.config.components @@ -280,7 +280,7 @@ async def test_setup_dependencies_platform(hass): component = EntityComponent(_LOGGER, DOMAIN, hass) await component.async_setup({DOMAIN: {"platform": "test_component"}}) - + await hass.async_block_till_done() assert "test_component" in hass.config.components assert "test_component2" in hass.config.components assert "test_domain.test_component" in hass.config.components diff --git a/tests/helpers/test_entity_platform.py b/tests/helpers/test_entity_platform.py index 039f83d7031..11dded7416f 100644 --- a/tests/helpers/test_entity_platform.py +++ b/tests/helpers/test_entity_platform.py @@ -184,6 +184,7 @@ async def test_platform_warn_slow_setup(hass): with patch.object(hass.loop, "call_later") as mock_call: await component.async_setup({DOMAIN: {"platform": "platform"}}) + await hass.async_block_till_done() assert mock_call.called # mock_calls[0] is the warning message for component setup @@ -209,6 +210,7 @@ async def test_platform_error_slow_setup(hass, caplog): component = EntityComponent(_LOGGER, DOMAIN, hass) mock_entity_platform(hass, "test_domain.test_platform", platform) await component.async_setup({DOMAIN: {"platform": "test_platform"}}) + await hass.async_block_till_done() assert len(called) == 1 assert "test_domain.test_platform" not in hass.config.components assert "test_platform is taking longer than 0 seconds" in caplog.text @@ -242,6 +244,7 @@ async def test_parallel_updates_async_platform(hass): component._platforms = {} await component.async_setup({DOMAIN: {"platform": "platform"}}) + await hass.async_block_till_done() handle = list(component._platforms.values())[-1] assert handle.parallel_updates is None @@ -268,6 +271,7 @@ async def test_parallel_updates_async_platform_with_constant(hass): component._platforms = {} await component.async_setup({DOMAIN: {"platform": "platform"}}) + await hass.async_block_till_done() handle = list(component._platforms.values())[-1] @@ -293,6 +297,7 @@ async def test_parallel_updates_sync_platform(hass): component._platforms = {} await component.async_setup({DOMAIN: {"platform": "platform"}}) + await hass.async_block_till_done() handle = list(component._platforms.values())[-1] @@ -319,6 +324,7 @@ async def test_parallel_updates_sync_platform_with_constant(hass): component._platforms = {} await component.async_setup({DOMAIN: {"platform": "platform"}}) + await hass.async_block_till_done() handle = list(component._platforms.values())[-1] diff --git a/tests/helpers/test_translation.py b/tests/helpers/test_translation.py index 77e55a1d6ed..440b3d75439 100644 --- a/tests/helpers/test_translation.py +++ b/tests/helpers/test_translation.py @@ -113,6 +113,7 @@ async def test_get_translations(hass, mock_config_flows): assert translations == {} assert await async_setup_component(hass, "switch", {"switch": {"platform": "test"}}) + await hass.async_block_till_done() translations = await translation.async_get_translations(hass, "en", "state") diff --git a/tests/test_setup.py b/tests/test_setup.py index 820b0c4db23..4ff380d0cc8 100644 --- a/tests/test_setup.py +++ b/tests/test_setup.py @@ -358,6 +358,7 @@ class TestSetup: "switch", {"comp_a": {"valid": True}, "switch": {"platform": "platform_a"}}, ) + self.hass.block_till_done() assert "comp_a" in self.hass.config.components def test_platform_specific_config_validation(self): @@ -380,6 +381,7 @@ class TestSetup: "switch", {"switch": {"platform": "platform_a", "invalid": True}}, ) + self.hass.block_till_done() assert mock_setup.call_count == 0 self.hass.data.pop(setup.DATA_SETUP) @@ -397,6 +399,7 @@ class TestSetup: } }, ) + self.hass.block_till_done() assert mock_setup.call_count == 0 self.hass.data.pop(setup.DATA_SETUP) @@ -408,6 +411,7 @@ class TestSetup: "switch", {"switch": {"platform": "platform_a", "valid": True}}, ) + self.hass.block_till_done() assert mock_setup.call_count == 1 def test_disable_component_if_invalid_return(self): From e30893ed80f03be5c46f7c61830ff2ac8446f154 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 1 Jun 2020 00:24:16 -0700 Subject: [PATCH 275/406] Add block_till_done to buienradar test (#36332) --- tests/components/buienradar/test_camera.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/components/buienradar/test_camera.py b/tests/components/buienradar/test_camera.py index 96c5d2ec67c..5a19d8e70dd 100644 --- a/tests/components/buienradar/test_camera.py +++ b/tests/components/buienradar/test_camera.py @@ -23,6 +23,7 @@ async def test_fetching_url_and_caching(aioclient_mock, hass, hass_client): await async_setup_component( hass, "camera", {"camera": {"name": "config_test", "platform": "buienradar"}} ) + await hass.async_block_till_done() client = await hass_client() From 691ec21ba41f98fb4ca77ed54a37071a92709ac1 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 1 Jun 2020 00:44:18 -0700 Subject: [PATCH 276/406] Mark min version of httplib2 (#36330) --- homeassistant/package_constraints.txt | 3 +++ script/gen_requirements_all.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 87f0b1feb76..ac7568b1b69 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -32,6 +32,9 @@ pycryptodome>=3.6.6 # Constrain urllib3 to ensure we deal with CVE-2019-11236 & CVE-2019-11324 urllib3>=1.24.3 +# Constrain httplib2 to protect against CVE-2020-11078 +httplib2>=0.18.0 + # Not needed for our supported Python versions enum34==1000000000.0.0 diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py index fdd0c564efb..c3b7b05dc30 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -61,6 +61,9 @@ pycryptodome>=3.6.6 # Constrain urllib3 to ensure we deal with CVE-2019-11236 & CVE-2019-11324 urllib3>=1.24.3 +# Constrain httplib2 to protect against CVE-2020-11078 +httplib2>=0.18.0 + # Not needed for our supported Python versions enum34==1000000000.0.0 From 7da15378b1f3647235cbf44b07cbac4ddc2524e9 Mon Sep 17 00:00:00 2001 From: Alan Tse Date: Mon, 1 Jun 2020 03:13:11 -0700 Subject: [PATCH 277/406] Bump teslajsonpy to 0.8.1 (#36323) --- homeassistant/components/tesla/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/tesla/manifest.json b/homeassistant/components/tesla/manifest.json index 3d21fbae960..39aa00cfb60 100644 --- a/homeassistant/components/tesla/manifest.json +++ b/homeassistant/components/tesla/manifest.json @@ -3,6 +3,6 @@ "name": "Tesla", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/tesla", - "requirements": ["teslajsonpy==0.8.0"], + "requirements": ["teslajsonpy==0.8.1"], "codeowners": ["@zabuldon", "@alandtse"] } diff --git a/requirements_all.txt b/requirements_all.txt index 98759a6a077..b91a4a9e39d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2084,7 +2084,7 @@ temperusb==1.5.3 tesla-powerwall==0.2.8 # homeassistant.components.tesla -teslajsonpy==0.8.0 +teslajsonpy==0.8.1 # homeassistant.components.thermoworks_smoke thermoworks_smoke==0.1.8 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 44b069ab2ca..dc6e1cba7da 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -844,7 +844,7 @@ tellduslive==0.10.11 tesla-powerwall==0.2.8 # homeassistant.components.tesla -teslajsonpy==0.8.0 +teslajsonpy==0.8.1 # homeassistant.components.toon toonapilib==3.2.4 From 5d7720832bb351e92cef5e74c0b63b433356b356 Mon Sep 17 00:00:00 2001 From: Florent Thoumie Date: Mon, 1 Jun 2020 03:20:46 -0700 Subject: [PATCH 278/406] Bump iaqualink to 0.3.4 (#36317) --- homeassistant/components/iaqualink/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/iaqualink/manifest.json b/homeassistant/components/iaqualink/manifest.json index 68b4554f73a..d0d9b7ed7f2 100644 --- a/homeassistant/components/iaqualink/manifest.json +++ b/homeassistant/components/iaqualink/manifest.json @@ -4,5 +4,5 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/iaqualink/", "codeowners": ["@flz"], - "requirements": ["iaqualink==0.3.3"] + "requirements": ["iaqualink==0.3.4"] } diff --git a/requirements_all.txt b/requirements_all.txt index b91a4a9e39d..4d9aaae1196 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -770,7 +770,7 @@ hydrawiser==0.1.1 iammeter==0.1.7 # homeassistant.components.iaqualink -iaqualink==0.3.3 +iaqualink==0.3.4 # homeassistant.components.watson_tts ibm-watson==4.0.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index dc6e1cba7da..aa7a3997dc9 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -340,7 +340,7 @@ httplib2==0.10.3 huawei-lte-api==1.4.12 # homeassistant.components.iaqualink -iaqualink==0.3.3 +iaqualink==0.3.4 # homeassistant.components.influxdb influxdb==5.2.3 From d0fedad000019e4ebe9193a121c18542bd96bdd6 Mon Sep 17 00:00:00 2001 From: Tom Date: Mon, 1 Jun 2020 14:01:17 +0200 Subject: [PATCH 279/406] Update plugwise to async and config_flow sensor part (#36219) Co-authored-by: J. Nick Koston --- .coveragerc | 1 + homeassistant/components/plugwise/__init__.py | 57 ++- homeassistant/components/plugwise/climate.py | 86 ++--- .../components/plugwise/config_flow.py | 2 +- homeassistant/components/plugwise/const.py | 21 +- .../components/plugwise/manifest.json | 1 - homeassistant/components/plugwise/sensor.py | 354 ++++++++++++++++++ 7 files changed, 439 insertions(+), 83 deletions(-) create mode 100644 homeassistant/components/plugwise/sensor.py diff --git a/.coveragerc b/.coveragerc index 5003e179b80..23b00aed6de 100644 --- a/.coveragerc +++ b/.coveragerc @@ -606,6 +606,7 @@ omit = homeassistant/components/plex/sensor.py homeassistant/components/plugwise/__init__.py homeassistant/components/plugwise/climate.py + homeassistant/components/plugwise/sensor.py homeassistant/components/plum_lightpad/* homeassistant/components/pocketcasts/sensor.py homeassistant/components/point/* diff --git a/homeassistant/components/plugwise/__init__.py b/homeassistant/components/plugwise/__init__.py index c6509091e1c..610acfc3e23 100644 --- a/homeassistant/components/plugwise/__init__.py +++ b/homeassistant/components/plugwise/__init__.py @@ -3,13 +3,14 @@ import asyncio from datetime import timedelta import logging +from typing import Dict from Plugwise_Smile.Smile import Smile import async_timeout import voluptuous as vol from homeassistant.config_entries import ConfigEntry -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import device_registry as dr from homeassistant.helpers.aiohttp_client import async_get_clientsession @@ -22,7 +23,8 @@ CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.Schema({})}, extra=vol.ALLOW_EXTRA) _LOGGER = logging.getLogger(__name__) -ALL_PLATFORMS = ["climate"] +SENSOR_PLATFORMS = ["sensor"] +ALL_PLATFORMS = ["climate", "sensor"] async def async_setup(hass: HomeAssistant, config: dict): @@ -100,7 +102,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: sw_version=api.smile_version[0], ) - for component in ALL_PLATFORMS: + platforms = ALL_PLATFORMS + + single_master_thermostat = api.single_master_thermostat() + if single_master_thermostat is None: + platforms = SENSOR_PLATFORMS + + for component in platforms: hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, component) ) @@ -127,11 +135,17 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): class SmileGateway(Entity): """Represent Smile Gateway.""" - def __init__(self, api, coordinator): - """Initialise the sensor.""" + def __init__(self, api, coordinator, name, dev_id): + """Initialise the gateway.""" self._api = api self._coordinator = coordinator + self._name = name + self._dev_id = dev_id + self._unique_id = None + self._model = None + + self._entity_name = self._name @property def unique_id(self): @@ -148,11 +162,40 @@ class SmileGateway(Entity): """Return True if entity is available.""" return self._coordinator.last_update_success + @property + def name(self): + """Return the name of the entity, if any.""" + if not self._name: + return None + return self._name + + @property + def device_info(self) -> Dict[str, any]: + """Return the device information.""" + + device_information = { + "identifiers": {(DOMAIN, self._dev_id)}, + "name": self._entity_name, + "manufacturer": "Plugwise", + } + + if self._model is not None: + device_information["model"] = self._model.replace("_", " ").title() + + if self._dev_id != self._api.gateway_id: + device_information["via_device"] = (DOMAIN, self._api.gateway_id) + + return device_information + async def async_added_to_hass(self): """Subscribe to updates.""" - self.async_on_remove(self._coordinator.async_add_listener(self._process_data)) + self._async_process_data() + self.async_on_remove( + self._coordinator.async_add_listener(self._async_process_data) + ) - def _process_data(self): + @callback + def _async_process_data(self): """Interpret and process API data.""" raise NotImplementedError diff --git a/homeassistant/components/plugwise/climate.py b/homeassistant/components/plugwise/climate.py index 209fdcdd242..42d4aa462b6 100644 --- a/homeassistant/components/plugwise/climate.py +++ b/homeassistant/components/plugwise/climate.py @@ -1,7 +1,6 @@ """Plugwise Climate component for Home Assistant.""" import logging -from typing import Dict from Plugwise_Smile.Smile import Smile @@ -17,16 +16,10 @@ from homeassistant.components.climate.const import ( SUPPORT_TARGET_TEMPERATURE, ) from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS +from homeassistant.core import callback from . import SmileGateway -from .const import ( - DEFAULT_MAX_TEMP, - DEFAULT_MIN_TEMP, - DOMAIN, - SCHEDULE_OFF, - SCHEDULE_ON, - THERMOSTAT_ICON, -) +from .const import DEFAULT_MAX_TEMP, DEFAULT_MIN_TEMP, DOMAIN, SCHEDULE_OFF, SCHEDULE_ON HVAC_MODES_HEAT_ONLY = [HVAC_MODE_HEAT, HVAC_MODE_AUTO] HVAC_MODES_HEAT_COOL = [HVAC_MODE_HEAT_COOL, HVAC_MODE_AUTO] @@ -47,20 +40,20 @@ async def async_setup_entry(hass, config_entry, async_add_entities): "zone_thermostat", "thermostatic_radiator_valve", ] - all_entities = api.get_all_devices() + all_devices = api.get_all_devices() - for dev_id, device in all_entities.items(): + for dev_id, device_properties in all_devices.items(): - if device["class"] not in thermostat_classes: + if device_properties["class"] not in thermostat_classes: continue thermostat = PwThermostat( api, coordinator, - device["name"], + device_properties["name"], dev_id, - device["location"], - device["class"], + device_properties["location"], + device_properties["class"], DEFAULT_MIN_TEMP, DEFAULT_MAX_TEMP, ) @@ -77,11 +70,9 @@ class PwThermostat(SmileGateway, ClimateEntity): self, api, coordinator, name, dev_id, loc_id, model, min_temp, max_temp ): """Set up the Plugwise API.""" - super().__init__(api, coordinator) + super().__init__(api, coordinator, name, dev_id) self._api = api - self._name = name - self._dev_id = dev_id self._loc_id = loc_id self._model = model self._min_temp = min_temp @@ -92,9 +83,9 @@ class PwThermostat(SmileGateway, ClimateEntity): self._preset_mode = None self._presets = None self._presets_list = None - self._boiler_state = None self._heating_state = None self._cooling_state = None + self._compressor_state = None self._dhw_state = None self._hvac_mode = None self._schema_names = None @@ -111,43 +102,16 @@ class PwThermostat(SmileGateway, ClimateEntity): def hvac_action(self): """Return the current action.""" if self._single_thermostat: - if self._heating_state or self._boiler_state: + if self._heating_state: return CURRENT_HVAC_HEAT if self._cooling_state: return CURRENT_HVAC_COOL return CURRENT_HVAC_IDLE - if self._heating_state is not None or self._boiler_state is not None: + if self._heating_state is not None: if self._setpoint > self._temperature: return CURRENT_HVAC_HEAT return CURRENT_HVAC_IDLE - @property - def name(self): - """Return the name of the thermostat, if any.""" - return self._name - - @property - def device_info(self) -> Dict[str, any]: - """Return the device information.""" - - device_information = { - "identifiers": {(DOMAIN, self._dev_id)}, - "name": self._name, - "manufacturer": "Plugwise", - "model": self._model.replace("_", " ").title(), - } - - if self._dev_id != self._api.gateway_id: - device_information["via_device"] = (DOMAIN, self._api.gateway_id) - del device_information["via_device"] - - return device_information - - @property - def icon(self): - """Return the icon to use in the frontend.""" - return THERMOSTAT_ICON - @property def supported_features(self): """Return the list of supported features.""" @@ -172,8 +136,8 @@ class PwThermostat(SmileGateway, ClimateEntity): @property def hvac_modes(self): """Return the available hvac modes list.""" - if self._heating_state is not None or self._boiler_state is not None: - if self._cooling_state is not None: + if self._heating_state is not None: + if self._compressor_state is not None: return HVAC_MODES_HEAT_COOL return HVAC_MODES_HEAT_ONLY @@ -258,7 +222,8 @@ class PwThermostat(SmileGateway, ClimateEntity): except Smile.PlugwiseError: _LOGGER.error("Error while communicating to device") - def _process_data(self): + @callback + def _async_process_data(self): """Update the data for this climate device.""" climate_data = self._api.get_device_data(self._dev_id) heater_central_data = self._api.get_device_data(self._api.heater_id) @@ -286,21 +251,18 @@ class PwThermostat(SmileGateway, ClimateEntity): if "active_preset" in climate_data: self._preset_mode = climate_data["active_preset"] - if "boiler_state" in heater_central_data: - if heater_central_data["boiler_state"] is not None: - self._boiler_state = heater_central_data["boiler_state"] - if "heating_state" in heater_central_data: - if heater_central_data["heating_state"] is not None: - self._heating_state = heater_central_data["heating_state"] - if "cooling_state" in heater_central_data: - if heater_central_data["cooling_state"] is not None: - self._cooling_state = heater_central_data["cooling_state"] + if heater_central_data.get("heating_state") is not None: + self._heating_state = heater_central_data["heating_state"] + if heater_central_data.get("cooling_state") is not None: + self._cooling_state = heater_central_data["cooling_state"] + if heater_central_data.get("compressor_state") is not None: + self._compressor_state = heater_central_data["compressor_state"] if self._schema_status: self._hvac_mode = HVAC_MODE_AUTO - elif self._heating_state is not None or self._boiler_state is not None: + elif self._heating_state is not None: self._hvac_mode = HVAC_MODE_HEAT - if self._cooling_state is not None: + if self._compressor_state is not None: self._hvac_mode = HVAC_MODE_HEAT_COOL self.async_write_ha_state() diff --git a/homeassistant/components/plugwise/config_flow.py b/homeassistant/components/plugwise/config_flow.py index 7182665a506..8f82a107576 100644 --- a/homeassistant/components/plugwise/config_flow.py +++ b/homeassistant/components/plugwise/config_flow.py @@ -47,7 +47,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user(self, user_input=None): """Handle the initial step.""" errors = {} - api = None + if user_input is not None: try: diff --git a/homeassistant/components/plugwise/const.py b/homeassistant/components/plugwise/const.py index 2f804ef09a3..b57532b9192 100644 --- a/homeassistant/components/plugwise/const.py +++ b/homeassistant/components/plugwise/const.py @@ -1,6 +1,11 @@ """Constant for Plugwise component.""" DOMAIN = "plugwise" +# Sensor mapping +SENSOR_MAP_MODEL = 0 +SENSOR_MAP_UOM = 1 +SENSOR_MAP_DEVICE_CLASS = 2 + # Default directives DEFAULT_NAME = "Smile" DEFAULT_USERNAME = "smile" @@ -10,8 +15,6 @@ DEFAULT_MIN_TEMP = 4 DEFAULT_MAX_TEMP = 30 DEFAULT_SCAN_INTERVAL = {"thermostat": 60, "power": 10} -DEVICE_CLASS_GAS = "gas" - # Configuration directives CONF_MIN_TEMP = "min_temp" CONF_MAX_TEMP = "max_temp" @@ -22,21 +25,15 @@ CONF_SOLAR = "solar" CONF_GAS = "gas" ATTR_ILLUMINANCE = "illuminance" +UNIT_LUMEN = "lm" + CURRENT_HVAC_DHW = "hot_water" + DEVICE_STATE = "device_state" SCHEDULE_ON = "true" SCHEDULE_OFF = "false" -# Icons -SWITCH_ICON = "mdi:electric-switch" -THERMOSTAT_ICON = "mdi:thermometer" -WATER_ICON = "mdi:water-pump" -FLAME_ICON = "mdi:fire" COOL_ICON = "mdi:snowflake" +FLAME_ICON = "mdi:fire" IDLE_ICON = "mdi:circle-off-outline" -GAS_ICON = "mdi:fire" -POWER_ICON = "mdi:flash" -POWER_FAILURE_ICON = "mdi:flash-off" -SWELL_SAG_ICON = "mdi:pulse" -VALVE_ICON = "mdi:valve" diff --git a/homeassistant/components/plugwise/manifest.json b/homeassistant/components/plugwise/manifest.json index 2a0d5a1e0fc..baecf485682 100644 --- a/homeassistant/components/plugwise/manifest.json +++ b/homeassistant/components/plugwise/manifest.json @@ -3,7 +3,6 @@ "name": "Plugwise", "documentation": "https://www.home-assistant.io/integrations/plugwise", "requirements": ["Plugwise_Smile==0.2.10"], - "dependencies": [], "codeowners": ["@CoMPaTech", "@bouwew"], "config_flow": true } diff --git a/homeassistant/components/plugwise/sensor.py b/homeassistant/components/plugwise/sensor.py new file mode 100644 index 00000000000..eabb5c6655f --- /dev/null +++ b/homeassistant/components/plugwise/sensor.py @@ -0,0 +1,354 @@ +"""Plugwise Sensor component for Home Assistant.""" + +import logging + +from homeassistant.const import ( + DEVICE_CLASS_BATTERY, + DEVICE_CLASS_ILLUMINANCE, + DEVICE_CLASS_POWER, + DEVICE_CLASS_PRESSURE, + DEVICE_CLASS_TEMPERATURE, + ENERGY_KILO_WATT_HOUR, + ENERGY_WATT_HOUR, + POWER_WATT, + PRESSURE_BAR, + TEMP_CELSIUS, + UNIT_PERCENTAGE, + VOLUME_CUBIC_METERS, +) +from homeassistant.core import callback +from homeassistant.helpers.entity import Entity + +from . import SmileGateway +from .const import ( + COOL_ICON, + DEVICE_STATE, + DOMAIN, + FLAME_ICON, + IDLE_ICON, + SENSOR_MAP_DEVICE_CLASS, + SENSOR_MAP_MODEL, + SENSOR_MAP_UOM, + UNIT_LUMEN, +) + +_LOGGER = logging.getLogger(__name__) + +ATTR_TEMPERATURE = [ + "Temperature", + TEMP_CELSIUS, + DEVICE_CLASS_TEMPERATURE, +] +ATTR_BATTERY_LEVEL = [ + "Charge", + UNIT_PERCENTAGE, + DEVICE_CLASS_BATTERY, +] +ATTR_ILLUMINANCE = [ + "Illuminance", + UNIT_LUMEN, + DEVICE_CLASS_ILLUMINANCE, +] +ATTR_PRESSURE = ["Pressure", PRESSURE_BAR, DEVICE_CLASS_PRESSURE] + +TEMP_SENSOR_MAP = { + "setpoint": ATTR_TEMPERATURE, + "temperature": ATTR_TEMPERATURE, + "intended_boiler_temperature": ATTR_TEMPERATURE, + "temperature_difference": ATTR_TEMPERATURE, + "outdoor_temperature": ATTR_TEMPERATURE, + "water_temperature": ATTR_TEMPERATURE, + "return_temperature": ATTR_TEMPERATURE, +} + +ENERGY_SENSOR_MAP = { + "electricity_consumed": ["Current Consumed Power", POWER_WATT, DEVICE_CLASS_POWER], + "electricity_produced": ["Current Produced Power", POWER_WATT, DEVICE_CLASS_POWER], + "electricity_consumed_interval": [ + "Consumed Power Interval", + ENERGY_WATT_HOUR, + DEVICE_CLASS_POWER, + ], + "electricity_produced_interval": [ + "Produced Power Interval", + ENERGY_WATT_HOUR, + DEVICE_CLASS_POWER, + ], + "electricity_consumed_off_peak_point": [ + "Current Consumed Power (off peak)", + POWER_WATT, + DEVICE_CLASS_POWER, + ], + "electricity_consumed_peak_point": [ + "Current Consumed Power", + POWER_WATT, + DEVICE_CLASS_POWER, + ], + "electricity_consumed_off_peak_cumulative": [ + "Cumulative Consumed Power (off peak)", + ENERGY_KILO_WATT_HOUR, + DEVICE_CLASS_POWER, + ], + "electricity_consumed_peak_cumulative": [ + "Cumulative Consumed Power", + ENERGY_KILO_WATT_HOUR, + DEVICE_CLASS_POWER, + ], + "electricity_produced_off_peak_point": [ + "Current Consumed Power (off peak)", + POWER_WATT, + DEVICE_CLASS_POWER, + ], + "electricity_produced_peak_point": [ + "Current Consumed Power", + POWER_WATT, + DEVICE_CLASS_POWER, + ], + "electricity_produced_off_peak_cumulative": [ + "Cumulative Consumed Power (off peak)", + ENERGY_KILO_WATT_HOUR, + DEVICE_CLASS_POWER, + ], + "electricity_produced_peak_cumulative": [ + "Cumulative Consumed Power", + ENERGY_KILO_WATT_HOUR, + DEVICE_CLASS_POWER, + ], + "gas_consumed_interval": ["Current Consumed Gas", VOLUME_CUBIC_METERS, None], + "gas_consumed_cumulative": ["Cumulative Consumed Gas", VOLUME_CUBIC_METERS, None], + "net_electricity_point": ["Current net Power", POWER_WATT, DEVICE_CLASS_POWER], + "net_electricity_cumulative": [ + "Cumulative net Power", + ENERGY_KILO_WATT_HOUR, + DEVICE_CLASS_POWER, + ], +} + +MISC_SENSOR_MAP = { + "battery": ATTR_BATTERY_LEVEL, + "illuminance": ATTR_ILLUMINANCE, + "modulation_level": ["Heater Modulation Level", UNIT_PERCENTAGE, None], + "valve_position": ["Valve Position", UNIT_PERCENTAGE, None], + "water_pressure": ATTR_PRESSURE, +} + +INDICATE_ACTIVE_LOCAL_DEVICE = [ + "cooling_state", + "flame_state", +] + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up the Smile sensors from a config entry.""" + api = hass.data[DOMAIN][config_entry.entry_id]["api"] + coordinator = hass.data[DOMAIN][config_entry.entry_id]["coordinator"] + + entities = [] + all_devices = api.get_all_devices() + single_thermostat = api.single_master_thermostat() + for dev_id, device_properties in all_devices.items(): + data = api.get_device_data(dev_id) + for sensor, sensor_type in { + **TEMP_SENSOR_MAP, + **ENERGY_SENSOR_MAP, + **MISC_SENSOR_MAP, + }.items(): + if sensor in data: + if data[sensor] is None: + continue + + if "power" in device_properties["types"]: + model = None + + if "plug" in device_properties["types"]: + model = "Metered Switch" + + entities.append( + PwPowerSensor( + api, + coordinator, + device_properties["name"], + dev_id, + sensor, + sensor_type, + model, + ) + ) + else: + entities.append( + PwThermostatSensor( + api, + coordinator, + device_properties["name"], + dev_id, + sensor, + sensor_type, + ) + ) + + if single_thermostat is False: + for state in INDICATE_ACTIVE_LOCAL_DEVICE: + if state in data: + entities.append( + PwAuxDeviceSensor( + api, + coordinator, + device_properties["name"], + dev_id, + DEVICE_STATE, + ) + ) + break + + async_add_entities(entities, True) + + +class SmileSensor(SmileGateway): + """Represent Smile Sensors.""" + + def __init__(self, api, coordinator, name, dev_id, sensor): + """Initialise the sensor.""" + super().__init__(api, coordinator, name, dev_id) + + self._sensor = sensor + + self._dev_class = None + self._state = None + self._unit_of_measurement = None + + if dev_id == self._api.heater_id: + self._entity_name = "Auxiliary" + + sensorname = sensor.replace("_", " ").title() + self._name = f"{self._entity_name} {sensorname}" + + if dev_id == self._api.gateway_id: + self._entity_name = f"Smile {self._entity_name}" + + self._unique_id = f"{dev_id}-{sensor}" + + @property + def device_class(self): + """Device class of this entity.""" + return self._dev_class + + @property + def state(self): + """Device class of this entity.""" + return self._state + + @property + def unit_of_measurement(self): + """Return the unit of measurement of this entity, if any.""" + return self._unit_of_measurement + + +class PwThermostatSensor(SmileSensor, Entity): + """Thermostat and climate sensor entities.""" + + def __init__(self, api, coordinator, name, dev_id, sensor, sensor_type): + """Set up the Plugwise API.""" + super().__init__(api, coordinator, name, dev_id, sensor) + + self._model = sensor_type[SENSOR_MAP_MODEL] + self._unit_of_measurement = sensor_type[SENSOR_MAP_UOM] + self._dev_class = sensor_type[SENSOR_MAP_DEVICE_CLASS] + + @callback + def _async_process_data(self): + """Update the entity.""" + data = self._api.get_device_data(self._dev_id) + + if not data: + _LOGGER.error("Received no data for device %s.", self._entity_name) + self.async_write_ha_state() + return + + if data.get(self._sensor) is not None: + measurement = data[self._sensor] + if self._sensor == "battery" or self._sensor == "valve_position": + measurement = measurement * 100 + if self._unit_of_measurement == UNIT_PERCENTAGE: + measurement = int(measurement) + self._state = measurement + + self.async_write_ha_state() + + +class PwAuxDeviceSensor(SmileSensor, Entity): + """Auxiliary sensor entities for the heating/cooling device.""" + + def __init__(self, api, coordinator, name, dev_id, sensor): + """Set up the Plugwise API.""" + super().__init__(api, coordinator, name, dev_id, sensor) + + self._cooling_state = False + self._heating_state = False + self._icon = None + + @property + def icon(self): + """Return the icon to use in the frontend.""" + return self._icon + + @callback + def _async_process_data(self): + """Update the entity.""" + data = self._api.get_device_data(self._dev_id) + + if not data: + _LOGGER.error("Received no data for device %s.", self._entity_name) + self.async_write_ha_state() + return + + if data.get("heating_state") is not None: + self._heating_state = data["heating_state"] + if data.get("cooling_state") is not None: + self._cooling_state = data["cooling_state"] + + self._state = "idle" + self._icon = IDLE_ICON + if self._heating_state: + self._state = "heating" + self._icon = FLAME_ICON + if self._cooling_state: + self._state = "cooling" + self._icon = COOL_ICON + + self.async_write_ha_state() + + +class PwPowerSensor(SmileSensor, Entity): + """Power sensor entities.""" + + def __init__(self, api, coordinator, name, dev_id, sensor, sensor_type, model): + """Set up the Plugwise API.""" + super().__init__(api, coordinator, name, dev_id, sensor) + + self._model = model + if model is None: + self._model = sensor_type[SENSOR_MAP_MODEL] + + self._unit_of_measurement = sensor_type[SENSOR_MAP_UOM] + self._dev_class = sensor_type[SENSOR_MAP_DEVICE_CLASS] + + if dev_id == self._api.gateway_id: + self._model = "P1 DSMR" + + @callback + def _async_process_data(self): + """Update the entity.""" + data = self._api.get_device_data(self._dev_id) + + if not data: + _LOGGER.error("Received no data for device %s.", self._entity_name) + self.async_write_ha_state() + return + + if data.get(self._sensor) is not None: + measurement = data[self._sensor] + if self._unit_of_measurement == ENERGY_KILO_WATT_HOUR: + measurement = int(measurement / 1000) + self._state = measurement + + self.async_write_ha_state() From aa1b32ad36213e3346cdd9919944f0b085e85e55 Mon Sep 17 00:00:00 2001 From: Wim Haanstra Date: Mon, 1 Jun 2020 14:10:09 +0200 Subject: [PATCH 280/406] Add new DSMR Reader fields that were added to MQTT (#36337) --- .../components/dsmr_reader/definitions.py | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/dsmr_reader/definitions.py b/homeassistant/components/dsmr_reader/definitions.py index b6740655434..0ec67bc97fd 100644 --- a/homeassistant/components/dsmr_reader/definitions.py +++ b/homeassistant/components/dsmr_reader/definitions.py @@ -1,6 +1,11 @@ """Definitions for DSMR Reader sensors added to MQTT.""" -from homeassistant.const import ENERGY_KILO_WATT_HOUR, VOLT, VOLUME_CUBIC_METERS +from homeassistant.const import ( + ELECTRICAL_CURRENT_AMPERE, + ENERGY_KILO_WATT_HOUR, + VOLT, + VOLUME_CUBIC_METERS, +) def dsmr_transform(value): @@ -98,6 +103,21 @@ DEFINITIONS = { "icon": "mdi:flash", "unit": VOLT, }, + "dsmr/reading/phase_power_current_l1": { + "name": "Phase power current L1", + "icon": "mdi:flash", + "unit": ELECTRICAL_CURRENT_AMPERE, + }, + "dsmr/reading/phase_power_current_l2": { + "name": "Phase power current L2", + "icon": "mdi:flash", + "unit": ELECTRICAL_CURRENT_AMPERE, + }, + "dsmr/reading/phase_power_current_l3": { + "name": "Phase power current L3", + "icon": "mdi:flash", + "unit": ELECTRICAL_CURRENT_AMPERE, + }, "dsmr/consumption/gas/delivered": { "name": "Gas usage", "icon": "mdi:fire", From 5827a26dfc7a3bebaba3d59e97c1ea7166b7571a Mon Sep 17 00:00:00 2001 From: Luke Pomfrey Date: Mon, 1 Jun 2020 12:15:01 +0000 Subject: [PATCH 281/406] Fix handling of min_size argument in OpenCV component (#36335) The OpenCV image_processing component accepts a `min_size` argument in each classifier that is then passed into the OpenCV library. The `min_size` argument is expected to be a tuple of 2 ints, and is entered into the platofrm schema as such. However, yaml will always produce a list instead of a tuple, which means in practice it's impossible to use the `min_size` argument. This changes the schema to accepts any sequence of 2 ints that is then coerced into a tuple suitable for passing to the OpenCV library. --- homeassistant/components/opencv/image_processing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/opencv/image_processing.py b/homeassistant/components/opencv/image_processing.py index 6b5e4e85513..028d6eacf24 100644 --- a/homeassistant/components/opencv/image_processing.py +++ b/homeassistant/components/opencv/image_processing.py @@ -62,7 +62,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( CONF_NEIGHBORS, DEFAULT_NEIGHBORS ): cv.positive_int, vol.Optional(CONF_MIN_SIZE, DEFAULT_MIN_SIZE): vol.Schema( - (int, int) + vol.All(vol.ExactSequence([int, int]), vol.Coerce(tuple)) ), } ), From e4aca9d73c4bde5df02b216d2682c978b9f7f69f Mon Sep 17 00:00:00 2001 From: Fredrik Erlandsson Date: Mon, 1 Jun 2020 14:52:51 +0200 Subject: [PATCH 282/406] Version bump pydaikin to 2.1.1 (#36341) --- homeassistant/components/daikin/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/daikin/manifest.json b/homeassistant/components/daikin/manifest.json index d642b2dc820..f555174494b 100644 --- a/homeassistant/components/daikin/manifest.json +++ b/homeassistant/components/daikin/manifest.json @@ -3,7 +3,7 @@ "name": "Daikin AC", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/daikin", - "requirements": ["pydaikin==2.1.0"], + "requirements": ["pydaikin==2.1.1"], "codeowners": ["@fredrike"], "zeroconf": ["_dkapi._tcp.local."], "quality_scale": "platinum" diff --git a/requirements_all.txt b/requirements_all.txt index 4d9aaae1196..a8aff04a190 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1266,7 +1266,7 @@ pycsspeechtts==1.0.3 # pycups==1.9.73 # homeassistant.components.daikin -pydaikin==2.1.0 +pydaikin==2.1.1 # homeassistant.components.danfoss_air pydanfossair==0.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index aa7a3997dc9..79516cf84bc 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -542,7 +542,7 @@ pychromecast==5.3.0 pycoolmasternet==0.0.4 # homeassistant.components.daikin -pydaikin==2.1.0 +pydaikin==2.1.1 # homeassistant.components.deconz pydeconz==70 From dcbe2136cfe0fdf3bc4096107ca560bc0cb0d7d5 Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Mon, 1 Jun 2020 09:24:56 -0400 Subject: [PATCH 283/406] Fix ZHA electrical measurement sensor (#36327) --- .../components/zha/core/channels/homeautomation.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/zha/core/channels/homeautomation.py b/homeassistant/components/zha/core/channels/homeautomation.py index b295a567b3d..2601cf47573 100644 --- a/homeassistant/components/zha/core/channels/homeautomation.py +++ b/homeassistant/components/zha/core/channels/homeautomation.py @@ -75,7 +75,6 @@ class ElectricalMeasurementChannel(ZigbeeChannel): async def async_initialize(self, from_cache): """Initialize channel.""" - await self.get_attribute_value("active_power", from_cache=from_cache) await self.fetch_config(from_cache) await super().async_initialize(from_cache) @@ -90,9 +89,11 @@ class ElectricalMeasurementChannel(ZigbeeChannel): ], from_cache=from_cache, ) - self._divisor = results.get("ac_power_divisor", results.get("power_divisor", 1)) + self._divisor = results.get( + "ac_power_divisor", results.get("power_divisor", self._divisor) + ) self._multiplier = results.get( - "ac_power_multiplier", results.get("power_multiplier", 1) + "ac_power_multiplier", results.get("power_multiplier", self._multiplier) ) @property From 141c4c2f33282f173b982a6d3b6f24f44b5ac82f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 1 Jun 2020 10:11:09 -0500 Subject: [PATCH 284/406] Fix various flapping tests that are missing block_till_done (#36346) * Add missing async_block_till_done to various tests * Add missing async_block_till_done to various tests --- tests/components/generic/test_camera.py | 5 +++++ tests/components/uvc/test_camera.py | 13 +++++++++++++ 2 files changed, 18 insertions(+) diff --git a/tests/components/generic/test_camera.py b/tests/components/generic/test_camera.py index e519e0f9416..a983efa115c 100644 --- a/tests/components/generic/test_camera.py +++ b/tests/components/generic/test_camera.py @@ -23,6 +23,7 @@ async def test_fetching_url(aioclient_mock, hass, hass_client): } }, ) + await hass.async_block_till_done() client = await hass_client() @@ -55,6 +56,7 @@ async def test_fetching_without_verify_ssl(aioclient_mock, hass, hass_client): } }, ) + await hass.async_block_till_done() client = await hass_client() @@ -81,6 +83,7 @@ async def test_fetching_url_with_verify_ssl(aioclient_mock, hass, hass_client): } }, ) + await hass.async_block_till_done() client = await hass_client() @@ -108,6 +111,7 @@ async def test_limit_refetch(aioclient_mock, hass, hass_client): } }, ) + await hass.async_block_till_done() client = await hass_client() @@ -171,6 +175,7 @@ async def test_camera_content_type(aioclient_mock, hass, hass_client): await async_setup_component( hass, "camera", {"camera": [cam_config_svg, cam_config_normal]} ) + await hass.async_block_till_done() client = await hass_client() diff --git a/tests/components/uvc/test_camera.py b/tests/components/uvc/test_camera.py index 1556a165447..22df898f006 100644 --- a/tests/components/uvc/test_camera.py +++ b/tests/components/uvc/test_camera.py @@ -54,6 +54,7 @@ class TestUVCSetup(unittest.TestCase): mock_remote.return_value.server_version = (3, 2, 0) assert setup_component(self.hass, "camera", {"camera": config}) + self.hass.block_till_done() assert mock_remote.call_count == 1 assert mock_remote.call_args == mock.call("foo", 123, "secret", ssl=False) @@ -78,6 +79,7 @@ class TestUVCSetup(unittest.TestCase): mock_remote.return_value.server_version = (3, 2, 0) assert setup_component(self.hass, "camera", {"camera": config}) + self.hass.block_till_done() assert mock_remote.call_count == 1 assert mock_remote.call_args == mock.call("foo", 7080, "secret", ssl=False) @@ -102,6 +104,7 @@ class TestUVCSetup(unittest.TestCase): mock_remote.return_value.server_version = (3, 1, 3) assert setup_component(self.hass, "camera", {"camera": config}) + self.hass.block_till_done() assert mock_remote.call_count == 1 assert mock_remote.call_args == mock.call("foo", 7080, "secret", ssl=False) @@ -116,14 +119,20 @@ class TestUVCSetup(unittest.TestCase): def test_setup_incomplete_config(self, mock_uvc): """Test the setup with incomplete configuration.""" assert setup_component(self.hass, "camera", {"platform": "uvc", "nvr": "foo"}) + self.hass.block_till_done() + assert not mock_uvc.called assert setup_component( self.hass, "camera", {"platform": "uvc", "key": "secret"} ) + self.hass.block_till_done() + assert not mock_uvc.called assert setup_component( self.hass, "camera", {"platform": "uvc", "port": "invalid"} ) + self.hass.block_till_done() + assert not mock_uvc.called @mock.patch.object(uvc, "UnifiVideoCamera") @@ -133,6 +142,8 @@ class TestUVCSetup(unittest.TestCase): config = {"platform": "uvc", "nvr": "foo", "key": "secret"} mock_remote.return_value.index.side_effect = error assert setup_component(self.hass, "camera", {"camera": config}) + self.hass.block_till_done() + assert not mock_uvc.called def test_setup_nvr_error_during_indexing_notauthorized(self): @@ -157,6 +168,8 @@ class TestUVCSetup(unittest.TestCase): mock_remote.return_value = None mock_remote.side_effect = error assert setup_component(self.hass, "camera", {"camera": config}) + self.hass.block_till_done() + assert not mock_remote.index.called assert not mock_uvc.called From 1c663dc179f44b69e8459c3f70c288808e6aed1f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 1 Jun 2020 10:11:48 -0500 Subject: [PATCH 285/406] Mark homekit accessories unavailable if the underlying entity is unavailable (#35685) * Mark homekit accessories unavailable if the underlying entity is unavailable. * bump pyhap to 2.9.0 * bump hap * Update homeassistant/components/homekit/accessories.py Co-authored-by: Franck Nijhof Co-authored-by: Franck Nijhof --- homeassistant/components/homekit/accessories.py | 7 +++++++ homeassistant/components/homekit/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/homekit/test_accessories.py | 5 ++++- 5 files changed, 14 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/homekit/accessories.py b/homeassistant/components/homekit/accessories.py index 3cd3c46613b..cb1c76656bb 100644 --- a/homeassistant/components/homekit/accessories.py +++ b/homeassistant/components/homekit/accessories.py @@ -25,6 +25,7 @@ from homeassistant.const import ( DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_TEMPERATURE, STATE_ON, + STATE_UNAVAILABLE, TEMP_CELSIUS, TEMP_FAHRENHEIT, UNIT_PERCENTAGE, @@ -322,6 +323,12 @@ class HomeAccessory(Accessory): CHAR_STATUS_LOW_BATTERY, value=0 ) + @property + def available(self): + """Return if accessory is available.""" + state = self.hass.states.get(self.entity_id) + return state is not None and state.state != STATE_UNAVAILABLE + async def run(self): """Handle accessory driver started event. diff --git a/homeassistant/components/homekit/manifest.json b/homeassistant/components/homekit/manifest.json index 8f7382b5d85..985fcc1e799 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.4","fnvhash==0.1.0","PyQRCode==1.2.1","base36==0.1.1","PyTurboJPEG==1.4.0"], + "requirements": ["HAP-python==2.9.1","fnvhash==0.1.0","PyQRCode==1.2.1","base36==0.1.1","PyTurboJPEG==1.4.0"], "dependencies": ["http", "camera", "ffmpeg"], "after_dependencies": ["logbook", "zeroconf"], "codeowners": ["@bdraco"], diff --git a/requirements_all.txt b/requirements_all.txt index a8aff04a190..90a2a87df1f 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.4 +HAP-python==2.9.1 # homeassistant.components.mastodon Mastodon.py==1.5.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 79516cf84bc..c30e6745cbc 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.4 +HAP-python==2.9.1 # homeassistant.components.plugwise Plugwise_Smile==0.2.10 diff --git a/tests/components/homekit/test_accessories.py b/tests/components/homekit/test_accessories.py index 092c68a5480..abc6d9b5528 100644 --- a/tests/components/homekit/test_accessories.py +++ b/tests/components/homekit/test_accessories.py @@ -42,6 +42,7 @@ from homeassistant.const import ( EVENT_TIME_CHANGED, STATE_OFF, STATE_ON, + STATE_UNAVAILABLE, __version__, ) import homeassistant.util.dt as dt_util @@ -88,7 +89,7 @@ async def test_home_accessory(hass, hk_driver): entity_id2 = "light.accessory" hass.states.async_set(entity_id, None) - hass.states.async_set(entity_id2, None) + hass.states.async_set(entity_id2, STATE_UNAVAILABLE) await hass.async_block_till_done() @@ -98,6 +99,7 @@ async def test_home_accessory(hass, hk_driver): assert acc.hass == hass assert acc.display_name == "Home Accessory" assert acc.aid == 2 + assert acc.available is True assert acc.category == 1 # Category.OTHER assert len(acc.services) == 1 serv = acc.services[0] # SERV_ACCESSORY_INFO @@ -127,6 +129,7 @@ async def test_home_accessory(hass, hk_driver): ATTR_INTERGRATION: "luxe", }, ) + assert acc3.available is False serv = acc3.services[0] # SERV_ACCESSORY_INFO assert serv.get_characteristic(CHAR_NAME).value == "Home Accessory" assert serv.get_characteristic(CHAR_MANUFACTURER).value == "Lux Brands" From e5d81aeb2a82723444985b7954db6f77f5754dc5 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 1 Jun 2020 17:43:20 +0200 Subject: [PATCH 286/406] Remove gearbest integration (ADR-0004) (#36347) --- .coveragerc | 1 - CODEOWNERS | 1 - homeassistant/components/gearbest/__init__.py | 1 - .../components/gearbest/manifest.json | 7 - homeassistant/components/gearbest/sensor.py | 123 ------------------ requirements_all.txt | 3 - 6 files changed, 136 deletions(-) delete mode 100644 homeassistant/components/gearbest/__init__.py delete mode 100644 homeassistant/components/gearbest/manifest.json delete mode 100644 homeassistant/components/gearbest/sensor.py diff --git a/.coveragerc b/.coveragerc index 23b00aed6de..e49103c3b3b 100644 --- a/.coveragerc +++ b/.coveragerc @@ -275,7 +275,6 @@ omit = homeassistant/components/garmin_connect/sensor.py homeassistant/components/gc100/* homeassistant/components/geniushub/* - homeassistant/components/gearbest/sensor.py homeassistant/components/geizhals/sensor.py homeassistant/components/gios/__init__.py homeassistant/components/gios/air_quality.py diff --git a/CODEOWNERS b/CODEOWNERS index c51b087c031..7404d3a62aa 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -141,7 +141,6 @@ homeassistant/components/fronius/* @nielstron homeassistant/components/frontend/* @home-assistant/frontend homeassistant/components/garmin_connect/* @cyberjunky homeassistant/components/gdacs/* @exxamalte -homeassistant/components/gearbest/* @HerrHofrat homeassistant/components/geniushub/* @zxdavb homeassistant/components/geo_rss_events/* @exxamalte homeassistant/components/geonetnz_quakes/* @exxamalte diff --git a/homeassistant/components/gearbest/__init__.py b/homeassistant/components/gearbest/__init__.py deleted file mode 100644 index c97d9469296..00000000000 --- a/homeassistant/components/gearbest/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""The gearbest component.""" diff --git a/homeassistant/components/gearbest/manifest.json b/homeassistant/components/gearbest/manifest.json deleted file mode 100644 index 4729fd6b6f3..00000000000 --- a/homeassistant/components/gearbest/manifest.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "domain": "gearbest", - "name": "Gearbest", - "documentation": "https://www.home-assistant.io/integrations/gearbest", - "requirements": ["gearbest_parser==1.0.7"], - "codeowners": ["@HerrHofrat"] -} diff --git a/homeassistant/components/gearbest/sensor.py b/homeassistant/components/gearbest/sensor.py deleted file mode 100644 index b9b2a35b89d..00000000000 --- a/homeassistant/components/gearbest/sensor.py +++ /dev/null @@ -1,123 +0,0 @@ -"""Parse prices of an item from gearbest.""" -from datetime import timedelta -import logging - -from gearbest_parser import CurrencyConverter, GearbestParser -import voluptuous as vol - -from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import CONF_CURRENCY, CONF_ID, CONF_NAME, CONF_URL -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity -from homeassistant.helpers.event import track_time_interval -from homeassistant.util import Throttle - -_LOGGER = logging.getLogger(__name__) - -CONF_ITEMS = "items" - -ICON = "mdi:coin" -MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=2 * 60 * 60) # 2h -MIN_TIME_BETWEEN_CURRENCY_UPDATES = timedelta(seconds=12 * 60 * 60) # 12h - - -_ITEM_SCHEMA = vol.All( - vol.Schema( - { - vol.Exclusive(CONF_URL, "XOR"): cv.string, - vol.Exclusive(CONF_ID, "XOR"): cv.string, - vol.Optional(CONF_NAME): cv.string, - vol.Optional(CONF_CURRENCY): cv.string, - } - ), - cv.has_at_least_one_key(CONF_URL, CONF_ID), -) - -_ITEMS_SCHEMA = vol.Schema([_ITEM_SCHEMA]) - -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( - {vol.Required(CONF_ITEMS): _ITEMS_SCHEMA, vol.Required(CONF_CURRENCY): cv.string} -) - - -def setup_platform(hass, config, add_entities, discovery_info=None): - """Set up the Gearbest sensor.""" - - currency = config.get(CONF_CURRENCY) - - sensors = [] - items = config.get(CONF_ITEMS) - - converter = CurrencyConverter() - converter.update() - - for item in items: - try: - sensors.append(GearbestSensor(converter, item, currency)) - except ValueError as exc: - _LOGGER.error(exc) - - def currency_update(event_time): - """Update currency list.""" - converter.update() - - track_time_interval(hass, currency_update, MIN_TIME_BETWEEN_CURRENCY_UPDATES) - - add_entities(sensors, True) - - -class GearbestSensor(Entity): - """Implementation of the sensor.""" - - def __init__(self, converter, item, currency): - """Initialize the sensor.""" - - self._name = item.get(CONF_NAME) - self._parser = GearbestParser() - self._parser.set_currency_converter(converter) - self._item = self._parser.load( - item.get(CONF_ID), item.get(CONF_URL), item.get(CONF_CURRENCY, currency) - ) - if self._item is None: - raise ValueError("id and url could not be resolved") - - @property - def name(self): - """Return the name of the item.""" - return self._name if self._name is not None else self._item.name - - @property - def icon(self): - """Return the icon for the frontend.""" - return ICON - - @property - def state(self): - """Return the price of the selected product.""" - return self._item.price - - @property - def unit_of_measurement(self): - """Return the currency.""" - return self._item.currency - - @property - def entity_picture(self): - """Return the image.""" - return self._item.image - - @property - def device_state_attributes(self): - """Return the state attributes.""" - attrs = { - "name": self._item.name, - "description": self._item.description, - "currency": self._item.currency, - "url": self._item.url, - } - return attrs - - @Throttle(MIN_TIME_BETWEEN_UPDATES) - def update(self): - """Get the latest price from gearbest and updates the state.""" - self._item.update() diff --git a/requirements_all.txt b/requirements_all.txt index 90a2a87df1f..b60a60be24f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -612,9 +612,6 @@ gTTS-token==1.1.3 # homeassistant.components.garmin_connect garminconnect==0.1.13 -# homeassistant.components.gearbest -gearbest_parser==1.0.7 - # homeassistant.components.geizhals geizhals==0.0.9 From a333417ddf15f60a9f1c28e61e67e3af30d659cc Mon Sep 17 00:00:00 2001 From: Lindsay Ward Date: Tue, 2 Jun 2020 02:36:43 +1000 Subject: [PATCH 287/406] Add only unique_id property to yeelightsunflower, not other changes (#36311) --- homeassistant/components/yeelightsunflower/light.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/yeelightsunflower/light.py b/homeassistant/components/yeelightsunflower/light.py index 2e17b92c90a..abe3d5a404b 100644 --- a/homeassistant/components/yeelightsunflower/light.py +++ b/homeassistant/components/yeelightsunflower/light.py @@ -25,7 +25,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({vol.Required(CONF_HOST): cv.string}) def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Yeelight Sunflower Light platform.""" - host = config.get(CONF_HOST) hub = yeelightsunflower.Hub(host) @@ -46,12 +45,18 @@ class SunflowerBulb(LightEntity): self._brightness = light.brightness self._is_on = light.is_on self._hs_color = light.rgb_color + self._unique_id = light.zid @property def name(self): """Return the display name of this light.""" return f"sunflower_{self._light.zid}" + @property + def unique_id(self): + """Return the unique ID of this light.""" + return self._unique_id + @property def available(self): """Return True if entity is available.""" From 47706dac1a228199e02e52ac8e24a0b9219c2ea9 Mon Sep 17 00:00:00 2001 From: Brynley McDonald Date: Tue, 2 Jun 2020 04:40:47 +1200 Subject: [PATCH 288/406] Implement google_assistant ModesTrait for input_select (#36313) --- .../components/google_assistant/const.py | 3 + .../components/google_assistant/trait.py | 61 ++++++++++++---- .../components/google_assistant/test_trait.py | 71 ++++++++++++++++++- 3 files changed, 121 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/google_assistant/const.py b/homeassistant/components/google_assistant/const.py index 47122979173..c1314aeaa41 100644 --- a/homeassistant/components/google_assistant/const.py +++ b/homeassistant/components/google_assistant/const.py @@ -8,6 +8,7 @@ from homeassistant.components import ( fan, group, input_boolean, + input_select, light, lock, media_player, @@ -44,6 +45,7 @@ DEFAULT_EXPOSED_DOMAINS = [ "fan", "group", "input_boolean", + "input_select", "light", "media_player", "scene", @@ -113,6 +115,7 @@ DOMAIN_TO_GOOGLE_TYPES = { fan.DOMAIN: TYPE_FAN, group.DOMAIN: TYPE_SWITCH, input_boolean.DOMAIN: TYPE_SWITCH, + input_select.DOMAIN: TYPE_SENSOR, light.DOMAIN: TYPE_LIGHT, lock.DOMAIN: TYPE_LOCK, media_player.DOMAIN: TYPE_SETTOP, diff --git a/homeassistant/components/google_assistant/trait.py b/homeassistant/components/google_assistant/trait.py index 3da91b2b612..0a90e542e51 100644 --- a/homeassistant/components/google_assistant/trait.py +++ b/homeassistant/components/google_assistant/trait.py @@ -9,6 +9,7 @@ from homeassistant.components import ( fan, group, input_boolean, + input_select, light, lock, media_player, @@ -1131,11 +1132,15 @@ class ModesTrait(_Trait): SYNONYMS = { "input source": ["input source", "input", "source"], "sound mode": ["sound mode", "effects"], + "option": ["option", "setting", "mode", "value"], } @staticmethod def supported(domain, features, device_class): """Test if state is supported.""" + if domain == input_select.DOMAIN: + return True + if domain != media_player.DOMAIN: return False @@ -1174,15 +1179,20 @@ class ModesTrait(_Trait): attrs = self.state.attributes modes = [] - if media_player.ATTR_INPUT_SOURCE_LIST in attrs: - modes.append( - _generate("input source", attrs[media_player.ATTR_INPUT_SOURCE_LIST]) - ) + if self.state.domain == media_player.DOMAIN: + if media_player.ATTR_INPUT_SOURCE_LIST in attrs: + modes.append( + _generate( + "input source", attrs[media_player.ATTR_INPUT_SOURCE_LIST] + ) + ) - if media_player.ATTR_SOUND_MODE_LIST in attrs: - modes.append( - _generate("sound mode", attrs[media_player.ATTR_SOUND_MODE_LIST]) - ) + if media_player.ATTR_SOUND_MODE_LIST in attrs: + modes.append( + _generate("sound mode", attrs[media_player.ATTR_SOUND_MODE_LIST]) + ) + elif self.state.domain == input_select.DOMAIN: + modes.append(_generate("option", attrs[input_select.ATTR_OPTIONS])) payload = {"availableModes": modes} @@ -1194,11 +1204,16 @@ class ModesTrait(_Trait): response = {} mode_settings = {} - if media_player.ATTR_INPUT_SOURCE_LIST in attrs: - mode_settings["input source"] = attrs.get(media_player.ATTR_INPUT_SOURCE) + if self.state.domain == media_player.DOMAIN: + if media_player.ATTR_INPUT_SOURCE_LIST in attrs: + mode_settings["input source"] = attrs.get( + media_player.ATTR_INPUT_SOURCE + ) - if media_player.ATTR_SOUND_MODE_LIST in attrs: - mode_settings["sound mode"] = attrs.get(media_player.ATTR_SOUND_MODE) + if media_player.ATTR_SOUND_MODE_LIST in attrs: + mode_settings["sound mode"] = attrs.get(media_player.ATTR_SOUND_MODE) + elif self.state.domain == input_select.DOMAIN: + mode_settings["option"] = self.state.state if mode_settings: response["on"] = self.state.state != STATE_OFF @@ -1210,6 +1225,28 @@ class ModesTrait(_Trait): async def execute(self, command, data, params, challenge): """Execute an SetModes command.""" settings = params.get("updateModeSettings") + + if self.state.domain == input_select.DOMAIN: + option = params["updateModeSettings"]["option"] + await self.hass.services.async_call( + input_select.DOMAIN, + input_select.SERVICE_SELECT_OPTION, + { + ATTR_ENTITY_ID: self.state.entity_id, + input_select.ATTR_OPTION: option, + }, + blocking=True, + context=data.context, + ) + + return + if self.state.domain != media_player.DOMAIN: + _LOGGER.info( + "Received an Options command for unrecognised domain %s", + self.state.domain, + ) + return + requested_source = settings.get("input source") sound_mode = settings.get("sound mode") diff --git a/tests/components/google_assistant/test_trait.py b/tests/components/google_assistant/test_trait.py index 801f4c1b5ba..adce4ef877b 100644 --- a/tests/components/google_assistant/test_trait.py +++ b/tests/components/google_assistant/test_trait.py @@ -11,6 +11,7 @@ from homeassistant.components import ( fan, group, input_boolean, + input_select, light, lock, media_player, @@ -1267,8 +1268,8 @@ async def test_fan_speed(hass): assert calls[0].data == {"entity_id": "fan.living_room_fan", "speed": "medium"} -async def test_modes(hass): - """Test Mode trait.""" +async def test_modes_media_player(hass): + """Test Media Player Mode trait.""" assert helpers.get_google_type(media_player.DOMAIN, None) is not None assert trait.ModesTrait.supported( media_player.DOMAIN, media_player.SUPPORT_SELECT_SOURCE, None @@ -1351,6 +1352,72 @@ async def test_modes(hass): assert calls[0].data == {"entity_id": "media_player.living_room", "source": "media"} +async def test_modes_input_select(hass): + """Test Input Select Mode trait.""" + assert helpers.get_google_type(input_select.DOMAIN, None) is not None + assert trait.ModesTrait.supported(input_select.DOMAIN, None, None) + + trt = trait.ModesTrait( + hass, + State( + "input_select.bla", + "abc", + attributes={input_select.ATTR_OPTIONS: ["abc", "123", "xyz"]}, + ), + BASIC_CONFIG, + ) + + attribs = trt.sync_attributes() + assert attribs == { + "availableModes": [ + { + "name": "option", + "name_values": [ + { + "name_synonym": ["option", "setting", "mode", "value"], + "lang": "en", + } + ], + "settings": [ + { + "setting_name": "abc", + "setting_values": [{"setting_synonym": ["abc"], "lang": "en"}], + }, + { + "setting_name": "123", + "setting_values": [{"setting_synonym": ["123"], "lang": "en"}], + }, + { + "setting_name": "xyz", + "setting_values": [{"setting_synonym": ["xyz"], "lang": "en"}], + }, + ], + "ordered": False, + } + ] + } + + assert trt.query_attributes() == { + "currentModeSettings": {"option": "abc"}, + "on": True, + "online": True, + } + + assert trt.can_execute( + trait.COMMAND_MODES, params={"updateModeSettings": {"option": "xyz"}}, + ) + + calls = async_mock_service( + hass, input_select.DOMAIN, input_select.SERVICE_SELECT_OPTION + ) + await trt.execute( + trait.COMMAND_MODES, BASIC_DATA, {"updateModeSettings": {"option": "xyz"}}, {}, + ) + + assert len(calls) == 1 + assert calls[0].data == {"entity_id": "input_select.bla", "option": "xyz"} + + async def test_sound_modes(hass): """Test Mode trait.""" assert helpers.get_google_type(media_player.DOMAIN, None) is not None From cf6043fc2d1c8176bf1ce0e9f4a37c06cacfe4e8 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Mon, 1 Jun 2020 18:45:38 +0200 Subject: [PATCH 289/406] Support Axis stream profile and configuring it through options flow (#36322) * Support stream profile and configuring it through options flow * Options flow test * Allow configuration of not using a stream profile * Shorten default stream profile string --- homeassistant/components/axis/camera.py | 67 ++++++++++--------- homeassistant/components/axis/config_flow.py | 54 ++++++++++++++- homeassistant/components/axis/const.py | 2 + homeassistant/components/axis/device.py | 9 +++ homeassistant/components/axis/strings.json | 12 +++- .../components/axis/translations/en.json | 10 +++ tests/components/axis/test_camera.py | 50 +++++++++++++- tests/components/axis/test_config_flow.py | 46 +++++++++++-- tests/components/axis/test_device.py | 15 ++++- 9 files changed, 220 insertions(+), 45 deletions(-) diff --git a/homeassistant/components/axis/camera.py b/homeassistant/components/axis/camera.py index c4cc5df68a0..8e7e4592cb6 100644 --- a/homeassistant/components/axis/camera.py +++ b/homeassistant/components/axis/camera.py @@ -9,7 +9,6 @@ from homeassistant.components.mjpeg.camera import ( ) from homeassistant.const import ( CONF_AUTHENTICATION, - CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_PORT, @@ -19,11 +18,7 @@ from homeassistant.const import ( from homeassistant.helpers.dispatcher import async_dispatcher_connect from .axis_base import AxisEntityBase -from .const import DOMAIN as AXIS_DOMAIN - -AXIS_IMAGE = "http://{host}:{port}/axis-cgi/jpg/image.cgi" -AXIS_VIDEO = "http://{host}:{port}/axis-cgi/mjpg/video.cgi" -AXIS_STREAM = "rtsp://{user}:{password}@{host}/axis-media/media.amp?videocodec=h264" +from .const import DEFAULT_STREAM_PROFILE, DOMAIN as AXIS_DOMAIN async def async_setup_entry(hass, config_entry, async_add_entities): @@ -35,27 +30,24 @@ async def async_setup_entry(hass, config_entry, async_add_entities): if not device.option_camera: return - config = { - CONF_NAME: config_entry.data[CONF_NAME], - CONF_USERNAME: config_entry.data[CONF_USERNAME], - CONF_PASSWORD: config_entry.data[CONF_PASSWORD], - CONF_MJPEG_URL: AXIS_VIDEO.format( - host=config_entry.data[CONF_HOST], port=config_entry.data[CONF_PORT], - ), - CONF_STILL_IMAGE_URL: AXIS_IMAGE.format( - host=config_entry.data[CONF_HOST], port=config_entry.data[CONF_PORT], - ), - CONF_AUTHENTICATION: HTTP_DIGEST_AUTHENTICATION, - } - async_add_entities([AxisCamera(config, device)]) + async_add_entities([AxisCamera(device)]) class AxisCamera(AxisEntityBase, MjpegCamera): """Representation of a Axis camera.""" - def __init__(self, config, device): + def __init__(self, device): """Initialize Axis Communications camera component.""" AxisEntityBase.__init__(self, device) + + config = { + CONF_NAME: device.config_entry.data[CONF_NAME], + CONF_USERNAME: device.config_entry.data[CONF_USERNAME], + CONF_PASSWORD: device.config_entry.data[CONF_PASSWORD], + CONF_MJPEG_URL: self.mjpeg_source, + CONF_STILL_IMAGE_URL: self.image_source, + CONF_AUTHENTICATION: HTTP_DIGEST_AUTHENTICATION, + } MjpegCamera.__init__(self, config) async def async_added_to_hass(self): @@ -73,21 +65,34 @@ class AxisCamera(AxisEntityBase, MjpegCamera): """Return supported features.""" return SUPPORT_STREAM - async def stream_source(self): - """Return the stream source.""" - return AXIS_STREAM.format( - user=self.device.config_entry.data[CONF_USERNAME], - password=self.device.config_entry.data[CONF_PASSWORD], - host=self.device.host, - ) - def _new_address(self): """Set new device address for video stream.""" - port = self.device.config_entry.data[CONF_PORT] - self._mjpeg_url = AXIS_VIDEO.format(host=self.device.host, port=port) - self._still_image_url = AXIS_IMAGE.format(host=self.device.host, port=port) + self._mjpeg_url = self.mjpeg_source + self._still_image_url = self.image_source @property def unique_id(self): """Return a unique identifier for this device.""" return f"{self.device.serial}-camera" + + @property + def image_source(self): + """Return still image URL for device.""" + return f"http://{self.device.host}:{self.device.config_entry.data[CONF_PORT]}/axis-cgi/jpg/image.cgi" + + @property + def mjpeg_source(self): + """Return mjpeg URL for device.""" + options = "" + if self.device.option_stream_profile != DEFAULT_STREAM_PROFILE: + options = f"?&streamprofile={self.device.option_stream_profile}" + + return f"http://{self.device.host}:{self.device.config_entry.data[CONF_PORT]}/axis-cgi/mjpg/video.cgi{options}" + + async def stream_source(self): + """Return the stream source.""" + options = "" + if self.device.option_stream_profile != DEFAULT_STREAM_PROFILE: + options = f"&streamprofile={self.device.option_stream_profile}" + + return f"rtsp://{self.device.config_entry.data[CONF_USERNAME]}:{self.device.config_entry.data[CONF_PASSWORD]}@{self.device.host}/axis-media/media.amp?videocodec=h264{options}" diff --git a/homeassistant/components/axis/config_flow.py b/homeassistant/components/axis/config_flow.py index 508bee6aff5..5b300fe323b 100644 --- a/homeassistant/components/axis/config_flow.py +++ b/homeassistant/components/axis/config_flow.py @@ -13,9 +13,15 @@ from homeassistant.const import ( CONF_PORT, CONF_USERNAME, ) +from homeassistant.core import callback from homeassistant.util.network import is_link_local -from .const import CONF_MODEL, DOMAIN +from .const import ( + CONF_MODEL, + CONF_STREAM_PROFILE, + DEFAULT_STREAM_PROFILE, + DOMAIN as AXIS_DOMAIN, +) from .device import get_device from .errors import AuthenticationRequired, CannotConnect @@ -32,12 +38,18 @@ AXIS_INCLUDE = EVENT_TYPES + PLATFORMS DEFAULT_PORT = 80 -class AxisFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): +class AxisFlowHandler(config_entries.ConfigFlow, domain=AXIS_DOMAIN): """Handle a Axis config flow.""" VERSION = 2 CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH + @staticmethod + @callback + def async_get_options_flow(config_entry): + """Get the options flow for this handler.""" + return AxisOptionsFlowHandler(config_entry) + def __init__(self): """Initialize the Axis config flow.""" self.device_config = {} @@ -109,7 +121,7 @@ class AxisFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): model = self.device_config[CONF_MODEL] same_model = [ entry.data[CONF_NAME] - for entry in self.hass.config_entries.async_entries(DOMAIN) + for entry in self.hass.config_entries.async_entries(AXIS_DOMAIN) if entry.data[CONF_MODEL] == model ] @@ -157,3 +169,39 @@ class AxisFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): } return await self.async_step_user() + + +class AxisOptionsFlowHandler(config_entries.OptionsFlow): + """Handle Axis device options.""" + + def __init__(self, config_entry): + """Initialize Axis device options flow.""" + self.config_entry = config_entry + self.options = dict(config_entry.options) + self.device = None + + async def async_step_init(self, user_input=None): + """Manage the Axis device options.""" + self.device = self.hass.data[AXIS_DOMAIN][self.config_entry.unique_id] + return await self.async_step_configure_stream() + + async def async_step_configure_stream(self, user_input=None): + """Manage the Axis device options.""" + if user_input is not None: + self.options.update(user_input) + return self.async_create_entry(title="", data=self.options) + + profiles = [DEFAULT_STREAM_PROFILE] + for profile in self.device.api.vapix.streaming_profiles: + profiles.append(profile.name) + + return self.async_show_form( + step_id="configure_stream", + data_schema=vol.Schema( + { + vol.Optional( + CONF_STREAM_PROFILE, default=self.device.option_stream_profile + ): vol.In(profiles) + } + ), + ) diff --git a/homeassistant/components/axis/const.py b/homeassistant/components/axis/const.py index 05a1211f89d..203bbdf94c7 100644 --- a/homeassistant/components/axis/const.py +++ b/homeassistant/components/axis/const.py @@ -14,8 +14,10 @@ ATTR_MANUFACTURER = "Axis Communications AB" CONF_CAMERA = "camera" CONF_EVENTS = "events" CONF_MODEL = "model" +CONF_STREAM_PROFILE = "stream_profile" DEFAULT_EVENTS = True +DEFAULT_STREAM_PROFILE = "No stream profile" DEFAULT_TRIGGER_TIME = 0 PLATFORMS = [BINARY_SENSOR_DOMAIN, CAMERA_DOMAIN, SWITCH_DOMAIN] diff --git a/homeassistant/components/axis/device.py b/homeassistant/components/axis/device.py index 54e534068a8..69cab856516 100644 --- a/homeassistant/components/axis/device.py +++ b/homeassistant/components/axis/device.py @@ -31,7 +31,9 @@ from .const import ( CONF_CAMERA, CONF_EVENTS, CONF_MODEL, + CONF_STREAM_PROFILE, DEFAULT_EVENTS, + DEFAULT_STREAM_PROFILE, DEFAULT_TRIGGER_TIME, DOMAIN as AXIS_DOMAIN, LOGGER, @@ -86,6 +88,13 @@ class AxisNetworkDevice: """Config entry option defining if platforms based on events should be created.""" return self.config_entry.options.get(CONF_EVENTS, DEFAULT_EVENTS) + @property + def option_stream_profile(self): + """Config entry option defining what stream profile camera platform should use.""" + return self.config_entry.options.get( + CONF_STREAM_PROFILE, DEFAULT_STREAM_PROFILE + ) + @property def option_trigger_time(self): """Config entry option defining minimum number of seconds to keep trigger high.""" diff --git a/homeassistant/components/axis/strings.json b/homeassistant/components/axis/strings.json index 88b117a9802..672bfe141b9 100644 --- a/homeassistant/components/axis/strings.json +++ b/homeassistant/components/axis/strings.json @@ -24,5 +24,15 @@ "link_local_address": "Link local addresses are not supported", "not_axis_device": "Discovered device not an Axis device" } + }, + "options": { + "step": { + "configure_stream": { + "data": { + "stream_profile": "Select stream profile to use" + }, + "title": "Axis device video stream options" + } + } } -} \ No newline at end of file +} diff --git a/homeassistant/components/axis/translations/en.json b/homeassistant/components/axis/translations/en.json index 2bc9650c010..29461ee0612 100644 --- a/homeassistant/components/axis/translations/en.json +++ b/homeassistant/components/axis/translations/en.json @@ -24,5 +24,15 @@ "title": "Set up Axis device" } } + }, + "options": { + "step": { + "configure_stream": { + "data": { + "stream_profile": "Select stream profile to use" + }, + "title": "Axis device video stream options" + } + } } } \ No newline at end of file diff --git a/tests/components/axis/test_camera.py b/tests/components/axis/test_camera.py index 6281c87740c..6db8de0a0a8 100644 --- a/tests/components/axis/test_camera.py +++ b/tests/components/axis/test_camera.py @@ -1,10 +1,17 @@ """Axis camera platform tests.""" -from homeassistant.components.axis.const import DOMAIN as AXIS_DOMAIN +from homeassistant.components import camera +from homeassistant.components.axis.const import ( + CONF_CAMERA, + CONF_STREAM_PROFILE, + DOMAIN as AXIS_DOMAIN, +) from homeassistant.components.camera import DOMAIN as CAMERA_DOMAIN from homeassistant.setup import async_setup_component -from .test_device import NAME, setup_axis_integration +from .test_device import ENTRY_OPTIONS, NAME, setup_axis_integration + +from tests.async_mock import patch async def test_platform_manually_configured(hass): @@ -28,3 +35,42 @@ async def test_camera(hass): cam = hass.states.get(f"camera.{NAME}") assert cam.state == "idle" assert cam.name == NAME + + camera_entity = camera._get_camera_from_entity_id(hass, f"camera.{NAME}") + assert camera_entity.image_source == "http://1.2.3.4:80/axis-cgi/jpg/image.cgi" + assert camera_entity.mjpeg_source == "http://1.2.3.4:80/axis-cgi/mjpg/video.cgi" + assert ( + await camera_entity.stream_source() + == "rtsp://root:pass@1.2.3.4/axis-media/media.amp?videocodec=h264" + ) + + +async def test_camera_with_stream_profile(hass): + """Test that Axis camera entity is using the correct path with stream profike.""" + with patch.dict(ENTRY_OPTIONS, {CONF_STREAM_PROFILE: "profile_1"}): + await setup_axis_integration(hass) + + assert len(hass.states.async_entity_ids(CAMERA_DOMAIN)) == 1 + + cam = hass.states.get(f"camera.{NAME}") + assert cam.state == "idle" + assert cam.name == NAME + + camera_entity = camera._get_camera_from_entity_id(hass, f"camera.{NAME}") + assert camera_entity.image_source == "http://1.2.3.4:80/axis-cgi/jpg/image.cgi" + assert ( + camera_entity.mjpeg_source + == "http://1.2.3.4:80/axis-cgi/mjpg/video.cgi?&streamprofile=profile_1" + ) + assert ( + await camera_entity.stream_source() + == "rtsp://root:pass@1.2.3.4/axis-media/media.amp?videocodec=h264&streamprofile=profile_1" + ) + + +async def test_camera_disabled(hass): + """Test that Axis camera platform is loaded properly but does not create camera entity.""" + with patch.dict(ENTRY_OPTIONS, {CONF_CAMERA: False}): + await setup_axis_integration(hass) + + assert len(hass.states.async_entity_ids(CAMERA_DOMAIN)) == 0 diff --git a/tests/components/axis/test_config_flow.py b/tests/components/axis/test_config_flow.py index ab3516873fa..941961f623a 100644 --- a/tests/components/axis/test_config_flow.py +++ b/tests/components/axis/test_config_flow.py @@ -1,6 +1,14 @@ """Test Axis config flow.""" +from homeassistant import data_entry_flow from homeassistant.components.axis import config_flow -from homeassistant.components.axis.const import CONF_MODEL, DOMAIN as AXIS_DOMAIN +from homeassistant.components.axis.const import ( + CONF_CAMERA, + CONF_EVENTS, + CONF_MODEL, + CONF_STREAM_PROFILE, + DEFAULT_STREAM_PROFILE, + DOMAIN as AXIS_DOMAIN, +) from homeassistant.const import ( CONF_HOST, CONF_MAC, @@ -268,8 +276,8 @@ async def test_zeroconf_flow_updated_configuration(hass): assert device.config_entry.data == { CONF_HOST: "1.2.3.4", CONF_PORT: 80, - CONF_USERNAME: "username", - CONF_PASSWORD: "password", + CONF_USERNAME: "root", + CONF_PASSWORD: "pass", CONF_MAC: MAC, CONF_MODEL: MODEL, CONF_NAME: NAME, @@ -291,8 +299,8 @@ async def test_zeroconf_flow_updated_configuration(hass): assert device.config_entry.data == { CONF_HOST: "2.3.4.5", CONF_PORT: 8080, - CONF_USERNAME: "username", - CONF_PASSWORD: "password", + CONF_USERNAME: "root", + CONF_PASSWORD: "pass", CONF_MAC: MAC, CONF_MODEL: MODEL, CONF_NAME: NAME, @@ -321,3 +329,31 @@ async def test_zeroconf_flow_ignore_link_local_address(hass): assert result["type"] == "abort" assert result["reason"] == "link_local_address" + + +async def test_option_flow(hass): + """Test config flow options.""" + device = await setup_axis_integration(hass) + assert device.option_stream_profile == DEFAULT_STREAM_PROFILE + + result = await hass.config_entries.options.async_init(device.config_entry.entry_id) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "configure_stream" + assert set(result["data_schema"].schema[CONF_STREAM_PROFILE].container) == { + DEFAULT_STREAM_PROFILE, + "profile_1", + "profile_2", + } + + result = await hass.config_entries.options.async_configure( + result["flow_id"], user_input={CONF_STREAM_PROFILE: "profile_1"}, + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["data"] == { + CONF_CAMERA: True, + CONF_EVENTS: True, + CONF_STREAM_PROFILE: "profile_1", + } + assert device.option_stream_profile == "profile_1" diff --git a/tests/components/axis/test_device.py b/tests/components/axis/test_device.py index c96d13659d1..e4b0a960979 100644 --- a/tests/components/axis/test_device.py +++ b/tests/components/axis/test_device.py @@ -52,8 +52,8 @@ ENTRY_OPTIONS = {CONF_CAMERA: True, CONF_EVENTS: True} ENTRY_CONFIG = { CONF_HOST: "1.2.3.4", - CONF_USERNAME: "username", - CONF_PASSWORD: "password", + CONF_USERNAME: "root", + CONF_PASSWORD: "pass", CONF_PORT: 80, CONF_MAC: MAC, CONF_MODEL: MODEL, @@ -152,6 +152,15 @@ root.Properties.Image.Rotation=0,180 root.Properties.System.SerialNumber=00408C12345 """ +STREAM_PROFILES_RESPONSE = """root.StreamProfile.MaxGroups=26 +root.StreamProfile.S0.Description=profile_1_description +root.StreamProfile.S0.Name=profile_1 +root.StreamProfile.S0.Parameters=videocodec=h264 +root.StreamProfile.S1.Description=profile_2_description +root.StreamProfile.S1.Name=profile_2 +root.StreamProfile.S1.Parameters=videocodec=h265 +""" + def vapix_session_request(session, url, **kwargs): """Return data based on url.""" @@ -170,7 +179,7 @@ def vapix_session_request(session, url, **kwargs): if PROPERTIES_URL in url: return PROPERTIES_RESPONSE if STREAM_PROFILES_URL in url: - return "" + return STREAM_PROFILES_RESPONSE async def setup_axis_integration(hass, config=ENTRY_CONFIG, options=ENTRY_OPTIONS): From acbffb511da15129159d3f8133b3adbf22c3df5e Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 1 Jun 2020 11:44:45 -0700 Subject: [PATCH 290/406] Fix base_url extract stack (#36331) * Fix base_url extract stack * Fix tests --- homeassistant/components/http/__init__.py | 2 +- tests/components/http/test_init.py | 170 ++++++++---------- .../components/image_processing/test_init.py | 6 +- .../test_image_processing.py | 2 +- .../test_image_processing.py | 2 +- .../openalpr_local/test_image_processing.py | 2 +- 6 files changed, 86 insertions(+), 98 deletions(-) diff --git a/homeassistant/components/http/__init__.py b/homeassistant/components/http/__init__.py index 069fc42c884..42d388ffa85 100644 --- a/homeassistant/components/http/__init__.py +++ b/homeassistant/components/http/__init__.py @@ -133,7 +133,7 @@ class ApiConfig: def base_url(self) -> str: """Proxy property to find caller of this deprecated property.""" found_frame = None - for frame in reversed(extract_stack()): + for frame in reversed(extract_stack()[:-1]): for path in ("custom_components/", "homeassistant/components/"): try: index = frame.filename.index(path) diff --git a/tests/components/http/test_init.py b/tests/components/http/test_init.py index 18ec9ccf471..2c95d03a9ef 100644 --- a/tests/components/http/test_init.py +++ b/tests/components/http/test_init.py @@ -1,7 +1,6 @@ """The tests for the Home Assistant HTTP component.""" from ipaddress import ip_network import logging -import unittest import pytest @@ -12,6 +11,32 @@ from homeassistant.util.ssl import server_context_intermediate, server_context_m from tests.async_mock import Mock, patch +@pytest.fixture +def mock_stack(): + """Mock extract stack.""" + with patch( + "homeassistant.components.http.extract_stack", + return_value=[ + Mock( + filename="/home/paulus/core/homeassistant/core.py", + lineno="23", + line="do_something()", + ), + Mock( + filename="/home/paulus/core/homeassistant/components/hue/light.py", + lineno="23", + line="self.light.is_on", + ), + Mock( + filename="/home/paulus/core/homeassistant/components/http/__init__.py", + lineno="157", + line="base_url", + ), + ], + ): + yield + + class TestView(http.HomeAssistantView): """Test the HTTP views.""" @@ -36,110 +61,73 @@ async def test_registering_view_while_running( hass.http.register_view(TestView) -class TestApiConfig(unittest.TestCase): - """Test API configuration methods.""" - - def test_api_base_url_with_domain(hass): - """Test setting API URL with domain.""" - api_config = http.ApiConfig("127.0.0.1", "example.com") - assert api_config.base_url == "http://example.com:8123" - - def test_api_base_url_with_ip(hass): - """Test setting API URL with IP.""" - api_config = http.ApiConfig("127.0.0.1", "1.1.1.1") - assert api_config.base_url == "http://1.1.1.1:8123" - - def test_api_base_url_with_ip_and_port(hass): - """Test setting API URL with IP and port.""" - api_config = http.ApiConfig("127.0.0.1", "1.1.1.1", 8124) - assert api_config.base_url == "http://1.1.1.1:8124" - - def test_api_base_url_with_protocol(hass): - """Test setting API URL with protocol.""" - api_config = http.ApiConfig("127.0.0.1", "https://example.com") - assert api_config.base_url == "https://example.com:8123" - - def test_api_base_url_with_protocol_and_port(hass): - """Test setting API URL with protocol and port.""" - api_config = http.ApiConfig("127.0.0.1", "https://example.com", 433) - assert api_config.base_url == "https://example.com:433" - - def test_api_base_url_with_ssl_enable(hass): - """Test setting API URL with use_ssl enabled.""" - api_config = http.ApiConfig("127.0.0.1", "example.com", use_ssl=True) - assert api_config.base_url == "https://example.com:8123" - - def test_api_base_url_with_ssl_enable_and_port(hass): - """Test setting API URL with use_ssl enabled and port.""" - api_config = http.ApiConfig("127.0.0.1", "1.1.1.1", use_ssl=True, port=8888) - assert api_config.base_url == "https://1.1.1.1:8888" - - def test_api_base_url_with_protocol_and_ssl_enable(hass): - """Test setting API URL with specific protocol and use_ssl enabled.""" - api_config = http.ApiConfig("127.0.0.1", "http://example.com", use_ssl=True) - assert api_config.base_url == "http://example.com:8123" - - def test_api_base_url_removes_trailing_slash(hass): - """Test a trialing slash is removed when setting the API URL.""" - api_config = http.ApiConfig("127.0.0.1", "http://example.com/") - assert api_config.base_url == "http://example.com:8123" - - def test_api_local_ip(hass): - """Test a trialing slash is removed when setting the API URL.""" - api_config = http.ApiConfig("127.0.0.1", "http://example.com/") - assert api_config.local_ip == "127.0.0.1" +def test_api_base_url_with_domain(mock_stack): + """Test setting API URL with domain.""" + api_config = http.ApiConfig("127.0.0.1", "example.com") + assert api_config.base_url == "http://example.com:8123" -async def test_api_base_url_with_domain(hass): - """Test setting API URL.""" - result = await async_setup_component( - hass, "http", {"http": {"base_url": "example.com"}} - ) - assert result - assert hass.config.api.base_url == "http://example.com" +def test_api_base_url_with_ip(mock_stack): + """Test setting API URL with IP.""" + api_config = http.ApiConfig("127.0.0.1", "1.1.1.1") + assert api_config.base_url == "http://1.1.1.1:8123" -async def test_api_base_url_with_ip(hass): - """Test setting api url.""" - result = await async_setup_component( - hass, "http", {"http": {"server_host": "1.1.1.1"}} - ) - assert result - assert hass.config.api.base_url == "http://1.1.1.1:8123" +def test_api_base_url_with_ip_and_port(mock_stack): + """Test setting API URL with IP and port.""" + api_config = http.ApiConfig("127.0.0.1", "1.1.1.1", 8124) + assert api_config.base_url == "http://1.1.1.1:8124" -async def test_api_base_url_with_ip_port(hass): - """Test setting api url.""" - result = await async_setup_component( - hass, "http", {"http": {"base_url": "1.1.1.1:8124"}} - ) - assert result - assert hass.config.api.base_url == "http://1.1.1.1:8124" +def test_api_base_url_with_protocol(mock_stack): + """Test setting API URL with protocol.""" + api_config = http.ApiConfig("127.0.0.1", "https://example.com") + assert api_config.base_url == "https://example.com:8123" -async def test_api_no_base_url(hass): +def test_api_base_url_with_protocol_and_port(mock_stack): + """Test setting API URL with protocol and port.""" + api_config = http.ApiConfig("127.0.0.1", "https://example.com", 433) + assert api_config.base_url == "https://example.com:433" + + +def test_api_base_url_with_ssl_enable(mock_stack): + """Test setting API URL with use_ssl enabled.""" + api_config = http.ApiConfig("127.0.0.1", "example.com", use_ssl=True) + assert api_config.base_url == "https://example.com:8123" + + +def test_api_base_url_with_ssl_enable_and_port(mock_stack): + """Test setting API URL with use_ssl enabled and port.""" + api_config = http.ApiConfig("127.0.0.1", "1.1.1.1", use_ssl=True, port=8888) + assert api_config.base_url == "https://1.1.1.1:8888" + + +def test_api_base_url_with_protocol_and_ssl_enable(mock_stack): + """Test setting API URL with specific protocol and use_ssl enabled.""" + api_config = http.ApiConfig("127.0.0.1", "http://example.com", use_ssl=True) + assert api_config.base_url == "http://example.com:8123" + + +def test_api_base_url_removes_trailing_slash(mock_stack): + """Test a trialing slash is removed when setting the API URL.""" + api_config = http.ApiConfig("127.0.0.1", "http://example.com/") + assert api_config.base_url == "http://example.com:8123" + + +def test_api_local_ip(mock_stack): + """Test a trialing slash is removed when setting the API URL.""" + api_config = http.ApiConfig("127.0.0.1", "http://example.com/") + assert api_config.local_ip == "127.0.0.1" + + +async def test_api_no_base_url(hass, mock_stack): """Test setting api url.""" result = await async_setup_component(hass, "http", {"http": {}}) assert result assert hass.config.api.base_url == "http://127.0.0.1:8123" -async def test_api_local_ip(hass): - """Test setting api url.""" - result = await async_setup_component(hass, "http", {"http": {}}) - assert result - assert hass.config.api.local_ip == "127.0.0.1" - - -async def test_api_base_url_removes_trailing_slash(hass): - """Test setting api url.""" - result = await async_setup_component( - hass, "http", {"http": {"base_url": "https://example.com/"}} - ) - assert result - assert hass.config.api.base_url == "https://example.com" - - async def test_not_log_password(hass, aiohttp_client, caplog, legacy_auth): """Test access with password doesn't get logged.""" assert await async_setup_component(hass, "api", {"http": {}}) diff --git a/tests/components/image_processing/test_init.py b/tests/components/image_processing/test_init.py index 9763dbf9415..72edcd7160a 100644 --- a/tests/components/image_processing/test_init.py +++ b/tests/components/image_processing/test_init.py @@ -62,7 +62,7 @@ class TestImageProcessing: self.hass.block_till_done() state = self.hass.states.get("camera.demo_camera") - self.url = f"{self.hass.config.api.base_url}{state.attributes.get(ATTR_ENTITY_PICTURE)}" + self.url = f"{self.hass.config.internal_url}{state.attributes.get(ATTR_ENTITY_PICTURE)}" def teardown_method(self): """Stop everything that was started.""" @@ -117,7 +117,7 @@ class TestImageProcessingAlpr: self.hass.block_till_done() state = self.hass.states.get("camera.demo_camera") - self.url = f"{self.hass.config.api.base_url}{state.attributes.get(ATTR_ENTITY_PICTURE)}" + self.url = f"{self.hass.config.internal_url}{state.attributes.get(ATTR_ENTITY_PICTURE)}" self.alpr_events = [] @@ -223,7 +223,7 @@ class TestImageProcessingFace: self.hass.block_till_done() state = self.hass.states.get("camera.demo_camera") - self.url = f"{self.hass.config.api.base_url}{state.attributes.get(ATTR_ENTITY_PICTURE)}" + self.url = f"{self.hass.config.internal_url}{state.attributes.get(ATTR_ENTITY_PICTURE)}" self.face_events = [] diff --git a/tests/components/microsoft_face_detect/test_image_processing.py b/tests/components/microsoft_face_detect/test_image_processing.py index b6f828318bc..d2e0eb19d57 100644 --- a/tests/components/microsoft_face_detect/test_image_processing.py +++ b/tests/components/microsoft_face_detect/test_image_processing.py @@ -118,7 +118,7 @@ class TestMicrosoftFaceDetect: self.hass.block_till_done() state = self.hass.states.get("camera.demo_camera") - url = f"{self.hass.config.api.base_url}{state.attributes.get(ATTR_ENTITY_PICTURE)}" + url = f"{self.hass.config.internal_url}{state.attributes.get(ATTR_ENTITY_PICTURE)}" face_events = [] diff --git a/tests/components/microsoft_face_identify/test_image_processing.py b/tests/components/microsoft_face_identify/test_image_processing.py index cc45220153e..856d308816c 100644 --- a/tests/components/microsoft_face_identify/test_image_processing.py +++ b/tests/components/microsoft_face_identify/test_image_processing.py @@ -119,7 +119,7 @@ class TestMicrosoftFaceIdentify: self.hass.block_till_done() state = self.hass.states.get("camera.demo_camera") - url = f"{self.hass.config.api.base_url}{state.attributes.get(ATTR_ENTITY_PICTURE)}" + url = f"{self.hass.config.internal_url}{state.attributes.get(ATTR_ENTITY_PICTURE)}" face_events = [] diff --git a/tests/components/openalpr_local/test_image_processing.py b/tests/components/openalpr_local/test_image_processing.py index bcf4fb8aafd..d98c27490e8 100644 --- a/tests/components/openalpr_local/test_image_processing.py +++ b/tests/components/openalpr_local/test_image_processing.py @@ -107,7 +107,7 @@ class TestOpenAlprLocal: self.hass.block_till_done() state = self.hass.states.get("camera.demo_camera") - self.url = f"{self.hass.config.api.base_url}{state.attributes.get(ATTR_ENTITY_PICTURE)}" + self.url = f"{self.hass.config.internal_url}{state.attributes.get(ATTR_ENTITY_PICTURE)}" self.alpr_events = [] From 391983a0cf56a226120057390ddd33586019b827 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 1 Jun 2020 23:25:06 +0200 Subject: [PATCH 291/406] Prevent race in pychromecast.start_discovery (#36350) * Workaround for race in pychromecast * Fix tests --- homeassistant/components/cast/discovery.py | 12 +++-- tests/components/cast/test_media_player.py | 56 +++++++++++++--------- 2 files changed, 42 insertions(+), 26 deletions(-) diff --git a/homeassistant/components/cast/discovery.py b/homeassistant/components/cast/discovery.py index b15346417de..4d58fc3383a 100644 --- a/homeassistant/components/cast/discovery.py +++ b/homeassistant/components/cast/discovery.py @@ -3,6 +3,7 @@ import logging import threading import pychromecast +import zeroconf from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.core import HomeAssistant @@ -84,10 +85,13 @@ def setup_internal_discovery(hass: HomeAssistant) -> None: ) _LOGGER.debug("Starting internal pychromecast discovery.") - listener, browser = pychromecast.start_discovery( - internal_add_callback, - internal_remove_callback, - ChromeCastZeroconf.get_zeroconf(), + listener = pychromecast.discovery.CastListener( + internal_add_callback, internal_remove_callback + ) + browser = zeroconf.ServiceBrowser( + ChromeCastZeroconf.get_zeroconf() or zeroconf.Zeroconf(), + "_googlecast._tcp.local.", + listener, ) def stop_discovery(event): diff --git a/tests/components/cast/test_media_player.py b/tests/components/cast/test_media_player.py index 2adb8b63052..ce9e4ad7bf8 100644 --- a/tests/components/cast/test_media_player.py +++ b/tests/components/cast/test_media_player.py @@ -80,16 +80,19 @@ async def async_setup_cast_internal_discovery(hass, config=None, discovery_info= browser = MagicMock(zc={}) with patch( - "homeassistant.components.cast.discovery.pychromecast.start_discovery", - return_value=(listener, browser), - ) as start_discovery: + "homeassistant.components.cast.discovery.pychromecast.discovery.CastListener", + return_value=listener, + ) as cast_listener, patch( + "homeassistant.components.cast.discovery.zeroconf.ServiceBrowser", + return_value=browser, + ): add_entities = await async_setup_cast(hass, config, discovery_info) await hass.async_block_till_done() await hass.async_block_till_done() - assert start_discovery.call_count == 1 + assert cast_listener.call_count == 1 - discovery_callback = start_discovery.call_args[0][0] + discovery_callback = cast_listener.call_args[0][0] def discover_chromecast(service_name: str, info: ChromecastInfo) -> None: """Discover a chromecast device.""" @@ -117,9 +120,12 @@ async def async_setup_media_player_cast(hass: HomeAssistantType, info: Chromecas "homeassistant.components.cast.discovery.pychromecast.get_chromecast_from_service", return_value=chromecast, ) as get_chromecast, patch( - "homeassistant.components.cast.discovery.pychromecast.start_discovery", - return_value=(listener, browser), - ) as start_discovery: + "homeassistant.components.cast.discovery.pychromecast.discovery.CastListener", + return_value=listener, + ) as cast_listener, patch( + "homeassistant.components.cast.discovery.zeroconf.ServiceBrowser", + return_value=browser, + ): await async_setup_component( hass, "media_player", @@ -128,7 +134,7 @@ async def async_setup_media_player_cast(hass: HomeAssistantType, info: Chromecas await hass.async_block_till_done() - discovery_callback = start_discovery.call_args[0][0] + discovery_callback = cast_listener.call_args[0][0] def discover_chromecast(service_name: str, info: ChromecastInfo) -> None: """Discover a chromecast device.""" @@ -153,15 +159,17 @@ async def async_setup_media_player_cast(hass: HomeAssistantType, info: Chromecas async def test_start_discovery_called_once(hass): """Test pychromecast.start_discovery called exactly once.""" with patch( - "homeassistant.components.cast.discovery.pychromecast.start_discovery", - return_value=(None, Mock()), - ) as start_discovery: + "homeassistant.components.cast.discovery.pychromecast.discovery.CastListener", + ) as cast_listener, patch( + "homeassistant.components.cast.discovery.zeroconf.ServiceBrowser", + return_value=Mock(), + ): await async_setup_cast(hass) - assert start_discovery.call_count == 1 + assert cast_listener.call_count == 1 await async_setup_cast(hass) - assert start_discovery.call_count == 1 + assert cast_listener.call_count == 1 async def test_stop_discovery_called_on_stop(hass): @@ -169,13 +177,15 @@ async def test_stop_discovery_called_on_stop(hass): browser = MagicMock(zc={}) with patch( - "homeassistant.components.cast.discovery.pychromecast.start_discovery", - return_value=(None, browser), - ) as start_discovery: + "homeassistant.components.cast.discovery.pychromecast.discovery.CastListener", + ) as cast_listener, patch( + "homeassistant.components.cast.discovery.zeroconf.ServiceBrowser", + return_value=browser, + ): # start_discovery should be called with empty config await async_setup_cast(hass, {}) - assert start_discovery.call_count == 1 + assert cast_listener.call_count == 1 with patch( "homeassistant.components.cast.discovery.pychromecast.stop_discovery" @@ -187,13 +197,15 @@ async def test_stop_discovery_called_on_stop(hass): stop_discovery.assert_called_once_with(browser) with patch( - "homeassistant.components.cast.discovery.pychromecast.start_discovery", - return_value=(None, browser), - ) as start_discovery: + "homeassistant.components.cast.discovery.pychromecast.discovery.CastListener", + ) as cast_listener, patch( + "homeassistant.components.cast.discovery.zeroconf.ServiceBrowser", + return_value=browser, + ): # start_discovery should be called again on re-startup await async_setup_cast(hass) - assert start_discovery.call_count == 1 + assert cast_listener.call_count == 1 async def test_create_cast_device_without_uuid(hass): From 07469127ce308d5918c1024c019db9874d04e025 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Mon, 1 Jun 2020 23:48:18 +0200 Subject: [PATCH 292/406] deCONZ - Add support for max/min mireds (#36355) * Add support for max/min mireds reported per light from deconz .77 * Bump dependency --- homeassistant/components/deconz/light.py | 10 ++++++++++ homeassistant/components/deconz/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/deconz/test_light.py | 4 ++++ 5 files changed, 17 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/deconz/light.py b/homeassistant/components/deconz/light.py index 48d286266e4..566ab563ca5 100644 --- a/homeassistant/components/deconz/light.py +++ b/homeassistant/components/deconz/light.py @@ -130,6 +130,16 @@ class DeconzLight(DeconzDevice, LightEntity): return color_util.color_xy_to_hs(*self._device.xy) return None + @property + def max_mireds(self): + """Return the warmest color_temp that this light supports.""" + return self._device.ctmax or super().max_mireds + + @property + def min_mireds(self): + """Return the coldest color_temp that this light supports.""" + return self._device.ctmin or super().min_mireds + @property def is_on(self): """Return true if light is on.""" diff --git a/homeassistant/components/deconz/manifest.json b/homeassistant/components/deconz/manifest.json index e33af57099d..149ea5f1bc5 100644 --- a/homeassistant/components/deconz/manifest.json +++ b/homeassistant/components/deconz/manifest.json @@ -3,7 +3,7 @@ "name": "deCONZ", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/deconz", - "requirements": ["pydeconz==70"], + "requirements": ["pydeconz==71"], "ssdp": [ { "manufacturer": "Royal Philips Electronics" diff --git a/requirements_all.txt b/requirements_all.txt index b60a60be24f..d6bf6723f58 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1269,7 +1269,7 @@ pydaikin==2.1.1 pydanfossair==0.1.0 # homeassistant.components.deconz -pydeconz==70 +pydeconz==71 # homeassistant.components.delijn pydelijn==0.6.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index c30e6745cbc..f8c3ef31090 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -545,7 +545,7 @@ pycoolmasternet==0.0.4 pydaikin==2.1.1 # homeassistant.components.deconz -pydeconz==70 +pydeconz==71 # homeassistant.components.zwave pydispatcher==2.0.5 diff --git a/tests/components/deconz/test_light.py b/tests/components/deconz/test_light.py index 229c085916e..782b2bf494b 100644 --- a/tests/components/deconz/test_light.py +++ b/tests/components/deconz/test_light.py @@ -46,6 +46,8 @@ LIGHTS = { "uniqueid": "00:00:00:00:00:00:00:00-00", }, "2": { + "ctmax": 454, + "ctmin": 155, "id": "Tunable white light id", "name": "Tunable white light", "state": {"on": True, "colormode": "ct", "ct": 2500, "reachable": True}, @@ -111,6 +113,8 @@ async def test_lights_and_groups(hass): tunable_white_light = hass.states.get("light.tunable_white_light") assert tunable_white_light.state == "on" assert tunable_white_light.attributes["color_temp"] == 2500 + assert tunable_white_light.attributes["max_mireds"] == 454 + assert tunable_white_light.attributes["min_mireds"] == 155 assert tunable_white_light.attributes["supported_features"] == 2 on_off_light = hass.states.get("light.on_off_light") From 1883b1d2a264ebdae30c2dd975df95262c192ec5 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 2 Jun 2020 00:19:00 +0200 Subject: [PATCH 293/406] Add config reloaded events for automation and scene (#34664) --- homeassistant/components/automation/__init__.py | 3 +++ homeassistant/components/homeassistant/scene.py | 3 +++ tests/components/automation/test_init.py | 9 ++++++++- tests/components/homeassistant/test_scene.py | 8 ++++++++ 4 files changed, 22 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index e5b66594d2f..42f8c9777f5 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -61,6 +61,8 @@ CONDITION_TYPE_OR = "or" DEFAULT_CONDITION_TYPE = CONDITION_TYPE_AND DEFAULT_INITIAL_STATE = True +EVENT_AUTOMATION_RELOADED = "automation_reloaded" + ATTR_LAST_TRIGGERED = "last_triggered" ATTR_VARIABLES = "variables" SERVICE_TRIGGER = "trigger" @@ -214,6 +216,7 @@ async def async_setup(hass, config): if conf is None: return await _async_process_config(hass, conf, component) + hass.bus.async_fire(EVENT_AUTOMATION_RELOADED, context=service_call.context) async_register_admin_service( hass, DOMAIN, SERVICE_RELOAD, reload_service_handler, schema=vol.Schema({}) diff --git a/homeassistant/components/homeassistant/scene.py b/homeassistant/components/homeassistant/scene.py index d14ef438a66..7e4a0433344 100644 --- a/homeassistant/components/homeassistant/scene.py +++ b/homeassistant/components/homeassistant/scene.py @@ -75,6 +75,7 @@ def _ensure_no_intersection(value): CONF_SCENE_ID = "scene_id" CONF_SNAPSHOT = "snapshot_entities" DATA_PLATFORM = "homeassistant_scene" +EVENT_SCENE_RELOADED = "scene_reloaded" STATES_SCHEMA = vol.All(dict, _convert_states) @@ -182,6 +183,8 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= _process_scenes_config(hass, async_add_entities, p_config) + hass.bus.async_fire(EVENT_SCENE_RELOADED, context=call.context) + hass.helpers.service.async_register_admin_service( SCENE_DOMAIN, SERVICE_RELOAD, reload_config ) diff --git a/tests/components/automation/test_init.py b/tests/components/automation/test_init.py index 7a082ba1931..a8baa9cfdb7 100644 --- a/tests/components/automation/test_init.py +++ b/tests/components/automation/test_init.py @@ -4,7 +4,7 @@ from datetime import timedelta import pytest import homeassistant.components.automation as automation -from homeassistant.components.automation import DOMAIN +from homeassistant.components.automation import DOMAIN, EVENT_AUTOMATION_RELOADED from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_NAME, @@ -483,6 +483,11 @@ async def test_reload_config_service(hass, calls, hass_admin_user, hass_read_onl assert len(calls) == 1 assert calls[0].data.get("event") == "test_event" + test_reload_event = [] + hass.bus.async_listen( + EVENT_AUTOMATION_RELOADED, lambda event: test_reload_event.append(event) + ) + with patch( "homeassistant.config.load_yaml_config_file", autospec=True, @@ -505,6 +510,8 @@ async def test_reload_config_service(hass, calls, hass_admin_user, hass_read_onl # De-flake ?! await hass.async_block_till_done() + assert len(test_reload_event) == 1 + assert hass.states.get("automation.hello") is None assert hass.states.get("automation.bye") is not None listeners = hass.bus.async_listeners() diff --git a/tests/components/homeassistant/test_scene.py b/tests/components/homeassistant/test_scene.py index 98afbd23e22..a1f502a3475 100644 --- a/tests/components/homeassistant/test_scene.py +++ b/tests/components/homeassistant/test_scene.py @@ -3,6 +3,7 @@ import pytest import voluptuous as vol from homeassistant.components.homeassistant import scene as ha_scene +from homeassistant.components.homeassistant.scene import EVENT_SCENE_RELOADED from homeassistant.setup import async_setup_component from tests.async_mock import patch @@ -13,6 +14,11 @@ async def test_reload_config_service(hass): """Test the reload config service.""" assert await async_setup_component(hass, "scene", {}) + test_reloaded_event = [] + hass.bus.async_listen( + EVENT_SCENE_RELOADED, lambda event: test_reloaded_event.append(event) + ) + with patch( "homeassistant.config.load_yaml_config_file", autospec=True, @@ -22,6 +28,7 @@ async def test_reload_config_service(hass): await hass.async_block_till_done() assert hass.states.get("scene.hallo") is not None + assert len(test_reloaded_event) == 1 with patch( "homeassistant.config.load_yaml_config_file", @@ -31,6 +38,7 @@ async def test_reload_config_service(hass): await hass.services.async_call("scene", "reload", blocking=True) await hass.async_block_till_done() + assert len(test_reloaded_event) == 2 assert hass.states.get("scene.hallo") is None assert hass.states.get("scene.bye") is not None From a6e9a312a309c64eb96118cacfb32f8ca8d79f2d Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Tue, 2 Jun 2020 00:20:52 +0200 Subject: [PATCH 294/406] deCONZ - Don't send off signals to light if already off (#36357) --- homeassistant/components/deconz/light.py | 3 +++ tests/components/deconz/test_light.py | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/homeassistant/components/deconz/light.py b/homeassistant/components/deconz/light.py index 566ab563ca5..513be407907 100644 --- a/homeassistant/components/deconz/light.py +++ b/homeassistant/components/deconz/light.py @@ -186,6 +186,9 @@ class DeconzLight(DeconzDevice, LightEntity): async def async_turn_off(self, **kwargs): """Turn off light.""" + if not self._device.state: + return + data = {"on": False} if ATTR_TRANSITION in kwargs: diff --git a/tests/components/deconz/test_light.py b/tests/components/deconz/test_light.py index 782b2bf494b..d070bd5b420 100644 --- a/tests/components/deconz/test_light.py +++ b/tests/components/deconz/test_light.py @@ -189,6 +189,26 @@ async def test_lights_and_groups(hass): json={"xy": (0.411, 0.351), "alert": "lselect", "effect": "none"}, ) + with patch.object(rgb_light_device, "_request", return_value=True) as set_callback: + await hass.services.async_call( + light.DOMAIN, + light.SERVICE_TURN_OFF, + {"entity_id": "light.rgb_light", "transition": 5, "flash": "short"}, + blocking=True, + ) + await hass.async_block_till_done() + assert not set_callback.called + + state_changed_event = { + "t": "event", + "e": "changed", + "r": "lights", + "id": "1", + "state": {"on": True}, + } + gateway.api.event_handler(state_changed_event) + await hass.async_block_till_done() + with patch.object(rgb_light_device, "_request", return_value=True) as set_callback: await hass.services.async_call( light.DOMAIN, From 2b42d77f5899b9a87b7713c4582b71bce1fd40dd Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 1 Jun 2020 16:19:12 -0700 Subject: [PATCH 295/406] Fix flaky media player test (#36358) --- tests/components/media_player/test_init.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/components/media_player/test_init.py b/tests/components/media_player/test_init.py index 35863f71362..d5eac466093 100644 --- a/tests/components/media_player/test_init.py +++ b/tests/components/media_player/test_init.py @@ -13,6 +13,7 @@ async def test_get_image(hass, hass_ws_client, caplog): await async_setup_component( hass, "media_player", {"media_player": {"platform": "demo"}} ) + await hass.async_block_till_done() client = await hass_ws_client(hass) From a4d4e26fe50023ed9790035e22b6bdd1f5b74feb Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Tue, 2 Jun 2020 00:05:54 +0000 Subject: [PATCH 296/406] [ci skip] Translation update --- .../components/axis/translations/no.json | 10 +++++++++ .../components/axis/translations/ru.json | 10 +++++++++ .../components/onvif/translations/pl.json | 2 +- .../components/openuv/translations/no.json | 3 +++ .../components/plugwise/translations/no.json | 22 +++++++++++++++++++ .../components/sonarr/translations/no.json | 18 ++++++++++++++- 6 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 homeassistant/components/plugwise/translations/no.json diff --git a/homeassistant/components/axis/translations/no.json b/homeassistant/components/axis/translations/no.json index 855bc4880ed..891b4b6d972 100644 --- a/homeassistant/components/axis/translations/no.json +++ b/homeassistant/components/axis/translations/no.json @@ -24,5 +24,15 @@ "title": "Sett opp Axis enhet" } } + }, + "options": { + "step": { + "configure_stream": { + "data": { + "stream_profile": "Velg str\u00f8mprofil som skal brukes" + }, + "title": "Axis videostr\u00f8m alternativer" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/axis/translations/ru.json b/homeassistant/components/axis/translations/ru.json index c970377084e..4f8aea64e78 100644 --- a/homeassistant/components/axis/translations/ru.json +++ b/homeassistant/components/axis/translations/ru.json @@ -24,5 +24,15 @@ "title": "Axis" } } + }, + "options": { + "step": { + "configure_stream": { + "data": { + "stream_profile": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043f\u0440\u043e\u0444\u0438\u043b\u044c \u043f\u043e\u0442\u043e\u043a\u0430 \u0434\u043b\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f" + }, + "title": "\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0432\u0438\u0434\u0435\u043e \u043f\u043e\u0442\u043e\u043a\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 Axis" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/onvif/translations/pl.json b/homeassistant/components/onvif/translations/pl.json index f1e0ea0bd69..d60d45c746f 100644 --- a/homeassistant/components/onvif/translations/pl.json +++ b/homeassistant/components/onvif/translations/pl.json @@ -40,7 +40,7 @@ "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.", + "description": "Klikaj\u0105c przycisk Zatwierd\u017a, 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" } } diff --git a/homeassistant/components/openuv/translations/no.json b/homeassistant/components/openuv/translations/no.json index 4152b732a7b..6ef1d389794 100644 --- a/homeassistant/components/openuv/translations/no.json +++ b/homeassistant/components/openuv/translations/no.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "Disse koordinatene er allerede registrert." + }, "error": { "identifier_exists": "Koordinatene er allerede registrert", "invalid_api_key": "Ugyldig API-n\u00f8kkel" diff --git a/homeassistant/components/plugwise/translations/no.json b/homeassistant/components/plugwise/translations/no.json new file mode 100644 index 00000000000..8205a7dab24 --- /dev/null +++ b/homeassistant/components/plugwise/translations/no.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Denne Smile-enheten er allerede konfigurert" + }, + "error": { + "cannot_connect": "Klarte ikke \u00e5 koble til, vennligst pr\u00f8v igjen", + "invalid_auth": "Ugyldig godkjenning, sjekk din 8-tegns Smile ID", + "unknown": "Uventet feil" + }, + "step": { + "user": { + "data": { + "host": "Smile IP-adresse", + "password": "" + }, + "description": "Detaljer", + "title": "Koble til Smile" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sonarr/translations/no.json b/homeassistant/components/sonarr/translations/no.json index af05fe620ee..0b98c67d820 100644 --- a/homeassistant/components/sonarr/translations/no.json +++ b/homeassistant/components/sonarr/translations/no.json @@ -4,7 +4,23 @@ "step": { "user": { "data": { - "port": "" + "api_key": "API N\u00f8kkel", + "base_path": "Bane til API", + "host": "Vert", + "port": "", + "ssl": "Sonarr bruker et SSL-sertifikat", + "verify_ssl": "Sonarr bruker et riktig sertifikat" + }, + "title": "Koble til Sonarr" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "upcoming_days": "Antall kommende dager som skal vises", + "wanted_max_items": "Maks antall \u00f8nskede elementer som skal vises" } } } From e6fe34e64d6d54350ca67c89ddb6c20c77b1fff1 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 2 Jun 2020 02:18:40 +0200 Subject: [PATCH 297/406] Migrate automation to use describe_event for logbook (#36356) --- .../components/automation/__init__.py | 15 +++- .../components/automation/manifest.json | 2 +- homeassistant/components/logbook/__init__.py | 17 ----- homeassistant/const.py | 1 - tests/components/automation/test_init.py | 41 ++++++++++- tests/components/logbook/test_init.py | 69 ------------------- 6 files changed, 53 insertions(+), 92 deletions(-) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 42f8c9777f5..9fabe0e2206 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -14,7 +14,6 @@ from homeassistant.const import ( CONF_ID, CONF_PLATFORM, CONF_ZONE, - EVENT_AUTOMATION_TRIGGERED, EVENT_HOMEASSISTANT_STARTED, SERVICE_RELOAD, SERVICE_TOGGLE, @@ -62,6 +61,7 @@ DEFAULT_CONDITION_TYPE = CONDITION_TYPE_AND DEFAULT_INITIAL_STATE = True EVENT_AUTOMATION_RELOADED = "automation_reloaded" +EVENT_AUTOMATION_TRIGGERED = "automation_triggered" ATTR_LAST_TRIGGERED = "last_triggered" ATTR_VARIABLES = "variables" @@ -222,6 +222,19 @@ async def async_setup(hass, config): hass, DOMAIN, SERVICE_RELOAD, reload_service_handler, schema=vol.Schema({}) ) + @callback + def async_describe_logbook_event(event): + """Describe a logbook event.""" + return { + "name": event.data.get(ATTR_NAME), + "message": "has been triggered", + "entity_id": event.data.get(ATTR_ENTITY_ID), + } + + hass.components.logbook.async_describe_event( + DOMAIN, EVENT_AUTOMATION_TRIGGERED, async_describe_logbook_event + ) + return True diff --git a/homeassistant/components/automation/manifest.json b/homeassistant/components/automation/manifest.json index 1b5fad1b588..a93baa0528a 100644 --- a/homeassistant/components/automation/manifest.json +++ b/homeassistant/components/automation/manifest.json @@ -2,7 +2,7 @@ "domain": "automation", "name": "Automation", "documentation": "https://www.home-assistant.io/integrations/automation", - "after_dependencies": ["device_automation", "webhook"], + "after_dependencies": ["device_automation", "logbook", "webhook"], "codeowners": ["@home-assistant/core"], "quality_scale": "internal" } diff --git a/homeassistant/components/logbook/__init__.py b/homeassistant/components/logbook/__init__.py index cd6a06720ae..82506c35b3b 100644 --- a/homeassistant/components/logbook/__init__.py +++ b/homeassistant/components/logbook/__init__.py @@ -22,7 +22,6 @@ from homeassistant.const import ( ATTR_NAME, CONF_EXCLUDE, CONF_INCLUDE, - EVENT_AUTOMATION_TRIGGERED, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, EVENT_LOGBOOK_ENTRY, @@ -82,7 +81,6 @@ ALL_EVENT_TYPES = [ EVENT_LOGBOOK_ENTRY, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, - EVENT_AUTOMATION_TRIGGERED, EVENT_SCRIPT_STARTED, ] @@ -316,17 +314,6 @@ def humanify(hass, events): "context_user_id": event.context.user_id, } - elif event.event_type == EVENT_AUTOMATION_TRIGGERED: - yield { - "when": event.time_fired, - "name": event.data.get(ATTR_NAME), - "message": "has been triggered", - "domain": "automation", - "entity_id": event.data.get(ATTR_ENTITY_ID), - "context_id": event.context.id, - "context_user_id": event.context.user_id, - } - elif event.event_type == EVENT_SCRIPT_STARTED: yield { "when": event.time_fired, @@ -461,10 +448,6 @@ def _keep_event(hass, event, entities_filter): domain = event.data.get(ATTR_DOMAIN) entity_id = event.data.get(ATTR_ENTITY_ID) - elif event.event_type == EVENT_AUTOMATION_TRIGGERED: - domain = "automation" - entity_id = event.data.get(ATTR_ENTITY_ID) - elif event.event_type == EVENT_SCRIPT_STARTED: domain = "script" entity_id = event.data.get(ATTR_ENTITY_ID) diff --git a/homeassistant/const.py b/homeassistant/const.py index 2ff1d65223b..2837c686c5d 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -180,7 +180,6 @@ CONF_XY = "xy" CONF_ZONE = "zone" # #### EVENTS #### -EVENT_AUTOMATION_TRIGGERED = "automation_triggered" EVENT_CALL_SERVICE = "call_service" EVENT_COMPONENT_LOADED = "component_loaded" EVENT_CORE_CONFIG_UPDATE = "core_config_updated" diff --git a/tests/components/automation/test_init.py b/tests/components/automation/test_init.py index a8baa9cfdb7..d17e55691bc 100644 --- a/tests/components/automation/test_init.py +++ b/tests/components/automation/test_init.py @@ -3,17 +3,21 @@ from datetime import timedelta import pytest +from homeassistant.components import logbook import homeassistant.components.automation as automation -from homeassistant.components.automation import DOMAIN, EVENT_AUTOMATION_RELOADED +from homeassistant.components.automation import ( + DOMAIN, + EVENT_AUTOMATION_RELOADED, + EVENT_AUTOMATION_TRIGGERED, +) from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_NAME, - EVENT_AUTOMATION_TRIGGERED, EVENT_HOMEASSISTANT_STARTED, STATE_OFF, STATE_ON, ) -from homeassistant.core import Context, CoreState, State +from homeassistant.core import Context, CoreState, Event, State from homeassistant.exceptions import HomeAssistantError, Unauthorized from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util @@ -1030,3 +1034,34 @@ async def test_extraction_functions(hass): "device-in-both", "device-in-last", } + + +async def test_logbook_humanify_automation_triggered_event(hass): + """Test humanifying Automation Trigger event.""" + await async_setup_component(hass, automation.DOMAIN, {}) + + event1, event2 = list( + logbook.humanify( + hass, + [ + Event( + EVENT_AUTOMATION_TRIGGERED, + {ATTR_ENTITY_ID: "automation.hello", ATTR_NAME: "Hello Automation"}, + ), + Event( + EVENT_AUTOMATION_TRIGGERED, + {ATTR_ENTITY_ID: "automation.bye", ATTR_NAME: "Bye Automation"}, + ), + ], + ) + ) + + assert event1["name"] == "Hello Automation" + assert event1["domain"] == "automation" + assert event1["message"] == "has been triggered" + assert event1["entity_id"] == "automation.hello" + + assert event2["name"] == "Bye Automation" + assert event2["domain"] == "automation" + assert event2["message"] == "has been triggered" + assert event2["entity_id"] == "automation.bye" diff --git a/tests/components/logbook/test_init.py b/tests/components/logbook/test_init.py index abe6f6ec515..27d39446fa3 100644 --- a/tests/components/logbook/test_init.py +++ b/tests/components/logbook/test_init.py @@ -14,7 +14,6 @@ from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_HIDDEN, ATTR_NAME, - EVENT_AUTOMATION_TRIGGERED, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, EVENT_SCRIPT_STARTED, @@ -305,45 +304,6 @@ class TestComponentLogbook(unittest.TestCase): entries[1], pointB, "blu", domain="sensor", entity_id=entity_id2 ) - def test_exclude_automation_events(self): - """Test if automation entries can be excluded by entity_id.""" - name = "My Automation Rule" - domain = "automation" - entity_id = "automation.my_automation_rule" - entity_id2 = "automation.my_automation_rule_2" - entity_id2 = "sensor.blu" - - eventA = ha.Event( - logbook.EVENT_AUTOMATION_TRIGGERED, - {logbook.ATTR_NAME: name, logbook.ATTR_ENTITY_ID: entity_id}, - ) - eventB = ha.Event( - logbook.EVENT_AUTOMATION_TRIGGERED, - {logbook.ATTR_NAME: name, logbook.ATTR_ENTITY_ID: entity_id2}, - ) - - config = logbook.CONFIG_SCHEMA( - { - ha.DOMAIN: {}, - logbook.DOMAIN: { - logbook.CONF_EXCLUDE: {logbook.CONF_ENTITIES: [entity_id]} - }, - } - ) - entities_filter = logbook._generate_filter_from_config(config[logbook.DOMAIN]) - events = [ - e - for e in (ha.Event(EVENT_HOMEASSISTANT_STOP), eventA, eventB) - if logbook._keep_event(self.hass, e, entities_filter) - ] - entries = list(logbook.humanify(self.hass, events)) - - assert len(entries) == 2 - self.assert_entry( - entries[0], name="Home Assistant", message="stopped", domain=ha.DOMAIN - ) - self.assert_entry(entries[1], name=name, domain=domain, entity_id=entity_id2) - def test_exclude_script_events(self): """Test if script start can be excluded by entity_id.""" name = "My Script Rule" @@ -1335,35 +1295,6 @@ async def test_logbook_view_period_entity(hass, hass_client): assert json[0]["entity_id"] == entity_id_test -async def test_humanify_automation_triggered_event(hass): - """Test humanifying Automation Trigger event.""" - event1, event2 = list( - logbook.humanify( - hass, - [ - ha.Event( - EVENT_AUTOMATION_TRIGGERED, - {ATTR_ENTITY_ID: "automation.hello", ATTR_NAME: "Hello Automation"}, - ), - ha.Event( - EVENT_AUTOMATION_TRIGGERED, - {ATTR_ENTITY_ID: "automation.bye", ATTR_NAME: "Bye Automation"}, - ), - ], - ) - ) - - assert event1["name"] == "Hello Automation" - assert event1["domain"] == "automation" - assert event1["message"] == "has been triggered" - assert event1["entity_id"] == "automation.hello" - - assert event2["name"] == "Bye Automation" - assert event2["domain"] == "automation" - assert event2["message"] == "has been triggered" - assert event2["entity_id"] == "automation.bye" - - async def test_humanify_script_started_event(hass): """Test humanifying Script Run event.""" event1, event2 = list( From cc4326276e6b110f849f470fec88e6f6eb4d0c3a Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Mon, 1 Jun 2020 21:49:02 -0600 Subject: [PATCH 298/406] RainMachine: Add time remaining as a zone attribute (#36361) --- homeassistant/components/rainmachine/switch.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/rainmachine/switch.py b/homeassistant/components/rainmachine/switch.py index 936aa8f1f0e..74704398061 100644 --- a/homeassistant/components/rainmachine/switch.py +++ b/homeassistant/components/rainmachine/switch.py @@ -22,7 +22,6 @@ from .const import ( _LOGGER = logging.getLogger(__name__) -ATTR_NEXT_RUN = "next_run" ATTR_AREA = "area" ATTR_CS_ON = "cs_on" ATTR_CURRENT_CYCLE = "current_cycle" @@ -30,6 +29,7 @@ ATTR_CYCLES = "cycles" ATTR_DELAY = "delay" ATTR_DELAY_ON = "delay_on" ATTR_FIELD_CAPACITY = "field_capacity" +ATTR_NEXT_RUN = "next_run" ATTR_NO_CYCLES = "number_of_cycles" ATTR_PRECIP_RATE = "sprinkler_head_precipitation_rate" ATTR_RESTRICTIONS = "restrictions" @@ -39,6 +39,7 @@ ATTR_SOIL_TYPE = "soil_type" ATTR_SPRINKLER_TYPE = "sprinkler_head_type" ATTR_STATUS = "status" ATTR_SUN_EXPOSURE = "sun_exposure" +ATTR_TIME_REMAINING = "time_remaining" ATTR_VEGETATION_TYPE = "vegetation_type" ATTR_ZONES = "zones" @@ -289,10 +290,10 @@ class RainMachineZone(RainMachineSwitch): self._attrs.update( { - ATTR_ID: self._switch_data["uid"], ATTR_AREA: details.get("waterSense").get("area"), ATTR_CURRENT_CYCLE: self._switch_data.get("cycle"), ATTR_FIELD_CAPACITY: details.get("waterSense").get("fieldCapacity"), + ATTR_ID: self._switch_data["uid"], ATTR_NO_CYCLES: self._switch_data.get("noOfCycles"), ATTR_PRECIP_RATE: details.get("waterSense").get("precipitationRate"), ATTR_RESTRICTIONS: self._switch_data.get("restriction"), @@ -300,6 +301,7 @@ class RainMachineZone(RainMachineSwitch): ATTR_SOIL_TYPE: SOIL_TYPE_MAP.get(details.get("sun")), ATTR_SPRINKLER_TYPE: SPRINKLER_TYPE_MAP.get(details.get("group_id")), ATTR_SUN_EXPOSURE: SUN_EXPOSURE_MAP.get(details.get("sun")), + ATTR_TIME_REMAINING: self._switch_data.get("remaining"), ATTR_VEGETATION_TYPE: VEGETATION_MAP.get(self._switch_data.get("type")), } ) From a3865fb3835a66928ecff1a6c739699ab57af322 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Tue, 2 Jun 2020 10:35:27 +0200 Subject: [PATCH 299/406] UniFi - Increase time to mark UniFi devices as away (#36366) --- homeassistant/components/unifi/device_tracker.py | 2 +- tests/components/unifi/test_device_tracker.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/unifi/device_tracker.py b/homeassistant/components/unifi/device_tracker.py index ebad63acb4e..602795404bb 100644 --- a/homeassistant/components/unifi/device_tracker.py +++ b/homeassistant/components/unifi/device_tracker.py @@ -301,7 +301,7 @@ class UniFiDeviceTracker(UniFiBase, ScannerEntity): self.cancel_scheduled_update = async_track_point_in_utc_time( self.hass, _no_heartbeat, - dt_util.utcnow() + timedelta(seconds=self.device.next_interval + 10), + dt_util.utcnow() + timedelta(seconds=self.device.next_interval + 60), ) elif ( diff --git a/tests/components/unifi/test_device_tracker.py b/tests/components/unifi/test_device_tracker.py index 33f296478c8..8f0df236c75 100644 --- a/tests/components/unifi/test_device_tracker.py +++ b/tests/components/unifi/test_device_tracker.py @@ -293,7 +293,7 @@ async def test_tracked_devices(hass): device_2 = hass.states.get("device_tracker.device_2") assert device_2.state == "home" - async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=40)) + async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=90)) await hass.async_block_till_done() device_1 = hass.states.get("device_tracker.device_1") From 0629b30ade8b619697e8cc28d651904e742cd70e Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 2 Jun 2020 10:50:34 +0200 Subject: [PATCH 300/406] Correct inst method names in system info, add Docker version (#36360) --- homeassistant/helpers/system_info.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/homeassistant/helpers/system_info.py b/homeassistant/helpers/system_info.py index 293a6e7bcef..a857858de1b 100644 --- a/homeassistant/helpers/system_info.py +++ b/homeassistant/helpers/system_info.py @@ -36,11 +36,9 @@ async def async_get_system_info(hass: HomeAssistantType) -> Dict: # Determine installation type on current data if info_object["docker"]: - info_object["installation_type"] = "Home Assistant Core on Docker" + info_object["installation_type"] = "Home Assistant Container" elif is_virtual_env(): - info_object[ - "installation_type" - ] = "Home Assistant Core in a Python Virtual Environment" + info_object["installation_type"] = "Home Assistant Core" # Enrich with Supervisor information if hass.components.hassio.is_hassio(): @@ -50,6 +48,7 @@ async def async_get_system_info(hass: HomeAssistantType) -> Dict: info_object["supervisor"] = info.get("supervisor") info_object["host_os"] = host.get("operating_system") info_object["chassis"] = host.get("chassis") + info_object["docker_version"] = info.get("docker") if info.get("hassos") is not None: info_object["installation_type"] = "Home Assistant" From da6a99b333dfe939f8d16f017b3ffa85fd30f52b Mon Sep 17 00:00:00 2001 From: Fredrik Erlandsson Date: Tue, 2 Jun 2020 13:24:15 +0200 Subject: [PATCH 301/406] Re-add connections to Daikin's device_info (#36340) * Re-add connections to device_info * Fix typo in identifiers Co-authored-by: Martin Hjelmare Co-authored-by: Martin Hjelmare --- homeassistant/components/daikin/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/daikin/__init__.py b/homeassistant/components/daikin/__init__.py index c51b30054df..2bd47651172 100644 --- a/homeassistant/components/daikin/__init__.py +++ b/homeassistant/components/daikin/__init__.py @@ -12,6 +12,7 @@ from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import CONF_HOST, CONF_HOSTS, CONF_PASSWORD from homeassistant.exceptions import ConfigEntryNotReady import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from homeassistant.helpers.typing import HomeAssistantType from homeassistant.util import Throttle @@ -156,7 +157,8 @@ class DaikinApi: """Return a device description for device registry.""" info = self.device.values return { - "identifieres": self.device.mac, + "connections": {(CONNECTION_NETWORK_MAC, self.device.mac)}, + "identifiers": self.device.mac, "manufacturer": "Daikin", "model": info.get("model"), "name": info.get("name"), From bde94d187caeaedfecc344fecc13bda968732f3d Mon Sep 17 00:00:00 2001 From: uvjustin <46082645+uvjustin@users.noreply.github.com> Date: Tue, 2 Jun 2020 20:26:25 +0800 Subject: [PATCH 302/406] Bump pyforked-daapd version to 0.1.10 (#36333) --- homeassistant/components/forked_daapd/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/forked_daapd/manifest.json b/homeassistant/components/forked_daapd/manifest.json index 171776a290a..2304e4196a7 100644 --- a/homeassistant/components/forked_daapd/manifest.json +++ b/homeassistant/components/forked_daapd/manifest.json @@ -3,7 +3,7 @@ "name": "forked-daapd", "documentation": "https://www.home-assistant.io/integrations/forked-daapd", "codeowners": ["@uvjustin"], - "requirements": ["pyforked-daapd==0.1.9", "pylibrespot-java==0.1.0"], + "requirements": ["pyforked-daapd==0.1.10", "pylibrespot-java==0.1.0"], "config_flow": true, "zeroconf": ["_daap._tcp.local."] } diff --git a/requirements_all.txt b/requirements_all.txt index d6bf6723f58..b40994c3df2 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1332,7 +1332,7 @@ pyflunearyou==1.0.7 pyfnip==0.2 # homeassistant.components.forked_daapd -pyforked-daapd==0.1.9 +pyforked-daapd==0.1.10 # homeassistant.components.fritzbox pyfritzhome==0.4.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f8c3ef31090..99f3c7a7163 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -563,7 +563,7 @@ pyflume==0.4.0 pyflunearyou==1.0.7 # homeassistant.components.forked_daapd -pyforked-daapd==0.1.9 +pyforked-daapd==0.1.10 # homeassistant.components.fritzbox pyfritzhome==0.4.2 From 37f7d262d752f5509b599b0f6924c72351777f42 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Tue, 2 Jun 2020 16:17:21 +0200 Subject: [PATCH 303/406] Change deCONZ sensor device classes (#36352) * Change vibration type to vibration instead of motion * Also replace icon and unit of measurement --- .../components/deconz/binary_sensor.py | 28 +++++++++---- homeassistant/components/deconz/sensor.py | 41 +++++++++++++++++-- tests/components/deconz/test_binary_sensor.py | 6 +++ tests/components/deconz/test_sensor.py | 9 ++++ 4 files changed, 73 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/deconz/binary_sensor.py b/homeassistant/components/deconz/binary_sensor.py index 95fa223c697..c8917934dd7 100644 --- a/homeassistant/components/deconz/binary_sensor.py +++ b/homeassistant/components/deconz/binary_sensor.py @@ -1,7 +1,15 @@ """Support for deCONZ binary sensors.""" -from pydeconz.sensor import Presence, Vibration +from pydeconz.sensor import CarbonMonoxide, Fire, OpenClose, Presence, Vibration, Water -from homeassistant.components.binary_sensor import BinarySensorEntity +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_GAS, + DEVICE_CLASS_MOISTURE, + DEVICE_CLASS_MOTION, + DEVICE_CLASS_OPENING, + DEVICE_CLASS_SMOKE, + DEVICE_CLASS_VIBRATION, + BinarySensorEntity, +) from homeassistant.const import ATTR_TEMPERATURE from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -14,6 +22,15 @@ ATTR_ORIENTATION = "orientation" ATTR_TILTANGLE = "tiltangle" ATTR_VIBRATIONSTRENGTH = "vibrationstrength" +DEVICE_CLASS = { + CarbonMonoxide: DEVICE_CLASS_GAS, + Fire: DEVICE_CLASS_SMOKE, + OpenClose: DEVICE_CLASS_OPENING, + Presence: DEVICE_CLASS_MOTION, + Vibration: DEVICE_CLASS_VIBRATION, + Water: DEVICE_CLASS_MOISTURE, +} + async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Old way of setting up deCONZ platforms.""" @@ -74,12 +91,7 @@ class DeconzBinarySensor(DeconzDevice, BinarySensorEntity): @property def device_class(self): """Return the class of the sensor.""" - return self._device.SENSOR_CLASS - - @property - def icon(self): - """Return the icon to use in the frontend.""" - return self._device.SENSOR_ICON + return DEVICE_CLASS.get(type(self._device)) @property def device_state_attributes(self): diff --git a/homeassistant/components/deconz/sensor.py b/homeassistant/components/deconz/sensor.py index ae0e55ae51f..330c262110a 100644 --- a/homeassistant/components/deconz/sensor.py +++ b/homeassistant/components/deconz/sensor.py @@ -3,9 +3,12 @@ from pydeconz.sensor import ( Battery, Consumption, Daylight, + Humidity, LightLevel, Power, + Pressure, Switch, + Temperature, Thermostat, ) @@ -13,6 +16,15 @@ from homeassistant.const import ( ATTR_TEMPERATURE, ATTR_VOLTAGE, DEVICE_CLASS_BATTERY, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_ILLUMINANCE, + DEVICE_CLASS_POWER, + DEVICE_CLASS_PRESSURE, + DEVICE_CLASS_TEMPERATURE, + ENERGY_KILO_WATT_HOUR, + POWER_WATT, + PRESSURE_HPA, + TEMP_CELSIUS, UNIT_PERCENTAGE, ) from homeassistant.core import callback @@ -31,6 +43,29 @@ ATTR_POWER = "power" ATTR_DAYLIGHT = "daylight" ATTR_EVENT_ID = "event_id" +DEVICE_CLASS = { + Humidity: DEVICE_CLASS_HUMIDITY, + LightLevel: DEVICE_CLASS_ILLUMINANCE, + Power: DEVICE_CLASS_POWER, + Pressure: DEVICE_CLASS_PRESSURE, + Temperature: DEVICE_CLASS_TEMPERATURE, +} + +ICON = { + Daylight: "mdi:white-balance-sunny", + Pressure: "mdi:gauge", + Temperature: "mdi:thermometer", +} + +UNIT_OF_MEASUREMENT = { + Consumption: ENERGY_KILO_WATT_HOUR, + Humidity: UNIT_PERCENTAGE, + LightLevel: "lx", + Power: POWER_WATT, + Pressure: PRESSURE_HPA, + Temperature: TEMP_CELSIUS, +} + async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Old way of setting up deCONZ platforms.""" @@ -119,17 +154,17 @@ class DeconzSensor(DeconzDevice): @property def device_class(self): """Return the class of the sensor.""" - return self._device.SENSOR_CLASS + return DEVICE_CLASS.get(type(self._device)) @property def icon(self): """Return the icon to use in the frontend.""" - return self._device.SENSOR_ICON + return ICON.get(type(self._device)) @property def unit_of_measurement(self): """Return the unit of measurement of this sensor.""" - return self._device.SENSOR_UNIT + return UNIT_OF_MEASUREMENT.get(type(self._device)) @property def device_state_attributes(self): diff --git a/tests/components/deconz/test_binary_sensor.py b/tests/components/deconz/test_binary_sensor.py index 864ba91fbc1..30f7251e067 100644 --- a/tests/components/deconz/test_binary_sensor.py +++ b/tests/components/deconz/test_binary_sensor.py @@ -3,6 +3,10 @@ from copy import deepcopy from homeassistant.components import deconz import homeassistant.components.binary_sensor as binary_sensor +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_MOTION, + DEVICE_CLASS_VIBRATION, +) from homeassistant.setup import async_setup_component from .test_gateway import DECONZ_WEB_REQUEST, setup_deconz_integration @@ -79,6 +83,7 @@ async def test_binary_sensors(hass): presence_sensor = hass.states.get("binary_sensor.presence_sensor") assert presence_sensor.state == "off" + assert presence_sensor.attributes["device_class"] == DEVICE_CLASS_MOTION temperature_sensor = hass.states.get("binary_sensor.temperature_sensor") assert temperature_sensor is None @@ -88,6 +93,7 @@ async def test_binary_sensors(hass): vibration_sensor = hass.states.get("binary_sensor.vibration_sensor") assert vibration_sensor.state == "on" + assert vibration_sensor.attributes["device_class"] == DEVICE_CLASS_VIBRATION state_changed_event = { "t": "event", diff --git a/tests/components/deconz/test_sensor.py b/tests/components/deconz/test_sensor.py index cda3138557d..9d87c7b91cb 100644 --- a/tests/components/deconz/test_sensor.py +++ b/tests/components/deconz/test_sensor.py @@ -3,6 +3,11 @@ from copy import deepcopy from homeassistant.components import deconz import homeassistant.components.sensor as sensor +from homeassistant.const import ( + DEVICE_CLASS_BATTERY, + DEVICE_CLASS_ILLUMINANCE, + DEVICE_CLASS_POWER, +) from homeassistant.setup import async_setup_component from .test_gateway import DECONZ_WEB_REQUEST, setup_deconz_integration @@ -112,6 +117,7 @@ async def test_sensors(hass): light_level_sensor = hass.states.get("sensor.light_level_sensor") assert light_level_sensor.state == "999.8" + assert light_level_sensor.attributes["device_class"] == DEVICE_CLASS_ILLUMINANCE presence_sensor = hass.states.get("sensor.presence_sensor") assert presence_sensor is None @@ -127,15 +133,18 @@ async def test_sensors(hass): switch_2_battery_level = hass.states.get("sensor.switch_2_battery_level") assert switch_2_battery_level.state == "100" + assert switch_2_battery_level.attributes["device_class"] == DEVICE_CLASS_BATTERY daylight_sensor = hass.states.get("sensor.daylight_sensor") assert daylight_sensor is None power_sensor = hass.states.get("sensor.power_sensor") assert power_sensor.state == "6" + assert power_sensor.attributes["device_class"] == DEVICE_CLASS_POWER consumption_sensor = hass.states.get("sensor.consumption_sensor") assert consumption_sensor.state == "0.002" + assert "device_class" not in consumption_sensor.attributes state_changed_event = { "t": "event", From e86bedb223c28e1926fa1af0b05c74f594ca0c0c Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 2 Jun 2020 17:29:59 +0200 Subject: [PATCH 304/406] Prevent possible secret values to show up in deprecation logs (#36368) Co-authored-by: Martin Hjelmare --- homeassistant/helpers/config_validation.py | 26 ++++++++++----------- tests/helpers/test_config_validation.py | 27 +++++++++------------- 2 files changed, 24 insertions(+), 29 deletions(-) diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 32121958b03..c24adc76597 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -657,30 +657,30 @@ def deprecated( if replacement_key and invalidation_version: warning = ( - "The '{key}' option (with value '{value}') is" - " deprecated, please replace it with '{replacement_key}'." + "The '{key}' option is deprecated," + " please replace it with '{replacement_key}'." " This option will become invalid in version" " {invalidation_version}" ) elif replacement_key: warning = ( - "The '{key}' option (with value '{value}') is" - " deprecated, please replace it with '{replacement_key}'" + "The '{key}' option is deprecated," + " please replace it with '{replacement_key}'" ) elif invalidation_version: warning = ( - "The '{key}' option (with value '{value}') is" - " deprecated, please remove it from your configuration." + "The '{key}' option is deprecated," + " please remove it from your configuration." " This option will become invalid in version" " {invalidation_version}" ) else: warning = ( - "The '{key}' option (with value '{value}') is" - " deprecated, please remove it from your configuration" + "The '{key}' option is deprecated," + " please remove it from your configuration" ) - def check_for_invalid_version(value: Optional[Any]) -> None: + def check_for_invalid_version() -> None: """Raise error if current version has reached invalidation.""" if not invalidation_version: return @@ -689,7 +689,6 @@ def deprecated( raise vol.Invalid( warning.format( key=key, - value=value, replacement_key=replacement_key, invalidation_version=invalidation_version, ) @@ -698,19 +697,20 @@ def deprecated( def validator(config: Dict) -> Dict: """Check if key is in config and log warning.""" if key in config: - value = config[key] - check_for_invalid_version(value) + check_for_invalid_version() KeywordStyleAdapter(logging.getLogger(module_name)).warning( warning, key=key, - value=value, replacement_key=replacement_key, invalidation_version=invalidation_version, ) + + value = config[key] if replacement_key: config.pop(key) else: value = default + keys = [key] if replacement_key: keys.append(replacement_key) diff --git a/tests/helpers/test_config_validation.py b/tests/helpers/test_config_validation.py index 72eb61bbacb..d0f19f356ae 100644 --- a/tests/helpers/test_config_validation.py +++ b/tests/helpers/test_config_validation.py @@ -548,8 +548,7 @@ def test_deprecated_with_no_optionals(caplog, schema): "homeassistant.helpers.config_validation", ] assert ( - "The 'mars' option (with value 'True') is deprecated, " - "please remove it from your configuration" + "The 'mars' option is deprecated, please remove it from your configuration" ) in caplog.text assert test_data == output @@ -582,8 +581,7 @@ def test_deprecated_with_replacement_key(caplog, schema): output = deprecated_schema(test_data.copy()) assert len(caplog.records) == 1 assert ( - "The 'mars' option (with value 'True') is deprecated, " - "please replace it with 'jupiter'" + "The 'mars' option is deprecated, please replace it with 'jupiter'" ) in caplog.text assert {"jupiter": True} == output @@ -617,7 +615,7 @@ def test_deprecated_with_invalidation_version(caplog, schema, version): ) message = ( - "The 'mars' option (with value 'True') is deprecated, " + "The 'mars' option is deprecated, " "please remove it from your configuration. " "This option will become invalid in version 1.0.0" ) @@ -643,7 +641,7 @@ def test_deprecated_with_invalidation_version(caplog, schema, version): with pytest.raises(vol.MultipleInvalid) as exc_info: invalidated_schema(test_data) assert str(exc_info.value) == ( - "The 'mars' option (with value 'True') is deprecated, " + "The 'mars' option is deprecated, " "please remove it from your configuration. This option will " "become invalid in version 0.1.0" ) @@ -671,7 +669,7 @@ def test_deprecated_with_replacement_key_and_invalidation_version( ) warning = ( - "The 'mars' option (with value 'True') is deprecated, " + "The 'mars' option is deprecated, " "please replace it with 'jupiter'. This option will become " "invalid in version 1.0.0" ) @@ -703,7 +701,7 @@ def test_deprecated_with_replacement_key_and_invalidation_version( with pytest.raises(vol.MultipleInvalid) as exc_info: invalidated_schema(test_data) assert str(exc_info.value) == ( - "The 'mars' option (with value 'True') is deprecated, " + "The 'mars' option is deprecated, " "please replace it with 'jupiter'. This option will become " "invalid in version 0.1.0" ) @@ -725,8 +723,7 @@ def test_deprecated_with_default(caplog, schema): assert len(caplog.records) == 1 assert caplog.records[0].name == __name__ assert ( - "The 'mars' option (with value 'True') is deprecated, " - "please remove it from your configuration" + "The 'mars' option is deprecated, please remove it from your configuration" ) in caplog.text assert test_data == output @@ -759,8 +756,7 @@ def test_deprecated_with_replacement_key_and_default(caplog, schema): output = deprecated_schema(test_data.copy()) assert len(caplog.records) == 1 assert ( - "The 'mars' option (with value 'True') is deprecated, " - "please replace it with 'jupiter'" + "The 'mars' option is deprecated, please replace it with 'jupiter'" ) in caplog.text assert {"jupiter": True} == output @@ -792,8 +788,7 @@ def test_deprecated_with_replacement_key_and_default(caplog, schema): output = deprecated_schema_with_default(test_data.copy()) assert len(caplog.records) == 1 assert ( - "The 'mars' option (with value 'True') is deprecated, " - "please replace it with 'jupiter'" + "The 'mars' option is deprecated, please replace it with 'jupiter'" ) in caplog.text assert {"jupiter": True} == output @@ -828,7 +823,7 @@ def test_deprecated_with_replacement_key_invalidation_version_default( output = deprecated_schema(test_data.copy()) assert len(caplog.records) == 1 assert ( - "The 'mars' option (with value 'True') is deprecated, " + "The 'mars' option is deprecated, " "please replace it with 'jupiter'. This option will become " "invalid in version 1.0.0" ) in caplog.text @@ -855,7 +850,7 @@ def test_deprecated_with_replacement_key_invalidation_version_default( with pytest.raises(vol.MultipleInvalid) as exc_info: invalidated_schema(test_data) assert str(exc_info.value) == ( - "The 'mars' option (with value 'True') is deprecated, " + "The 'mars' option is deprecated, " "please replace it with 'jupiter'. This option will become " "invalid in version 0.1.0" ) From 5d780ded29e2556f52d902c1017433904f0b7bad Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 2 Jun 2020 10:54:13 -0500 Subject: [PATCH 305/406] Fix flapping aiohttp_client test (#36379) --- tests/helpers/test_aiohttp_client.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/helpers/test_aiohttp_client.py b/tests/helpers/test_aiohttp_client.py index 1b5121d75e0..0f37ebd7b3b 100644 --- a/tests/helpers/test_aiohttp_client.py +++ b/tests/helpers/test_aiohttp_client.py @@ -27,6 +27,7 @@ def camera_client_fixture(hass, hass_client): }, ) ) + hass.loop.run_until_complete(hass.async_block_till_done()) yield hass.loop.run_until_complete(hass_client()) From 26cbca101ade36886ef387b66d944d02c24733f2 Mon Sep 17 00:00:00 2001 From: Quentame Date: Tue, 2 Jun 2020 18:22:51 +0200 Subject: [PATCH 306/406] Refactor Synology entries to allow not fetching the API when it's disabled + add security binary sensor (#35565) - add Synology DSM Security binary sensor (enabled by default) - use device name instead of id in names - add device type to name - show disk manufacturer, model and firmware version in devices - some entries are disabled by default (`entity_registry_enabled_default`) - binary sensor + sensor uses `device_class` when possible - do not fetch a concerned API if all entries of it are disabled - entity unique_id now uses key instead of label - entity entity_id changes for disk and volume: example from `sensor.synology_status_sda` to `sensor.synology_drive_1_status`, or from `sensor.synology_average_disk_temp_volume_1` to `sensor.synology_volume_1_average_disk_temp` - now binary sensor: - disk_exceed_bad_sector_thr - disk_below_remain_life_thr - removed sensor: - volume type (RAID, SHR ...) - disk name (Drive [X]) - disk device (/dev/sd[Y]) --- .coveragerc | 1 + .../components/synology_dsm/__init__.py | 326 +++++++++++++++++- .../components/synology_dsm/binary_sensor.py | 66 ++++ .../components/synology_dsm/const.py | 251 ++++++++++++-- .../components/synology_dsm/sensor.py | 138 +------- 5 files changed, 622 insertions(+), 160 deletions(-) create mode 100644 homeassistant/components/synology_dsm/binary_sensor.py diff --git a/.coveragerc b/.coveragerc index e49103c3b3b..e17ba7a6503 100644 --- a/.coveragerc +++ b/.coveragerc @@ -757,6 +757,7 @@ omit = homeassistant/components/synology/camera.py homeassistant/components/synology_chat/notify.py homeassistant/components/synology_dsm/__init__.py + homeassistant/components/synology_dsm/binary_sensor.py homeassistant/components/synology_dsm/sensor.py homeassistant/components/synology_srm/device_tracker.py homeassistant/components/syslog/notify.py diff --git a/homeassistant/components/synology_dsm/__init__.py b/homeassistant/components/synology_dsm/__init__.py index b2ff2d2e8ef..d4dbecf8f1c 100644 --- a/homeassistant/components/synology_dsm/__init__.py +++ b/homeassistant/components/synology_dsm/__init__.py @@ -1,7 +1,11 @@ """The Synology DSM component.""" +import asyncio from datetime import timedelta +import logging +from typing import Dict from synology_dsm import SynologyDSM +from synology_dsm.api.core.security import SynoCoreSecurity from synology_dsm.api.core.utilization import SynoCoreUtilization from synology_dsm.api.dsm.information import SynoDSMInformation from synology_dsm.api.storage.storage import SynoStorage @@ -9,6 +13,7 @@ import voluptuous as vol from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import ( + ATTR_ATTRIBUTION, CONF_DISKS, CONF_HOST, CONF_MAC, @@ -18,18 +23,36 @@ from homeassistant.const import ( CONF_SSL, CONF_USERNAME, ) +from homeassistant.core import callback +from homeassistant.helpers import entity_registry import homeassistant.helpers.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 homeassistant.helpers.typing import HomeAssistantType from .const import ( + BASE_NAME, CONF_VOLUMES, DEFAULT_SCAN_INTERVAL, DEFAULT_SSL, DOMAIN, + ENTITY_CLASS, + ENTITY_ENABLE, + ENTITY_ICON, + ENTITY_NAME, + ENTITY_UNIT, + PLATFORMS, + STORAGE_DISK_BINARY_SENSORS, + STORAGE_DISK_SENSORS, + STORAGE_VOL_SENSORS, SYNO_API, + TEMP_SENSORS_KEYS, UNDO_UPDATE_LISTENER, + UTILISATION_SENSORS, ) CONFIG_SCHEMA = vol.Schema( @@ -49,6 +72,11 @@ CONFIG_SCHEMA = vol.Schema( extra=vol.ALLOW_EXTRA, ) +ATTRIBUTION = "Data provided by Synology" + + +_LOGGER = logging.getLogger(__name__) + async def async_setup(hass, config): """Set up Synology DSM sensors from legacy config file.""" @@ -71,6 +99,65 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry): """Set up Synology DSM sensors.""" api = SynoApi(hass, entry) + # Migrate old unique_id + @callback + def _async_migrator(entity_entry: entity_registry.RegistryEntry): + """Migrate away from ID using label.""" + # Reject if new unique_id + if "SYNO." in entity_entry.unique_id: + return None + + entries = { + **STORAGE_DISK_BINARY_SENSORS, + **STORAGE_DISK_SENSORS, + **STORAGE_VOL_SENSORS, + **UTILISATION_SENSORS, + } + infos = entity_entry.unique_id.split("_") + serial = infos.pop(0) + label = infos.pop(0) + device_id = "_".join(infos) + + # Removed entity + if ( + "Type" in entity_entry.unique_id + or "Device" in entity_entry.unique_id + or "Name" in entity_entry.unique_id + ): + return None + + entity_type = None + for entity_key, entity_attrs in entries.items(): + if ( + device_id + and entity_attrs[ENTITY_NAME] == "Status" + and "Status" in entity_entry.unique_id + and "(Smart)" not in entity_entry.unique_id + ): + if "sd" in device_id and "disk" in entity_key: + entity_type = entity_key + continue + if "volume" in device_id and "volume" in entity_key: + entity_type = entity_key + continue + + if entity_attrs[ENTITY_NAME] == label: + entity_type = entity_key + + new_unique_id = "_".join([serial, entity_type]) + if device_id: + new_unique_id += f"_{device_id}" + + _LOGGER.info( + "Migrating unique_id from [%s] to [%s]", + entity_entry.unique_id, + new_unique_id, + ) + return {"new_unique_id": new_unique_id} + + await entity_registry.async_migrate_entries(hass, entry.entry_id, _async_migrator) + + # Continue setup await api.async_setup() undo_listener = entry.add_update_listener(_async_update_listener) @@ -88,16 +175,24 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry): entry, data={**entry.data, CONF_MAC: network.macs} ) - hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, "sensor") - ) + for platform in PLATFORMS: + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(entry, platform) + ) return True async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry): """Unload Synology DSM sensors.""" - unload_ok = await hass.config_entries.async_forward_entry_unload(entry, "sensor") + unload_ok = all( + await asyncio.gather( + *[ + hass.config_entries.async_forward_entry_unload(entry, platform) + for platform in PLATFORMS + ] + ) + ) if unload_ok: entry_data = hass.data[DOMAIN][entry.unique_id] @@ -121,10 +216,18 @@ class SynoApi: self._hass = hass self._entry = entry + # DSM APIs self.dsm: SynologyDSM = None self.information: SynoDSMInformation = None - self.utilisation: SynoCoreUtilization = None + self.security: SynoCoreSecurity = None self.storage: SynoStorage = None + self.utilisation: SynoCoreUtilization = None + + # Should we fetch them + self._fetching_entities = {} + self._with_security = True + self._with_storage = True + self._with_utilisation = True self._unsub_dispatcher = None @@ -144,12 +247,14 @@ class SynoApi: device_token=self._entry.data.get("device_token"), ) + self._async_setup_api_requests() + await self._hass.async_add_executor_job(self._fetch_device_configuration) - await self.update() + await self.async_update() self._unsub_dispatcher = async_track_time_interval( self._hass, - self.update, + self.async_update, timedelta( minutes=self._entry.options.get( CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL @@ -157,17 +262,216 @@ class SynoApi: ), ) + @callback + def subscribe(self, api_key, unique_id): + """Subscribe an entity from API fetches.""" + if api_key not in self._fetching_entities: + self._fetching_entities[api_key] = set() + self._fetching_entities[api_key].add(unique_id) + + @callback + def unsubscribe() -> None: + """Unsubscribe an entity from API fetches (when disable).""" + self._fetching_entities[api_key].remove(unique_id) + + return unsubscribe + + @callback + def _async_setup_api_requests(self): + """Determine if we should fetch each API, if one entity needs it.""" + # Entities not added yet, fetch all + if not self._fetching_entities: + return + + # Determine if we should fetch an API + self._with_security = bool( + self._fetching_entities.get(SynoCoreSecurity.API_KEY) + ) + self._with_storage = bool(self._fetching_entities.get(SynoStorage.API_KEY)) + self._with_utilisation = bool( + self._fetching_entities.get(SynoCoreUtilization.API_KEY) + ) + + # Reset not used API + if not self._with_security: + self.dsm.reset(self.security) + self.security = None + + if not self._with_storage: + self.dsm.reset(self.storage) + self.storage = None + + if not self._with_utilisation: + self.dsm.reset(self.utilisation) + self.utilisation = None + def _fetch_device_configuration(self): """Fetch initial device config.""" self.information = self.dsm.information - self.utilisation = self.dsm.utilisation - self.storage = self.dsm.storage + + if self._with_security: + self.security = self.dsm.security + + if self._with_storage: + self.storage = self.dsm.storage + + if self._with_utilisation: + self.utilisation = self.dsm.utilisation async def async_unload(self): """Stop interacting with the NAS and prepare for removal from hass.""" self._unsub_dispatcher() - async def update(self, now=None): + async def async_update(self, now=None): """Update function for updating API information.""" + self._async_setup_api_requests() await self._hass.async_add_executor_job(self.dsm.update) async_dispatcher_send(self._hass, self.signal_sensor_update) + + +class SynologyDSMEntity(Entity): + """Representation of a Synology NAS entry.""" + + def __init__( + self, api: SynoApi, entity_type: str, entity_info: Dict[str, str], + ): + """Initialize the Synology DSM entity.""" + self._api = api + self._api_key = entity_type.split(":")[0] + self.entity_type = entity_type.split(":")[-1] + self._name = f"{BASE_NAME} {entity_info[ENTITY_NAME]}" + self._class = entity_info[ENTITY_CLASS] + self._enable_default = entity_info[ENTITY_ENABLE] + self._icon = entity_info[ENTITY_ICON] + self._unit = entity_info[ENTITY_UNIT] + self._unique_id = f"{self._api.information.serial}_{entity_type}" + + @property + def unique_id(self) -> str: + """Return a unique ID.""" + return self._unique_id + + @property + def name(self) -> str: + """Return the name.""" + return self._name + + @property + def icon(self) -> str: + """Return the icon.""" + return self._icon + + @property + def unit_of_measurement(self) -> str: + """Return the unit the value is expressed in.""" + if self.entity_type in TEMP_SENSORS_KEYS: + return self.hass.config.units.temperature_unit + return self._unit + + @property + def device_class(self) -> str: + """Return the class of this device.""" + return self._class + + @property + def device_state_attributes(self) -> Dict[str, any]: + """Return the state attributes.""" + return {ATTR_ATTRIBUTION: ATTRIBUTION} + + @property + def device_info(self) -> Dict[str, any]: + """Return the device information.""" + return { + "identifiers": {(DOMAIN, self._api.information.serial)}, + "name": "Synology NAS", + "manufacturer": "Synology", + "model": self._api.information.model, + "sw_version": self._api.information.version_string, + } + + @property + def entity_registry_enabled_default(self) -> bool: + """Return if the entity should be enabled when first added to the entity registry.""" + return self._enable_default + + @property + def should_poll(self) -> bool: + """No polling needed.""" + return False + + async def async_update(self): + """Only used by the generic entity update service.""" + if not self.enabled: + return + + await self._api.async_update() + + async def async_added_to_hass(self): + """Register state update callback.""" + self.async_on_remove( + async_dispatcher_connect( + self.hass, self._api.signal_sensor_update, self.async_write_ha_state + ) + ) + + self.async_on_remove(self._api.subscribe(self._api_key, self.unique_id)) + + +class SynologyDSMDeviceEntity(SynologyDSMEntity): + """Representation of a Synology NAS disk or volume entry.""" + + def __init__( + self, + api: SynoApi, + entity_type: str, + entity_info: Dict[str, str], + device_id: str = None, + ): + """Initialize the Synology DSM disk or volume entity.""" + super().__init__(api, entity_type, entity_info) + self._device_id = device_id + self._device_name = None + self._device_manufacturer = None + self._device_model = None + self._device_firmware = None + self._device_type = None + + if "volume" in entity_type: + volume = self._api.storage._get_volume(self._device_id) + # Volume does not have a name + self._device_name = volume["id"].replace("_", " ").capitalize() + self._device_manufacturer = "Synology" + self._device_model = self._api.information.model + self._device_firmware = self._api.information.version_string + self._device_type = ( + volume["device_type"] + .replace("_", " ") + .replace("raid", "RAID") + .replace("shr", "SHR") + ) + elif "disk" in entity_type: + disk = self._api.storage._get_disk(self._device_id) + self._device_name = disk["name"] + self._device_manufacturer = disk["vendor"] + self._device_model = disk["model"].strip() + self._device_firmware = disk["firm"] + self._device_type = disk["diskType"] + self._name = f"{BASE_NAME} {self._device_name} {entity_info[ENTITY_NAME]}" + self._unique_id += f"_{self._device_id}" + + @property + def available(self) -> bool: + """Return True if entity is available.""" + return bool(self._api.storage) + + @property + def device_info(self) -> Dict[str, any]: + """Return the device information.""" + return { + "identifiers": {(DOMAIN, self._api.information.serial, self._device_id)}, + "name": f"Synology NAS ({self._device_name} - {self._device_type})", + "manufacturer": self._device_manufacturer, + "model": self._device_model, + "sw_version": self._device_firmware, + "via_device": (DOMAIN, self._api.information.serial), + } diff --git a/homeassistant/components/synology_dsm/binary_sensor.py b/homeassistant/components/synology_dsm/binary_sensor.py new file mode 100644 index 00000000000..3dfc21b8a7b --- /dev/null +++ b/homeassistant/components/synology_dsm/binary_sensor.py @@ -0,0 +1,66 @@ +"""Support for Synology DSM binary sensors.""" +from homeassistant.components.binary_sensor import BinarySensorEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_DISKS +from homeassistant.helpers.typing import HomeAssistantType + +from . import SynologyDSMDeviceEntity, SynologyDSMEntity +from .const import ( + DOMAIN, + SECURITY_BINARY_SENSORS, + STORAGE_DISK_BINARY_SENSORS, + SYNO_API, +) + + +async def async_setup_entry( + hass: HomeAssistantType, entry: ConfigEntry, async_add_entities +) -> None: + """Set up the Synology NAS binary sensor.""" + + api = hass.data[DOMAIN][entry.unique_id][SYNO_API] + + entities = [ + SynoDSMSecurityBinarySensor( + api, sensor_type, SECURITY_BINARY_SENSORS[sensor_type] + ) + for sensor_type in SECURITY_BINARY_SENSORS + ] + + # Handle all disks + if api.storage.disks_ids: + for disk in entry.data.get(CONF_DISKS, api.storage.disks_ids): + entities += [ + SynoDSMStorageBinarySensor( + api, sensor_type, STORAGE_DISK_BINARY_SENSORS[sensor_type], disk + ) + for sensor_type in STORAGE_DISK_BINARY_SENSORS + ] + + async_add_entities(entities) + + +class SynoDSMSecurityBinarySensor(SynologyDSMEntity, BinarySensorEntity): + """Representation a Synology Security binary sensor.""" + + @property + def is_on(self) -> bool: + """Return the state.""" + return getattr(self._api.security, self.entity_type) != "safe" + + @property + def available(self) -> bool: + """Return True if entity is available.""" + return bool(self._api.security) + + +class SynoDSMStorageBinarySensor(SynologyDSMDeviceEntity, BinarySensorEntity): + """Representation a Synology Storage binary sensor.""" + + @property + def is_on(self) -> bool: + """Return the state.""" + attr = getattr(self._api.storage, self.entity_type)(self._device_id) + if attr is None: + return None + return attr diff --git a/homeassistant/components/synology_dsm/const.py b/homeassistant/components/synology_dsm/const.py index e0a166e908b..c525bec2229 100644 --- a/homeassistant/components/synology_dsm/const.py +++ b/homeassistant/components/synology_dsm/const.py @@ -1,4 +1,9 @@ """Constants for Synology DSM.""" + +from synology_dsm.api.core.security import SynoCoreSecurity +from synology_dsm.api.core.utilization import SynoCoreUtilization +from synology_dsm.api.storage.storage import SynoStorage + from homeassistant.const import ( DATA_MEGABYTES, DATA_RATE_KILOBYTES_PER_SECOND, @@ -7,6 +12,8 @@ from homeassistant.const import ( ) DOMAIN = "synology_dsm" +PLATFORMS = ["binary_sensor", "sensor"] + BASE_NAME = "Synology" # Entry keys @@ -15,47 +22,231 @@ UNDO_UPDATE_LISTENER = "undo_update_listener" # Configuration CONF_VOLUMES = "volumes" + DEFAULT_SSL = True DEFAULT_PORT = 5000 DEFAULT_PORT_SSL = 5001 # Options DEFAULT_SCAN_INTERVAL = 15 # min + +ENTITY_NAME = "name" +ENTITY_UNIT = "unit" +ENTITY_ICON = "icon" +ENTITY_CLASS = "device_class" +ENTITY_ENABLE = "enable" + +# Entity keys should start with the API_KEY to fetch + +# Binary sensors +STORAGE_DISK_BINARY_SENSORS = { + f"{SynoStorage.API_KEY}:disk_exceed_bad_sector_thr": { + ENTITY_NAME: "Exceeded Max Bad Sectors", + ENTITY_UNIT: None, + ENTITY_ICON: "mdi:test-tube", + ENTITY_CLASS: None, + ENTITY_ENABLE: True, + }, + f"{SynoStorage.API_KEY}:disk_below_remain_life_thr": { + ENTITY_NAME: "Below Min Remaining Life", + ENTITY_UNIT: None, + ENTITY_ICON: "mdi:test-tube", + ENTITY_CLASS: None, + ENTITY_ENABLE: True, + }, +} + +SECURITY_BINARY_SENSORS = { + f"{SynoCoreSecurity.API_KEY}:status": { + ENTITY_NAME: "Security status", + ENTITY_UNIT: None, + ENTITY_ICON: "mdi:checkbox-marked-circle-outline", + ENTITY_CLASS: "safety", + ENTITY_ENABLE: True, + }, +} + +# Sensors UTILISATION_SENSORS = { - "cpu_other_load": ["CPU Load (Other)", UNIT_PERCENTAGE, "mdi:chip"], - "cpu_user_load": ["CPU Load (User)", UNIT_PERCENTAGE, "mdi:chip"], - "cpu_system_load": ["CPU Load (System)", UNIT_PERCENTAGE, "mdi:chip"], - "cpu_total_load": ["CPU Load (Total)", UNIT_PERCENTAGE, "mdi:chip"], - "cpu_1min_load": ["CPU Load (1 min)", UNIT_PERCENTAGE, "mdi:chip"], - "cpu_5min_load": ["CPU Load (5 min)", UNIT_PERCENTAGE, "mdi:chip"], - "cpu_15min_load": ["CPU Load (15 min)", UNIT_PERCENTAGE, "mdi:chip"], - "memory_real_usage": ["Memory Usage (Real)", UNIT_PERCENTAGE, "mdi:memory"], - "memory_size": ["Memory Size", DATA_MEGABYTES, "mdi:memory"], - "memory_cached": ["Memory Cached", DATA_MEGABYTES, "mdi:memory"], - "memory_available_swap": ["Memory Available (Swap)", DATA_MEGABYTES, "mdi:memory"], - "memory_available_real": ["Memory Available (Real)", DATA_MEGABYTES, "mdi:memory"], - "memory_total_swap": ["Memory Total (Swap)", DATA_MEGABYTES, "mdi:memory"], - "memory_total_real": ["Memory Total (Real)", DATA_MEGABYTES, "mdi:memory"], - "network_up": ["Network Up", DATA_RATE_KILOBYTES_PER_SECOND, "mdi:upload"], - "network_down": ["Network Down", DATA_RATE_KILOBYTES_PER_SECOND, "mdi:download"], + f"{SynoCoreUtilization.API_KEY}:cpu_other_load": { + ENTITY_NAME: "CPU Load (Other)", + ENTITY_UNIT: UNIT_PERCENTAGE, + ENTITY_ICON: "mdi:chip", + ENTITY_CLASS: None, + ENTITY_ENABLE: False, + }, + f"{SynoCoreUtilization.API_KEY}:cpu_user_load": { + ENTITY_NAME: "CPU Load (User)", + ENTITY_UNIT: UNIT_PERCENTAGE, + ENTITY_ICON: "mdi:chip", + ENTITY_CLASS: None, + ENTITY_ENABLE: True, + }, + f"{SynoCoreUtilization.API_KEY}:cpu_system_load": { + ENTITY_NAME: "CPU Load (System)", + ENTITY_UNIT: UNIT_PERCENTAGE, + ENTITY_ICON: "mdi:chip", + ENTITY_CLASS: None, + ENTITY_ENABLE: False, + }, + f"{SynoCoreUtilization.API_KEY}:cpu_total_load": { + ENTITY_NAME: "CPU Load (Total)", + ENTITY_UNIT: UNIT_PERCENTAGE, + ENTITY_ICON: "mdi:chip", + ENTITY_CLASS: None, + ENTITY_ENABLE: True, + }, + f"{SynoCoreUtilization.API_KEY}:cpu_1min_load": { + ENTITY_NAME: "CPU Load (1 min)", + ENTITY_UNIT: UNIT_PERCENTAGE, + ENTITY_ICON: "mdi:chip", + ENTITY_CLASS: None, + ENTITY_ENABLE: False, + }, + f"{SynoCoreUtilization.API_KEY}:cpu_5min_load": { + ENTITY_NAME: "CPU Load (5 min)", + ENTITY_UNIT: UNIT_PERCENTAGE, + ENTITY_ICON: "mdi:chip", + ENTITY_CLASS: None, + ENTITY_ENABLE: True, + }, + f"{SynoCoreUtilization.API_KEY}:cpu_15min_load": { + ENTITY_NAME: "CPU Load (15 min)", + ENTITY_UNIT: UNIT_PERCENTAGE, + ENTITY_ICON: "mdi:chip", + ENTITY_CLASS: None, + ENTITY_ENABLE: True, + }, + f"{SynoCoreUtilization.API_KEY}:memory_real_usage": { + ENTITY_NAME: "Memory Usage (Real)", + ENTITY_UNIT: UNIT_PERCENTAGE, + ENTITY_ICON: "mdi:memory", + ENTITY_CLASS: None, + ENTITY_ENABLE: True, + }, + f"{SynoCoreUtilization.API_KEY}:memory_size": { + ENTITY_NAME: "Memory Size", + ENTITY_UNIT: DATA_MEGABYTES, + ENTITY_ICON: "mdi:memory", + ENTITY_CLASS: None, + ENTITY_ENABLE: False, + }, + f"{SynoCoreUtilization.API_KEY}:memory_cached": { + ENTITY_NAME: "Memory Cached", + ENTITY_UNIT: DATA_MEGABYTES, + ENTITY_ICON: "mdi:memory", + ENTITY_CLASS: None, + ENTITY_ENABLE: False, + }, + f"{SynoCoreUtilization.API_KEY}:memory_available_swap": { + ENTITY_NAME: "Memory Available (Swap)", + ENTITY_UNIT: DATA_MEGABYTES, + ENTITY_ICON: "mdi:memory", + ENTITY_CLASS: None, + ENTITY_ENABLE: True, + }, + f"{SynoCoreUtilization.API_KEY}:memory_available_real": { + ENTITY_NAME: "Memory Available (Real)", + ENTITY_UNIT: DATA_MEGABYTES, + ENTITY_ICON: "mdi:memory", + ENTITY_CLASS: None, + ENTITY_ENABLE: True, + }, + f"{SynoCoreUtilization.API_KEY}:memory_total_swap": { + ENTITY_NAME: "Memory Total (Swap)", + ENTITY_UNIT: DATA_MEGABYTES, + ENTITY_ICON: "mdi:memory", + ENTITY_CLASS: None, + ENTITY_ENABLE: True, + }, + f"{SynoCoreUtilization.API_KEY}:memory_total_real": { + ENTITY_NAME: "Memory Total (Real)", + ENTITY_UNIT: DATA_MEGABYTES, + ENTITY_ICON: "mdi:memory", + ENTITY_CLASS: None, + ENTITY_ENABLE: True, + }, + f"{SynoCoreUtilization.API_KEY}:network_up": { + ENTITY_NAME: "Network Up", + ENTITY_UNIT: DATA_RATE_KILOBYTES_PER_SECOND, + ENTITY_ICON: "mdi:upload", + ENTITY_CLASS: None, + ENTITY_ENABLE: True, + }, + f"{SynoCoreUtilization.API_KEY}:network_down": { + ENTITY_NAME: "Network Down", + ENTITY_UNIT: DATA_RATE_KILOBYTES_PER_SECOND, + ENTITY_ICON: "mdi:download", + ENTITY_CLASS: None, + ENTITY_ENABLE: True, + }, } STORAGE_VOL_SENSORS = { - "volume_status": ["Status", None, "mdi:checkbox-marked-circle-outline"], - "volume_device_type": ["Type", None, "mdi:harddisk"], - "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"], + f"{SynoStorage.API_KEY}:volume_status": { + ENTITY_NAME: "Status", + ENTITY_UNIT: None, + ENTITY_ICON: "mdi:checkbox-marked-circle-outline", + ENTITY_CLASS: None, + ENTITY_ENABLE: True, + }, + f"{SynoStorage.API_KEY}:volume_size_total": { + ENTITY_NAME: "Total Size", + ENTITY_UNIT: DATA_TERABYTES, + ENTITY_ICON: "mdi:chart-pie", + ENTITY_CLASS: None, + ENTITY_ENABLE: False, + }, + f"{SynoStorage.API_KEY}:volume_size_used": { + ENTITY_NAME: "Used Space", + ENTITY_UNIT: DATA_TERABYTES, + ENTITY_ICON: "mdi:chart-pie", + ENTITY_CLASS: None, + ENTITY_ENABLE: True, + }, + f"{SynoStorage.API_KEY}:volume_percentage_used": { + ENTITY_NAME: "Volume Used", + ENTITY_UNIT: UNIT_PERCENTAGE, + ENTITY_ICON: "mdi:chart-pie", + ENTITY_CLASS: None, + ENTITY_ENABLE: True, + }, + f"{SynoStorage.API_KEY}:volume_disk_temp_avg": { + ENTITY_NAME: "Average Disk Temp", + ENTITY_UNIT: None, + ENTITY_ICON: "mdi:thermometer", + ENTITY_CLASS: "temperature", + ENTITY_ENABLE: True, + }, + f"{SynoStorage.API_KEY}:volume_disk_temp_max": { + ENTITY_NAME: "Maximum Disk Temp", + ENTITY_UNIT: None, + ENTITY_ICON: "mdi:thermometer", + ENTITY_CLASS: "temperature", + ENTITY_ENABLE: False, + }, } STORAGE_DISK_SENSORS = { - "disk_name": ["Name", None, "mdi:harddisk"], - "disk_device": ["Device", None, "mdi:dots-horizontal"], - "disk_smart_status": ["Status (Smart)", None, "mdi:checkbox-marked-circle-outline"], - "disk_status": ["Status", None, "mdi:checkbox-marked-circle-outline"], - "disk_exceed_bad_sector_thr": ["Exceeded Max Bad Sectors", None, "mdi:test-tube"], - "disk_below_remain_life_thr": ["Below Min Remaining Life", None, "mdi:test-tube"], - "disk_temp": ["Temperature", None, "mdi:thermometer"], + f"{SynoStorage.API_KEY}:disk_smart_status": { + ENTITY_NAME: "Status (Smart)", + ENTITY_UNIT: None, + ENTITY_ICON: "mdi:checkbox-marked-circle-outline", + ENTITY_CLASS: None, + ENTITY_ENABLE: False, + }, + f"{SynoStorage.API_KEY}:disk_status": { + ENTITY_NAME: "Status", + ENTITY_UNIT: None, + ENTITY_ICON: "mdi:checkbox-marked-circle-outline", + ENTITY_CLASS: None, + ENTITY_ENABLE: True, + }, + f"{SynoStorage.API_KEY}:disk_temp": { + ENTITY_NAME: "Temperature", + ENTITY_UNIT: None, + ENTITY_ICON: "mdi:thermometer", + ENTITY_CLASS: "temperature", + ENTITY_ENABLE: True, + }, } diff --git a/homeassistant/components/synology_dsm/sensor.py b/homeassistant/components/synology_dsm/sensor.py index 81873cad4cd..22171fdf2f5 100644 --- a/homeassistant/components/synology_dsm/sensor.py +++ b/homeassistant/components/synology_dsm/sensor.py @@ -1,9 +1,6 @@ -"""Support for Synology DSM Sensors.""" -from typing import Dict - +"""Support for Synology DSM sensors.""" from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( - ATTR_ATTRIBUTION, CONF_DISKS, DATA_MEGABYTES, DATA_RATE_KILOBYTES_PER_SECOND, @@ -11,14 +8,11 @@ from homeassistant.const import ( PRECISION_TENTHS, TEMP_CELSIUS, ) -from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import Entity from homeassistant.helpers.temperature import display_temp from homeassistant.helpers.typing import HomeAssistantType -from . import SynoApi +from . import SynologyDSMDeviceEntity, SynologyDSMEntity from .const import ( - BASE_NAME, CONF_VOLUMES, DOMAIN, STORAGE_DISK_SENSORS, @@ -28,8 +22,6 @@ from .const import ( UTILISATION_SENSORS, ) -ATTRIBUTION = "Data provided by Synology" - async def async_setup_entry( hass: HomeAssistantType, entry: ConfigEntry, async_add_entities @@ -38,16 +30,16 @@ async def async_setup_entry( api = hass.data[DOMAIN][entry.unique_id][SYNO_API] - sensors = [ - SynoNasUtilSensor(api, sensor_type, UTILISATION_SENSORS[sensor_type]) + entities = [ + SynoDSMUtilSensor(api, sensor_type, UTILISATION_SENSORS[sensor_type]) for sensor_type in UTILISATION_SENSORS ] # Handle all volumes if api.storage.volumes_ids: for volume in entry.data.get(CONF_VOLUMES, api.storage.volumes_ids): - sensors += [ - SynoNasStorageSensor( + entities += [ + SynoDSMStorageSensor( api, sensor_type, STORAGE_VOL_SENSORS[sensor_type], volume ) for sensor_type in STORAGE_VOL_SENSORS @@ -56,106 +48,23 @@ async def async_setup_entry( # Handle all disks if api.storage.disks_ids: for disk in entry.data.get(CONF_DISKS, api.storage.disks_ids): - sensors += [ - SynoNasStorageSensor( + entities += [ + SynoDSMStorageSensor( api, sensor_type, STORAGE_DISK_SENSORS[sensor_type], disk ) for sensor_type in STORAGE_DISK_SENSORS ] - async_add_entities(sensors) + async_add_entities(entities) -class SynoNasSensor(Entity): - """Representation of a Synology NAS sensor.""" - - def __init__( - self, - api: SynoApi, - sensor_type: str, - sensor_info: Dict[str, str], - monitored_device: str = None, - ): - """Initialize the sensor.""" - self._api = api - self.sensor_type = sensor_type - self._name = f"{BASE_NAME} {sensor_info[0]}" - self._unit = sensor_info[1] - self._icon = sensor_info[2] - self.monitored_device = monitored_device - self._unique_id = f"{self._api.information.serial}_{sensor_info[0]}" - - if self.monitored_device: - self._name += f" ({self.monitored_device})" - self._unique_id += f"_{self.monitored_device}" - - self._unsub_dispatcher = None - - @property - def unique_id(self) -> str: - """Return a unique ID.""" - return self._unique_id - - @property - def name(self) -> str: - """Return the name.""" - return self._name - - @property - def icon(self) -> str: - """Return the icon.""" - return self._icon - - @property - def unit_of_measurement(self) -> str: - """Return the unit the value is expressed in.""" - if self.sensor_type in TEMP_SENSORS_KEYS: - return self.hass.config.units.temperature_unit - return self._unit - - @property - def device_state_attributes(self) -> Dict[str, any]: - """Return the state attributes.""" - return {ATTR_ATTRIBUTION: ATTRIBUTION} - - @property - def device_info(self) -> Dict[str, any]: - """Return the device information.""" - return { - "identifiers": {(DOMAIN, self._api.information.serial)}, - "name": "Synology NAS", - "manufacturer": "Synology", - "model": self._api.information.model, - "sw_version": self._api.information.version_string, - } - - @property - def should_poll(self) -> bool: - """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( - self.hass, self._api.signal_sensor_update, self.async_write_ha_state - ) - - async def async_will_remove_from_hass(self): - """Clean up after entity before removal.""" - self._unsub_dispatcher() - - -class SynoNasUtilSensor(SynoNasSensor): +class SynoDSMUtilSensor(SynologyDSMEntity): """Representation a Synology Utilisation sensor.""" @property def state(self): """Return the state.""" - attr = getattr(self._api.utilisation, self.sensor_type) + attr = getattr(self._api.utilisation, self.entity_type) if callable(attr): attr = attr() if attr is None: @@ -171,14 +80,19 @@ class SynoNasUtilSensor(SynoNasSensor): return attr + @property + def available(self) -> bool: + """Return True if entity is available.""" + return bool(self._api.utilisation) -class SynoNasStorageSensor(SynoNasSensor): + +class SynoDSMStorageSensor(SynologyDSMDeviceEntity): """Representation a Synology Storage sensor.""" @property def state(self): """Return the state.""" - attr = getattr(self._api.storage, self.sensor_type)(self.monitored_device) + attr = getattr(self._api.storage, self.entity_type)(self._device_id) if attr is None: return None @@ -187,21 +101,7 @@ class SynoNasStorageSensor(SynoNasSensor): return round(attr / 1024.0 ** 4, 2) # Temperature - if self.sensor_type in TEMP_SENSORS_KEYS: + if self.entity_type in TEMP_SENSORS_KEYS: return display_temp(self.hass, attr, TEMP_CELSIUS, PRECISION_TENTHS) return attr - - @property - def device_info(self) -> Dict[str, any]: - """Return the device information.""" - return { - "identifiers": { - (DOMAIN, self._api.information.serial, self.monitored_device) - }, - "name": f"Synology NAS ({self.monitored_device})", - "manufacturer": "Synology", - "model": self._api.information.model, - "sw_version": self._api.information.version_string, - "via_device": (DOMAIN, self._api.information.serial), - } From c68e0a1d4646f6697f1972b99a5a5ad132f87d27 Mon Sep 17 00:00:00 2001 From: Tom Date: Tue, 2 Jun 2020 18:30:00 +0200 Subject: [PATCH 307/406] Update plugwise to async and config_flow binary_sensor part (#36378) * Add binary_sensor component * Version bump * Blushing commit - tnx @bdraco --- .coveragerc | 1 + homeassistant/components/plugwise/__init__.py | 2 +- .../components/plugwise/binary_sensor.py | 96 +++++++++++++++++++ homeassistant/components/plugwise/const.py | 2 + .../components/plugwise/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 7 files changed, 103 insertions(+), 4 deletions(-) create mode 100644 homeassistant/components/plugwise/binary_sensor.py diff --git a/.coveragerc b/.coveragerc index e17ba7a6503..41398ac5f0e 100644 --- a/.coveragerc +++ b/.coveragerc @@ -604,6 +604,7 @@ omit = homeassistant/components/plex/media_player.py homeassistant/components/plex/sensor.py homeassistant/components/plugwise/__init__.py + homeassistant/components/plugwise/binary_sensor.py homeassistant/components/plugwise/climate.py homeassistant/components/plugwise/sensor.py homeassistant/components/plum_lightpad/* diff --git a/homeassistant/components/plugwise/__init__.py b/homeassistant/components/plugwise/__init__.py index 610acfc3e23..abcb614253d 100644 --- a/homeassistant/components/plugwise/__init__.py +++ b/homeassistant/components/plugwise/__init__.py @@ -24,7 +24,7 @@ CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.Schema({})}, extra=vol.ALLOW_EXTRA) _LOGGER = logging.getLogger(__name__) SENSOR_PLATFORMS = ["sensor"] -ALL_PLATFORMS = ["climate", "sensor"] +ALL_PLATFORMS = ["binary_sensor", "climate", "sensor"] async def async_setup(hass: HomeAssistant, config: dict): diff --git a/homeassistant/components/plugwise/binary_sensor.py b/homeassistant/components/plugwise/binary_sensor.py new file mode 100644 index 00000000000..a2156cd37f9 --- /dev/null +++ b/homeassistant/components/plugwise/binary_sensor.py @@ -0,0 +1,96 @@ +"""Plugwise Binary Sensor component for Home Assistant.""" + +import logging + +from homeassistant.components.binary_sensor import BinarySensorEntity +from homeassistant.const import STATE_OFF, STATE_ON +from homeassistant.core import callback + +from .const import DOMAIN, FLAME_ICON, FLOW_OFF_ICON, FLOW_ON_ICON, IDLE_ICON +from .sensor import SmileSensor + +BINARY_SENSOR_MAP = { + "dhw_state": ["Domestic Hot Water State", None], + "slave_boiler_state": ["Secondary Heater Device State", None], +} + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up the Smile binary_sensors from a config entry.""" + api = hass.data[DOMAIN][config_entry.entry_id]["api"] + coordinator = hass.data[DOMAIN][config_entry.entry_id]["coordinator"] + + entities = [] + + all_devices = api.get_all_devices() + for dev_id, device_properties in all_devices.items(): + if device_properties["class"] == "heater_central": + data = api.get_device_data(dev_id) + for binary_sensor, dummy in BINARY_SENSOR_MAP.items(): + if binary_sensor in data: + entities.append( + PwBinarySensor( + api, + coordinator, + device_properties["name"], + binary_sensor, + dev_id, + device_properties["class"], + ) + ) + + async_add_entities(entities, True) + + +class PwBinarySensor(SmileSensor, BinarySensorEntity): + """Representation of a Plugwise binary_sensor.""" + + def __init__(self, api, coordinator, name, binary_sensor, dev_id, model): + """Set up the Plugwise API.""" + super().__init__(api, coordinator, name, dev_id, binary_sensor) + + self._binary_sensor = binary_sensor + + self._is_on = False + self._icon = None + + self._unique_id = f"{dev_id}-{binary_sensor}" + + @property + def is_on(self): + """Return true if the binary sensor is on.""" + return self._is_on + + @property + def icon(self): + """Return the icon to use in the frontend.""" + return self._icon + + @callback + def _async_process_data(self): + """Update the entity.""" + data = self._api.get_device_data(self._dev_id) + + if not data: + _LOGGER.error("Received no data for device %s.", self._binary_sensor) + self.async_write_ha_state() + return + + if self._binary_sensor in data: + self._is_on = data[self._binary_sensor] + + self._state = STATE_OFF + if self._binary_sensor == "dhw_state": + self._icon = FLOW_OFF_ICON + if self._binary_sensor == "slave_boiler_state": + self._icon = IDLE_ICON + if self._is_on: + self._state = STATE_ON + if self._binary_sensor == "dhw_state": + self._icon = FLOW_ON_ICON + if self._binary_sensor == "slave_boiler_state": + self._icon = FLAME_ICON + + self.async_write_ha_state() diff --git a/homeassistant/components/plugwise/const.py b/homeassistant/components/plugwise/const.py index b57532b9192..9dc4c24b1e1 100644 --- a/homeassistant/components/plugwise/const.py +++ b/homeassistant/components/plugwise/const.py @@ -37,3 +37,5 @@ SCHEDULE_OFF = "false" COOL_ICON = "mdi:snowflake" FLAME_ICON = "mdi:fire" IDLE_ICON = "mdi:circle-off-outline" +FLOW_OFF_ICON = "mdi:water-pump-off" +FLOW_ON_ICON = "mdi:water-pump" diff --git a/homeassistant/components/plugwise/manifest.json b/homeassistant/components/plugwise/manifest.json index baecf485682..fa4cf32a2ec 100644 --- a/homeassistant/components/plugwise/manifest.json +++ b/homeassistant/components/plugwise/manifest.json @@ -2,7 +2,7 @@ "domain": "plugwise", "name": "Plugwise", "documentation": "https://www.home-assistant.io/integrations/plugwise", - "requirements": ["Plugwise_Smile==0.2.10"], + "requirements": ["Plugwise_Smile==0.2.13"], "codeowners": ["@CoMPaTech", "@bouwew"], "config_flow": true } diff --git a/requirements_all.txt b/requirements_all.txt index b40994c3df2..92477ae5ea9 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -44,7 +44,7 @@ Mastodon.py==1.5.1 OPi.GPIO==0.4.0 # homeassistant.components.plugwise -Plugwise_Smile==0.2.10 +Plugwise_Smile==0.2.13 # homeassistant.components.essent PyEssent==0.13 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 99f3c7a7163..0069aac21a5 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -7,7 +7,7 @@ HAP-python==2.9.1 # homeassistant.components.plugwise -Plugwise_Smile==0.2.10 +Plugwise_Smile==0.2.13 # homeassistant.components.flick_electric PyFlick==0.0.2 From dfc3a24522a01376e13105cca727b3ca21c96cce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Rutkai?= Date: Tue, 2 Jun 2020 19:11:33 +0200 Subject: [PATCH 308/406] Update list of voices in Watson TTS service (#36377) Co-authored-by: Franck Nijhof --- homeassistant/components/watson_tts/tts.py | 46 +++++++++++++--------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/watson_tts/tts.py b/homeassistant/components/watson_tts/tts.py index 74803464484..67dfa32e673 100644 --- a/homeassistant/components/watson_tts/tts.py +++ b/homeassistant/components/watson_tts/tts.py @@ -22,39 +22,47 @@ CONF_TEXT_TYPE = "text" # List from https://tinyurl.com/watson-tts-docs SUPPORTED_VOICES = [ - "de-DE_BirgitVoice", - "de-DE_BirgitV2Voice", + "ar-AR_OmarVoice", "de-DE_BirgitV3Voice", - "de-DE_DieterVoice", - "de-DE_DieterV2Voice", + "de-DE_BirgitVoice", "de-DE_DieterV3Voice", - "en-GB_KateVoice", + "de-DE_DieterVoice", + "de-DE_ErikaV3Voice", "en-GB_KateV3Voice", - "en-US_AllisonVoice", - "en-US_AllisonV2Voice", + "en-GB_KateVoice", "en-US_AllisonV3Voice", - "en-US_LisaVoice", - "en-US_LisaV2Voice", + "en-US_AllisonVoice", + "en-US_EmilyV3Voice", + "en-US_HenryV3Voice", + "en-US_KevinV3Voice", "en-US_LisaV3Voice", - "en-US_MichaelVoice", - "en-US_MichaelV2Voice", + "en-US_LisaVoice", "en-US_MichaelV3Voice", - "es-ES_EnriqueVoice", + "en-US_MichaelVoice", + "en-US_OliviaV3Voice", "es-ES_EnriqueV3Voice", - "es-ES_LauraVoice", + "es-ES_EnriqueVoice", "es-ES_LauraV3Voice", - "es-LA_SofiaVoice", + "es-ES_LauraVoice", "es-LA_SofiaV3Voice", - "es-US_SofiaVoice", + "es-LA_SofiaVoice", "es-US_SofiaV3Voice", - "fr-FR_ReneeVoice", + "es-US_SofiaVoice", "fr-FR_ReneeV3Voice", - "it-IT_FrancescaVoice", - "it-IT_FrancescaV2Voice", + "fr-FR_ReneeVoice", "it-IT_FrancescaV3Voice", + "it-IT_FrancescaVoice", + "ja-JP_EmiV3Voice", "ja-JP_EmiVoice", - "pt-BR_IsabelaVoice", + "ko-KR_YoungmiVoice", + "ko-KR_YunaVoice", + "nl-NL_EmmaVoice", + "nl-NL_LiamVoice", "pt-BR_IsabelaV3Voice", + "pt-BR_IsabelaVoice", + "zh-CN_LiNaVoice", + "zh-CN_WangWeiVoice", + "zh-CN_ZhangJingVoice", ] SUPPORTED_OUTPUT_FORMATS = [ From 08566f84cd228c3fef131f0c9e8390575cd60e8a Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 2 Jun 2020 19:37:52 +0200 Subject: [PATCH 309/406] Remove wunderlist, platform is decommissioned (#36380) --- .coveragerc | 1 - .../components/wunderlist/__init__.py | 91 ------------------- .../components/wunderlist/manifest.json | 7 -- .../components/wunderlist/services.yaml | 15 --- requirements_all.txt | 3 - 5 files changed, 117 deletions(-) delete mode 100644 homeassistant/components/wunderlist/__init__.py delete mode 100644 homeassistant/components/wunderlist/manifest.json delete mode 100644 homeassistant/components/wunderlist/services.yaml diff --git a/.coveragerc b/.coveragerc index 41398ac5f0e..9fd84e52be6 100644 --- a/.coveragerc +++ b/.coveragerc @@ -883,7 +883,6 @@ omit = homeassistant/components/wirelesstag/* homeassistant/components/worldtidesinfo/sensor.py homeassistant/components/worxlandroid/sensor.py - homeassistant/components/wunderlist/* homeassistant/components/x10/light.py homeassistant/components/xbox_live/sensor.py homeassistant/components/xeoma/camera.py diff --git a/homeassistant/components/wunderlist/__init__.py b/homeassistant/components/wunderlist/__init__.py deleted file mode 100644 index 30d665091b6..00000000000 --- a/homeassistant/components/wunderlist/__init__.py +++ /dev/null @@ -1,91 +0,0 @@ -"""Support to interact with Wunderlist.""" -import logging - -import voluptuous as vol -from wunderpy2 import WunderApi - -from homeassistant.const import CONF_ACCESS_TOKEN, CONF_CLIENT_ID, CONF_NAME -import homeassistant.helpers.config_validation as cv - -_LOGGER = logging.getLogger(__name__) - -DOMAIN = "wunderlist" - -CONF_LIST_NAME = "list_name" -CONF_STARRED = "starred" - - -CONFIG_SCHEMA = vol.Schema( - { - DOMAIN: vol.Schema( - { - vol.Required(CONF_CLIENT_ID): cv.string, - vol.Required(CONF_ACCESS_TOKEN): cv.string, - } - ) - }, - extra=vol.ALLOW_EXTRA, -) - - -SERVICE_CREATE_TASK = "create_task" - -SERVICE_SCHEMA_CREATE_TASK = vol.Schema( - { - vol.Required(CONF_LIST_NAME): cv.string, - vol.Required(CONF_NAME): cv.string, - vol.Optional(CONF_STARRED, default=False): cv.boolean, - } -) - - -def setup(hass, config): - """Set up the Wunderlist component.""" - conf = config[DOMAIN] - client_id = conf.get(CONF_CLIENT_ID) - access_token = conf.get(CONF_ACCESS_TOKEN) - data = Wunderlist(access_token, client_id) - if not data.check_credentials(): - _LOGGER.error("Invalid credentials") - return False - - hass.services.register( - DOMAIN, "create_task", data.create_task, schema=SERVICE_SCHEMA_CREATE_TASK - ) - return True - - -class Wunderlist: - """Representation of an interface to Wunderlist.""" - - def __init__(self, access_token, client_id): - """Create new instance of Wunderlist component.""" - api = WunderApi() - self._client = api.get_client(access_token, client_id) - - _LOGGER.debug("Instance created") - - def check_credentials(self): - """Check if the provided credentials are valid.""" - try: - self._client.get_lists() - return True - except ValueError: - return False - - def create_task(self, call): - """Create a new task on a list of Wunderlist.""" - list_name = call.data[CONF_LIST_NAME] - task_title = call.data[CONF_NAME] - starred = call.data[CONF_STARRED] - list_id = self._list_by_name(list_name) - self._client.create_task(list_id, task_title, starred=starred) - return True - - def _list_by_name(self, name): - """Return a list ID by name.""" - lists = self._client.get_lists() - tmp = [lst for lst in lists if lst["title"] == name] - if tmp: - return tmp[0]["id"] - return None diff --git a/homeassistant/components/wunderlist/manifest.json b/homeassistant/components/wunderlist/manifest.json deleted file mode 100644 index 414a5eb7d33..00000000000 --- a/homeassistant/components/wunderlist/manifest.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "domain": "wunderlist", - "name": "Wunderlist", - "documentation": "https://www.home-assistant.io/integrations/wunderlist", - "requirements": ["wunderpy2==0.1.6"], - "codeowners": [] -} diff --git a/homeassistant/components/wunderlist/services.yaml b/homeassistant/components/wunderlist/services.yaml deleted file mode 100644 index 1b824e43843..00000000000 --- a/homeassistant/components/wunderlist/services.yaml +++ /dev/null @@ -1,15 +0,0 @@ -# Describes the format for available Wunderlist - -create_task: - description: > - Create a new task in Wunderlist. - fields: - list_name: - description: name of the new list where the task will be created - example: "Shopping list" - name: - description: name of the new task - example: "Buy 5 bottles of beer" - starred: - description: Create the task as starred [Optional] - example: true diff --git a/requirements_all.txt b/requirements_all.txt index 92477ae5ea9..682ca8ed0f7 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2197,9 +2197,6 @@ withings-api==2.1.3 # homeassistant.components.wled wled==0.3.0 -# homeassistant.components.wunderlist -wunderpy2==0.1.6 - # homeassistant.components.xbee xbee-helper==0.0.7 From db8d4053d668e8c63b63e8d5c3260a1dd6fe013e Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Tue, 2 Jun 2020 20:11:23 +0200 Subject: [PATCH 310/406] Add supervisor get addon info helper (#36260) --- homeassistant/components/hassio/__init__.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/homeassistant/components/hassio/__init__.py b/homeassistant/components/hassio/__init__.py index bb4663a9654..0bd766589a1 100644 --- a/homeassistant/components/hassio/__init__.py +++ b/homeassistant/components/hassio/__init__.py @@ -17,6 +17,7 @@ from homeassistant.const import ( from homeassistant.core import DOMAIN as HASS_DOMAIN, callback from homeassistant.exceptions import HomeAssistantError import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.typing import HomeAssistantType from homeassistant.loader import bind_hass from homeassistant.util.dt import utcnow @@ -124,6 +125,21 @@ MAP_SERVICE_API = { } +@bind_hass +async def async_get_addon_info(hass: HomeAssistantType, addon_id: str) -> dict: + """Return add-on info. + + The addon_id is a snakecased concatenation of the 'repository' value + found in the add-on info and the 'slug' value found in the add-on config.json. + In the add-on info the addon_id is called 'slug'. + + The caller of the function should handle HassioAPIError. + """ + hassio = hass.data[DOMAIN] + result = await hassio.get_addon_info(addon_id) + return result["data"] + + @callback @bind_hass def get_homeassistant_version(hass): From 770b622a6ec2ecc61f46c9ae735249fe34f22f00 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Tue, 2 Jun 2020 20:12:19 +0200 Subject: [PATCH 311/406] Axis - Add device class property to binary sensors (#36384) * Add device class property to binary sensors * Update tests --- .../components/axis/binary_sensor.py | 28 +++++++++++++++++-- tests/components/axis/test_binary_sensor.py | 7 ++++- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/axis/binary_sensor.py b/homeassistant/components/axis/binary_sensor.py index 83ea325a9dd..c9e8436fdeb 100644 --- a/homeassistant/components/axis/binary_sensor.py +++ b/homeassistant/components/axis/binary_sensor.py @@ -2,9 +2,21 @@ from datetime import timedelta -from axis.event_stream import CLASS_INPUT, CLASS_OUTPUT +from axis.event_stream import ( + CLASS_INPUT, + CLASS_LIGHT, + CLASS_MOTION, + CLASS_OUTPUT, + CLASS_SOUND, +) -from homeassistant.components.binary_sensor import BinarySensorEntity +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_CONNECTIVITY, + DEVICE_CLASS_LIGHT, + DEVICE_CLASS_MOTION, + DEVICE_CLASS_SOUND, + BinarySensorEntity, +) from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.event import async_track_point_in_utc_time @@ -13,6 +25,13 @@ from homeassistant.util.dt import utcnow from .axis_base import AxisEventBase from .const import DOMAIN as AXIS_DOMAIN +DEVICE_CLASS = { + CLASS_INPUT: DEVICE_CLASS_CONNECTIVITY, + CLASS_LIGHT: DEVICE_CLASS_LIGHT, + CLASS_MOTION: DEVICE_CLASS_MOTION, + CLASS_SOUND: DEVICE_CLASS_SOUND, +} + async def async_setup_entry(hass, config_entry, async_add_entities): """Set up a Axis binary sensor.""" @@ -84,3 +103,8 @@ class AxisBinarySensor(AxisEventBase, BinarySensorEntity): ) return super().name + + @property + def device_class(self): + """Return the class of the sensor.""" + return DEVICE_CLASS.get(self.event.CLASS) diff --git a/tests/components/axis/test_binary_sensor.py b/tests/components/axis/test_binary_sensor.py index 6ff215f2488..1ffe37ef857 100644 --- a/tests/components/axis/test_binary_sensor.py +++ b/tests/components/axis/test_binary_sensor.py @@ -1,7 +1,10 @@ """Axis binary sensor platform tests.""" from homeassistant.components.axis.const import DOMAIN as AXIS_DOMAIN -from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_MOTION, + DOMAIN as BINARY_SENSOR_DOMAIN, +) from homeassistant.setup import async_setup_component from .test_device import NAME, setup_axis_integration @@ -58,7 +61,9 @@ async def test_binary_sensors(hass): pir = hass.states.get(f"binary_sensor.{NAME}_pir_0") assert pir.state == "off" assert pir.name == f"{NAME} PIR 0" + assert pir.attributes["device_class"] == DEVICE_CLASS_MOTION vmd4 = hass.states.get(f"binary_sensor.{NAME}_vmd4_camera1profile1") assert vmd4.state == "on" assert vmd4.name == f"{NAME} VMD4 Camera1Profile1" + assert vmd4.attributes["device_class"] == DEVICE_CLASS_MOTION From 7338feb659d62a238a793d526a8c50f798b4d0cf Mon Sep 17 00:00:00 2001 From: Marius <33354141+Mariusthvdb@users.noreply.github.com> Date: Tue, 2 Jun 2020 20:27:41 +0200 Subject: [PATCH 312/406] Add device_class to Stookalert (#34638) Co-authored-by: Franck Nijhof Co-authored-by: Franck Nijhof --- homeassistant/components/stookalert/binary_sensor.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/homeassistant/components/stookalert/binary_sensor.py b/homeassistant/components/stookalert/binary_sensor.py index a07f208ac9d..df0c1db369c 100644 --- a/homeassistant/components/stookalert/binary_sensor.py +++ b/homeassistant/components/stookalert/binary_sensor.py @@ -13,6 +13,7 @@ _LOGGER = logging.getLogger(__name__) SCAN_INTERVAL = timedelta(minutes=60) CONF_PROVINCE = "province" +DEFAULT_DEVICE_CLASS = "safety" DEFAULT_NAME = "Stookalert" ATTRIBUTION = "Data provided by rivm.nl" PROVINCES = [ @@ -74,6 +75,11 @@ class StookalertBinarySensor(BinarySensorEntity): """Return True if the Alert is active.""" return self._api_handler.state == 1 + @property + def device_class(self): + """Return the device class of this binary sensor.""" + return DEFAULT_DEVICE_CLASS + def update(self): """Update the data from the Stookalert handler.""" self._api_handler.get_alerts() From 578d4a9b6ad66ebbb6949904ee956d75f2e597e5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 2 Jun 2020 13:54:11 -0500 Subject: [PATCH 313/406] Make the frontend available sooner (Part 1 of 2) (#36263) * Part 1 of 2 (no breaking changes in part 1). When integrations configured via the UI block startup or fail to start, the webserver can remain offline which make it is impossible to recover without manually changing files in .storage since the UI is not available. This change is the foundation that part 2 will build on and enable a listener to start the webserver when the frontend is finished loading. Frontend Changes (home-assistant/frontend#6068) * Address review comments * bump timeout to 1800s, adjust comment * bump timeout to 4h * remove timeout failsafe * and the test --- homeassistant/bootstrap.py | 9 +++ homeassistant/components/http/__init__.py | 49 +++++++++------ homeassistant/components/http/view.py | 6 +- .../components/websocket_api/decorators.py | 5 +- .../components/websocket_api/http.py | 4 +- homeassistant/core.py | 7 +++ tests/components/hassio/test_discovery.py | 5 +- tests/components/http/test_data_validator.py | 2 +- tests/components/http/test_view.py | 16 ++++- tests/components/logbook/test_init.py | 3 +- tests/components/panel_iframe/test_init.py | 63 ++++++++++--------- tests/test_bootstrap.py | 14 +++-- tests/test_core.py | 5 +- 13 files changed, 126 insertions(+), 62 deletions(-) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 596b0250de6..a4c5fa14fa8 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -44,6 +44,13 @@ STAGE_1_INTEGRATIONS = { "mqtt_eventstream", # To provide account link implementations "cloud", + # Ensure supervisor is available + "hassio", + # Get the frontend up and running as soon + # as possible so problem integrations can + # be removed + "frontend", + "config", } @@ -399,6 +406,8 @@ async def _async_set_up_integrations( ) if stage_1_domains: + _LOGGER.info("Setting up %s", stage_1_domains) + await async_setup_multi_components(stage_1_domains) # Load all integrations diff --git a/homeassistant/components/http/__init__.py b/homeassistant/components/http/__init__.py index 42d388ffa85..e06ceb087c2 100644 --- a/homeassistant/components/http/__init__.py +++ b/homeassistant/components/http/__init__.py @@ -4,7 +4,7 @@ import logging import os import ssl from traceback import extract_stack -from typing import Optional, cast +from typing import Dict, Optional, cast from aiohttp import web from aiohttp.web_exceptions import HTTPMovedPermanently @@ -15,7 +15,7 @@ from homeassistant.const import ( EVENT_HOMEASSISTANT_STOP, SERVER_PORT, ) -from homeassistant.core import HomeAssistant +from homeassistant.core import Event, HomeAssistant from homeassistant.helpers import storage import homeassistant.helpers.config_validation as cv from homeassistant.loader import bind_hass @@ -216,29 +216,25 @@ async def async_setup(hass, config): ssl_profile=ssl_profile, ) - async def stop_server(event): + startup_listeners = [] + + async def stop_server(event: Event) -> None: """Stop the server.""" await server.stop() - async def start_server(event): + async def start_server(event: Event) -> None: """Start the server.""" + + for listener in startup_listeners: + listener() + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_server) - await server.start() - # If we are set up successful, we store the HTTP settings for safe mode. - store = storage.Store(hass, STORAGE_VERSION, STORAGE_KEY) + await start_http_server_and_save_config(hass, dict(conf), server) - if CONF_TRUSTED_PROXIES in conf: - conf_to_save = dict(conf) - conf_to_save[CONF_TRUSTED_PROXIES] = [ - str(ip.network_address) for ip in conf_to_save[CONF_TRUSTED_PROXIES] - ] - else: - conf_to_save = conf - - await store.async_save(conf_to_save) - - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, start_server) + startup_listeners.append( + hass.bus.async_listen(EVENT_HOMEASSISTANT_START, start_server) + ) hass.http = server @@ -418,3 +414,20 @@ class HomeAssistantHTTP: """Stop the aiohttp server.""" await self.site.stop() await self.runner.cleanup() + + +async def start_http_server_and_save_config( + hass: HomeAssistant, conf: Dict, server: HomeAssistantHTTP +) -> None: + """Startup the http server and save the config.""" + await server.start() # type: ignore + + # If we are set up successful, we store the HTTP settings for safe mode. + store = storage.Store(hass, STORAGE_VERSION, STORAGE_KEY) + + if CONF_TRUSTED_PROXIES in conf: + conf[CONF_TRUSTED_PROXIES] = [ + str(ip.network_address) for ip in conf[CONF_TRUSTED_PROXIES] + ] + + await store.async_save(conf) diff --git a/homeassistant/components/http/view.py b/homeassistant/components/http/view.py index 701c497d88c..eb6c757384e 100644 --- a/homeassistant/components/http/view.py +++ b/homeassistant/components/http/view.py @@ -14,7 +14,7 @@ from aiohttp.web_exceptions import ( import voluptuous as vol from homeassistant import exceptions -from homeassistant.const import CONTENT_TYPE_JSON, HTTP_OK +from homeassistant.const import CONTENT_TYPE_JSON, HTTP_OK, HTTP_SERVICE_UNAVAILABLE from homeassistant.core import Context, is_callback from homeassistant.helpers.json import JSONEncoder @@ -107,8 +107,8 @@ def request_handler_factory(view: HomeAssistantView, handler: Callable) -> Calla async def handle(request: web.Request) -> web.StreamResponse: """Handle incoming request.""" - if not request.app[KEY_HASS].is_running: - return web.Response(status=503) + if request.app[KEY_HASS].is_stopping: + return web.Response(status=HTTP_SERVICE_UNAVAILABLE) authenticated = request.get(KEY_AUTHENTICATED, False) diff --git a/homeassistant/components/websocket_api/decorators.py b/homeassistant/components/websocket_api/decorators.py index 87b5d5baf92..d4a4cff1a8f 100644 --- a/homeassistant/components/websocket_api/decorators.py +++ b/homeassistant/components/websocket_api/decorators.py @@ -1,4 +1,5 @@ """Decorators for the Websocket API.""" +import asyncio from functools import wraps import logging from typing import Awaitable, Callable @@ -31,7 +32,9 @@ def async_response( @wraps(func) def schedule_handler(hass, connection, msg): """Schedule the handler.""" - hass.async_create_task(_handle_async_response(func, hass, connection, msg)) + # As the webserver is now started before the start + # event we do not want to block for websocket responders + asyncio.create_task(_handle_async_response(func, hass, connection, msg)) return schedule_handler diff --git a/homeassistant/components/websocket_api/http.py b/homeassistant/components/websocket_api/http.py index e20e53d139a..ab412e06583 100644 --- a/homeassistant/components/websocket_api/http.py +++ b/homeassistant/components/websocket_api/http.py @@ -165,7 +165,9 @@ class WebSocketHandler: EVENT_HOMEASSISTANT_STOP, handle_hass_stop ) - self._writer_task = self.hass.async_create_task(self._writer()) + # As the webserver is now started before the start + # event we do not want to block for websocket responses + self._writer_task = asyncio.create_task(self._writer()) auth = AuthPhase(self._logger, self.hass, self._send_message, request) connection = None diff --git a/homeassistant/core.py b/homeassistant/core.py index 34df648a4df..eb7457daecb 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -209,6 +209,11 @@ class HomeAssistant: """Return if Home Assistant is running.""" return self.state in (CoreState.starting, CoreState.running) + @property + def is_stopping(self) -> bool: + """Return if Home Assistant is stopping.""" + return self.state in (CoreState.stopping, CoreState.final_write) + def start(self) -> int: """Start Home Assistant. @@ -260,6 +265,7 @@ class HomeAssistant: setattr(self.loop, "_thread_ident", threading.get_ident()) self.bus.async_fire(EVENT_HOMEASSISTANT_START) + self.bus.async_fire(EVENT_CORE_CONFIG_UPDATE) try: # Only block for EVENT_HOMEASSISTANT_START listener @@ -1391,6 +1397,7 @@ class Config: "version": __version__, "config_source": self.config_source, "safe_mode": self.safe_mode, + "state": self.hass.state.value, "external_url": self.external_url, "internal_url": self.internal_url, } diff --git a/tests/components/hassio/test_discovery.py b/tests/components/hassio/test_discovery.py index 845c60c2f85..9d148745f18 100644 --- a/tests/components/hassio/test_discovery.py +++ b/tests/components/hassio/test_discovery.py @@ -60,6 +60,9 @@ async def test_hassio_discovery_startup(hass, aioclient_mock, hassio_client): async def test_hassio_discovery_startup_done(hass, aioclient_mock, hassio_client): """Test startup and discovery with hass discovery.""" + aioclient_mock.post( + "http://127.0.0.1/supervisor/options", json={"result": "ok", "data": {}}, + ) aioclient_mock.get( "http://127.0.0.1/discovery", json={ @@ -101,7 +104,7 @@ async def test_hassio_discovery_startup_done(hass, aioclient_mock, hassio_client await async_setup_component(hass, "hassio", {}) await hass.async_block_till_done() - assert aioclient_mock.call_count == 2 + assert aioclient_mock.call_count == 3 assert mock_mqtt.called mock_mqtt.assert_called_with( { diff --git a/tests/components/http/test_data_validator.py b/tests/components/http/test_data_validator.py index b0a14a31bc5..c7b5ed42ccd 100644 --- a/tests/components/http/test_data_validator.py +++ b/tests/components/http/test_data_validator.py @@ -11,7 +11,7 @@ from tests.async_mock import Mock async def get_client(aiohttp_client, validator): """Generate a client that hits a view decorated with validator.""" app = web.Application() - app["hass"] = Mock(is_running=True) + app["hass"] = Mock(is_stopping=False) class TestView(HomeAssistantView): url = "/" diff --git a/tests/components/http/test_view.py b/tests/components/http/test_view.py index a6e4bdc12c8..045f0837983 100644 --- a/tests/components/http/test_view.py +++ b/tests/components/http/test_view.py @@ -19,7 +19,13 @@ from tests.async_mock import AsyncMock, Mock @pytest.fixture def mock_request(): """Mock a request.""" - return Mock(app={"hass": Mock(is_running=True)}, match_info={}) + return Mock(app={"hass": Mock(is_stopping=False)}, match_info={}) + + +@pytest.fixture +def mock_request_with_stopping(): + """Mock a request.""" + return Mock(app={"hass": Mock(is_stopping=True)}, match_info={}) async def test_invalid_json(caplog): @@ -55,3 +61,11 @@ async def test_handling_service_not_found(mock_request): Mock(requires_auth=False), AsyncMock(side_effect=ServiceNotFound("test", "test")), )(mock_request) + + +async def test_not_running(mock_request_with_stopping): + """Test we get a 503 when not running.""" + response = await request_handler_factory( + Mock(requires_auth=False), AsyncMock(side_effect=Unauthorized) + )(mock_request_with_stopping) + assert response.status == 503 diff --git a/tests/components/logbook/test_init.py b/tests/components/logbook/test_init.py index 27d39446fa3..e1341e64e92 100644 --- a/tests/components/logbook/test_init.py +++ b/tests/components/logbook/test_init.py @@ -42,7 +42,8 @@ class TestComponentLogbook(unittest.TestCase): """Set up things to be run when tests are started.""" self.hass = get_test_home_assistant() init_recorder_component(self.hass) # Force an in memory DB - assert setup_component(self.hass, logbook.DOMAIN, self.EMPTY_CONFIG) + with patch("homeassistant.components.http.start_http_server_and_save_config"): + assert setup_component(self.hass, logbook.DOMAIN, self.EMPTY_CONFIG) def tearDown(self): """Stop everything that was started.""" diff --git a/tests/components/panel_iframe/test_init.py b/tests/components/panel_iframe/test_init.py index d586c4c199e..b38f3c4b1fa 100644 --- a/tests/components/panel_iframe/test_init.py +++ b/tests/components/panel_iframe/test_init.py @@ -4,6 +4,7 @@ import unittest from homeassistant import setup from homeassistant.components import frontend +from tests.async_mock import patch from tests.common import get_test_home_assistant @@ -26,38 +27,42 @@ class TestPanelIframe(unittest.TestCase): ] for conf in to_try: - assert not setup.setup_component( - self.hass, "panel_iframe", {"panel_iframe": conf} - ) + with patch( + "homeassistant.components.http.start_http_server_and_save_config" + ): + assert not setup.setup_component( + self.hass, "panel_iframe", {"panel_iframe": conf} + ) def test_correct_config(self): """Test correct config.""" - assert setup.setup_component( - self.hass, - "panel_iframe", - { - "panel_iframe": { - "router": { - "icon": "mdi:network-wireless", - "title": "Router", - "url": "http://192.168.1.1", - "require_admin": True, - }, - "weather": { - "icon": "mdi:weather", - "title": "Weather", - "url": "https://www.wunderground.com/us/ca/san-diego", - "require_admin": True, - }, - "api": {"icon": "mdi:weather", "title": "Api", "url": "/api"}, - "ftp": { - "icon": "mdi:weather", - "title": "FTP", - "url": "ftp://some/ftp", - }, - } - }, - ) + with patch("homeassistant.components.http.start_http_server_and_save_config"): + assert setup.setup_component( + self.hass, + "panel_iframe", + { + "panel_iframe": { + "router": { + "icon": "mdi:network-wireless", + "title": "Router", + "url": "http://192.168.1.1", + "require_admin": True, + }, + "weather": { + "icon": "mdi:weather", + "title": "Weather", + "url": "https://www.wunderground.com/us/ca/san-diego", + "require_admin": True, + }, + "api": {"icon": "mdi:weather", "title": "Api", "url": "/api"}, + "ftp": { + "icon": "mdi:weather", + "title": "FTP", + "url": "ftp://some/ftp", + }, + } + }, + ) panels = self.hass.data[frontend.DATA_PANELS] diff --git a/tests/test_bootstrap.py b/tests/test_bootstrap.py index 9597dfa60b8..3ea42d4545b 100644 --- a/tests/test_bootstrap.py +++ b/tests/test_bootstrap.py @@ -261,7 +261,9 @@ async def test_setup_hass( with patch( "homeassistant.config.async_hass_config_yaml", return_value={"browser": {}, "frontend": {}}, - ), patch.object(bootstrap, "LOG_SLOW_STARTUP_INTERVAL", 5000): + ), patch.object(bootstrap, "LOG_SLOW_STARTUP_INTERVAL", 5000), patch( + "homeassistant.components.http.start_http_server_and_save_config" + ): hass = await bootstrap.async_setup_hass( config_dir=get_test_config_dir(), verbose=verbose, @@ -338,7 +340,7 @@ async def test_setup_hass_invalid_yaml( """Test it works.""" with patch( "homeassistant.config.async_hass_config_yaml", side_effect=HomeAssistantError - ): + ), patch("homeassistant.components.http.start_http_server_and_save_config"): hass = await bootstrap.async_setup_hass( config_dir=get_test_config_dir(), verbose=False, @@ -391,7 +393,9 @@ async def test_setup_hass_safe_mode( hass.config_entries._async_schedule_save() await flush_store(hass.config_entries._store) - with patch("homeassistant.components.browser.setup") as browser_setup: + with patch("homeassistant.components.browser.setup") as browser_setup, patch( + "homeassistant.components.http.start_http_server_and_save_config" + ): hass = await bootstrap.async_setup_hass( config_dir=get_test_config_dir(), verbose=False, @@ -421,7 +425,7 @@ async def test_setup_hass_invalid_core_config( with patch( "homeassistant.config.async_hass_config_yaml", return_value={"homeassistant": {"non-existing": 1}}, - ): + ), patch("homeassistant.components.http.start_http_server_and_save_config"): hass = await bootstrap.async_setup_hass( config_dir=get_test_config_dir(), verbose=False, @@ -451,7 +455,7 @@ async def test_setup_safe_mode_if_no_frontend( with patch( "homeassistant.config.async_hass_config_yaml", return_value={"map": {}, "person": {"invalid": True}}, - ): + ), patch("homeassistant.components.http.start_http_server_and_save_config"): hass = await bootstrap.async_setup_hass( config_dir=get_test_config_dir(), verbose=verbose, diff --git a/tests/test_core.py b/tests/test_core.py index 3bc001b78b6..9fc257eaf2d 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -35,7 +35,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, Mock, patch +from tests.async_mock import MagicMock, Mock, PropertyMock, patch from tests.common import async_mock_service, get_test_home_assistant PST = pytz.timezone("America/Los_Angeles") @@ -901,6 +901,8 @@ class TestConfig(unittest.TestCase): def test_as_dict(self): """Test as dict.""" self.config.config_dir = "/test/ha-config" + self.config.hass = MagicMock() + type(self.config.hass.state).value = PropertyMock(return_value="RUNNING") expected = { "latitude": 0, "longitude": 0, @@ -914,6 +916,7 @@ class TestConfig(unittest.TestCase): "version": __version__, "config_source": "default", "safe_mode": False, + "state": "RUNNING", "external_url": None, "internal_url": None, } From 7722e417ad61689cb07825d31adabcddc18c475c Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 2 Jun 2020 21:22:08 +0200 Subject: [PATCH 314/406] Stable device id when a deleted device is restored (#36309) * Stable device id when a deleted device is restored. * Tweak * Store only basic data for deleted devices * Simplify code * Simplify code * Address review comments. * Improve test * Fix missing save --- homeassistant/helpers/device_registry.py | 80 +++++++- tests/common.py | 3 +- tests/helpers/test_device_registry.py | 242 ++++++++++++++++++++++- 3 files changed, 321 insertions(+), 4 deletions(-) diff --git a/homeassistant/helpers/device_registry.py b/homeassistant/helpers/device_registry.py index 2e91d0d6622..aeee9e802c9 100644 --- a/homeassistant/helpers/device_registry.py +++ b/homeassistant/helpers/device_registry.py @@ -33,6 +33,26 @@ CONNECTION_UPNP = "upnp" CONNECTION_ZIGBEE = "zigbee" +@attr.s(slots=True, frozen=True) +class DeletedDeviceEntry: + """Deleted Device Registry Entry.""" + + config_entries: Set[str] = attr.ib() + connections: Set[Tuple[str, str]] = attr.ib() + identifiers: Set[Tuple[str, str]] = attr.ib() + id: str = attr.ib() + + def to_device_entry(self): + """Create DeviceEntry from DeletedDeviceEntry.""" + return DeviceEntry( + config_entries=self.config_entries, + connections=self.connections, + identifiers=self.identifiers, + id=self.id, + is_new=True, + ) + + @attr.s(slots=True, frozen=True) class DeviceEntry: """Device Registry Entry.""" @@ -81,6 +101,7 @@ class DeviceRegistry: """Class to hold a registry of devices.""" devices: Dict[str, DeviceEntry] + deleted_devices: Dict[str, DeletedDeviceEntry] def __init__(self, hass: HomeAssistantType) -> None: """Initialize the device registry.""" @@ -104,6 +125,18 @@ class DeviceRegistry: return device return None + @callback + def _async_get_deleted_device( + self, identifiers: set, connections: set + ) -> Optional[DeletedDeviceEntry]: + """Check if device has previously been registered.""" + for device in self.deleted_devices.values(): + if any(iden in device.identifiers for iden in identifiers) or any( + conn in device.connections for conn in connections + ): + return device + return None + @callback def async_get_or_create( self, @@ -136,7 +169,12 @@ class DeviceRegistry: device = self.async_get_device(identifiers, connections) if device is None: - device = DeviceEntry(is_new=True) + deleted_device = self._async_get_deleted_device(identifiers, connections) + if deleted_device is None: + device = DeviceEntry(is_new=True) + else: + self.deleted_devices.pop(deleted_device.id) + device = deleted_device.to_device_entry() self.devices[device.id] = device if via_device is not None: @@ -283,7 +321,13 @@ class DeviceRegistry: @callback def async_remove_device(self, device_id: str) -> None: """Remove a device from the device registry.""" - del self.devices[device_id] + device = self.devices.pop(device_id) + self.deleted_devices[device_id] = DeletedDeviceEntry( + config_entries=device.config_entries, + connections=device.connections, + identifiers=device.identifiers, + id=device.id, + ) self.hass.bus.async_fire( EVENT_DEVICE_REGISTRY_UPDATED, {"action": "remove", "device_id": device_id} ) @@ -296,6 +340,7 @@ class DeviceRegistry: data = await self._store.async_load() devices = OrderedDict() + deleted_devices = OrderedDict() if data is not None: for device in data["devices"]: @@ -319,8 +364,17 @@ class DeviceRegistry: area_id=device.get("area_id"), name_by_user=device.get("name_by_user"), ) + # Introduced in 0.111 + for device in data.get("deleted_devices", []): + deleted_devices[device["id"]] = DeletedDeviceEntry( + config_entries=set(device["config_entries"]), + connections={tuple(conn) for conn in device["connections"]}, + identifiers={tuple(iden) for iden in device["identifiers"]}, + id=device["id"], + ) self.devices = devices + self.deleted_devices = deleted_devices @callback def async_schedule_save(self) -> None: @@ -349,6 +403,15 @@ class DeviceRegistry: } for entry in self.devices.values() ] + data["deleted_devices"] = [ + { + "config_entries": list(entry.config_entries), + "connections": list(entry.connections), + "identifiers": list(entry.identifiers), + "id": entry.id, + } + for entry in self.deleted_devices.values() + ] return data @@ -357,6 +420,19 @@ class DeviceRegistry: """Clear config entry from registry entries.""" for device in list(self.devices.values()): self._async_update_device(device.id, remove_config_entry_id=config_entry_id) + for deleted_device in list(self.deleted_devices.values()): + config_entries = deleted_device.config_entries + if config_entry_id not in config_entries: + continue + if config_entries == {config_entry_id}: + # Permanently remove the device from the device registry. + del self.deleted_devices[deleted_device.id] + else: + config_entries = config_entries - {config_entry_id} + self.deleted_devices[deleted_device.id] = attr.evolve( + deleted_device, config_entries=config_entries + ) + self.async_schedule_save() @callback def async_clear_area_id(self, area_id: str) -> None: diff --git a/tests/common.py b/tests/common.py index d6ae25adb5e..2136de3584f 100644 --- a/tests/common.py +++ b/tests/common.py @@ -381,10 +381,11 @@ def mock_area_registry(hass, mock_entries=None): return registry -def mock_device_registry(hass, mock_entries=None): +def mock_device_registry(hass, mock_entries=None, mock_deleted_entries=None): """Mock the Device Registry.""" registry = device_registry.DeviceRegistry(hass) registry.devices = mock_entries or OrderedDict() + registry.deleted_devices = mock_deleted_entries or OrderedDict() hass.data[device_registry.DATA_REGISTRY] = registry return registry diff --git a/tests/helpers/test_device_registry.py b/tests/helpers/test_device_registry.py index 3fbb73a2aa8..82fadc35dd2 100644 --- a/tests/helpers/test_device_registry.py +++ b/tests/helpers/test_device_registry.py @@ -153,11 +153,21 @@ async def test_loading_from_storage(hass, hass_storage): "area_id": "12345A", "name_by_user": "Test Friendly Name", } - ] + ], + "deleted_devices": [ + { + "config_entries": ["1234"], + "connections": [["Zigbee", "23.45.67.89.01"]], + "id": "bcdefghijklmn", + "identifiers": [["serial", "34:56:AB:CD:EF:12"]], + } + ], }, } registry = await device_registry.async_get_registry(hass) + assert len(registry.devices) == 1 + assert len(registry.deleted_devices) == 1 entry = registry.async_get_or_create( config_entry_id="1234", @@ -171,6 +181,20 @@ async def test_loading_from_storage(hass, hass_storage): assert entry.name_by_user == "Test Friendly Name" assert entry.entry_type == "service" assert isinstance(entry.config_entries, set) + assert isinstance(entry.connections, set) + assert isinstance(entry.identifiers, set) + + entry = registry.async_get_or_create( + config_entry_id="1234", + connections={("Zigbee", "23.45.67.89.01")}, + identifiers={("serial", "34:56:AB:CD:EF:12")}, + manufacturer="manufacturer", + model="model", + ) + assert entry.id == "bcdefghijklmn" + assert isinstance(entry.config_entries, set) + assert isinstance(entry.connections, set) + assert isinstance(entry.identifiers, set) async def test_removing_config_entries(hass, registry, update_events): @@ -224,6 +248,79 @@ async def test_removing_config_entries(hass, registry, update_events): assert update_events[4]["device_id"] == entry3.id +async def test_deleted_device_removing_config_entries(hass, registry, update_events): + """Make sure we do not get duplicate entries.""" + entry = registry.async_get_or_create( + config_entry_id="123", + connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, + identifiers={("bridgeid", "0123")}, + manufacturer="manufacturer", + model="model", + ) + entry2 = registry.async_get_or_create( + config_entry_id="456", + connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, + identifiers={("bridgeid", "0123")}, + manufacturer="manufacturer", + model="model", + ) + entry3 = registry.async_get_or_create( + config_entry_id="123", + connections={(device_registry.CONNECTION_NETWORK_MAC, "34:56:78:CD:EF:12")}, + identifiers={("bridgeid", "4567")}, + manufacturer="manufacturer", + model="model", + ) + + assert len(registry.devices) == 2 + assert len(registry.deleted_devices) == 0 + assert entry.id == entry2.id + assert entry.id != entry3.id + assert entry2.config_entries == {"123", "456"} + + registry.async_remove_device(entry.id) + registry.async_remove_device(entry3.id) + + assert len(registry.devices) == 0 + assert len(registry.deleted_devices) == 2 + + await hass.async_block_till_done() + assert len(update_events) == 5 + assert update_events[0]["action"] == "create" + assert update_events[0]["device_id"] == entry.id + assert update_events[1]["action"] == "update" + assert update_events[1]["device_id"] == entry2.id + assert update_events[2]["action"] == "create" + assert update_events[2]["device_id"] == entry3.id + assert update_events[3]["action"] == "remove" + assert update_events[3]["device_id"] == entry.id + assert update_events[4]["action"] == "remove" + assert update_events[4]["device_id"] == entry3.id + + registry.async_clear_config_entry("123") + assert len(registry.devices) == 0 + assert len(registry.deleted_devices) == 1 + + registry.async_clear_config_entry("456") + assert len(registry.devices) == 0 + assert len(registry.deleted_devices) == 0 + + # No event when a deleted device is purged + await hass.async_block_till_done() + assert len(update_events) == 5 + + # Re-add, expect new device id + entry2 = registry.async_get_or_create( + config_entry_id="123", + connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, + identifiers={("bridgeid", "0123")}, + manufacturer="manufacturer", + model="model", + ) + + assert entry.id != entry2.id + + async def test_removing_area_id(registry): """Make sure we can clear area id.""" entry = registry.async_get_or_create( @@ -243,6 +340,36 @@ async def test_removing_area_id(registry): assert entry_w_area != entry_wo_area +async def test_deleted_device_removing_area_id(registry): + """Make sure we can clear area id of deleted device.""" + entry = registry.async_get_or_create( + config_entry_id="123", + connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, + identifiers={("bridgeid", "0123")}, + manufacturer="manufacturer", + model="model", + ) + + entry_w_area = registry.async_update_device(entry.id, area_id="12345A") + + registry.async_remove_device(entry.id) + registry.async_clear_area_id("12345A") + + entry2 = registry.async_get_or_create( + config_entry_id="123", + connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, + identifiers={("bridgeid", "0123")}, + manufacturer="manufacturer", + model="model", + ) + assert entry.id == entry2.id + + entry_wo_area = registry.async_get_device({("bridgeid", "0123")}, set()) + + assert not entry_wo_area.area_id + assert entry_w_area != entry_wo_area + + async def test_specifying_via_device_create(registry): """Test specifying a via_device and updating.""" via = registry.async_get_or_create( @@ -320,7 +447,19 @@ async def test_loading_saving_data(hass, registry): via_device=("hue", "0123"), ) + orig_light2 = registry.async_get_or_create( + config_entry_id="456", + connections=set(), + identifiers={("hue", "789")}, + manufacturer="manufacturer", + model="light", + via_device=("hue", "0123"), + ) + + registry.async_remove_device(orig_light2.id) + assert len(registry.devices) == 2 + assert len(registry.deleted_devices) == 1 orig_via = registry.async_update_device( orig_via.id, area_id="mock-area-id", name_by_user="mock-name-by-user" @@ -333,6 +472,7 @@ async def test_loading_saving_data(hass, registry): # Ensure same order assert list(registry.devices) == list(registry2.devices) + assert list(registry.deleted_devices) == list(registry2.deleted_devices) new_via = registry2.async_get_device({("hue", "0123")}, set()) new_light = registry2.async_get_device({("hue", "456")}, set()) @@ -584,3 +724,103 @@ async def test_cleanup_entity_registry_change(hass): ent_reg.async_remove(entity.entity_id) await hass.async_block_till_done() assert len(mock_call.mock_calls) == 2 + + +async def test_restore_device(hass, registry, update_events): + """Make sure device id is stable.""" + entry = registry.async_get_or_create( + config_entry_id="123", + connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, + identifiers={("bridgeid", "0123")}, + manufacturer="manufacturer", + model="model", + ) + + assert len(registry.devices) == 1 + assert len(registry.deleted_devices) == 0 + + registry.async_remove_device(entry.id) + + assert len(registry.devices) == 0 + assert len(registry.deleted_devices) == 1 + + entry2 = registry.async_get_or_create( + config_entry_id="123", + connections={(device_registry.CONNECTION_NETWORK_MAC, "34:56:78:CD:EF:12")}, + identifiers={("bridgeid", "4567")}, + manufacturer="manufacturer", + model="model", + ) + entry3 = registry.async_get_or_create( + config_entry_id="123", + connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, + identifiers={("bridgeid", "0123")}, + manufacturer="manufacturer", + model="model", + ) + + assert entry.id == entry3.id + assert entry.id != entry2.id + assert len(registry.devices) == 2 + assert len(registry.deleted_devices) == 0 + + assert isinstance(entry3.config_entries, set) + assert isinstance(entry3.connections, set) + assert isinstance(entry3.identifiers, set) + + await hass.async_block_till_done() + + assert len(update_events) == 4 + assert update_events[0]["action"] == "create" + assert update_events[0]["device_id"] == entry.id + assert update_events[1]["action"] == "remove" + assert update_events[1]["device_id"] == entry.id + assert update_events[2]["action"] == "create" + assert update_events[2]["device_id"] == entry2.id + assert update_events[3]["action"] == "create" + assert update_events[3]["device_id"] == entry3.id + + +async def test_restore_simple_device(hass, registry, update_events): + """Make sure device id is stable.""" + entry = registry.async_get_or_create( + config_entry_id="123", + connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, + identifiers={("bridgeid", "0123")}, + ) + + assert len(registry.devices) == 1 + assert len(registry.deleted_devices) == 0 + + registry.async_remove_device(entry.id) + + assert len(registry.devices) == 0 + assert len(registry.deleted_devices) == 1 + + entry2 = registry.async_get_or_create( + config_entry_id="123", + connections={(device_registry.CONNECTION_NETWORK_MAC, "34:56:78:CD:EF:12")}, + identifiers={("bridgeid", "4567")}, + ) + entry3 = registry.async_get_or_create( + config_entry_id="123", + connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, + identifiers={("bridgeid", "0123")}, + ) + + assert entry.id == entry3.id + assert entry.id != entry2.id + assert len(registry.devices) == 2 + assert len(registry.deleted_devices) == 0 + + await hass.async_block_till_done() + + assert len(update_events) == 4 + assert update_events[0]["action"] == "create" + assert update_events[0]["device_id"] == entry.id + assert update_events[1]["action"] == "remove" + assert update_events[1]["device_id"] == entry.id + assert update_events[2]["action"] == "create" + assert update_events[2]["device_id"] == entry2.id + assert update_events[3]["action"] == "create" + assert update_events[3]["device_id"] == entry3.id From 1771fbbd342ef9578291379ed7c4635f3719e888 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 2 Jun 2020 21:26:31 +0200 Subject: [PATCH 315/406] Upgrade pytest to 5.4.3 (#36385) --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 9a8e7ab4510..60d085752ed 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -16,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.4.2 +pytest==5.4.3 requests_mock==1.8.0 responses==0.10.6 From 61c08e792d23b3d8a5cf868683a9a6cb93a5a821 Mon Sep 17 00:00:00 2001 From: Tom Date: Tue, 2 Jun 2020 21:45:14 +0200 Subject: [PATCH 316/406] Update plugwise to async and config_flow switch part (#36383) * Add switch component * Update homeassistant/components/plugwise/switch.py Co-authored-by: J. Nick Koston * Update homeassistant/components/plugwise/switch.py Co-authored-by: J. Nick Koston * Improvements by @bdraco Co-authored-by: J. Nick Koston --- .coveragerc | 1 + homeassistant/components/plugwise/__init__.py | 2 +- homeassistant/components/plugwise/switch.py | 84 +++++++++++++++++++ 3 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/plugwise/switch.py diff --git a/.coveragerc b/.coveragerc index 9fd84e52be6..54120f305f4 100644 --- a/.coveragerc +++ b/.coveragerc @@ -607,6 +607,7 @@ omit = homeassistant/components/plugwise/binary_sensor.py homeassistant/components/plugwise/climate.py homeassistant/components/plugwise/sensor.py + homeassistant/components/plugwise/switch.py homeassistant/components/plum_lightpad/* homeassistant/components/pocketcasts/sensor.py homeassistant/components/point/* diff --git a/homeassistant/components/plugwise/__init__.py b/homeassistant/components/plugwise/__init__.py index abcb614253d..a0b98f9d1c0 100644 --- a/homeassistant/components/plugwise/__init__.py +++ b/homeassistant/components/plugwise/__init__.py @@ -24,7 +24,7 @@ CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.Schema({})}, extra=vol.ALLOW_EXTRA) _LOGGER = logging.getLogger(__name__) SENSOR_PLATFORMS = ["sensor"] -ALL_PLATFORMS = ["binary_sensor", "climate", "sensor"] +ALL_PLATFORMS = ["binary_sensor", "climate", "sensor", "switch"] async def async_setup(hass: HomeAssistant, config: dict): diff --git a/homeassistant/components/plugwise/switch.py b/homeassistant/components/plugwise/switch.py new file mode 100644 index 00000000000..50b704e36ac --- /dev/null +++ b/homeassistant/components/plugwise/switch.py @@ -0,0 +1,84 @@ +"""Plugwise Switch component for HomeAssistant.""" + +import logging + +from Plugwise_Smile.Smile import Smile + +from homeassistant.components.switch import SwitchEntity +from homeassistant.core import callback + +from . import SmileGateway +from .const import DOMAIN + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up the Smile switches from a config entry.""" + api = hass.data[DOMAIN][config_entry.entry_id]["api"] + coordinator = hass.data[DOMAIN][config_entry.entry_id]["coordinator"] + + entities = [] + all_devices = api.get_all_devices() + for dev_id, device_properties in all_devices.items(): + if "plug" in device_properties["types"]: + model = "Metered Switch" + entities.append( + PwSwitch(api, coordinator, device_properties["name"], dev_id, model) + ) + + async_add_entities(entities, True) + + +class PwSwitch(SmileGateway, SwitchEntity): + """Representation of a Plugwise plug.""" + + def __init__(self, api, coordinator, name, dev_id, model): + """Set up the Plugwise API.""" + super().__init__(api, coordinator, name, dev_id) + + self._model = model + + self._is_on = False + + self._unique_id = f"{dev_id}-plug" + + @property + def is_on(self): + """Return true if device is on.""" + return self._is_on + + async def async_turn_on(self, **kwargs): + """Turn the device on.""" + try: + if await self._api.set_relay_state(self._dev_id, "on"): + self._is_on = True + self.async_write_ha_state() + except Smile.PlugwiseError: + _LOGGER.error("Error while communicating to device") + + async def async_turn_off(self, **kwargs): + """Turn the device off.""" + try: + if await self._api.set_relay_state(self._dev_id, "off"): + self._is_on = False + self.async_write_ha_state() + except Smile.PlugwiseError: + _LOGGER.error("Error while communicating to device") + + @callback + def _async_process_data(self): + """Update the data from the Plugs.""" + _LOGGER.debug("Update switch called") + + data = self._api.get_device_data(self._dev_id) + + if not data: + _LOGGER.error("Received no data for device %s.", self._name) + self.async_write_ha_state() + return + + if "relay" in data: + self._is_on = data["relay"] + + self.async_write_ha_state() From acfd907c50fc54a4aad7f1166a98fd6ddb46ef84 Mon Sep 17 00:00:00 2001 From: Martin Weinelt Date: Tue, 2 Jun 2020 22:47:49 +0200 Subject: [PATCH 317/406] Expose switch attributes in Prometheus component (#35216) --- homeassistant/components/prometheus/__init__.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/homeassistant/components/prometheus/__init__.py b/homeassistant/components/prometheus/__init__.py index c09c20ec3b0..d31718d9d2c 100644 --- a/homeassistant/components/prometheus/__init__.py +++ b/homeassistant/components/prometheus/__init__.py @@ -152,6 +152,20 @@ class PrometheusMetrics: ) metric.labels(**self._labels(state)).inc() + def _handle_attributes(self, state): + for key, value in state.attributes.items(): + metric = self._metric( + f"{state.domain}_attr_{key.lower()}", + self.prometheus_cli.Gauge, + f"{key} attribute of {state.domain} entity", + ) + + try: + value = float(value) + metric.labels(**self._labels(state)).set(value) + except ValueError: + pass + def _metric(self, metric, factory, documentation, labels=None): if labels is None: labels = ["entity", "friendly_name", "domain"] @@ -368,6 +382,8 @@ class PrometheusMetrics: except ValueError: pass + self._handle_attributes(state) + def _handle_zwave(self, state): self._battery(state) From 00387bf87031857e8ffc249c11e812b0e6af14b2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 2 Jun 2020 17:02:09 -0500 Subject: [PATCH 318/406] Make the frontend available sooner (Part 2 of 2) (#36264) * Part 1 of 2 (no breaking changes in part 1). When integrations configured via the UI block startup or fail to start, the webserver can remain offline which make it is impossible to recover without manually changing files in .storage since the UI is not available. This change is the foundation that part 2 will build on and enable a listener to start the webserver when the frontend is finished loading. Frontend Changes (home-assistant/frontend#6068) * Part 1 of 2 (no breaking changes in part 1). When integrations configured via the UI block startup or fail to start, the webserver can remain offline which make it is impossible to recover without manually changing files in .storage since the UI is not available. This change is the foundation that part 2 will build on and enable a listener to start the webserver when the frontend is finished loading. Frontend Changes (home-assistant/frontend#6068) * Part 2 of 2 (breaking changes in part 2). When integrations configured via the UI block startup or fail to start, the webserver can remain offline which make it is impossible to recover without manually changing files in .storage since the UI is not available. This change is the foundation that part 2 will build on and enable a listener to start the webserver when the frontend is finished loading. * bump timeout to 1800s, adjust comment * bump timeout to 1800s, adjust comment * bump timeout to 4h * bump timeout to 4h * remove timeout failsafe * remove timeout failsafe * and the test * and the test * find the test that needs mocking * find the test that needs mocking * Revert "find the test that needs mocking" This reverts commit 064e7787a8e9bc65df965530726fa1c41f8bcd36. * Revert "find the test that needs mocking" This reverts commit 064e7787a8e9bc65df965530726fa1c41f8bcd36. * fix the one that was missed due to the conflict --- homeassistant/components/http/__init__.py | 12 ++++++++++++ tests/test_bootstrap.py | 2 ++ 2 files changed, 14 insertions(+) diff --git a/homeassistant/components/http/__init__.py b/homeassistant/components/http/__init__.py index e06ceb087c2..b387cea350e 100644 --- a/homeassistant/components/http/__init__.py +++ b/homeassistant/components/http/__init__.py @@ -19,6 +19,7 @@ from homeassistant.core import Event, HomeAssistant from homeassistant.helpers import storage import homeassistant.helpers.config_validation as cv from homeassistant.loader import bind_hass +from homeassistant.setup import ATTR_COMPONENT, EVENT_COMPONENT_LOADED import homeassistant.util as hass_util from homeassistant.util import ssl as ssl_util @@ -232,6 +233,17 @@ async def async_setup(hass, config): await start_http_server_and_save_config(hass, dict(conf), server) + async def async_wait_frontend_load(event: Event) -> None: + """Wait for the frontend to load.""" + + if event.data[ATTR_COMPONENT] != "frontend": + return + + await start_server(event) + + startup_listeners.append( + hass.bus.async_listen(EVENT_COMPONENT_LOADED, async_wait_frontend_load) + ) startup_listeners.append( hass.bus.async_listen(EVENT_HOMEASSISTANT_START, start_server) ) diff --git a/tests/test_bootstrap.py b/tests/test_bootstrap.py index 3ea42d4545b..e14afdca28a 100644 --- a/tests/test_bootstrap.py +++ b/tests/test_bootstrap.py @@ -316,6 +316,8 @@ async def test_setup_hass_takes_longer_than_log_slow_startup( ), patch.object(bootstrap, "LOG_SLOW_STARTUP_INTERVAL", 0.3), patch( "homeassistant.components.frontend.async_setup", side_effect=_async_setup_that_blocks_startup, + ), patch( + "homeassistant.components.http.start_http_server_and_save_config" ): await bootstrap.async_setup_hass( config_dir=get_test_config_dir(), From 44d7169642309e600b88ed64d6a7de3dad31e10a Mon Sep 17 00:00:00 2001 From: jjlawren Date: Tue, 2 Jun 2020 17:37:10 -0500 Subject: [PATCH 319/406] Fix flaky Plex test (#36391) --- tests/components/plex/test_config_flow.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/components/plex/test_config_flow.py b/tests/components/plex/test_config_flow.py index b43448b4d97..0b0ff2a2711 100644 --- a/tests/components/plex/test_config_flow.py +++ b/tests/components/plex/test_config_flow.py @@ -576,6 +576,8 @@ async def test_manual_config(hass): config_flow.DOMAIN, context={"source": "user"} ) + assert result["type"] == "form" + assert result["step_id"] == "user" assert result["data_schema"] is None hass.config_entries.flow.async_abort(result["flow_id"]) @@ -588,9 +590,10 @@ async def test_manual_config(hass): assert result["type"] == "form" assert result["step_id"] == "user_advanced" - result = await hass.config_entries.flow.async_configure( - result["flow_id"], user_input={"setup_method": AUTOMATIC_SETUP_STRING} - ) + with patch("plexauth.PlexAuth.initiate_auth"): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={"setup_method": AUTOMATIC_SETUP_STRING} + ) assert result["type"] == "external" hass.config_entries.flow.async_abort(result["flow_id"]) From bfc5aa90b1e60572aa50f22ac6f0d604aaec8dd7 Mon Sep 17 00:00:00 2001 From: Vincent Le Bourlot Date: Wed, 3 Jun 2020 00:44:36 +0200 Subject: [PATCH 320/406] Add support for rts LightRTSComponent switch. (#36249) --- homeassistant/components/tahoma/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/tahoma/__init__.py b/homeassistant/components/tahoma/__init__.py index 1e1bbd31eae..f3dd51bed96 100644 --- a/homeassistant/components/tahoma/__init__.py +++ b/homeassistant/components/tahoma/__init__.py @@ -62,6 +62,7 @@ TAHOMA_TYPES = { "rts:DualCurtainRTSComponent": "cover", "rts:ExteriorVenetianBlindRTSComponent": "cover", "rts:GarageDoor4TRTSComponent": "switch", + "rts:LightRTSComponent": "switch", "rts:RollerShutterRTSComponent": "cover", "rts:OnOffRTSComponent": "switch", "rts:VenetianBlindRTSComponent": "cover", From 5f4fdaa1711cfbfea5c393c42ba1bf483c68dbb0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 2 Jun 2020 17:47:39 -0500 Subject: [PATCH 321/406] Remove zeroconf options from homekit (#35687) * Remove zeroconf options from homekit homekit uses the system shared zeroconf instance which made the interface choice option controlled by the zeroconf integration setting. * change to cv.deprecated * adj * fix remaining tests from original merge conflict * remove invalidation_version --- homeassistant/components/homekit/__init__.py | 59 +++++------ .../components/homekit/config_flow.py | 11 +-- homeassistant/components/homekit/const.py | 2 - homeassistant/components/homekit/strings.json | 3 +- .../components/homekit/translations/en.json | 3 +- tests/components/homekit/test_config_flow.py | 22 +---- tests/components/homekit/test_homekit.py | 97 ++++--------------- 7 files changed, 48 insertions(+), 149 deletions(-) diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index 5814fdaad2b..a315ddd41e9 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -6,7 +6,6 @@ import os from aiohttp import web import voluptuous as vol -from zeroconf import InterfaceChoice from homeassistant.components import zeroconf from homeassistant.components.binary_sensor import ( @@ -71,7 +70,6 @@ from .const import ( DEFAULT_AUTO_START, DEFAULT_PORT, DEFAULT_SAFE_MODE, - DEFAULT_ZEROCONF_DEFAULT_INTERFACE, DOMAIN, EVENT_HOMEKIT_CHANGED, HOMEKIT, @@ -113,23 +111,24 @@ def _has_all_unique_names_and_ports(bridges): return bridges -BRIDGE_SCHEMA = vol.Schema( - { - 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, +BRIDGE_SCHEMA = vol.All( + cv.deprecated(CONF_ZEROCONF_DEFAULT_INTERFACE), + vol.Schema( + { + 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): cv.boolean, + }, + extra=vol.ALLOW_EXTRA, + ), ) CONFIG_SCHEMA = vol.Schema( @@ -233,11 +232,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): }, ) ) - interface_choice = ( - InterfaceChoice.Default - if options.get(CONF_ZEROCONF_DEFAULT_INTERFACE) - else None - ) homekit = HomeKit( hass, @@ -248,11 +242,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): entity_config, safe_mode, advertise_ip, - interface_choice, entry.entry_id, ) - await hass.async_add_executor_job(homekit.setup) - await homekit.async_setup_zeroconf() + zeroconf_instance = await zeroconf.async_get_instance(hass) + await hass.async_add_executor_job(homekit.setup, zeroconf_instance) undo_listener = entry.add_update_listener(_async_update_listener) @@ -404,7 +397,6 @@ class HomeKit: entity_config, safe_mode, advertise_ip=None, - interface_choice=None, entry_id=None, ): """Initialize a HomeKit object.""" @@ -416,14 +408,13 @@ class HomeKit: self._config = entity_config 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 self.driver = None - def setup(self): + def setup(self, zeroconf_instance): """Set up bridge and accessory driver.""" # pylint: disable=import-outside-toplevel from .accessories import HomeBridge, HomeDriver @@ -440,7 +431,7 @@ class HomeKit: port=self._port, persist_file=persist_file, advertised_address=self._advertise_ip, - interface_choice=self._interface_choice, + zeroconf_instance=zeroconf_instance, ) # If we do not load the mac address will be wrong @@ -455,12 +446,6 @@ class HomeKit: _LOGGER.debug("Safe_mode selected for %s", self._name) self.driver.safe_mode = True - async def async_setup_zeroconf(self): - """Share the system zeroconf instance.""" - # Replace the existing zeroconf instance. - await self.hass.async_add_executor_job(self.driver.advertiser.close) - self.driver.advertiser = await zeroconf.async_get_instance(self.hass) - def reset_accessories(self, entity_ids): """Reset the accessory to load the latest configuration.""" aid_storage = self.hass.data[DOMAIN][self._entry_id][AID_STORAGE] diff --git a/homeassistant/components/homekit/config_flow.py b/homeassistant/components/homekit/config_flow.py index 1dde5be9e98..4cd6b9ffd78 100644 --- a/homeassistant/components/homekit/config_flow.py +++ b/homeassistant/components/homekit/config_flow.py @@ -23,11 +23,9 @@ from .const import ( CONF_FILTER, CONF_SAFE_MODE, CONF_VIDEO_CODEC, - CONF_ZEROCONF_DEFAULT_INTERFACE, DEFAULT_AUTO_START, DEFAULT_CONFIG_FLOW_PORT, DEFAULT_SAFE_MODE, - DEFAULT_ZEROCONF_DEFAULT_INTERFACE, SHORT_BRIDGE_NAME, VIDEO_CODEC_COPY, ) @@ -227,14 +225,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow): 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, + ): bool } ) diff --git a/homeassistant/components/homekit/const.py b/homeassistant/components/homekit/const.py index 2a93ae4cf7e..75a3ad5520b 100644 --- a/homeassistant/components/homekit/const.py +++ b/homeassistant/components/homekit/const.py @@ -68,7 +68,6 @@ DEFAULT_MAX_WIDTH = 1920 DEFAULT_PORT = 51827 DEFAULT_CONFIG_FLOW_PORT = 51828 DEFAULT_SAFE_MODE = False -DEFAULT_ZEROCONF_DEFAULT_INTERFACE = False DEFAULT_VIDEO_CODEC = VIDEO_CODEC_LIBX264 DEFAULT_VIDEO_MAP = "0:v:0" DEFAULT_VIDEO_PACKET_SIZE = 1316 @@ -267,7 +266,6 @@ HK_NOT_CHARGABLE = 2 CONFIG_OPTIONS = [ CONF_FILTER, CONF_AUTO_START, - CONF_ZEROCONF_DEFAULT_INTERFACE, CONF_SAFE_MODE, CONF_ENTITY_CONFIG, ] diff --git a/homeassistant/components/homekit/strings.json b/homeassistant/components/homekit/strings.json index 131ecd8db4c..39011d75719 100644 --- a/homeassistant/components/homekit/strings.json +++ b/homeassistant/components/homekit/strings.json @@ -30,8 +30,7 @@ "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)" + "safe_mode": "Safe Mode (enable only if pairing fails)" }, "description": "These settings only need to be adjusted if the HomeKit bridge is not functional.", "title": "Advanced Configuration" diff --git a/homeassistant/components/homekit/translations/en.json b/homeassistant/components/homekit/translations/en.json index 1fc5ae59a0c..a70f9e90ed7 100644 --- a/homeassistant/components/homekit/translations/en.json +++ b/homeassistant/components/homekit/translations/en.json @@ -23,8 +23,7 @@ "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)" + "safe_mode": "Safe Mode (enable only if pairing fails)" }, "description": "These settings only need to be adjusted if the HomeKit bridge is not functional.", "title": "Advanced Configuration" diff --git a/tests/components/homekit/test_config_flow.py b/tests/components/homekit/test_config_flow.py index cb4d81408cb..3bda3ed7491 100644 --- a/tests/components/homekit/test_config_flow.py +++ b/tests/components/homekit/test_config_flow.py @@ -27,7 +27,6 @@ def _mock_config_entry_with_options_populated(): }, "auto_start": False, "safe_mode": False, - "zeroconf_default_interface": True, }, ) @@ -149,12 +148,7 @@ async def test_options_flow_advanced(hass): 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, - }, + result2["flow_id"], user_input={"auto_start": True, "safe_mode": True}, ) assert result3["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY @@ -167,7 +161,6 @@ async def test_options_flow_advanced(hass): "include_entities": [], }, "safe_mode": True, - "zeroconf_default_interface": False, } @@ -202,8 +195,7 @@ async def test_options_flow_basic(hass): 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}, + result2["flow_id"], user_input={"safe_mode": True}, ) assert result3["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY @@ -216,7 +208,6 @@ async def test_options_flow_basic(hass): "include_entities": [], }, "safe_mode": True, - "zeroconf_default_interface": False, } @@ -264,8 +255,7 @@ async def test_options_flow_with_cameras(hass): with patch("homeassistant.components.homekit.async_setup_entry", return_value=True): result4 = await hass.config_entries.options.async_configure( - result3["flow_id"], - user_input={"safe_mode": True, "zeroconf_default_interface": False}, + result3["flow_id"], user_input={"safe_mode": True}, ) assert result4["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY @@ -279,7 +269,6 @@ async def test_options_flow_with_cameras(hass): }, "entity_config": {"camera.native_h264": {"video_codec": "copy"}}, "safe_mode": True, - "zeroconf_default_interface": False, } # Now run though again and verify we can turn off copy @@ -315,8 +304,7 @@ async def test_options_flow_with_cameras(hass): with patch("homeassistant.components.homekit.async_setup_entry", return_value=True): result4 = await hass.config_entries.options.async_configure( - result3["flow_id"], - user_input={"safe_mode": True, "zeroconf_default_interface": False}, + result3["flow_id"], user_input={"safe_mode": True}, ) assert result4["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY @@ -330,7 +318,6 @@ async def test_options_flow_with_cameras(hass): }, "entity_config": {"camera.native_h264": {}}, "safe_mode": True, - "zeroconf_default_interface": False, } @@ -353,7 +340,6 @@ async def test_options_flow_blocked_when_from_yaml(hass): "exclude_entities": ["climate.front_gate"], }, "safe_mode": False, - "zeroconf_default_interface": True, }, source=SOURCE_IMPORT, ) diff --git a/tests/components/homekit/test_homekit.py b/tests/components/homekit/test_homekit.py index dbca6135c89..ca31b4501b9 100644 --- a/tests/components/homekit/test_homekit.py +++ b/tests/components/homekit/test_homekit.py @@ -2,8 +2,8 @@ import os from typing import Dict +from asynctest import MagicMock import pytest -from zeroconf import InterfaceChoice from homeassistant.components import zeroconf from homeassistant.components.binary_sensor import ( @@ -26,10 +26,10 @@ from homeassistant.components.homekit.const import ( CONF_AUTO_START, CONF_ENTRY_INDEX, CONF_SAFE_MODE, - CONF_ZEROCONF_DEFAULT_INTERFACE, DEFAULT_PORT, DEFAULT_SAFE_MODE, DOMAIN, + HOMEKIT, HOMEKIT_FILE, SERVICE_HOMEKIT_RESET_ACCESSORY, SERVICE_HOMEKIT_START, @@ -99,7 +99,6 @@ async def test_setup_min(hass): with patch(f"{PATH_HOMEKIT}.HomeKit") as mock_homekit: mock_homekit.return_value = homekit = Mock() type(homekit).async_start = AsyncMock() - type(homekit).async_setup_zeroconf = AsyncMock() assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() @@ -112,7 +111,6 @@ async def test_setup_min(hass): {}, DEFAULT_SAFE_MODE, None, - None, entry.entry_id, ) assert mock_homekit().setup.called is True @@ -130,18 +128,13 @@ async def test_setup_auto_start_disabled(hass): 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_SAFE_MODE: DEFAULT_SAFE_MODE, - CONF_ZEROCONF_DEFAULT_INTERFACE: True, - }, + options={CONF_AUTO_START: False, CONF_SAFE_MODE: DEFAULT_SAFE_MODE}, ) 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() - type(homekit).async_setup_zeroconf = AsyncMock() assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() @@ -154,7 +147,6 @@ async def test_setup_auto_start_disabled(hass): {}, DEFAULT_SAFE_MODE, None, - InterfaceChoice.Default, entry.entry_id, ) assert mock_homekit().setup.called is True @@ -201,15 +193,15 @@ async def test_homekit_setup(hass, hk_driver): {}, DEFAULT_SAFE_MODE, advertise_ip=None, - interface_choice=None, entry_id=entry.entry_id, ) + zeroconf_mock = MagicMock() with patch( f"{PATH_HOMEKIT}.accessories.HomeDriver", return_value=hk_driver ) as mock_driver, patch("homeassistant.util.get_local_ip") as mock_ip: mock_ip.return_value = IP_ADDRESS - await hass.async_add_executor_job(homekit.setup) + await hass.async_add_executor_job(homekit.setup, zeroconf_mock) path = get_persist_fullpath_for_entry_id(hass, entry.entry_id) assert isinstance(homekit.bridge, HomeBridge) @@ -221,7 +213,7 @@ async def test_homekit_setup(hass, hk_driver): port=DEFAULT_PORT, persist_file=path, advertised_address=None, - interface_choice=None, + zeroconf_instance=zeroconf_mock, ) assert homekit.driver.safe_mode is False @@ -245,15 +237,15 @@ async def test_homekit_setup_ip_address(hass, hk_driver): {}, None, None, - interface_choice=None, entry_id=entry.entry_id, ) + mock_zeroconf = MagicMock() 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) + await hass.async_add_executor_job(homekit.setup, mock_zeroconf) mock_driver.assert_called_with( hass, entry.entry_id, @@ -262,7 +254,7 @@ async def test_homekit_setup_ip_address(hass, hk_driver): port=DEFAULT_PORT, persist_file=path, advertised_address=None, - interface_choice=None, + zeroconf_instance=mock_zeroconf, ) @@ -282,15 +274,15 @@ async def test_homekit_setup_advertise_ip(hass, hk_driver): {}, None, "192.168.1.100", - interface_choice=None, entry_id=entry.entry_id, ) + zeroconf_instance = MagicMock() 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) + await hass.async_add_executor_job(homekit.setup, zeroconf_instance) mock_driver.assert_called_with( hass, entry.entry_id, @@ -299,44 +291,7 @@ async def test_homekit_setup_advertise_ip(hass, hk_driver): port=DEFAULT_PORT, persist_file=path, advertised_address="192.168.1.100", - interface_choice=None, - ) - - -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, - DEFAULT_PORT, - "0.0.0.0", - {}, - {}, - 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=path, - advertised_address=None, - interface_choice=InterfaceChoice.Default, + zeroconf_instance=zeroconf_instance, ) @@ -356,12 +311,11 @@ async def test_homekit_setup_safe_mode(hass, hk_driver): {}, 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) + await hass.async_add_executor_job(homekit.setup, MagicMock()) assert homekit.driver.safe_mode is True @@ -378,7 +332,6 @@ async def test_homekit_add_accessory(hass): {}, DEFAULT_SAFE_MODE, advertise_ip=None, - interface_choice=None, entry_id=entry.entry_id, ) homekit.driver = "driver" @@ -415,7 +368,6 @@ async def test_homekit_remove_accessory(hass): {}, DEFAULT_SAFE_MODE, advertise_ip=None, - interface_choice=None, entry_id=entry.entry_id, ) homekit.driver = "driver" @@ -441,7 +393,6 @@ async def test_homekit_entity_filter(hass): {}, DEFAULT_SAFE_MODE, advertise_ip=None, - interface_choice=None, entry_id=entry.entry_id, ) homekit.bridge = Mock() @@ -476,7 +427,6 @@ async def test_homekit_start(hass, hk_driver, device_reg, debounce_patcher): {}, DEFAULT_SAFE_MODE, advertise_ip=None, - interface_choice=None, entry_id=entry.entry_id, ) homekit.bridge = Mock() @@ -566,7 +516,6 @@ async def test_homekit_start_with_a_broken_accessory(hass, hk_driver, debounce_p {}, DEFAULT_SAFE_MODE, advertise_ip=None, - interface_choice=None, entry_id=entry.entry_id, ) @@ -612,7 +561,6 @@ async def test_homekit_stop(hass): {}, DEFAULT_SAFE_MODE, advertise_ip=None, - interface_choice=None, entry_id=entry.entry_id, ) homekit.driver = Mock() @@ -653,7 +601,6 @@ async def test_homekit_reset_accessories(hass): {entity_id: {}}, DEFAULT_SAFE_MODE, advertise_ip=None, - interface_choice=None, entry_id=entry.entry_id, ) homekit.bridge = Mock() @@ -702,7 +649,6 @@ async def test_homekit_too_many_accessories(hass, hk_driver): {}, DEFAULT_SAFE_MODE, advertise_ip=None, - interface_choice=None, entry_id=entry.entry_id, ) homekit.bridge = Mock() @@ -737,7 +683,6 @@ async def test_homekit_finds_linked_batteries( {"light.demo": {}}, DEFAULT_SAFE_MODE, advertise_ip=None, - interface_choice=None, entry_id=entry.entry_id, ) homekit.driver = hk_driver @@ -832,7 +777,6 @@ async def test_setup_imported(hass): with patch(f"{PATH_HOMEKIT}.HomeKit") as mock_homekit: mock_homekit.return_value = homekit = Mock() type(homekit).async_start = AsyncMock() - type(homekit).async_setup_zeroconf = AsyncMock() assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() @@ -845,7 +789,6 @@ async def test_setup_imported(hass): {}, DEFAULT_SAFE_MODE, None, - None, entry.entry_id, ) assert mock_homekit().setup.called is True @@ -887,7 +830,6 @@ async def test_yaml_updates_update_config_entry_for_name(hass): with patch(f"{PATH_HOMEKIT}.HomeKit") as mock_homekit: mock_homekit.return_value = homekit = Mock() type(homekit).async_start = AsyncMock() - type(homekit).async_setup_zeroconf = AsyncMock() assert await async_setup_component( hass, "homekit", {"homekit": {CONF_NAME: BRIDGE_NAME, CONF_PORT: 12345}} ) @@ -902,7 +844,6 @@ async def test_yaml_updates_update_config_entry_for_name(hass): {}, DEFAULT_SAFE_MODE, None, - None, entry.entry_id, ) assert mock_homekit().setup.called is True @@ -932,7 +873,7 @@ async def test_raise_config_entry_not_ready(hass): await hass.async_block_till_done() -async def test_homekit_uses_system_zeroconf(hass, hk_driver, mock_zeroconf): +async def test_homekit_uses_system_zeroconf(hass, mock_zeroconf): """Test HomeKit uses system zeroconf.""" entry = MockConfigEntry( domain=DOMAIN, @@ -941,13 +882,15 @@ async def test_homekit_uses_system_zeroconf(hass, hk_driver, mock_zeroconf): ) system_zc = await zeroconf.async_get_instance(hass) - with patch(f"{PATH_HOMEKIT}.accessories.HomeDriver", return_value=hk_driver), patch( - f"{PATH_HOMEKIT}.HomeKit.async_start" + with patch(f"{PATH_HOMEKIT}.HomeKit.add_bridge_accessory"), patch( + f"{PATH_HOMEKIT}.show_setup_message" + ), patch("pyhap.accessory_driver.AccessoryDriver.add_accessory"), patch( + "pyhap.accessory_driver.AccessoryDriver.start" ): entry.add_to_hass(hass) assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() - assert hk_driver.advertiser == system_zc + assert hass.data[DOMAIN][entry.entry_id][HOMEKIT].driver.advertiser == system_zc def _write_data(path: str, data: Dict) -> None: @@ -972,7 +915,6 @@ async def test_homekit_ignored_missing_devices( {"light.demo": {}}, DEFAULT_SAFE_MODE, advertise_ip=None, - interface_choice=None, entry_id=entry.entry_id, ) homekit.driver = hk_driver @@ -1052,7 +994,6 @@ async def test_homekit_finds_linked_motion_sensors( {"camera.camera_demo": {}}, DEFAULT_SAFE_MODE, advertise_ip=None, - interface_choice=None, entry_id=entry.entry_id, ) homekit.driver = hk_driver From f94bbdab612b2f8cf01e1d510cc33290eb53fa5d Mon Sep 17 00:00:00 2001 From: Konstantin Antselovich Date: Tue, 2 Jun 2020 16:08:50 -0700 Subject: [PATCH 322/406] Disable jemalloc via specific ENV variable, see Issue#36237 (#36274) --- rootfs/etc/services.d/home-assistant/run | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/rootfs/etc/services.d/home-assistant/run b/rootfs/etc/services.d/home-assistant/run index 750d00a91ec..11af113e4b9 100644 --- a/rootfs/etc/services.d/home-assistant/run +++ b/rootfs/etc/services.d/home-assistant/run @@ -2,9 +2,11 @@ # ============================================================================== # Start Home Assistant service # ============================================================================== + cd /config || bashio::exit.nok "Can't find config folder!" -# Enable Jemalloc for Home Assistant Core -export LD_PRELOAD="/usr/local/lib/libjemalloc.so.2" - +# Enable Jemalloc for Home Assistant Core, unless disabled +if [[ -z "${DISABLE_JEMALLOC+x}" ]]; then + export LD_PRELOAD="/usr/local/lib/libjemalloc.so.2" +fi exec python3 -m homeassistant --config /config From 5d6a563ac7a149167630d02f04e100d4e658a62d Mon Sep 17 00:00:00 2001 From: Brynley McDonald Date: Wed, 3 Jun 2020 11:20:59 +1200 Subject: [PATCH 323/406] Implement Google Assistant media traits (#35803) Co-authored-by: Paulus Schoutsen --- .../components/google_assistant/trait.py | 197 +++++++++++++++++- tests/components/google_assistant/__init__.py | 13 +- .../google_assistant/test_smart_home.py | 10 +- .../components/google_assistant/test_trait.py | 170 +++++++++++++++ 4 files changed, 386 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/google_assistant/trait.py b/homeassistant/components/google_assistant/trait.py index 0a90e542e51..41f980fbbdf 100644 --- a/homeassistant/components/google_assistant/trait.py +++ b/homeassistant/components/google_assistant/trait.py @@ -42,9 +42,13 @@ from homeassistant.const import ( STATE_ALARM_DISARMED, STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED, + STATE_IDLE, STATE_LOCKED, STATE_OFF, STATE_ON, + STATE_PAUSED, + STATE_PLAYING, + STATE_STANDBY, STATE_UNAVAILABLE, STATE_UNKNOWN, TEMP_CELSIUS, @@ -52,7 +56,7 @@ from homeassistant.const import ( ) from homeassistant.core import DOMAIN as HA_DOMAIN from homeassistant.helpers.network import get_url -from homeassistant.util import color as color_util, temperature as temp_util +from homeassistant.util import color as color_util, dt, temperature as temp_util from .const import ( CHALLENGE_ACK_NEEDED, @@ -85,6 +89,8 @@ TRAIT_OPENCLOSE = f"{PREFIX_TRAITS}OpenClose" TRAIT_VOLUME = f"{PREFIX_TRAITS}Volume" TRAIT_ARMDISARM = f"{PREFIX_TRAITS}ArmDisarm" TRAIT_HUMIDITY_SETTING = f"{PREFIX_TRAITS}HumiditySetting" +TRAIT_TRANSPORT_CONTROL = f"{PREFIX_TRAITS}TransportControl" +TRAIT_MEDIA_STATE = f"{PREFIX_TRAITS}MediaState" PREFIX_COMMANDS = "action.devices.commands." COMMAND_ONOFF = f"{PREFIX_COMMANDS}OnOff" @@ -109,6 +115,15 @@ COMMAND_OPENCLOSE = f"{PREFIX_COMMANDS}OpenClose" COMMAND_SET_VOLUME = f"{PREFIX_COMMANDS}setVolume" COMMAND_VOLUME_RELATIVE = f"{PREFIX_COMMANDS}volumeRelative" COMMAND_ARMDISARM = f"{PREFIX_COMMANDS}ArmDisarm" +COMMAND_MEDIA_NEXT = f"{PREFIX_COMMANDS}mediaNext" +COMMAND_MEDIA_PAUSE = f"{PREFIX_COMMANDS}mediaPause" +COMMAND_MEDIA_PREVIOUS = f"{PREFIX_COMMANDS}mediaPrevious" +COMMAND_MEDIA_RESUME = f"{PREFIX_COMMANDS}mediaResume" +COMMAND_MEDIA_SEEK_RELATIVE = f"{PREFIX_COMMANDS}mediaSeekRelative" +COMMAND_MEDIA_SEEK_TO_POSITION = f"{PREFIX_COMMANDS}mediaSeekToPosition" +COMMAND_MEDIA_SHUFFLE = f"{PREFIX_COMMANDS}mediaShuffle" +COMMAND_MEDIA_STOP = f"{PREFIX_COMMANDS}mediaStop" + TRAITS = [] @@ -1500,3 +1515,183 @@ def _verify_ack_challenge(data, state, challenge): return if not challenge or not challenge.get("ack"): raise ChallengeNeeded(CHALLENGE_ACK_NEEDED) + + +MEDIA_COMMAND_SUPPORT_MAPPING = { + COMMAND_MEDIA_NEXT: media_player.SUPPORT_NEXT_TRACK, + COMMAND_MEDIA_PAUSE: media_player.SUPPORT_PAUSE, + COMMAND_MEDIA_PREVIOUS: media_player.SUPPORT_PREVIOUS_TRACK, + COMMAND_MEDIA_RESUME: media_player.SUPPORT_PLAY, + COMMAND_MEDIA_SEEK_RELATIVE: media_player.SUPPORT_SEEK, + COMMAND_MEDIA_SEEK_TO_POSITION: media_player.SUPPORT_SEEK, + COMMAND_MEDIA_SHUFFLE: media_player.SUPPORT_SHUFFLE_SET, + COMMAND_MEDIA_STOP: media_player.SUPPORT_STOP, +} + +MEDIA_COMMAND_ATTRIBUTES = { + COMMAND_MEDIA_NEXT: "NEXT", + COMMAND_MEDIA_PAUSE: "PAUSE", + COMMAND_MEDIA_PREVIOUS: "PREVIOUS", + COMMAND_MEDIA_RESUME: "RESUME", + COMMAND_MEDIA_SEEK_RELATIVE: "SEEK_RELATIVE", + COMMAND_MEDIA_SEEK_TO_POSITION: "SEEK_TO_POSITION", + COMMAND_MEDIA_SHUFFLE: "SHUFFLE", + COMMAND_MEDIA_STOP: "STOP", +} + + +@register_trait +class TransportControlTrait(_Trait): + """Trait to control media playback. + + https://developers.google.com/actions/smarthome/traits/transportcontrol + """ + + name = TRAIT_TRANSPORT_CONTROL + commands = [ + COMMAND_MEDIA_NEXT, + COMMAND_MEDIA_PAUSE, + COMMAND_MEDIA_PREVIOUS, + COMMAND_MEDIA_RESUME, + COMMAND_MEDIA_SEEK_RELATIVE, + COMMAND_MEDIA_SEEK_TO_POSITION, + COMMAND_MEDIA_SHUFFLE, + COMMAND_MEDIA_STOP, + ] + + @staticmethod + def supported(domain, features, device_class): + """Test if state is supported.""" + if domain == media_player.DOMAIN: + for feature in MEDIA_COMMAND_SUPPORT_MAPPING.values(): + if features & feature: + return True + + return False + + def sync_attributes(self): + """Return opening direction.""" + response = {} + + if self.state.domain == media_player.DOMAIN: + features = self.state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) + + support = [] + for command, feature in MEDIA_COMMAND_SUPPORT_MAPPING.items(): + if features & feature: + support.append(MEDIA_COMMAND_ATTRIBUTES[command]) + response["transportControlSupportedCommands"] = support + + return response + + def query_attributes(self): + """Return the attributes of this trait for this entity.""" + + return {} + + async def execute(self, command, data, params, challenge): + """Execute a media command.""" + + service_attrs = {ATTR_ENTITY_ID: self.state.entity_id} + + if command == COMMAND_MEDIA_SEEK_RELATIVE: + service = media_player.SERVICE_MEDIA_SEEK + + rel_position = params["relativePositionMs"] / 1000 + seconds_since = 0 # Default to 0 seconds + if self.state.state == STATE_PLAYING: + now = dt.utcnow() + upd_at = self.state.attributes.get( + media_player.ATTR_MEDIA_POSITION_UPDATED_AT, now + ) + seconds_since = (now - upd_at).total_seconds() + position = self.state.attributes.get(media_player.ATTR_MEDIA_POSITION, 0) + max_position = self.state.attributes.get( + media_player.ATTR_MEDIA_DURATION, 0 + ) + service_attrs[media_player.ATTR_MEDIA_SEEK_POSITION] = min( + max(position + seconds_since + rel_position, 0), max_position + ) + elif command == COMMAND_MEDIA_SEEK_TO_POSITION: + service = media_player.SERVICE_MEDIA_SEEK + + max_position = self.state.attributes.get( + media_player.ATTR_MEDIA_DURATION, 0 + ) + service_attrs[media_player.ATTR_MEDIA_SEEK_POSITION] = min( + max(params["absPositionMs"] / 1000, 0), max_position + ) + elif command == COMMAND_MEDIA_NEXT: + service = media_player.SERVICE_MEDIA_NEXT_TRACK + elif command == COMMAND_MEDIA_PAUSE: + service = media_player.SERVICE_MEDIA_PAUSE + elif command == COMMAND_MEDIA_PREVIOUS: + service = media_player.SERVICE_MEDIA_PREVIOUS_TRACK + elif command == COMMAND_MEDIA_RESUME: + service = media_player.SERVICE_MEDIA_PLAY + elif command == COMMAND_MEDIA_SHUFFLE: + service = media_player.SERVICE_SHUFFLE_SET + + # Google Assistant only supports enabling shuffle + service_attrs[media_player.ATTR_MEDIA_SHUFFLE] = True + elif command == COMMAND_MEDIA_STOP: + service = media_player.SERVICE_MEDIA_STOP + else: + raise SmartHomeError(ERR_NOT_SUPPORTED, "Command not supported") + + await self.hass.services.async_call( + media_player.DOMAIN, + service, + service_attrs, + blocking=True, + context=data.context, + ) + + +@register_trait +class MediaStateTrait(_Trait): + """Trait to get media playback state. + + https://developers.google.com/actions/smarthome/traits/mediastate + """ + + name = TRAIT_MEDIA_STATE + commands = [] + + activity_lookup = { + STATE_OFF: "INACTIVE", + STATE_IDLE: "STANDBY", + STATE_PLAYING: "ACTIVE", + STATE_ON: "STANDBY", + STATE_PAUSED: "STANDBY", + STATE_STANDBY: "STANDBY", + STATE_UNAVAILABLE: "INACTIVE", + STATE_UNKNOWN: "INACTIVE", + } + + playback_lookup = { + STATE_OFF: "STOPPED", + STATE_IDLE: "STOPPED", + STATE_PLAYING: "PLAYING", + STATE_ON: "STOPPED", + STATE_PAUSED: "PAUSED", + STATE_STANDBY: "STOPPED", + STATE_UNAVAILABLE: "STOPPED", + STATE_UNKNOWN: "STOPPED", + } + + @staticmethod + def supported(domain, features, device_class): + """Test if state is supported.""" + return domain == media_player.DOMAIN + + def sync_attributes(self): + """Return attributes for a sync request.""" + return {"supportActivityState": True, "supportPlaybackState": True} + + def query_attributes(self): + """Return the attributes of this trait for this entity.""" + return { + "activityState": self.activity_lookup.get(self.state.state, "INACTIVE"), + "playbackState": self.playback_lookup.get(self.state.state, "STOPPED"), + } diff --git a/tests/components/google_assistant/__init__.py b/tests/components/google_assistant/__init__.py index cd239f14b27..e8b5cd87be0 100644 --- a/tests/components/google_assistant/__init__.py +++ b/tests/components/google_assistant/__init__.py @@ -167,6 +167,8 @@ DEMO_DEVICES = [ "action.devices.traits.OnOff", "action.devices.traits.Volume", "action.devices.traits.Modes", + "action.devices.traits.TransportControl", + "action.devices.traits.MediaState", ], "type": "action.devices.types.SETTOP", "willReportState": False, @@ -178,6 +180,8 @@ DEMO_DEVICES = [ "action.devices.traits.OnOff", "action.devices.traits.Volume", "action.devices.traits.Modes", + "action.devices.traits.TransportControl", + "action.devices.traits.MediaState", ], "type": "action.devices.types.SETTOP", "willReportState": False, @@ -185,7 +189,12 @@ DEMO_DEVICES = [ { "id": "media_player.lounge_room", "name": {"name": "Lounge room"}, - "traits": ["action.devices.traits.OnOff", "action.devices.traits.Modes"], + "traits": [ + "action.devices.traits.OnOff", + "action.devices.traits.Modes", + "action.devices.traits.TransportControl", + "action.devices.traits.MediaState", + ], "type": "action.devices.types.SETTOP", "willReportState": False, }, @@ -196,6 +205,8 @@ DEMO_DEVICES = [ "action.devices.traits.OnOff", "action.devices.traits.Volume", "action.devices.traits.Modes", + "action.devices.traits.TransportControl", + "action.devices.traits.MediaState", ], "type": "action.devices.types.SETTOP", "willReportState": False, diff --git a/tests/components/google_assistant/test_smart_home.py b/tests/components/google_assistant/test_smart_home.py index cce8f5a6194..e9795a9320f 100644 --- a/tests/components/google_assistant/test_smart_home.py +++ b/tests/components/google_assistant/test_smart_home.py @@ -769,10 +769,16 @@ async def test_device_media_player(hass, device_class, google_type): "agentUserId": "test-agent", "devices": [ { - "attributes": {}, + "attributes": { + "supportActivityState": True, + "supportPlaybackState": True, + }, "id": sensor.entity_id, "name": {"name": sensor.name}, - "traits": ["action.devices.traits.OnOff"], + "traits": [ + "action.devices.traits.OnOff", + "action.devices.traits.MediaState", + ], "type": google_type, "willReportState": False, } diff --git a/tests/components/google_assistant/test_trait.py b/tests/components/google_assistant/test_trait.py index adce4ef877b..3dca89b8193 100644 --- a/tests/components/google_assistant/test_trait.py +++ b/tests/components/google_assistant/test_trait.py @@ -1,4 +1,5 @@ """Tests for the Google Assistant traits.""" +from datetime import datetime, timedelta import logging import pytest @@ -35,8 +36,13 @@ from homeassistant.const import ( STATE_ALARM_ARMED_AWAY, STATE_ALARM_DISARMED, STATE_ALARM_PENDING, + STATE_IDLE, STATE_OFF, STATE_ON, + STATE_PAUSED, + STATE_PLAYING, + STATE_STANDBY, + STATE_UNAVAILABLE, STATE_UNKNOWN, TEMP_CELSIUS, TEMP_FAHRENHEIT, @@ -1850,3 +1856,167 @@ async def test_humidity_setting_sensor_data(hass, state, ambient): with pytest.raises(helpers.SmartHomeError) as err: await trt.execute(trait.COMMAND_ONOFF, BASIC_DATA, {"on": False}, {}) assert err.value.code == const.ERR_NOT_SUPPORTED + + +async def test_transport_control(hass): + """Test the TransportControlTrait.""" + assert helpers.get_google_type(media_player.DOMAIN, None) is not None + + for feature in trait.MEDIA_COMMAND_SUPPORT_MAPPING.values(): + assert trait.TransportControlTrait.supported(media_player.DOMAIN, feature, None) + + now = datetime(2020, 1, 1) + + trt = trait.TransportControlTrait( + hass, + State( + "media_player.bla", + media_player.STATE_PLAYING, + { + media_player.ATTR_MEDIA_POSITION: 100, + media_player.ATTR_MEDIA_DURATION: 200, + media_player.ATTR_MEDIA_POSITION_UPDATED_AT: now + - timedelta(seconds=10), + media_player.ATTR_MEDIA_VOLUME_LEVEL: 0.5, + ATTR_SUPPORTED_FEATURES: media_player.SUPPORT_PLAY + | media_player.SUPPORT_STOP, + }, + ), + BASIC_CONFIG, + ) + + assert trt.sync_attributes() == { + "transportControlSupportedCommands": ["RESUME", "STOP"] + } + assert trt.query_attributes() == {} + + # COMMAND_MEDIA_SEEK_RELATIVE + calls = async_mock_service( + hass, media_player.DOMAIN, media_player.SERVICE_MEDIA_SEEK + ) + + # Patch to avoid time ticking over during the command failing the test + with patch("homeassistant.util.dt.utcnow", return_value=now): + await trt.execute( + trait.COMMAND_MEDIA_SEEK_RELATIVE, + BASIC_DATA, + {"relativePositionMs": 10000}, + {}, + ) + assert len(calls) == 1 + assert calls[0].data == { + ATTR_ENTITY_ID: "media_player.bla", + # 100s (current position) + 10s (from command) + 10s (from updated_at) + media_player.ATTR_MEDIA_SEEK_POSITION: 120, + } + + # COMMAND_MEDIA_SEEK_TO_POSITION + calls = async_mock_service( + hass, media_player.DOMAIN, media_player.SERVICE_MEDIA_SEEK + ) + await trt.execute( + trait.COMMAND_MEDIA_SEEK_TO_POSITION, BASIC_DATA, {"absPositionMs": 50000}, {} + ) + assert len(calls) == 1 + assert calls[0].data == { + ATTR_ENTITY_ID: "media_player.bla", + media_player.ATTR_MEDIA_SEEK_POSITION: 50, + } + + # COMMAND_MEDIA_NEXT + calls = async_mock_service( + hass, media_player.DOMAIN, media_player.SERVICE_MEDIA_NEXT_TRACK + ) + await trt.execute(trait.COMMAND_MEDIA_NEXT, BASIC_DATA, {}, {}) + assert len(calls) == 1 + assert calls[0].data == {ATTR_ENTITY_ID: "media_player.bla"} + + # COMMAND_MEDIA_PAUSE + calls = async_mock_service( + hass, media_player.DOMAIN, media_player.SERVICE_MEDIA_PAUSE + ) + await trt.execute(trait.COMMAND_MEDIA_PAUSE, BASIC_DATA, {}, {}) + assert len(calls) == 1 + assert calls[0].data == {ATTR_ENTITY_ID: "media_player.bla"} + + # COMMAND_MEDIA_PREVIOUS + calls = async_mock_service( + hass, media_player.DOMAIN, media_player.SERVICE_MEDIA_PREVIOUS_TRACK + ) + await trt.execute(trait.COMMAND_MEDIA_PREVIOUS, BASIC_DATA, {}, {}) + assert len(calls) == 1 + assert calls[0].data == {ATTR_ENTITY_ID: "media_player.bla"} + + # COMMAND_MEDIA_RESUME + calls = async_mock_service( + hass, media_player.DOMAIN, media_player.SERVICE_MEDIA_PLAY + ) + await trt.execute(trait.COMMAND_MEDIA_RESUME, BASIC_DATA, {}, {}) + assert len(calls) == 1 + assert calls[0].data == {ATTR_ENTITY_ID: "media_player.bla"} + + # COMMAND_MEDIA_SHUFFLE + calls = async_mock_service( + hass, media_player.DOMAIN, media_player.SERVICE_SHUFFLE_SET + ) + await trt.execute(trait.COMMAND_MEDIA_SHUFFLE, BASIC_DATA, {}, {}) + assert len(calls) == 1 + assert calls[0].data == { + ATTR_ENTITY_ID: "media_player.bla", + media_player.ATTR_MEDIA_SHUFFLE: True, + } + + # COMMAND_MEDIA_STOP + calls = async_mock_service( + hass, media_player.DOMAIN, media_player.SERVICE_MEDIA_STOP + ) + await trt.execute(trait.COMMAND_MEDIA_STOP, BASIC_DATA, {}, {}) + assert len(calls) == 1 + assert calls[0].data == {ATTR_ENTITY_ID: "media_player.bla"} + + +@pytest.mark.parametrize( + "state", + ( + STATE_OFF, + STATE_IDLE, + STATE_PLAYING, + STATE_ON, + STATE_PAUSED, + STATE_STANDBY, + STATE_UNAVAILABLE, + STATE_UNKNOWN, + ), +) +async def test_media_state(hass, state): + """Test the MediaStateTrait.""" + assert helpers.get_google_type(media_player.DOMAIN, None) is not None + + assert trait.TransportControlTrait.supported( + media_player.DOMAIN, media_player.SUPPORT_PLAY, None + ) + + trt = trait.MediaStateTrait( + hass, + State( + "media_player.bla", + state, + { + media_player.ATTR_MEDIA_POSITION: 100, + media_player.ATTR_MEDIA_DURATION: 200, + media_player.ATTR_MEDIA_VOLUME_LEVEL: 0.5, + ATTR_SUPPORTED_FEATURES: media_player.SUPPORT_PLAY + | media_player.SUPPORT_STOP, + }, + ), + BASIC_CONFIG, + ) + + assert trt.sync_attributes() == { + "supportActivityState": True, + "supportPlaybackState": True, + } + assert trt.query_attributes() == { + "activityState": trt.activity_lookup.get(state), + "playbackState": trt.playback_lookup.get(state), + } From e347f3770cc4c7224b988c4d71102eec0320bce3 Mon Sep 17 00:00:00 2001 From: Aaron Godfrey Date: Tue, 2 Jun 2020 16:37:41 -0700 Subject: [PATCH 324/406] Use SCAN_INTERVAL instead of Throttle to allow manual todoist updates (#35297) --- homeassistant/components/todoist/calendar.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/todoist/calendar.py b/homeassistant/components/todoist/calendar.py index 0ce8101f49c..014dbd37a4e 100644 --- a/homeassistant/components/todoist/calendar.py +++ b/homeassistant/components/todoist/calendar.py @@ -9,7 +9,7 @@ from homeassistant.components.calendar import PLATFORM_SCHEMA, CalendarEventDevi from homeassistant.const import CONF_ID, CONF_NAME, CONF_TOKEN import homeassistant.helpers.config_validation as cv from homeassistant.helpers.template import DATE_STR_FORMAT -from homeassistant.util import Throttle, dt +from homeassistant.util import dt from .const import ( ALL_DAY, @@ -84,7 +84,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( } ) -MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=15) +SCAN_INTERVAL = timedelta(minutes=15) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -302,8 +302,7 @@ class TodoistProjectData: platform itself, but are not used by this component at all. The 'update' method polls the Todoist API for new projects/tasks, as well - as any updates to current projects/tasks. This is throttled to every - MIN_TIME_BETWEEN_UPDATES minutes. + as any updates to current projects/tasks. This occurs every SCAN_INTERVAL minutes. """ def __init__( @@ -514,7 +513,6 @@ class TodoistProjectData: events.append(event) return events - @Throttle(MIN_TIME_BETWEEN_UPDATES) def update(self): """Get the latest data.""" if self._id is None: From 41801061256ff2bd3b6c35a784a5629949629e53 Mon Sep 17 00:00:00 2001 From: jjlawren Date: Tue, 2 Jun 2020 18:38:24 -0500 Subject: [PATCH 325/406] Bump plexapi to 4.0.0 (#36389) --- homeassistant/components/plex/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/plex/manifest.json b/homeassistant/components/plex/manifest.json index 7e78cdda4b2..386f772947a 100644 --- a/homeassistant/components/plex/manifest.json +++ b/homeassistant/components/plex/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/plex", "requirements": [ - "plexapi==3.6.0", + "plexapi==4.0.0", "plexauth==0.0.5", "plexwebsocket==0.0.10" ], diff --git a/requirements_all.txt b/requirements_all.txt index 682ca8ed0f7..b18b2048b11 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1078,7 +1078,7 @@ pillow==7.1.2 pizzapi==0.0.3 # homeassistant.components.plex -plexapi==3.6.0 +plexapi==4.0.0 # homeassistant.components.plex plexauth==0.0.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 0069aac21a5..12e90ae36f1 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -453,7 +453,7 @@ pilight==0.1.1 pillow==7.1.2 # homeassistant.components.plex -plexapi==3.6.0 +plexapi==4.0.0 # homeassistant.components.plex plexauth==0.0.5 From 5ba610859d82d08cbe3f5fc9ad03c64d03cfd059 Mon Sep 17 00:00:00 2001 From: baurandr Date: Tue, 2 Jun 2020 19:46:17 -0400 Subject: [PATCH 326/406] Fix exception in callback async_remove_from_mem (#34075) Co-authored-by: Martin Hjelmare --- homeassistant/components/tts/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/tts/__init__.py b/homeassistant/components/tts/__init__.py index b7d3b4f1f4c..6dc2e9b7d45 100644 --- a/homeassistant/components/tts/__init__.py +++ b/homeassistant/components/tts/__init__.py @@ -399,7 +399,7 @@ class SpeechManager: @callback def async_remove_from_mem(): """Cleanup memcache.""" - self.mem_cache.pop(key) + self.mem_cache.pop(key, None) self.hass.loop.call_later(self.time_memory, async_remove_from_mem) From eedbb86b67856e42930ec3822ee96761354d0b49 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 2 Jun 2020 18:54:58 -0500 Subject: [PATCH 327/406] Fix flapping buienradar tests (#36394) --- tests/components/buienradar/test_camera.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/components/buienradar/test_camera.py b/tests/components/buienradar/test_camera.py index 5a19d8e70dd..9935cc19cb8 100644 --- a/tests/components/buienradar/test_camera.py +++ b/tests/components/buienradar/test_camera.py @@ -56,6 +56,7 @@ async def test_expire_delta(aioclient_mock, hass, hass_client): } }, ) + await hass.async_block_till_done() client = await hass_client() @@ -79,6 +80,7 @@ async def test_only_one_fetch_at_a_time(aioclient_mock, hass, hass_client): await async_setup_component( hass, "camera", {"camera": {"name": "config_test", "platform": "buienradar"}} ) + await hass.async_block_till_done() client = await hass_client() @@ -102,6 +104,7 @@ async def test_dimension(aioclient_mock, hass, hass_client): "camera", {"camera": {"name": "config_test", "platform": "buienradar", "dimension": 700}}, ) + await hass.async_block_till_done() client = await hass_client() @@ -125,6 +128,7 @@ async def test_belgium_country(aioclient_mock, hass, hass_client): } }, ) + await hass.async_block_till_done() client = await hass_client() @@ -140,6 +144,7 @@ async def test_failure_response_not_cached(aioclient_mock, hass, hass_client): await async_setup_component( hass, "camera", {"camera": {"name": "config_test", "platform": "buienradar"}} ) + await hass.async_block_till_done() client = await hass_client() @@ -173,6 +178,7 @@ async def test_last_modified_updates(aioclient_mock, hass, hass_client): } }, ) + await hass.async_block_till_done() client = await hass_client() @@ -201,6 +207,7 @@ async def test_retries_after_error(aioclient_mock, hass, hass_client): await async_setup_component( hass, "camera", {"camera": {"name": "config_test", "platform": "buienradar"}} ) + await hass.async_block_till_done() client = await hass_client() From 69531593f241ca89813a114665cb5f2844e59335 Mon Sep 17 00:00:00 2001 From: Sean Leonard Date: Tue, 2 Jun 2020 17:01:16 -0700 Subject: [PATCH 328/406] Sort minecraft_server players_online sensor's players_list (#35280) Sort the players_list attribute of the minecraft_server players_online sensor in order to eliminate uneccessary state updates and ease comparisons in state changes. --- homeassistant/components/minecraft_server/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/minecraft_server/__init__.py b/homeassistant/components/minecraft_server/__init__.py index 3a8598d3fac..164bb264f90 100644 --- a/homeassistant/components/minecraft_server/__init__.py +++ b/homeassistant/components/minecraft_server/__init__.py @@ -197,6 +197,7 @@ class MinecraftServer: if status_response.players.sample is not None: for player in status_response.players.sample: self.players_list.append(player.name) + self.players_list.sort() # Inform user once about successful update if necessary. if self._last_status_request_failed: From 763ab79e6cc2b1c29df979c5ed523c3ed117d08a Mon Sep 17 00:00:00 2001 From: HomeAssistant Azure Date: Wed, 3 Jun 2020 00:03:00 +0000 Subject: [PATCH 329/406] [ci skip] Translation update --- homeassistant/components/axis/translations/ca.json | 10 ++++++++++ homeassistant/components/axis/translations/es.json | 10 ++++++++++ .../components/axis/translations/zh-Hant.json | 10 ++++++++++ .../components/binary_sensor/translations/ca.json | 4 ++-- homeassistant/components/homekit/translations/en.json | 3 ++- 5 files changed, 34 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/axis/translations/ca.json b/homeassistant/components/axis/translations/ca.json index 449feb28fc7..8cbbe638d24 100644 --- a/homeassistant/components/axis/translations/ca.json +++ b/homeassistant/components/axis/translations/ca.json @@ -24,5 +24,15 @@ "title": "Configuraci\u00f3 de dispositiu Axis" } } + }, + "options": { + "step": { + "configure_stream": { + "data": { + "stream_profile": "Selecciona el perfil de transmissi\u00f3 de v\u00eddeo a utilitzar" + }, + "title": "Opcions de transmissi\u00f3 de v\u00eddeo del dispositiu Axis" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/axis/translations/es.json b/homeassistant/components/axis/translations/es.json index f4c6cbbb3a7..38a33309145 100644 --- a/homeassistant/components/axis/translations/es.json +++ b/homeassistant/components/axis/translations/es.json @@ -24,5 +24,15 @@ "title": "Configurar dispositivo Axis" } } + }, + "options": { + "step": { + "configure_stream": { + "data": { + "stream_profile": "Selecciona el perfil de transmisi\u00f3n a usar" + }, + "title": "Opciones de transmisi\u00f3n de v\u00eddeo del dispositivo Axis" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/axis/translations/zh-Hant.json b/homeassistant/components/axis/translations/zh-Hant.json index 362d7205461..8e90d449f11 100644 --- a/homeassistant/components/axis/translations/zh-Hant.json +++ b/homeassistant/components/axis/translations/zh-Hant.json @@ -24,5 +24,15 @@ "title": "\u8a2d\u5b9a Axis \u8a2d\u5099" } } + }, + "options": { + "step": { + "configure_stream": { + "data": { + "stream_profile": "\u9078\u64c7\u6240\u8981\u4f7f\u7528\u7684\u4e32\u6d41\u8a2d\u5b9a" + }, + "title": "Axis \u8a2d\u5099\u5f71\u50cf\u4e32\u6d41\u9078\u9805" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/binary_sensor/translations/ca.json b/homeassistant/components/binary_sensor/translations/ca.json index d145344ed37..bf16523251e 100644 --- a/homeassistant/components/binary_sensor/translations/ca.json +++ b/homeassistant/components/binary_sensor/translations/ca.json @@ -143,8 +143,8 @@ "on": "Obert/a" }, "presence": { - "off": "Lliure", - "on": "Detectat" + "off": "Fora", + "on": "A casa" }, "problem": { "off": "OK", diff --git a/homeassistant/components/homekit/translations/en.json b/homeassistant/components/homekit/translations/en.json index a70f9e90ed7..1fc5ae59a0c 100644 --- a/homeassistant/components/homekit/translations/en.json +++ b/homeassistant/components/homekit/translations/en.json @@ -23,7 +23,8 @@ "advanced": { "data": { "auto_start": "Autostart (disable if using Z-Wave or other delayed start system)", - "safe_mode": "Safe Mode (enable only if pairing fails)" + "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" From 94a9b364b055a11c78d0eba06ef024b00397b5a7 Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Tue, 2 Jun 2020 20:25:12 -0400 Subject: [PATCH 330/406] Move Blink trigger_camera service to camera platform (#35635) --- homeassistant/components/blink/__init__.py | 14 ------------- homeassistant/components/blink/camera.py | 22 ++++++++++++++++++-- homeassistant/components/blink/services.yaml | 8 +++---- 3 files changed, 24 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/blink/__init__.py b/homeassistant/components/blink/__init__.py index 3576574c357..04f9652bcb5 100644 --- a/homeassistant/components/blink/__init__.py +++ b/homeassistant/components/blink/__init__.py @@ -25,12 +25,10 @@ from .const import ( SERVICE_REFRESH, SERVICE_SAVE_VIDEO, SERVICE_SEND_PIN, - SERVICE_TRIGGER, ) _LOGGER = logging.getLogger(__name__) -SERVICE_TRIGGER_SCHEMA = vol.Schema({vol.Required(CONF_NAME): cv.string}) SERVICE_SAVE_VIDEO_SCHEMA = vol.Schema( {vol.Required(CONF_NAME): cv.string, vol.Required(CONF_FILENAME): cv.string} ) @@ -106,14 +104,6 @@ async def async_setup_entry(hass, entry): hass.config_entries.async_forward_entry_setup(entry, component) ) - def trigger_camera(call): - """Trigger a camera.""" - cameras = hass.data[DOMAIN][entry.entry_id].cameras - name = call.data[CONF_NAME] - if name in cameras: - cameras[name].snap_picture() - blink_refresh() - def blink_refresh(event_time=None): """Call blink to refresh info.""" hass.data[DOMAIN][entry.entry_id].refresh(force_cache=True) @@ -130,9 +120,6 @@ async def async_setup_entry(hass, entry): ) hass.services.async_register(DOMAIN, SERVICE_REFRESH, blink_refresh) - hass.services.async_register( - DOMAIN, SERVICE_TRIGGER, trigger_camera, schema=SERVICE_TRIGGER_SCHEMA - ) hass.services.async_register( DOMAIN, SERVICE_SAVE_VIDEO, async_save_video, schema=SERVICE_SAVE_VIDEO_SCHEMA ) @@ -163,7 +150,6 @@ async def async_unload_entry(hass, entry): return True hass.services.async_remove(DOMAIN, SERVICE_REFRESH) - hass.services.async_remove(DOMAIN, SERVICE_TRIGGER) hass.services.async_remove(DOMAIN, SERVICE_SAVE_VIDEO_SCHEMA) hass.services.async_remove(DOMAIN, SERVICE_SEND_PIN) diff --git a/homeassistant/components/blink/camera.py b/homeassistant/components/blink/camera.py index c675d4dda56..d4282bed606 100644 --- a/homeassistant/components/blink/camera.py +++ b/homeassistant/components/blink/camera.py @@ -1,15 +1,22 @@ """Support for Blink system camera.""" import logging -from homeassistant.components.camera import Camera +import voluptuous as vol -from .const import DEFAULT_BRAND, DOMAIN +from homeassistant.components.camera import Camera +from homeassistant.const import ATTR_ENTITY_ID +from homeassistant.helpers import entity_platform +import homeassistant.helpers.config_validation as cv + +from .const import DEFAULT_BRAND, DOMAIN, SERVICE_TRIGGER _LOGGER = logging.getLogger(__name__) ATTR_VIDEO_CLIP = "video" ATTR_IMAGE = "image" +SERVICE_TRIGGER_SCHEMA = vol.Schema({vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids}) + async def async_setup_entry(hass, config, async_add_entities): """Set up a Blink Camera.""" @@ -20,6 +27,12 @@ async def async_setup_entry(hass, config, async_add_entities): async_add_entities(entities) + platform = entity_platform.current_platform.get() + + platform.async_register_entity_service( + SERVICE_TRIGGER, SERVICE_TRIGGER_SCHEMA, "trigger_camera" + ) + class BlinkCamera(Camera): """An implementation of a Blink Camera.""" @@ -69,6 +82,11 @@ class BlinkCamera(Camera): """Return the camera brand.""" return DEFAULT_BRAND + def trigger_camera(self): + """Trigger camera to take a snapshot.""" + self._camera.snap_picture() + self.data.refresh() + def camera_image(self): """Return a still image response from the camera.""" return self._camera.image_from_cache.content diff --git a/homeassistant/components/blink/services.yaml b/homeassistant/components/blink/services.yaml index 9a4d00ee0b8..dc6491e2139 100644 --- a/homeassistant/components/blink/services.yaml +++ b/homeassistant/components/blink/services.yaml @@ -4,11 +4,11 @@ blink_update: description: Force a refresh. trigger_camera: - description: Request named camera to take new image. + description: Request camera to take new image. fields: - name: - description: Name of camera to take new image. - example: "Living Room" + entity_id: + description: Name(s) of camera entities to take new image. + example: "camera.living_room_camera" save_video: description: Save last recorded video clip to local file. From d2e6b863b7776fbe4c88715e278cd05abf0eeff9 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 3 Jun 2020 02:29:49 +0200 Subject: [PATCH 331/406] Upgrade wled 0.4.1 (#36091) --- homeassistant/components/wled/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/wled/__init__.py | 12 ++++++++---- tests/components/wled/test_config_flow.py | 14 ++++++++++---- tests/components/wled/test_init.py | 8 ++++---- tests/components/wled/test_light.py | 11 ++++++----- tests/components/wled/test_switch.py | 9 +++++---- 8 files changed, 36 insertions(+), 24 deletions(-) diff --git a/homeassistant/components/wled/manifest.json b/homeassistant/components/wled/manifest.json index 0e5bb990bae..aa3f944ed1e 100644 --- a/homeassistant/components/wled/manifest.json +++ b/homeassistant/components/wled/manifest.json @@ -3,7 +3,7 @@ "name": "WLED", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/wled", - "requirements": ["wled==0.3.0"], + "requirements": ["wled==0.4.1"], "zeroconf": ["_wled._tcp.local."], "codeowners": ["@frenck"], "quality_scale": "platinum" diff --git a/requirements_all.txt b/requirements_all.txt index b18b2048b11..a835730dbae 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2195,7 +2195,7 @@ wirelesstagpy==0.4.0 withings-api==2.1.3 # homeassistant.components.wled -wled==0.3.0 +wled==0.4.1 # homeassistant.components.xbee xbee-helper==0.0.7 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 12e90ae36f1..1287a65fcdd 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -898,7 +898,7 @@ wiffi==1.0.0 withings-api==2.1.3 # homeassistant.components.wled -wled==0.3.0 +wled==0.4.1 # homeassistant.components.bluesound # homeassistant.components.rest diff --git a/tests/components/wled/__init__.py b/tests/components/wled/__init__.py index f6bd0643450..487ccd9ab9e 100644 --- a/tests/components/wled/__init__.py +++ b/tests/components/wled/__init__.py @@ -1,5 +1,7 @@ """Tests for the WLED integration.""" +import json + from homeassistant.components.wled.const import DOMAIN from homeassistant.const import CONF_HOST, CONF_MAC from homeassistant.core import HomeAssistant @@ -17,27 +19,29 @@ async def init_integration( """Set up the WLED integration in Home Assistant.""" fixture = "wled/rgb.json" if not rgbw else "wled/rgbw.json" + data = json.loads(load_fixture(fixture)) + aioclient_mock.get( "http://192.168.1.123:80/json/", - text=load_fixture(fixture), + json=data, headers={"Content-Type": "application/json"}, ) aioclient_mock.post( "http://192.168.1.123:80/json/state", - json={}, + json=data["state"], headers={"Content-Type": "application/json"}, ) aioclient_mock.get( "http://192.168.1.123:80/json/info", - json={}, + json=data["info"], headers={"Content-Type": "application/json"}, ) aioclient_mock.get( "http://192.168.1.123:80/json/state", - json={}, + json=data["state"], headers={"Content-Type": "application/json"}, ) diff --git a/tests/components/wled/test_config_flow.py b/tests/components/wled/test_config_flow.py index 6de14a024d4..f5f1ec3099c 100644 --- a/tests/components/wled/test_config_flow.py +++ b/tests/components/wled/test_config_flow.py @@ -1,5 +1,6 @@ """Tests for the WLED config flow.""" import aiohttp +from wled import WLEDConnectionError from homeassistant import data_entry_flow from homeassistant.components.wled import config_flow @@ -9,6 +10,7 @@ from homeassistant.core import HomeAssistant from . import init_integration +from tests.async_mock import MagicMock, patch from tests.common import load_fixture from tests.test_util.aiohttp import AiohttpClientMocker @@ -59,8 +61,9 @@ async def test_show_zerconf_form( assert result["type"] == data_entry_flow.RESULT_TYPE_FORM +@patch("homeassistant.components.wled.WLED.update", side_effect=WLEDConnectionError) async def test_connection_error( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker + update_mock: MagicMock, hass: HomeAssistant, aioclient_mock: AiohttpClientMocker ) -> None: """Test we show user form on WLED connection error.""" aioclient_mock.get("http://example.com/json/", exc=aiohttp.ClientError) @@ -76,8 +79,9 @@ async def test_connection_error( assert result["type"] == data_entry_flow.RESULT_TYPE_FORM +@patch("homeassistant.components.wled.WLED.update", side_effect=WLEDConnectionError) async def test_zeroconf_connection_error( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker + update_mock: MagicMock, hass: HomeAssistant, aioclient_mock: AiohttpClientMocker ) -> None: """Test we abort zeroconf flow on WLED connection error.""" aioclient_mock.get("http://192.168.1.123/json/", exc=aiohttp.ClientError) @@ -92,8 +96,9 @@ async def test_zeroconf_connection_error( assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT +@patch("homeassistant.components.wled.WLED.update", side_effect=WLEDConnectionError) async def test_zeroconf_confirm_connection_error( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker + update_mock: MagicMock, hass: HomeAssistant, aioclient_mock: AiohttpClientMocker ) -> None: """Test we abort zeroconf flow on WLED connection error.""" aioclient_mock.get("http://192.168.1.123:80/json/", exc=aiohttp.ClientError) @@ -112,8 +117,9 @@ async def test_zeroconf_confirm_connection_error( assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT +@patch("homeassistant.components.wled.WLED.update", side_effect=WLEDConnectionError) async def test_zeroconf_no_data( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker + update_mock: MagicMock, hass: HomeAssistant, aioclient_mock: AiohttpClientMocker ) -> None: """Test we abort if zeroconf provides no data.""" flow = config_flow.WLEDFlowHandler() diff --git a/tests/components/wled/test_init.py b/tests/components/wled/test_init.py index 053c5ebaca0..8ed043530ae 100644 --- a/tests/components/wled/test_init.py +++ b/tests/components/wled/test_init.py @@ -1,20 +1,20 @@ """Tests for the WLED integration.""" -import aiohttp +from wled import WLEDConnectionError from homeassistant.components.wled.const import DOMAIN from homeassistant.config_entries import ENTRY_STATE_SETUP_RETRY from homeassistant.core import HomeAssistant +from tests.async_mock import MagicMock, patch from tests.components.wled import init_integration from tests.test_util.aiohttp import AiohttpClientMocker +@patch("homeassistant.components.wled.WLED.update", side_effect=WLEDConnectionError) async def test_config_entry_not_ready( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker + mock_update: MagicMock, hass: HomeAssistant, aioclient_mock: AiohttpClientMocker ) -> None: """Test the WLED configuration entry not ready.""" - aioclient_mock.get("http://192.168.1.123:80/json/", exc=aiohttp.ClientError) - entry = await init_integration(hass, aioclient_mock) assert entry.state == ENTRY_STATE_SETUP_RETRY diff --git a/tests/components/wled/test_light.py b/tests/components/wled/test_light.py index f2cc5514a2e..307f1e56411 100644 --- a/tests/components/wled/test_light.py +++ b/tests/components/wled/test_light.py @@ -1,5 +1,5 @@ """Tests for the WLED light platform.""" -import aiohttp +from wled import WLEDConnectionError from homeassistant.components.light import ( ATTR_BRIGHTNESS, @@ -144,7 +144,7 @@ async def test_light_error( aioclient_mock.post("http://192.168.1.123:80/json/state", text="", status=400) await init_integration(hass, aioclient_mock) - with patch("homeassistant.components.wled.WLEDDataUpdateCoordinator.async_refresh"): + with patch("homeassistant.components.wled.WLED.update"): await hass.services.async_call( LIGHT_DOMAIN, SERVICE_TURN_OFF, @@ -162,10 +162,11 @@ async def test_light_connection_error( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker ) -> None: """Test error handling of the WLED switches.""" - aioclient_mock.post("http://192.168.1.123:80/json/state", exc=aiohttp.ClientError) await init_integration(hass, aioclient_mock) - with patch("homeassistant.components.wled.WLEDDataUpdateCoordinator.async_refresh"): + with patch("homeassistant.components.wled.WLED.update"), patch( + "homeassistant.components.wled.WLED.light", side_effect=WLEDConnectionError + ): await hass.services.async_call( LIGHT_DOMAIN, SERVICE_TURN_OFF, @@ -342,7 +343,7 @@ async def test_effect_service_error( aioclient_mock.post("http://192.168.1.123:80/json/state", text="", status=400) await init_integration(hass, aioclient_mock) - with patch("homeassistant.components.wled.WLEDDataUpdateCoordinator.async_refresh"): + with patch("homeassistant.components.wled.WLED.update"): await hass.services.async_call( DOMAIN, SERVICE_EFFECT, diff --git a/tests/components/wled/test_switch.py b/tests/components/wled/test_switch.py index 7d411984cff..388e3317b39 100644 --- a/tests/components/wled/test_switch.py +++ b/tests/components/wled/test_switch.py @@ -1,5 +1,5 @@ """Tests for the WLED switch platform.""" -import aiohttp +from wled import WLEDConnectionError from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.components.wled.const import ( @@ -142,7 +142,7 @@ async def test_switch_error( aioclient_mock.post("http://192.168.1.123:80/json/state", text="", status=400) await init_integration(hass, aioclient_mock) - with patch("homeassistant.components.wled.WLEDDataUpdateCoordinator.async_refresh"): + with patch("homeassistant.components.wled.WLED.update"): await hass.services.async_call( SWITCH_DOMAIN, SERVICE_TURN_ON, @@ -160,10 +160,11 @@ async def test_switch_connection_error( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker ) -> None: """Test error handling of the WLED switches.""" - aioclient_mock.post("http://192.168.1.123:80/json/state", exc=aiohttp.ClientError) await init_integration(hass, aioclient_mock) - with patch("homeassistant.components.wled.WLEDDataUpdateCoordinator.async_refresh"): + with patch("homeassistant.components.wled.WLED.update"), patch( + "homeassistant.components.wled.WLED.nightlight", side_effect=WLEDConnectionError + ): await hass.services.async_call( SWITCH_DOMAIN, SERVICE_TURN_ON, From a5d520b60307dbdf2961d8340b1ef9377f3a2a76 Mon Sep 17 00:00:00 2001 From: fb22 <4872297+fb22@users.noreply.github.com> Date: Wed, 3 Jun 2020 02:32:08 +0200 Subject: [PATCH 332/406] Add llamalab_automate optional message delivery priority (#34234) * Add optional message delivery priority * Sort components.notify import * Sort components.notify import --- .../components/llamalab_automate/notify.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/llamalab_automate/notify.py b/homeassistant/components/llamalab_automate/notify.py index f093edbbc6b..b4ed9a4e628 100644 --- a/homeassistant/components/llamalab_automate/notify.py +++ b/homeassistant/components/llamalab_automate/notify.py @@ -4,13 +4,19 @@ import logging import requests import voluptuous as vol -from homeassistant.components.notify import PLATFORM_SCHEMA, BaseNotificationService +from homeassistant.components.notify import ( + ATTR_DATA, + PLATFORM_SCHEMA, + BaseNotificationService, +) from homeassistant.const import CONF_API_KEY, CONF_DEVICE, HTTP_OK from homeassistant.helpers import config_validation as cv _LOGGER = logging.getLogger(__name__) _RESOURCE = "https://llamalab.com/automate/cloud/message" +ATTR_PRIORITY = "priority" + CONF_TO = "to" PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( @@ -42,11 +48,20 @@ class AutomateNotificationService(BaseNotificationService): def send_message(self, message="", **kwargs): """Send a message to a user.""" - _LOGGER.debug("Sending to: %s, %s", self._recipient, str(self._device)) + + # Extract params from data dict + data = dict(kwargs.get(ATTR_DATA) or {}) + priority = data.get(ATTR_PRIORITY, "Normal") + + _LOGGER.debug( + "Sending to: %s, %s, prio: %s", self._recipient, str(self._device), priority + ) + data = { "secret": self._secret, "to": self._recipient, "device": self._device, + "priority": priority, "payload": message, } From e94228fddfc5215055afaeb30dd0b39110447ceb Mon Sep 17 00:00:00 2001 From: Markus Bong Date: Wed, 3 Jun 2020 02:52:36 +0200 Subject: [PATCH 333/406] Use show_advanced_options in devolo home control (#35360) --- .../devolo_home_control/config_flow.py | 17 +++-- .../devolo_home_control/test_config_flow.py | 65 ++++++++++++++----- 2 files changed, 60 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/devolo_home_control/config_flow.py b/homeassistant/components/devolo_home_control/config_flow.py index d104bdde275..93b2cfc11e5 100644 --- a/homeassistant/components/devolo_home_control/config_flow.py +++ b/homeassistant/components/devolo_home_control/config_flow.py @@ -30,12 +30,17 @@ class DevoloHomeControlFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): self.data_schema = { vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str, - vol.Required(CONF_MYDEVOLO, default=DEFAULT_MYDEVOLO): str, - vol.Required(CONF_HOMECONTROL, default=DEFAULT_MPRM): str, } async def async_step_user(self, user_input=None): """Handle a flow initiated by the user.""" + if self.show_advanced_options: + self.data_schema = { + vol.Required(CONF_USERNAME): str, + vol.Required(CONF_PASSWORD): str, + vol.Required(CONF_MYDEVOLO): str, + vol.Required(CONF_HOMECONTROL): str, + } if user_input is None: return self._show_form(user_input) user = user_input[CONF_USERNAME] @@ -46,8 +51,12 @@ class DevoloHomeControlFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): mydevolo = Mydevolo() mydevolo.user = user mydevolo.password = password - mydevolo.url = user_input[CONF_MYDEVOLO] - mydevolo.mprm = user_input[CONF_HOMECONTROL] + if self.show_advanced_options: + mydevolo.url = user_input[CONF_MYDEVOLO] + mydevolo.mprm = user_input[CONF_HOMECONTROL] + else: + mydevolo.url = DEFAULT_MYDEVOLO + mydevolo.mprm = DEFAULT_MPRM credentials_valid = await self.hass.async_add_executor_job( mydevolo.credentials_valid ) diff --git a/tests/components/devolo_home_control/test_config_flow.py b/tests/components/devolo_home_control/test_config_flow.py index aacf33b69c1..01b4603257a 100644 --- a/tests/components/devolo_home_control/test_config_flow.py +++ b/tests/components/devolo_home_control/test_config_flow.py @@ -30,12 +30,7 @@ async def test_form(hass): ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], - { - "username": "test-username", - "password": "test-password", - "home_control_url": "https://homecontrol.mydevolo.com", - "mydevolo_url": "https://www.mydevolo.com", - }, + {"username": "test-username", "password": "test-password"}, ) assert result2["type"] == "create_entry" @@ -67,12 +62,7 @@ async def test_form_invalid_credentials(hass): ): result = await hass.config_entries.flow.async_configure( result["flow_id"], - { - "username": "test-username", - "password": "test-password", - "home_control_url": "https://homecontrol.mydevolo.com", - "mydevolo_url": "https://www.mydevolo.com", - }, + {"username": "test-username", "password": "test-password"}, ) assert result["errors"] == {"base": "invalid_credentials"} @@ -91,12 +81,51 @@ async def test_form_already_configured(hass): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER}, - data={ - "username": "test-username", - "password": "test-password", - "home_control_url": "https://homecontrol.mydevolo.com", - "mydevolo_url": "https://www.mydevolo.com", - }, + data={"username": "test-username", "password": "test-password"}, ) assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result["reason"] == "already_configured" + + +async def test_form_advanced_options(hass): + """Test if we get the advanced options if user has enabled it.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "user", "show_advanced_options": True} + ) + assert result["type"] == "form" + assert result["errors"] == {} + + with patch( + "homeassistant.components.devolo_home_control.async_setup", return_value=True, + ) as mock_setup, patch( + "homeassistant.components.devolo_home_control.async_setup_entry", + return_value=True, + ) as mock_setup_entry, patch( + "homeassistant.components.devolo_home_control.config_flow.Mydevolo.credentials_valid", + return_value=True, + ), patch( + "homeassistant.components.devolo_home_control.config_flow.Mydevolo.get_gateway_ids", + return_value=["123456"], + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "username": "test-username", + "password": "test-password", + "home_control_url": "https://test_url.test", + "mydevolo_url": "https://test_mydevolo_url.test", + }, + ) + + assert result2["type"] == "create_entry" + assert result2["title"] == "devolo Home Control" + assert result2["data"] == { + "username": "test-username", + "password": "test-password", + "home_control_url": "https://test_url.test", + "mydevolo_url": "https://test_mydevolo_url.test", + } + + await hass.async_block_till_done() + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 From 2f6ffe706873ce975792efca40f01c54828a7da5 Mon Sep 17 00:00:00 2001 From: Boris Kaplounovsky Date: Wed, 3 Jun 2020 03:55:55 +0300 Subject: [PATCH 334/406] Fix json overwriting if you have >1 PS4 connected (#35778) --- homeassistant/components/ps4/__init__.py | 14 +++++++------- homeassistant/components/ps4/const.py | 2 +- homeassistant/components/ps4/media_player.py | 6 +++--- tests/components/ps4/test_init.py | 10 +++++----- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/ps4/__init__.py b/homeassistant/components/ps4/__init__.py index d2c6e9859de..2a7a667088e 100644 --- a/homeassistant/components/ps4/__init__.py +++ b/homeassistant/components/ps4/__init__.py @@ -157,9 +157,9 @@ def format_unique_id(creds, mac_address): return f"{mac_address}_{suffix}" -def load_games(hass: HomeAssistantType) -> dict: +def load_games(hass: HomeAssistantType, unique_id: str) -> dict: """Load games for sources.""" - g_file = hass.config.path(GAMES_FILE) + g_file = hass.config.path(GAMES_FILE.format(unique_id)) try: games = load_json(g_file, dict) except HomeAssistantError as error: @@ -172,20 +172,20 @@ def load_games(hass: HomeAssistantType) -> dict: # If file exists if os.path.isfile(g_file): - games = _reformat_data(hass, games) + games = _reformat_data(hass, games, unique_id) return games -def save_games(hass: HomeAssistantType, games: dict): +def save_games(hass: HomeAssistantType, games: dict, unique_id: str): """Save games to file.""" - g_file = hass.config.path(GAMES_FILE) + g_file = hass.config.path(GAMES_FILE.format(unique_id)) try: save_json(g_file, games) except OSError as error: _LOGGER.error("Could not save game list, %s", error) -def _reformat_data(hass: HomeAssistantType, games: dict) -> dict: +def _reformat_data(hass: HomeAssistantType, games: dict, unique_id: str) -> dict: """Reformat data to correct format.""" data_reformatted = False @@ -204,7 +204,7 @@ def _reformat_data(hass: HomeAssistantType, games: dict) -> dict: _LOGGER.debug("Reformatting media data for item: %s, %s", game, data) if data_reformatted: - save_games(hass, games) + save_games(hass, games, unique_id) return games diff --git a/homeassistant/components/ps4/const.py b/homeassistant/components/ps4/const.py index 779da61ca48..0974286ebe8 100644 --- a/homeassistant/components/ps4/const.py +++ b/homeassistant/components/ps4/const.py @@ -5,7 +5,7 @@ DEFAULT_NAME = "PlayStation 4" DEFAULT_REGION = "United States" DEFAULT_ALIAS = "Home-Assistant" DOMAIN = "ps4" -GAMES_FILE = ".ps4-games.json" +GAMES_FILE = ".ps4-games.{}.json" PS4_DATA = "ps4_data" COMMANDS = ("up", "down", "right", "left", "enter", "back", "option", "ps", "ps_hold") diff --git a/homeassistant/components/ps4/media_player.py b/homeassistant/components/ps4/media_player.py index 39b60be0493..9bd4ddbefbe 100644 --- a/homeassistant/components/ps4/media_player.py +++ b/homeassistant/components/ps4/media_player.py @@ -163,7 +163,7 @@ class PS4Device(MediaPlayerEntity): status = self._ps4.status if status is not None: - self._games = load_games(self.hass) + self._games = load_games(self.hass, self._unique_id) if self._games: self.get_source_list() @@ -300,7 +300,7 @@ class PS4Device(MediaPlayerEntity): self._media_image, self._media_type, ) - self._games = load_games(self.hass) + self._games = load_games(self.hass, self._unique_id) self.get_source_list() @@ -324,7 +324,7 @@ class PS4Device(MediaPlayerEntity): } } games.update(game) - save_games(self.hass, games) + save_games(self.hass, games, self._unique_id) async def async_get_device_info(self, status): """Set device info for registry.""" diff --git a/tests/components/ps4/test_init.py b/tests/components/ps4/test_init.py index 7f7eff33ebb..75a983cc97a 100644 --- a/tests/components/ps4/test_init.py +++ b/tests/components/ps4/test_init.py @@ -201,7 +201,7 @@ def test_games_reformat_to_dict(hass): ), patch("homeassistant.components.ps4.save_json", side_effect=MagicMock()), patch( "os.path.isfile", return_value=True ): - mock_games = ps4.load_games(hass) + mock_games = ps4.load_games(hass, MOCK_ENTRY_ID) # New format is a nested dict. assert isinstance(mock_games, dict) @@ -223,7 +223,7 @@ def test_load_games(hass): ), patch("homeassistant.components.ps4.save_json", side_effect=MagicMock()), patch( "os.path.isfile", return_value=True ): - mock_games = ps4.load_games(hass) + mock_games = ps4.load_games(hass, MOCK_ENTRY_ID) assert isinstance(mock_games, dict) @@ -242,7 +242,7 @@ def test_loading_games_returns_dict(hass): ), patch("homeassistant.components.ps4.save_json", side_effect=MagicMock()), patch( "os.path.isfile", return_value=True ): - mock_games = ps4.load_games(hass) + mock_games = ps4.load_games(hass, MOCK_ENTRY_ID) assert isinstance(mock_games, dict) assert not mock_games @@ -252,7 +252,7 @@ def test_loading_games_returns_dict(hass): ), patch("homeassistant.components.ps4.save_json", side_effect=MagicMock()), patch( "os.path.isfile", return_value=True ): - mock_games = ps4.load_games(hass) + mock_games = ps4.load_games(hass, MOCK_ENTRY_ID) assert isinstance(mock_games, dict) assert not mock_games @@ -260,7 +260,7 @@ def test_loading_games_returns_dict(hass): with patch("homeassistant.components.ps4.load_json", return_value=[]), patch( "homeassistant.components.ps4.save_json", side_effect=MagicMock() ), patch("os.path.isfile", return_value=True): - mock_games = ps4.load_games(hass) + mock_games = ps4.load_games(hass, MOCK_ENTRY_ID) assert isinstance(mock_games, dict) assert not mock_games From 544094af21a133f2d618af995d5e19d39df68bab Mon Sep 17 00:00:00 2001 From: "J.P. Hutchins" <34154542+JPHutchins@users.noreply.github.com> Date: Tue, 2 Jun 2020 18:13:15 -0700 Subject: [PATCH 335/406] Suppress error for start_torrents toggle (#35799) --- homeassistant/components/transmission/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/components/transmission/__init__.py b/homeassistant/components/transmission/__init__.py index 32177e91160..a09fb6a7456 100644 --- a/homeassistant/components/transmission/__init__.py +++ b/homeassistant/components/transmission/__init__.py @@ -362,6 +362,8 @@ class TransmissionData: def start_torrents(self): """Start all torrents.""" + if len(self.torrents) <= 0: + return self._api.start_all() def stop_torrents(self): From b5f12bd9c1c7ebd8ec6aec46f0a87c164f9bab8b Mon Sep 17 00:00:00 2001 From: Tom Harris Date: Tue, 2 Jun 2020 21:16:44 -0400 Subject: [PATCH 336/406] Add climate platform to Insteon (#35763) --- homeassistant/components/insteon/climate.py | 228 ++++++++++++++++++ homeassistant/components/insteon/const.py | 3 + .../components/insteon/insteon_entity.py | 9 + homeassistant/components/insteon/ipdb.py | 5 + homeassistant/components/insteon/schemas.py | 3 + .../components/insteon/services.yaml | 6 + homeassistant/components/insteon/utils.py | 17 ++ 7 files changed, 271 insertions(+) create mode 100644 homeassistant/components/insteon/climate.py diff --git a/homeassistant/components/insteon/climate.py b/homeassistant/components/insteon/climate.py new file mode 100644 index 00000000000..79af0892f94 --- /dev/null +++ b/homeassistant/components/insteon/climate.py @@ -0,0 +1,228 @@ +"""Support for Insteon thermostat.""" +import logging +from typing import List, Optional + +from pyinsteon.constants import ThermostatMode +from pyinsteon.operating_flag import CELSIUS + +from homeassistant.components.climate import ClimateEntity +from homeassistant.components.climate.const import ( + ATTR_TARGET_TEMP_HIGH, + ATTR_TARGET_TEMP_LOW, + CURRENT_HVAC_COOL, + CURRENT_HVAC_FAN, + CURRENT_HVAC_HEAT, + CURRENT_HVAC_IDLE, + DOMAIN, + HVAC_MODE_AUTO, + HVAC_MODE_COOL, + HVAC_MODE_FAN_ONLY, + HVAC_MODE_HEAT, + HVAC_MODE_HEAT_COOL, + HVAC_MODE_OFF, + SUPPORT_FAN_MODE, + SUPPORT_TARGET_HUMIDITY, + SUPPORT_TARGET_TEMPERATURE, + SUPPORT_TARGET_TEMPERATURE_RANGE, +) +from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT + +from .insteon_entity import InsteonEntity +from .utils import async_add_insteon_entities + +_LOGGER = logging.getLogger(__name__) + +COOLING = 1 +HEATING = 2 +DEHUMIDIFYING = 3 +HUMIDIFYING = 4 + +TEMPERATURE = 10 +HUMIDITY = 11 +SYSTEM_MODE = 12 +FAN_MODE = 13 +COOL_SET_POINT = 14 +HEAT_SET_POINT = 15 +HUMIDITY_HIGH = 16 +HUMIDITY_LOW = 17 + + +HVAC_MODES = { + 0: HVAC_MODE_OFF, + 1: HVAC_MODE_HEAT, + 2: HVAC_MODE_COOL, + 3: HVAC_MODE_HEAT_COOL, +} +FAN_MODES = {4: HVAC_MODE_AUTO, 8: HVAC_MODE_FAN_ONLY} +SUPPORTED_FEATURES = ( + SUPPORT_FAN_MODE + | SUPPORT_TARGET_HUMIDITY + | SUPPORT_TARGET_TEMPERATURE + | SUPPORT_TARGET_TEMPERATURE_RANGE +) + + +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): + """Set up the Insteon platform.""" + async_add_insteon_entities( + hass, DOMAIN, InsteonClimateEntity, async_add_entities, discovery_info + ) + + +class InsteonClimateEntity(InsteonEntity, ClimateEntity): + """A Class for an Insteon climate entity.""" + + @property + def supported_features(self): + """Return the supported features for this entity.""" + return SUPPORTED_FEATURES + + @property + def temperature_unit(self) -> str: + """Return the unit of measurement.""" + if self._insteon_device.properties[CELSIUS].value: + return TEMP_CELSIUS + return TEMP_FAHRENHEIT + + @property + def current_humidity(self) -> Optional[int]: + """Return the current humidity.""" + return self._insteon_device.groups[HUMIDITY].value + + @property + def hvac_mode(self) -> str: + """Return hvac operation ie. heat, cool mode.""" + return HVAC_MODES[self._insteon_device.groups[SYSTEM_MODE].value] + + @property + def hvac_modes(self) -> List[str]: + """Return the list of available hvac operation modes.""" + return list(HVAC_MODES.values()) + + @property + def current_temperature(self) -> Optional[float]: + """Return the current temperature.""" + return self._insteon_device.groups[TEMPERATURE].value + + @property + def target_temperature(self) -> Optional[float]: + """Return the temperature we try to reach.""" + if self._insteon_device.groups[SYSTEM_MODE].value == ThermostatMode.HEAT: + return self._insteon_device.groups[HEAT_SET_POINT].value + if self._insteon_device.groups[SYSTEM_MODE].value == ThermostatMode.COOL: + return self._insteon_device.groups[COOL_SET_POINT].value + return None + + @property + def target_temperature_high(self) -> Optional[float]: + """Return the highbound target temperature we try to reach.""" + if self._insteon_device.groups[SYSTEM_MODE].value == ThermostatMode.AUTO: + return self._insteon_device.groups[COOL_SET_POINT].value + return None + + @property + def target_temperature_low(self) -> Optional[float]: + """Return the lowbound target temperature we try to reach.""" + if self._insteon_device.groups[SYSTEM_MODE].value == ThermostatMode.AUTO: + return self._insteon_device.groups[HEAT_SET_POINT].value + return None + + @property + def fan_mode(self) -> Optional[str]: + """Return the fan setting.""" + return FAN_MODES[self._insteon_device.groups[FAN_MODE].value] + + @property + def fan_modes(self) -> Optional[List[str]]: + """Return the list of available fan modes.""" + return list(FAN_MODES.values()) + + @property + def target_humidity(self) -> Optional[int]: + """Return the humidity we try to reach.""" + high = self._insteon_device.groups[HUMIDITY_HIGH].value + low = self._insteon_device.groups[HUMIDITY_LOW].value + # May not be loaded yet so return a default if required + return (high + low) / 2 if high and low else None + + @property + def min_humidity(self) -> int: + """Return the minimum humidity.""" + return 1 + + @property + def hvac_action(self) -> Optional[str]: + """Return the current running hvac operation if supported. + + Need to be one of CURRENT_HVAC_*. + """ + if self._insteon_device.groups[COOLING].value: + return CURRENT_HVAC_COOL + if self._insteon_device.groups[HEATING].value: + return CURRENT_HVAC_HEAT + if self._insteon_device.groups[FAN_MODE].value == ThermostatMode.FAN_ALWAYS_ON: + return CURRENT_HVAC_FAN + return CURRENT_HVAC_IDLE + + @property + def device_state_attributes(self): + """Provide attributes for display on device card.""" + attr = super().device_state_attributes + humidifier = "off" + if self._insteon_device.groups[DEHUMIDIFYING].value: + humidifier = "dehumidifying" + if self._insteon_device.groups[HUMIDIFYING].value: + humidifier = "humidifying" + attr["humidifier"] = humidifier + return attr + + async def async_set_temperature(self, **kwargs) -> None: + """Set new target temperature.""" + target_temp = kwargs.get(ATTR_TEMPERATURE) + target_temp_low = kwargs.get(ATTR_TARGET_TEMP_LOW) + target_temp_high = kwargs.get(ATTR_TARGET_TEMP_HIGH) + if target_temp is not None: + if self._insteon_device.groups[SYSTEM_MODE].value == ThermostatMode.HEAT: + await self._insteon_device.async_set_heat_set_point(target_temp) + elif self._insteon_device.groups[SYSTEM_MODE].value == ThermostatMode.COOL: + await self._insteon_device.async_set_cool_set_point(target_temp) + else: + await self._insteon_device.async_set_heat_set_point(target_temp_low) + await self._insteon_device.async_set_cool_set_point(target_temp_high) + + async def async_set_fan_mode(self, fan_mode: str) -> None: + """Set new target fan mode.""" + mode = list(FAN_MODES.keys())[list(FAN_MODES.values()).index(fan_mode)] + await self._insteon_device.async_set_mode(mode) + + async def async_set_hvac_mode(self, hvac_mode: str) -> None: + """Set new target hvac mode.""" + mode = list(HVAC_MODES.keys())[list(HVAC_MODES.values()).index(hvac_mode)] + await self._insteon_device.async_set_mode(mode) + + async def async_set_humidity(self, humidity): + """Set new humidity level.""" + change = humidity - self.target_humidity + high = self._insteon_device.groups[HUMIDITY_HIGH].value + change + low = self._insteon_device.groups[HUMIDITY_LOW].value + change + await self._insteon_device.async_set_humidity_low_set_point(low) + await self._insteon_device.async_set_humidity_high_set_point(high) + + async def async_added_to_hass(self): + """Register INSTEON update events.""" + await super().async_added_to_hass() + await self._insteon_device.async_read_op_flags() + for group in [ + COOLING, + HEATING, + DEHUMIDIFYING, + HUMIDIFYING, + HEAT_SET_POINT, + FAN_MODE, + SYSTEM_MODE, + TEMPERATURE, + HUMIDITY, + HUMIDITY_HIGH, + HUMIDITY_LOW, + ]: + self._insteon_device.groups[group].subscribe(self.async_entity_update) diff --git a/homeassistant/components/insteon/const.py b/homeassistant/components/insteon/const.py index 950efd8dc7f..c55d733b73d 100644 --- a/homeassistant/components/insteon/const.py +++ b/homeassistant/components/insteon/const.py @@ -36,6 +36,7 @@ DOMAIN = "insteon" INSTEON_COMPONENTS = [ "binary_sensor", + "climate", "cover", "fan", "light", @@ -76,10 +77,12 @@ SRV_RESPONDER = "responder" SRV_HOUSECODE = "housecode" SRV_SCENE_ON = "scene_on" SRV_SCENE_OFF = "scene_off" +SRV_ADD_DEFAULT_LINKS = "add_default_links" SIGNAL_LOAD_ALDB = "load_aldb" SIGNAL_PRINT_ALDB = "print_aldb" SIGNAL_SAVE_DEVICES = "save_devices" +SIGNAL_ADD_DEFAULT_LINKS = "add_default_links" HOUSECODES = [ "a", diff --git a/homeassistant/components/insteon/insteon_entity.py b/homeassistant/components/insteon/insteon_entity.py index 80bb860477e..787c64ec841 100644 --- a/homeassistant/components/insteon/insteon_entity.py +++ b/homeassistant/components/insteon/insteon_entity.py @@ -9,6 +9,7 @@ from homeassistant.helpers.dispatcher import ( from homeassistant.helpers.entity import Entity from .const import ( + SIGNAL_ADD_DEFAULT_LINKS, SIGNAL_LOAD_ALDB, SIGNAL_PRINT_ALDB, SIGNAL_SAVE_DEVICES, @@ -96,6 +97,10 @@ class InsteonEntity(Entity): ) print_signal = f"{self.entity_id}_{SIGNAL_PRINT_ALDB}" async_dispatcher_connect(self.hass, print_signal, self._print_aldb) + default_links_signal = f"{self.entity_id}_{SIGNAL_ADD_DEFAULT_LINKS}" + async_dispatcher_connect( + self.hass, default_links_signal, self._async_add_default_links + ) async def _async_read_aldb(self, reload): """Call device load process and print to log.""" @@ -116,3 +121,7 @@ class InsteonEntity(Entity): else: label = f"Group {self.group:d}" return label + + async def _async_add_default_links(self): + """Add default links between the device and the modem.""" + await self._insteon_device.async_add_default_links(self.address) diff --git a/homeassistant/components/insteon/ipdb.py b/homeassistant/components/insteon/ipdb.py index aa3c0932919..a3e79fcd6d4 100644 --- a/homeassistant/components/insteon/ipdb.py +++ b/homeassistant/components/insteon/ipdb.py @@ -2,6 +2,8 @@ import logging from pyinsteon.device_types import ( + ClimateControl_Thermostat, + ClimateControl_WirelessThermostat, DimmableLightingControl, DimmableLightingControl_DinRail, DimmableLightingControl_FanLinc, @@ -40,6 +42,7 @@ from pyinsteon.device_types import ( ) from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR +from homeassistant.components.climate import DOMAIN as CLIMATE from homeassistant.components.cover import DOMAIN as COVER from homeassistant.components.fan import DOMAIN as FAN from homeassistant.components.light import DOMAIN as LIGHT @@ -95,6 +98,8 @@ DEVICE_PLATFORM = { SwitchedLightingControl_OutletLinc: {SWITCH: [1], ON_OFF_EVENTS: [1]}, SwitchedLightingControl_SwitchLinc: {SWITCH: [1], ON_OFF_EVENTS: [1]}, SwitchedLightingControl_ToggleLinc: {SWITCH: [1], ON_OFF_EVENTS: [1]}, + ClimateControl_Thermostat: {CLIMATE: [1]}, + ClimateControl_WirelessThermostat: {CLIMATE: [1]}, WindowCovering: {COVER: [1]}, X10Dimmable: {LIGHT: [1]}, X10OnOff: {SWITCH: [1]}, diff --git a/homeassistant/components/insteon/schemas.py b/homeassistant/components/insteon/schemas.py index b3192fc8f66..0fe8f30d95b 100644 --- a/homeassistant/components/insteon/schemas.py +++ b/homeassistant/components/insteon/schemas.py @@ -147,3 +147,6 @@ X10_HOUSECODE_SCHEMA = vol.Schema({vol.Required(SRV_HOUSECODE): vol.In(HOUSECODE TRIGGER_SCENE_SCHEMA = vol.Schema( {vol.Required(SRV_ALL_LINK_GROUP): vol.Range(min=0, max=255)} ) + + +ADD_DEFAULT_LINKS_SCHEMA = vol.Schema({vol.Required(CONF_ENTITY_ID): cv.entity_id}) diff --git a/homeassistant/components/insteon/services.yaml b/homeassistant/components/insteon/services.yaml index 3d232569e9c..716b9a1e040 100644 --- a/homeassistant/components/insteon/services.yaml +++ b/homeassistant/components/insteon/services.yaml @@ -60,3 +60,9 @@ scene_off: group: description: INSTEON group or scene number example: 26 +add_default_links: + description: Add the default links between the device and the Insteon Modem (IM) + fields: + entity_id: + description: Name of the device to load. Use "all" to load the database of all devices. + example: "light.1a2b3c" diff --git a/homeassistant/components/insteon/utils.py b/homeassistant/components/insteon/utils.py index c0b93d93485..32a0949dfeb 100644 --- a/homeassistant/components/insteon/utils.py +++ b/homeassistant/components/insteon/utils.py @@ -36,10 +36,12 @@ from .const import ( EVENT_GROUP_ON, EVENT_GROUP_ON_FAST, ON_OFF_EVENTS, + SIGNAL_ADD_DEFAULT_LINKS, SIGNAL_LOAD_ALDB, SIGNAL_PRINT_ALDB, SIGNAL_SAVE_DEVICES, SRV_ADD_ALL_LINK, + SRV_ADD_DEFAULT_LINKS, SRV_ALL_LINK_GROUP, SRV_ALL_LINK_MODE, SRV_CONTROLLER, @@ -58,6 +60,7 @@ from .const import ( from .ipdb import get_device_platforms, get_platform_groups from .schemas import ( ADD_ALL_LINK_SCHEMA, + ADD_DEFAULT_LINKS_SCHEMA, DEL_ALL_LINK_SCHEMA, LOAD_ALDB_SCHEMA, PRINT_ALDB_SCHEMA, @@ -231,6 +234,13 @@ def async_register_services(hass): group = service.data.get(SRV_ALL_LINK_GROUP) await async_trigger_scene_off(group) + @callback + def async_add_default_links(service): + """Add the default All-Link entries to a device.""" + entity_id = service.data[CONF_ENTITY_ID] + signal = f"{entity_id}_{SIGNAL_ADD_DEFAULT_LINKS}" + async_dispatcher_send(hass, signal) + hass.services.async_register( DOMAIN, SRV_ADD_ALL_LINK, async_srv_add_all_link, schema=ADD_ALL_LINK_SCHEMA ) @@ -268,6 +278,13 @@ def async_register_services(hass): hass.services.async_register( DOMAIN, SRV_SCENE_OFF, async_srv_scene_off, schema=TRIGGER_SCENE_SCHEMA ) + + hass.services.async_register( + DOMAIN, + SRV_ADD_DEFAULT_LINKS, + async_add_default_links, + schema=ADD_DEFAULT_LINKS_SCHEMA, + ) async_dispatcher_connect(hass, SIGNAL_SAVE_DEVICES, async_srv_save_devices) _LOGGER.debug("Insteon Services registered") From 7e2872bab3826ff8437743ae67adafbfc2f844f7 Mon Sep 17 00:00:00 2001 From: Vincent Le Bourlot Date: Wed, 3 Jun 2020 03:17:55 +0200 Subject: [PATCH 337/406] Add more tahoma sensors (#36256) --- homeassistant/components/tahoma/__init__.py | 2 ++ homeassistant/components/tahoma/sensor.py | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/homeassistant/components/tahoma/__init__.py b/homeassistant/components/tahoma/__init__.py index f3dd51bed96..1a6b326f4e5 100644 --- a/homeassistant/components/tahoma/__init__.py +++ b/homeassistant/components/tahoma/__init__.py @@ -66,6 +66,8 @@ TAHOMA_TYPES = { "rts:RollerShutterRTSComponent": "cover", "rts:OnOffRTSComponent": "switch", "rts:VenetianBlindRTSComponent": "cover", + "somfythermostat:SomfyThermostatTemperatureSensor": "sensor", + "somfythermostat:SomfyThermostatHumiditySensor": "sensor", } diff --git a/homeassistant/components/tahoma/sensor.py b/homeassistant/components/tahoma/sensor.py index 20364a243b3..8ceb07e1a9f 100644 --- a/homeassistant/components/tahoma/sensor.py +++ b/homeassistant/components/tahoma/sensor.py @@ -56,6 +56,13 @@ class TahomaSensor(TahomaDevice, Entity): return None if self.tahoma_device.type == "rtds:RTDSMotionSensor": return None + if ( + self.tahoma_device.type + == "somfythermostat:SomfyThermostatTemperatureSensor" + ): + return TEMP_CELSIUS + if self.tahoma_device.type == "somfythermostat:SomfyThermostatHumiditySensor": + return UNIT_PERCENTAGE def update(self): """Update the state.""" @@ -86,6 +93,19 @@ class TahomaSensor(TahomaDevice, Entity): float(self.tahoma_device.active_states["core:TemperatureState"]), 1 ) self._available = True + if ( + self.tahoma_device.type + == "somfythermostat:SomfyThermostatTemperatureSensor" + ): + self.current_value = float( + f"{self.tahoma_device.active_states['core:TemperatureState']:.2f}" + ) + self._available = True + if self.tahoma_device.type == "somfythermostat:SomfyThermostatHumiditySensor": + self.current_value = float( + f"{self.tahoma_device.active_states['core:RelativeHumidityState']:.2f}" + ) + self._available = True _LOGGER.debug("Update %s, value: %d", self._name, self.current_value) From 209f9a812ebf93b80c758906182dc5966d216f89 Mon Sep 17 00:00:00 2001 From: willscottuk Date: Wed, 3 Jun 2020 02:22:39 +0100 Subject: [PATCH 338/406] Fix Canary entries API removal (#36218) --- homeassistant/components/canary/__init__.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/homeassistant/components/canary/__init__.py b/homeassistant/components/canary/__init__.py index d6effc7eb80..165787a7f46 100644 --- a/homeassistant/components/canary/__init__.py +++ b/homeassistant/components/canary/__init__.py @@ -71,7 +71,6 @@ class CanaryData: self._locations_by_id = {} self._readings_by_device_id = {} - self._entries_by_location_id = {} self.update() @@ -82,9 +81,6 @@ class CanaryData: location_id = location.location_id self._locations_by_id[location_id] = location - self._entries_by_location_id[location_id] = self._api.get_entries( - location_id, entry_type="motion", limit=1 - ) for device in location.devices: if device.is_online: @@ -97,10 +93,6 @@ class CanaryData: """Return a list of locations.""" return self._locations_by_id.values() - def get_motion_entries(self, location_id): - """Return a list of motion entries based on location_id.""" - return self._entries_by_location_id.get(location_id, []) - def get_location(self, location_id): """Return a location based on location_id.""" return self._locations_by_id.get(location_id, []) From 8cd640867cc7556d2766a59c4c04094a64ea60ff Mon Sep 17 00:00:00 2001 From: rajlaud <50647620+rajlaud@users.noreply.github.com> Date: Tue, 2 Jun 2020 20:31:44 -0500 Subject: [PATCH 339/406] Update pysqueezebox to 0.2.1 (#35956) --- homeassistant/components/squeezebox/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/squeezebox/manifest.json b/homeassistant/components/squeezebox/manifest.json index ae076c88b4a..98456de67b5 100644 --- a/homeassistant/components/squeezebox/manifest.json +++ b/homeassistant/components/squeezebox/manifest.json @@ -3,5 +3,5 @@ "name": "Logitech Squeezebox", "documentation": "https://www.home-assistant.io/integrations/squeezebox", "codeowners": ["@rajlaud"], - "requirements": ["pysqueezebox==0.1.4"] + "requirements": ["pysqueezebox==0.2.1"] } diff --git a/requirements_all.txt b/requirements_all.txt index a835730dbae..46a02e8040e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1621,7 +1621,7 @@ pysonos==0.0.30 pyspcwebgw==0.4.0 # homeassistant.components.squeezebox -pysqueezebox==0.1.4 +pysqueezebox==0.2.1 # homeassistant.components.stiebel_eltron pystiebeleltron==0.0.1.dev2 From a7a58b9eac41174a79928c05030b21a63992ad93 Mon Sep 17 00:00:00 2001 From: Felipe Martins Diel <41558831+felipediel@users.noreply.github.com> Date: Tue, 2 Jun 2020 23:44:45 -0300 Subject: [PATCH 340/406] Fix SP2-CL (0x7544) sensor update (#36242) * Fix SP2-CL (0x7544) sensor update This device does not support get_energy(). We need to ignore the CommandNotSupportedError in the update method. * Format with Black * Only wrap code that throws with catch Co-authored-by: Paulus Schoutsen --- homeassistant/components/broadlink/switch.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/broadlink/switch.py b/homeassistant/components/broadlink/switch.py index b62da4ebb3e..d41bac3beae 100644 --- a/homeassistant/components/broadlink/switch.py +++ b/homeassistant/components/broadlink/switch.py @@ -4,7 +4,7 @@ from ipaddress import ip_address import logging import broadlink as blk -from broadlink.exceptions import BroadlinkException +from broadlink.exceptions import BroadlinkException, CommandNotSupportedError import voluptuous as vol from homeassistant.components.switch import DOMAIN, PLATFORM_SCHEMA, SwitchEntity @@ -264,13 +264,19 @@ class BroadlinkSP2Switch(BroadlinkSP1Switch): async def async_update(self): """Update the state of the device.""" try: - state = await self.device.async_request(self.device.api.check_power) - load_power = await self.device.async_request(self.device.api.get_energy) + self._state = await self.device.async_request(self.device.api.check_power) except BroadlinkException as err_msg: _LOGGER.error("Failed to update state: %s", err_msg) return - self._state = state - self._load_power = load_power + + try: + self._load_power = await self.device.async_request( + self.device.api.get_energy + ) + except CommandNotSupportedError: + return + except BroadlinkException as err_msg: + _LOGGER.error("Failed to update load power: %s", err_msg) class BroadlinkMP1Slot(BroadlinkRMSwitch): From 0e2e39a5f2d067325ec2db80acadc5683052fef1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diefferson=20Koderer=20M=C3=B4ro?= Date: Tue, 2 Jun 2020 23:45:20 -0300 Subject: [PATCH 341/406] Fix Method GetNetworkInterfaces not implemented (#36243) --- homeassistant/components/onvif/config_flow.py | 18 ++++++++++++++---- homeassistant/components/onvif/device.py | 18 ++++++++++++++---- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/onvif/config_flow.py b/homeassistant/components/onvif/config_flow.py index 29784b25429..fada2d497a7 100644 --- a/homeassistant/components/onvif/config_flow.py +++ b/homeassistant/components/onvif/config_flow.py @@ -205,10 +205,20 @@ class OnvifFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): # Get the MAC address to use as the unique ID for the config flow if not self.device_id: - network_interfaces = await device_mgmt.GetNetworkInterfaces() - for interface in network_interfaces: - if interface.Enabled: - self.device_id = interface.Info.HwAddress + try: + network_interfaces = await device_mgmt.GetNetworkInterfaces() + for interface in network_interfaces: + if interface.Enabled: + self.device_id = interface.Info.HwAddress + except Fault as fault: + if "not implemented" not in fault.message: + raise fault + + LOGGER.debug( + "Couldn't get network interfaces from ONVIF deivice '%s'. Error: %s", + self.onvif_config[CONF_NAME], + fault, + ) # If no network interfaces are exposed, fallback to serial number if not self.device_id: diff --git a/homeassistant/components/onvif/device.py b/homeassistant/components/onvif/device.py index bbce71f5d28..eec270d5b94 100644 --- a/homeassistant/components/onvif/device.py +++ b/homeassistant/components/onvif/device.py @@ -217,10 +217,20 @@ class ONVIFDevice: # Grab the last MAC address for backwards compatibility mac = None - network_interfaces = await device_mgmt.GetNetworkInterfaces() - for interface in network_interfaces: - if interface.Enabled: - mac = interface.Info.HwAddress + try: + network_interfaces = await device_mgmt.GetNetworkInterfaces() + for interface in network_interfaces: + if interface.Enabled: + mac = interface.Info.HwAddress + except Fault as fault: + if "not implemented" not in fault.message: + raise fault + + LOGGER.debug( + "Couldn't get network interfaces from ONVIF deivice '%s'. Error: %s", + self.name, + fault, + ) return DeviceInfo( device_info.Manufacturer, From e80fac36d896007d6148949a2dfdab0f1a911090 Mon Sep 17 00:00:00 2001 From: Patrick Kishino Date: Wed, 3 Jun 2020 15:00:50 +0900 Subject: [PATCH 342/406] Bumped PyAv to 8.0.2 to fix mac os stream issue (#36396) --- homeassistant/components/stream/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/stream/manifest.json b/homeassistant/components/stream/manifest.json index e90d93cbfe3..d434d1ce1ed 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==8.0.1"], + "requirements": ["av==8.0.2"], "dependencies": ["http"], "codeowners": ["@hunterjm"], "quality_scale": "internal" diff --git a/requirements_all.txt b/requirements_all.txt index 46a02e8040e..ea5709b926e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -294,7 +294,7 @@ atenpdu==0.3.0 aurorapy==0.2.6 # homeassistant.components.stream -av==8.0.1 +av==8.0.2 # homeassistant.components.avea avea==1.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 1287a65fcdd..25a09b63978 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -144,7 +144,7 @@ arcam-fmj==0.4.6 async-upnp-client==0.14.13 # homeassistant.components.stream -av==8.0.1 +av==8.0.2 # homeassistant.components.axis axis==28 From 94d8e77f8c0940f863a8d856395b7a3acd31c14d Mon Sep 17 00:00:00 2001 From: Jason Hunter Date: Wed, 3 Jun 2020 03:55:25 -0400 Subject: [PATCH 343/406] Add non-root dev container user (#34984) * add non-root container user * fix dockerfile and homeassistant editable install * just install in home directory * less impactful default changes * separate RUN for better layer caching * use vscode-remote base image --- Dockerfile.dev | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.dev b/Dockerfile.dev index be8e2223390..4c2a7ebe9e3 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -1,4 +1,4 @@ -FROM python:3.8 +FROM mcr.microsoft.com/vscode/devcontainers/python:0-3.8 RUN \ apt-get update && apt-get install -y --no-install-recommends \ From a8e7bf6cf7d958c45926b417152ef01a2d902c73 Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Wed, 3 Jun 2020 10:23:31 +0200 Subject: [PATCH 344/406] Axis - bump dependency (#36402) --- homeassistant/components/axis/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/axis/manifest.json b/homeassistant/components/axis/manifest.json index 417e1781c7c..f0d33fb4159 100644 --- a/homeassistant/components/axis/manifest.json +++ b/homeassistant/components/axis/manifest.json @@ -3,7 +3,7 @@ "name": "Axis", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/axis", - "requirements": ["axis==28"], + "requirements": ["axis==29"], "zeroconf": ["_axis-video._tcp.local."], "after_dependencies": ["mqtt"], "codeowners": ["@Kane610"] diff --git a/requirements_all.txt b/requirements_all.txt index ea5709b926e..6471d73325e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -306,7 +306,7 @@ avea==1.4 avri-api==0.1.7 # homeassistant.components.axis -axis==28 +axis==29 # homeassistant.components.azure_event_hub azure-eventhub==1.3.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 25a09b63978..1abed405b95 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -147,7 +147,7 @@ async-upnp-client==0.14.13 av==8.0.2 # homeassistant.components.axis -axis==28 +axis==29 # homeassistant.components.homekit base36==0.1.1 From 5e2b87866e907da0ca1908e669cbd42438334580 Mon Sep 17 00:00:00 2001 From: Eduard van Valkenburg Date: Wed, 3 Jun 2020 10:32:14 +0200 Subject: [PATCH 345/406] Update azure_event_hub (#31448) --- .../components/azure_event_hub/__init__.py | 228 ++++++++++++++---- .../components/azure_event_hub/const.py | 13 + .../components/azure_event_hub/manifest.json | 2 +- requirements_all.txt | 2 +- 4 files changed, 196 insertions(+), 49 deletions(-) create mode 100644 homeassistant/components/azure_event_hub/const.py diff --git a/homeassistant/components/azure_event_hub/__init__.py b/homeassistant/components/azure_event_hub/__init__.py index cc59790b646..3b44c6423be 100644 --- a/homeassistant/components/azure_event_hub/__init__.py +++ b/homeassistant/components/azure_event_hub/__init__.py @@ -1,89 +1,223 @@ """Support for Azure Event Hubs.""" +import asyncio import json import logging +import time from typing import Any, Dict -from azure.eventhub import EventData, EventHubClientAsync +from azure.eventhub import EventData +from azure.eventhub.aio import EventHubProducerClient, EventHubSharedKeyCredential +from azure.eventhub.exceptions import EventHubError import voluptuous as vol from homeassistant.const import ( EVENT_HOMEASSISTANT_STOP, - EVENT_STATE_CHANGED, + MATCH_ALL, STATE_UNAVAILABLE, STATE_UNKNOWN, ) from homeassistant.core import Event, HomeAssistant import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entityfilter import FILTER_SCHEMA +from homeassistant.helpers.event import async_call_later from homeassistant.helpers.json import JSONEncoder +from .const import ( + ADDITIONAL_ARGS, + CONF_EVENT_HUB_CON_STRING, + CONF_EVENT_HUB_INSTANCE_NAME, + CONF_EVENT_HUB_NAMESPACE, + CONF_EVENT_HUB_SAS_KEY, + CONF_EVENT_HUB_SAS_POLICY, + CONF_FILTER, + CONF_MAX_DELAY, + CONF_SEND_INTERVAL, + DOMAIN, +) + _LOGGER = logging.getLogger(__name__) -DOMAIN = "azure_event_hub" - -CONF_EVENT_HUB_NAMESPACE = "event_hub_namespace" -CONF_EVENT_HUB_INSTANCE_NAME = "event_hub_instance_name" -CONF_EVENT_HUB_SAS_POLICY = "event_hub_sas_policy" -CONF_EVENT_HUB_SAS_KEY = "event_hub_sas_key" -CONF_FILTER = "filter" - CONFIG_SCHEMA = vol.Schema( { DOMAIN: vol.Schema( { - vol.Required(CONF_EVENT_HUB_NAMESPACE): cv.string, - vol.Required(CONF_EVENT_HUB_INSTANCE_NAME): cv.string, - vol.Required(CONF_EVENT_HUB_SAS_POLICY): cv.string, - vol.Required(CONF_EVENT_HUB_SAS_KEY): cv.string, - vol.Required(CONF_FILTER): FILTER_SCHEMA, - } + vol.Exclusive(CONF_EVENT_HUB_CON_STRING, "setup_methods"): cv.string, + vol.Exclusive(CONF_EVENT_HUB_NAMESPACE, "setup_methods"): cv.string, + vol.Optional(CONF_EVENT_HUB_INSTANCE_NAME): cv.string, + vol.Optional(CONF_EVENT_HUB_SAS_POLICY): cv.string, + vol.Optional(CONF_EVENT_HUB_SAS_KEY): cv.string, + vol.Optional(CONF_SEND_INTERVAL, default=5): cv.positive_int, + vol.Optional(CONF_MAX_DELAY, default=30): cv.positive_int, + vol.Optional(CONF_FILTER, default={}): FILTER_SCHEMA, + }, + cv.has_at_least_one_key( + CONF_EVENT_HUB_CON_STRING, CONF_EVENT_HUB_NAMESPACE + ), ) }, extra=vol.ALLOW_EXTRA, ) -async def async_setup(hass: HomeAssistant, yaml_config: Dict[str, Any]): +async def async_setup(hass, yaml_config): """Activate Azure EH component.""" config = yaml_config[DOMAIN] + if config.get(CONF_EVENT_HUB_CON_STRING): + client_args = {"conn_str": config[CONF_EVENT_HUB_CON_STRING]} + conn_str_client = True + else: + client_args = { + "fully_qualified_namespace": f"{config[CONF_EVENT_HUB_NAMESPACE]}.servicebus.windows.net", + "credential": EventHubSharedKeyCredential( + policy=config[CONF_EVENT_HUB_SAS_POLICY], + key=config[CONF_EVENT_HUB_SAS_KEY], + ), + "eventhub_name": config[CONF_EVENT_HUB_INSTANCE_NAME], + } + conn_str_client = False - event_hub_address = ( - f"amqps://{config[CONF_EVENT_HUB_NAMESPACE]}" - f".servicebus.windows.net/{config[CONF_EVENT_HUB_INSTANCE_NAME]}" + instance = hass.data[DOMAIN] = AzureEventHub( + hass, + client_args, + conn_str_client, + config[CONF_FILTER], + config[CONF_SEND_INTERVAL], + config[CONF_MAX_DELAY], ) - entities_filter = config[CONF_FILTER] - client = EventHubClientAsync( - event_hub_address, - debug=True, - username=config[CONF_EVENT_HUB_SAS_POLICY], - password=config[CONF_EVENT_HUB_SAS_KEY], - ) - async_sender = client.add_async_sender() - await client.run_async() + hass.async_create_task(instance.async_start()) + return True - encoder = JSONEncoder() - async def async_send_to_event_hub(event: Event): - """Send states to Event Hub.""" +class AzureEventHub: + """A event handler class for Azure Event Hub.""" + + def __init__( + self, + hass: HomeAssistant, + client_args: Dict[str, Any], + conn_str_client: bool, + entities_filter: vol.Schema, + send_interval: int, + max_delay: int, + ): + """Initialize the listener.""" + self.hass = hass + self.queue = asyncio.PriorityQueue() + self._client_args = client_args + self._conn_str_client = conn_str_client + self._entities_filter = entities_filter + self._send_interval = send_interval + self._max_delay = max_delay + send_interval + self._listener_remover = None + self._next_send_remover = None + self.shutdown = False + + async def async_start(self): + """Start the recorder, suppress logging and register the callbacks and do the first send after five seconds, to capture the startup events.""" + # suppress the INFO and below logging on the underlying packages, they are very verbose, even at INFO + logging.getLogger("uamqp").setLevel(logging.WARNING) + logging.getLogger("azure.eventhub").setLevel(logging.WARNING) + + self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self.async_shutdown) + self._listener_remover = self.hass.bus.async_listen( + MATCH_ALL, self.async_listen + ) + # schedule the first send after 10 seconds to capture startup events, after that each send will schedule the next after the interval. + self._next_send_remover = async_call_later(self.hass, 10, self.async_send) + + async def async_shutdown(self, _: Event): + """Shut down the AEH by queueing None and calling send.""" + if self._next_send_remover: + self._next_send_remover() + if self._listener_remover: + self._listener_remover() + await self.queue.put((3, (time.monotonic(), None))) + await self.async_send(None) + + async def async_listen(self, event: Event): + """Listen for new messages on the bus and queue them for AEH.""" + await self.queue.put((2, (time.monotonic(), event))) + + async def async_send(self, _): + """Write preprocessed events to eventhub, with retry.""" + client = self._get_client() + async with client: + while not self.queue.empty(): + data_batch, dequeue_count = await self.fill_batch(client) + _LOGGER.debug( + "Sending %d event(s), out of %d events in the queue", + len(data_batch), + dequeue_count, + ) + if data_batch: + try: + await client.send_batch(data_batch) + except EventHubError as exc: + _LOGGER.error("Error in sending events to Event Hub: %s", exc) + finally: + for _ in range(dequeue_count): + self.queue.task_done() + await client.close() + + if not self.shutdown: + self._next_send_remover = async_call_later( + self.hass, self._send_interval, self.async_send + ) + + async def fill_batch(self, client): + """Return a batch of events formatted for writing. + + Uses get_nowait instead of await get, because the functions batches and doesn't wait for each single event, the send function is called. + + Throws ValueError on add to batch when the EventDataBatch object reaches max_size. Put the item back in the queue and the next batch will include it. + """ + event_batch = await client.create_batch() + dequeue_count = 0 + dropped = 0 + while not self.shutdown: + try: + _, (timestamp, event) = self.queue.get_nowait() + except asyncio.QueueEmpty: + break + dequeue_count += 1 + if not event: + self.shutdown = True + break + event_data = self._event_to_filtered_event_data(event) + if not event_data: + continue + if time.monotonic() - timestamp <= self._max_delay: + try: + event_batch.add(event_data) + except ValueError: + self.queue.put_nowait((1, (timestamp, event))) + break + else: + dropped += 1 + + if dropped: + _LOGGER.warning( + "Dropped %d old events, consider increasing the max_delay", dropped + ) + + return event_batch, dequeue_count + + def _event_to_filtered_event_data(self, event: Event): + """Filter event states and create EventData object.""" state = event.data.get("new_state") if ( state is None or state.state in (STATE_UNKNOWN, "", STATE_UNAVAILABLE) - or not entities_filter(state.entity_id) + or not self._entities_filter(state.entity_id) ): - return + return None + return EventData(json.dumps(obj=state, cls=JSONEncoder).encode("utf-8")) - event_data = EventData( - json.dumps(obj=state.as_dict(), default=encoder.encode).encode("utf-8") - ) - await async_sender.send(event_data) - - async def async_shutdown(event: Event): - """Shut down the client.""" - await client.stop_async() - - hass.bus.async_listen(EVENT_STATE_CHANGED, async_send_to_event_hub) - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, async_shutdown) - - return True + def _get_client(self): + """Get a Event Producer Client.""" + if self._conn_str_client: + return EventHubProducerClient.from_connection_string( + **self._client_args, **ADDITIONAL_ARGS + ) + return EventHubProducerClient(**self._client_args, **ADDITIONAL_ARGS) diff --git a/homeassistant/components/azure_event_hub/const.py b/homeassistant/components/azure_event_hub/const.py new file mode 100644 index 00000000000..1786bb5cbf2 --- /dev/null +++ b/homeassistant/components/azure_event_hub/const.py @@ -0,0 +1,13 @@ +"""Constants and shared schema for the Azure Event Hub integration.""" +DOMAIN = "azure_event_hub" + +CONF_EVENT_HUB_NAMESPACE = "event_hub_namespace" +CONF_EVENT_HUB_INSTANCE_NAME = "event_hub_instance_name" +CONF_EVENT_HUB_SAS_POLICY = "event_hub_sas_policy" +CONF_EVENT_HUB_SAS_KEY = "event_hub_sas_key" +CONF_EVENT_HUB_CON_STRING = "event_hub_connection_string" +CONF_SEND_INTERVAL = "send_interval" +CONF_MAX_DELAY = "max_delay" +CONF_FILTER = "filter" + +ADDITIONAL_ARGS = {"logging_enable": False} diff --git a/homeassistant/components/azure_event_hub/manifest.json b/homeassistant/components/azure_event_hub/manifest.json index f9d4cf09e04..08bae34d731 100644 --- a/homeassistant/components/azure_event_hub/manifest.json +++ b/homeassistant/components/azure_event_hub/manifest.json @@ -2,6 +2,6 @@ "domain": "azure_event_hub", "name": "Azure Event Hub", "documentation": "https://www.home-assistant.io/integrations/azure_event_hub", - "requirements": ["azure-eventhub==1.3.1"], + "requirements": ["azure-eventhub==5.1.0"], "codeowners": ["@eavanvalkenburg"] } diff --git a/requirements_all.txt b/requirements_all.txt index 6471d73325e..a785ea2e137 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -309,7 +309,7 @@ avri-api==0.1.7 axis==29 # homeassistant.components.azure_event_hub -azure-eventhub==1.3.1 +azure-eventhub==5.1.0 # homeassistant.components.azure_service_bus azure-servicebus==0.50.1 From 37832d5e81c3258efaebd71affc442e64a5209d6 Mon Sep 17 00:00:00 2001 From: Tom Harris Date: Wed, 3 Jun 2020 05:52:56 -0400 Subject: [PATCH 346/406] Bump pyinsteon to 1.0.3 (#36398) --- homeassistant/components/insteon/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/insteon/manifest.json b/homeassistant/components/insteon/manifest.json index 0f0ccf842ff..63c258d5f58 100644 --- a/homeassistant/components/insteon/manifest.json +++ b/homeassistant/components/insteon/manifest.json @@ -2,6 +2,6 @@ "domain": "insteon", "name": "Insteon", "documentation": "https://www.home-assistant.io/integrations/insteon", - "requirements": ["pyinsteon==1.0.2"], + "requirements": ["pyinsteon==1.0.3"], "codeowners": ["@teharris1"] } \ No newline at end of file diff --git a/requirements_all.txt b/requirements_all.txt index a785ea2e137..3b4d71495ce 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1375,7 +1375,7 @@ pyialarm==0.3 pyicloud==0.9.7 # homeassistant.components.insteon -pyinsteon==1.0.2 +pyinsteon==1.0.3 # homeassistant.components.intesishome pyintesishome==1.7.4 From d14112748c748e06a3311960a83902f1f08f16ef Mon Sep 17 00:00:00 2001 From: Anton Tolchanov <1687799+knyar@users.noreply.github.com> Date: Wed, 3 Jun 2020 10:53:21 +0100 Subject: [PATCH 347/406] Add a Prometheus metric for HVAC action (#31945) --- homeassistant/components/climate/const.py | 11 +++++++++ .../components/prometheus/__init__.py | 23 +++++++++++++++---- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/climate/const.py b/homeassistant/components/climate/const.py index b489071db57..af6c9364b18 100644 --- a/homeassistant/components/climate/const.py +++ b/homeassistant/components/climate/const.py @@ -84,6 +84,17 @@ CURRENT_HVAC_IDLE = "idle" CURRENT_HVAC_FAN = "fan" +# A list of possible HVAC actions. +CURRENT_HVAC_ACTIONS = [ + CURRENT_HVAC_OFF, + CURRENT_HVAC_HEAT, + CURRENT_HVAC_COOL, + CURRENT_HVAC_DRY, + CURRENT_HVAC_IDLE, + CURRENT_HVAC_FAN, +] + + ATTR_AUX_HEAT = "aux_heat" ATTR_CURRENT_HUMIDITY = "current_humidity" ATTR_CURRENT_TEMPERATURE = "current_temperature" diff --git a/homeassistant/components/prometheus/__init__.py b/homeassistant/components/prometheus/__init__.py index d31718d9d2c..aea26414bee 100644 --- a/homeassistant/components/prometheus/__init__.py +++ b/homeassistant/components/prometheus/__init__.py @@ -7,7 +7,11 @@ import prometheus_client import voluptuous as vol from homeassistant import core as hacore -from homeassistant.components.climate.const import ATTR_CURRENT_TEMPERATURE +from homeassistant.components.climate.const import ( + ATTR_CURRENT_TEMPERATURE, + ATTR_HVAC_ACTION, + CURRENT_HVAC_ACTIONS, +) from homeassistant.components.http import HomeAssistantView from homeassistant.const import ( ATTR_DEVICE_CLASS, @@ -166,9 +170,10 @@ class PrometheusMetrics: except ValueError: pass - def _metric(self, metric, factory, documentation, labels=None): - if labels is None: - labels = ["entity", "friendly_name", "domain"] + def _metric(self, metric, factory, documentation, extra_labels=None): + labels = ["entity", "friendly_name", "domain"] + if extra_labels is not None: + labels.extend(extra_labels) try: return self._metrics[metric] @@ -303,6 +308,16 @@ class PrometheusMetrics: ) metric.labels(**self._labels(state)).set(current_temp) + current_action = state.attributes.get(ATTR_HVAC_ACTION) + if current_action: + metric = self._metric( + "climate_action", self.prometheus_cli.Gauge, "HVAC action", ["action"], + ) + for action in CURRENT_HVAC_ACTIONS: + metric.labels(**dict(self._labels(state), action=action)).set( + float(action == current_action) + ) + def _handle_sensor(self, state): unit = self._unit_string(state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)) From 8ccdaf29279bac580c4c828d9fff56229e64a94c Mon Sep 17 00:00:00 2001 From: Fabian Peter Hammerle Date: Wed, 3 Jun 2020 11:59:06 +0200 Subject: [PATCH 348/406] Add huawei_lte sensor for number of unread sms (#35665) --- CODEOWNERS | 2 +- homeassistant/components/huawei_lte/__init__.py | 2 ++ homeassistant/components/huawei_lte/const.py | 2 ++ homeassistant/components/huawei_lte/manifest.json | 2 +- homeassistant/components/huawei_lte/sensor.py | 4 ++++ 5 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 7404d3a62aa..d00f428efed 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -180,7 +180,7 @@ homeassistant/components/homematicip_cloud/* @SukramJ homeassistant/components/honeywell/* @zxdavb homeassistant/components/html5/* @robbiet480 homeassistant/components/http/* @home-assistant/core -homeassistant/components/huawei_lte/* @scop +homeassistant/components/huawei_lte/* @scop @fphammerle homeassistant/components/huawei_router/* @abmantis homeassistant/components/hue/* @balloob homeassistant/components/hunterdouglas_powerview/* @bdraco diff --git a/homeassistant/components/huawei_lte/__init__.py b/homeassistant/components/huawei_lte/__init__.py index 272efa5d722..87f66f87700 100644 --- a/homeassistant/components/huawei_lte/__init__.py +++ b/homeassistant/components/huawei_lte/__init__.py @@ -68,6 +68,7 @@ from .const import ( KEY_MONITORING_TRAFFIC_STATISTICS, KEY_NET_CURRENT_PLMN, KEY_NET_NET_MODE, + KEY_SMS_SMS_COUNT, KEY_WLAN_HOST_LIST, KEY_WLAN_WIFI_FEATURE_SWITCH, NOTIFY_SUPPRESS_TIMEOUT, @@ -243,6 +244,7 @@ class Router: ) self._get_data(KEY_NET_CURRENT_PLMN, self.client.net.current_plmn) self._get_data(KEY_NET_NET_MODE, self.client.net.net_mode) + self._get_data(KEY_SMS_SMS_COUNT, self.client.sms.sms_count) self._get_data(KEY_WLAN_HOST_LIST, self.client.wlan.host_list) self._get_data( KEY_WLAN_WIFI_FEATURE_SWITCH, self.client.wlan.wifi_feature_switch diff --git a/homeassistant/components/huawei_lte/const.py b/homeassistant/components/huawei_lte/const.py index 583c1c7d6f1..1019d4d19cd 100644 --- a/homeassistant/components/huawei_lte/const.py +++ b/homeassistant/components/huawei_lte/const.py @@ -32,6 +32,7 @@ KEY_MONITORING_STATUS = "monitoring_status" KEY_MONITORING_TRAFFIC_STATISTICS = "monitoring_traffic_statistics" KEY_NET_CURRENT_PLMN = "net_current_plmn" KEY_NET_NET_MODE = "net_net_mode" +KEY_SMS_SMS_COUNT = "sms_sms_count" KEY_WLAN_HOST_LIST = "wlan_host_list" KEY_WLAN_WIFI_FEATURE_SWITCH = "wlan_wifi_feature_switch" @@ -47,6 +48,7 @@ SENSOR_KEYS = { KEY_MONITORING_TRAFFIC_STATISTICS, KEY_NET_CURRENT_PLMN, KEY_NET_NET_MODE, + KEY_SMS_SMS_COUNT, } SWITCH_KEYS = {KEY_DIALUP_MOBILE_DATASWITCH} diff --git a/homeassistant/components/huawei_lte/manifest.json b/homeassistant/components/huawei_lte/manifest.json index 0660aa361f5..fd574784838 100644 --- a/homeassistant/components/huawei_lte/manifest.json +++ b/homeassistant/components/huawei_lte/manifest.json @@ -15,5 +15,5 @@ "manufacturer": "Huawei" } ], - "codeowners": ["@scop"] + "codeowners": ["@scop", "@fphammerle"] } diff --git a/homeassistant/components/huawei_lte/sensor.py b/homeassistant/components/huawei_lte/sensor.py index 84d8e72c2ff..018e5236c74 100644 --- a/homeassistant/components/huawei_lte/sensor.py +++ b/homeassistant/components/huawei_lte/sensor.py @@ -22,6 +22,7 @@ from .const import ( KEY_MONITORING_TRAFFIC_STATISTICS, KEY_NET_CURRENT_PLMN, KEY_NET_NET_MODE, + KEY_SMS_SMS_COUNT, SENSOR_KEYS, ) @@ -195,6 +196,9 @@ SENSOR_META = { None, ), ), + (KEY_SMS_SMS_COUNT, "LocalUnread"): dict( + name="SMS unread", icon="mdi:email-receive", + ), } From fcef2590214c07ebb0e61dc3c60b4a03111735fa Mon Sep 17 00:00:00 2001 From: Robert Van Gorkom Date: Wed, 3 Jun 2020 03:40:46 -0700 Subject: [PATCH 349/406] Prefer use cloud url for oauth2 for Withings (#36348) --- homeassistant/components/withings/__init__.py | 9 +++++++-- homeassistant/components/withings/common.py | 13 +++++++++++++ tests/components/withings/common.py | 8 ++++++-- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/withings/__init__.py b/homeassistant/components/withings/__init__.py index 75cf96a37cf..93a6250ce03 100644 --- a/homeassistant/components/withings/__init__.py +++ b/homeassistant/components/withings/__init__.py @@ -12,7 +12,12 @@ from homeassistant.helpers import config_entry_oauth2_flow, config_validation as from homeassistant.helpers.typing import ConfigType, HomeAssistantType from . import config_flow -from .common import _LOGGER, NotAuthenticatedError, get_data_manager +from .common import ( + _LOGGER, + NotAuthenticatedError, + WithingsLocalOAuth2Implementation, + get_data_manager, +) from .const import CONF_PROFILES, CONFIG, CREDENTIALS, DOMAIN CONFIG_SCHEMA = vol.Schema( @@ -44,7 +49,7 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: config_flow.WithingsFlowHandler.async_register_implementation( hass, - config_entry_oauth2_flow.LocalOAuth2Implementation( + WithingsLocalOAuth2Implementation( hass, DOMAIN, conf[CONF_CLIENT_ID], diff --git a/homeassistant/components/withings/common.py b/homeassistant/components/withings/common.py index ac7bc149cd9..1539b973cb8 100644 --- a/homeassistant/components/withings/common.py +++ b/homeassistant/components/withings/common.py @@ -20,9 +20,12 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError, PlatformNotReady from homeassistant.helpers.config_entry_oauth2_flow import ( + AUTH_CALLBACK_PATH, AbstractOAuth2Implementation, + LocalOAuth2Implementation, OAuth2Session, ) +from homeassistant.helpers.network import get_url from homeassistant.util import dt, slugify from . import const @@ -335,3 +338,13 @@ def get_data_manager( ) return dm_dict[entry_id] + + +class WithingsLocalOAuth2Implementation(LocalOAuth2Implementation): + """Oauth2 implementation that only uses the external url.""" + + @property + def redirect_uri(self) -> str: + """Return the redirect uri.""" + url = get_url(self.hass, allow_internal=False, prefer_cloud=True) + return f"{url}{AUTH_CALLBACK_PATH}" diff --git a/tests/components/withings/common.py b/tests/components/withings/common.py index 0bb7966da53..ca3fef6159e 100644 --- a/tests/components/withings/common.py +++ b/tests/components/withings/common.py @@ -22,6 +22,7 @@ from homeassistant.config_entries import SOURCE_USER from homeassistant.const import ( CONF_CLIENT_ID, CONF_CLIENT_SECRET, + CONF_EXTERNAL_URL, CONF_UNIT_SYSTEM, CONF_UNIT_SYSTEM_METRIC, ) @@ -56,8 +57,11 @@ async def setup_hass(hass: HomeAssistant) -> dict: profiles = ["Person0", "Person1", "Person2", "Person3", "Person4"] hass_config = { - "homeassistant": {CONF_UNIT_SYSTEM: CONF_UNIT_SYSTEM_METRIC}, - api.DOMAIN: {"base_url": "http://localhost/"}, + "homeassistant": { + CONF_UNIT_SYSTEM: CONF_UNIT_SYSTEM_METRIC, + CONF_EXTERNAL_URL: "http://example.local/", + }, + api.DOMAIN: {}, http.DOMAIN: {"server_port": 8080}, const.DOMAIN: { CONF_CLIENT_ID: "my_client_id", From 95563e04e8dc89d8f7d18e162e2272d49f5e8538 Mon Sep 17 00:00:00 2001 From: Scotte Zinn Date: Wed, 3 Jun 2020 07:01:13 -0400 Subject: [PATCH 350/406] Allow synology_dsm configuration to specify a host name (#36305) * Allow configuration to specify a host name. This will default to "synology", so the sensors would be named sensor.synology_... which is the original implementation. By specifying a name, which is required for multiple synology hosts, you can then have sensor.hostA_... and sensor.hostB_... * Get the base name for the sensors from the Synology DSM hostname * Don't need the name for the config * Added missing import * Fixed another typo * Removed definition of unused BASE_NAME * Removed end of line to revert * Moved for ordering --- homeassistant/components/synology_dsm/__init__.py | 8 +++++--- homeassistant/components/synology_dsm/const.py | 2 -- homeassistant/components/synology_dsm/strings.json | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/synology_dsm/__init__.py b/homeassistant/components/synology_dsm/__init__.py index d4dbecf8f1c..89dc39e427c 100644 --- a/homeassistant/components/synology_dsm/__init__.py +++ b/homeassistant/components/synology_dsm/__init__.py @@ -8,6 +8,7 @@ from synology_dsm import SynologyDSM from synology_dsm.api.core.security import SynoCoreSecurity from synology_dsm.api.core.utilization import SynoCoreUtilization from synology_dsm.api.dsm.information import SynoDSMInformation +from synology_dsm.api.dsm.network import SynoDSMNetwork from synology_dsm.api.storage.storage import SynoStorage import voluptuous as vol @@ -35,7 +36,6 @@ from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.typing import HomeAssistantType from .const import ( - BASE_NAME, CONF_VOLUMES, DEFAULT_SCAN_INTERVAL, DEFAULT_SSL, @@ -219,6 +219,7 @@ class SynoApi: # DSM APIs self.dsm: SynologyDSM = None self.information: SynoDSMInformation = None + self.network: SynoDSMNetwork = None self.security: SynoCoreSecurity = None self.storage: SynoStorage = None self.utilisation: SynoCoreUtilization = None @@ -308,6 +309,7 @@ class SynoApi: def _fetch_device_configuration(self): """Fetch initial device config.""" self.information = self.dsm.information + self.network = self.dsm.network if self._with_security: self.security = self.dsm.security @@ -339,7 +341,7 @@ class SynologyDSMEntity(Entity): self._api = api self._api_key = entity_type.split(":")[0] self.entity_type = entity_type.split(":")[-1] - self._name = f"{BASE_NAME} {entity_info[ENTITY_NAME]}" + self._name = f"{api.network.hostname} {entity_info[ENTITY_NAME]}" self._class = entity_info[ENTITY_CLASS] self._enable_default = entity_info[ENTITY_ENABLE] self._icon = entity_info[ENTITY_ICON] @@ -456,7 +458,7 @@ class SynologyDSMDeviceEntity(SynologyDSMEntity): self._device_model = disk["model"].strip() self._device_firmware = disk["firm"] self._device_type = disk["diskType"] - self._name = f"{BASE_NAME} {self._device_name} {entity_info[ENTITY_NAME]}" + self._name = f"{self._api.network.hostname} {self._device_name} {entity_info[ENTITY_NAME]}" self._unique_id += f"_{self._device_id}" @property diff --git a/homeassistant/components/synology_dsm/const.py b/homeassistant/components/synology_dsm/const.py index c525bec2229..d19e919e41b 100644 --- a/homeassistant/components/synology_dsm/const.py +++ b/homeassistant/components/synology_dsm/const.py @@ -14,8 +14,6 @@ from homeassistant.const import ( DOMAIN = "synology_dsm" PLATFORMS = ["binary_sensor", "sensor"] -BASE_NAME = "Synology" - # Entry keys SYNO_API = "syno_api" UNDO_UPDATE_LISTENER = "undo_update_listener" diff --git a/homeassistant/components/synology_dsm/strings.json b/homeassistant/components/synology_dsm/strings.json index 0024a7db612..c46f645719f 100644 --- a/homeassistant/components/synology_dsm/strings.json +++ b/homeassistant/components/synology_dsm/strings.json @@ -49,4 +49,4 @@ } } } -} \ No newline at end of file +} From 465b98513b20a8e8f030cf795dd72f559f984a66 Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Wed, 3 Jun 2020 14:01:56 +0200 Subject: [PATCH 351/406] Add config flow to Dune HD (#36345) * Add config_flow to the dunehd integration * Add tests * Run gen_requirements_all * Fix pylint error * Better hostname validation * Build device info in the class --- .coveragerc | 2 + CODEOWNERS | 1 + homeassistant/components/dunehd/__init__.py | 50 ++++++++- .../components/dunehd/config_flow.py | 101 ++++++++++++++++++ homeassistant/components/dunehd/const.py | 4 + homeassistant/components/dunehd/manifest.json | 5 +- .../components/dunehd/media_player.py | 81 +++++++------- homeassistant/components/dunehd/strings.json | 21 ++++ homeassistant/generated/config_flows.py | 1 + requirements_test_all.txt | 3 + tests/components/dunehd/__init__.py | 1 + tests/components/dunehd/test_config_flow.py | 98 +++++++++++++++++ 12 files changed, 325 insertions(+), 43 deletions(-) create mode 100644 homeassistant/components/dunehd/config_flow.py create mode 100644 homeassistant/components/dunehd/const.py create mode 100644 homeassistant/components/dunehd/strings.json create mode 100644 tests/components/dunehd/__init__.py create mode 100644 tests/components/dunehd/test_config_flow.py diff --git a/.coveragerc b/.coveragerc index 54120f305f4..de92c1c23e8 100644 --- a/.coveragerc +++ b/.coveragerc @@ -176,6 +176,8 @@ omit = homeassistant/components/dsmr_reader/* homeassistant/components/dte_energy_bridge/sensor.py homeassistant/components/dublin_bus_transport/sensor.py + homeassistant/components/dunehd/__init__.py + homeassistant/components/dunehd/const.py homeassistant/components/dunehd/media_player.py homeassistant/components/dwd_weather_warnings/sensor.py homeassistant/components/dweet/* diff --git a/CODEOWNERS b/CODEOWNERS index d00f428efed..82e3e388026 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -98,6 +98,7 @@ homeassistant/components/directv/* @ctalkington homeassistant/components/discogs/* @thibmaek homeassistant/components/doorbird/* @oblogic7 @bdraco homeassistant/components/dsmr_reader/* @depl0y +homeassistant/components/dunehd/* @bieniu homeassistant/components/dweet/* @fabaff homeassistant/components/dynalite/* @ziv1234 homeassistant/components/dyson/* @etheralm diff --git a/homeassistant/components/dunehd/__init__.py b/homeassistant/components/dunehd/__init__.py index a8b8a8cf7af..a1fa456aa09 100644 --- a/homeassistant/components/dunehd/__init__.py +++ b/homeassistant/components/dunehd/__init__.py @@ -1 +1,49 @@ -"""The dunehd component.""" +"""The Dune HD component.""" +import asyncio + +from pdunehd import DuneHDPlayer + +from homeassistant.const import CONF_HOST + +from .const import DOMAIN + +PLATFORMS = ["media_player"] + + +async def async_setup(hass, config): + """Set up the Dune HD component.""" + return True + + +async def async_setup_entry(hass, config_entry): + """Set up a config entry.""" + host = config_entry.data[CONF_HOST] + + player = DuneHDPlayer(host) + + hass.data.setdefault(DOMAIN, {}) + hass.data[DOMAIN][config_entry.entry_id] = player + + for component in PLATFORMS: + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(config_entry, component) + ) + + 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: + hass.data[DOMAIN].pop(config_entry.entry_id) + + return unload_ok diff --git a/homeassistant/components/dunehd/config_flow.py b/homeassistant/components/dunehd/config_flow.py new file mode 100644 index 00000000000..1f0efa668f9 --- /dev/null +++ b/homeassistant/components/dunehd/config_flow.py @@ -0,0 +1,101 @@ +"""Adds config flow for Dune HD integration.""" +import ipaddress +import logging +import re + +from pdunehd import DuneHDPlayer +import voluptuous as vol + +from homeassistant import config_entries, exceptions +from homeassistant.const import CONF_HOST + +from .const import DOMAIN # pylint:disable=unused-import + +_LOGGER = logging.getLogger(__name__) + + +def host_valid(host): + """Return True if hostname or IP address is valid.""" + try: + if ipaddress.ip_address(host).version == (4 or 6): + return True + except ValueError: + if len(host) > 253: + return False + allowed = re.compile(r"(?!-)[A-Z\d\-\_]{1,63}(? Date: Wed, 3 Jun 2020 15:20:01 +0200 Subject: [PATCH 352/406] Update frontend to 20200603.0 (#36405) --- 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 14ae15dd87b..0853f6a2886 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==20200519.5"], + "requirements": ["home-assistant-frontend==20200603.0"], "dependencies": [ "api", "auth", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index ac7568b1b69..eedb6b7fb0a 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -12,7 +12,7 @@ cryptography==2.9.2 defusedxml==0.6.0 distro==1.5.0 hass-nabucasa==0.34.4 -home-assistant-frontend==20200519.5 +home-assistant-frontend==20200603.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 3b4d71495ce..00b309569b1 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -734,7 +734,7 @@ hole==0.5.1 holidays==0.10.2 # homeassistant.components.frontend -home-assistant-frontend==20200519.5 +home-assistant-frontend==20200603.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ac58342c7a6..b7348e7f256 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -321,7 +321,7 @@ hole==0.5.1 holidays==0.10.2 # homeassistant.components.frontend -home-assistant-frontend==20200519.5 +home-assistant-frontend==20200603.0 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From 355d655542f711a2b2ca1ca0dc89ace3725b73ab Mon Sep 17 00:00:00 2001 From: celestinjr <30021355+celestinjr@users.noreply.github.com> Date: Wed, 3 Jun 2020 09:13:53 -0500 Subject: [PATCH 353/406] Enable handling of 'num_repeats' for itach (#36362) --- homeassistant/components/itach/remote.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/itach/remote.py b/homeassistant/components/itach/remote.py index 2a1a2eac0ca..8f1f642e49e 100644 --- a/homeassistant/components/itach/remote.py +++ b/homeassistant/components/itach/remote.py @@ -5,7 +5,7 @@ import pyitachip2ir import voluptuous as vol from homeassistant.components import remote -from homeassistant.components.remote import PLATFORM_SCHEMA +from homeassistant.components.remote import ATTR_NUM_REPEATS, PLATFORM_SCHEMA from homeassistant.const import ( CONF_DEVICES, CONF_HOST, @@ -106,19 +106,22 @@ class ITachIP2IRRemote(remote.RemoteEntity): def turn_on(self, **kwargs): """Turn the device on.""" self._power = True - self.itachip2ir.send(self._name, "ON", 1) + num_repeats = kwargs.get(ATTR_NUM_REPEATS, 1) + self.itachip2ir.send(self._name, "ON", num_repeats) self.schedule_update_ha_state() def turn_off(self, **kwargs): """Turn the device off.""" self._power = False - self.itachip2ir.send(self._name, "OFF", 1) + num_repeats = kwargs.get(ATTR_NUM_REPEATS, 1) + self.itachip2ir.send(self._name, "OFF", num_repeats) self.schedule_update_ha_state() def send_command(self, command, **kwargs): """Send a command to one device.""" + num_repeats = kwargs.get(ATTR_NUM_REPEATS, 1) for single_command in command: - self.itachip2ir.send(self._name, single_command, 1) + self.itachip2ir.send(self._name, single_command, num_repeats) def update(self): """Update the device.""" From 0950ab0dd8ceb0fc6030a1032cf26e4d1585f2e7 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 3 Jun 2020 17:18:50 +0200 Subject: [PATCH 354/406] Fix dynamically add/remove WLED strip segments (#36407) --- homeassistant/components/wled/light.py | 66 ++++++- tests/components/wled/test_light.py | 36 +++- tests/fixtures/wled/rgb_single_segment.json | 202 ++++++++++++++++++++ 3 files changed, 298 insertions(+), 6 deletions(-) create mode 100644 tests/fixtures/wled/rgb_single_segment.json diff --git a/homeassistant/components/wled/light.py b/homeassistant/components/wled/light.py index 6b86be6265b..77f8960ada2 100644 --- a/homeassistant/components/wled/light.py +++ b/homeassistant/components/wled/light.py @@ -1,4 +1,5 @@ """Support for LED lights.""" +from functools import partial import logging from typing import Any, Callable, Dict, List, Optional, Tuple, Union @@ -20,8 +21,12 @@ from homeassistant.components.light import ( LightEntity, ) from homeassistant.config_entries import ConfigEntry +from homeassistant.core import callback from homeassistant.helpers import config_validation as cv, entity_platform from homeassistant.helpers.entity import Entity +from homeassistant.helpers.entity_registry import ( + async_get_registry as async_get_entity_registry, +) from homeassistant.helpers.typing import HomeAssistantType import homeassistant.util.color as color_util @@ -70,12 +75,12 @@ async def async_setup_entry( "async_effect", ) - lights = [ - WLEDLight(entry.entry_id, coordinator, light.segment_id) - for light in coordinator.data.state.segments - ] + update_segments = partial( + async_update_segments, entry, coordinator, {}, async_add_entities + ) - async_add_entities(lights, True) + coordinator.async_add_listener(update_segments) + update_segments() class WLEDLight(LightEntity, WLEDDeviceEntity): @@ -105,6 +110,16 @@ class WLEDLight(LightEntity, WLEDDeviceEntity): """Return the unique ID for this sensor.""" return f"{self.coordinator.data.info.mac_address}_{self._segment}" + @property + def available(self) -> bool: + """Return True if entity is available.""" + try: + self.coordinator.data.state.segments[self._segment] + except IndexError: + return False + + return super().available + @property def device_state_attributes(self) -> Optional[Dict[str, Any]]: """Return the state attributes of the entity.""" @@ -259,3 +274,44 @@ class WLEDLight(LightEntity, WLEDDeviceEntity): data[ATTR_SPEED] = speed await self.coordinator.wled.light(**data) + + +@callback +def async_update_segments( + entry: ConfigEntry, + coordinator: WLEDDataUpdateCoordinator, + current: Dict[int, WLEDLight], + async_add_entities, +) -> None: + """Update segments.""" + segment_ids = {light.segment_id for light in coordinator.data.state.segments} + current_ids = set(current) + + # Process new segments, add them to Home Assistant + new_segments = [] + for segment_id in segment_ids - current_ids: + current[segment_id] = WLEDLight(entry.entry_id, coordinator, segment_id) + new_segments.append(current[segment_id]) + + if new_segments: + async_add_entities(new_segments) + + # Process deleted segments, remove them from Home Assistant + for segment_id in current_ids - segment_ids: + coordinator.hass.async_create_task( + async_remove_segment(segment_id, coordinator, current) + ) + + +async def async_remove_segment( + segment_id: int, + coordinator: WLEDDataUpdateCoordinator, + current: Dict[int, WLEDLight], +) -> None: + """Remove WLED segment light from Home Assistant.""" + entity = current[segment_id] + await entity.async_remove() + registry = await async_get_entity_registry(coordinator.hass) + if entity.entity_id in registry.entities: + registry.async_remove(entity.entity_id) + del current[segment_id] diff --git a/tests/components/wled/test_light.py b/tests/components/wled/test_light.py index 307f1e56411..8854b00ff83 100644 --- a/tests/components/wled/test_light.py +++ b/tests/components/wled/test_light.py @@ -1,5 +1,7 @@ """Tests for the WLED light platform.""" -from wled import WLEDConnectionError +import json + +from wled import Device as WLEDDevice, WLEDConnectionError from homeassistant.components.light import ( ATTR_BRIGHTNESS, @@ -11,6 +13,7 @@ from homeassistant.components.light import ( ATTR_WHITE_VALUE, DOMAIN as LIGHT_DOMAIN, ) +from homeassistant.components.wled import SCAN_INTERVAL from homeassistant.components.wled.const import ( ATTR_INTENSITY, ATTR_PALETTE, @@ -30,8 +33,10 @@ from homeassistant.const import ( STATE_UNAVAILABLE, ) from homeassistant.core import HomeAssistant +import homeassistant.util.dt as dt_util from tests.async_mock import patch +from tests.common import async_fire_time_changed, load_fixture from tests.components.wled import init_integration from tests.test_util.aiohttp import AiohttpClientMocker @@ -137,6 +142,35 @@ async def test_switch_change_state( ) +async def test_dynamically_handle_segments( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: + """Test if a new/deleted segment is dynamically added/removed.""" + await init_integration(hass, aioclient_mock) + + assert hass.states.get("light.wled_rgb_light") + assert hass.states.get("light.wled_rgb_light_1") + + data = json.loads(load_fixture("wled/rgb_single_segment.json")) + device = WLEDDevice(data) + + # Test removal if segment went missing + with patch( + "homeassistant.components.wled.WLED.update", return_value=device, + ): + async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL) + await hass.async_block_till_done() + assert hass.states.get("light.wled_rgb_light") + assert not hass.states.get("light.wled_rgb_light_1") + + # Test adding if segment shows up again + async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL) + await hass.async_block_till_done() + + assert hass.states.get("light.wled_rgb_light") + assert hass.states.get("light.wled_rgb_light_1") + + async def test_light_error( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, caplog ) -> None: diff --git a/tests/fixtures/wled/rgb_single_segment.json b/tests/fixtures/wled/rgb_single_segment.json new file mode 100644 index 00000000000..e53ce680ece --- /dev/null +++ b/tests/fixtures/wled/rgb_single_segment.json @@ -0,0 +1,202 @@ +{ + "state": { + "on": true, + "bri": 127, + "transition": 7, + "ps": -1, + "pl": -1, + "nl": { + "on": false, + "dur": 60, + "fade": true, + "tbri": 0 + }, + "udpn": { + "send": false, + "recv": true + }, + "seg": [ + { + "id": 0, + "start": 0, + "stop": 30, + "len": 20, + "col": [[255, 159, 0], [0, 0, 0], [0, 0, 0]], + "fx": 0, + "sx": 32, + "ix": 128, + "pal": 0, + "sel": true, + "rev": false, + "cln": -1 + } + ] + }, + "info": { + "ver": "0.8.5", + "vid": 1909122, + "leds": { + "count": 30, + "rgbw": false, + "pin": [2], + "pwr": 470, + "maxpwr": 850, + "maxseg": 10 + }, + "name": "WLED RGB Light", + "udpport": 21324, + "live": false, + "fxcount": 81, + "palcount": 50, + "wifi": { + "bssid": "AA:AA:AA:AA:AA:BB", + "rssi": -62, + "signal": 76, + "channel": 11 + }, + "arch": "esp8266", + "core": "2_4_2", + "freeheap": 14600, + "uptime": 32, + "opt": 119, + "brand": "WLED", + "product": "DIY light", + "btype": "bin", + "mac": "aabbccddeeff" + }, + "effects": [ + "Solid", + "Blink", + "Breathe", + "Wipe", + "Wipe Random", + "Random Colors", + "Sweep", + "Dynamic", + "Colorloop", + "Rainbow", + "Scan", + "Dual Scan", + "Fade", + "Chase", + "Chase Rainbow", + "Running", + "Saw", + "Twinkle", + "Dissolve", + "Dissolve Rnd", + "Sparkle", + "Dark Sparkle", + "Sparkle+", + "Strobe", + "Strobe Rainbow", + "Mega Strobe", + "Blink Rainbow", + "Android", + "Chase", + "Chase Random", + "Chase Rainbow", + "Chase Flash", + "Chase Flash Rnd", + "Rainbow Runner", + "Colorful", + "Traffic Light", + "Sweep Random", + "Running 2", + "Red & Blue", + "Stream", + "Scanner", + "Lighthouse", + "Fireworks", + "Rain", + "Merry Christmas", + "Fire Flicker", + "Gradient", + "Loading", + "In Out", + "In In", + "Out Out", + "Out In", + "Circus", + "Halloween", + "Tri Chase", + "Tri Wipe", + "Tri Fade", + "Lightning", + "ICU", + "Multi Comet", + "Dual Scanner", + "Stream 2", + "Oscillate", + "Pride 2015", + "Juggle", + "Palette", + "Fire 2012", + "Colorwaves", + "BPM", + "Fill Noise", + "Noise 1", + "Noise 2", + "Noise 3", + "Noise 4", + "Colortwinkle", + "Lake", + "Meteor", + "Smooth Meteor", + "Railway", + "Ripple", + "Twinklefox" + ], + "palettes": [ + "Default", + "Random Cycle", + "Primary Color", + "Based on Primary", + "Set Colors", + "Based on Set", + "Party", + "Cloud", + "Lava", + "Ocean", + "Forest", + "Rainbow", + "Rainbow Bands", + "Sunset", + "Rivendell", + "Breeze", + "Red & Blue", + "Yellowout", + "Analogous", + "Splash", + "Pastel", + "Sunset 2", + "Beech", + "Vintage", + "Departure", + "Landscape", + "Beach", + "Sherbet", + "Hult", + "Hult 64", + "Drywet", + "Jul", + "Grintage", + "Rewhi", + "Tertiary", + "Fire", + "Icefire", + "Cyane", + "Light Pink", + "Autumn", + "Magenta", + "Magred", + "Yelmag", + "Yelblu", + "Orange & Teal", + "Tiamat", + "April Night", + "Orangery", + "C9", + "Sakura" + ] +} From b8f8b6fa506023d40a1e7b531c06de0344fc76f7 Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Wed, 3 Jun 2020 17:46:42 +0200 Subject: [PATCH 355/406] Fix using the async api from sync context (#36408) --- homeassistant/components/dunehd/media_player.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/dunehd/media_player.py b/homeassistant/components/dunehd/media_player.py index cd60e2acd60..8d73585cd69 100644 --- a/homeassistant/components/dunehd/media_player.py +++ b/homeassistant/components/dunehd/media_player.py @@ -43,7 +43,7 @@ DUNEHD_PLAYER_SUPPORT = ( ) -def setup_platform(hass, config, add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the Dune HD media player platform.""" host = config.get(CONF_HOST) From 660265fe5055a8d925fde6d1f772c8770bea98ce Mon Sep 17 00:00:00 2001 From: uvjustin <46082645+uvjustin@users.noreply.github.com> Date: Wed, 3 Jun 2020 23:51:15 +0800 Subject: [PATCH 356/406] Swap title and album name for streams in forked_daapd (#36381) --- homeassistant/components/forked_daapd/media_player.py | 8 ++++++++ tests/components/forked_daapd/test_media_player.py | 9 ++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/forked_daapd/media_player.py b/homeassistant/components/forked_daapd/media_player.py index 07cc11807fd..02790b17764 100644 --- a/homeassistant/components/forked_daapd/media_player.py +++ b/homeassistant/components/forked_daapd/media_player.py @@ -504,6 +504,10 @@ class ForkedDaapdMaster(MediaPlayerEntity): @property def media_title(self): """Title of current playing media.""" + # Use album field when data_kind is url + # https://github.com/ejurgensen/forked-daapd/issues/351 + if self._track_info["data_kind"] == "url": + return self._track_info["album"] return self._track_info["title"] @property @@ -514,6 +518,10 @@ class ForkedDaapdMaster(MediaPlayerEntity): @property def media_album_name(self): """Album name of current playing media, music track only.""" + # Use title field when data_kind is url + # https://github.com/ejurgensen/forked-daapd/issues/351 + if self._track_info["data_kind"] == "url": + return self._track_info["title"] return self._track_info["album"] @property diff --git a/tests/components/forked_daapd/test_media_player.py b/tests/components/forked_daapd/test_media_player.py index 43a39146199..4f5cb57d66c 100644 --- a/tests/components/forked_daapd/test_media_player.py +++ b/tests/components/forked_daapd/test_media_player.py @@ -364,9 +364,9 @@ def test_master_state(hass, mock_api_object): assert state.attributes[ATTR_MEDIA_CONTENT_TYPE] == MEDIA_TYPE_MUSIC assert state.attributes[ATTR_MEDIA_DURATION] == 0.05 assert state.attributes[ATTR_MEDIA_POSITION] == 0.005 - assert state.attributes[ATTR_MEDIA_TITLE] == "Short TTS file" + assert state.attributes[ATTR_MEDIA_TITLE] == "No album" # reversed for url assert state.attributes[ATTR_MEDIA_ARTIST] == "Google" - assert state.attributes[ATTR_MEDIA_ALBUM_NAME] == "No album" + assert state.attributes[ATTR_MEDIA_ALBUM_NAME] == "Short TTS file" # reversed assert state.attributes[ATTR_MEDIA_ALBUM_ARTIST] == "The xx" assert state.attributes[ATTR_MEDIA_TRACK] == 1 assert not state.attributes[ATTR_MEDIA_SHUFFLE] @@ -466,7 +466,7 @@ async def test_last_outputs_master(hass, mock_api_object): assert mock_api_object.set_enabled_outputs.call_count == 2 -async def test_bunch_of_stuff_master(hass, mock_api_object, get_request_return_values): +async def test_bunch_of_stuff_master(hass, get_request_return_values, mock_api_object): """Run bunch of stuff.""" await _service_call(hass, TEST_MASTER_ENTITY_NAME, SERVICE_TURN_ON) await _service_call(hass, TEST_MASTER_ENTITY_NAME, SERVICE_TURN_OFF) @@ -710,6 +710,9 @@ async def test_librespot_java_stuff( await hass.async_block_till_done() state = hass.states.get(TEST_MASTER_ENTITY_NAME) assert state.attributes[ATTR_INPUT_SOURCE] == "librespot-java (pipe)" + # test title and album not reversed when data_kind not url + assert state.attributes[ATTR_MEDIA_TITLE] == "librespot-java" + assert state.attributes[ATTR_MEDIA_ALBUM_NAME] == "some album" async def test_librespot_java_play_media(hass, pipe_control_api_object): From eb95c5cd2e1d91b4122e64ee604f5a3a6ca8586b Mon Sep 17 00:00:00 2001 From: Jason Hunter Date: Wed, 3 Jun 2020 11:51:57 -0400 Subject: [PATCH 357/406] update renew logic to work better with cameras responding with invalid termination times by extending the duration (#36393) --- homeassistant/components/onvif/device.py | 10 ---------- homeassistant/components/onvif/event.py | 10 +++++++--- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/onvif/device.py b/homeassistant/components/onvif/device.py index eec270d5b94..39f37eb02e4 100644 --- a/homeassistant/components/onvif/device.py +++ b/homeassistant/components/onvif/device.py @@ -100,16 +100,6 @@ class ONVIFDevice: if self.capabilities.ptz: self.device.create_ptz_service() - if self._dt_diff_seconds > 300 and self.capabilities.events: - self.capabilities.events = False - LOGGER.warning( - "The system clock on '%s' is more than 5 minutes off. " - "Although this device supports events, they will be " - "disabled until the device clock is fixed as we will " - "not be able to renew the subscription.", - self.name, - ) - if self.capabilities.events: self.events = EventManager( self.hass, self.device, self.config_entry.unique_id diff --git a/homeassistant/components/onvif/event.py b/homeassistant/components/onvif/event.py index 3888db4fa8e..9084a06e7db 100644 --- a/homeassistant/components/onvif/event.py +++ b/homeassistant/components/onvif/event.py @@ -107,7 +107,9 @@ class EventManager: if not self._subscription: return - termination_time = (dt_util.utcnow() + dt.timedelta(minutes=30)).isoformat() + termination_time = ( + (dt_util.utcnow() + dt.timedelta(days=1)).replace(microsecond=0).isoformat() + ) await self._subscription.Renew(termination_time) async def async_pull_messages(self, _now: dt = None) -> None: @@ -119,8 +121,10 @@ class EventManager: req.Timeout = dt.timedelta(seconds=60) response = await pullpoint.PullMessages(req) - # Renew subscription if less than 60 seconds left - if (response.TerminationTime - dt_util.utcnow()).total_seconds() < 60: + # Renew subscription if less than two hours is left + if ( + dt_util.as_utc(response.TerminationTime) - dt_util.utcnow() + ).total_seconds() < 7200: await self.async_renew() # Parse response From 3b606504a88b4a53becc8ae80563cc27896ec4d6 Mon Sep 17 00:00:00 2001 From: Emilv2 Date: Wed, 3 Jun 2020 17:52:20 +0200 Subject: [PATCH 358/406] Add new is_reatime attribute for De Lijn (#36369) --- homeassistant/components/delijn/sensor.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/delijn/sensor.py b/homeassistant/components/delijn/sensor.py index 7673e473e43..8c73fecf26e 100644 --- a/homeassistant/components/delijn/sensor.py +++ b/homeassistant/components/delijn/sensor.py @@ -94,6 +94,7 @@ class DeLijnPublicTransportSensor(Entity): self._attributes["final_destination"] = first["final_destination"] self._attributes["due_at_schedule"] = first["due_at_schedule"] self._attributes["due_at_realtime"] = first["due_at_realtime"] + self._attributes["is_realtime"] = first["is_realtime"] self._attributes["next_passages"] = self.line.passages self._available = True except (KeyError, IndexError): From 9aac8482d5824a00916b9834336c9c981bfce2f7 Mon Sep 17 00:00:00 2001 From: jjlawren Date: Wed, 3 Jun 2020 11:20:21 -0500 Subject: [PATCH 359/406] Remove deprecated Plex YAML config (#36388) --- homeassistant/components/plex/__init__.py | 78 ------------- homeassistant/components/plex/config_flow.py | 16 --- homeassistant/components/plex/const.py | 2 - homeassistant/components/plex/server.py | 2 +- homeassistant/components/plex/strings.json | 4 +- tests/components/plex/test_config_flow.py | 111 ++++++------------- tests/components/plex/test_init.py | 49 +------- 7 files changed, 36 insertions(+), 226 deletions(-) diff --git a/homeassistant/components/plex/__init__.py b/homeassistant/components/plex/__init__.py index cc281ed8147..e460115ef0b 100644 --- a/homeassistant/components/plex/__init__.py +++ b/homeassistant/components/plex/__init__.py @@ -9,18 +9,12 @@ from plexwebsocket import PlexWebsocket import requests.exceptions import voluptuous as vol -from homeassistant import config_entries -from homeassistant.components.media_player import DOMAIN as MP_DOMAIN from homeassistant.components.media_player.const import ( ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_CONTENT_TYPE, ) from homeassistant.const import ( ATTR_ENTITY_ID, - CONF_HOST, - CONF_PORT, - CONF_SSL, - CONF_TOKEN, CONF_URL, CONF_VERIFY_SSL, EVENT_HOMEASSISTANT_STOP, @@ -34,19 +28,12 @@ from homeassistant.helpers.dispatcher import ( ) from .const import ( - CONF_IGNORE_NEW_SHARED_USERS, CONF_SERVER, CONF_SERVER_IDENTIFIER, - CONF_SHOW_ALL_CONTROLS, - CONF_USE_EPISODE_ART, - DEFAULT_PORT, - DEFAULT_SSL, - DEFAULT_VERIFY_SSL, DISPATCHERS, DOMAIN as PLEX_DOMAIN, PLATFORMS, PLATFORMS_COMPLETED, - PLEX_MEDIA_PLAYER_OPTIONS, PLEX_SERVER_CONFIG, PLEX_UPDATE_PLATFORMS_SIGNAL, SERVERS, @@ -56,40 +43,6 @@ from .const import ( from .errors import ShouldUpdateConfigEntry from .server import PlexServer -MEDIA_PLAYER_SCHEMA = vol.All( - cv.deprecated(CONF_SHOW_ALL_CONTROLS, invalidation_version="0.110"), - vol.Schema( - { - vol.Optional(CONF_USE_EPISODE_ART, default=False): cv.boolean, - vol.Optional(CONF_SHOW_ALL_CONTROLS): cv.boolean, - vol.Optional(CONF_IGNORE_NEW_SHARED_USERS, default=False): cv.boolean, - } - ), -) - -SERVER_CONFIG_SCHEMA = vol.Schema( - vol.All( - { - vol.Optional(CONF_HOST): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, - vol.Optional(CONF_TOKEN): cv.string, - vol.Optional(CONF_SERVER): cv.string, - vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean, - vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): cv.boolean, - vol.Optional(MP_DOMAIN, default={}): MEDIA_PLAYER_SCHEMA, - }, - cv.has_at_least_one_key(CONF_HOST, CONF_TOKEN), - ) -) - -CONFIG_SCHEMA = vol.Schema( - vol.All( - cv.deprecated(PLEX_DOMAIN, invalidation_version="0.111"), - {PLEX_DOMAIN: SERVER_CONFIG_SCHEMA}, - ), - extra=vol.ALLOW_EXTRA, -) - _LOGGER = logging.getLogger(__package__) @@ -100,32 +53,9 @@ async def async_setup(hass, config): {SERVERS: {}, DISPATCHERS: {}, WEBSOCKETS: {}, PLATFORMS_COMPLETED: {}}, ) - plex_config = config.get(PLEX_DOMAIN, {}) - if plex_config: - _async_setup_plex(hass, plex_config) - return True -def _async_setup_plex(hass, config): - """Pass configuration to a config flow.""" - server_config = dict(config) - if MP_DOMAIN in server_config: - hass.data.setdefault(PLEX_MEDIA_PLAYER_OPTIONS, server_config.pop(MP_DOMAIN)) - if CONF_HOST in server_config: - protocol = "https" if server_config.pop(CONF_SSL) else "http" - server_config[ - CONF_URL - ] = f"{protocol}://{server_config.pop(CONF_HOST)}:{server_config.pop(CONF_PORT)}" - hass.async_create_task( - hass.config_entries.flow.async_init( - PLEX_DOMAIN, - context={"source": config_entries.SOURCE_IMPORT}, - data=server_config, - ) - ) - - async def async_setup_entry(hass, entry): """Set up Plex from a config entry.""" server_config = entry.data[PLEX_SERVER_CONFIG] @@ -135,14 +65,6 @@ async def async_setup_entry(hass, entry): entry, unique_id=entry.data[CONF_SERVER_IDENTIFIER] ) - if MP_DOMAIN not in entry.options: - options = dict(entry.options) - options.setdefault( - MP_DOMAIN, - hass.data.get(PLEX_MEDIA_PLAYER_OPTIONS) or MEDIA_PLAYER_SCHEMA({}), - ) - hass.config_entries.async_update_entry(entry, options=options) - plex_server = PlexServer( hass, server_config, entry.data[CONF_SERVER_IDENTIFIER], entry.options ) diff --git a/homeassistant/components/plex/config_flow.py b/homeassistant/components/plex/config_flow.py index a4e2bb0a589..5057b535ea6 100644 --- a/homeassistant/components/plex/config_flow.py +++ b/homeassistant/components/plex/config_flow.py @@ -170,10 +170,6 @@ class PlexFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Validate a provided configuration.""" errors = {} self.current_login = server_config - is_importing = ( - self.context["source"] # pylint: disable=no-member - == config_entries.SOURCE_IMPORT - ) plex_server = PlexServer(self.hass, server_config) try: @@ -196,11 +192,6 @@ class PlexFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): errors[CONF_HOST] = "not_found" except ServerNotSpecified as available_servers: - if is_importing: - _LOGGER.warning( - "Imported configuration has multiple available Plex servers. Specify server in configuration or add a new Integration." - ) - return self.async_abort(reason="non-interactive") self.available_servers = available_servers.args[0] return await self.async_step_select_server() @@ -209,8 +200,6 @@ class PlexFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): return self.async_abort(reason="unknown") if errors: - if is_importing: - return self.async_abort(reason="non-interactive") if self._manual: return await self.async_step_manual_setup( user_input=server_config, errors=errors @@ -274,11 +263,6 @@ class PlexFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): errors={}, ) - async def async_step_import(self, import_config): - """Import from Plex configuration.""" - _LOGGER.debug("Imported Plex configuration") - return await self.async_step_server_validate(import_config) - async def async_step_integration_discovery(self, discovery_info): """Handle GDM discovery.""" machine_identifier = discovery_info["data"]["Resource-Identifier"] diff --git a/homeassistant/components/plex/const.py b/homeassistant/components/plex/const.py index 0ab76e10f50..9d9b8ed8915 100644 --- a/homeassistant/components/plex/const.py +++ b/homeassistant/components/plex/const.py @@ -16,7 +16,6 @@ PLATFORMS_COMPLETED = "platforms_completed" SERVERS = "servers" WEBSOCKETS = "websockets" -PLEX_MEDIA_PLAYER_OPTIONS = "plex_mp_options" PLEX_SERVER_CONFIG = "server_config" PLEX_NEW_MP_SIGNAL = "plex_new_mp_signal.{}" @@ -27,7 +26,6 @@ PLEX_UPDATE_SENSOR_SIGNAL = "plex_update_sensor_signal.{}" CONF_SERVER = "server" CONF_SERVER_IDENTIFIER = "server_id" CONF_USE_EPISODE_ART = "use_episode_art" -CONF_SHOW_ALL_CONTROLS = "show_all_controls" CONF_IGNORE_NEW_SHARED_USERS = "ignore_new_shared_users" CONF_IGNORE_PLEX_WEB_CLIENTS = "ignore_plex_web_clients" CONF_MONITORED_USERS = "monitored_users" diff --git a/homeassistant/components/plex/server.py b/homeassistant/components/plex/server.py index cf0a90e2b0a..dda4c0a46b5 100644 --- a/homeassistant/components/plex/server.py +++ b/homeassistant/components/plex/server.py @@ -353,7 +353,7 @@ class PlexServer: @property def option_use_episode_art(self): """Return use_episode_art option.""" - return self.options[MP_DOMAIN][CONF_USE_EPISODE_ART] + return self.options[MP_DOMAIN].get(CONF_USE_EPISODE_ART, False) @property def option_monitored_users(self): diff --git a/homeassistant/components/plex/strings.json b/homeassistant/components/plex/strings.json index 09dbc9a8ecf..2f50e2d3090 100644 --- a/homeassistant/components/plex/strings.json +++ b/homeassistant/components/plex/strings.json @@ -41,8 +41,6 @@ "all_configured": "All linked servers already configured", "already_configured": "This Plex server is already configured", "already_in_progress": "Plex is being configured", - "invalid_import": "Imported configuration is invalid", - "non-interactive": "Non-interactive import", "token_request_timeout": "Timed out obtaining token", "unknown": "Failed for unknown reason" } @@ -60,4 +58,4 @@ } } } -} \ No newline at end of file +} diff --git a/tests/components/plex/test_config_flow.py b/tests/components/plex/test_config_flow.py index 0b0ff2a2711..c51c0670525 100644 --- a/tests/components/plex/test_config_flow.py +++ b/tests/components/plex/test_config_flow.py @@ -37,7 +37,7 @@ from homeassistant.const import ( from homeassistant.helpers.dispatcher import async_dispatcher_send from .const import DEFAULT_DATA, DEFAULT_OPTIONS, MOCK_SERVERS, MOCK_TOKEN -from .mock_classes import MockGDM, MockPlexAccount, MockPlexServer +from .mock_classes import MockGDM, MockPlexAccount, MockPlexServer, MockResource from tests.async_mock import patch from tests.common import MockConfigEntry @@ -75,45 +75,42 @@ async def test_bad_credentials(hass): assert result["errors"][CONF_TOKEN] == "faulty_credentials" -async def test_import_success(hass): - """Test a successful configuration import.""" - - mock_plex_server = MockPlexServer() - - with patch("plexapi.server.PlexServer", return_value=mock_plex_server): - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": "import"}, - data={ - CONF_TOKEN: MOCK_TOKEN, - CONF_URL: f"https://{MOCK_SERVERS[0][CONF_HOST]}:{MOCK_SERVERS[0][CONF_PORT]}", - }, - ) - - assert result["type"] == "create_entry" - assert result["title"] == mock_plex_server.friendlyName - assert result["data"][CONF_SERVER] == mock_plex_server.friendlyName - assert result["data"][CONF_SERVER_IDENTIFIER] == mock_plex_server.machineIdentifier - assert result["data"][PLEX_SERVER_CONFIG][CONF_URL] == mock_plex_server._baseurl - assert result["data"][PLEX_SERVER_CONFIG][CONF_TOKEN] == MOCK_TOKEN - - -async def test_import_bad_hostname(hass): +async def test_bad_hostname(hass): """Test when an invalid address is provided.""" + mock_plex_account = MockPlexAccount() + + await async_process_ha_core_config( + hass, {"internal_url": "http://example.local:8123"}, + ) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": "user"} + ) + assert result["type"] == "form" + assert result["step_id"] == "user" with patch( - "plexapi.server.PlexServer", side_effect=requests.exceptions.ConnectionError + "plexapi.myplex.MyPlexAccount", return_value=mock_plex_account + ), patch.object( + MockResource, "connect", side_effect=requests.exceptions.ConnectionError + ), patch( + "plexauth.PlexAuth.initiate_auth" + ), patch( + "plexauth.PlexAuth.token", return_value=MOCK_TOKEN ): - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": "import"}, - data={ - CONF_TOKEN: MOCK_TOKEN, - CONF_URL: f"http://{MOCK_SERVERS[0][CONF_HOST]}:{MOCK_SERVERS[0][CONF_PORT]}", - }, + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input={} ) - assert result["type"] == "abort" - assert result["reason"] == "non-interactive" + assert result["type"] == "external" + + result = await hass.config_entries.flow.async_configure(result["flow_id"]) + assert result["type"] == "external_done" + + result = await hass.config_entries.flow.async_configure(result["flow_id"]) + + assert result["type"] == "form" + assert result["step_id"] == "user" + assert result["errors"][CONF_HOST] == "not_found" async def test_unknown_exception(hass): @@ -311,35 +308,6 @@ async def test_adding_last_unconfigured_server(hass): assert result["data"][PLEX_SERVER_CONFIG][CONF_TOKEN] == MOCK_TOKEN -async def test_already_configured(hass): - """Test a duplicated successful flow.""" - - mock_plex_server = MockPlexServer() - - MockConfigEntry( - domain=DOMAIN, - data={ - CONF_SERVER: MOCK_SERVERS[0][CONF_SERVER], - CONF_SERVER_IDENTIFIER: MOCK_SERVERS[0][CONF_SERVER_IDENTIFIER], - }, - unique_id=MOCK_SERVERS[0][CONF_SERVER_IDENTIFIER], - ).add_to_hass(hass) - - with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch( - "plexauth.PlexAuth.initiate_auth" - ), patch("plexauth.PlexAuth.token", return_value=MOCK_TOKEN): - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": "import"}, - data={ - CONF_TOKEN: MOCK_TOKEN, - CONF_URL: f"http://{MOCK_SERVERS[0][CONF_HOST]}:{MOCK_SERVERS[0][CONF_PORT]}", - }, - ) - assert result["type"] == "abort" - assert result["reason"] == "already_configured" - - async def test_all_available_servers_configured(hass): """Test when all available servers are already configured.""" @@ -542,21 +510,6 @@ async def test_callback_view(hass, aiohttp_client): assert resp.status == 200 -async def test_multiple_servers_with_import(hass): - """Test importing a config with multiple servers available.""" - - with patch( - "plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount(servers=2) - ), patch("plexauth.PlexAuth.initiate_auth"), patch( - "plexauth.PlexAuth.token", return_value=MOCK_TOKEN - ): - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": "import"}, data={CONF_TOKEN: MOCK_TOKEN}, - ) - assert result["type"] == "abort" - assert result["reason"] == "non-interactive" - - async def test_manual_config(hass): """Test creating via manual configuration.""" await async_process_ha_core_config( diff --git a/tests/components/plex/test_init.py b/tests/components/plex/test_init.py index e34476f1813..461efe9d320 100644 --- a/tests/components/plex/test_init.py +++ b/tests/components/plex/test_init.py @@ -6,7 +6,6 @@ import ssl import plexapi import requests -from homeassistant.components.media_player import DOMAIN as MP_DOMAIN import homeassistant.components.plex.const as const from homeassistant.config_entries import ( ENTRY_STATE_LOADED, @@ -14,60 +13,16 @@ from homeassistant.config_entries import ( ENTRY_STATE_SETUP_ERROR, ENTRY_STATE_SETUP_RETRY, ) -from homeassistant.const import ( - CONF_HOST, - CONF_PORT, - CONF_SSL, - CONF_TOKEN, - CONF_URL, - CONF_VERIFY_SSL, -) +from homeassistant.const import CONF_URL, CONF_VERIFY_SSL from homeassistant.helpers.dispatcher import async_dispatcher_send -from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util -from .const import DEFAULT_DATA, DEFAULT_OPTIONS, MOCK_SERVERS, MOCK_TOKEN +from .const import DEFAULT_DATA, DEFAULT_OPTIONS from .mock_classes import MockPlexAccount, MockPlexServer from tests.async_mock import patch from tests.common import MockConfigEntry, async_fire_time_changed - -async def test_setup_with_config(hass): - """Test setup component with config.""" - config = { - const.DOMAIN: { - CONF_HOST: MOCK_SERVERS[0][CONF_HOST], - CONF_PORT: MOCK_SERVERS[0][CONF_PORT], - CONF_TOKEN: MOCK_TOKEN, - CONF_SSL: True, - CONF_VERIFY_SSL: True, - MP_DOMAIN: { - const.CONF_IGNORE_NEW_SHARED_USERS: False, - const.CONF_USE_EPISODE_ART: False, - }, - }, - } - - mock_plex_server = MockPlexServer() - - with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch( - "homeassistant.components.plex.PlexWebsocket.listen" - ) as mock_listen: - assert await async_setup_component(hass, const.DOMAIN, config) is True - await hass.async_block_till_done() - - assert mock_listen.called - assert len(hass.config_entries.async_entries(const.DOMAIN)) == 1 - entry = hass.config_entries.async_entries(const.DOMAIN)[0] - assert entry.state == ENTRY_STATE_LOADED - - server_id = mock_plex_server.machineIdentifier - loaded_server = hass.data[const.DOMAIN][const.SERVERS][server_id] - - assert loaded_server.plex_server == mock_plex_server - - # class TestClockedPlex(ClockedTestCase): # """Create clock-controlled tests.async_mock class.""" From 1186c2c48cd4e8b39b9f3b3f0238503fd962a222 Mon Sep 17 00:00:00 2001 From: Frederik Gladhorn Date: Wed, 3 Jun 2020 18:36:59 +0200 Subject: [PATCH 360/406] Pass config into NAD constructor (#34961) Co-authored-by: Martin Hjelmare --- homeassistant/components/nad/media_player.py | 71 +++++++------------- 1 file changed, 24 insertions(+), 47 deletions(-) diff --git a/homeassistant/components/nad/media_player.py b/homeassistant/components/nad/media_player.py index 066088ab994..a7245866188 100644 --- a/homeassistant/components/nad/media_player.py +++ b/homeassistant/components/nad/media_player.py @@ -64,65 +64,42 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the NAD platform.""" - if config.get(CONF_TYPE) == "RS232": + if config.get(CONF_TYPE) in ("RS232", "Telnet"): add_entities( - [ - NAD( - config.get(CONF_NAME), - NADReceiver(config.get(CONF_SERIAL_PORT)), - config.get(CONF_MIN_VOLUME), - config.get(CONF_MAX_VOLUME), - config.get(CONF_SOURCE_DICT), - ) - ], - True, - ) - elif config.get(CONF_TYPE) == "Telnet": - add_entities( - [ - NAD( - config.get(CONF_NAME), - NADReceiverTelnet(config.get(CONF_HOST), config.get(CONF_PORT)), - config.get(CONF_MIN_VOLUME), - config.get(CONF_MAX_VOLUME), - config.get(CONF_SOURCE_DICT), - ) - ], - True, + [NAD(config)], True, ) else: add_entities( - [ - NADtcp( - config.get(CONF_NAME), - NADReceiverTCP(config.get(CONF_HOST)), - config.get(CONF_MIN_VOLUME), - config.get(CONF_MAX_VOLUME), - config.get(CONF_VOLUME_STEP), - ) - ], - True, + [NADtcp(config)], True, ) class NAD(MediaPlayerEntity): """Representation of a NAD Receiver.""" - def __init__(self, name, nad_receiver, min_volume, max_volume, source_dict): + def __init__(self, config): """Initialize the NAD Receiver device.""" - self._name = name - self._nad_receiver = nad_receiver - self._min_volume = min_volume - self._max_volume = max_volume - self._source_dict = source_dict + self.config = config + self._instantiate_nad_receiver() + self._min_volume = config[CONF_MIN_VOLUME] + self._max_volume = config[CONF_MAX_VOLUME] + self._source_dict = config[CONF_SOURCE_DICT] self._reverse_mapping = {value: key for key, value in self._source_dict.items()} self._volume = self._state = self._mute = self._source = None + def _instantiate_nad_receiver(self) -> NADReceiver: + if self.config[CONF_TYPE] == "RS232": + self._nad_receiver = NADReceiver(self.config[CONF_SERIAL_PORT]) + else: + host = self.config.get(CONF_HOST) + port = self.config[CONF_PORT] + self._nad_receiver = NADReceiverTelnet(host, port) + @property def name(self): """Return the name of the device.""" - return self._name + return self.config[CONF_NAME] @property def state(self): @@ -232,13 +209,13 @@ class NAD(MediaPlayerEntity): class NADtcp(MediaPlayerEntity): """Representation of a NAD Digital amplifier.""" - def __init__(self, name, nad_device, min_volume, max_volume, volume_step): + def __init__(self, config): """Initialize the amplifier.""" - self._name = name - self._nad_receiver = nad_device - self._min_vol = (min_volume + 90) * 2 # from dB to nad vol (0-200) - self._max_vol = (max_volume + 90) * 2 # from dB to nad vol (0-200) - self._volume_step = volume_step + self._name = config[CONF_NAME] + self._nad_receiver = NADReceiverTCP(config.get(CONF_HOST)) + self._min_vol = (config[CONF_MIN_VOLUME] + 90) * 2 # from dB to nad vol (0-200) + self._max_vol = (config[CONF_MAX_VOLUME] + 90) * 2 # from dB to nad vol (0-200) + self._volume_step = config[CONF_VOLUME_STEP] self._state = None self._mute = None self._nad_volume = None From 98a056f7a90e504e62c8518ec9b69dd4663f1f8f Mon Sep 17 00:00:00 2001 From: Austin Mroczek Date: Wed, 3 Jun 2020 09:38:31 -0700 Subject: [PATCH 361/406] Notify user if arming or disarming totalconnect alarm fails (#36085) --- .../totalconnect/alarm_control_panel.py | 13 +- .../components/totalconnect/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/totalconnect/common.py | 129 +++++++++++++++ .../totalconnect/test_alarm_control_panel.py | 153 ++++++++++++++++++ 6 files changed, 294 insertions(+), 7 deletions(-) create mode 100644 tests/components/totalconnect/common.py create mode 100644 tests/components/totalconnect/test_alarm_control_panel.py diff --git a/homeassistant/components/totalconnect/alarm_control_panel.py b/homeassistant/components/totalconnect/alarm_control_panel.py index 632673233ec..d7c17a1ccff 100644 --- a/homeassistant/components/totalconnect/alarm_control_panel.py +++ b/homeassistant/components/totalconnect/alarm_control_panel.py @@ -17,6 +17,7 @@ from homeassistant.const import ( STATE_ALARM_DISARMING, STATE_ALARM_TRIGGERED, ) +from homeassistant.exceptions import HomeAssistantError from .const import DOMAIN @@ -114,16 +115,20 @@ class TotalConnectAlarm(alarm.AlarmControlPanelEntity): def alarm_disarm(self, code=None): """Send disarm command.""" - self._client.disarm(self._location_id) + if self._client.disarm(self._location_id) is not True: + raise HomeAssistantError(f"TotalConnect failed to disarm {self._name}.") def alarm_arm_home(self, code=None): """Send arm home command.""" - self._client.arm_stay(self._location_id) + if self._client.arm_stay(self._location_id) is not True: + raise HomeAssistantError(f"TotalConnect failed to arm home {self._name}.") def alarm_arm_away(self, code=None): """Send arm away command.""" - self._client.arm_away(self._location_id) + if self._client.arm_away(self._location_id) is not True: + raise HomeAssistantError(f"TotalConnect failed to arm away {self._name}.") def alarm_arm_night(self, code=None): """Send arm night command.""" - self._client.arm_stay_night(self._location_id) + if self._client.arm_stay_night(self._location_id) is not True: + raise HomeAssistantError(f"TotalConnect failed to arm night {self._name}.") diff --git a/homeassistant/components/totalconnect/manifest.json b/homeassistant/components/totalconnect/manifest.json index fc19c889d8b..665a42aba1a 100644 --- a/homeassistant/components/totalconnect/manifest.json +++ b/homeassistant/components/totalconnect/manifest.json @@ -2,7 +2,7 @@ "domain": "totalconnect", "name": "Honeywell Total Connect Alarm", "documentation": "https://www.home-assistant.io/integrations/totalconnect", - "requirements": ["total_connect_client==0.54.1"], + "requirements": ["total_connect_client==0.55"], "dependencies": [], "codeowners": ["@austinmroczek"], "config_flow": true diff --git a/requirements_all.txt b/requirements_all.txt index 00b309569b1..dcbdbf8deb7 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2102,7 +2102,7 @@ todoist-python==8.0.0 toonapilib==3.2.4 # homeassistant.components.totalconnect -total_connect_client==0.54.1 +total_connect_client==0.55 # homeassistant.components.tplink_lte tp-connected==0.0.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index b7348e7f256..04ed4863511 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -853,7 +853,7 @@ teslajsonpy==0.8.1 toonapilib==3.2.4 # homeassistant.components.totalconnect -total_connect_client==0.54.1 +total_connect_client==0.55 # homeassistant.components.transmission transmissionrpc==0.11 diff --git a/tests/components/totalconnect/common.py b/tests/components/totalconnect/common.py new file mode 100644 index 00000000000..c2d6f92015c --- /dev/null +++ b/tests/components/totalconnect/common.py @@ -0,0 +1,129 @@ +"""Common methods used across tests for TotalConnect.""" +from total_connect_client import TotalConnectClient + +from homeassistant.components.totalconnect import 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 + +LOCATION_INFO_BASIC_NORMAL = { + "LocationID": "123456", + "LocationName": "test", + "SecurityDeviceID": "987654", + "PhotoURL": "http://www.example.com/some/path/to/file.jpg", + "LocationModuleFlags": "Security=1,Video=0,Automation=0,GPS=0,VideoPIR=0", + "DeviceList": None, +} + +LOCATIONS = {"LocationInfoBasic": [LOCATION_INFO_BASIC_NORMAL]} + +MODULE_FLAGS = "Some=0,Fake=1,Flags=2" + +USER = { + "UserID": "1234567", + "Username": "username", + "UserFeatureList": "Master=0,User Administration=0,Configuration Administration=0", +} + +RESPONSE_AUTHENTICATE = { + "ResultCode": 0, + "SessionID": 1, + "Locations": LOCATIONS, + "ModuleFlags": MODULE_FLAGS, + "UserInfo": USER, +} + +PARTITION_DISARMED = { + "PartitionID": "1", + "ArmingState": TotalConnectClient.TotalConnectLocation.DISARMED, +} + +PARTITION_ARMED_STAY = { + "PartitionID": "1", + "ArmingState": TotalConnectClient.TotalConnectLocation.ARMED_STAY, +} + +PARTITION_ARMED_AWAY = { + "PartitionID": "1", + "ArmingState": TotalConnectClient.TotalConnectLocation.ARMED_AWAY, +} + +PARTITION_INFO_DISARMED = {0: PARTITION_DISARMED} +PARTITION_INFO_ARMED_STAY = {0: PARTITION_ARMED_STAY} +PARTITION_INFO_ARMED_AWAY = {0: PARTITION_ARMED_AWAY} + +PARTITIONS_DISARMED = {"PartitionInfo": PARTITION_INFO_DISARMED} +PARTITIONS_ARMED_STAY = {"PartitionInfo": PARTITION_INFO_ARMED_STAY} +PARTITIONS_ARMED_AWAY = {"PartitionInfo": PARTITION_INFO_ARMED_AWAY} + +ZONE_NORMAL = { + "ZoneID": "1", + "ZoneDescription": "Normal", + "ZoneStatus": TotalConnectClient.ZONE_STATUS_NORMAL, + "PartitionId": "1", +} + +ZONE_INFO = [ZONE_NORMAL] +ZONES = {"ZoneInfo": ZONE_INFO} + +METADATA_DISARMED = { + "Partitions": PARTITIONS_DISARMED, + "Zones": ZONES, + "PromptForImportSecuritySettings": False, + "IsInACLoss": False, + "IsCoverTampered": False, + "Bell1SupervisionFailure": False, + "Bell2SupervisionFailure": False, + "IsInLowBattery": False, +} + +METADATA_ARMED_STAY = METADATA_DISARMED.copy() +METADATA_ARMED_STAY["Partitions"] = PARTITIONS_ARMED_STAY + +METADATA_ARMED_AWAY = METADATA_DISARMED.copy() +METADATA_ARMED_AWAY["Partitions"] = PARTITIONS_ARMED_AWAY + +RESPONSE_DISARMED = {"ResultCode": 0, "PanelMetadataAndStatus": METADATA_DISARMED} +RESPONSE_ARMED_STAY = {"ResultCode": 0, "PanelMetadataAndStatus": METADATA_ARMED_STAY} +RESPONSE_ARMED_AWAY = {"ResultCode": 0, "PanelMetadataAndStatus": METADATA_ARMED_AWAY} + +RESPONSE_ARM_SUCCESS = {"ResultCode": TotalConnectClient.TotalConnectClient.ARM_SUCCESS} +RESPONSE_ARM_FAILURE = { + "ResultCode": TotalConnectClient.TotalConnectClient.COMMAND_FAILED +} +RESPONSE_DISARM_SUCCESS = { + "ResultCode": TotalConnectClient.TotalConnectClient.DISARM_SUCCESS +} +RESPONSE_DISARM_FAILURE = { + "ResultCode": TotalConnectClient.TotalConnectClient.COMMAND_FAILED, + "ResultData": "Command Failed", +} + + +async def setup_platform(hass, platform): + """Set up the TotalConnect platform.""" + # first set up a config entry and add it to hass + mock_entry = MockConfigEntry( + domain=DOMAIN, + data={CONF_USERNAME: "user@email.com", CONF_PASSWORD: "password"}, + ) + mock_entry.add_to_hass(hass) + + responses = [RESPONSE_AUTHENTICATE, RESPONSE_DISARMED] + + with patch("homeassistant.components.totalconnect.PLATFORMS", [platform]), patch( + "zeep.Client", autospec=True + ), patch( + "homeassistant.components.totalconnect.TotalConnectClient.TotalConnectClient.request", + side_effect=responses, + ) as mock_request, patch( + "homeassistant.components.totalconnect.TotalConnectClient.TotalConnectClient.get_zone_details", + return_value=True, + ): + assert await async_setup_component(hass, DOMAIN, {}) + assert mock_request.call_count == 2 + await hass.async_block_till_done() + + return mock_entry diff --git a/tests/components/totalconnect/test_alarm_control_panel.py b/tests/components/totalconnect/test_alarm_control_panel.py new file mode 100644 index 00000000000..75e07f09bf7 --- /dev/null +++ b/tests/components/totalconnect/test_alarm_control_panel.py @@ -0,0 +1,153 @@ +"""Tests for the TotalConnect alarm control panel device.""" +import pytest + +from homeassistant.components.alarm_control_panel import DOMAIN as ALARM_DOMAIN +from homeassistant.const import ( + ATTR_ENTITY_ID, + ATTR_FRIENDLY_NAME, + SERVICE_ALARM_ARM_AWAY, + SERVICE_ALARM_ARM_HOME, + SERVICE_ALARM_DISARM, + STATE_ALARM_ARMED_AWAY, + STATE_ALARM_ARMED_HOME, + STATE_ALARM_DISARMED, +) + +from .common import ( + RESPONSE_ARM_FAILURE, + RESPONSE_ARM_SUCCESS, + RESPONSE_ARMED_AWAY, + RESPONSE_ARMED_STAY, + RESPONSE_DISARM_FAILURE, + RESPONSE_DISARM_SUCCESS, + RESPONSE_DISARMED, + setup_platform, +) + +from tests.async_mock import patch + +ENTITY_ID = "alarm_control_panel.test" +CODE = "-1" +DATA = {ATTR_ENTITY_ID: ENTITY_ID} + + +async def test_attributes(hass): + """Test the alarm control panel attributes are correct.""" + with patch( + "homeassistant.components.totalconnect.TotalConnectClient.TotalConnectClient.request", + return_value=RESPONSE_DISARMED, + ) as mock_request: + await setup_platform(hass, ALARM_DOMAIN) + state = hass.states.get(ENTITY_ID) + assert state.state == STATE_ALARM_DISARMED + mock_request.assert_called_once() + assert state.attributes.get(ATTR_FRIENDLY_NAME) == "test" + + +async def test_arm_home_success(hass): + """Test arm home method success.""" + responses = [RESPONSE_DISARMED, RESPONSE_ARM_SUCCESS, RESPONSE_ARMED_STAY] + with patch( + "homeassistant.components.totalconnect.TotalConnectClient.TotalConnectClient.request", + side_effect=responses, + ): + await setup_platform(hass, ALARM_DOMAIN) + assert STATE_ALARM_DISARMED == hass.states.get(ENTITY_ID).state + + await hass.services.async_call( + ALARM_DOMAIN, SERVICE_ALARM_ARM_HOME, DATA, blocking=True + ) + + await hass.async_block_till_done() + assert STATE_ALARM_ARMED_HOME == hass.states.get(ENTITY_ID).state + + +async def test_arm_home_failure(hass): + """Test arm home method failure.""" + responses = [RESPONSE_DISARMED, RESPONSE_ARM_FAILURE, RESPONSE_DISARMED] + with patch( + "homeassistant.components.totalconnect.TotalConnectClient.TotalConnectClient.request", + side_effect=responses, + ): + await setup_platform(hass, ALARM_DOMAIN) + assert STATE_ALARM_DISARMED == hass.states.get(ENTITY_ID).state + + with pytest.raises(Exception) as e: + await hass.services.async_call( + ALARM_DOMAIN, SERVICE_ALARM_ARM_HOME, DATA, blocking=True + ) + await hass.async_block_till_done() + assert f"{e.value}" == "TotalConnect failed to arm home test." + assert STATE_ALARM_DISARMED == hass.states.get(ENTITY_ID).state + + +async def test_arm_away_success(hass): + """Test arm away method success.""" + responses = [RESPONSE_DISARMED, RESPONSE_ARM_SUCCESS, RESPONSE_ARMED_AWAY] + with patch( + "homeassistant.components.totalconnect.TotalConnectClient.TotalConnectClient.request", + side_effect=responses, + ): + await setup_platform(hass, ALARM_DOMAIN) + assert STATE_ALARM_DISARMED == hass.states.get(ENTITY_ID).state + + await hass.services.async_call( + ALARM_DOMAIN, SERVICE_ALARM_ARM_AWAY, DATA, blocking=True + ) + await hass.async_block_till_done() + assert STATE_ALARM_ARMED_AWAY == hass.states.get(ENTITY_ID).state + + +async def test_arm_away_failure(hass): + """Test arm away method failure.""" + responses = [RESPONSE_DISARMED, RESPONSE_ARM_FAILURE, RESPONSE_DISARMED] + with patch( + "homeassistant.components.totalconnect.TotalConnectClient.TotalConnectClient.request", + side_effect=responses, + ): + await setup_platform(hass, ALARM_DOMAIN) + assert STATE_ALARM_DISARMED == hass.states.get(ENTITY_ID).state + + with pytest.raises(Exception) as e: + await hass.services.async_call( + ALARM_DOMAIN, SERVICE_ALARM_ARM_AWAY, DATA, blocking=True + ) + await hass.async_block_till_done() + assert f"{e.value}" == "TotalConnect failed to arm away test." + assert STATE_ALARM_DISARMED == hass.states.get(ENTITY_ID).state + + +async def test_disarm_success(hass): + """Test disarm method success.""" + responses = [RESPONSE_ARMED_AWAY, RESPONSE_DISARM_SUCCESS, RESPONSE_DISARMED] + with patch( + "homeassistant.components.totalconnect.TotalConnectClient.TotalConnectClient.request", + side_effect=responses, + ): + await setup_platform(hass, ALARM_DOMAIN) + assert STATE_ALARM_ARMED_AWAY == hass.states.get(ENTITY_ID).state + + await hass.services.async_call( + ALARM_DOMAIN, SERVICE_ALARM_DISARM, DATA, blocking=True + ) + await hass.async_block_till_done() + assert STATE_ALARM_DISARMED == hass.states.get(ENTITY_ID).state + + +async def test_disarm_failure(hass): + """Test disarm method failure.""" + responses = [RESPONSE_ARMED_AWAY, RESPONSE_DISARM_FAILURE, RESPONSE_ARMED_AWAY] + with patch( + "homeassistant.components.totalconnect.TotalConnectClient.TotalConnectClient.request", + side_effect=responses, + ): + await setup_platform(hass, ALARM_DOMAIN) + assert STATE_ALARM_ARMED_AWAY == hass.states.get(ENTITY_ID).state + + with pytest.raises(Exception) as e: + await hass.services.async_call( + ALARM_DOMAIN, SERVICE_ALARM_DISARM, DATA, blocking=True + ) + await hass.async_block_till_done() + assert f"{e.value}" == "TotalConnect failed to disarm test." + assert STATE_ALARM_ARMED_AWAY == hass.states.get(ENTITY_ID).state From 1510d5625a286b0b1abcc5f6c3a40d055c2ce8b5 Mon Sep 17 00:00:00 2001 From: Frederik Gladhorn Date: Wed, 3 Jun 2020 18:43:44 +0200 Subject: [PATCH 362/406] Update NAD states only when the device is on (#34809) Co-authored-by: Franck Nijhof Co-authored-by: Paulus Schoutsen --- homeassistant/components/nad/media_player.py | 35 +++++++++++--------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/nad/media_player.py b/homeassistant/components/nad/media_player.py index a7245866188..6145a89a594 100644 --- a/homeassistant/components/nad/media_player.py +++ b/homeassistant/components/nad/media_player.py @@ -167,23 +167,28 @@ class NAD(MediaPlayerEntity): """List of available input sources.""" return sorted(list(self._reverse_mapping.keys())) - def update(self): + @property + def available(self): + """Return if device is available.""" + return self._state is not None + + def update(self) -> None: """Retrieve latest state.""" - if self._nad_receiver.main_power("?") == "Off": - self._state = STATE_OFF - else: - self._state = STATE_ON + power_state = self._nad_receiver.main_power("?") + if not power_state: + self._state = None + return + self._state = ( + STATE_ON if self._nad_receiver.main_power("?") == "On" else STATE_OFF + ) - if self._nad_receiver.main_mute("?") == "Off": - self._mute = False - else: - self._mute = True - - 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("?")) + if self._state == STATE_ON: + self._mute = self._nad_receiver.main_mute("?") == "On" + 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 2b5bb8dac0e2ef274f562671e040fd091a17daa5 Mon Sep 17 00:00:00 2001 From: Andre Lengwenus Date: Wed, 3 Jun 2020 18:44:04 +0200 Subject: [PATCH 363/406] Cover group considers opening and closing states (#36203) --- homeassistant/components/group/cover.py | 26 ++++++- tests/components/group/test_cover.py | 91 ++++++++++++++++++++----- 2 files changed, 99 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/group/cover.py b/homeassistant/components/group/cover.py index c4e691eeff9..0832d466f8c 100644 --- a/homeassistant/components/group/cover.py +++ b/homeassistant/components/group/cover.py @@ -35,7 +35,9 @@ from homeassistant.const import ( ATTR_SUPPORTED_FEATURES, CONF_ENTITIES, CONF_NAME, - STATE_CLOSED, + STATE_CLOSING, + STATE_OPEN, + STATE_OPENING, ) from homeassistant.core import State, callback import homeassistant.helpers.config_validation as cv @@ -73,6 +75,8 @@ class CoverGroup(CoverEntity): """Initialize a CoverGroup entity.""" self._name = name self._is_closed = False + self._is_closing = False + self._is_opening = False self._cover_position: Optional[int] = 100 self._tilt_position = None self._supported_features = 0 @@ -176,6 +180,16 @@ class CoverGroup(CoverEntity): """Return if all covers in group are closed.""" return self._is_closed + @property + def is_opening(self): + """Return if the cover is opening or not.""" + return self._is_opening + + @property + def is_closing(self): + """Return if the cover is closing or not.""" + return self._is_closing + @property def current_cover_position(self) -> Optional[int]: """Return current position for all covers.""" @@ -253,13 +267,21 @@ class CoverGroup(CoverEntity): self._assumed_state = False self._is_closed = True + self._is_closing = False + self._is_opening = False for entity_id in self._entities: state = self.hass.states.get(entity_id) if not state: continue - if state.state != STATE_CLOSED: + if state.state == STATE_OPEN: self._is_closed = False break + if state.state == STATE_CLOSING: + self._is_closing = True + break + if state.state == STATE_OPENING: + self._is_opening = True + break self._cover_position = None if self._covers[KEY_POSITION]: diff --git a/tests/components/group/test_cover.py b/tests/components/group/test_cover.py index ac338dd8a80..1adad3e3d85 100644 --- a/tests/components/group/test_cover.py +++ b/tests/components/group/test_cover.py @@ -28,7 +28,9 @@ from homeassistant.const import ( SERVICE_TOGGLE, SERVICE_TOGGLE_COVER_TILT, STATE_CLOSED, + STATE_CLOSING, STATE_OPEN, + STATE_OPENING, ) from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util @@ -41,7 +43,7 @@ DEMO_COVER_POS = "cover.hall_window" DEMO_COVER_TILT = "cover.living_room_window" DEMO_TILT = "cover.tilt_demo" -CONFIG = { +CONFIG_ALL = { DOMAIN: [ {"platform": "demo"}, { @@ -51,28 +53,36 @@ CONFIG = { ] } +CONFIG_POS = { + DOMAIN: [ + {"platform": "demo"}, + { + "platform": "group", + CONF_ENTITIES: [DEMO_COVER_POS, DEMO_COVER_TILT, DEMO_TILT], + }, + ] +} + +CONFIG_ATTRIBUTES = { + DOMAIN: { + "platform": "group", + CONF_ENTITIES: [DEMO_COVER, DEMO_COVER_POS, DEMO_COVER_TILT, DEMO_TILT], + } +} + @pytest.fixture -async def setup_comp(hass): +async def setup_comp(hass, config_count): """Set up group cover component.""" - with assert_setup_component(2, DOMAIN): - await async_setup_component(hass, DOMAIN, CONFIG) + config, count = config_count + with assert_setup_component(count, DOMAIN): + await async_setup_component(hass, DOMAIN, config) await hass.async_block_till_done() -async def test_attributes(hass): +@pytest.mark.parametrize("config_count", [(CONFIG_ATTRIBUTES, 1)]) +async def test_attributes(hass, setup_comp): """Test handling of state attributes.""" - config = { - DOMAIN: { - "platform": "group", - CONF_ENTITIES: [DEMO_COVER, DEMO_COVER_POS, DEMO_COVER_TILT, DEMO_TILT], - } - } - - with assert_setup_component(1, DOMAIN): - await async_setup_component(hass, DOMAIN, config) - await hass.async_block_till_done() - state = hass.states.get(COVER_GROUP) assert state.state == STATE_CLOSED assert state.attributes[ATTR_FRIENDLY_NAME] == DEFAULT_NAME @@ -193,11 +203,13 @@ async def test_attributes(hass): assert state.attributes[ATTR_ASSUMED_STATE] is True +@pytest.mark.parametrize("config_count", [(CONFIG_ALL, 2)]) async def test_open_covers(hass, setup_comp): """Test open cover function.""" await hass.services.async_call( DOMAIN, SERVICE_OPEN_COVER, {ATTR_ENTITY_ID: COVER_GROUP}, blocking=True ) + for _ in range(10): future = dt_util.utcnow() + timedelta(seconds=1) async_fire_time_changed(hass, future) @@ -212,11 +224,13 @@ async def test_open_covers(hass, setup_comp): assert hass.states.get(DEMO_COVER_TILT).attributes[ATTR_CURRENT_POSITION] == 100 +@pytest.mark.parametrize("config_count", [(CONFIG_ALL, 2)]) async def test_close_covers(hass, setup_comp): """Test close cover function.""" await hass.services.async_call( DOMAIN, SERVICE_CLOSE_COVER, {ATTR_ENTITY_ID: COVER_GROUP}, blocking=True ) + for _ in range(10): future = dt_util.utcnow() + timedelta(seconds=1) async_fire_time_changed(hass, future) @@ -231,6 +245,7 @@ async def test_close_covers(hass, setup_comp): assert hass.states.get(DEMO_COVER_TILT).attributes[ATTR_CURRENT_POSITION] == 0 +@pytest.mark.parametrize("config_count", [(CONFIG_ALL, 2)]) async def test_toggle_covers(hass, setup_comp): """Test toggle cover function.""" # Start covers in open state @@ -280,6 +295,7 @@ async def test_toggle_covers(hass, setup_comp): assert hass.states.get(DEMO_COVER_TILT).attributes[ATTR_CURRENT_POSITION] == 100 +@pytest.mark.parametrize("config_count", [(CONFIG_ALL, 2)]) async def test_stop_covers(hass, setup_comp): """Test stop cover function.""" await hass.services.async_call( @@ -305,6 +321,7 @@ async def test_stop_covers(hass, setup_comp): assert hass.states.get(DEMO_COVER_TILT).attributes[ATTR_CURRENT_POSITION] == 80 +@pytest.mark.parametrize("config_count", [(CONFIG_ALL, 2)]) async def test_set_cover_position(hass, setup_comp): """Test set cover position function.""" await hass.services.async_call( @@ -327,6 +344,7 @@ async def test_set_cover_position(hass, setup_comp): assert hass.states.get(DEMO_COVER_TILT).attributes[ATTR_CURRENT_POSITION] == 50 +@pytest.mark.parametrize("config_count", [(CONFIG_ALL, 2)]) async def test_open_tilts(hass, setup_comp): """Test open tilt function.""" await hass.services.async_call( @@ -346,6 +364,7 @@ async def test_open_tilts(hass, setup_comp): ) +@pytest.mark.parametrize("config_count", [(CONFIG_ALL, 2)]) async def test_close_tilts(hass, setup_comp): """Test close tilt function.""" await hass.services.async_call( @@ -363,6 +382,7 @@ async def test_close_tilts(hass, setup_comp): assert hass.states.get(DEMO_COVER_TILT).attributes[ATTR_CURRENT_TILT_POSITION] == 0 +@pytest.mark.parametrize("config_count", [(CONFIG_ALL, 2)]) async def test_toggle_tilts(hass, setup_comp): """Test toggle tilt function.""" # Start tilted open @@ -415,6 +435,7 @@ async def test_toggle_tilts(hass, setup_comp): ) +@pytest.mark.parametrize("config_count", [(CONFIG_ALL, 2)]) async def test_stop_tilts(hass, setup_comp): """Test stop tilts function.""" await hass.services.async_call( @@ -438,6 +459,7 @@ async def test_stop_tilts(hass, setup_comp): assert hass.states.get(DEMO_COVER_TILT).attributes[ATTR_CURRENT_TILT_POSITION] == 60 +@pytest.mark.parametrize("config_count", [(CONFIG_ALL, 2)]) async def test_set_tilt_positions(hass, setup_comp): """Test set tilt position function.""" await hass.services.async_call( @@ -456,3 +478,40 @@ async def test_set_tilt_positions(hass, setup_comp): assert state.attributes[ATTR_CURRENT_TILT_POSITION] == 80 assert hass.states.get(DEMO_COVER_TILT).attributes[ATTR_CURRENT_TILT_POSITION] == 80 + + +@pytest.mark.parametrize("config_count", [(CONFIG_POS, 2)]) +async def test_is_opening_closing(hass, setup_comp): + """Test is_opening property.""" + await hass.services.async_call( + DOMAIN, SERVICE_OPEN_COVER, {ATTR_ENTITY_ID: COVER_GROUP}, blocking=True + ) + + assert hass.states.get(DEMO_COVER_POS).state == STATE_OPENING + assert hass.states.get(DEMO_COVER_TILT).state == STATE_OPENING + assert hass.states.get(COVER_GROUP).state == STATE_OPENING + + for _ in range(10): + future = dt_util.utcnow() + timedelta(seconds=1) + async_fire_time_changed(hass, future) + await hass.async_block_till_done() + + await hass.services.async_call( + DOMAIN, SERVICE_CLOSE_COVER, {ATTR_ENTITY_ID: COVER_GROUP}, blocking=True + ) + + assert hass.states.get(DEMO_COVER_POS).state == STATE_CLOSING + assert hass.states.get(DEMO_COVER_TILT).state == STATE_CLOSING + assert hass.states.get(COVER_GROUP).state == STATE_CLOSING + + hass.states.async_set(DEMO_COVER_POS, STATE_OPENING, {ATTR_SUPPORTED_FEATURES: 11}) + await hass.async_block_till_done() + + assert hass.states.get(DEMO_COVER_POS).state == STATE_OPENING + assert hass.states.get(COVER_GROUP).state == STATE_OPENING + + hass.states.async_set(DEMO_COVER_POS, STATE_CLOSING, {ATTR_SUPPORTED_FEATURES: 11}) + await hass.async_block_till_done() + + assert hass.states.get(DEMO_COVER_POS).state == STATE_CLOSING + assert hass.states.get(COVER_GROUP).state == STATE_CLOSING From 9871efd52fbd0341d296fd1e2bfba33d7148b575 Mon Sep 17 00:00:00 2001 From: nicx Date: Wed, 3 Jun 2020 18:54:51 +0200 Subject: [PATCH 364/406] Add CalDAV upcoming appointments period option (#34584) Co-authored-by: Martin Hjelmare --- homeassistant/components/caldav/calendar.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/caldav/calendar.py b/homeassistant/components/caldav/calendar.py index 3691f704a13..1f1de734a8f 100644 --- a/homeassistant/components/caldav/calendar.py +++ b/homeassistant/components/caldav/calendar.py @@ -32,6 +32,7 @@ CONF_CALENDARS = "calendars" CONF_CUSTOM_CALENDARS = "custom_calendars" CONF_CALENDAR = "calendar" CONF_SEARCH = "search" +CONF_DAYS = "days" OFFSET = "!!" @@ -55,6 +56,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( ], ), vol.Optional(CONF_VERIFY_SSL, default=True): cv.boolean, + vol.Optional(CONF_DAYS, default=1): cv.positive_int, } ) @@ -66,6 +68,7 @@ def setup_platform(hass, config, add_entities, disc_info=None): url = config[CONF_URL] username = config.get(CONF_USERNAME) password = config.get(CONF_PASSWORD) + days = config[CONF_DAYS] client = caldav.DAVClient( url, None, username, password, ssl_verify_cert=config[CONF_VERIFY_SSL] @@ -92,7 +95,7 @@ def setup_platform(hass, config, add_entities, disc_info=None): entity_id = generate_entity_id(ENTITY_ID_FORMAT, device_id, hass=hass) calendar_devices.append( WebDavCalendarEventDevice( - name, calendar, entity_id, True, cust_calendar[CONF_SEARCH] + name, calendar, entity_id, days, True, cust_calendar[CONF_SEARCH] ) ) @@ -102,7 +105,7 @@ def setup_platform(hass, config, add_entities, disc_info=None): device_id = calendar.name entity_id = generate_entity_id(ENTITY_ID_FORMAT, device_id, hass=hass) calendar_devices.append( - WebDavCalendarEventDevice(name, calendar, entity_id) + WebDavCalendarEventDevice(name, calendar, entity_id, days) ) add_entities(calendar_devices, True) @@ -111,9 +114,9 @@ def setup_platform(hass, config, add_entities, disc_info=None): class WebDavCalendarEventDevice(CalendarEventDevice): """A device for getting the next Task from a WebDav Calendar.""" - def __init__(self, name, calendar, entity_id, all_day=False, search=None): + def __init__(self, name, calendar, entity_id, days, all_day=False, search=None): """Create the WebDav Calendar Event Device.""" - self.data = WebDavCalendarData(calendar, all_day, search) + self.data = WebDavCalendarData(calendar, days, all_day, search) self.entity_id = entity_id self._event = None self._name = name @@ -153,9 +156,10 @@ class WebDavCalendarEventDevice(CalendarEventDevice): class WebDavCalendarData: """Class to utilize the calendar dav client object to get next event.""" - def __init__(self, calendar, include_all_day, search): + def __init__(self, calendar, days, include_all_day, search): """Set up how we are going to search the WebDav calendar.""" self.calendar = calendar + self.days = days self.include_all_day = include_all_day self.search = search self.event = None @@ -192,7 +196,7 @@ class WebDavCalendarData: def update(self): """Get the latest data.""" start_of_today = dt.start_of_local_day() - start_of_tomorrow = dt.start_of_local_day() + timedelta(days=1) + start_of_tomorrow = dt.start_of_local_day() + timedelta(days=self.days) # We have to retrieve the results for the whole day as the server # won't return events that have already started From d155a67687092b51562ee94dabcfb0a8879367a9 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 3 Jun 2020 18:55:41 +0200 Subject: [PATCH 365/406] Update frontend to 20200603.1 (#36409) --- 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 0853f6a2886..269c16ce9a8 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==20200603.0"], + "requirements": ["home-assistant-frontend==20200603.1"], "dependencies": [ "api", "auth", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index eedb6b7fb0a..b7fd9f43d2b 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -12,7 +12,7 @@ cryptography==2.9.2 defusedxml==0.6.0 distro==1.5.0 hass-nabucasa==0.34.4 -home-assistant-frontend==20200603.0 +home-assistant-frontend==20200603.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 dcbdbf8deb7..065b06fda40 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -734,7 +734,7 @@ hole==0.5.1 holidays==0.10.2 # homeassistant.components.frontend -home-assistant-frontend==20200603.0 +home-assistant-frontend==20200603.1 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 04ed4863511..158790eb6d1 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -321,7 +321,7 @@ hole==0.5.1 holidays==0.10.2 # homeassistant.components.frontend -home-assistant-frontend==20200603.0 +home-assistant-frontend==20200603.1 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From eba5b50e5887024b02c432cc74de2ab595b69a77 Mon Sep 17 00:00:00 2001 From: matlimatli Date: Wed, 3 Jun 2020 19:28:02 +0200 Subject: [PATCH 366/406] Add support for showing text on Keba EV chargers (#36056) * Add support for showing text on Keba EV chargers * Changed implementation to use the notify interface * Removed stale references to set_text * Clean up Co-authored-by: Martin Hjelmare --- homeassistant/components/keba/__init__.py | 2 +- homeassistant/components/keba/manifest.json | 2 +- homeassistant/components/keba/notify.py | 33 +++++++++++++++++++++ requirements_all.txt | 2 +- 4 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 homeassistant/components/keba/notify.py diff --git a/homeassistant/components/keba/__init__.py b/homeassistant/components/keba/__init__.py index cbc9428b2ea..764110f94b9 100644 --- a/homeassistant/components/keba/__init__.py +++ b/homeassistant/components/keba/__init__.py @@ -12,7 +12,7 @@ import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) DOMAIN = "keba" -SUPPORTED_COMPONENTS = ["binary_sensor", "sensor", "lock"] +SUPPORTED_COMPONENTS = ["binary_sensor", "sensor", "lock", "notify"] CONF_RFID = "rfid" CONF_FS = "failsafe" diff --git a/homeassistant/components/keba/manifest.json b/homeassistant/components/keba/manifest.json index 0b1b72d99ab..29c4ec86c49 100644 --- a/homeassistant/components/keba/manifest.json +++ b/homeassistant/components/keba/manifest.json @@ -2,6 +2,6 @@ "domain": "keba", "name": "Keba Charging Station", "documentation": "https://www.home-assistant.io/integrations/keba", - "requirements": ["keba-kecontact==1.0.0"], + "requirements": ["keba-kecontact==1.1.0"], "codeowners": ["@dannerph"] } diff --git a/homeassistant/components/keba/notify.py b/homeassistant/components/keba/notify.py new file mode 100644 index 00000000000..7b3f23277a6 --- /dev/null +++ b/homeassistant/components/keba/notify.py @@ -0,0 +1,33 @@ +"""Support for Keba notifications.""" +import logging + +from homeassistant.components.notify import ATTR_DATA, BaseNotificationService + +from . import DOMAIN + +_LOGGER = logging.getLogger(__name__) + + +async def async_get_service(hass, config, discovery_info=None): + """Return the notify service.""" + + client = hass.data[DOMAIN] + return KebaNotificationService(client) + + +class KebaNotificationService(BaseNotificationService): + """Notification service for KEBA EV Chargers.""" + + def __init__(self, client): + """Initialize the service.""" + self._client = client + + async def async_send_message(self, message="", **kwargs): + """Send the message.""" + text = message.replace(" ", "$") # Will be translated back by the display + + data = kwargs[ATTR_DATA] or {} + min_time = float(data.get("min_time", 2)) + max_time = float(data.get("max_time", 10)) + + await self._client.set_text(text, min_time, max_time) diff --git a/requirements_all.txt b/requirements_all.txt index 065b06fda40..1e17ef46fae 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -807,7 +807,7 @@ jsonrpc-websocket==0.6 kaiterra-async-client==0.0.2 # homeassistant.components.keba -keba-kecontact==1.0.0 +keba-kecontact==1.1.0 # homeassistant.scripts.keyring keyring==21.2.0 From dcb04acc6575c18aff654b7ed1e623760ba3a7af Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Wed, 3 Jun 2020 20:08:37 +0200 Subject: [PATCH 367/406] Set automation last_triggered earlier (#35671) Co-authored-by: Paulus Schoutsen --- homeassistant/components/automation/__init__.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 9fabe0e2206..8b2c036034b 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -389,6 +389,8 @@ class AutomationEntity(ToggleEntity, RestoreEntity): trigger_context = Context(parent_id=parent_id) self.async_set_context(trigger_context) + self._last_triggered = utcnow() + self.async_write_ha_state() self.hass.bus.async_fire( EVENT_AUTOMATION_TRIGGERED, {ATTR_NAME: self._name, ATTR_ENTITY_ID: self.entity_id}, @@ -402,9 +404,6 @@ class AutomationEntity(ToggleEntity, RestoreEntity): except Exception: # pylint: disable=broad-except pass - self._last_triggered = utcnow() - self.async_write_ha_state() - async def async_will_remove_from_hass(self): """Remove listeners when removing automation from Home Assistant.""" await super().async_will_remove_from_hass() From ccdf5d13bd66e7752d9edf95cae09cc58b3ad1db Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 3 Jun 2020 21:21:42 +0200 Subject: [PATCH 368/406] Bumped version to 0.111.0b0 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 2837c686c5d..1b8b2b8969c 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 111 -PATCH_VERSION = "0.dev0" +PATCH_VERSION = "0b0" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 0) From 2c49311ee45883942901c106a301509db0b6c81b Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 4 Jun 2020 08:48:39 -0700 Subject: [PATCH 369/406] Guard blowing up converting 0 mired/kelvin (#35486) --- homeassistant/components/hue/light.py | 19 ++++++++++++---- tests/util/test_color.py | 32 ++++++++++----------------- 2 files changed, 27 insertions(+), 24 deletions(-) diff --git a/homeassistant/components/hue/light.py b/homeassistant/components/hue/light.py index c9d543dba94..c8d7de55ce8 100644 --- a/homeassistant/components/hue/light.py +++ b/homeassistant/components/hue/light.py @@ -296,18 +296,29 @@ class HueLight(LightEntity): @property def min_mireds(self): """Return the coldest color_temp that this light supports.""" - if self.is_group or "ct" not in self.light.controlcapabilities: + if self.is_group: return super().min_mireds - return self.light.controlcapabilities["ct"]["min"] + min_mireds = self.light.controlcapabilities.get("ct", {}).get("min") + + # We filter out '0' too, which can be incorrectly reported by 3rd party buls + if not min_mireds: + return super().min_mireds + + return min_mireds @property def max_mireds(self): """Return the warmest color_temp that this light supports.""" - if self.is_group or "ct" not in self.light.controlcapabilities: + if self.is_group: return super().max_mireds - return self.light.controlcapabilities["ct"]["max"] + max_mireds = self.light.controlcapabilities.get("ct", {}).get("max") + + if not max_mireds: + return super().max_mireds + + return max_mireds @property def is_on(self): diff --git a/tests/util/test_color.py b/tests/util/test_color.py index 99ebcd72554..8f520f4a7ec 100644 --- a/tests/util/test_color.py +++ b/tests/util/test_color.py @@ -288,28 +288,20 @@ def test_gamut(): assert not color_util.check_valid_gamut(GAMUT_INVALID_4) -def test_should_return_25000_kelvin_when_input_is_40_mired(): - """Function should return 25000K if given 40 mired.""" - kelvin = color_util.color_temperature_mired_to_kelvin(40) - assert kelvin == 25000 +def test_color_temperature_mired_to_kelvin(): + """Test color_temperature_mired_to_kelvin.""" + assert color_util.color_temperature_mired_to_kelvin(40) == 25000 + assert color_util.color_temperature_mired_to_kelvin(200) == 5000 + with pytest.raises(ZeroDivisionError): + assert color_util.color_temperature_mired_to_kelvin(0) -def test_should_return_5000_kelvin_when_input_is_200_mired(): - """Function should return 5000K if given 200 mired.""" - kelvin = color_util.color_temperature_mired_to_kelvin(200) - assert kelvin == 5000 - - -def test_should_return_40_mired_when_input_is_25000_kelvin(): - """Function should return 40 mired when given 25000 Kelvin.""" - mired = color_util.color_temperature_kelvin_to_mired(25000) - assert mired == 40 - - -def test_should_return_200_mired_when_input_is_5000_kelvin(): - """Function should return 200 mired when given 5000 Kelvin.""" - mired = color_util.color_temperature_kelvin_to_mired(5000) - assert mired == 200 +def test_color_temperature_kelvin_to_mired(): + """Test color_temperature_kelvin_to_mired.""" + assert color_util.color_temperature_kelvin_to_mired(25000) == 40 + assert color_util.color_temperature_kelvin_to_mired(5000) == 200 + with pytest.raises(ZeroDivisionError): + assert color_util.color_temperature_kelvin_to_mired(0) def test_returns_same_value_for_any_two_temperatures_below_1000(): From 3aa4bb54692c06364e6ee74aa5881ec00c714440 Mon Sep 17 00:00:00 2001 From: Chris Talkington Date: Thu, 4 Jun 2020 11:59:39 -0500 Subject: [PATCH 370/406] Add roku exception handling for service calls (#36328) --- homeassistant/components/roku/__init__.py | 18 +++++++++++++++++- homeassistant/components/roku/media_player.py | 14 +++++++++++++- homeassistant/components/roku/remote.py | 5 ++++- 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/roku/__init__.py b/homeassistant/components/roku/__init__.py index ef233f64a1b..a3357ec4cf9 100644 --- a/homeassistant/components/roku/__init__.py +++ b/homeassistant/components/roku/__init__.py @@ -4,7 +4,7 @@ from datetime import timedelta import logging from typing import Any, Dict -from rokuecp import Roku, RokuError +from rokuecp import Roku, RokuConnectionError, RokuError from rokuecp.models import Device import voluptuous as vol @@ -92,6 +92,22 @@ async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry) -> boo return unload_ok +def roku_exception_handler(func): + """Decorate Roku calls to handle Roku exceptions.""" + + async def handler(self, *args, **kwargs): + try: + await func(self, *args, **kwargs) + except RokuConnectionError as error: + if self.available: + _LOGGER.error("Error communicating with API: %s", error) + except RokuError as error: + if self.available: + _LOGGER.error("Invalid response from API: %s", error) + + return handler + + class RokuDataUpdateCoordinator(DataUpdateCoordinator): """Class to manage fetching Roku data.""" diff --git a/homeassistant/components/roku/media_player.py b/homeassistant/components/roku/media_player.py index 69c9e24ad89..168d4a4a6fe 100644 --- a/homeassistant/components/roku/media_player.py +++ b/homeassistant/components/roku/media_player.py @@ -19,7 +19,7 @@ from homeassistant.components.media_player.const import ( ) from homeassistant.const import STATE_HOME, STATE_IDLE, STATE_PLAYING, STATE_STANDBY -from . import RokuDataUpdateCoordinator, RokuEntity +from . import RokuDataUpdateCoordinator, RokuEntity, roku_exception_handler from .const import DOMAIN _LOGGER = logging.getLogger(__name__) @@ -161,49 +161,60 @@ class RokuMediaPlayer(RokuEntity, MediaPlayerEntity): """List of available input sources.""" return ["Home"] + sorted(app.name for app in self.coordinator.data.apps) + @roku_exception_handler async def async_turn_on(self) -> None: """Turn on the Roku.""" await self.coordinator.roku.remote("poweron") + @roku_exception_handler async def async_turn_off(self) -> None: """Turn off the Roku.""" await self.coordinator.roku.remote("poweroff") + @roku_exception_handler async def async_media_pause(self) -> None: """Send pause command.""" if self.state != STATE_STANDBY: await self.coordinator.roku.remote("play") + @roku_exception_handler async def async_media_play(self) -> None: """Send play command.""" if self.state != STATE_STANDBY: await self.coordinator.roku.remote("play") + @roku_exception_handler async def async_media_play_pause(self) -> None: """Send play/pause command.""" if self.state != STATE_STANDBY: await self.coordinator.roku.remote("play") + @roku_exception_handler async def async_media_previous_track(self) -> None: """Send previous track command.""" await self.coordinator.roku.remote("reverse") + @roku_exception_handler async def async_media_next_track(self) -> None: """Send next track command.""" await self.coordinator.roku.remote("forward") + @roku_exception_handler async def async_mute_volume(self, mute) -> None: """Mute the volume.""" await self.coordinator.roku.remote("volume_mute") + @roku_exception_handler async def async_volume_up(self) -> None: """Volume up media player.""" await self.coordinator.roku.remote("volume_up") + @roku_exception_handler async def async_volume_down(self) -> None: """Volume down media player.""" await self.coordinator.roku.remote("volume_down") + @roku_exception_handler async def async_play_media(self, media_type: str, media_id: str, **kwargs) -> None: """Tune to channel.""" if media_type != MEDIA_TYPE_CHANNEL: @@ -216,6 +227,7 @@ class RokuMediaPlayer(RokuEntity, MediaPlayerEntity): await self.coordinator.roku.tune(media_id) + @roku_exception_handler async def async_select_source(self, source: str) -> None: """Select input source.""" if source == "Home": diff --git a/homeassistant/components/roku/remote.py b/homeassistant/components/roku/remote.py index 99e398fea68..5b893b6a0f8 100644 --- a/homeassistant/components/roku/remote.py +++ b/homeassistant/components/roku/remote.py @@ -5,7 +5,7 @@ from homeassistant.components.remote import ATTR_NUM_REPEATS, RemoteEntity from homeassistant.config_entries import ConfigEntry from homeassistant.helpers.typing import HomeAssistantType -from . import RokuDataUpdateCoordinator, RokuEntity +from . import RokuDataUpdateCoordinator, RokuEntity, roku_exception_handler from .const import DOMAIN @@ -43,14 +43,17 @@ class RokuRemote(RokuEntity, RemoteEntity): """Return true if device is on.""" return not self.coordinator.data.state.standby + @roku_exception_handler async def async_turn_on(self, **kwargs) -> None: """Turn the device on.""" await self.coordinator.roku.remote("poweron") + @roku_exception_handler async def async_turn_off(self, **kwargs) -> None: """Turn the device off.""" await self.coordinator.roku.remote("poweroff") + @roku_exception_handler async def async_send_command(self, command: List, **kwargs) -> None: """Send a command to one device.""" num_repeats = kwargs[ATTR_NUM_REPEATS] From 4cd04e3f99aa4aa4f773ee4a3da129710ae54a42 Mon Sep 17 00:00:00 2001 From: Chris Talkington Date: Thu, 4 Jun 2020 02:39:49 -0500 Subject: [PATCH 371/406] Update sonarr to 0.2.2 (#36429) --- homeassistant/components/sonarr/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/sonarr/manifest.json b/homeassistant/components/sonarr/manifest.json index 61c30102e34..c1edb8ec521 100644 --- a/homeassistant/components/sonarr/manifest.json +++ b/homeassistant/components/sonarr/manifest.json @@ -3,7 +3,7 @@ "name": "Sonarr", "documentation": "https://www.home-assistant.io/integrations/sonarr", "codeowners": ["@ctalkington"], - "requirements": ["sonarr==0.2.1"], + "requirements": ["sonarr==0.2.2"], "config_flow": true, "quality_scale": "silver" } diff --git a/requirements_all.txt b/requirements_all.txt index 1e17ef46fae..4336412fa41 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1990,7 +1990,7 @@ somecomfort==0.5.2 somfy-mylink-synergy==1.0.6 # homeassistant.components.sonarr -sonarr==0.2.1 +sonarr==0.2.2 # homeassistant.components.marytts speak2mary==1.4.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 158790eb6d1..a6b782fc400 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -813,7 +813,7 @@ solaredge==0.0.2 somecomfort==0.5.2 # homeassistant.components.sonarr -sonarr==0.2.1 +sonarr==0.2.2 # homeassistant.components.marytts speak2mary==1.4.0 From 84bd2067e4874718c4c9ee6a8d593960aecb2c5c Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Thu, 4 Jun 2020 10:15:30 +0200 Subject: [PATCH 372/406] Fix deCONZ groups don't report ctmax/min (#36432) * Groups don't report ctmax/min --- homeassistant/components/deconz/light.py | 28 ++++++++++++++---------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/deconz/light.py b/homeassistant/components/deconz/light.py index 513be407907..fc0c01b30df 100644 --- a/homeassistant/components/deconz/light.py +++ b/homeassistant/components/deconz/light.py @@ -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, LightEntity): +class DeconzBaseLight(DeconzDevice, LightEntity): """Representation of a deCONZ light.""" def __init__(self, device, gateway): @@ -130,16 +130,6 @@ class DeconzLight(DeconzDevice, LightEntity): return color_util.color_xy_to_hs(*self._device.xy) return None - @property - def max_mireds(self): - """Return the warmest color_temp that this light supports.""" - return self._device.ctmax or super().max_mireds - - @property - def min_mireds(self): - """Return the coldest color_temp that this light supports.""" - return self._device.ctmin or super().min_mireds - @property def is_on(self): """Return true if light is on.""" @@ -214,7 +204,21 @@ class DeconzLight(DeconzDevice, LightEntity): return attributes -class DeconzGroup(DeconzLight): +class DeconzLight(DeconzBaseLight): + """Representation of a deCONZ light.""" + + @property + def max_mireds(self): + """Return the warmest color_temp that this light supports.""" + return self._device.ctmax or super().max_mireds + + @property + def min_mireds(self): + """Return the coldest color_temp that this light supports.""" + return self._device.ctmin or super().min_mireds + + +class DeconzGroup(DeconzBaseLight): """Representation of a deCONZ group.""" def __init__(self, device, gateway): From c7da4d77ce4dfe080e3380f8b4d6fcfc823bd317 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 4 Jun 2020 01:13:01 -0700 Subject: [PATCH 373/406] Add partial mobile app sensor validation (#36433) --- homeassistant/components/mobile_app/const.py | 1 + .../components/mobile_app/webhook.py | 33 ++++++++++++++++--- tests/components/mobile_app/test_entity.py | 7 +++- 3 files changed, 36 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/mobile_app/const.py b/homeassistant/components/mobile_app/const.py index a9cdc676932..0fc4a5ee407 100644 --- a/homeassistant/components/mobile_app/const.py +++ b/homeassistant/components/mobile_app/const.py @@ -57,6 +57,7 @@ ERR_ENCRYPTION_NOT_AVAILABLE = "encryption_not_available" ERR_ENCRYPTION_REQUIRED = "encryption_required" ERR_SENSOR_NOT_REGISTERED = "not_registered" ERR_SENSOR_DUPLICATE_UNIQUE_ID = "duplicate_unique_id" +ERR_INVALID_FORMAT = "invalid_format" ATTR_SENSOR_ATTRIBUTES = "attributes" diff --git a/homeassistant/components/mobile_app/webhook.py b/homeassistant/components/mobile_app/webhook.py index adc90c15e98..09618390e18 100644 --- a/homeassistant/components/mobile_app/webhook.py +++ b/homeassistant/components/mobile_app/webhook.py @@ -76,6 +76,7 @@ from .const import ( ERR_ENCRYPTION_ALREADY_ENABLED, ERR_ENCRYPTION_NOT_AVAILABLE, ERR_ENCRYPTION_REQUIRED, + ERR_INVALID_FORMAT, ERR_SENSOR_DUPLICATE_UNIQUE_ID, ERR_SENSOR_NOT_REGISTERED, SIGNAL_LOCATION_UPDATE, @@ -394,20 +395,31 @@ async def webhook_register_sensor(hass, config_entry, data): vol.All( cv.ensure_list, [ + # Partial schema, enough to identify schema. + # We don't validate everything because otherwise 1 invalid sensor + # will invalidate all sensors. vol.Schema( { - vol.Optional(ATTR_SENSOR_ATTRIBUTES, default={}): dict, - vol.Optional(ATTR_SENSOR_ICON, default="mdi:cellphone"): cv.icon, - vol.Required(ATTR_SENSOR_STATE): vol.Any(bool, str, int, float), vol.Required(ATTR_SENSOR_TYPE): vol.In(SENSOR_TYPES), vol.Required(ATTR_SENSOR_UNIQUE_ID): cv.string, - } + }, + extra=vol.ALLOW_EXTRA, ) ], ) ) async def webhook_update_sensor_states(hass, config_entry, data): """Handle an update sensor states webhook.""" + sensor_schema_full = vol.Schema( + { + vol.Optional(ATTR_SENSOR_ATTRIBUTES, default={}): dict, + vol.Optional(ATTR_SENSOR_ICON, default="mdi:cellphone"): cv.icon, + vol.Required(ATTR_SENSOR_STATE): vol.Any(bool, str, int, float), + vol.Required(ATTR_SENSOR_TYPE): vol.In(SENSOR_TYPES), + vol.Required(ATTR_SENSOR_UNIQUE_ID): cv.string, + } + ) + resp = {} for sensor in data: entity_type = sensor[ATTR_SENSOR_TYPE] @@ -429,6 +441,19 @@ async def webhook_update_sensor_states(hass, config_entry, data): entry = hass.data[DOMAIN][entity_type][unique_store_key] + try: + sensor = sensor_schema_full(sensor) + except vol.Invalid as err: + err_msg = vol.humanize.humanize_error(sensor, err) + _LOGGER.error( + "Received invalid sensor payload for %s: %s", unique_id, err_msg + ) + resp[unique_id] = { + "success": False, + "error": {"code": ERR_INVALID_FORMAT, "message": err_msg}, + } + continue + new_state = {**entry, **sensor} hass.data[DOMAIN][entity_type][unique_store_key] = new_state diff --git a/tests/components/mobile_app/test_entity.py b/tests/components/mobile_app/test_entity.py index 78259cd1145..16b21e7b264 100644 --- a/tests/components/mobile_app/test_entity.py +++ b/tests/components/mobile_app/test_entity.py @@ -57,13 +57,18 @@ async def test_sensor(hass, create_registrations, webhook_client): "state": 123, "type": "sensor", "unique_id": "battery_state", - } + }, + # This invalid data should not invalidate whole request + {"type": "sensor", "unique_id": "invalid_state", "invalid": "data"}, ], }, ) assert update_resp.status == 200 + json = await update_resp.json() + assert json["invalid_state"]["success"] is False + updated_entity = hass.states.get("sensor.test_1_battery_state") assert updated_entity.state == "123" From 1b5a601417a93273875988f8be12fa477f67b974 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 4 Jun 2020 11:51:06 -0500 Subject: [PATCH 374/406] Ensure verbose logging flag is respected. (#36444) --- homeassistant/bootstrap.py | 2 +- homeassistant/util/logging.py | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index a4c5fa14fa8..086416b7d35 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -288,7 +288,7 @@ def async_enable_logging( logger = logging.getLogger("") logger.addHandler(err_handler) - logger.setLevel(logging.INFO) + logger.setLevel(logging.INFO if verbose else logging.WARNING) # Save the log file location for access by other components. hass.data[DATA_LOGGING] = err_log_path diff --git a/homeassistant/util/logging.py b/homeassistant/util/logging.py index 943e701a144..ed710f573f4 100644 --- a/homeassistant/util/logging.py +++ b/homeassistant/util/logging.py @@ -77,9 +77,7 @@ def async_activate_log_queue_handler(hass: HomeAssistant) -> None: logging.root.removeHandler(handler) migrated_handlers.append(handler) - listener = logging.handlers.QueueListener( - simple_queue, *migrated_handlers, respect_handler_level=False - ) + listener = logging.handlers.QueueListener(simple_queue, *migrated_handlers) listener.start() From 36207e56b977c954dc8fc29ffb5c45152eb7e8b7 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 4 Jun 2020 10:00:31 -0700 Subject: [PATCH 375/406] Fix invalid device info for Daikin devices (#36448) --- homeassistant/components/daikin/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/homeassistant/components/daikin/__init__.py b/homeassistant/components/daikin/__init__.py index 2bd47651172..35ea9ff6f35 100644 --- a/homeassistant/components/daikin/__init__.py +++ b/homeassistant/components/daikin/__init__.py @@ -158,7 +158,6 @@ class DaikinApi: info = self.device.values return { "connections": {(CONNECTION_NETWORK_MAC, self.device.mac)}, - "identifiers": self.device.mac, "manufacturer": "Daikin", "model": info.get("model"), "name": info.get("name"), From 0840092b29a76ed5af7ebb1ee3c2a062cb9a46a7 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 4 Jun 2020 10:02:49 -0700 Subject: [PATCH 376/406] Bumped version to 0.111.0b1 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 1b8b2b8969c..4fa16578d32 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 111 -PATCH_VERSION = "0b0" +PATCH_VERSION = "0b1" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 0) From ca511cb06cc97deb05ea4a316e07000e935820f0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 5 Jun 2020 16:33:26 -0500 Subject: [PATCH 377/406] Upgrade zeroconf to 0.27.1 (#36277) --- homeassistant/components/discovery/manifest.json | 1 + homeassistant/components/dyson/manifest.json | 1 + homeassistant/components/soundtouch/manifest.json | 1 + homeassistant/components/ssdp/manifest.json | 1 + homeassistant/components/zeroconf/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/zeroconf/test_init.py | 4 ++-- tests/test_requirements.py | 4 +++- 10 files changed, 13 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/discovery/manifest.json b/homeassistant/components/discovery/manifest.json index 76e4ff701c5..b2065edfc54 100644 --- a/homeassistant/components/discovery/manifest.json +++ b/homeassistant/components/discovery/manifest.json @@ -3,6 +3,7 @@ "name": "Discovery", "documentation": "https://www.home-assistant.io/integrations/discovery", "requirements": ["netdisco==2.6.0"], + "after_dependencies": ["zeroconf"], "codeowners": [], "quality_scale": "internal" } diff --git a/homeassistant/components/dyson/manifest.json b/homeassistant/components/dyson/manifest.json index 60800963842..35a76180e2e 100644 --- a/homeassistant/components/dyson/manifest.json +++ b/homeassistant/components/dyson/manifest.json @@ -3,5 +3,6 @@ "name": "Dyson", "documentation": "https://www.home-assistant.io/integrations/dyson", "requirements": ["libpurecool==0.6.1"], + "after_dependencies": ["zeroconf"], "codeowners": ["@etheralm"] } diff --git a/homeassistant/components/soundtouch/manifest.json b/homeassistant/components/soundtouch/manifest.json index c9cc4f32734..58bdab1a2d7 100644 --- a/homeassistant/components/soundtouch/manifest.json +++ b/homeassistant/components/soundtouch/manifest.json @@ -3,5 +3,6 @@ "name": "Bose Soundtouch", "documentation": "https://www.home-assistant.io/integrations/soundtouch", "requirements": ["libsoundtouch==0.8"], + "after_dependencies": ["zeroconf"], "codeowners": [] } diff --git a/homeassistant/components/ssdp/manifest.json b/homeassistant/components/ssdp/manifest.json index a2683346b63..85b91ff005c 100644 --- a/homeassistant/components/ssdp/manifest.json +++ b/homeassistant/components/ssdp/manifest.json @@ -3,5 +3,6 @@ "name": "Simple Service Discovery Protocol (SSDP)", "documentation": "https://www.home-assistant.io/integrations/ssdp", "requirements": ["defusedxml==0.6.0", "netdisco==2.6.0"], + "after_dependencies": ["zeroconf"], "codeowners": [] } diff --git a/homeassistant/components/zeroconf/manifest.json b/homeassistant/components/zeroconf/manifest.json index e28594d5598..c5e0efe1fe3 100644 --- a/homeassistant/components/zeroconf/manifest.json +++ b/homeassistant/components/zeroconf/manifest.json @@ -2,7 +2,7 @@ "domain": "zeroconf", "name": "Zero-configuration networking (zeroconf)", "documentation": "https://www.home-assistant.io/integrations/zeroconf", - "requirements": ["zeroconf==0.26.3"], + "requirements": ["zeroconf==0.27.1"], "dependencies": ["api"], "codeowners": ["@robbiet480", "@Kane610"], "quality_scale": "internal" diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index b7fd9f43d2b..d78680c6131 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -25,7 +25,7 @@ ruamel.yaml==0.15.100 sqlalchemy==1.3.17 voluptuous-serialize==2.3.0 voluptuous==0.11.7 -zeroconf==0.26.3 +zeroconf==0.27.1 pycryptodome>=3.6.6 diff --git a/requirements_all.txt b/requirements_all.txt index 4336412fa41..421f3703e35 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2239,7 +2239,7 @@ youtube_dl==2020.05.29 zengge==0.2 # homeassistant.components.zeroconf -zeroconf==0.26.3 +zeroconf==0.27.1 # homeassistant.components.zha zha-quirks==0.0.39 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a6b782fc400..4ff9eb99931 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -915,7 +915,7 @@ xmltodict==0.12.0 ya_ma==0.3.8 # homeassistant.components.zeroconf -zeroconf==0.26.3 +zeroconf==0.27.1 # homeassistant.components.zha zha-quirks==0.0.39 diff --git a/tests/components/zeroconf/test_init.py b/tests/components/zeroconf/test_init.py index 74069fa5faa..45b1d9b1171 100644 --- a/tests/components/zeroconf/test_init.py +++ b/tests/components/zeroconf/test_init.py @@ -40,7 +40,7 @@ def get_service_info_mock(service_type, name): return ServiceInfo( service_type, name, - address=b"\n\x00\x00\x14", + addresses=[b"\n\x00\x00\x14"], port=80, weight=0, priority=0, @@ -56,7 +56,7 @@ def get_homekit_info_mock(model, pairing_status): return ServiceInfo( service_type, name, - address=b"\n\x00\x00\x14", + addresses=[b"\n\x00\x00\x14"], port=80, weight=0, priority=0, diff --git a/tests/test_requirements.py b/tests/test_requirements.py index f98485e8006..20202f91e89 100644 --- a/tests/test_requirements.py +++ b/tests/test_requirements.py @@ -221,8 +221,10 @@ async def test_discovery_requirements_ssdp(hass): ) as mock_process: await async_get_integration_with_requirements(hass, "ssdp_comp") - assert len(mock_process.mock_calls) == 1 + assert len(mock_process.mock_calls) == 3 assert mock_process.mock_calls[0][1][2] == ssdp.requirements + # Ensure zeroconf is a dep for ssdp + assert mock_process.mock_calls[1][1][1] == "zeroconf" @pytest.mark.parametrize( From e053f75a08955eda8d2b02da49e32ba8efe71b89 Mon Sep 17 00:00:00 2001 From: shbatm Date: Thu, 4 Jun 2020 17:22:35 -0500 Subject: [PATCH 378/406] Fix error on empty UOM for ISY994 Climate Device (#36454) --- homeassistant/components/isy994/climate.py | 2 +- homeassistant/components/isy994/cover.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/isy994/climate.py b/homeassistant/components/isy994/climate.py index 8299265a381..7dfd9a083d3 100644 --- a/homeassistant/components/isy994/climate.py +++ b/homeassistant/components/isy994/climate.py @@ -133,7 +133,7 @@ class ISYThermostatEntity(ISYNodeEntity, ClimateEntity): # Which state values used depends on the mode property's UOM: uom = hvac_mode.uom # Handle special case for ISYv4 Firmware: - if uom == UOM_ISYV4_NONE: + if uom in (UOM_ISYV4_NONE, ""): uom = ( UOM_HVAC_MODE_INSTEON if self._node.protocol == PROTO_INSTEON diff --git a/homeassistant/components/isy994/cover.py b/homeassistant/components/isy994/cover.py index bbcc6f3bf15..41273f61f01 100644 --- a/homeassistant/components/isy994/cover.py +++ b/homeassistant/components/isy994/cover.py @@ -83,8 +83,8 @@ class ISYCoverEntity(ISYNodeEntity, CoverEntity): def set_cover_position(self, **kwargs): """Move the cover to a specific position.""" - position = kwargs.get(ATTR_POSITION) - if position and self._node.uom == UOM_8_BIT_RANGE: + position = kwargs[ATTR_POSITION] + if self._node.uom == UOM_8_BIT_RANGE: position = int(position * 255 / 100) if not self._node.turn_on(val=position): _LOGGER.error("Unable to set cover position") From 1831b3bfb7f14d2394d9fe25f82e39cbca35c35b Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 5 Jun 2020 00:11:40 -0700 Subject: [PATCH 379/406] Bump hass-nabucasa to 0.34.5 (#36461) --- 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 33a13b9462d..5fb12bbb102 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.4"], + "requirements": ["hass-nabucasa==0.34.5"], "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 d78680c6131..479d60bc47b 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -11,7 +11,7 @@ ciso8601==2.1.3 cryptography==2.9.2 defusedxml==0.6.0 distro==1.5.0 -hass-nabucasa==0.34.4 +hass-nabucasa==0.34.5 home-assistant-frontend==20200603.1 importlib-metadata==1.6.0 jinja2>=2.11.1 diff --git a/requirements_all.txt b/requirements_all.txt index 421f3703e35..03d4c63bd88 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -704,7 +704,7 @@ habitipy==0.2.0 hangups==0.4.9 # homeassistant.components.cloud -hass-nabucasa==0.34.4 +hass-nabucasa==0.34.5 # homeassistant.components.mqtt hbmqtt==0.9.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 4ff9eb99931..cd0251f5e73 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -303,7 +303,7 @@ ha-ffmpeg==2.0 hangups==0.4.9 # homeassistant.components.cloud -hass-nabucasa==0.34.4 +hass-nabucasa==0.34.5 # homeassistant.components.mqtt hbmqtt==0.9.5 From 1646113b69d07ed95424a3d87398639821c5de73 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 5 Jun 2020 12:29:09 -0500 Subject: [PATCH 380/406] Update myq for latest api changes (#36469) --- homeassistant/components/myq/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/myq/manifest.json b/homeassistant/components/myq/manifest.json index 953f7a31097..8a68eae03ea 100644 --- a/homeassistant/components/myq/manifest.json +++ b/homeassistant/components/myq/manifest.json @@ -2,7 +2,7 @@ "domain": "myq", "name": "MyQ", "documentation": "https://www.home-assistant.io/integrations/myq", - "requirements": ["pymyq==2.0.2"], + "requirements": ["pymyq==2.0.3"], "codeowners": ["@bdraco"], "config_flow": true, "homekit": { diff --git a/requirements_all.txt b/requirements_all.txt index 03d4c63bd88..99459e56d91 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1468,7 +1468,7 @@ pymsteams==0.1.12 pymusiccast==0.1.6 # homeassistant.components.myq -pymyq==2.0.2 +pymyq==2.0.3 # homeassistant.components.mysensors pymysensors==0.18.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index cd0251f5e73..64d6b9522a8 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -639,7 +639,7 @@ pymodbus==2.3.0 pymonoprice==0.3 # homeassistant.components.myq -pymyq==2.0.2 +pymyq==2.0.3 # homeassistant.components.nut pynut2==2.1.2 From 33ec31409157c968733d1e7ebf8bc0ae10076799 Mon Sep 17 00:00:00 2001 From: Lindsay Ward Date: Fri, 5 Jun 2020 16:40:50 +1000 Subject: [PATCH 381/406] Fix yeelight_sunflower hs_color using RGB values (#36470) --- homeassistant/components/yeelightsunflower/light.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/yeelightsunflower/light.py b/homeassistant/components/yeelightsunflower/light.py index abe3d5a404b..00ea467c0d1 100644 --- a/homeassistant/components/yeelightsunflower/light.py +++ b/homeassistant/components/yeelightsunflower/light.py @@ -44,7 +44,7 @@ class SunflowerBulb(LightEntity): self._available = light.available self._brightness = light.brightness self._is_on = light.is_on - self._hs_color = light.rgb_color + self._rgb_color = light.rgb_color self._unique_id = light.zid @property @@ -75,7 +75,7 @@ class SunflowerBulb(LightEntity): @property def hs_color(self): """Return the color property.""" - return self._hs_color + return color_util.color_RGB_to_hs(*self._rgb_color) @property def supported_features(self): @@ -109,4 +109,4 @@ class SunflowerBulb(LightEntity): self._available = self._light.available self._brightness = self._light.brightness self._is_on = self._light.is_on - self._hs_color = color_util.color_RGB_to_hs(*self._light.rgb_color) + self._rgb_color = self._light.rgb_color From d1c083eed9db963e1e97bc8f341bbbe4b197624a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 5 Jun 2020 12:30:20 -0500 Subject: [PATCH 382/406] Upgrade pysonos to 0.0.31 (#36483) --- homeassistant/components/sonos/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/sonos/manifest.json b/homeassistant/components/sonos/manifest.json index 020bfbade56..7ce4af02e45 100644 --- a/homeassistant/components/sonos/manifest.json +++ b/homeassistant/components/sonos/manifest.json @@ -3,7 +3,7 @@ "name": "Sonos", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/sonos", - "requirements": ["pysonos==0.0.30"], + "requirements": ["pysonos==0.0.31"], "ssdp": [ { "st": "urn:schemas-upnp-org:device:ZonePlayer:1" diff --git a/requirements_all.txt b/requirements_all.txt index 99459e56d91..63ff5e9acaf 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1615,7 +1615,7 @@ pysnmp==4.4.12 pysoma==0.0.10 # homeassistant.components.sonos -pysonos==0.0.30 +pysonos==0.0.31 # homeassistant.components.spc pyspcwebgw==0.4.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 64d6b9522a8..e49ff4c82ad 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -693,7 +693,7 @@ pysmartthings==0.7.1 pysoma==0.0.10 # homeassistant.components.sonos -pysonos==0.0.30 +pysonos==0.0.31 # homeassistant.components.spc pyspcwebgw==0.4.0 From a1b2dcc5a884383f8fc38128c88bee1014f30c2e Mon Sep 17 00:00:00 2001 From: jrester <31157644+jrester@users.noreply.github.com> Date: Fri, 5 Jun 2020 22:03:17 +0200 Subject: [PATCH 383/406] Update tesla-powerwall to 0.2.10 (#36486) modified: homeassistant/components/powerwall/manifest.json modified: requirements_all.txt modified: requirements_test_all.txt --- homeassistant/components/powerwall/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/powerwall/manifest.json b/homeassistant/components/powerwall/manifest.json index 7b2095c4a2a..da5f6e4b7ed 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.8"], + "requirements": ["tesla-powerwall==0.2.10"], "codeowners": ["@bdraco", "@jrester"] } diff --git a/requirements_all.txt b/requirements_all.txt index 63ff5e9acaf..99cd6880fdf 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2078,7 +2078,7 @@ temperusb==1.5.3 # tensorflow==1.13.2 # homeassistant.components.powerwall -tesla-powerwall==0.2.8 +tesla-powerwall==0.2.10 # homeassistant.components.tesla teslajsonpy==0.8.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e49ff4c82ad..9b520efc75b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -844,7 +844,7 @@ sunwatcher==0.2.1 tellduslive==0.10.11 # homeassistant.components.powerwall -tesla-powerwall==0.2.8 +tesla-powerwall==0.2.10 # homeassistant.components.tesla teslajsonpy==0.8.1 From 85ee0a1a3ae44f1823874f879e31828a227f7ee4 Mon Sep 17 00:00:00 2001 From: Alexei Chetroi Date: Fri, 5 Jun 2020 17:11:46 -0400 Subject: [PATCH 384/406] Process events from ZHA Window Covering Remote (#36489) --- .../components/zha/core/channels/closures.py | 7 ++- tests/components/zha/test_cover.py | 52 ++++++++++++++++++- tests/components/zha/zha_devices_list.py | 2 +- 3 files changed, 57 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/zha/core/channels/closures.py b/homeassistant/components/zha/core/channels/closures.py index 826c99fbd3b..5641deffb60 100644 --- a/homeassistant/components/zha/core/channels/closures.py +++ b/homeassistant/components/zha/core/channels/closures.py @@ -7,7 +7,7 @@ from homeassistant.core import callback from .. import registries from ..const import REPORT_CONFIG_IMMEDIATE, SIGNAL_ATTR_UPDATED -from .base import ZigbeeChannel +from .base import ClientChannel, ZigbeeChannel _LOGGER = logging.getLogger(__name__) @@ -50,6 +50,11 @@ class Shade(ZigbeeChannel): """Shade channel.""" +@registries.CLIENT_CHANNELS_REGISTRY.register(closures.WindowCovering.cluster_id) +class WindowCoveringClient(ClientChannel): + """Window client channel.""" + + @registries.ZIGBEE_CHANNEL_REGISTRY.register(closures.WindowCovering.cluster_id) class WindowCovering(ZigbeeChannel): """Window channel.""" diff --git a/tests/components/zha/test_cover.py b/tests/components/zha/test_cover.py index c3404a2bb83..2c497f6880f 100644 --- a/tests/components/zha/test_cover.py +++ b/tests/components/zha/test_cover.py @@ -15,18 +15,24 @@ from homeassistant.components.cover import ( SERVICE_SET_COVER_POSITION, SERVICE_STOP_COVER, ) -from homeassistant.const import STATE_CLOSED, STATE_OPEN, STATE_UNAVAILABLE +from homeassistant.const import ( + ATTR_COMMAND, + STATE_CLOSED, + STATE_OPEN, + STATE_UNAVAILABLE, +) from homeassistant.core import CoreState, State from .common import ( async_enable_traffic, async_test_rejoin, find_entity_id, + make_zcl_header, send_attributes_report, ) from tests.async_mock import AsyncMock, MagicMock, call, patch -from tests.common import mock_coro, mock_restore_cache +from tests.common import async_capture_events, mock_coro, mock_restore_cache @pytest.fixture @@ -43,6 +49,20 @@ def zigpy_cover_device(zigpy_device_mock): return zigpy_device_mock(endpoints) +@pytest.fixture +def zigpy_cover_remote(zigpy_device_mock): + """Zigpy cover remote device.""" + + endpoints = { + 1: { + "device_type": 0x0203, + "in_clusters": [], + "out_clusters": [closures.WindowCovering.cluster_id], + } + } + return zigpy_device_mock(endpoints) + + @pytest.fixture def zigpy_shade_device(zigpy_device_mock): """Zigpy shade device.""" @@ -375,3 +395,31 @@ async def test_keen_vent(hass, zha_device_joined_restored, zigpy_keen_vent): assert cluster_level.request.call_count == 1 assert hass.states.get(entity_id).state == STATE_OPEN assert hass.states.get(entity_id).attributes[ATTR_CURRENT_POSITION] == 100 + + +async def test_cover_remote(hass, zha_device_joined_restored, zigpy_cover_remote): + """Test zha cover remote.""" + + # load up cover domain + await zha_device_joined_restored(zigpy_cover_remote) + + cluster = zigpy_cover_remote.endpoints[1].out_clusters[ + closures.WindowCovering.cluster_id + ] + zha_events = async_capture_events(hass, "zha_event") + + # up command + hdr = make_zcl_header(0, global_command=False) + cluster.handle_message(hdr, []) + await hass.async_block_till_done() + + assert len(zha_events) == 1 + assert zha_events[0].data[ATTR_COMMAND] == "up_open" + + # down command + hdr = make_zcl_header(1, global_command=False) + cluster.handle_message(hdr, []) + await hass.async_block_till_done() + + assert len(zha_events) == 2 + assert zha_events[1].data[ATTR_COMMAND] == "down_close" diff --git a/tests/components/zha/zha_devices_list.py b/tests/components/zha/zha_devices_list.py index 0b1ec9ae2c6..d4ea1377d97 100644 --- a/tests/components/zha/zha_devices_list.py +++ b/tests/components/zha/zha_devices_list.py @@ -813,7 +813,7 @@ DEVICES = [ "entity_id": "sensor.ikea_of_sweden_tradfri_on_off_switch_77665544_power", } }, - "event_channels": ["1:0x0006", "1:0x0008", "1:0x0019"], + "event_channels": ["1:0x0006", "1:0x0008", "1:0x0019", "1:0x0102"], "manufacturer": "IKEA of Sweden", "model": "TRADFRI on/off switch", "node_descriptor": b"\x02@\x80|\x11RR\x00\x00,R\x00\x00", From 7520aa5b2594d69c2f04a0c5a21bd0fac83c50c8 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 5 Jun 2020 21:43:58 +0200 Subject: [PATCH 385/406] Fix iOS app crashing on None values in Zeroconf service info (#36490) --- homeassistant/components/zeroconf/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/zeroconf/__init__.py b/homeassistant/components/zeroconf/__init__.py index 436b38fd704..9f0d203d2b3 100644 --- a/homeassistant/components/zeroconf/__init__.py +++ b/homeassistant/components/zeroconf/__init__.py @@ -130,10 +130,10 @@ def setup(hass, config): "location_name": hass.config.location_name, "uuid": uuid, "version": __version__, - "external_url": None, - "internal_url": None, + "external_url": "", + "internal_url": "", # Old base URL, for backward compatibility - "base_url": None, + "base_url": "", # Always needs authentication "requires_api_password": True, } From 401b0dce68e6b1bd4ef1fe0da549cafc958b754d Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 5 Jun 2020 14:34:22 -0700 Subject: [PATCH 386/406] Bumped version to 0.111.0b2 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 4fa16578d32..756567e9745 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 111 -PATCH_VERSION = "0b1" +PATCH_VERSION = "0b2" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 0) From 4b1761ccb5a5e03f47064becd9e19514f5fdceb1 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 5 Jun 2020 01:59:55 -0700 Subject: [PATCH 387/406] Use builtin mock (#36473) --- tests/components/gogogate2/conftest.py | 3 ++- tests/components/marytts/test_tts.py | 3 +-- tests/components/seventeentrack/test_sensor.py | 12 ++++++------ tests/components/vera/common.py | 2 +- tests/components/vera/conftest.py | 3 ++- tests/components/vera/test_config_flow.py | 3 +-- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/components/gogogate2/conftest.py b/tests/components/gogogate2/conftest.py index 6e2e58d8f9c..31e85c5f14e 100644 --- a/tests/components/gogogate2/conftest.py +++ b/tests/components/gogogate2/conftest.py @@ -1,12 +1,13 @@ """Fixtures for tests.""" -from mock import patch import pytest from homeassistant.core import HomeAssistant from .common import ComponentFactory +from tests.async_mock import patch + @pytest.fixture() def component_factory(hass: HomeAssistant): diff --git a/tests/components/marytts/test_tts.py b/tests/components/marytts/test_tts.py index 637ed1900b8..da221a7effd 100644 --- a/tests/components/marytts/test_tts.py +++ b/tests/components/marytts/test_tts.py @@ -4,8 +4,6 @@ import os import shutil from urllib.parse import urlencode -from mock import Mock, patch - from homeassistant.components.media_player.const import ( ATTR_MEDIA_CONTENT_ID, DOMAIN as DOMAIN_MP, @@ -16,6 +14,7 @@ from homeassistant.config import async_process_ha_core_config from homeassistant.const import HTTP_INTERNAL_SERVER_ERROR from homeassistant.setup import setup_component +from tests.async_mock import Mock, patch from tests.common import assert_setup_component, get_test_home_assistant, mock_service diff --git a/tests/components/seventeentrack/test_sensor.py b/tests/components/seventeentrack/test_sensor.py index 272f2fe7d76..330f4a66152 100644 --- a/tests/components/seventeentrack/test_sensor.py +++ b/tests/components/seventeentrack/test_sensor.py @@ -2,7 +2,6 @@ import datetime from typing import Union -import mock from py17track.package import Package import pytest @@ -14,6 +13,7 @@ from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.setup import async_setup_component from homeassistant.util import utcnow +from tests.async_mock import MagicMock, patch from tests.common import async_fire_time_changed VALID_CONFIG_MINIMAL = { @@ -113,7 +113,7 @@ class ProfileMock: @pytest.fixture(autouse=True, name="mock_client") def fixture_mock_client(): """Mock py17track client.""" - with mock.patch( + with patch( "homeassistant.components.seventeentrack.sensor.SeventeenTrackClient", new=ClientMock, ): @@ -137,7 +137,7 @@ async def _goto_future(hass, future=None): """Move to future.""" if not future: future = utcnow() + datetime.timedelta(minutes=10) - with mock.patch("homeassistant.util.utcnow", return_value=future): + with patch("homeassistant.util.utcnow", return_value=future): async_fire_time_changed(hass, future) await hass.async_block_till_done() @@ -245,7 +245,7 @@ async def test_delivered_not_shown(hass): ) ProfileMock.package_list = [package] - hass.components.persistent_notification = mock.MagicMock() + hass.components.persistent_notification = MagicMock() await _setup_seventeentrack(hass, VALID_CONFIG_FULL_NO_DELIVERED) assert not hass.states.async_entity_ids() hass.components.persistent_notification.create.assert_called() @@ -258,7 +258,7 @@ async def test_delivered_shown(hass): ) ProfileMock.package_list = [package] - hass.components.persistent_notification = mock.MagicMock() + hass.components.persistent_notification = MagicMock() await _setup_seventeentrack(hass, VALID_CONFIG_FULL) assert hass.states.get("sensor.seventeentrack_package_456") is not None @@ -283,7 +283,7 @@ async def test_becomes_delivered_not_shown_notification(hass): ) ProfileMock.package_list = [package_delivered] - hass.components.persistent_notification = mock.MagicMock() + hass.components.persistent_notification = MagicMock() await _goto_future(hass) hass.components.persistent_notification.create.assert_called() diff --git a/tests/components/vera/common.py b/tests/components/vera/common.py index 5574c93c515..31e7c706ec9 100644 --- a/tests/components/vera/common.py +++ b/tests/components/vera/common.py @@ -2,13 +2,13 @@ from typing import Callable, Dict, NamedTuple, Tuple -from mock import MagicMock import pyvera as pv from homeassistant.components.vera.const import CONF_CONTROLLER, DOMAIN from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component +from tests.async_mock import MagicMock from tests.common import MockConfigEntry SetupCallback = Callable[[pv.VeraController, dict], None] diff --git a/tests/components/vera/conftest.py b/tests/components/vera/conftest.py index 2c15d3e4182..193c0733736 100644 --- a/tests/components/vera/conftest.py +++ b/tests/components/vera/conftest.py @@ -1,10 +1,11 @@ """Fixtures for tests.""" -from mock import patch import pytest from .common import ComponentFactory +from tests.async_mock import patch + @pytest.fixture() def vera_component_factory(): diff --git a/tests/components/vera/test_config_flow.py b/tests/components/vera/test_config_flow.py index 3915d4d0577..793e313125c 100644 --- a/tests/components/vera/test_config_flow.py +++ b/tests/components/vera/test_config_flow.py @@ -1,5 +1,4 @@ """Vera tests.""" -from mock import patch from requests.exceptions import RequestException from homeassistant import config_entries, data_entry_flow @@ -12,7 +11,7 @@ from homeassistant.data_entry_flow import ( RESULT_TYPE_FORM, ) -from tests.async_mock import MagicMock +from tests.async_mock import MagicMock, patch from tests.common import MockConfigEntry From c987ca735e3b170e955e31c897781f0f2b7d7d5b Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 4 Jun 2020 20:32:00 +0200 Subject: [PATCH 388/406] Bump pychromecast to 6.0.0 (#36414) * Revert "Prevent race in pychromecast.start_discovery (#36350)" This reverts commit 391983a0cf56a226120057390ddd33586019b827. * Adapt to pychromecast 6.0.0 --- homeassistant/components/cast/discovery.py | 13 +++---- homeassistant/components/cast/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/cast/test_media_player.py | 38 +++++++++------------ 5 files changed, 24 insertions(+), 33 deletions(-) diff --git a/homeassistant/components/cast/discovery.py b/homeassistant/components/cast/discovery.py index 4d58fc3383a..e7d13ea0a18 100644 --- a/homeassistant/components/cast/discovery.py +++ b/homeassistant/components/cast/discovery.py @@ -3,7 +3,6 @@ import logging import threading import pychromecast -import zeroconf from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.core import HomeAssistant @@ -85,14 +84,12 @@ def setup_internal_discovery(hass: HomeAssistant) -> None: ) _LOGGER.debug("Starting internal pychromecast discovery.") - listener = pychromecast.discovery.CastListener( - internal_add_callback, internal_remove_callback - ) - browser = zeroconf.ServiceBrowser( - ChromeCastZeroconf.get_zeroconf() or zeroconf.Zeroconf(), - "_googlecast._tcp.local.", - listener, + listener = pychromecast.CastListener( + internal_add_callback, + internal_remove_callback, + internal_add_callback, # Use internal_add_callback also for updates ) + browser = pychromecast.start_discovery(listener, ChromeCastZeroconf.get_zeroconf()) def stop_discovery(event): """Stop discovery of new chromecasts.""" diff --git a/homeassistant/components/cast/manifest.json b/homeassistant/components/cast/manifest.json index 0be595de549..edf0373dd5d 100644 --- a/homeassistant/components/cast/manifest.json +++ b/homeassistant/components/cast/manifest.json @@ -3,7 +3,7 @@ "name": "Google Cast", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/cast", - "requirements": ["pychromecast==5.3.0"], + "requirements": ["pychromecast==6.0.0"], "after_dependencies": ["cloud","zeroconf"], "zeroconf": ["_googlecast._tcp.local."], "codeowners": ["@emontnemery"] diff --git a/requirements_all.txt b/requirements_all.txt index 99cd6880fdf..3c64d0886a2 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1245,7 +1245,7 @@ pycfdns==0.0.1 pychannels==1.0.0 # homeassistant.components.cast -pychromecast==5.3.0 +pychromecast==6.0.0 # homeassistant.components.cmus pycmus==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9b520efc75b..be9b90152f6 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -539,7 +539,7 @@ pyblackbird==0.5 pybotvac==0.0.17 # homeassistant.components.cast -pychromecast==5.3.0 +pychromecast==6.0.0 # homeassistant.components.coolmaster pycoolmasternet==0.0.4 diff --git a/tests/components/cast/test_media_player.py b/tests/components/cast/test_media_player.py index ce9e4ad7bf8..90b3896396c 100644 --- a/tests/components/cast/test_media_player.py +++ b/tests/components/cast/test_media_player.py @@ -80,17 +80,17 @@ async def async_setup_cast_internal_discovery(hass, config=None, discovery_info= browser = MagicMock(zc={}) with patch( - "homeassistant.components.cast.discovery.pychromecast.discovery.CastListener", + "homeassistant.components.cast.discovery.pychromecast.CastListener", return_value=listener, ) as cast_listener, patch( - "homeassistant.components.cast.discovery.zeroconf.ServiceBrowser", + "homeassistant.components.cast.discovery.pychromecast.start_discovery", return_value=browser, - ): + ) as start_discovery: add_entities = await async_setup_cast(hass, config, discovery_info) await hass.async_block_till_done() await hass.async_block_till_done() - assert cast_listener.call_count == 1 + assert start_discovery.call_count == 1 discovery_callback = cast_listener.call_args[0][0] @@ -120,10 +120,10 @@ async def async_setup_media_player_cast(hass: HomeAssistantType, info: Chromecas "homeassistant.components.cast.discovery.pychromecast.get_chromecast_from_service", return_value=chromecast, ) as get_chromecast, patch( - "homeassistant.components.cast.discovery.pychromecast.discovery.CastListener", + "homeassistant.components.cast.discovery.pychromecast.CastListener", return_value=listener, ) as cast_listener, patch( - "homeassistant.components.cast.discovery.zeroconf.ServiceBrowser", + "homeassistant.components.cast.discovery.pychromecast.start_discovery", return_value=browser, ): await async_setup_component( @@ -159,17 +159,15 @@ async def async_setup_media_player_cast(hass: HomeAssistantType, info: Chromecas async def test_start_discovery_called_once(hass): """Test pychromecast.start_discovery called exactly once.""" with patch( - "homeassistant.components.cast.discovery.pychromecast.discovery.CastListener", - ) as cast_listener, patch( - "homeassistant.components.cast.discovery.zeroconf.ServiceBrowser", + "homeassistant.components.cast.discovery.pychromecast.start_discovery", return_value=Mock(), - ): + ) as start_discovery: await async_setup_cast(hass) - assert cast_listener.call_count == 1 + assert start_discovery.call_count == 1 await async_setup_cast(hass) - assert cast_listener.call_count == 1 + assert start_discovery.call_count == 1 async def test_stop_discovery_called_on_stop(hass): @@ -177,15 +175,13 @@ async def test_stop_discovery_called_on_stop(hass): browser = MagicMock(zc={}) with patch( - "homeassistant.components.cast.discovery.pychromecast.discovery.CastListener", - ) as cast_listener, patch( - "homeassistant.components.cast.discovery.zeroconf.ServiceBrowser", + "homeassistant.components.cast.discovery.pychromecast.start_discovery", return_value=browser, - ): + ) as start_discovery: # start_discovery should be called with empty config await async_setup_cast(hass, {}) - assert cast_listener.call_count == 1 + assert start_discovery.call_count == 1 with patch( "homeassistant.components.cast.discovery.pychromecast.stop_discovery" @@ -197,15 +193,13 @@ async def test_stop_discovery_called_on_stop(hass): stop_discovery.assert_called_once_with(browser) with patch( - "homeassistant.components.cast.discovery.pychromecast.discovery.CastListener", - ) as cast_listener, patch( - "homeassistant.components.cast.discovery.zeroconf.ServiceBrowser", + "homeassistant.components.cast.discovery.pychromecast.start_discovery", return_value=browser, - ): + ) as start_discovery: # start_discovery should be called again on re-startup await async_setup_cast(hass) - assert cast_listener.call_count == 1 + assert start_discovery.call_count == 1 async def test_create_cast_device_without_uuid(hass): From dd3b0df22d58f5d3b687a74e3e1476c2871362c8 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Sat, 6 Jun 2020 06:58:26 +0200 Subject: [PATCH 389/406] Update frontend to 20200603.2 (#36494) --- 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 269c16ce9a8..682c3e9b62f 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==20200603.1"], + "requirements": ["home-assistant-frontend==20200603.2"], "dependencies": [ "api", "auth", diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 479d60bc47b..f5ea30c53ee 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -12,7 +12,7 @@ cryptography==2.9.2 defusedxml==0.6.0 distro==1.5.0 hass-nabucasa==0.34.5 -home-assistant-frontend==20200603.1 +home-assistant-frontend==20200603.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 3c64d0886a2..dec749de4c2 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -734,7 +734,7 @@ hole==0.5.1 holidays==0.10.2 # homeassistant.components.frontend -home-assistant-frontend==20200603.1 +home-assistant-frontend==20200603.2 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index be9b90152f6..9a8c2bf6622 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -321,7 +321,7 @@ hole==0.5.1 holidays==0.10.2 # homeassistant.components.frontend -home-assistant-frontend==20200603.1 +home-assistant-frontend==20200603.2 # homeassistant.components.zwave homeassistant-pyozw==0.1.10 From b9bc147339dfe779ab5bdf0c9b80390e0cfdbc97 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 6 Jun 2020 10:02:39 -0700 Subject: [PATCH 390/406] Update netdisco (#36499) --- homeassistant/components/discovery/manifest.json | 2 +- homeassistant/components/ssdp/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/discovery/manifest.json b/homeassistant/components/discovery/manifest.json index b2065edfc54..89d800a1c36 100644 --- a/homeassistant/components/discovery/manifest.json +++ b/homeassistant/components/discovery/manifest.json @@ -2,7 +2,7 @@ "domain": "discovery", "name": "Discovery", "documentation": "https://www.home-assistant.io/integrations/discovery", - "requirements": ["netdisco==2.6.0"], + "requirements": ["netdisco==2.7.0"], "after_dependencies": ["zeroconf"], "codeowners": [], "quality_scale": "internal" diff --git a/homeassistant/components/ssdp/manifest.json b/homeassistant/components/ssdp/manifest.json index 85b91ff005c..54fac55198e 100644 --- a/homeassistant/components/ssdp/manifest.json +++ b/homeassistant/components/ssdp/manifest.json @@ -2,7 +2,7 @@ "domain": "ssdp", "name": "Simple Service Discovery Protocol (SSDP)", "documentation": "https://www.home-assistant.io/integrations/ssdp", - "requirements": ["defusedxml==0.6.0", "netdisco==2.6.0"], + "requirements": ["defusedxml==0.6.0", "netdisco==2.7.0"], "after_dependencies": ["zeroconf"], "codeowners": [] } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index f5ea30c53ee..5178df84da2 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -15,7 +15,7 @@ hass-nabucasa==0.34.5 home-assistant-frontend==20200603.2 importlib-metadata==1.6.0 jinja2>=2.11.1 -netdisco==2.6.0 +netdisco==2.7.0 pip>=8.0.3 python-slugify==4.0.0 pytz>=2020.1 diff --git a/requirements_all.txt b/requirements_all.txt index dec749de4c2..6abc7be4a30 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -946,7 +946,7 @@ netdata==0.1.2 # homeassistant.components.discovery # homeassistant.components.ssdp -netdisco==2.6.0 +netdisco==2.7.0 # homeassistant.components.neurio_energy neurio==0.3.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9a8c2bf6622..bcafa9c569e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -399,7 +399,7 @@ nessclient==0.9.15 # homeassistant.components.discovery # homeassistant.components.ssdp -netdisco==2.6.0 +netdisco==2.7.0 # homeassistant.components.nexia nexia==0.9.3 From e43a0087e45a6a4da4868fbf1381666f3fbdda5b Mon Sep 17 00:00:00 2001 From: matgad Date: Sat, 6 Jun 2020 15:32:26 +0200 Subject: [PATCH 391/406] Bump version zigpy-cc (#36506) --- 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 ef96d6efef1..63a87932ba9 100644 --- a/homeassistant/components/zha/manifest.json +++ b/homeassistant/components/zha/manifest.json @@ -7,7 +7,7 @@ "bellows==0.16.2", "pyserial==3.4", "zha-quirks==0.0.39", - "zigpy-cc==0.4.2", + "zigpy-cc==0.4.4", "zigpy-deconz==0.9.2", "zigpy==0.20.4", "zigpy-xbee==0.12.1", diff --git a/requirements_all.txt b/requirements_all.txt index 6abc7be4a30..8e40ab5d96d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2251,7 +2251,7 @@ zhong_hong_hvac==1.0.9 ziggo-mediabox-xl==1.1.0 # homeassistant.components.zha -zigpy-cc==0.4.2 +zigpy-cc==0.4.4 # homeassistant.components.zha zigpy-deconz==0.9.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index bcafa9c569e..3b2cd18a226 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -921,7 +921,7 @@ zeroconf==0.27.1 zha-quirks==0.0.39 # homeassistant.components.zha -zigpy-cc==0.4.2 +zigpy-cc==0.4.4 # homeassistant.components.zha zigpy-deconz==0.9.2 From 609d202c4d1b46d7ef27541881906206b1ba55fc Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sun, 7 Jun 2020 23:37:58 +0200 Subject: [PATCH 392/406] Fix WLED power and brightness with WLED 0.10+ (#36529) --- homeassistant/components/wled/light.py | 163 ++++++++++--- homeassistant/components/wled/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/wled/test_light.py | 240 ++++++++++++++++---- 5 files changed, 337 insertions(+), 72 deletions(-) diff --git a/homeassistant/components/wled/light.py b/homeassistant/components/wled/light.py index 77f8960ada2..6a22bf6852f 100644 --- a/homeassistant/components/wled/light.py +++ b/homeassistant/components/wled/light.py @@ -83,20 +83,78 @@ async def async_setup_entry( update_segments() -class WLEDLight(LightEntity, WLEDDeviceEntity): - """Defines a WLED light.""" +class WLEDMasterLight(LightEntity, WLEDDeviceEntity): + """Defines a WLED master light.""" + + def __init__(self, entry_id: str, coordinator: WLEDDataUpdateCoordinator): + """Initialize WLED master light.""" + super().__init__( + entry_id=entry_id, + coordinator=coordinator, + name=f"{coordinator.data.info.name} Master", + icon="mdi:led-strip-variant", + ) + + @property + def unique_id(self) -> str: + """Return the unique ID for this sensor.""" + return f"{self.coordinator.data.info.mac_address}" + + @property + def supported_features(self) -> int: + """Flag supported features.""" + return SUPPORT_BRIGHTNESS | SUPPORT_TRANSITION + + @property + def brightness(self) -> Optional[int]: + """Return the brightness of this light between 1..255.""" + return self.coordinator.data.state.brightness + + @property + def is_on(self) -> bool: + """Return the state of the light.""" + return bool(self.coordinator.data.state.on) + + @wled_exception_handler + async def async_turn_off(self, **kwargs: Any) -> None: + """Turn off the light.""" + data = {ATTR_ON: False} + + if ATTR_TRANSITION in kwargs: + # WLED uses 100ms per unit, so 10 = 1 second. + data[ATTR_TRANSITION] = round(kwargs[ATTR_TRANSITION] * 10) + + await self.coordinator.wled.master(**data) + + @wled_exception_handler + async def async_turn_on(self, **kwargs: Any) -> None: + """Turn on the light.""" + data = {ATTR_ON: True} + + if ATTR_TRANSITION in kwargs: + # WLED uses 100ms per unit, so 10 = 1 second. + data[ATTR_TRANSITION] = round(kwargs[ATTR_TRANSITION] * 10) + + if ATTR_BRIGHTNESS in kwargs: + data[ATTR_BRIGHTNESS] = kwargs[ATTR_BRIGHTNESS] + + await self.coordinator.wled.master(**data) + + +class WLEDSegmentLight(LightEntity, WLEDDeviceEntity): + """Defines a WLED light based on a segment.""" def __init__( self, entry_id: str, coordinator: WLEDDataUpdateCoordinator, segment: int ): - """Initialize WLED light.""" + """Initialize WLED segment light.""" self._rgbw = coordinator.data.info.leds.rgbw self._segment = segment - # Only apply the segment ID if it is not the first segment - name = coordinator.data.info.name - if segment != 0: - name += f" {segment}" + # If this is the one and only segment, use a simpler name + name = f"{coordinator.data.info.name} Segment {self._segment}" + if len(coordinator.data.state.segments) == 1: + name = coordinator.data.info.name super().__init__( entry_id=entry_id, @@ -155,7 +213,16 @@ class WLEDLight(LightEntity, WLEDDeviceEntity): @property def brightness(self) -> Optional[int]: """Return the brightness of this light between 1..255.""" - return self.coordinator.data.state.brightness + state = self.coordinator.data.state + + # If this is the one and only segment, calculate brightness based + # on the master and segment brightness + if len(state.segments) == 1: + return int( + (state.segments[self._segment].brightness * state.brightness) / 255 + ) + + return state.segments[self._segment].brightness @property def white_value(self) -> Optional[int]: @@ -187,18 +254,30 @@ class WLEDLight(LightEntity, WLEDDeviceEntity): @property def is_on(self) -> bool: """Return the state of the light.""" - return bool(self.coordinator.data.state.on) + state = self.coordinator.data.state + + # If there is a single segment, take master into account + if len(state.segments) == 1 and not state.on: + return False + + return bool(state.segments[self._segment].on) @wled_exception_handler async def async_turn_off(self, **kwargs: Any) -> None: """Turn off the light.""" - data = {ATTR_ON: False, ATTR_SEGMENT_ID: self._segment} + data = {ATTR_ON: False} if ATTR_TRANSITION in kwargs: # WLED uses 100ms per unit, so 10 = 1 second. data[ATTR_TRANSITION] = round(kwargs[ATTR_TRANSITION] * 10) - await self.coordinator.wled.light(**data) + # If there is a single segment, control via the master + if len(self.coordinator.data.state.segments) == 1: + await self.coordinator.wled.master(**data) + return + + data[ATTR_SEGMENT_ID] = self._segment + await self.coordinator.wled.segment(**data) @wled_exception_handler async def async_turn_on(self, **kwargs: Any) -> None: @@ -248,7 +327,23 @@ class WLEDLight(LightEntity, WLEDDeviceEntity): else: data[ATTR_COLOR_PRIMARY] += (self.white_value,) - await self.coordinator.wled.light(**data) + # When only 1 segment is present, switch along the master, and use + # the master for power/brightness control. + if len(self.coordinator.data.state.segments) == 1: + master_data = {ATTR_ON: True} + if ATTR_BRIGHTNESS in data: + master_data[ATTR_BRIGHTNESS] = data[ATTR_BRIGHTNESS] + data[ATTR_BRIGHTNESS] = 255 + + if ATTR_TRANSITION in data: + master_data[ATTR_TRANSITION] = data[ATTR_TRANSITION] + del data[ATTR_TRANSITION] + + await self.coordinator.wled.segment(**data) + await self.coordinator.wled.master(**master_data) + return + + await self.coordinator.wled.segment(**data) @wled_exception_handler async def async_effect( @@ -273,45 +368,59 @@ class WLEDLight(LightEntity, WLEDDeviceEntity): if speed is not None: data[ATTR_SPEED] = speed - await self.coordinator.wled.light(**data) + await self.coordinator.wled.segment(**data) @callback def async_update_segments( entry: ConfigEntry, coordinator: WLEDDataUpdateCoordinator, - current: Dict[int, WLEDLight], + current: Dict[int, WLEDSegmentLight], async_add_entities, ) -> None: """Update segments.""" segment_ids = {light.segment_id for light in coordinator.data.state.segments} current_ids = set(current) - # Process new segments, add them to Home Assistant - new_segments = [] - for segment_id in segment_ids - current_ids: - current[segment_id] = WLEDLight(entry.entry_id, coordinator, segment_id) - new_segments.append(current[segment_id]) + # Discard master (if present) + current_ids.discard(-1) - if new_segments: - async_add_entities(new_segments) + # Process new segments, add them to Home Assistant + new_entities = [] + for segment_id in segment_ids - current_ids: + current[segment_id] = WLEDSegmentLight(entry.entry_id, coordinator, segment_id) + new_entities.append(current[segment_id]) + + # More than 1 segment now? Add master controls + if len(current_ids) < 2 and len(segment_ids) > 1: + current[-1] = WLEDMasterLight(entry.entry_id, coordinator) + new_entities.append(current[-1]) + + if new_entities: + async_add_entities(new_entities) # Process deleted segments, remove them from Home Assistant for segment_id in current_ids - segment_ids: coordinator.hass.async_create_task( - async_remove_segment(segment_id, coordinator, current) + async_remove_entity(segment_id, coordinator, current) + ) + + # Remove master if there is only 1 segment left + if len(current_ids) > 1 and len(segment_ids) < 2: + coordinator.hass.async_create_task( + async_remove_entity(-1, coordinator, current) ) -async def async_remove_segment( - segment_id: int, +async def async_remove_entity( + index: int, coordinator: WLEDDataUpdateCoordinator, - current: Dict[int, WLEDLight], + current: Dict[int, WLEDSegmentLight], ) -> None: """Remove WLED segment light from Home Assistant.""" - entity = current[segment_id] + entity = current[index] await entity.async_remove() registry = await async_get_entity_registry(coordinator.hass) if entity.entity_id in registry.entities: registry.async_remove(entity.entity_id) - del current[segment_id] + del current[index] diff --git a/homeassistant/components/wled/manifest.json b/homeassistant/components/wled/manifest.json index aa3f944ed1e..1653ecf1365 100644 --- a/homeassistant/components/wled/manifest.json +++ b/homeassistant/components/wled/manifest.json @@ -3,7 +3,7 @@ "name": "WLED", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/wled", - "requirements": ["wled==0.4.1"], + "requirements": ["wled==0.4.2"], "zeroconf": ["_wled._tcp.local."], "codeowners": ["@frenck"], "quality_scale": "platinum" diff --git a/requirements_all.txt b/requirements_all.txt index 8e40ab5d96d..8234ee4cd54 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2195,7 +2195,7 @@ wirelesstagpy==0.4.0 withings-api==2.1.3 # homeassistant.components.wled -wled==0.4.1 +wled==0.4.2 # homeassistant.components.xbee xbee-helper==0.0.7 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 3b2cd18a226..a113578bc65 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -901,7 +901,7 @@ wiffi==1.0.0 withings-api==2.1.3 # homeassistant.components.wled -wled==0.4.1 +wled==0.4.2 # homeassistant.components.bluesound # homeassistant.components.rest diff --git a/tests/components/wled/test_light.py b/tests/components/wled/test_light.py index 8854b00ff83..35e8ea0f7ef 100644 --- a/tests/components/wled/test_light.py +++ b/tests/components/wled/test_light.py @@ -29,6 +29,7 @@ from homeassistant.const import ( ATTR_ICON, SERVICE_TURN_OFF, SERVICE_TURN_ON, + STATE_OFF, STATE_ON, STATE_UNAVAILABLE, ) @@ -50,7 +51,7 @@ async def test_rgb_light_state( entity_registry = await hass.helpers.entity_registry.async_get_registry() # First segment of the strip - state = hass.states.get("light.wled_rgb_light") + state = hass.states.get("light.wled_rgb_light_segment_0") assert state assert state.attributes.get(ATTR_BRIGHTNESS) == 127 assert state.attributes.get(ATTR_EFFECT) == "Solid" @@ -64,12 +65,12 @@ async def test_rgb_light_state( assert state.attributes.get(ATTR_SPEED) == 32 assert state.state == STATE_ON - entry = entity_registry.async_get("light.wled_rgb_light") + entry = entity_registry.async_get("light.wled_rgb_light_segment_0") assert entry assert entry.unique_id == "aabbccddeeff_0" # Second segment of the strip - state = hass.states.get("light.wled_rgb_light_1") + state = hass.states.get("light.wled_rgb_light_segment_1") assert state assert state.attributes.get(ATTR_BRIGHTNESS) == 127 assert state.attributes.get(ATTR_EFFECT) == "Blink" @@ -83,22 +84,32 @@ async def test_rgb_light_state( assert state.attributes.get(ATTR_SPEED) == 16 assert state.state == STATE_ON - entry = entity_registry.async_get("light.wled_rgb_light_1") + entry = entity_registry.async_get("light.wled_rgb_light_segment_1") assert entry assert entry.unique_id == "aabbccddeeff_1" + # Test master control of the lightstrip + state = hass.states.get("light.wled_rgb_light_master") + assert state + assert state.attributes.get(ATTR_BRIGHTNESS) == 127 + assert state.state == STATE_ON -async def test_switch_change_state( + entry = entity_registry.async_get("light.wled_rgb_light_master") + assert entry + assert entry.unique_id == "aabbccddeeff" + + +async def test_segment_change_state( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, caplog ) -> None: - """Test the change of state of the WLED switches.""" + """Test the change of state of the WLED segments.""" await init_integration(hass, aioclient_mock) - with patch("wled.WLED.light") as light_mock: + with patch("wled.WLED.segment") as light_mock: await hass.services.async_call( LIGHT_DOMAIN, SERVICE_TURN_OFF, - {ATTR_ENTITY_ID: "light.wled_rgb_light", ATTR_TRANSITION: 5}, + {ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0", ATTR_TRANSITION: 5}, blocking=True, ) await hass.async_block_till_done() @@ -106,14 +117,14 @@ async def test_switch_change_state( on=False, segment_id=0, transition=50, ) - with patch("wled.WLED.light") as light_mock: + with patch("wled.WLED.segment") as light_mock: await hass.services.async_call( LIGHT_DOMAIN, SERVICE_TURN_ON, { ATTR_BRIGHTNESS: 42, ATTR_EFFECT: "Chase", - ATTR_ENTITY_ID: "light.wled_rgb_light", + ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0", ATTR_RGB_COLOR: [255, 0, 0], ATTR_TRANSITION: 5, }, @@ -129,11 +140,11 @@ async def test_switch_change_state( transition=50, ) - with patch("wled.WLED.light") as light_mock: + with patch("wled.WLED.segment") as light_mock: await hass.services.async_call( LIGHT_DOMAIN, SERVICE_TURN_ON, - {ATTR_ENTITY_ID: "light.wled_rgb_light", ATTR_COLOR_TEMP: 400}, + {ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0", ATTR_COLOR_TEMP: 400}, blocking=True, ) await hass.async_block_till_done() @@ -142,33 +153,178 @@ async def test_switch_change_state( ) +async def test_master_change_state( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, caplog +) -> None: + """Test the change of state of the WLED master light control.""" + await init_integration(hass, aioclient_mock) + + with patch("wled.WLED.master") as light_mock: + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: "light.wled_rgb_light_master", ATTR_TRANSITION: 5}, + blocking=True, + ) + await hass.async_block_till_done() + light_mock.assert_called_once_with( + on=False, transition=50, + ) + + with patch("wled.WLED.master") as light_mock: + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_ON, + { + ATTR_BRIGHTNESS: 42, + ATTR_ENTITY_ID: "light.wled_rgb_light_master", + ATTR_TRANSITION: 5, + }, + blocking=True, + ) + await hass.async_block_till_done() + light_mock.assert_called_once_with( + brightness=42, on=True, transition=50, + ) + + with patch("wled.WLED.master") as light_mock: + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: "light.wled_rgb_light_master", ATTR_TRANSITION: 5}, + blocking=True, + ) + await hass.async_block_till_done() + light_mock.assert_called_once_with( + on=False, transition=50, + ) + + with patch("wled.WLED.master") as light_mock: + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_ON, + { + ATTR_BRIGHTNESS: 42, + ATTR_ENTITY_ID: "light.wled_rgb_light_master", + ATTR_TRANSITION: 5, + }, + blocking=True, + ) + await hass.async_block_till_done() + light_mock.assert_called_once_with( + brightness=42, on=True, transition=50, + ) + + async def test_dynamically_handle_segments( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker ) -> None: """Test if a new/deleted segment is dynamically added/removed.""" await init_integration(hass, aioclient_mock) - assert hass.states.get("light.wled_rgb_light") - assert hass.states.get("light.wled_rgb_light_1") + assert hass.states.get("light.wled_rgb_light_master") + assert hass.states.get("light.wled_rgb_light_segment_0") + assert hass.states.get("light.wled_rgb_light_segment_1") data = json.loads(load_fixture("wled/rgb_single_segment.json")) device = WLEDDevice(data) - # Test removal if segment went missing + # Test removal if segment went missing, including the master entity with patch( "homeassistant.components.wled.WLED.update", return_value=device, ): async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL) await hass.async_block_till_done() - assert hass.states.get("light.wled_rgb_light") - assert not hass.states.get("light.wled_rgb_light_1") + assert hass.states.get("light.wled_rgb_light_segment_0") + assert not hass.states.get("light.wled_rgb_light_segment_1") + assert not hass.states.get("light.wled_rgb_light_master") - # Test adding if segment shows up again + # Test adding if segment shows up again, including the master entity async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL) await hass.async_block_till_done() - assert hass.states.get("light.wled_rgb_light") - assert hass.states.get("light.wled_rgb_light_1") + assert hass.states.get("light.wled_rgb_light_master") + assert hass.states.get("light.wled_rgb_light_segment_0") + assert hass.states.get("light.wled_rgb_light_segment_1") + + +async def test_single_segment_behavior( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, caplog +) -> None: + """Test the behavior of the integration with a single segment.""" + await init_integration(hass, aioclient_mock) + + data = json.loads(load_fixture("wled/rgb_single_segment.json")) + device = WLEDDevice(data) + + # Test absent master + with patch( + "homeassistant.components.wled.WLED.update", return_value=device, + ): + async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL) + await hass.async_block_till_done() + + assert not hass.states.get("light.wled_rgb_light_master") + + state = hass.states.get("light.wled_rgb_light_segment_0") + assert state + assert state.state == STATE_ON + + # Test segment brightness takes master into account + device.state.brightness = 100 + device.state.segments[0].brightness = 255 + with patch( + "homeassistant.components.wled.WLED.update", return_value=device, + ): + async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL) + await hass.async_block_till_done() + + state = hass.states.get("light.wled_rgb_light_segment_0") + assert state + assert state.attributes.get(ATTR_BRIGHTNESS) == 100 + + # Test segment is off when master is off + device.state.on = False + with patch( + "homeassistant.components.wled.WLED.update", return_value=device, + ): + async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL) + await hass.async_block_till_done() + state = hass.states.get("light.wled_rgb_light_segment_0") + assert state + assert state.state == STATE_OFF + + # Test master is turned off when turning off a single segment + with patch("wled.WLED.master") as master_mock: + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0", ATTR_TRANSITION: 5}, + blocking=True, + ) + await hass.async_block_till_done() + master_mock.assert_called_once_with( + on=False, transition=50, + ) + + # Test master is turned on when turning on a single segment, and segment + # brightness is set to 255. + with patch("wled.WLED.master") as master_mock, patch( + "wled.WLED.segment" + ) as segment_mock: + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_ON, + { + ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0", + ATTR_TRANSITION: 5, + ATTR_BRIGHTNESS: 42, + }, + blocking=True, + ) + await hass.async_block_till_done() + master_mock.assert_called_once_with(on=True, transition=50, brightness=42) + segment_mock.assert_called_once_with(on=True, segment_id=0, brightness=255) async def test_light_error( @@ -182,12 +338,12 @@ async def test_light_error( await hass.services.async_call( LIGHT_DOMAIN, SERVICE_TURN_OFF, - {ATTR_ENTITY_ID: "light.wled_rgb_light"}, + {ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0"}, blocking=True, ) await hass.async_block_till_done() - state = hass.states.get("light.wled_rgb_light") + state = hass.states.get("light.wled_rgb_light_segment_0") assert state.state == STATE_ON assert "Invalid response from API" in caplog.text @@ -199,17 +355,17 @@ async def test_light_connection_error( await init_integration(hass, aioclient_mock) with patch("homeassistant.components.wled.WLED.update"), patch( - "homeassistant.components.wled.WLED.light", side_effect=WLEDConnectionError + "homeassistant.components.wled.WLED.segment", side_effect=WLEDConnectionError ): await hass.services.async_call( LIGHT_DOMAIN, SERVICE_TURN_OFF, - {ATTR_ENTITY_ID: "light.wled_rgb_light"}, + {ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0"}, blocking=True, ) await hass.async_block_till_done() - state = hass.states.get("light.wled_rgb_light") + state = hass.states.get("light.wled_rgb_light_segment_0") assert state.state == STATE_UNAVAILABLE @@ -224,7 +380,7 @@ async def test_rgbw_light( assert state.attributes.get(ATTR_HS_COLOR) == (0.0, 100.0) assert state.attributes.get(ATTR_WHITE_VALUE) == 139 - with patch("wled.WLED.light") as light_mock: + with patch("wled.WLED.segment") as light_mock: await hass.services.async_call( LIGHT_DOMAIN, SERVICE_TURN_ON, @@ -236,7 +392,7 @@ async def test_rgbw_light( on=True, segment_id=0, color_primary=(255, 159, 70, 139), ) - with patch("wled.WLED.light") as light_mock: + with patch("wled.WLED.segment") as light_mock: await hass.services.async_call( LIGHT_DOMAIN, SERVICE_TURN_ON, @@ -248,7 +404,7 @@ async def test_rgbw_light( color_primary=(255, 0, 0, 100), on=True, segment_id=0, ) - with patch("wled.WLED.light") as light_mock: + with patch("wled.WLED.segment") as light_mock: await hass.services.async_call( LIGHT_DOMAIN, SERVICE_TURN_ON, @@ -271,13 +427,13 @@ async def test_effect_service( """Test the effect service of a WLED light.""" await init_integration(hass, aioclient_mock) - with patch("wled.WLED.light") as light_mock: + with patch("wled.WLED.segment") as light_mock: await hass.services.async_call( DOMAIN, SERVICE_EFFECT, { ATTR_EFFECT: "Rainbow", - ATTR_ENTITY_ID: "light.wled_rgb_light", + ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0", ATTR_INTENSITY: 200, ATTR_REVERSE: True, ATTR_SPEED: 100, @@ -289,11 +445,11 @@ async def test_effect_service( effect="Rainbow", intensity=200, reverse=True, segment_id=0, speed=100, ) - with patch("wled.WLED.light") as light_mock: + with patch("wled.WLED.segment") as light_mock: await hass.services.async_call( DOMAIN, SERVICE_EFFECT, - {ATTR_ENTITY_ID: "light.wled_rgb_light", ATTR_EFFECT: 9}, + {ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0", ATTR_EFFECT: 9}, blocking=True, ) await hass.async_block_till_done() @@ -301,12 +457,12 @@ async def test_effect_service( segment_id=0, effect=9, ) - with patch("wled.WLED.light") as light_mock: + with patch("wled.WLED.segment") as light_mock: await hass.services.async_call( DOMAIN, SERVICE_EFFECT, { - ATTR_ENTITY_ID: "light.wled_rgb_light", + ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0", ATTR_INTENSITY: 200, ATTR_REVERSE: True, ATTR_SPEED: 100, @@ -318,13 +474,13 @@ async def test_effect_service( intensity=200, reverse=True, segment_id=0, speed=100, ) - with patch("wled.WLED.light") as light_mock: + with patch("wled.WLED.segment") as light_mock: await hass.services.async_call( DOMAIN, SERVICE_EFFECT, { ATTR_EFFECT: "Rainbow", - ATTR_ENTITY_ID: "light.wled_rgb_light", + ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0", ATTR_REVERSE: True, ATTR_SPEED: 100, }, @@ -335,13 +491,13 @@ async def test_effect_service( effect="Rainbow", reverse=True, segment_id=0, speed=100, ) - with patch("wled.WLED.light") as light_mock: + with patch("wled.WLED.segment") as light_mock: await hass.services.async_call( DOMAIN, SERVICE_EFFECT, { ATTR_EFFECT: "Rainbow", - ATTR_ENTITY_ID: "light.wled_rgb_light", + ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0", ATTR_INTENSITY: 200, ATTR_SPEED: 100, }, @@ -352,13 +508,13 @@ async def test_effect_service( effect="Rainbow", intensity=200, segment_id=0, speed=100, ) - with patch("wled.WLED.light") as light_mock: + with patch("wled.WLED.segment") as light_mock: await hass.services.async_call( DOMAIN, SERVICE_EFFECT, { ATTR_EFFECT: "Rainbow", - ATTR_ENTITY_ID: "light.wled_rgb_light", + ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0", ATTR_INTENSITY: 200, ATTR_REVERSE: True, }, @@ -381,11 +537,11 @@ async def test_effect_service_error( await hass.services.async_call( DOMAIN, SERVICE_EFFECT, - {ATTR_ENTITY_ID: "light.wled_rgb_light", ATTR_EFFECT: 9}, + {ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0", ATTR_EFFECT: 9}, blocking=True, ) await hass.async_block_till_done() - state = hass.states.get("light.wled_rgb_light") + state = hass.states.get("light.wled_rgb_light_segment_0") assert state.state == STATE_ON assert "Invalid response from API" in caplog.text From 865d65c3809fab91788114c49a8701d6d1730aaf Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 7 Jun 2020 17:24:48 -0700 Subject: [PATCH 393/406] Bumped version to 0.111.0b3 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 756567e9745..113c6e59488 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 111 -PATCH_VERSION = "0b2" +PATCH_VERSION = "0b3" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 0) From 5d77eb1839cacee618526c54b1746ec9a0259f3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Mon, 8 Jun 2020 04:45:34 +0100 Subject: [PATCH 394/406] Fix intent component initialisation (#36064) The intent component expect this method from every module that is called intent. Fixes #35522 --- homeassistant/components/alexa/intent.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/homeassistant/components/alexa/intent.py b/homeassistant/components/alexa/intent.py index f879b66268b..c04b493beec 100644 --- a/homeassistant/components/alexa/intent.py +++ b/homeassistant/components/alexa/intent.py @@ -40,6 +40,16 @@ def async_setup(hass): hass.http.register_view(AlexaIntentsView) +async def async_setup_intents(hass): + """ + Do intents setup. + + Right now this module does not expose any, but the intent component breaks + without it. + """ + pass # pylint: disable=unnecessary-pass + + class UnknownRequest(HomeAssistantError): """When an unknown Alexa request is passed in.""" From 53ba45cc8fca50e80b984eb71bfb91417f6bda58 Mon Sep 17 00:00:00 2001 From: shbatm Date: Sun, 7 Jun 2020 20:00:53 -0500 Subject: [PATCH 395/406] Add Z-Wave Notification Sensor support to ISY994 (#36548) --- homeassistant/components/isy994/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/isy994/const.py b/homeassistant/components/isy994/const.py index cf8b304179d..afbe44011d8 100644 --- a/homeassistant/components/isy994/const.py +++ b/homeassistant/components/isy994/const.py @@ -232,7 +232,7 @@ NODE_FILTERS = { "RemoteLinc2_ADV", ], FILTER_INSTEON_TYPE: ["0.16.", "0.17.", "0.18.", "9.0.", "9.7."], - FILTER_ZWAVE_CAT: (["118", "143"] + list(map(str, range(180, 185)))), + FILTER_ZWAVE_CAT: (["118", "143"] + list(map(str, range(180, 186)))), }, LOCK: { FILTER_UOM: ["11"], From a84378bb79a36c8df5ddbf4f756004e5fa84f35b Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 8 Jun 2020 10:20:25 -0700 Subject: [PATCH 396/406] Mobile app fixes (#36559) --- .../components/mobile_app/webhook.py | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/mobile_app/webhook.py b/homeassistant/components/mobile_app/webhook.py index b278401d864..c155e722976 100644 --- a/homeassistant/components/mobile_app/webhook.py +++ b/homeassistant/components/mobile_app/webhook.py @@ -1,4 +1,5 @@ """Webhook handlers for mobile_app.""" +import asyncio from functools import wraps import logging import secrets @@ -28,7 +29,7 @@ from homeassistant.const import ( HTTP_CREATED, ) from homeassistant.core import EventOrigin -from homeassistant.exceptions import HomeAssistantError, ServiceNotFound, TemplateError +from homeassistant.exceptions import ServiceNotFound, TemplateError from homeassistant.helpers import config_validation as cv, device_registry as dr from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.template import attach @@ -95,6 +96,7 @@ from .helpers import ( _LOGGER = logging.getLogger(__name__) +DELAY_SAVE = 10 WEBHOOK_COMMANDS = Registry() @@ -184,7 +186,10 @@ async def handle_webhook( "Received webhook payload for type %s: %s", webhook_type, webhook_payload ) - return await WEBHOOK_COMMANDS[webhook_type](hass, config_entry, webhook_payload) + # Shield so we make sure we finish the webhook, even if sender hangs up. + return await asyncio.shield( + WEBHOOK_COMMANDS[webhook_type](hass, config_entry, webhook_payload) + ) @WEBHOOK_COMMANDS.register("call_service") @@ -376,11 +381,9 @@ async def webhook_register_sensor(hass, config_entry, data): hass.data[DOMAIN][entity_type][unique_store_key] = data - try: - await hass.data[DOMAIN][DATA_STORE].async_save(savable_state(hass)) - except HomeAssistantError as ex: - _LOGGER.error("Error registering sensor: %s", ex) - return empty_okay_response() + hass.data[DOMAIN][DATA_STORE].async_delay_save( + lambda: savable_state(hass), DELAY_SAVE + ) register_signal = f"{DOMAIN}_{data[ATTR_SENSOR_TYPE]}_register" async_dispatcher_send(hass, register_signal, data) @@ -458,18 +461,14 @@ async def webhook_update_sensor_states(hass, config_entry, data): hass.data[DOMAIN][entity_type][unique_store_key] = new_state - safe = savable_state(hass) - - try: - await hass.data[DOMAIN][DATA_STORE].async_save(safe) - except HomeAssistantError as ex: - _LOGGER.error("Error updating mobile_app registration: %s", ex) - return empty_okay_response() - async_dispatcher_send(hass, SIGNAL_SENSOR_UPDATE, new_state) resp[unique_id] = {"success": True} + hass.data[DOMAIN][DATA_STORE].async_delay_save( + lambda: savable_state(hass), DELAY_SAVE + ) + return webhook_response(resp, registration=config_entry.data) From b3d5717df8889542326d59eb35e234708fad8ccb Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 8 Jun 2020 21:11:37 +0200 Subject: [PATCH 397/406] Fix mobile_app sensor re-registration handling (#36567) --- homeassistant/components/mobile_app/const.py | 1 - .../components/mobile_app/webhook.py | 24 ++++++------ tests/components/mobile_app/test_entity.py | 39 ++++++++++++++++--- 3 files changed, 45 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/mobile_app/const.py b/homeassistant/components/mobile_app/const.py index 0fc4a5ee407..6e83a08c508 100644 --- a/homeassistant/components/mobile_app/const.py +++ b/homeassistant/components/mobile_app/const.py @@ -56,7 +56,6 @@ ERR_ENCRYPTION_ALREADY_ENABLED = "encryption_already_enabled" ERR_ENCRYPTION_NOT_AVAILABLE = "encryption_not_available" ERR_ENCRYPTION_REQUIRED = "encryption_required" ERR_SENSOR_NOT_REGISTERED = "not_registered" -ERR_SENSOR_DUPLICATE_UNIQUE_ID = "duplicate_unique_id" ERR_INVALID_FORMAT = "invalid_format" diff --git a/homeassistant/components/mobile_app/webhook.py b/homeassistant/components/mobile_app/webhook.py index c155e722976..d0ab79ab7e2 100644 --- a/homeassistant/components/mobile_app/webhook.py +++ b/homeassistant/components/mobile_app/webhook.py @@ -78,7 +78,6 @@ from .const import ( ERR_ENCRYPTION_NOT_AVAILABLE, ERR_ENCRYPTION_REQUIRED, ERR_INVALID_FORMAT, - ERR_SENSOR_DUPLICATE_UNIQUE_ID, ERR_SENSOR_NOT_REGISTERED, SIGNAL_LOCATION_UPDATE, SIGNAL_SENSOR_UPDATE, @@ -364,29 +363,30 @@ async def webhook_enable_encryption(hass, config_entry, data): async def webhook_register_sensor(hass, config_entry, data): """Handle a register sensor webhook.""" entity_type = data[ATTR_SENSOR_TYPE] - unique_id = data[ATTR_SENSOR_UNIQUE_ID] unique_store_key = f"{config_entry.data[CONF_WEBHOOK_ID]}_{unique_id}" - - if unique_store_key in hass.data[DOMAIN][entity_type]: - _LOGGER.error("Refusing to re-register existing sensor %s!", unique_id) - return error_response( - ERR_SENSOR_DUPLICATE_UNIQUE_ID, - f"{entity_type} {unique_id} already exists!", - status=409, - ) + existing_sensor = unique_store_key in hass.data[DOMAIN][entity_type] data[CONF_WEBHOOK_ID] = config_entry.data[CONF_WEBHOOK_ID] + # If sensor already is registered, update current state instead + if existing_sensor: + _LOGGER.debug("Re-register existing sensor %s", unique_id) + entry = hass.data[DOMAIN][entity_type][unique_store_key] + data = {**entry, **data} + hass.data[DOMAIN][entity_type][unique_store_key] = data hass.data[DOMAIN][DATA_STORE].async_delay_save( lambda: savable_state(hass), DELAY_SAVE ) - register_signal = f"{DOMAIN}_{data[ATTR_SENSOR_TYPE]}_register" - async_dispatcher_send(hass, register_signal, data) + if existing_sensor: + async_dispatcher_send(hass, SIGNAL_SENSOR_UPDATE, data) + else: + register_signal = f"{DOMAIN}_{data[ATTR_SENSOR_TYPE]}_register" + async_dispatcher_send(hass, register_signal, data) return webhook_response( {"success": True}, registration=config_entry.data, status=HTTP_CREATED, diff --git a/tests/components/mobile_app/test_entity.py b/tests/components/mobile_app/test_entity.py index 9f91e659fb0..d0d2a4f841a 100644 --- a/tests/components/mobile_app/test_entity.py +++ b/tests/components/mobile_app/test_entity.py @@ -95,8 +95,8 @@ async def test_sensor_must_register(hass, create_registrations, webhook_client): assert json["battery_state"]["error"]["code"] == "not_registered" -async def test_sensor_id_no_dupes(hass, create_registrations, webhook_client): - """Test that sensors must have a unique ID.""" +async def test_sensor_id_no_dupes(hass, create_registrations, webhook_client, caplog): + """Test that a duplicate unique ID in registration updates the sensor.""" webhook_id = create_registrations[1]["webhook_id"] webhook_url = f"/api/webhook/{webhook_id}" @@ -120,14 +120,41 @@ async def test_sensor_id_no_dupes(hass, create_registrations, webhook_client): reg_json = await reg_resp.json() assert reg_json == {"success": True} + await hass.async_block_till_done() + assert "Re-register existing sensor" not in caplog.text + + entity = hass.states.get("sensor.test_1_battery_state") + assert entity is not None + + assert entity.attributes["device_class"] == "battery" + assert entity.attributes["icon"] == "mdi:battery" + assert entity.attributes["unit_of_measurement"] == UNIT_PERCENTAGE + assert entity.attributes["foo"] == "bar" + assert entity.domain == "sensor" + assert entity.name == "Test 1 Battery State" + assert entity.state == "100" + + payload["data"]["state"] = 99 dupe_resp = await webhook_client.post(webhook_url, json=payload) - assert dupe_resp.status == 409 + assert dupe_resp.status == 201 + dupe_reg_json = await dupe_resp.json() + assert dupe_reg_json == {"success": True} + await hass.async_block_till_done() - dupe_json = await dupe_resp.json() - assert dupe_json["success"] is False - assert dupe_json["error"]["code"] == "duplicate_unique_id" + assert "Re-register existing sensor" in caplog.text + + entity = hass.states.get("sensor.test_1_battery_state") + assert entity is not None + + assert entity.attributes["device_class"] == "battery" + assert entity.attributes["icon"] == "mdi:battery" + assert entity.attributes["unit_of_measurement"] == UNIT_PERCENTAGE + assert entity.attributes["foo"] == "bar" + assert entity.domain == "sensor" + assert entity.name == "Test 1 Battery State" + assert entity.state == "99" async def test_register_sensor_no_state(hass, create_registrations, webhook_client): From 111a00aeebfe43332a9076809d6a5bc92c2dc17a Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 8 Jun 2020 12:28:53 -0700 Subject: [PATCH 398/406] Bumped version to 0.111.0b4 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 113c6e59488..e97198a9001 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 111 -PATCH_VERSION = "0b3" +PATCH_VERSION = "0b4" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 0) From 5a9a95abe48f3a6e1eab84e3d025ee6bef8c3f1d Mon Sep 17 00:00:00 2001 From: Donnie Date: Tue, 9 Jun 2020 11:26:37 -0700 Subject: [PATCH 399/406] Fix nanoleaf incorrect effect update (#36517) --- homeassistant/components/nanoleaf/light.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/nanoleaf/light.py b/homeassistant/components/nanoleaf/light.py index 5073a421e49..a7bf75a15d2 100644 --- a/homeassistant/components/nanoleaf/light.py +++ b/homeassistant/components/nanoleaf/light.py @@ -210,6 +210,10 @@ class NanoleafLight(LightEntity): self._light.brightness = int(brightness / 2.55) if effect: + if effect not in self._effects_list: + raise ValueError( + f"Attempting to apply effect not in the effect list: '{effect}'" + ) self._light.effect = effect def turn_off(self, **kwargs): @@ -227,8 +231,13 @@ class NanoleafLight(LightEntity): self._available = self._light.available self._brightness = self._light.brightness self._color_temp = self._light.color_temperature - self._effect = self._light.effect self._effects_list = self._light.effects + # Nanoleaf api returns non-existent effect named "*Solid*" when light set to solid color. + # This causes various issues with scening (see https://github.com/home-assistant/core/issues/36359). + # Until fixed at the library level, we should ensure the effect exists before saving to light properties + self._effect = ( + self._light.effect if self._light.effect in self._effects_list else None + ) self._hs_color = self._light.hue, self._light.saturation self._state = self._light.on except Unavailable as err: From fb7af0384f37afab03a10ec329ac95b726a3501a Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 8 Jun 2020 23:05:55 +0200 Subject: [PATCH 400/406] bump aiokef to 0.2.10 (#36574) 0.2.9 generated a lot of calls on the event loop. --- homeassistant/components/kef/manifest.json | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/kef/manifest.json b/homeassistant/components/kef/manifest.json index bb68d37707f..1eb9a9e19c2 100644 --- a/homeassistant/components/kef/manifest.json +++ b/homeassistant/components/kef/manifest.json @@ -3,5 +3,5 @@ "name": "KEF", "documentation": "https://www.home-assistant.io/integrations/kef", "codeowners": ["@basnijholt"], - "requirements": ["aiokef==0.2.9", "getmac==0.8.2"] + "requirements": ["aiokef==0.2.10", "getmac==0.8.2"] } diff --git a/requirements_all.txt b/requirements_all.txt index 8234ee4cd54..c90ac8a5130 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -197,7 +197,7 @@ aioimaplib==0.7.15 aiokafka==0.5.1 # homeassistant.components.kef -aiokef==0.2.9 +aiokef==0.2.10 # homeassistant.components.lifx aiolifx==0.6.7 From 94c3d9bac0ae3c490d3e2e13da3768fcbf75c549 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 9 Jun 2020 01:23:26 -0700 Subject: [PATCH 401/406] Fix default for loading games file ps4 (#36592) --- homeassistant/components/ps4/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/ps4/__init__.py b/homeassistant/components/ps4/__init__.py index 2a7a667088e..390637c26a3 100644 --- a/homeassistant/components/ps4/__init__.py +++ b/homeassistant/components/ps4/__init__.py @@ -161,7 +161,7 @@ def load_games(hass: HomeAssistantType, unique_id: str) -> dict: """Load games for sources.""" g_file = hass.config.path(GAMES_FILE.format(unique_id)) try: - games = load_json(g_file, dict) + games = load_json(g_file) except HomeAssistantError as error: games = {} _LOGGER.error("Failed to load games file: %s", error) From a69938afa2f46717f3807aa58acfe5536d3faf40 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 9 Jun 2020 12:39:29 -0700 Subject: [PATCH 402/406] Bumped version to 0.111.0b5 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index e97198a9001..031b122f507 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 111 -PATCH_VERSION = "0b4" +PATCH_VERSION = "0b5" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 0) From d7ad974244cf25c5af73c9af524c3e24ef6d831f Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 10 Jun 2020 01:09:34 +0200 Subject: [PATCH 403/406] Escape <> in owntracks translations (#36612) --- homeassistant/components/owntracks/strings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/owntracks/strings.json b/homeassistant/components/owntracks/strings.json index b82761461ec..12aba21be72 100644 --- a/homeassistant/components/owntracks/strings.json +++ b/homeassistant/components/owntracks/strings.json @@ -8,7 +8,7 @@ }, "abort": { "one_instance_allowed": "Only a single instance is necessary." }, "create_entry": { - "default": "\n\nOn Android, open [the OwnTracks app]({android_url}), go to preferences -> connection. Change the following settings:\n - Mode: Private HTTP\n - Host: {webhook_url}\n - Identification:\n - Username: ``\n - Device ID: ``\n\nOn iOS, open [the OwnTracks app]({ios_url}), tap (i) icon in top left -> settings. Change the following settings:\n - Mode: HTTP\n - URL: {webhook_url}\n - Turn on authentication\n - UserID: ``\n\n{secret}\n\nSee [the documentation]({docs_url}) for more information." + "default": "\n\nOn Android, open [the OwnTracks app]({android_url}), go to preferences -> connection. Change the following settings:\n - Mode: Private HTTP\n - Host: {webhook_url}\n - Identification:\n - Username: `''`\n - Device ID: `''`\n\nOn iOS, open [the OwnTracks app]({ios_url}), tap (i) icon in top left -> settings. Change the following settings:\n - Mode: HTTP\n - URL: {webhook_url}\n - Turn on authentication\n - UserID: `''`\n\n{secret}\n\nSee [the documentation]({docs_url}) for more information." } } } From 01756011ff12e7ef0a52354d0180b4630309ff72 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 9 Jun 2020 16:40:08 -0700 Subject: [PATCH 404/406] Bump hass-nabucasa to 0.34.6 (#36613) --- 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 5fb12bbb102..b72aec18c34 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.5"], + "requirements": ["hass-nabucasa==0.34.6"], "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 5178df84da2..1f971ecda57 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -11,7 +11,7 @@ ciso8601==2.1.3 cryptography==2.9.2 defusedxml==0.6.0 distro==1.5.0 -hass-nabucasa==0.34.5 +hass-nabucasa==0.34.6 home-assistant-frontend==20200603.2 importlib-metadata==1.6.0 jinja2>=2.11.1 diff --git a/requirements_all.txt b/requirements_all.txt index c90ac8a5130..49c704d7b8f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -704,7 +704,7 @@ habitipy==0.2.0 hangups==0.4.9 # homeassistant.components.cloud -hass-nabucasa==0.34.5 +hass-nabucasa==0.34.6 # homeassistant.components.mqtt hbmqtt==0.9.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a113578bc65..420533f0e1f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -303,7 +303,7 @@ ha-ffmpeg==2.0 hangups==0.4.9 # homeassistant.components.cloud -hass-nabucasa==0.34.5 +hass-nabucasa==0.34.6 # homeassistant.components.mqtt hbmqtt==0.9.5 From d6f7a984b28b1ac70beed10f4aba28082c37370c Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Wed, 10 Jun 2020 03:34:16 +0200 Subject: [PATCH 405/406] Bump coronavirus to 1.1.1 (#36614) --- homeassistant/components/coronavirus/manifest.json | 8 ++++++-- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/coronavirus/manifest.json b/homeassistant/components/coronavirus/manifest.json index 5248cf38221..ae5083a5f98 100644 --- a/homeassistant/components/coronavirus/manifest.json +++ b/homeassistant/components/coronavirus/manifest.json @@ -3,6 +3,10 @@ "name": "Coronavirus (COVID-19)", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/coronavirus", - "requirements": ["coronavirus==1.1.0"], - "codeowners": ["@home_assistant/core"] + "requirements": [ + "coronavirus==1.1.1" + ], + "codeowners": [ + "@home_assistant/core" + ] } diff --git a/requirements_all.txt b/requirements_all.txt index 49c704d7b8f..728e70634cf 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -442,7 +442,7 @@ connect-box==0.2.5 construct==2.9.45 # homeassistant.components.coronavirus -coronavirus==1.1.0 +coronavirus==1.1.1 # homeassistant.scripts.credstash # credstash==1.15.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 420533f0e1f..c576dd99695 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -191,7 +191,7 @@ colorlog==4.1.0 construct==2.9.45 # homeassistant.components.coronavirus -coronavirus==1.1.0 +coronavirus==1.1.1 # homeassistant.scripts.credstash # credstash==1.15.0 From 1bbd05dee78349942d4f35dd920f5864f12a2458 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 10 Jun 2020 14:58:56 +0200 Subject: [PATCH 406/406] Bumped version to 0.111.0 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 031b122f507..67ad32d392b 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 111 -PATCH_VERSION = "0b5" +PATCH_VERSION = "0" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 7, 0)