From eb91941640808e36655ec368213adcb5d030c19d Mon Sep 17 00:00:00 2001 From: Penny Wood Date: Mon, 20 Dec 2021 14:25:01 +0800 Subject: [PATCH 01/28] Update version of iZone library to add some bug fixes (#61548) --- homeassistant/components/izone/climate.py | 5 ----- homeassistant/components/izone/manifest.json | 4 ++-- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/izone/climate.py b/homeassistant/components/izone/climate.py index db47b087c0d..3aead1c2176 100644 --- a/homeassistant/components/izone/climate.py +++ b/homeassistant/components/izone/climate.py @@ -511,11 +511,6 @@ class ZoneDevice(ClimateEntity): """Return True if entity is available.""" return self._controller.available - @property - def assumed_state(self) -> bool: - """Return True if unable to access real state of the entity.""" - return self._controller.assumed_state - @property def unique_id(self): """Return the ID of the controller device.""" diff --git a/homeassistant/components/izone/manifest.json b/homeassistant/components/izone/manifest.json index 82927fef795..9cdf30ad42b 100644 --- a/homeassistant/components/izone/manifest.json +++ b/homeassistant/components/izone/manifest.json @@ -2,11 +2,11 @@ "domain": "izone", "name": "iZone", "documentation": "https://www.home-assistant.io/integrations/izone", - "requirements": ["python-izone==1.1.8"], + "requirements": ["python-izone==1.2.3"], "codeowners": ["@Swamp-Ig"], "config_flow": true, "homekit": { "models": ["iZone"] }, - "iot_class": "local_push" + "iot_class": "local_polling" } diff --git a/requirements_all.txt b/requirements_all.txt index 47d2b09b186..10232f9496e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1890,7 +1890,7 @@ python-gitlab==1.6.0 python-hpilo==4.3 # homeassistant.components.izone -python-izone==1.1.8 +python-izone==1.2.3 # homeassistant.components.joaoapps_join python-join-api==0.0.6 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 003ece83507..0465c1aebec 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1136,7 +1136,7 @@ python-ecobee-api==0.2.14 python-forecastio==1.4.0 # homeassistant.components.izone -python-izone==1.1.8 +python-izone==1.2.3 # homeassistant.components.juicenet python-juicenet==1.0.2 From 69b1a93793a72358bf239b08181c024d71fefafb Mon Sep 17 00:00:00 2001 From: dougiteixeira <31328123+dougiteixeira@users.noreply.github.com> Date: Sun, 2 Jan 2022 16:46:18 -0300 Subject: [PATCH 02/28] Fix Tuya vacuum display battery level (#61643) Co-authored-by: Franck Nijhof --- homeassistant/components/tuya/vacuum.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/tuya/vacuum.py b/homeassistant/components/tuya/vacuum.py index 2afeb1880f7..4fc90b88ac0 100644 --- a/homeassistant/components/tuya/vacuum.py +++ b/homeassistant/components/tuya/vacuum.py @@ -112,9 +112,9 @@ class TuyaVacuumEntity(TuyaEntity, StateVacuumEntity): self._supported_features |= SUPPORT_FAN_SPEED self._fan_speed_type = EnumTypeData.from_json(function.values) - if function := device.function.get(DPCode.ELECTRICITY_LEFT): + if status_range := device.status_range.get(DPCode.ELECTRICITY_LEFT): self._supported_features |= SUPPORT_BATTERY - self._battery_level_type = IntegerTypeData.from_json(function.values) + self._battery_level_type = IntegerTypeData.from_json(status_range.values) @property def battery_level(self) -> int | None: From e3d2993d98302796c5d651e9cd7a7f7d5ca38090 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Tue, 4 Jan 2022 13:42:54 +0100 Subject: [PATCH 03/28] Update no_ip URL (#62477) --- homeassistant/components/no_ip/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/no_ip/__init__.py b/homeassistant/components/no_ip/__init__.py index 1c4bcb40819..245222724ab 100644 --- a/homeassistant/components/no_ip/__init__.py +++ b/homeassistant/components/no_ip/__init__.py @@ -35,7 +35,7 @@ NO_IP_ERRORS = { "911": "A fatal error on NO-IP's side such as a database outage", } -UPDATE_URL = "https://dynupdate.noip.com/nic/update" +UPDATE_URL = "https://dynupdate.no-ip.com/nic/update" HA_USER_AGENT = f"{SERVER_SOFTWARE} {EMAIL}" CONFIG_SCHEMA = vol.Schema( From 76d5e2ea90eb60752712f658e5a69b0d201823ca Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Tue, 4 Jan 2022 22:43:22 +0100 Subject: [PATCH 04/28] Do not create a number LED brightness entity for Xiaomi Miio devices that do not support it (#62819) Co-authored-by: Paulus Schoutsen --- homeassistant/components/xiaomi_miio/number.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/homeassistant/components/xiaomi_miio/number.py b/homeassistant/components/xiaomi_miio/number.py index 2823c6c2582..94b3412d44a 100644 --- a/homeassistant/components/xiaomi_miio/number.py +++ b/homeassistant/components/xiaomi_miio/number.py @@ -4,6 +4,7 @@ from __future__ import annotations from dataclasses import dataclass from homeassistant.components.number import NumberEntity, NumberEntityDescription +from homeassistant.components.number.const import DOMAIN as PLATFORM_DOMAIN from homeassistant.const import DEGREE, ENTITY_CATEGORY_CONFIG, TIME_MINUTES from homeassistant.core import callback @@ -248,6 +249,15 @@ async def async_setup_entry(hass, config_entry, async_add_entities): return for feature, description in NUMBER_TYPES.items(): + if feature == FEATURE_SET_LED_BRIGHTNESS and model != MODEL_FAN_ZA5: + # Delete LED bightness entity created by mistake if it exists + entity_reg = hass.helpers.entity_registry.async_get() + entity_id = entity_reg.async_get_entity_id( + PLATFORM_DOMAIN, DOMAIN, f"{description.key}_{config_entry.unique_id}" + ) + if entity_id: + entity_reg.async_remove(entity_id) + continue if feature & features: if ( description.key == ATTR_OSCILLATION_ANGLE From b78e44d1d190c69f8e9966314b34363e10f580ff Mon Sep 17 00:00:00 2001 From: Marvin Wichmann Date: Thu, 30 Dec 2021 01:13:58 +0100 Subject: [PATCH 05/28] Fix local_ip handling in KNX options flow (#62969) --- homeassistant/components/knx/config_flow.py | 21 +++-- homeassistant/components/knx/strings.json | 6 +- .../components/knx/translations/en.json | 12 ++- tests/components/knx/test_config_flow.py | 77 ++++++++++++++----- 4 files changed, 84 insertions(+), 32 deletions(-) diff --git a/homeassistant/components/knx/config_flow.py b/homeassistant/components/knx/config_flow.py index 96aa8f67e3b..01e71eb37af 100644 --- a/homeassistant/components/knx/config_flow.py +++ b/homeassistant/components/knx/config_flow.py @@ -28,6 +28,7 @@ from .schema import ConnectionSchema CONF_KNX_GATEWAY: Final = "gateway" CONF_MAX_RATE_LIMIT: Final = 60 +CONF_DEFAULT_LOCAL_IP: Final = "0.0.0.0" DEFAULT_ENTRY_DATA: Final = { ConnectionSchema.CONF_KNX_STATE_UPDATER: ConnectionSchema.CONF_KNX_DEFAULT_STATE_UPDATER, @@ -328,6 +329,12 @@ class KNXOptionsFlowHandler(OptionsFlow): entry_data = { **DEFAULT_ENTRY_DATA, **self.general_settings, + ConnectionSchema.CONF_KNX_LOCAL_IP: self.general_settings.get( + ConnectionSchema.CONF_KNX_LOCAL_IP + ) + if self.general_settings.get(ConnectionSchema.CONF_KNX_LOCAL_IP) + != CONF_DEFAULT_LOCAL_IP + else None, CONF_HOST: self.current_config.get(CONF_HOST, ""), } @@ -337,7 +344,7 @@ class KNXOptionsFlowHandler(OptionsFlow): **user_input, } - entry_title = entry_data[CONF_KNX_CONNECTION_TYPE].capitalize() + entry_title = str(entry_data[CONF_KNX_CONNECTION_TYPE]).capitalize() if entry_data[CONF_KNX_CONNECTION_TYPE] == CONF_KNX_TUNNELING: entry_title = f"{CONF_KNX_TUNNELING.capitalize()} @ {entry_data[CONF_HOST]}" @@ -388,12 +395,16 @@ class KNXOptionsFlowHandler(OptionsFlow): } if self.show_advanced_options: + local_ip = ( + self.current_config.get(ConnectionSchema.CONF_KNX_LOCAL_IP) + if self.current_config.get(ConnectionSchema.CONF_KNX_LOCAL_IP) + is not None + else CONF_DEFAULT_LOCAL_IP + ) data_schema[ - vol.Optional( + vol.Required( ConnectionSchema.CONF_KNX_LOCAL_IP, - default=self.current_config.get( - ConnectionSchema.CONF_KNX_LOCAL_IP, - ), + default=local_ip, ) ] = str data_schema[ diff --git a/homeassistant/components/knx/strings.json b/homeassistant/components/knx/strings.json index 4db92888aab..d219880be5c 100644 --- a/homeassistant/components/knx/strings.json +++ b/homeassistant/components/knx/strings.json @@ -20,7 +20,7 @@ "host": "[%key:common::config_flow::data::host%]", "individual_address": "Individual address for the connection", "route_back": "Route Back / NAT Mode", - "local_ip": "Local IP (leave empty if unsure)" + "local_ip": "Local IP of Home Assistant (leave empty for automatic detection)" } }, "routing": { @@ -29,7 +29,7 @@ "individual_address": "Individual address for the routing connection", "multicast_group": "The multicast group used for routing", "multicast_port": "The multicast port used for routing", - "local_ip": "Local IP (leave empty if unsure)" + "local_ip": "Local IP of Home Assistant (leave empty for automatic detection)" } } }, @@ -49,7 +49,7 @@ "individual_address": "Default individual address", "multicast_group": "Multicast group used for routing and discovery", "multicast_port": "Multicast port used for routing and discovery", - "local_ip": "Local IP (leave empty if unsure)", + "local_ip": "Local IP of Home Assistant (use 0.0.0.0 for automatic detection)", "state_updater": "Globally enable reading states from the KNX Bus", "rate_limit": "Maximum outgoing telegrams per second" } diff --git a/homeassistant/components/knx/translations/en.json b/homeassistant/components/knx/translations/en.json index 91b9dfce5f3..43aeced6698 100644 --- a/homeassistant/components/knx/translations/en.json +++ b/homeassistant/components/knx/translations/en.json @@ -12,7 +12,7 @@ "data": { "host": "Host", "individual_address": "Individual address for the connection", - "local_ip": "Local IP (leave empty if unsure)", + "local_ip": "Local IP of Home Assistant (leave empty for automatic detection)", "port": "Port", "route_back": "Route Back / NAT Mode" }, @@ -21,9 +21,9 @@ "routing": { "data": { "individual_address": "Individual address for the routing connection", + "local_ip": "Local IP of Home Assistant (leave empty for automatic detection)", "multicast_group": "The multicast group used for routing", - "multicast_port": "The multicast port used for routing", - "local_ip": "Local IP (leave empty if unsure)" + "multicast_port": "The multicast port used for routing" }, "description": "Please configure the routing options." }, @@ -47,6 +47,10 @@ "data": { "connection_type": "KNX Connection Type", "individual_address": "Default individual address", +<<<<<<< HEAD +======= + "local_ip": "Local IP of Home Assistant (use 0.0.0.0 for automatic detection)", +>>>>>>> b9247f3952 (Fix local_ip handling in KNX options flow (#62969)) "multicast_group": "Multicast group used for routing and discovery", "multicast_port": "Multicast port used for routing and discovery", "local_ip": "Local IP (leave empty if unsure)", @@ -63,4 +67,4 @@ } } } -} \ No newline at end of file +} diff --git a/tests/components/knx/test_config_flow.py b/tests/components/knx/test_config_flow.py index 4f3e1734b69..aec757a1086 100644 --- a/tests/components/knx/test_config_flow.py +++ b/tests/components/knx/test_config_flow.py @@ -1,6 +1,7 @@ """Test the KNX config flow.""" from unittest.mock import patch +import pytest from xknx import XKNX from xknx.io import DEFAULT_MCAST_GRP from xknx.io.gateway_scanner import GatewayDescriptor @@ -8,6 +9,7 @@ from xknx.io.gateway_scanner import GatewayDescriptor from homeassistant import config_entries from homeassistant.components.knx import ConnectionSchema from homeassistant.components.knx.config_flow import ( + CONF_DEFAULT_LOCAL_IP, CONF_KNX_GATEWAY, DEFAULT_ENTRY_DATA, ) @@ -585,6 +587,7 @@ async def test_options_flow( CONF_KNX_CONNECTION_TYPE: CONF_KNX_AUTOMATIC, CONF_KNX_INDIVIDUAL_ADDRESS: "15.15.255", CONF_HOST: "", + ConnectionSchema.CONF_KNX_LOCAL_IP: None, ConnectionSchema.CONF_KNX_MCAST_PORT: 3675, ConnectionSchema.CONF_KNX_MCAST_GRP: DEFAULT_MCAST_GRP, ConnectionSchema.CONF_KNX_RATE_LIMIT: 20, @@ -643,14 +646,65 @@ async def test_tunneling_options_flow( ConnectionSchema.CONF_KNX_MCAST_GRP: DEFAULT_MCAST_GRP, ConnectionSchema.CONF_KNX_RATE_LIMIT: 20, ConnectionSchema.CONF_KNX_STATE_UPDATER: True, + ConnectionSchema.CONF_KNX_LOCAL_IP: None, CONF_HOST: "192.168.1.1", CONF_PORT: 3675, ConnectionSchema.CONF_KNX_ROUTE_BACK: True, } +@pytest.mark.parametrize( + "user_input,config_entry_data", + [ + ( + { + CONF_KNX_CONNECTION_TYPE: CONF_KNX_AUTOMATIC, + CONF_KNX_INDIVIDUAL_ADDRESS: "15.15.250", + ConnectionSchema.CONF_KNX_MCAST_PORT: 3675, + ConnectionSchema.CONF_KNX_MCAST_GRP: DEFAULT_MCAST_GRP, + ConnectionSchema.CONF_KNX_RATE_LIMIT: 25, + ConnectionSchema.CONF_KNX_STATE_UPDATER: False, + ConnectionSchema.CONF_KNX_LOCAL_IP: "192.168.1.112", + }, + { + CONF_KNX_CONNECTION_TYPE: CONF_KNX_AUTOMATIC, + CONF_KNX_INDIVIDUAL_ADDRESS: "15.15.250", + CONF_HOST: "", + ConnectionSchema.CONF_KNX_MCAST_PORT: 3675, + ConnectionSchema.CONF_KNX_MCAST_GRP: DEFAULT_MCAST_GRP, + ConnectionSchema.CONF_KNX_RATE_LIMIT: 25, + ConnectionSchema.CONF_KNX_STATE_UPDATER: False, + ConnectionSchema.CONF_KNX_LOCAL_IP: "192.168.1.112", + }, + ), + ( + { + CONF_KNX_CONNECTION_TYPE: CONF_KNX_AUTOMATIC, + CONF_KNX_INDIVIDUAL_ADDRESS: "15.15.250", + ConnectionSchema.CONF_KNX_MCAST_PORT: 3675, + ConnectionSchema.CONF_KNX_MCAST_GRP: DEFAULT_MCAST_GRP, + ConnectionSchema.CONF_KNX_RATE_LIMIT: 25, + ConnectionSchema.CONF_KNX_STATE_UPDATER: False, + ConnectionSchema.CONF_KNX_LOCAL_IP: CONF_DEFAULT_LOCAL_IP, + }, + { + CONF_KNX_CONNECTION_TYPE: CONF_KNX_AUTOMATIC, + CONF_KNX_INDIVIDUAL_ADDRESS: "15.15.250", + CONF_HOST: "", + ConnectionSchema.CONF_KNX_MCAST_PORT: 3675, + ConnectionSchema.CONF_KNX_MCAST_GRP: DEFAULT_MCAST_GRP, + ConnectionSchema.CONF_KNX_RATE_LIMIT: 25, + ConnectionSchema.CONF_KNX_STATE_UPDATER: False, + ConnectionSchema.CONF_KNX_LOCAL_IP: None, + }, + ), + ], +) async def test_advanced_options( - hass: HomeAssistant, mock_config_entry: MockConfigEntry + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + user_input, + config_entry_data, ) -> None: """Test options config flow.""" mock_config_entry.add_to_hass(hass) @@ -668,28 +722,11 @@ async def test_advanced_options( result2 = await hass.config_entries.options.async_configure( result["flow_id"], - user_input={ - CONF_KNX_CONNECTION_TYPE: CONF_KNX_AUTOMATIC, - CONF_KNX_INDIVIDUAL_ADDRESS: "15.15.250", - ConnectionSchema.CONF_KNX_MCAST_PORT: 3675, - ConnectionSchema.CONF_KNX_MCAST_GRP: DEFAULT_MCAST_GRP, - ConnectionSchema.CONF_KNX_RATE_LIMIT: 25, - ConnectionSchema.CONF_KNX_STATE_UPDATER: False, - ConnectionSchema.CONF_KNX_LOCAL_IP: "192.168.1.112", - }, + user_input=user_input, ) await hass.async_block_till_done() assert result2.get("type") == RESULT_TYPE_CREATE_ENTRY assert not result2.get("data") - assert mock_config_entry.data == { - CONF_KNX_CONNECTION_TYPE: CONF_KNX_AUTOMATIC, - CONF_KNX_INDIVIDUAL_ADDRESS: "15.15.250", - CONF_HOST: "", - ConnectionSchema.CONF_KNX_MCAST_PORT: 3675, - ConnectionSchema.CONF_KNX_MCAST_GRP: DEFAULT_MCAST_GRP, - ConnectionSchema.CONF_KNX_RATE_LIMIT: 25, - ConnectionSchema.CONF_KNX_STATE_UPDATER: False, - ConnectionSchema.CONF_KNX_LOCAL_IP: "192.168.1.112", - } + assert mock_config_entry.data == config_entry_data From 1f81f84b6eb904a8b21a645bc61d4958248e05c6 Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Wed, 29 Dec 2021 18:26:52 +0100 Subject: [PATCH 06/28] Fix reporting correct colormode for 3rd party Hue lights (#63015) --- homeassistant/components/hue/v2/group.py | 2 - homeassistant/components/hue/v2/light.py | 60 +++++++++++++++++++----- 2 files changed, 48 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/hue/v2/group.py b/homeassistant/components/hue/v2/group.py index 7ef91f684fe..775c2dd1d76 100644 --- a/homeassistant/components/hue/v2/group.py +++ b/homeassistant/components/hue/v2/group.py @@ -7,7 +7,6 @@ from typing import Any from aiohue.v2 import HueBridgeV2 from aiohue.v2.controllers.events import EventType from aiohue.v2.controllers.groups import GroupedLight, Room, Zone -from aiohue.v2.models.feature import AlertEffectType from homeassistant.components.light import ( ATTR_BRIGHTNESS, @@ -193,7 +192,6 @@ class GroupedHueLight(HueBaseEntity, LightEntity): color_xy=xy_color if light.supports_color else None, color_temp=color_temp if light.supports_color_temperature else None, transition_time=transition, - alert=AlertEffectType.BREATHE if flash is not None else None, allowed_errors=ALLOWED_ERRORS, ) for light in self.controller.get_lights(self.resource.id) diff --git a/homeassistant/components/hue/v2/light.py b/homeassistant/components/hue/v2/light.py index 42444fd9ad0..ee40222b083 100644 --- a/homeassistant/components/hue/v2/light.py +++ b/homeassistant/components/hue/v2/light.py @@ -91,6 +91,9 @@ class HueLight(HueBaseEntity, LightEntity): self._supported_color_modes.add(COLOR_MODE_BRIGHTNESS) # support transition if brightness control self._attr_supported_features |= SUPPORT_TRANSITION + self._last_xy: tuple[float, float] | None = self.xy_color + self._last_color_temp: int = self.color_temp + self._set_color_mode() @property def brightness(self) -> int | None: @@ -100,18 +103,6 @@ class HueLight(HueBaseEntity, LightEntity): return round((dimming.brightness / 100) * 255) return None - @property - def color_mode(self) -> str: - """Return the current color mode of the light.""" - if color_temp := self.resource.color_temperature: - if color_temp.mirek_valid and color_temp.mirek is not None: - return COLOR_MODE_COLOR_TEMP - if self.resource.supports_color: - return COLOR_MODE_XY - if self.resource.supports_dimming: - return COLOR_MODE_BRIGHTNESS - return COLOR_MODE_ONOFF - @property def is_on(self) -> bool: """Return true if device is on (brightness above 0).""" @@ -158,6 +149,11 @@ class HueLight(HueBaseEntity, LightEntity): "dynamics": self.resource.dynamics.status.value, } + @callback + def on_update(self) -> None: + """Call on update event.""" + self._set_color_mode() + async def async_turn_on(self, **kwargs: Any) -> None: """Turn the device on.""" transition = normalize_hue_transition(kwargs.get(ATTR_TRANSITION)) @@ -212,3 +208,43 @@ class HueLight(HueBaseEntity, LightEntity): id=self.resource.id, short=flash == FLASH_SHORT, ) + + @callback + def _set_color_mode(self) -> None: + """Set current colormode of light.""" + last_xy = self._last_xy + last_color_temp = self._last_color_temp + self._last_xy = self.xy_color + self._last_color_temp = self.color_temp + + # Certified Hue lights return `mired_valid` to indicate CT is active + if color_temp := self.resource.color_temperature: + if color_temp.mirek_valid and color_temp.mirek is not None: + self._attr_color_mode = COLOR_MODE_COLOR_TEMP + return + + # Non-certified lights do not report their current color mode correctly + # so we keep track of the color values to determine which is active + if last_color_temp != self.color_temp: + self._attr_color_mode = COLOR_MODE_COLOR_TEMP + return + if last_xy != self.xy_color: + self._attr_color_mode = COLOR_MODE_XY + return + + # if we didn't detect any changes, abort and use previous values + if self._attr_color_mode is not None: + return + + # color mode not yet determined, work it out here + # Note that for lights that do not correctly report `mirek_valid` + # we might have an invalid startup state which will be auto corrected + if self.resource.supports_color: + self._attr_color_mode = COLOR_MODE_XY + elif self.resource.supports_color_temperature: + self._attr_color_mode = COLOR_MODE_COLOR_TEMP + elif self.resource.supports_dimming: + self._attr_color_mode = COLOR_MODE_BRIGHTNESS + else: + # fallback to on_off + self._attr_color_mode = COLOR_MODE_ONOFF From 5e42f95bc668a8a771e39a7a84b0c4bf652e6447 Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Fri, 31 Dec 2021 05:46:52 +0100 Subject: [PATCH 07/28] Hue allow per-device availability override (#63025) Co-authored-by: Paulus Schoutsen --- homeassistant/components/hue/config_flow.py | 66 +++++++++++++++---- homeassistant/components/hue/const.py | 1 + homeassistant/components/hue/strings.json | 3 +- .../components/hue/translations/en.json | 3 +- .../components/hue/translations/nl.json | 3 +- homeassistant/components/hue/v2/entity.py | 62 ++++++++++------- homeassistant/components/hue/v2/group.py | 2 +- tests/components/hue/test_config_flow.py | 26 +++++++- 8 files changed, 122 insertions(+), 44 deletions(-) diff --git a/homeassistant/components/hue/config_flow.py b/homeassistant/components/hue/config_flow.py index 49fca2158d5..987afe17012 100644 --- a/homeassistant/components/hue/config_flow.py +++ b/homeassistant/components/hue/config_flow.py @@ -17,13 +17,15 @@ from homeassistant.components import ssdp, zeroconf from homeassistant.const import CONF_API_KEY, CONF_HOST from homeassistant.core import callback from homeassistant.data_entry_flow import FlowResult -from homeassistant.helpers import aiohttp_client +from homeassistant.helpers import aiohttp_client, device_registry +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.typing import ConfigType from .const import ( CONF_ALLOW_HUE_GROUPS, CONF_ALLOW_UNREACHABLE, CONF_API_VERSION, + CONF_IGNORE_AVAILABILITY, DEFAULT_ALLOW_HUE_GROUPS, DEFAULT_ALLOW_UNREACHABLE, DOMAIN, @@ -46,17 +48,11 @@ class HueFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): @callback def async_get_options_flow( config_entry: config_entries.ConfigEntry, - ) -> HueOptionsFlowHandler: + ) -> HueV1OptionsFlowHandler | HueV2OptionsFlowHandler: """Get the options flow for this handler.""" - return HueOptionsFlowHandler(config_entry) - - @classmethod - @callback - def async_supports_options_flow( - cls, config_entry: config_entries.ConfigEntry - ) -> bool: - """Return options flow support for this handler.""" - return config_entry.data.get(CONF_API_VERSION, 1) == 1 + if config_entry.data.get(CONF_API_VERSION, 1) == 1: + return HueV1OptionsFlowHandler(config_entry) + return HueV2OptionsFlowHandler(config_entry) def __init__(self) -> None: """Initialize the Hue flow.""" @@ -288,8 +284,8 @@ class HueFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): return await self.async_step_link() -class HueOptionsFlowHandler(config_entries.OptionsFlow): - """Handle Hue options.""" +class HueV1OptionsFlowHandler(config_entries.OptionsFlow): + """Handle Hue options for V1 implementation.""" def __init__(self, config_entry: config_entries.ConfigEntry) -> None: """Initialize Hue options flow.""" @@ -319,3 +315,47 @@ class HueOptionsFlowHandler(config_entries.OptionsFlow): } ), ) + + +class HueV2OptionsFlowHandler(config_entries.OptionsFlow): + """Handle Hue options for V2 implementation.""" + + def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + """Initialize Hue options flow.""" + self.config_entry = config_entry + + async def async_step_init(self, user_input: ConfigType | None = None) -> FlowResult: + """Manage Hue options.""" + if user_input is not None: + return self.async_create_entry(title="", data=user_input) + + # create a list of Hue device ID's that the user can select + # to ignore availability status + dev_reg = device_registry.async_get(self.hass) + entries = device_registry.async_entries_for_config_entry( + dev_reg, self.config_entry.entry_id + ) + dev_ids = { + identifier[1]: entry.name + for entry in entries + for identifier in entry.identifiers + if identifier[0] == DOMAIN + } + # filter any non existing device id's from the list + cur_ids = [ + item + for item in self.config_entry.options.get(CONF_IGNORE_AVAILABILITY, []) + if item in dev_ids + ] + + return self.async_show_form( + step_id="init", + data_schema=vol.Schema( + { + vol.Optional( + CONF_IGNORE_AVAILABILITY, + default=cur_ids, + ): cv.multi_select(dev_ids), + } + ), + ) diff --git a/homeassistant/components/hue/const.py b/homeassistant/components/hue/const.py index eef453fb83d..798148b92c0 100644 --- a/homeassistant/components/hue/const.py +++ b/homeassistant/components/hue/const.py @@ -3,6 +3,7 @@ DOMAIN = "hue" CONF_API_VERSION = "api_version" +CONF_IGNORE_AVAILABILITY = "ignore_availability" CONF_SUBTYPE = "subtype" diff --git a/homeassistant/components/hue/strings.json b/homeassistant/components/hue/strings.json index 458e21419ab..266f26016c4 100644 --- a/homeassistant/components/hue/strings.json +++ b/homeassistant/components/hue/strings.json @@ -70,7 +70,8 @@ "data": { "allow_hue_groups": "Allow Hue groups", "allow_hue_scenes": "Allow Hue scenes", - "allow_unreachable": "Allow unreachable bulbs to report their state correctly" + "allow_unreachable": "Allow unreachable bulbs to report their state correctly", + "ignore_availability": "Ignore connectivity status for the given devices" } } } diff --git a/homeassistant/components/hue/translations/en.json b/homeassistant/components/hue/translations/en.json index f0b8e560729..7757aca9373 100644 --- a/homeassistant/components/hue/translations/en.json +++ b/homeassistant/components/hue/translations/en.json @@ -69,7 +69,8 @@ "data": { "allow_hue_groups": "Allow Hue groups", "allow_hue_scenes": "Allow Hue scenes", - "allow_unreachable": "Allow unreachable bulbs to report their state correctly" + "allow_unreachable": "Allow unreachable bulbs to report their state correctly", + "ignore_availability": "Ignore connectivity status for the given devices" } } } diff --git a/homeassistant/components/hue/translations/nl.json b/homeassistant/components/hue/translations/nl.json index 12eeaf71af0..7997a03c4aa 100644 --- a/homeassistant/components/hue/translations/nl.json +++ b/homeassistant/components/hue/translations/nl.json @@ -69,7 +69,8 @@ "data": { "allow_hue_groups": "Sta Hue-groepen toe", "allow_hue_scenes": "Sta Hue sc\u00e8nes toe", - "allow_unreachable": "Onbereikbare lampen toestaan hun status correct te melden" + "allow_unreachable": "Onbereikbare lampen toestaan hun status correct te melden", + "ignore_availability": "Negeer beschikbaarheid status voor deze apparaten" } } } diff --git a/homeassistant/components/hue/v2/entity.py b/homeassistant/components/hue/v2/entity.py index 8253d4ffbef..70987fff2be 100644 --- a/homeassistant/components/hue/v2/entity.py +++ b/homeassistant/components/hue/v2/entity.py @@ -12,7 +12,7 @@ from homeassistant.helpers.entity import DeviceInfo, Entity from homeassistant.helpers.entity_registry import async_get as async_get_entity_registry from ..bridge import HueBridge -from ..const import DOMAIN +from ..const import CONF_IGNORE_AVAILABILITY, DOMAIN RESOURCE_TYPE_NAMES = { # a simple mapping of hue resource type to Hass name @@ -71,7 +71,7 @@ class HueBaseEntity(Entity): async def async_added_to_hass(self) -> None: """Call when entity is added.""" - self._check_availability_workaround() + self._check_availability() # Add value_changed callbacks. self.async_on_remove( self.controller.subscribe( @@ -80,7 +80,7 @@ class HueBaseEntity(Entity): (EventType.RESOURCE_UPDATED, EventType.RESOURCE_DELETED), ) ) - # also subscribe to device update event to catch devicer changes (e.g. name) + # also subscribe to device update event to catch device changes (e.g. name) if self.device is None: return self.async_on_remove( @@ -92,25 +92,27 @@ class HueBaseEntity(Entity): ) # subscribe to zigbee_connectivity to catch availability changes if zigbee := self.bridge.api.devices.get_zigbee_connectivity(self.device.id): - self.bridge.api.sensors.zigbee_connectivity.subscribe( - self._handle_event, - zigbee.id, - EventType.RESOURCE_UPDATED, + self.async_on_remove( + self.bridge.api.sensors.zigbee_connectivity.subscribe( + self._handle_event, + zigbee.id, + EventType.RESOURCE_UPDATED, + ) ) @property def available(self) -> bool: """Return entity availability.""" + # entities without a device attached should be always available if self.device is None: - # entities without a device attached should be always available return True + # the zigbee connectivity sensor itself should be always available if self.resource.type == ResourceTypes.ZIGBEE_CONNECTIVITY: - # the zigbee connectivity sensor itself should be always available return True if self._ignore_availability: return True + # all device-attached entities get availability from the zigbee connectivity if zigbee := self.bridge.api.devices.get_zigbee_connectivity(self.device.id): - # all device-attached entities get availability from the zigbee connectivity return zigbee.status == ConnectivityServiceStatus.CONNECTED return True @@ -130,30 +132,41 @@ class HueBaseEntity(Entity): ent_reg.async_remove(self.entity_id) else: self.logger.debug("Received status update for %s", self.entity_id) - self._check_availability_workaround() + self._check_availability() self.on_update() self.async_write_ha_state() @callback - def _check_availability_workaround(self): + def _check_availability(self): """Check availability of the device.""" - if self.resource.type != ResourceTypes.LIGHT: - return + # return if we already processed this entity if self._ignore_availability is not None: - # already processed return + # only do the availability check for entities connected to a device + if self.device is None: + return + # ignore availability if user added device to ignore list + if self.device.id in self.bridge.config_entry.options.get( + CONF_IGNORE_AVAILABILITY, [] + ): + self._ignore_availability = True + self.logger.info( + "Device %s is configured to ignore availability status. ", + self.name, + ) + return + # certified products (normally) report their state correctly + # no need for workaround/reporting if self.device.product_data.certified: - # certified products report their state correctly self._ignore_availability = False return # some (3th party) Hue lights report their connection status incorrectly # causing the zigbee availability to report as disconnected while in fact - # it can be controlled. Although this is in fact something the device manufacturer - # should fix, we work around it here. If the light is reported unavailable + # it can be controlled. If the light is reported unavailable # by the zigbee connectivity but the state changes its considered as a # malfunctioning device and we report it. - # while the user should actually fix this issue instead of ignoring it, we - # ignore the availability for this light from this point. + # While the user should actually fix this issue, we allow to + # ignore the availability for this light/device from the config options. cur_state = self.resource.on.on if self._last_state is None: self._last_state = cur_state @@ -166,9 +179,10 @@ class HueBaseEntity(Entity): # the device state changed from on->off or off->on # while it was reported as not connected! self.logger.warning( - "Light %s changed state while reported as disconnected. " - "This might be an indicator that routing is not working for this device. " - "Home Assistant will ignore availability for this light from now on. " + "Device %s changed state while reported as disconnected. " + "This might be an indicator that routing is not working for this device " + "or the device is having connectivity issues. " + "You can disable availability reporting for this device in the Hue options. " "Device details: %s - %s (%s) fw: %s", self.name, self.device.product_data.manufacturer_name, @@ -178,6 +192,4 @@ class HueBaseEntity(Entity): ) # do we want to store this in some persistent storage? self._ignore_availability = True - else: - self._ignore_availability = False self._last_state = cur_state diff --git a/homeassistant/components/hue/v2/group.py b/homeassistant/components/hue/v2/group.py index 775c2dd1d76..b8fdb0b0b1d 100644 --- a/homeassistant/components/hue/v2/group.py +++ b/homeassistant/components/hue/v2/group.py @@ -102,7 +102,7 @@ class GroupedHueLight(HueBaseEntity, LightEntity): # Entities for Hue groups are disabled by default # unless they were enabled in old version (legacy option) - self._attr_entity_registry_enabled_default = bridge.config_entry.data.get( + self._attr_entity_registry_enabled_default = bridge.config_entry.options.get( CONF_ALLOW_HUE_GROUPS, False ) diff --git a/tests/components/hue/test_config_flow.py b/tests/components/hue/test_config_flow.py index 6ce8ff3e1c4..0aa032ddb0d 100644 --- a/tests/components/hue/test_config_flow.py +++ b/tests/components/hue/test_config_flow.py @@ -11,6 +11,7 @@ from homeassistant import config_entries from homeassistant.components import ssdp, zeroconf from homeassistant.components.hue import config_flow, const from homeassistant.components.hue.errors import CannotConnect +from homeassistant.helpers import device_registry as dr from tests.common import MockConfigEntry @@ -701,12 +702,33 @@ async def test_options_flow_v2(hass): """Test options config flow for a V2 bridge.""" entry = MockConfigEntry( domain="hue", - unique_id="v2bridge", + unique_id="aabbccddeeff", data={"host": "0.0.0.0", "api_version": 2}, ) entry.add_to_hass(hass) - assert config_flow.HueFlowHandler.async_supports_options_flow(entry) is False + dev_reg = dr.async_get(hass) + mock_dev_id = "aabbccddee" + dev_reg.async_get_or_create( + config_entry_id=entry.entry_id, identifiers={(const.DOMAIN, mock_dev_id)} + ) + + result = await hass.config_entries.options.async_init(entry.entry_id) + + assert result["type"] == "form" + assert result["step_id"] == "init" + schema = result["data_schema"].schema + assert _get_schema_default(schema, const.CONF_IGNORE_AVAILABILITY) == [] + + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={const.CONF_IGNORE_AVAILABILITY: [mock_dev_id]}, + ) + + assert result["type"] == "create_entry" + assert result["data"] == { + const.CONF_IGNORE_AVAILABILITY: [mock_dev_id], + } async def test_bridge_zeroconf(hass, aioclient_mock): From 17dbdbb395a9e40792ddffffe7b839cc23109c64 Mon Sep 17 00:00:00 2001 From: Tobias Sauerwein Date: Mon, 20 Dec 2021 06:00:55 +0100 Subject: [PATCH 08/28] Bump pyatmo to 6.2.1 (#62291) --- homeassistant/components/netatmo/climate.py | 11 +++++------ homeassistant/components/netatmo/manifest.json | 2 +- homeassistant/components/netatmo/select.py | 12 ++++-------- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 5 files changed, 12 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/netatmo/climate.py b/homeassistant/components/netatmo/climate.py index 1ead9d7cbdb..c8b5e01e5db 100644 --- a/homeassistant/components/netatmo/climate.py +++ b/homeassistant/components/netatmo/climate.py @@ -136,14 +136,13 @@ async def async_setup_entry( for home_id in climate_topology.home_ids: signal_name = f"{CLIMATE_STATE_CLASS_NAME}-{home_id}" - try: - await data_handler.register_data_class( - CLIMATE_STATE_CLASS_NAME, signal_name, None, home_id=home_id - ) - except KeyError: + await data_handler.register_data_class( + CLIMATE_STATE_CLASS_NAME, signal_name, None, home_id=home_id + ) + + if (climate_state := data_handler.data[signal_name]) is None: continue - climate_state = data_handler.data[signal_name] climate_topology.register_handler(home_id, climate_state.process_topology) for room in climate_state.homes[home_id].rooms.values(): diff --git a/homeassistant/components/netatmo/manifest.json b/homeassistant/components/netatmo/manifest.json index 501d5142bcc..4ee7aeb0fde 100644 --- a/homeassistant/components/netatmo/manifest.json +++ b/homeassistant/components/netatmo/manifest.json @@ -3,7 +3,7 @@ "name": "Netatmo", "documentation": "https://www.home-assistant.io/integrations/netatmo", "requirements": [ - "pyatmo==6.2.0" + "pyatmo==6.2.1" ], "after_dependencies": [ "cloud", diff --git a/homeassistant/components/netatmo/select.py b/homeassistant/components/netatmo/select.py index 98576497f3e..4d69c9ab853 100644 --- a/homeassistant/components/netatmo/select.py +++ b/homeassistant/components/netatmo/select.py @@ -49,17 +49,13 @@ async def async_setup_entry( for home_id in climate_topology.home_ids: signal_name = f"{CLIMATE_STATE_CLASS_NAME}-{home_id}" - try: - await data_handler.register_data_class( - CLIMATE_STATE_CLASS_NAME, signal_name, None, home_id=home_id - ) - except KeyError: - continue - await data_handler.register_data_class( CLIMATE_STATE_CLASS_NAME, signal_name, None, home_id=home_id ) - climate_state = data_handler.data.get(signal_name) + + if (climate_state := data_handler.data[signal_name]) is None: + continue + climate_topology.register_handler(home_id, climate_state.process_topology) hass.data[DOMAIN][DATA_SCHEDULES][home_id] = climate_state.homes[ diff --git a/requirements_all.txt b/requirements_all.txt index 10232f9496e..ab797dfdf03 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1364,7 +1364,7 @@ pyarlo==0.2.4 pyatag==0.3.5.3 # homeassistant.components.netatmo -pyatmo==6.2.0 +pyatmo==6.2.1 # homeassistant.components.atome pyatome==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 0465c1aebec..7a34615a511 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -832,7 +832,7 @@ pyarlo==0.2.4 pyatag==0.3.5.3 # homeassistant.components.netatmo -pyatmo==6.2.0 +pyatmo==6.2.1 # homeassistant.components.apple_tv pyatv==0.8.2 From 30620ef48f91bdc1e780ae48e35be919b0bd13b6 Mon Sep 17 00:00:00 2001 From: Tobias Sauerwein Date: Thu, 30 Dec 2021 09:00:30 +0100 Subject: [PATCH 09/28] Bump pyatmo to v6.2.2 (#63053) Signed-off-by: cgtobi --- homeassistant/components/netatmo/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/netatmo/manifest.json b/homeassistant/components/netatmo/manifest.json index 4ee7aeb0fde..581a954df30 100644 --- a/homeassistant/components/netatmo/manifest.json +++ b/homeassistant/components/netatmo/manifest.json @@ -3,7 +3,7 @@ "name": "Netatmo", "documentation": "https://www.home-assistant.io/integrations/netatmo", "requirements": [ - "pyatmo==6.2.1" + "pyatmo==6.2.2" ], "after_dependencies": [ "cloud", diff --git a/requirements_all.txt b/requirements_all.txt index ab797dfdf03..537c5cc62f7 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1364,7 +1364,7 @@ pyarlo==0.2.4 pyatag==0.3.5.3 # homeassistant.components.netatmo -pyatmo==6.2.1 +pyatmo==6.2.2 # homeassistant.components.atome pyatome==0.1.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 7a34615a511..37189de291b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -832,7 +832,7 @@ pyarlo==0.2.4 pyatag==0.3.5.3 # homeassistant.components.netatmo -pyatmo==6.2.1 +pyatmo==6.2.2 # homeassistant.components.apple_tv pyatv==0.8.2 From 17e5766d2322f7ac5c46b4f1161d69a79e525987 Mon Sep 17 00:00:00 2001 From: ollo69 <60491700+ollo69@users.noreply.github.com> Date: Thu, 30 Dec 2021 10:25:44 +0100 Subject: [PATCH 10/28] Ignore serial number "blank" from NUT (#63066) --- homeassistant/components/nut/__init__.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/nut/__init__.py b/homeassistant/components/nut/__init__.py index 1a040b99f57..6de2640da9c 100644 --- a/homeassistant/components/nut/__init__.py +++ b/homeassistant/components/nut/__init__.py @@ -32,6 +32,8 @@ from .const import ( UNDO_UPDATE_LISTENER, ) +NUT_FAKE_SERIAL = ["unknown", "blank"] + _LOGGER = logging.getLogger(__name__) @@ -140,7 +142,9 @@ def _firmware_from_status(status): def _serial_from_status(status): """Find the best serialvalue from the status.""" serial = status.get("device.serial") or status.get("ups.serial") - if serial and (serial.lower() == "unknown" or serial.count("0") == len(serial)): + if serial and ( + serial.lower() in NUT_FAKE_SERIAL or serial.count("0") == len(serial) + ): return None return serial From c4d871a9d7d808e3abde4005264102b3f1729a16 Mon Sep 17 00:00:00 2001 From: Clifford Roche Date: Thu, 30 Dec 2021 15:15:59 -0500 Subject: [PATCH 11/28] Bump greeclimate to 1.0.1 (#63092) --- homeassistant/components/gree/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/gree/manifest.json b/homeassistant/components/gree/manifest.json index f4f8cf153a3..3af6f77236d 100644 --- a/homeassistant/components/gree/manifest.json +++ b/homeassistant/components/gree/manifest.json @@ -3,7 +3,7 @@ "name": "Gree Climate", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/gree", - "requirements": ["greeclimate==0.12.5"], + "requirements": ["greeclimate==1.0.1"], "codeowners": ["@cmroche"], "iot_class": "local_polling" } diff --git a/requirements_all.txt b/requirements_all.txt index 537c5cc62f7..53229f471ab 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -754,7 +754,7 @@ gpiozero==1.5.1 gps3==0.33.3 # homeassistant.components.gree -greeclimate==0.12.5 +greeclimate==1.0.1 # homeassistant.components.greeneye_monitor greeneye_monitor==2.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 37189de291b..e14b0e335b5 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -467,7 +467,7 @@ google-nest-sdm==0.4.9 googlemaps==2.5.1 # homeassistant.components.gree -greeclimate==0.12.5 +greeclimate==1.0.1 # homeassistant.components.greeneye_monitor greeneye_monitor==2.1 From 9df40d7056dddf11c07066d36d4e082b23a93801 Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Fri, 31 Dec 2021 19:47:03 +0200 Subject: [PATCH 12/28] Fix Shelly error fetching device triggers for sleeping devices (#63103) --- .../components/shelly/device_trigger.py | 3 ++ .../components/shelly/test_device_trigger.py | 36 +++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/homeassistant/components/shelly/device_trigger.py b/homeassistant/components/shelly/device_trigger.py index f5abf76e8f2..3e839507127 100644 --- a/homeassistant/components/shelly/device_trigger.py +++ b/homeassistant/components/shelly/device_trigger.py @@ -123,6 +123,9 @@ async def async_get_triggers( append_input_triggers(triggers, input_triggers, device_id) return triggers + if not block_wrapper.device.initialized: + return triggers + assert block_wrapper.device.blocks for block in block_wrapper.device.blocks: diff --git a/tests/components/shelly/test_device_trigger.py b/tests/components/shelly/test_device_trigger.py index a81662159c2..73f92ca9640 100644 --- a/tests/components/shelly/test_device_trigger.py +++ b/tests/components/shelly/test_device_trigger.py @@ -168,6 +168,42 @@ async def test_get_triggers_button(hass): assert_lists_same(triggers, expected_triggers) +async def test_get_triggers_non_initialized_devices(hass): + """Test we get the empty triggers for non-initialized devices.""" + await async_setup_component(hass, "shelly", {}) + + config_entry = MockConfigEntry( + domain=DOMAIN, + data={"sleep_period": 43200, "model": "SHDW-2", "host": "1.2.3.4"}, + unique_id="12345678", + ) + config_entry.add_to_hass(hass) + + device = Mock( + blocks=None, + settings=None, + shelly=None, + update=AsyncMock(), + initialized=False, + ) + + hass.data[DOMAIN] = {DATA_CONFIG_ENTRY: {}} + hass.data[DOMAIN][DATA_CONFIG_ENTRY][config_entry.entry_id] = {} + coap_wrapper = hass.data[DOMAIN][DATA_CONFIG_ENTRY][config_entry.entry_id][ + BLOCK + ] = BlockDeviceWrapper(hass, config_entry, device) + + coap_wrapper.async_setup() + + expected_triggers = [] + + triggers = await async_get_device_automations( + hass, DeviceAutomationType.TRIGGER, coap_wrapper.device_id + ) + + assert_lists_same(triggers, expected_triggers) + + async def test_get_triggers_for_invalid_device_id(hass, device_reg, coap_wrapper): """Test error raised for invalid shelly device_id.""" assert coap_wrapper From e39cf1a4c65419f8f8e9d81e0d331cb919ee7d6a Mon Sep 17 00:00:00 2001 From: Karthik T Date: Mon, 3 Jan 2022 04:51:50 +0800 Subject: [PATCH 13/28] Fix systemmonitor CPU temp for Armbian on PineA64 (#63111) --- homeassistant/components/systemmonitor/sensor.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/systemmonitor/sensor.py b/homeassistant/components/systemmonitor/sensor.py index 8bbdae820e3..a05601caa02 100644 --- a/homeassistant/components/systemmonitor/sensor.py +++ b/homeassistant/components/systemmonitor/sensor.py @@ -307,6 +307,7 @@ CPU_SENSOR_PREFIXES = [ "soc_thermal 1", "Tctl", "cpu0-thermal", + "cpu0_thermal", ] From 4c52b9731ff26d719c08ede8081fb1bfaef28e0f Mon Sep 17 00:00:00 2001 From: ryborg Date: Tue, 4 Jan 2022 12:24:44 -0500 Subject: [PATCH 14/28] Fix CO/CO2 sensors mixup in Google Assistant (#63152) Co-authored-by: Franck Nijhof --- homeassistant/components/google_assistant/trait.py | 4 ++-- tests/components/google_assistant/test_trait.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/google_assistant/trait.py b/homeassistant/components/google_assistant/trait.py index 843ebfee87a..c3a7d403a2f 100644 --- a/homeassistant/components/google_assistant/trait.py +++ b/homeassistant/components/google_assistant/trait.py @@ -2296,8 +2296,8 @@ class SensorStateTrait(_Trait): sensor_types = { sensor.DEVICE_CLASS_AQI: ("AirQuality", "AQI"), - sensor.DEVICE_CLASS_CO: ("CarbonDioxideLevel", "PARTS_PER_MILLION"), - sensor.DEVICE_CLASS_CO2: ("CarbonMonoxideLevel", "PARTS_PER_MILLION"), + sensor.DEVICE_CLASS_CO: ("CarbonMonoxideLevel", "PARTS_PER_MILLION"), + sensor.DEVICE_CLASS_CO2: ("CarbonDioxideLevel", "PARTS_PER_MILLION"), sensor.DEVICE_CLASS_PM25: ("PM2.5", "MICROGRAMS_PER_CUBIC_METER"), sensor.DEVICE_CLASS_PM10: ("PM10", "MICROGRAMS_PER_CUBIC_METER"), sensor.DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS: ( diff --git a/tests/components/google_assistant/test_trait.py b/tests/components/google_assistant/test_trait.py index a396c1bc91d..278b6dc2ffe 100644 --- a/tests/components/google_assistant/test_trait.py +++ b/tests/components/google_assistant/test_trait.py @@ -3030,8 +3030,8 @@ async def test_sensorstate(hass): """Test SensorState trait support for sensor domain.""" sensor_types = { sensor.DEVICE_CLASS_AQI: ("AirQuality", "AQI"), - sensor.DEVICE_CLASS_CO: ("CarbonDioxideLevel", "PARTS_PER_MILLION"), - sensor.DEVICE_CLASS_CO2: ("CarbonMonoxideLevel", "PARTS_PER_MILLION"), + sensor.DEVICE_CLASS_CO: ("CarbonMonoxideLevel", "PARTS_PER_MILLION"), + sensor.DEVICE_CLASS_CO2: ("CarbonDioxideLevel", "PARTS_PER_MILLION"), sensor.DEVICE_CLASS_PM25: ("PM2.5", "MICROGRAMS_PER_CUBIC_METER"), sensor.DEVICE_CLASS_PM10: ("PM10", "MICROGRAMS_PER_CUBIC_METER"), sensor.DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS: ( From 60f4521a2d6d93b321bd1bca857c6c51c7972916 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 2 Jan 2022 05:32:39 -1000 Subject: [PATCH 15/28] Bump flux_led to 0.27.28 to fix missing white channel on SK6812RGBW strips (#63154) --- homeassistant/components/flux_led/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/flux_led/manifest.json b/homeassistant/components/flux_led/manifest.json index 9f1eaa5d4a4..583570d4784 100644 --- a/homeassistant/components/flux_led/manifest.json +++ b/homeassistant/components/flux_led/manifest.json @@ -3,7 +3,7 @@ "name": "Flux LED/MagicHome", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/flux_led", - "requirements": ["flux_led==0.27.21"], + "requirements": ["flux_led==0.27.28"], "quality_scale": "platinum", "codeowners": ["@icemanch"], "iot_class": "local_push", diff --git a/requirements_all.txt b/requirements_all.txt index 53229f471ab..851f906ebbc 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -659,7 +659,7 @@ fjaraskupan==1.0.2 flipr-api==1.4.1 # homeassistant.components.flux_led -flux_led==0.27.21 +flux_led==0.27.28 # homeassistant.components.homekit fnvhash==0.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e14b0e335b5..5c023211aa5 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -399,7 +399,7 @@ fjaraskupan==1.0.2 flipr-api==1.4.1 # homeassistant.components.flux_led -flux_led==0.27.21 +flux_led==0.27.28 # homeassistant.components.homekit fnvhash==0.1.0 From 5490a65101051c7f41073b6a6a6d5b0906b00f0f Mon Sep 17 00:00:00 2001 From: trdischat <52774325+trdischat@users.noreply.github.com> Date: Sat, 1 Jan 2022 23:15:27 -0800 Subject: [PATCH 16/28] Add default Fronius logger model for v0 API (#63184) --- homeassistant/components/fronius/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/fronius/__init__.py b/homeassistant/components/fronius/__init__.py index cf648d3b613..5a130f68987 100644 --- a/homeassistant/components/fronius/__init__.py +++ b/homeassistant/components/fronius/__init__.py @@ -160,7 +160,10 @@ class FroniusSolarNet: ) if self.logger_coordinator: _logger_info = self.logger_coordinator.data[SOLAR_NET_ID_SYSTEM] - solar_net_device[ATTR_MODEL] = _logger_info["product_type"]["value"] + # API v0 doesn't provide product_type + solar_net_device[ATTR_MODEL] = _logger_info.get("product_type", {}).get( + "value", "Datalogger Web" + ) solar_net_device[ATTR_SW_VERSION] = _logger_info["software_version"][ "value" ] From 5910460f0017f4875f27c02ab720a6d6fc9cd8c4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 3 Jan 2022 01:06:44 -1000 Subject: [PATCH 17/28] Prevent doorbird integration from overloading the device on startup (#63253) --- homeassistant/components/doorbird/__init__.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/doorbird/__init__.py b/homeassistant/components/doorbird/__init__.py index 7497304f9e1..4d9f0091e6c 100644 --- a/homeassistant/components/doorbird/__init__.py +++ b/homeassistant/components/doorbird/__init__.py @@ -248,8 +248,10 @@ class ConfiguredDoorBird: if self.custom_url is not None: hass_url = self.custom_url + favorites = self.device.favorites() + for event in self.doorstation_events: - self._register_event(hass_url, event) + self._register_event(hass_url, event, favs=favorites) _LOGGER.info("Successfully registered URL for %s on %s", event, self.name) @@ -261,15 +263,15 @@ class ConfiguredDoorBird: def _get_event_name(self, event): return f"{self.slug}_{event}" - def _register_event(self, hass_url, event): + def _register_event(self, hass_url, event, favs=None): """Add a schedule entry in the device for a sensor.""" url = f"{hass_url}{API_URL}/{event}?token={self._token}" # Register HA URL as webhook if not already, then get the ID - if not self.webhook_is_registered(url): + if not self.webhook_is_registered(url, favs=favs): self.device.change_favorite("http", f"Home Assistant ({event})", url) - if not self.get_webhook_id(url): + if not self.get_webhook_id(url, favs=favs): _LOGGER.warning( 'Could not find favorite for URL "%s". ' 'Skipping sensor "%s"', url, From ed4f0a50a5714a08e97bb60718f8477db06dffc3 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 3 Jan 2022 01:05:35 -1000 Subject: [PATCH 18/28] Bump flux_led to 0.27.32 to fix incorrect strip order on A2 devices (#63262) --- homeassistant/components/flux_led/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/flux_led/manifest.json b/homeassistant/components/flux_led/manifest.json index 583570d4784..6703065476e 100644 --- a/homeassistant/components/flux_led/manifest.json +++ b/homeassistant/components/flux_led/manifest.json @@ -3,7 +3,7 @@ "name": "Flux LED/MagicHome", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/flux_led", - "requirements": ["flux_led==0.27.28"], + "requirements": ["flux_led==0.27.32"], "quality_scale": "platinum", "codeowners": ["@icemanch"], "iot_class": "local_push", diff --git a/requirements_all.txt b/requirements_all.txt index 851f906ebbc..36f39fe9390 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -659,7 +659,7 @@ fjaraskupan==1.0.2 flipr-api==1.4.1 # homeassistant.components.flux_led -flux_led==0.27.28 +flux_led==0.27.32 # homeassistant.components.homekit fnvhash==0.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 5c023211aa5..9350933db34 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -399,7 +399,7 @@ fjaraskupan==1.0.2 flipr-api==1.4.1 # homeassistant.components.flux_led -flux_led==0.27.28 +flux_led==0.27.32 # homeassistant.components.homekit fnvhash==0.1.0 From f2f0fba611eabfd0153914ec17c1e58d696ce041 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 3 Jan 2022 10:46:56 -0800 Subject: [PATCH 19/28] Sisyphus: Fix bad super call (#63327) Co-authored-by: Franck Nijhof --- homeassistant/components/sisyphus/media_player.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/sisyphus/media_player.py b/homeassistant/components/sisyphus/media_player.py index 21e50b19a1b..f255c007582 100644 --- a/homeassistant/components/sisyphus/media_player.py +++ b/homeassistant/components/sisyphus/media_player.py @@ -163,7 +163,7 @@ class SisyphusPlayer(MediaPlayerEntity): if self._table.active_track: return self._table.active_track.get_thumbnail_url(Track.ThumbnailSize.LARGE) - return super.media_image_url() + return super().media_image_url async def async_turn_on(self): """Wake up a sleeping table.""" From 39cfc1c8399a4437e6b19e9a1d2e4dc3f7451509 Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Tue, 4 Jan 2022 19:32:25 +0100 Subject: [PATCH 20/28] Fix status type in Shelly climate platform (#63347) --- homeassistant/components/shelly/climate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/shelly/climate.py b/homeassistant/components/shelly/climate.py index 06140fcba72..777a41a6664 100644 --- a/homeassistant/components/shelly/climate.py +++ b/homeassistant/components/shelly/climate.py @@ -219,7 +219,7 @@ class BlockSleepingClimate( return CURRENT_HVAC_OFF return ( - CURRENT_HVAC_IDLE if self.device_block.status == "0" else CURRENT_HVAC_HEAT + CURRENT_HVAC_HEAT if bool(self.device_block.status) else CURRENT_HVAC_IDLE ) @property From 4d9d186ddff678041588dd259324f0c168b5c20a Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Tue, 4 Jan 2022 15:37:33 +0100 Subject: [PATCH 21/28] Bump micloud to 0.5 (#63348) --- homeassistant/components/xiaomi_miio/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/xiaomi_miio/manifest.json b/homeassistant/components/xiaomi_miio/manifest.json index 8de844cdd44..da2b94f5382 100644 --- a/homeassistant/components/xiaomi_miio/manifest.json +++ b/homeassistant/components/xiaomi_miio/manifest.json @@ -3,7 +3,7 @@ "name": "Xiaomi Miio", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/xiaomi_miio", - "requirements": ["construct==2.10.56", "micloud==0.4", "python-miio==0.5.9.2"], + "requirements": ["construct==2.10.56", "micloud==0.5", "python-miio==0.5.9.2"], "codeowners": ["@rytilahti", "@syssi", "@starkillerOG", "@bieniu"], "zeroconf": ["_miio._udp.local."], "iot_class": "local_polling" diff --git a/requirements_all.txt b/requirements_all.txt index 36f39fe9390..34002e25317 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1006,7 +1006,7 @@ meteofrance-api==1.0.2 mficlient==0.3.0 # homeassistant.components.xiaomi_miio -micloud==0.4 +micloud==0.5 # homeassistant.components.miflora miflora==0.7.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 9350933db34..ccbcfd7d5e2 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -612,7 +612,7 @@ meteofrance-api==1.0.2 mficlient==0.3.0 # homeassistant.components.xiaomi_miio -micloud==0.4 +micloud==0.5 # homeassistant.components.mill mill-local==0.1.0 From 9126125e16c5a4f88dd3c579c741c21718e7ef24 Mon Sep 17 00:00:00 2001 From: Christopher Masto Date: Tue, 4 Jan 2022 17:04:43 -0500 Subject: [PATCH 22/28] Work around ingress glitch with 304 responses (#63355) Co-authored-by: Paulus Schoutsen --- homeassistant/components/hassio/ingress.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/hassio/ingress.py b/homeassistant/components/hassio/ingress.py index 6935bbdc7da..5369089d0f0 100644 --- a/homeassistant/components/hassio/ingress.py +++ b/homeassistant/components/hassio/ingress.py @@ -134,7 +134,7 @@ class HassIOIngress(HomeAssistantView): if ( hdrs.CONTENT_LENGTH in result.headers and int(result.headers.get(hdrs.CONTENT_LENGTH, 0)) < 4194000 - ): + ) or result.status in (204, 304): # Return Response body = await result.read() return web.Response( From 082c9c34eab81a2805ebab4a7c07475c6303012f Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Tue, 4 Jan 2022 17:15:19 +0100 Subject: [PATCH 23/28] Fix Hue grouped light color_mode calculation (#63374) --- homeassistant/components/hue/v2/group.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/hue/v2/group.py b/homeassistant/components/hue/v2/group.py index b8fdb0b0b1d..835f90d3f6f 100644 --- a/homeassistant/components/hue/v2/group.py +++ b/homeassistant/components/hue/v2/group.py @@ -298,7 +298,10 @@ class GroupedHueLight(HueBaseEntity, LightEntity): supported_color_modes.add(COLOR_MODE_ONOFF) self._attr_supported_color_modes = supported_color_modes # pick a winner for the current colormode - if lights_in_colortemp_mode == lights_with_color_temp_support: + if ( + lights_with_color_temp_support > 0 + and lights_in_colortemp_mode == lights_with_color_temp_support + ): self._attr_color_mode = COLOR_MODE_COLOR_TEMP elif lights_with_color_support > 0: self._attr_color_mode = COLOR_MODE_XY From 27a74d2720773ff6b9ece7311921e4c806fce078 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 4 Jan 2022 22:16:35 +0100 Subject: [PATCH 24/28] Fix missing timezone in GTFS timestamp sensor (#63401) --- homeassistant/components/gtfs/sensor.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/gtfs/sensor.py b/homeassistant/components/gtfs/sensor.py index 367a45aa073..beaa37e7429 100644 --- a/homeassistant/components/gtfs/sensor.py +++ b/homeassistant/components/gtfs/sensor.py @@ -619,7 +619,9 @@ class GTFSDepartureSensor(SensorEntity): if not self._departure: self._state = None else: - self._state = self._departure["departure_time"] + self._state = self._departure["departure_time"].replace( + tzinfo=dt_util.UTC + ) # Fetch trip and route details once, unless updated if not self._departure: From 599c6240f4652f53c4d23d88d86afeefeb18e31e Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 4 Jan 2022 14:12:39 -0800 Subject: [PATCH 25/28] Bumped version to 2021.12.8 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index f9131e0a4f3..45104f0d81b 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -7,7 +7,7 @@ from homeassistant.backports.enum import StrEnum MAJOR_VERSION: Final = 2021 MINOR_VERSION: Final = 12 -PATCH_VERSION: Final = "7" +PATCH_VERSION: Final = "8" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 8, 0) From 41b43199a39d81cab2e5eefb99fe0a5d865d2ad2 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 4 Jan 2022 14:48:13 -0800 Subject: [PATCH 26/28] Fix merge conflicts --- homeassistant/components/knx/translations/en.json | 4 ---- tests/components/shelly/test_device_trigger.py | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/homeassistant/components/knx/translations/en.json b/homeassistant/components/knx/translations/en.json index 43aeced6698..beff8cecd00 100644 --- a/homeassistant/components/knx/translations/en.json +++ b/homeassistant/components/knx/translations/en.json @@ -47,13 +47,9 @@ "data": { "connection_type": "KNX Connection Type", "individual_address": "Default individual address", -<<<<<<< HEAD -======= "local_ip": "Local IP of Home Assistant (use 0.0.0.0 for automatic detection)", ->>>>>>> b9247f3952 (Fix local_ip handling in KNX options flow (#62969)) "multicast_group": "Multicast group used for routing and discovery", "multicast_port": "Multicast port used for routing and discovery", - "local_ip": "Local IP (leave empty if unsure)", "rate_limit": "Maximum outgoing telegrams per second", "state_updater": "Globally enable reading states from the KNX Bus" } diff --git a/tests/components/shelly/test_device_trigger.py b/tests/components/shelly/test_device_trigger.py index 73f92ca9640..238fb9b0336 100644 --- a/tests/components/shelly/test_device_trigger.py +++ b/tests/components/shelly/test_device_trigger.py @@ -198,7 +198,7 @@ async def test_get_triggers_non_initialized_devices(hass): expected_triggers = [] triggers = await async_get_device_automations( - hass, DeviceAutomationType.TRIGGER, coap_wrapper.device_id + hass, "trigger", coap_wrapper.device_id ) assert_lists_same(triggers, expected_triggers) From 888abd63a18b541954ca39b065b8cc06ef502848 Mon Sep 17 00:00:00 2001 From: jjlawren Date: Tue, 4 Jan 2022 17:16:53 -0600 Subject: [PATCH 27/28] Handle missing monitored users in Plex options (#63411) --- homeassistant/components/plex/config_flow.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/plex/config_flow.py b/homeassistant/components/plex/config_flow.py index b2606c6eeaf..82a26cfc237 100644 --- a/homeassistant/components/plex/config_flow.py +++ b/homeassistant/components/plex/config_flow.py @@ -380,6 +380,7 @@ class PlexOptionsFlowHandler(config_entries.OptionsFlow): for user in plex_server.option_monitored_users if plex_server.option_monitored_users[user]["enabled"] } + default_accounts.intersection_update(plex_server.accounts) for user in plex_server.accounts: if user not in known_accounts: available_accounts[user] += " [New]" From 53b3369c2990866309bd600b0cc3f38afb3b7b8d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 4 Jan 2022 19:22:28 -1000 Subject: [PATCH 28/28] Handle no enabled ipv4 addresses in the network integration (#63416) --- homeassistant/components/network/__init__.py | 14 ++++++ tests/components/network/test_init.py | 48 ++++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/homeassistant/components/network/__init__.py b/homeassistant/components/network/__init__.py index 7cc864727d7..b3ef88e7ab2 100644 --- a/homeassistant/components/network/__init__.py +++ b/homeassistant/components/network/__init__.py @@ -2,8 +2,10 @@ from __future__ import annotations from ipaddress import IPv4Address, IPv6Address, ip_interface +import logging from homeassistant.core import HomeAssistant, callback +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.typing import ConfigType from homeassistant.loader import bind_hass @@ -12,6 +14,8 @@ from .const import IPV4_BROADCAST_ADDR, PUBLIC_TARGET_IP from .models import Adapter from .network import Network, async_get_network +_LOGGER = logging.getLogger(__name__) + @bind_hass async def async_get_adapters(hass: HomeAssistant) -> list[Adapter]: @@ -32,6 +36,16 @@ async def async_get_source_ip( all_ipv4s.extend([ipv4["address"] for ipv4 in ipv4s]) source_ip = util.async_get_source_ip(target_ip) + if not all_ipv4s: + _LOGGER.warning( + "Because the system does not have any enabled IPv4 addresses, source address detection may be inaccurate" + ) + if source_ip is None: + raise HomeAssistantError( + "Could not determine source ip because the system does not have any enabled IPv4 addresses and creating a socket failed" + ) + return source_ip + return source_ip if source_ip in all_ipv4s else all_ipv4s[0] diff --git a/tests/components/network/test_init.py b/tests/components/network/test_init.py index 5a6802a14fb..1103c6fa850 100644 --- a/tests/components/network/test_init.py +++ b/tests/components/network/test_init.py @@ -3,6 +3,7 @@ from ipaddress import IPv4Address from unittest.mock import MagicMock, Mock, patch import ifaddr +import pytest from homeassistant.components import network from homeassistant.components.network.const import ( @@ -13,6 +14,7 @@ from homeassistant.components.network.const import ( STORAGE_KEY, STORAGE_VERSION, ) +from homeassistant.exceptions import HomeAssistantError from homeassistant.setup import async_setup_component _NO_LOOPBACK_IPADDR = "192.168.1.5" @@ -602,3 +604,49 @@ async def test_async_get_ipv4_broadcast_addresses_multiple(hass, hass_storage): IPv4Address("192.168.1.255"), IPv4Address("169.254.255.255"), } + + +async def test_async_get_source_ip_no_enabled_addresses(hass, hass_storage, caplog): + """Test getting the source ip address when all adapters are disabled.""" + hass_storage[STORAGE_KEY] = { + "version": STORAGE_VERSION, + "key": STORAGE_KEY, + "data": {ATTR_CONFIGURED_ADAPTERS: ["eth1"]}, + } + + with patch( + "homeassistant.components.network.util.ifaddr.get_adapters", + return_value=[], + ), patch( + "homeassistant.components.network.util.socket.socket", + return_value=_mock_socket(["192.168.1.5"]), + ): + assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) + await hass.async_block_till_done() + + assert await network.async_get_source_ip(hass, MDNS_TARGET_IP) == "192.168.1.5" + + assert "source address detection may be inaccurate" in caplog.text + + +async def test_async_get_source_ip_cannot_be_determined_and_no_enabled_addresses( + hass, hass_storage, caplog +): + """Test getting the source ip address when all adapters are disabled and getting it fails.""" + hass_storage[STORAGE_KEY] = { + "version": STORAGE_VERSION, + "key": STORAGE_KEY, + "data": {ATTR_CONFIGURED_ADAPTERS: ["eth1"]}, + } + + with patch( + "homeassistant.components.network.util.ifaddr.get_adapters", + return_value=[], + ), patch( + "homeassistant.components.network.util.socket.socket", + return_value=_mock_socket([None]), + ): + assert not await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) + await hass.async_block_till_done() + with pytest.raises(HomeAssistantError): + await network.async_get_source_ip(hass, MDNS_TARGET_IP)