diff --git a/homeassistant/components/accuweather/weather.py b/homeassistant/components/accuweather/weather.py index 00726f6db38..4ab9342de62 100644 --- a/homeassistant/components/accuweather/weather.py +++ b/homeassistant/components/accuweather/weather.py @@ -17,7 +17,12 @@ from homeassistant.components.weather import ( WeatherEntity, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_NAME, TEMP_CELSIUS, TEMP_FAHRENHEIT +from homeassistant.const import ( + CONF_NAME, + SPEED_MILES_PER_HOUR, + TEMP_CELSIUS, + TEMP_FAHRENHEIT, +) from homeassistant.core import HomeAssistant from homeassistant.helpers.device_registry import DeviceEntryType from homeassistant.helpers.entity import DeviceInfo @@ -62,9 +67,13 @@ class AccuWeatherEntity(CoordinatorEntity, WeatherEntity): """Initialize.""" super().__init__(coordinator) self._unit_system = API_METRIC if coordinator.is_metric else API_IMPERIAL - self._attr_wind_speed_unit = self.coordinator.data["Wind"]["Speed"][ - self._unit_system - ]["Unit"] + wind_speed_unit = self.coordinator.data["Wind"]["Speed"][self._unit_system][ + "Unit" + ] + if wind_speed_unit == "mi/h": + self._attr_wind_speed_unit = SPEED_MILES_PER_HOUR + else: + self._attr_wind_speed_unit = wind_speed_unit self._attr_name = name self._attr_unique_id = coordinator.location_key self._attr_temperature_unit = ( diff --git a/homeassistant/components/amcrest/camera.py b/homeassistant/components/amcrest/camera.py index 6e729a8f1b5..3846f4945a9 100644 --- a/homeassistant/components/amcrest/camera.py +++ b/homeassistant/components/amcrest/camera.py @@ -515,8 +515,8 @@ class AmcrestCam(Camera): max_tries = 3 for tries in range(max_tries, 0, -1): try: - await getattr(self, f"_set_{func}")(value) - new_value = await getattr(self, f"_get_{func}")() + await getattr(self, f"_async_set_{func}")(value) + new_value = await getattr(self, f"_async_get_{func}")() if new_value != value: raise AmcrestCommandFailed except (AmcrestError, AmcrestCommandFailed) as error: diff --git a/homeassistant/components/androidtv/__init__.py b/homeassistant/components/androidtv/__init__.py index 81d4a3f0645..9b968385602 100644 --- a/homeassistant/components/androidtv/__init__.py +++ b/homeassistant/components/androidtv/__init__.py @@ -18,6 +18,7 @@ from homeassistant.const import ( from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import entity_registry as er +from homeassistant.helpers.device_registry import format_mac from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.storage import STORAGE_DIR from homeassistant.helpers.typing import ConfigType @@ -33,16 +34,30 @@ from .const import ( DEVICE_ANDROIDTV, DEVICE_FIRETV, DOMAIN, + PROP_ETHMAC, PROP_SERIALNO, + PROP_WIFIMAC, SIGNAL_CONFIG_ENTITY, ) PLATFORMS = [Platform.MEDIA_PLAYER] RELOAD_OPTIONS = [CONF_STATE_DETECTION_RULES] +_INVALID_MACS = {"ff:ff:ff:ff:ff:ff"} + _LOGGER = logging.getLogger(__name__) +def get_androidtv_mac(dev_props): + """Return formatted mac from device properties.""" + for prop_mac in (PROP_ETHMAC, PROP_WIFIMAC): + if if_mac := dev_props.get(prop_mac): + mac = format_mac(if_mac) + if mac not in _INVALID_MACS: + return mac + return None + + def _setup_androidtv(hass, config): """Generate an ADB key (if needed) and load it.""" adbkey = config.get(CONF_ADBKEY, hass.config.path(STORAGE_DIR, "androidtv_adbkey")) diff --git a/homeassistant/components/androidtv/config_flow.py b/homeassistant/components/androidtv/config_flow.py index 0ec37fdeb6f..8f0efc34799 100644 --- a/homeassistant/components/androidtv/config_flow.py +++ b/homeassistant/components/androidtv/config_flow.py @@ -11,9 +11,8 @@ from homeassistant import config_entries from homeassistant.const import CONF_DEVICE_CLASS, CONF_HOST, CONF_NAME, CONF_PORT from homeassistant.core import callback from homeassistant.helpers import config_validation as cv -from homeassistant.helpers.device_registry import format_mac -from . import async_connect_androidtv +from . import async_connect_androidtv, get_androidtv_mac from .const import ( CONF_ADB_SERVER_IP, CONF_ADB_SERVER_PORT, @@ -132,9 +131,7 @@ class AndroidTVFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): PROP_WIFIMAC, dev_prop.get(PROP_WIFIMAC), ) - unique_id = format_mac( - dev_prop.get(PROP_ETHMAC) or dev_prop.get(PROP_WIFIMAC, "") - ) + unique_id = get_androidtv_mac(dev_prop) await aftv.adb_close() return None, unique_id diff --git a/homeassistant/components/androidtv/media_player.py b/homeassistant/components/androidtv/media_player.py index 03b1e679961..1ab592143c6 100644 --- a/homeassistant/components/androidtv/media_player.py +++ b/homeassistant/components/androidtv/media_player.py @@ -51,12 +51,13 @@ from homeassistant.const import ( ) from homeassistant.core import HomeAssistant from homeassistant.helpers import config_validation as cv, entity_platform -from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, format_mac +from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType +from . import get_androidtv_mac from .const import ( ANDROID_DEV, ANDROID_DEV_OPT, @@ -80,8 +81,6 @@ from .const import ( DEVICE_ANDROIDTV, DEVICE_CLASSES, DOMAIN, - PROP_ETHMAC, - PROP_WIFIMAC, SIGNAL_CONFIG_ENTITY, ) @@ -343,7 +342,7 @@ class ADBDevice(MediaPlayerEntity): self._attr_device_info[ATTR_MANUFACTURER] = manufacturer if sw_version := info.get(ATTR_SW_VERSION): self._attr_device_info[ATTR_SW_VERSION] = sw_version - if mac := format_mac(info.get(PROP_ETHMAC) or info.get(PROP_WIFIMAC, "")): + if mac := get_androidtv_mac(info): self._attr_device_info[ATTR_CONNECTIONS] = {(CONNECTION_NETWORK_MAC, mac)} self._app_id_to_name = {} diff --git a/homeassistant/components/config/device_registry.py b/homeassistant/components/config/device_registry.py index 5e7c2ef1938..686fffec252 100644 --- a/homeassistant/components/config/device_registry.py +++ b/homeassistant/components/config/device_registry.py @@ -62,7 +62,7 @@ async def websocket_update_device(hass, connection, msg): msg.pop("type") msg_id = msg.pop("id") - if "disabled_by" in msg: + if msg.get("disabled_by") is not None: msg["disabled_by"] = DeviceEntryDisabler(msg["disabled_by"]) entry = registry.async_update_device(**msg) diff --git a/homeassistant/components/dhcp/__init__.py b/homeassistant/components/dhcp/__init__.py index 4310f8f2caf..dd247c4cab9 100644 --- a/homeassistant/components/dhcp/__init__.py +++ b/homeassistant/components/dhcp/__init__.py @@ -179,6 +179,7 @@ class WatcherBase: lowercase_hostname, ) + matched_domains = set() for entry in self._integration_matchers: if MAC_ADDRESS in entry and not fnmatch.fnmatch( uppercase_mac, entry[MAC_ADDRESS] @@ -191,6 +192,11 @@ class WatcherBase: continue _LOGGER.debug("Matched %s against %s", data, entry) + if entry["domain"] in matched_domains: + # Only match once per domain + continue + + matched_domains.add(entry["domain"]) discovery_flow.async_create_flow( self.hass, entry["domain"], diff --git a/homeassistant/components/doods/manifest.json b/homeassistant/components/doods/manifest.json index 3957b257364..86ad7ae4a90 100644 --- a/homeassistant/components/doods/manifest.json +++ b/homeassistant/components/doods/manifest.json @@ -2,7 +2,7 @@ "domain": "doods", "name": "DOODS - Dedicated Open Object Detection Service", "documentation": "https://www.home-assistant.io/integrations/doods", - "requirements": ["pydoods==1.0.2", "pillow==9.0.0"], + "requirements": ["pydoods==1.0.2", "pillow==9.0.1"], "codeowners": [], "iot_class": "local_polling" } diff --git a/homeassistant/components/flux_led/manifest.json b/homeassistant/components/flux_led/manifest.json index e10bf72b8c3..4e6cf97fa31 100644 --- a/homeassistant/components/flux_led/manifest.json +++ b/homeassistant/components/flux_led/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "dependencies": ["network"], "documentation": "https://www.home-assistant.io/integrations/flux_led", - "requirements": ["flux_led==0.28.20"], + "requirements": ["flux_led==0.28.21"], "quality_scale": "platinum", "codeowners": ["@icemanch", "@bdraco"], "iot_class": "local_push", diff --git a/homeassistant/components/fritz/common.py b/homeassistant/components/fritz/common.py index 2cd6616f134..078bb4b4225 100644 --- a/homeassistant/components/fritz/common.py +++ b/homeassistant/components/fritz/common.py @@ -567,13 +567,6 @@ class AvmWrapper(FritzBoxTools): ) return {} - async def async_get_wan_dsl_interface_config(self) -> dict[str, Any]: - """Call WANDSLInterfaceConfig service.""" - - return await self.hass.async_add_executor_job( - partial(self.get_wan_dsl_interface_config) - ) - async def async_get_wan_link_properties(self) -> dict[str, Any]: """Call WANCommonInterfaceConfig service.""" @@ -678,11 +671,6 @@ class AvmWrapper(FritzBoxTools): return self._service_call_action("WLANConfiguration", str(index), "GetInfo") - def get_wan_dsl_interface_config(self) -> dict[str, Any]: - """Call WANDSLInterfaceConfig service.""" - - return self._service_call_action("WANDSLInterfaceConfig", "1", "GetInfo") - def get_wan_link_properties(self) -> dict[str, Any]: """Call WANCommonInterfaceConfig service.""" diff --git a/homeassistant/components/fritz/sensor.py b/homeassistant/components/fritz/sensor.py index 6155cdc5914..5e4b18eebca 100644 --- a/homeassistant/components/fritz/sensor.py +++ b/homeassistant/components/fritz/sensor.py @@ -277,10 +277,14 @@ async def async_setup_entry( _LOGGER.debug("Setting up FRITZ!Box sensors") avm_wrapper: AvmWrapper = hass.data[DOMAIN][entry.entry_id] - dsl: bool = False - dslinterface = await avm_wrapper.async_get_wan_dsl_interface_config() - if dslinterface: - dsl = dslinterface["NewEnable"] + link_properties = await avm_wrapper.async_get_wan_link_properties() + dsl: bool = link_properties.get("NewWANAccessType") == "DSL" + + _LOGGER.debug( + "WANAccessType of FritzBox %s is '%s'", + avm_wrapper.host, + link_properties.get("NewWANAccessType"), + ) entities = [ FritzBoxSensor(avm_wrapper, entry.title, description) diff --git a/homeassistant/components/homekit/diagnostics.py b/homeassistant/components/homekit/diagnostics.py new file mode 100644 index 00000000000..2a54c1ef543 --- /dev/null +++ b/homeassistant/components/homekit/diagnostics.py @@ -0,0 +1,44 @@ +"""Diagnostics support for HomeKit.""" +from __future__ import annotations + +from typing import Any + +from pyhap.accessory_driver import AccessoryDriver +from pyhap.state import State + +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant + +from . import HomeKit +from .const import DOMAIN, HOMEKIT + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, entry: ConfigEntry +) -> dict[str, Any]: + """Return diagnostics for a config entry.""" + homekit: HomeKit = hass.data[DOMAIN][entry.entry_id][HOMEKIT] + driver: AccessoryDriver = homekit.driver + data: dict[str, Any] = { + "status": homekit.status, + "config-entry": { + "title": entry.title, + "version": entry.version, + "data": dict(entry.data), + "options": dict(entry.options), + }, + } + if not driver: + return data + data.update(driver.get_accessories()) + state: State = driver.state + data.update( + { + "client_properties": { + str(client): props for client, props in state.client_properties.items() + }, + "config_version": state.config_version, + "pairing_id": state.mac, + } + ) + return data diff --git a/homeassistant/components/homekit/type_lights.py b/homeassistant/components/homekit/type_lights.py index cdff3105ec3..081f6f1bdd4 100644 --- a/homeassistant/components/homekit/type_lights.py +++ b/homeassistant/components/homekit/type_lights.py @@ -1,4 +1,6 @@ """Class to hold all light accessories.""" +from __future__ import annotations + import logging import math @@ -12,12 +14,13 @@ from homeassistant.components.light import ( ATTR_HS_COLOR, ATTR_MAX_MIREDS, ATTR_MIN_MIREDS, - ATTR_RGB_COLOR, ATTR_RGBW_COLOR, ATTR_RGBWW_COLOR, ATTR_SUPPORTED_COLOR_MODES, + ATTR_WHITE, COLOR_MODE_RGBW, COLOR_MODE_RGBWW, + COLOR_MODE_WHITE, DOMAIN, brightness_supported, color_supported, @@ -32,9 +35,9 @@ from homeassistant.const import ( from homeassistant.core import callback from homeassistant.helpers.event import async_call_later from homeassistant.util.color import ( - color_hsv_to_RGB, color_temperature_mired_to_kelvin, color_temperature_to_hs, + color_temperature_to_rgbww, ) from .accessories import TYPES, HomeAccessory @@ -51,12 +54,13 @@ from .const import ( _LOGGER = logging.getLogger(__name__) -RGB_COLOR = "rgb_color" CHANGE_COALESCE_TIME_WINDOW = 0.01 +DEFAULT_MIN_MIREDS = 153 +DEFAULT_MAX_MIREDS = 500 -COLOR_MODES_WITH_WHITES = {COLOR_MODE_RGBW, COLOR_MODE_RGBWW} +COLOR_MODES_WITH_WHITES = {COLOR_MODE_RGBW, COLOR_MODE_RGBWW, COLOR_MODE_WHITE} @TYPES.register("Light") @@ -79,8 +83,12 @@ class Light(HomeAccessory): self.color_modes = color_modes = ( attributes.get(ATTR_SUPPORTED_COLOR_MODES) or [] ) + self._previous_color_mode = attributes.get(ATTR_COLOR_MODE) self.color_supported = color_supported(color_modes) self.color_temp_supported = color_temp_supported(color_modes) + self.rgbw_supported = COLOR_MODE_RGBW in color_modes + self.rgbww_supported = COLOR_MODE_RGBWW in color_modes + self.white_supported = COLOR_MODE_WHITE in color_modes self.brightness_supported = brightness_supported(color_modes) if self.brightness_supported: @@ -89,7 +97,9 @@ class Light(HomeAccessory): if self.color_supported: self.chars.extend([CHAR_HUE, CHAR_SATURATION]) - if self.color_temp_supported: + if self.color_temp_supported or COLOR_MODES_WITH_WHITES.intersection( + self.color_modes + ): self.chars.append(CHAR_COLOR_TEMPERATURE) serv_light = self.add_preload_service(SERV_LIGHTBULB, self.chars) @@ -101,13 +111,22 @@ class Light(HomeAccessory): # to set to the correct initial value. self.char_brightness = serv_light.configure_char(CHAR_BRIGHTNESS, value=100) - if self.color_temp_supported: - min_mireds = math.floor(attributes.get(ATTR_MIN_MIREDS, 153)) - max_mireds = math.ceil(attributes.get(ATTR_MAX_MIREDS, 500)) + if CHAR_COLOR_TEMPERATURE in self.chars: + self.min_mireds = math.floor( + attributes.get(ATTR_MIN_MIREDS, DEFAULT_MIN_MIREDS) + ) + self.max_mireds = math.ceil( + attributes.get(ATTR_MAX_MIREDS, DEFAULT_MAX_MIREDS) + ) + if not self.color_temp_supported and not self.rgbww_supported: + self.max_mireds = self.min_mireds self.char_color_temp = serv_light.configure_char( CHAR_COLOR_TEMPERATURE, - value=min_mireds, - properties={PROP_MIN_VALUE: min_mireds, PROP_MAX_VALUE: max_mireds}, + value=self.min_mireds, + properties={ + PROP_MIN_VALUE: self.min_mireds, + PROP_MAX_VALUE: self.max_mireds, + }, ) if self.color_supported: @@ -165,33 +184,32 @@ class Light(HomeAccessory): ) return + # Handle white channels if CHAR_COLOR_TEMPERATURE in char_values: - params[ATTR_COLOR_TEMP] = char_values[CHAR_COLOR_TEMPERATURE] - events.append(f"color temperature at {params[ATTR_COLOR_TEMP]}") + temp = char_values[CHAR_COLOR_TEMPERATURE] + events.append(f"color temperature at {temp}") + bright_val = round( + ((brightness_pct or self.char_brightness.value) * 255) / 100 + ) + if self.color_temp_supported: + params[ATTR_COLOR_TEMP] = temp + elif self.rgbww_supported: + params[ATTR_RGBWW_COLOR] = color_temperature_to_rgbww( + temp, bright_val, self.min_mireds, self.max_mireds + ) + elif self.rgbw_supported: + params[ATTR_RGBW_COLOR] = (*(0,) * 3, bright_val) + elif self.white_supported: + params[ATTR_WHITE] = bright_val - elif ( - CHAR_HUE in char_values - or CHAR_SATURATION in char_values - # If we are adjusting brightness we need to send the full RGBW/RGBWW values - # since HomeKit does not support RGBW/RGBWW - or brightness_pct - and COLOR_MODES_WITH_WHITES.intersection(self.color_modes) - ): + elif CHAR_HUE in char_values or CHAR_SATURATION in char_values: hue_sat = ( char_values.get(CHAR_HUE, self.char_hue.value), char_values.get(CHAR_SATURATION, self.char_saturation.value), ) _LOGGER.debug("%s: Set hs_color to %s", self.entity_id, hue_sat) events.append(f"set color at {hue_sat}") - # HomeKit doesn't support RGBW/RGBWW so we need to remove any white values - if COLOR_MODE_RGBWW in self.color_modes: - val = brightness_pct or self.char_brightness.value - params[ATTR_RGBWW_COLOR] = (*color_hsv_to_RGB(*hue_sat, val), 0, 0) - elif COLOR_MODE_RGBW in self.color_modes: - val = brightness_pct or self.char_brightness.value - params[ATTR_RGBW_COLOR] = (*color_hsv_to_RGB(*hue_sat, val), 0) - else: - params[ATTR_HS_COLOR] = hue_sat + params[ATTR_HS_COLOR] = hue_sat if ( brightness_pct @@ -200,6 +218,9 @@ class Light(HomeAccessory): ): params[ATTR_BRIGHTNESS_PCT] = brightness_pct + _LOGGER.debug( + "Calling light service with params: %s -> %s", char_values, params + ) self.async_call_service(DOMAIN, service, params, ", ".join(events)) @callback @@ -210,52 +231,59 @@ class Light(HomeAccessory): attributes = new_state.attributes color_mode = attributes.get(ATTR_COLOR_MODE) self.char_on.set_value(int(state == STATE_ON)) + color_mode_changed = self._previous_color_mode != color_mode + self._previous_color_mode = color_mode # Handle Brightness - if self.brightness_supported: - if ( - color_mode - and COLOR_MODES_WITH_WHITES.intersection({color_mode}) - and (rgb_color := attributes.get(ATTR_RGB_COLOR)) - ): - # HomeKit doesn't support RGBW/RGBWW so we need to - # give it the color brightness only - brightness = max(rgb_color) - else: - brightness = attributes.get(ATTR_BRIGHTNESS) - if isinstance(brightness, (int, float)): - brightness = round(brightness / 255 * 100, 0) - # The homeassistant component might report its brightness as 0 but is - # not off. But 0 is a special value in homekit. When you turn on a - # homekit accessory it will try to restore the last brightness state - # which will be the last value saved by char_brightness.set_value. - # But if it is set to 0, HomeKit will update the brightness to 100 as - # it thinks 0 is off. - # - # Therefore, if the the brightness is 0 and the device is still on, - # the brightness is mapped to 1 otherwise the update is ignored in - # order to avoid this incorrect behavior. - if brightness == 0 and state == STATE_ON: - brightness = 1 - self.char_brightness.set_value(brightness) + if ( + self.brightness_supported + and (brightness := attributes.get(ATTR_BRIGHTNESS)) is not None + and isinstance(brightness, (int, float)) + ): + brightness = round(brightness / 255 * 100, 0) + # The homeassistant component might report its brightness as 0 but is + # not off. But 0 is a special value in homekit. When you turn on a + # homekit accessory it will try to restore the last brightness state + # which will be the last value saved by char_brightness.set_value. + # But if it is set to 0, HomeKit will update the brightness to 100 as + # it thinks 0 is off. + # + # Therefore, if the the brightness is 0 and the device is still on, + # the brightness is mapped to 1 otherwise the update is ignored in + # order to avoid this incorrect behavior. + if brightness == 0 and state == STATE_ON: + brightness = 1 + self.char_brightness.set_value(brightness) + if color_mode_changed: + self.char_brightness.notify() # Handle Color - color must always be set before color temperature # or the iOS UI will not display it correctly. if self.color_supported: - if ATTR_COLOR_TEMP in attributes: + if color_temp := attributes.get(ATTR_COLOR_TEMP): hue, saturation = color_temperature_to_hs( - color_temperature_mired_to_kelvin( - new_state.attributes[ATTR_COLOR_TEMP] - ) + color_temperature_mired_to_kelvin(color_temp) ) + elif color_mode == COLOR_MODE_WHITE: + hue, saturation = 0, 0 else: hue, saturation = attributes.get(ATTR_HS_COLOR, (None, None)) if isinstance(hue, (int, float)) and isinstance(saturation, (int, float)): self.char_hue.set_value(round(hue, 0)) self.char_saturation.set_value(round(saturation, 0)) + if color_mode_changed: + # If the color temp changed, be sure to force the color to update + self.char_hue.notify() + self.char_saturation.notify() - # Handle color temperature - if self.color_temp_supported: - color_temp = attributes.get(ATTR_COLOR_TEMP) + # Handle white channels + if CHAR_COLOR_TEMPERATURE in self.chars: + color_temp = None + if self.color_temp_supported: + color_temp = attributes.get(ATTR_COLOR_TEMP) + elif color_mode == COLOR_MODE_WHITE: + color_temp = self.min_mireds if isinstance(color_temp, (int, float)): self.char_color_temp.set_value(round(color_temp, 0)) + if color_mode_changed: + self.char_color_temp.notify() diff --git a/homeassistant/components/image/manifest.json b/homeassistant/components/image/manifest.json index 5f624ea8e1c..2363b124e43 100644 --- a/homeassistant/components/image/manifest.json +++ b/homeassistant/components/image/manifest.json @@ -3,7 +3,7 @@ "name": "Image", "config_flow": false, "documentation": "https://www.home-assistant.io/integrations/image", - "requirements": ["pillow==9.0.0"], + "requirements": ["pillow==9.0.1"], "dependencies": ["http"], "codeowners": ["@home-assistant/core"], "quality_scale": "internal" diff --git a/homeassistant/components/intellifire/manifest.json b/homeassistant/components/intellifire/manifest.json index 42edf00ad25..965bb6f32db 100644 --- a/homeassistant/components/intellifire/manifest.json +++ b/homeassistant/components/intellifire/manifest.json @@ -3,7 +3,7 @@ "name": "IntelliFire", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/intellifire", - "requirements": ["intellifire4py==0.5"], + "requirements": ["intellifire4py==0.6"], "dependencies": [], "codeowners": ["@jeeftor"], "iot_class": "local_polling" diff --git a/homeassistant/components/knx/manifest.json b/homeassistant/components/knx/manifest.json index 9889f39ab35..663c0e5839a 100644 --- a/homeassistant/components/knx/manifest.json +++ b/homeassistant/components/knx/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/knx", "requirements": [ - "xknx==0.19.1" + "xknx==0.19.2" ], "codeowners": [ "@Julius2342", diff --git a/homeassistant/components/nest/diagnostics.py b/homeassistant/components/nest/diagnostics.py index 0b6cfff6bae..859aa834581 100644 --- a/homeassistant/components/nest/diagnostics.py +++ b/homeassistant/components/nest/diagnostics.py @@ -12,7 +12,7 @@ from google_nest_sdm.exceptions import ApiException from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant -from .const import DATA_SUBSCRIBER, DOMAIN +from .const import DATA_SDM, DATA_SUBSCRIBER, DOMAIN REDACT_DEVICE_TRAITS = {InfoTrait.NAME} @@ -21,6 +21,9 @@ async def async_get_config_entry_diagnostics( hass: HomeAssistant, config_entry: ConfigEntry ) -> dict: """Return diagnostics for a config entry.""" + if DATA_SDM not in config_entry.data: + return {} + if DATA_SUBSCRIBER not in hass.data[DOMAIN]: return {"error": "No subscriber configured"} diff --git a/homeassistant/components/ovo_energy/sensor.py b/homeassistant/components/ovo_energy/sensor.py index ba332a08a16..8f9a18d1f11 100644 --- a/homeassistant/components/ovo_energy/sensor.py +++ b/homeassistant/components/ovo_energy/sensor.py @@ -121,14 +121,22 @@ async def async_setup_entry( if coordinator.data: if coordinator.data.electricity: for description in SENSOR_TYPES_ELECTRICITY: - if description.key == KEY_LAST_ELECTRICITY_COST: + if ( + description.key == KEY_LAST_ELECTRICITY_COST + and coordinator.data.electricity[-1] is not None + and coordinator.data.electricity[-1].cost is not None + ): description.native_unit_of_measurement = ( coordinator.data.electricity[-1].cost.currency_unit ) entities.append(OVOEnergySensor(coordinator, description, client)) if coordinator.data.gas: for description in SENSOR_TYPES_GAS: - if description.key == KEY_LAST_GAS_COST: + if ( + description.key == KEY_LAST_GAS_COST + and coordinator.data.gas[-1] is not None + and coordinator.data.gas[-1].cost is not None + ): description.native_unit_of_measurement = coordinator.data.gas[ -1 ].cost.currency_unit diff --git a/homeassistant/components/proxy/manifest.json b/homeassistant/components/proxy/manifest.json index 4f19e6afae2..d1be59ebc87 100644 --- a/homeassistant/components/proxy/manifest.json +++ b/homeassistant/components/proxy/manifest.json @@ -2,6 +2,6 @@ "domain": "proxy", "name": "Camera Proxy", "documentation": "https://www.home-assistant.io/integrations/proxy", - "requirements": ["pillow==9.0.0"], + "requirements": ["pillow==9.0.1"], "codeowners": [] } diff --git a/homeassistant/components/qrcode/manifest.json b/homeassistant/components/qrcode/manifest.json index 63eca334d7b..37697f2af83 100644 --- a/homeassistant/components/qrcode/manifest.json +++ b/homeassistant/components/qrcode/manifest.json @@ -2,7 +2,7 @@ "domain": "qrcode", "name": "QR Code", "documentation": "https://www.home-assistant.io/integrations/qrcode", - "requirements": ["pillow==9.0.0", "pyzbar==0.1.7"], + "requirements": ["pillow==9.0.1", "pyzbar==0.1.7"], "codeowners": [], "iot_class": "calculated" } diff --git a/homeassistant/components/saj/sensor.py b/homeassistant/components/saj/sensor.py index 670455d7354..2fb3729d0a8 100644 --- a/homeassistant/components/saj/sensor.py +++ b/homeassistant/components/saj/sensor.py @@ -20,7 +20,6 @@ from homeassistant.const import ( CONF_TYPE, CONF_USERNAME, ENERGY_KILO_WATT_HOUR, - EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, MASS_KILOGRAMS, POWER_WATT, @@ -33,6 +32,7 @@ from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.event import async_call_later +from homeassistant.helpers.start import async_at_start from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType _LOGGER = logging.getLogger(__name__) @@ -131,17 +131,19 @@ async def async_setup_platform( return values + @callback def start_update_interval(event): """Start the update interval scheduling.""" nonlocal remove_interval_update remove_interval_update = async_track_time_interval_backoff(hass, async_saj) + @callback def stop_update_interval(event): """Properly cancel the scheduled update.""" remove_interval_update() # pylint: disable=not-callable - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, start_update_interval) hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP, stop_update_interval) + async_at_start(hass, start_update_interval) @callback diff --git a/homeassistant/components/seven_segments/manifest.json b/homeassistant/components/seven_segments/manifest.json index a49a471038c..db8e57673b1 100644 --- a/homeassistant/components/seven_segments/manifest.json +++ b/homeassistant/components/seven_segments/manifest.json @@ -2,7 +2,7 @@ "domain": "seven_segments", "name": "Seven Segments OCR", "documentation": "https://www.home-assistant.io/integrations/seven_segments", - "requirements": ["pillow==9.0.0"], + "requirements": ["pillow==9.0.1"], "codeowners": ["@fabaff"], "iot_class": "local_polling" } diff --git a/homeassistant/components/shelly/manifest.json b/homeassistant/components/shelly/manifest.json index 568f2b878ae..1d269705652 100644 --- a/homeassistant/components/shelly/manifest.json +++ b/homeassistant/components/shelly/manifest.json @@ -3,7 +3,7 @@ "name": "Shelly", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/shelly", - "requirements": ["aioshelly==1.0.8"], + "requirements": ["aioshelly==1.0.9"], "zeroconf": [ { "type": "_http._tcp.local.", diff --git a/homeassistant/components/sighthound/manifest.json b/homeassistant/components/sighthound/manifest.json index def1359b1ee..8edef306d8d 100644 --- a/homeassistant/components/sighthound/manifest.json +++ b/homeassistant/components/sighthound/manifest.json @@ -2,7 +2,7 @@ "domain": "sighthound", "name": "Sighthound", "documentation": "https://www.home-assistant.io/integrations/sighthound", - "requirements": ["pillow==9.0.0", "simplehound==0.3"], + "requirements": ["pillow==9.0.1", "simplehound==0.3"], "codeowners": ["@robmarkcole"], "iot_class": "cloud_polling" } diff --git a/homeassistant/components/simplisafe/__init__.py b/homeassistant/components/simplisafe/__init__.py index a133ec6c2dc..2fd7e10a2f8 100644 --- a/homeassistant/components/simplisafe/__init__.py +++ b/homeassistant/components/simplisafe/__init__.py @@ -482,6 +482,7 @@ class SimpliSafe: self._websocket_reconnect_task: asyncio.Task | None = None self.entry = entry self.initial_event_to_use: dict[int, dict[str, Any]] = {} + self.subscription_data: dict[int, Any] = api.subscription_data self.systems: dict[int, SystemType] = {} # This will get filled in by async_init: diff --git a/homeassistant/components/simplisafe/diagnostics.py b/homeassistant/components/simplisafe/diagnostics.py index bc0dddef47c..c7c03467c94 100644 --- a/homeassistant/components/simplisafe/diagnostics.py +++ b/homeassistant/components/simplisafe/diagnostics.py @@ -11,14 +11,28 @@ from homeassistant.core import HomeAssistant from . import SimpliSafe from .const import DOMAIN +CONF_CREDIT_CARD = "creditCard" +CONF_EXPIRES = "expires" +CONF_LOCATION = "location" +CONF_LOCATION_NAME = "locationName" +CONF_PAYMENT_PROFILE_ID = "paymentProfileId" CONF_SERIAL = "serial" +CONF_SID = "sid" CONF_SYSTEM_ID = "system_id" +CONF_UID = "uid" CONF_WIFI_SSID = "wifi_ssid" TO_REDACT = { CONF_ADDRESS, + CONF_CREDIT_CARD, + CONF_EXPIRES, + CONF_LOCATION, + CONF_LOCATION_NAME, + CONF_PAYMENT_PROFILE_ID, CONF_SERIAL, + CONF_SID, CONF_SYSTEM_ID, + CONF_UID, CONF_WIFI_SSID, } @@ -34,6 +48,7 @@ async def async_get_config_entry_diagnostics( "entry": { "options": dict(entry.options), }, + "subscription_data": simplisafe.subscription_data, "systems": [system.as_dict() for system in simplisafe.systems.values()], }, TO_REDACT, diff --git a/homeassistant/components/simplisafe/manifest.json b/homeassistant/components/simplisafe/manifest.json index fa343e7466a..62b5b9aa7b7 100644 --- a/homeassistant/components/simplisafe/manifest.json +++ b/homeassistant/components/simplisafe/manifest.json @@ -3,7 +3,7 @@ "name": "SimpliSafe", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/simplisafe", - "requirements": ["simplisafe-python==2022.01.0"], + "requirements": ["simplisafe-python==2022.02.0"], "codeowners": ["@bachya"], "iot_class": "cloud_polling", "dhcp": [ diff --git a/homeassistant/components/sonos/manifest.json b/homeassistant/components/sonos/manifest.json index b482556f287..bd16701435c 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": ["soco==0.26.0"], + "requirements": ["soco==0.26.2"], "dependencies": ["ssdp"], "after_dependencies": ["plex", "spotify", "zeroconf", "media_source"], "zeroconf": ["_sonos._tcp.local."], diff --git a/homeassistant/components/sonos/media_player.py b/homeassistant/components/sonos/media_player.py index d490120faf8..41453117c13 100644 --- a/homeassistant/components/sonos/media_player.py +++ b/homeassistant/components/sonos/media_player.py @@ -558,7 +558,7 @@ class SonosMediaPlayerEntity(SonosEntity, MediaPlayerEntity): plex_plugin.play_now(media) return - share_link = self.speaker.share_link + share_link = self.coordinator.share_link if share_link.is_share_link(media_id): if kwargs.get(ATTR_MEDIA_ENQUEUE): share_link.add_share_link_to_queue(media_id) diff --git a/homeassistant/components/synology_dsm/common.py b/homeassistant/components/synology_dsm/common.py index 273c9cc6a42..54a0735186f 100644 --- a/homeassistant/components/synology_dsm/common.py +++ b/homeassistant/components/synology_dsm/common.py @@ -16,6 +16,7 @@ from synology_dsm.api.storage.storage import SynoStorage from synology_dsm.api.surveillance_station import SynoSurveillanceStation from synology_dsm.exceptions import ( SynologyDSMAPIErrorException, + SynologyDSMException, SynologyDSMLoginFailedException, SynologyDSMRequestException, ) @@ -237,7 +238,11 @@ class SynoApi: async def async_unload(self) -> None: """Stop interacting with the NAS and prepare for removal from hass.""" - await self._syno_api_executer(self.dsm.logout) + try: + await self._syno_api_executer(self.dsm.logout) + except SynologyDSMException: + # ignore API errors during logout + pass async def async_update(self, now: timedelta | None = None) -> None: """Update function for updating API information.""" diff --git a/homeassistant/components/synology_dsm/config_flow.py b/homeassistant/components/synology_dsm/config_flow.py index 91ad49c5f84..256ad5eef8e 100644 --- a/homeassistant/components/synology_dsm/config_flow.py +++ b/homeassistant/components/synology_dsm/config_flow.py @@ -267,7 +267,7 @@ class SynologyDSMFlowHandler(ConfigFlow, domain=DOMAIN): and existing_entry.data[CONF_HOST] != parsed_url.hostname and not fqdn_with_ssl_verification ): - _LOGGER.debug( + _LOGGER.info( "Update host from '%s' to '%s' for NAS '%s' via SSDP discovery", existing_entry.data[CONF_HOST], parsed_url.hostname, diff --git a/homeassistant/components/tensorflow/manifest.json b/homeassistant/components/tensorflow/manifest.json index 26b7421ef44..b9b0ff6b5c5 100644 --- a/homeassistant/components/tensorflow/manifest.json +++ b/homeassistant/components/tensorflow/manifest.json @@ -7,7 +7,7 @@ "tf-models-official==2.5.0", "pycocotools==2.0.1", "numpy==1.21.4", - "pillow==9.0.0" + "pillow==9.0.1" ], "codeowners": [], "iot_class": "local_polling" diff --git a/homeassistant/const.py b/homeassistant/const.py index d05b222fc55..5c2939e0b1f 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -7,7 +7,7 @@ from .backports.enum import StrEnum MAJOR_VERSION: Final = 2022 MINOR_VERSION: Final = 2 -PATCH_VERSION: Final = "2" +PATCH_VERSION: Final = "3" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0) diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 3f0b9516ead..5cded6a179d 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -20,7 +20,7 @@ httpx==0.21.3 ifaddr==0.1.7 jinja2==3.0.3 paho-mqtt==1.6.1 -pillow==9.0.0 +pillow==9.0.1 pip>=8.0.3,<20.3 pyserial==3.5 python-slugify==4.0.1 diff --git a/homeassistant/util/color.py b/homeassistant/util/color.py index f055a5f32eb..21c877f9377 100644 --- a/homeassistant/util/color.py +++ b/homeassistant/util/color.py @@ -472,7 +472,10 @@ def color_rgbww_to_rgb( except ZeroDivisionError: ct_ratio = 0.5 color_temp_mired = min_mireds + ct_ratio * mired_range - color_temp_kelvin = color_temperature_mired_to_kelvin(color_temp_mired) + if color_temp_mired: + color_temp_kelvin = color_temperature_mired_to_kelvin(color_temp_mired) + else: + color_temp_kelvin = 0 w_r, w_g, w_b = color_temperature_to_rgb(color_temp_kelvin) white_level = max(cw, ww) / 255 diff --git a/requirements_all.txt b/requirements_all.txt index e4ed0d86319..7f6e269746b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -254,7 +254,7 @@ aioridwell==2021.12.2 aiosenseme==0.6.1 # homeassistant.components.shelly -aioshelly==1.0.8 +aioshelly==1.0.9 # homeassistant.components.steamist aiosteamist==0.3.1 @@ -681,7 +681,7 @@ fjaraskupan==1.0.2 flipr-api==1.4.1 # homeassistant.components.flux_led -flux_led==0.28.20 +flux_led==0.28.21 # homeassistant.components.homekit fnvhash==0.1.0 @@ -914,7 +914,7 @@ influxdb-client==1.24.0 influxdb==5.3.1 # homeassistant.components.intellifire -intellifire4py==0.5 +intellifire4py==0.6 # homeassistant.components.iotawatt iotawattpy==0.1.0 @@ -1249,7 +1249,7 @@ pilight==0.1.1 # homeassistant.components.seven_segments # homeassistant.components.sighthound # homeassistant.components.tensorflow -pillow==9.0.0 +pillow==9.0.1 # homeassistant.components.dominos pizzapi==0.0.3 @@ -2190,7 +2190,7 @@ simplehound==0.3 simplepush==1.1.4 # homeassistant.components.simplisafe -simplisafe-python==2022.01.0 +simplisafe-python==2022.02.0 # homeassistant.components.sisyphus sisyphus-control==3.1.2 @@ -2228,7 +2228,7 @@ smhi-pkg==1.0.15 snapcast==2.1.3 # homeassistant.components.sonos -soco==0.26.0 +soco==0.26.2 # homeassistant.components.solaredge_local solaredge-local==0.2.0 @@ -2496,7 +2496,7 @@ xbox-webapi==2.0.11 xboxapi==2.0.1 # homeassistant.components.knx -xknx==0.19.1 +xknx==0.19.2 # homeassistant.components.bluesound # homeassistant.components.fritz diff --git a/requirements_test_all.txt b/requirements_test_all.txt index cf75245b5db..901f753105d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -189,7 +189,7 @@ aioridwell==2021.12.2 aiosenseme==0.6.1 # homeassistant.components.shelly -aioshelly==1.0.8 +aioshelly==1.0.9 # homeassistant.components.steamist aiosteamist==0.3.1 @@ -427,7 +427,7 @@ fjaraskupan==1.0.2 flipr-api==1.4.1 # homeassistant.components.flux_led -flux_led==0.28.20 +flux_led==0.28.21 # homeassistant.components.homekit fnvhash==0.1.0 @@ -586,7 +586,7 @@ influxdb-client==1.24.0 influxdb==5.3.1 # homeassistant.components.intellifire -intellifire4py==0.5 +intellifire4py==0.6 # homeassistant.components.iotawatt iotawattpy==0.1.0 @@ -771,7 +771,7 @@ pilight==0.1.1 # homeassistant.components.seven_segments # homeassistant.components.sighthound # homeassistant.components.tensorflow -pillow==9.0.0 +pillow==9.0.1 # homeassistant.components.plex plexapi==4.9.1 @@ -1337,7 +1337,7 @@ sharkiqpy==0.1.8 simplehound==0.3 # homeassistant.components.simplisafe -simplisafe-python==2022.01.0 +simplisafe-python==2022.02.0 # homeassistant.components.slack slackclient==2.5.0 @@ -1355,7 +1355,7 @@ smarthab==0.21 smhi-pkg==1.0.15 # homeassistant.components.sonos -soco==0.26.0 +soco==0.26.2 # homeassistant.components.solaredge solaredge==0.0.2 @@ -1527,7 +1527,7 @@ wolf_smartset==0.1.11 xbox-webapi==2.0.11 # homeassistant.components.knx -xknx==0.19.1 +xknx==0.19.2 # homeassistant.components.bluesound # homeassistant.components.fritz diff --git a/setup.cfg b/setup.cfg index c1de0930cbc..8d9f8974eb7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = homeassistant -version = 2022.2.2 +version = 2022.2.3 author = The Home Assistant Authors author_email = hello@home-assistant.io license = Apache-2.0 diff --git a/tests/components/androidtv/patchers.py b/tests/components/androidtv/patchers.py index 4411945c71b..7cc14bbd7b5 100644 --- a/tests/components/androidtv/patchers.py +++ b/tests/components/androidtv/patchers.py @@ -1,13 +1,17 @@ """Define patches used for androidtv tests.""" - from unittest.mock import mock_open, patch +from androidtv.constants import CMD_DEVICE_PROPERTIES, CMD_MAC_ETH0, CMD_MAC_WLAN0 + KEY_PYTHON = "python" KEY_SERVER = "server" ADB_DEVICE_TCP_ASYNC_FAKE = "AdbDeviceTcpAsyncFake" DEVICE_ASYNC_FAKE = "DeviceAsyncFake" +PROPS_DEV_INFO = "fake\nfake\n0123456\nfake" +PROPS_DEV_MAC = "ether ab:cd:ef:gh:ij:kl brd" + class AdbDeviceTcpAsyncFake: """A fake of the `adb_shell.adb_device_async.AdbDeviceTcpAsync` class.""" @@ -100,12 +104,18 @@ def patch_connect(success): } -def patch_shell(response=None, error=False): +def patch_shell(response=None, error=False, mac_eth=False): """Mock the `AdbDeviceTcpAsyncFake.shell` and `DeviceAsyncFake.shell` methods.""" async def shell_success(self, cmd, *args, **kwargs): """Mock the `AdbDeviceTcpAsyncFake.shell` and `DeviceAsyncFake.shell` methods when they are successful.""" self.shell_cmd = cmd + if cmd == CMD_DEVICE_PROPERTIES: + return PROPS_DEV_INFO + if cmd == CMD_MAC_WLAN0: + return PROPS_DEV_MAC + if cmd == CMD_MAC_ETH0: + return PROPS_DEV_MAC if mac_eth else None return response async def shell_fail_python(self, cmd, *args, **kwargs): @@ -185,15 +195,3 @@ PATCH_ANDROIDTV_UPDATE_EXCEPTION = patch( "androidtv.androidtv.androidtv_async.AndroidTVAsync.update", side_effect=ZeroDivisionError, ) - -PATCH_DEVICE_PROPERTIES = patch( - "androidtv.basetv.basetv_async.BaseTVAsync.get_device_properties", - return_value={ - "manufacturer": "a", - "model": "b", - "serialno": "c", - "sw_version": "d", - "wifimac": "ab:cd:ef:gh:ij:kl", - "ethmac": None, - }, -) diff --git a/tests/components/androidtv/test_config_flow.py b/tests/components/androidtv/test_config_flow.py index 757be8f6d8d..991d3757749 100644 --- a/tests/components/androidtv/test_config_flow.py +++ b/tests/components/androidtv/test_config_flow.py @@ -31,6 +31,7 @@ from homeassistant.components.androidtv.const import ( DEFAULT_PORT, DOMAIN, PROP_ETHMAC, + PROP_WIFIMAC, ) from homeassistant.components.media_player import DOMAIN as MP_DOMAIN from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER @@ -42,6 +43,7 @@ from tests.components.androidtv.patchers import isfile ADBKEY = "adbkey" ETH_MAC = "a1:b1:c1:d1:e1:f1" +WIFI_MAC = "a2:b2:c2:d2:e2:f2" HOST = "127.0.0.1" VALID_DETECT_RULE = [{"paused": {"media_session_state": 3}}] @@ -84,18 +86,28 @@ PATCH_SETUP_ENTRY = patch( class MockConfigDevice: """Mock class to emulate Android TV device.""" - def __init__(self, eth_mac=ETH_MAC): + def __init__(self, eth_mac=ETH_MAC, wifi_mac=None): """Initialize a fake device to test config flow.""" self.available = True - self.device_properties = {PROP_ETHMAC: eth_mac} + self.device_properties = {PROP_ETHMAC: eth_mac, PROP_WIFIMAC: wifi_mac} async def adb_close(self): """Fake method to close connection.""" self.available = False -@pytest.mark.parametrize("config", [CONFIG_PYTHON_ADB, CONFIG_ADB_SERVER]) -async def test_user(hass, config): +@pytest.mark.parametrize( + ["config", "eth_mac", "wifi_mac"], + [ + (CONFIG_PYTHON_ADB, ETH_MAC, None), + (CONFIG_ADB_SERVER, ETH_MAC, None), + (CONFIG_PYTHON_ADB, None, WIFI_MAC), + (CONFIG_ADB_SERVER, None, WIFI_MAC), + (CONFIG_PYTHON_ADB, ETH_MAC, WIFI_MAC), + (CONFIG_ADB_SERVER, ETH_MAC, WIFI_MAC), + ], +) +async def test_user(hass, config, eth_mac, wifi_mac): """Test user config.""" flow_result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER, "show_advanced_options": True} @@ -106,7 +118,7 @@ async def test_user(hass, config): # test with all provided with patch( CONNECT_METHOD, - return_value=(MockConfigDevice(), None), + return_value=(MockConfigDevice(eth_mac, wifi_mac), None), ), PATCH_SETUP_ENTRY as mock_setup_entry, PATCH_GET_HOST_IP: result = await hass.config_entries.flow.async_configure( flow_result["flow_id"], user_input=config @@ -273,7 +285,7 @@ async def test_invalid_serial(hass): """Test for invalid serialno.""" with patch( CONNECT_METHOD, - return_value=(MockConfigDevice(eth_mac=""), None), + return_value=(MockConfigDevice(eth_mac=None), None), ), PATCH_GET_HOST_IP: result = await hass.config_entries.flow.async_init( DOMAIN, diff --git a/tests/components/androidtv/test_media_player.py b/tests/components/androidtv/test_media_player.py index e97de0fc928..a0bab1736ff 100644 --- a/tests/components/androidtv/test_media_player.py +++ b/tests/components/androidtv/test_media_player.py @@ -142,29 +142,6 @@ def _setup(config): return patch_key, entity_id, config_entry -async def test_setup_with_properties(hass): - """Test that setup succeeds with device properties. - - the response must be a string with the following info separated with line break: - "manufacturer, model, serialno, version, mac_wlan0_output, mac_eth0_output" - - """ - - patch_key, entity_id, config_entry = _setup(CONFIG_ANDROIDTV_ADB_SERVER) - config_entry.add_to_hass(hass) - response = "fake\nfake\n0123456\nfake\nether a1:b1:c1:d1:e1:f1 brd\nnone" - - with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ - patch_key - ], patchers.patch_shell(response)[patch_key]: - with patchers.PATCH_DEVICE_PROPERTIES: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() - - state = hass.states.get(entity_id) - assert state is not None - - @pytest.mark.parametrize( "config", [ @@ -190,9 +167,8 @@ async def test_reconnect(hass, caplog, config): ], patchers.patch_shell(SHELL_RESPONSE_OFF)[ patch_key ], patchers.PATCH_KEYGEN, patchers.PATCH_ANDROIDTV_OPEN, patchers.PATCH_SIGNER: - with patchers.PATCH_DEVICE_PROPERTIES: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() await hass.helpers.entity_component.async_update_entity(entity_id) state = hass.states.get(entity_id) @@ -259,9 +235,8 @@ async def test_adb_shell_returns_none(hass, config): ], patchers.patch_shell(SHELL_RESPONSE_OFF)[ patch_key ], patchers.PATCH_KEYGEN, patchers.PATCH_ANDROIDTV_OPEN, patchers.PATCH_SIGNER: - with patchers.PATCH_DEVICE_PROPERTIES: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() await hass.helpers.entity_component.async_update_entity(entity_id) state = hass.states.get(entity_id) @@ -289,9 +264,8 @@ async def test_setup_with_adbkey(hass): ], patchers.patch_shell(SHELL_RESPONSE_OFF)[ patch_key ], patchers.PATCH_ANDROIDTV_OPEN, patchers.PATCH_SIGNER, PATCH_ISFILE, PATCH_ACCESS: - with patchers.PATCH_DEVICE_PROPERTIES: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() await hass.helpers.entity_component.async_update_entity(entity_id) state = hass.states.get(entity_id) @@ -324,9 +298,8 @@ async def test_sources(hass, config0): with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: - with patchers.PATCH_DEVICE_PROPERTIES: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() await hass.helpers.entity_component.async_update_entity(entity_id) state = hass.states.get(entity_id) @@ -404,9 +377,8 @@ async def _test_exclude_sources(hass, config0, expected_sources): with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: - with patchers.PATCH_DEVICE_PROPERTIES: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() await hass.helpers.entity_component.async_update_entity(entity_id) state = hass.states.get(entity_id) @@ -486,9 +458,8 @@ async def _test_select_source(hass, config0, source, expected_arg, method_patch) with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: - with patchers.PATCH_DEVICE_PROPERTIES: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() await hass.helpers.entity_component.async_update_entity(entity_id) state = hass.states.get(entity_id) @@ -714,9 +685,8 @@ async def test_setup_fail(hass, config): ], patchers.patch_shell(SHELL_RESPONSE_OFF)[ patch_key ], patchers.PATCH_KEYGEN, patchers.PATCH_ANDROIDTV_OPEN, patchers.PATCH_SIGNER: - with patchers.PATCH_DEVICE_PROPERTIES: - assert await hass.config_entries.async_setup(config_entry.entry_id) is False - await hass.async_block_till_done() + assert await hass.config_entries.async_setup(config_entry.entry_id) is False + await hass.async_block_till_done() await hass.helpers.entity_component.async_update_entity(entity_id) state = hass.states.get(entity_id) @@ -733,9 +703,8 @@ async def test_adb_command(hass): with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: - with patchers.PATCH_DEVICE_PROPERTIES: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() with patch( "androidtv.basetv.basetv_async.BaseTVAsync.adb_shell", return_value=response @@ -763,9 +732,8 @@ async def test_adb_command_unicode_decode_error(hass): with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: - with patchers.PATCH_DEVICE_PROPERTIES: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() with patch( "androidtv.basetv.basetv_async.BaseTVAsync.adb_shell", @@ -793,9 +761,8 @@ async def test_adb_command_key(hass): with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: - with patchers.PATCH_DEVICE_PROPERTIES: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() with patch( "androidtv.basetv.basetv_async.BaseTVAsync.adb_shell", return_value=response @@ -823,9 +790,8 @@ async def test_adb_command_get_properties(hass): with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: - with patchers.PATCH_DEVICE_PROPERTIES: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() with patch( "androidtv.androidtv.androidtv_async.AndroidTVAsync.get_properties_dict", @@ -853,9 +819,8 @@ async def test_learn_sendevent(hass): with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: - with patchers.PATCH_DEVICE_PROPERTIES: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() with patch( "androidtv.basetv.basetv_async.BaseTVAsync.learn_sendevent", @@ -882,9 +847,8 @@ async def test_update_lock_not_acquired(hass): with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: - with patchers.PATCH_DEVICE_PROPERTIES: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() with patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: await hass.helpers.entity_component.async_update_entity(entity_id) @@ -918,9 +882,8 @@ async def test_download(hass): with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: - with patchers.PATCH_DEVICE_PROPERTIES: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() # Failed download because path is not whitelisted with patch("androidtv.basetv.basetv_async.BaseTVAsync.adb_pull") as patch_pull: @@ -965,9 +928,8 @@ async def test_upload(hass): with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: - with patchers.PATCH_DEVICE_PROPERTIES: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() # Failed upload because path is not whitelisted with patch("androidtv.basetv.basetv_async.BaseTVAsync.adb_push") as patch_push: @@ -1010,9 +972,8 @@ async def test_androidtv_volume_set(hass): with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: - with patchers.PATCH_DEVICE_PROPERTIES: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() with patch( "androidtv.basetv.basetv_async.BaseTVAsync.set_volume_level", return_value=0.5 @@ -1038,9 +999,8 @@ async def test_get_image(hass, hass_ws_client): with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: - with patchers.PATCH_DEVICE_PROPERTIES: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() with patchers.patch_shell("11")[patch_key]: await hass.helpers.entity_component.async_update_entity(entity_id) @@ -1115,9 +1075,8 @@ async def test_services_androidtv(hass): with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[patch_key]: with patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: - with patchers.PATCH_DEVICE_PROPERTIES: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() with patchers.patch_shell(SHELL_RESPONSE_STANDBY)[patch_key]: await _test_service( @@ -1162,9 +1121,8 @@ async def test_services_firetv(hass): with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[patch_key]: with patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: - with patchers.PATCH_DEVICE_PROPERTIES: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() with patchers.patch_shell(SHELL_RESPONSE_STANDBY)[patch_key]: await _test_service(hass, entity_id, SERVICE_MEDIA_STOP, "back") @@ -1179,9 +1137,8 @@ async def test_volume_mute(hass): with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[patch_key]: with patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: - with patchers.PATCH_DEVICE_PROPERTIES: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() with patchers.patch_shell(SHELL_RESPONSE_STANDBY)[patch_key]: service_data = {ATTR_ENTITY_ID: entity_id, ATTR_MEDIA_VOLUME_MUTED: True} @@ -1224,9 +1181,8 @@ async def test_connection_closed_on_ha_stop(hass): with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: - with patchers.PATCH_DEVICE_PROPERTIES: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() with patch( "androidtv.androidtv.androidtv_async.AndroidTVAsync.adb_close" @@ -1249,9 +1205,8 @@ async def test_exception(hass): ], patchers.patch_shell(SHELL_RESPONSE_OFF)[ patch_key ], patchers.PATCH_KEYGEN, patchers.PATCH_ANDROIDTV_OPEN, patchers.PATCH_SIGNER: - with patchers.PATCH_DEVICE_PROPERTIES: - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() await hass.helpers.entity_component.async_update_entity(entity_id) state = hass.states.get(entity_id) diff --git a/tests/components/dhcp/test_init.py b/tests/components/dhcp/test_init.py index fb3387aeab6..0956230d787 100644 --- a/tests/components/dhcp/test_init.py +++ b/tests/components/dhcp/test_init.py @@ -255,6 +255,33 @@ async def test_dhcp_match_macaddress(hass): ) +async def test_dhcp_multiple_match_only_one_flow(hass): + """Test matching the domain multiple times only generates one flow.""" + integration_matchers = [ + {"domain": "mock-domain", "macaddress": "B8B7F1*"}, + {"domain": "mock-domain", "hostname": "connect"}, + ] + + packet = Ether(RAW_DHCP_REQUEST) + + async_handle_dhcp_packet = await _async_get_handle_dhcp_packet( + hass, integration_matchers + ) + with patch.object(hass.config_entries.flow, "async_init") as mock_init: + await async_handle_dhcp_packet(packet) + + assert len(mock_init.mock_calls) == 1 + assert mock_init.mock_calls[0][1][0] == "mock-domain" + assert mock_init.mock_calls[0][2]["context"] == { + "source": config_entries.SOURCE_DHCP + } + assert mock_init.mock_calls[0][2]["data"] == dhcp.DhcpServiceInfo( + ip="192.168.210.56", + hostname="connect", + macaddress="b8b7f16db533", + ) + + async def test_dhcp_match_macaddress_without_hostname(hass): """Test matching based on macaddress only.""" integration_matchers = [{"domain": "mock-domain", "macaddress": "606BBD*"}] diff --git a/tests/components/homekit/test_diagnostics.py b/tests/components/homekit/test_diagnostics.py new file mode 100644 index 00000000000..e3a85b85972 --- /dev/null +++ b/tests/components/homekit/test_diagnostics.py @@ -0,0 +1,119 @@ +"""Test homekit diagnostics.""" +from unittest.mock import ANY, patch + +from homeassistant.components.homekit.const import DOMAIN +from homeassistant.const import CONF_NAME, CONF_PORT, EVENT_HOMEASSISTANT_STARTED + +from .util import async_init_integration + +from tests.common import MockConfigEntry +from tests.components.diagnostics import get_diagnostics_for_config_entry + + +async def test_config_entry_not_running( + hass, hass_client, hk_driver, mock_async_zeroconf +): + """Test generating diagnostics for a config entry.""" + entry = await async_init_integration(hass) + diag = await get_diagnostics_for_config_entry(hass, hass_client, entry) + assert diag == { + "config-entry": { + "data": {"name": "mock_name", "port": 12345}, + "options": {}, + "title": "Mock Title", + "version": 1, + }, + "status": 0, + } + + +async def test_config_entry_running(hass, hass_client, hk_driver, mock_async_zeroconf): + """Test generating diagnostics for a config entry.""" + entry = MockConfigEntry( + domain=DOMAIN, data={CONF_NAME: "mock_name", CONF_PORT: 12345} + ) + entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(entry.entry_id) + hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) + await hass.async_block_till_done() + diag = await get_diagnostics_for_config_entry(hass, hass_client, entry) + assert diag == { + "accessories": [ + { + "aid": 1, + "services": [ + { + "characteristics": [ + {"format": "bool", "iid": 2, "perms": ["pw"], "type": "14"}, + { + "format": "string", + "iid": 3, + "perms": ["pr"], + "type": "20", + "value": "Home Assistant", + }, + { + "format": "string", + "iid": 4, + "perms": ["pr"], + "type": "21", + "value": "Bridge", + }, + { + "format": "string", + "iid": 5, + "perms": ["pr"], + "type": "23", + "value": "mock_name", + }, + { + "format": "string", + "iid": 6, + "perms": ["pr"], + "type": "30", + "value": "homekit.bridge", + }, + { + "format": "string", + "iid": 7, + "perms": ["pr"], + "type": "52", + "value": ANY, + }, + ], + "iid": 1, + "type": "3E", + }, + { + "characteristics": [ + { + "format": "string", + "iid": 9, + "perms": ["pr", "ev"], + "type": "37", + "value": "01.01.00", + } + ], + "iid": 8, + "type": "A2", + }, + ], + } + ], + "client_properties": {}, + "config-entry": { + "data": {"name": "mock_name", "port": 12345}, + "options": {}, + "title": "Mock Title", + "version": 1, + }, + "config_version": 2, + "pairing_id": ANY, + "status": 1, + } + + with patch("pyhap.accessory_driver.AccessoryDriver.async_start"), patch( + "homeassistant.components.homekit.HomeKit.async_stop" + ), patch("homeassistant.components.homekit.async_port_is_available"): + assert await hass.config_entries.async_unload(entry.entry_id) + await hass.async_block_till_done() diff --git a/tests/components/homekit/test_type_lights.py b/tests/components/homekit/test_type_lights.py index 8e7b60b0a47..6835629be37 100644 --- a/tests/components/homekit/test_type_lights.py +++ b/tests/components/homekit/test_type_lights.py @@ -5,7 +5,11 @@ from datetime import timedelta from pyhap.const import HAP_REPR_AID, HAP_REPR_CHARS, HAP_REPR_IID, HAP_REPR_VALUE import pytest -from homeassistant.components.homekit.const import ATTR_VALUE +from homeassistant.components.homekit.const import ( + ATTR_VALUE, + PROP_MAX_VALUE, + PROP_MIN_VALUE, +) from homeassistant.components.homekit.type_lights import ( CHANGE_COALESCE_TIME_WINDOW, Light, @@ -22,9 +26,12 @@ from homeassistant.components.light import ( ATTR_RGBW_COLOR, ATTR_RGBWW_COLOR, ATTR_SUPPORTED_COLOR_MODES, + ATTR_WHITE, COLOR_MODE_COLOR_TEMP, + COLOR_MODE_RGB, COLOR_MODE_RGBW, COLOR_MODE_RGBWW, + COLOR_MODE_WHITE, DOMAIN, ) from homeassistant.const import ( @@ -573,7 +580,7 @@ async def test_light_restore(hass, hk_driver, events): @pytest.mark.parametrize( - "supported_color_modes, state_props, turn_on_props, turn_on_props_with_brightness", + "supported_color_modes, state_props, turn_on_props_with_brightness", [ [ [COLOR_MODE_COLOR_TEMP, COLOR_MODE_RGBW], @@ -584,8 +591,7 @@ async def test_light_restore(hass, hk_driver, events): ATTR_BRIGHTNESS: 255, ATTR_COLOR_MODE: COLOR_MODE_RGBW, }, - {ATTR_RGBW_COLOR: (31, 127, 71, 0)}, - {ATTR_RGBW_COLOR: (15, 63, 35, 0)}, + {ATTR_HS_COLOR: (145, 75), ATTR_BRIGHTNESS_PCT: 25}, ], [ [COLOR_MODE_COLOR_TEMP, COLOR_MODE_RGBWW], @@ -596,21 +602,19 @@ async def test_light_restore(hass, hk_driver, events): ATTR_BRIGHTNESS: 255, ATTR_COLOR_MODE: COLOR_MODE_RGBWW, }, - {ATTR_RGBWW_COLOR: (31, 127, 71, 0, 0)}, - {ATTR_RGBWW_COLOR: (15, 63, 35, 0, 0)}, + {ATTR_HS_COLOR: (145, 75), ATTR_BRIGHTNESS_PCT: 25}, ], ], ) -async def test_light_rgb_with_white( +async def test_light_rgb_with_color_temp( hass, hk_driver, events, supported_color_modes, state_props, - turn_on_props, turn_on_props_with_brightness, ): - """Test lights with RGBW/RGBWW.""" + """Test lights with RGBW/RGBWW with color temp support.""" entity_id = "light.demo" hass.states.async_set( @@ -629,7 +633,7 @@ async def test_light_rgb_with_white( await hass.async_block_till_done() assert acc.char_hue.value == 23 assert acc.char_saturation.value == 100 - assert acc.char_brightness.value == 50 + assert acc.char_brightness.value == 100 # Set from HomeKit call_turn_on = async_mock_service(hass, DOMAIN, "turn_on") @@ -658,11 +662,10 @@ async def test_light_rgb_with_white( await _wait_for_light_coalesce(hass) assert call_turn_on assert call_turn_on[-1].data[ATTR_ENTITY_ID] == entity_id - for k, v in turn_on_props.items(): - assert call_turn_on[-1].data[k] == v + assert call_turn_on[-1].data[ATTR_HS_COLOR] == (145, 75) assert len(events) == 1 assert events[-1].data[ATTR_VALUE] == "set color at (145, 75)" - assert acc.char_brightness.value == 50 + assert acc.char_brightness.value == 100 hk_driver.set_characteristics( { @@ -697,7 +700,204 @@ async def test_light_rgb_with_white( @pytest.mark.parametrize( - "supported_color_modes, state_props, turn_on_props, turn_on_props_with_brightness", + "supported_color_modes, state_props, turn_on_props_with_brightness", + [ + [ + [COLOR_MODE_RGBW], + { + ATTR_RGBW_COLOR: (128, 50, 0, 255), + ATTR_RGB_COLOR: (128, 50, 0), + ATTR_HS_COLOR: (23.438, 100.0), + ATTR_BRIGHTNESS: 255, + ATTR_COLOR_MODE: COLOR_MODE_RGBW, + }, + {ATTR_RGBW_COLOR: (0, 0, 0, 191)}, + ], + [ + [COLOR_MODE_RGBWW], + { + ATTR_RGBWW_COLOR: (128, 50, 0, 255, 255), + ATTR_RGB_COLOR: (128, 50, 0), + ATTR_HS_COLOR: (23.438, 100.0), + ATTR_BRIGHTNESS: 255, + ATTR_COLOR_MODE: COLOR_MODE_RGBWW, + }, + {ATTR_RGBWW_COLOR: (0, 0, 0, 165, 26)}, + ], + ], +) +async def test_light_rgbwx_with_color_temp_and_brightness( + hass, + hk_driver, + events, + supported_color_modes, + state_props, + turn_on_props_with_brightness, +): + """Test lights with RGBW/RGBWW with color temp support and setting brightness.""" + entity_id = "light.demo" + + hass.states.async_set( + entity_id, + STATE_ON, + {ATTR_SUPPORTED_COLOR_MODES: supported_color_modes, **state_props}, + ) + await hass.async_block_till_done() + acc = Light(hass, hk_driver, "Light", entity_id, 1, None) + hk_driver.add_accessory(acc) + + assert acc.char_hue.value == 23 + assert acc.char_saturation.value == 100 + + await acc.run() + await hass.async_block_till_done() + assert acc.char_hue.value == 23 + assert acc.char_saturation.value == 100 + assert acc.char_brightness.value == 100 + + # Set from HomeKit + call_turn_on = async_mock_service(hass, DOMAIN, "turn_on") + + char_color_temp_iid = acc.char_color_temp.to_HAP()[HAP_REPR_IID] + char_brightness_iid = acc.char_brightness.to_HAP()[HAP_REPR_IID] + + hk_driver.set_characteristics( + { + HAP_REPR_CHARS: [ + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_color_temp_iid, + HAP_REPR_VALUE: 200, + }, + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_brightness_iid, + HAP_REPR_VALUE: 75, + }, + ] + }, + "mock_addr", + ) + await _wait_for_light_coalesce(hass) + assert call_turn_on + assert call_turn_on[-1].data[ATTR_ENTITY_ID] == entity_id + for k, v in turn_on_props_with_brightness.items(): + assert call_turn_on[-1].data[k] == v + assert len(events) == 1 + assert events[-1].data[ATTR_VALUE] == "brightness at 75%, color temperature at 200" + assert acc.char_brightness.value == 75 + + +async def test_light_rgb_or_w_lights( + hass, + hk_driver, + events, +): + """Test lights with RGB or W lights.""" + entity_id = "light.demo" + + hass.states.async_set( + entity_id, + STATE_ON, + { + ATTR_SUPPORTED_COLOR_MODES: [COLOR_MODE_RGB, COLOR_MODE_WHITE], + ATTR_RGBW_COLOR: (128, 50, 0, 255), + ATTR_RGB_COLOR: (128, 50, 0), + ATTR_HS_COLOR: (23.438, 100.0), + ATTR_BRIGHTNESS: 255, + ATTR_COLOR_MODE: COLOR_MODE_RGB, + }, + ) + await hass.async_block_till_done() + acc = Light(hass, hk_driver, "Light", entity_id, 1, None) + hk_driver.add_accessory(acc) + + assert acc.char_hue.value == 23 + assert acc.char_saturation.value == 100 + + await acc.run() + await hass.async_block_till_done() + assert acc.char_hue.value == 23 + assert acc.char_saturation.value == 100 + assert acc.char_brightness.value == 100 + assert acc.char_color_temp.value == 153 + + # Set from HomeKit + call_turn_on = async_mock_service(hass, DOMAIN, "turn_on") + + char_hue_iid = acc.char_hue.to_HAP()[HAP_REPR_IID] + char_saturation_iid = acc.char_saturation.to_HAP()[HAP_REPR_IID] + char_brightness_iid = acc.char_brightness.to_HAP()[HAP_REPR_IID] + char_color_temp_iid = acc.char_color_temp.to_HAP()[HAP_REPR_IID] + + hk_driver.set_characteristics( + { + HAP_REPR_CHARS: [ + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_hue_iid, + HAP_REPR_VALUE: 145, + }, + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_saturation_iid, + HAP_REPR_VALUE: 75, + }, + ] + }, + "mock_addr", + ) + await _wait_for_light_coalesce(hass) + assert call_turn_on + assert call_turn_on[-1].data[ATTR_ENTITY_ID] == entity_id + assert call_turn_on[-1].data[ATTR_HS_COLOR] == (145, 75) + assert len(events) == 1 + assert events[-1].data[ATTR_VALUE] == "set color at (145, 75)" + assert acc.char_brightness.value == 100 + + hk_driver.set_characteristics( + { + HAP_REPR_CHARS: [ + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_color_temp_iid, + HAP_REPR_VALUE: acc.min_mireds, + }, + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_brightness_iid, + HAP_REPR_VALUE: 25, + }, + ] + }, + "mock_addr", + ) + await _wait_for_light_coalesce(hass) + assert call_turn_on + assert call_turn_on[-1].data[ATTR_ENTITY_ID] == entity_id + assert call_turn_on[-1].data[ATTR_WHITE] == round(25 * 255 / 100) + assert len(events) == 2 + assert events[-1].data[ATTR_VALUE] == "brightness at 25%, color temperature at 153" + assert acc.char_brightness.value == 25 + + hass.states.async_set( + entity_id, + STATE_ON, + { + ATTR_SUPPORTED_COLOR_MODES: [COLOR_MODE_RGB, COLOR_MODE_WHITE], + ATTR_BRIGHTNESS: 255, + ATTR_COLOR_MODE: COLOR_MODE_WHITE, + }, + ) + await hass.async_block_till_done() + assert acc.char_hue.value == 0 + assert acc.char_saturation.value == 0 + assert acc.char_brightness.value == 100 + assert acc.char_color_temp.value == 153 + + +@pytest.mark.parametrize( + "supported_color_modes, state_props", [ [ [COLOR_MODE_COLOR_TEMP, COLOR_MODE_RGBW], @@ -708,8 +908,6 @@ async def test_light_rgb_with_white( ATTR_BRIGHTNESS: 255, ATTR_COLOR_MODE: COLOR_MODE_RGBW, }, - {ATTR_RGBW_COLOR: (31, 127, 71, 0)}, - {ATTR_COLOR_TEMP: 2700}, ], [ [COLOR_MODE_COLOR_TEMP, COLOR_MODE_RGBWW], @@ -720,8 +918,6 @@ async def test_light_rgb_with_white( ATTR_BRIGHTNESS: 255, ATTR_COLOR_MODE: COLOR_MODE_RGBWW, }, - {ATTR_RGBWW_COLOR: (31, 127, 71, 0, 0)}, - {ATTR_COLOR_TEMP: 2700}, ], ], ) @@ -731,8 +927,6 @@ async def test_light_rgb_with_white_switch_to_temp( events, supported_color_modes, state_props, - turn_on_props, - turn_on_props_with_brightness, ): """Test lights with RGBW/RGBWW that preserves brightness when switching to color temp.""" entity_id = "light.demo" @@ -753,7 +947,7 @@ async def test_light_rgb_with_white_switch_to_temp( await hass.async_block_till_done() assert acc.char_hue.value == 23 assert acc.char_saturation.value == 100 - assert acc.char_brightness.value == 50 + assert acc.char_brightness.value == 100 # Set from HomeKit call_turn_on = async_mock_service(hass, DOMAIN, "turn_on") @@ -782,19 +976,17 @@ async def test_light_rgb_with_white_switch_to_temp( await _wait_for_light_coalesce(hass) assert call_turn_on assert call_turn_on[-1].data[ATTR_ENTITY_ID] == entity_id - for k, v in turn_on_props.items(): - assert call_turn_on[-1].data[k] == v + assert call_turn_on[-1].data[ATTR_HS_COLOR] == (145, 75) assert len(events) == 1 assert events[-1].data[ATTR_VALUE] == "set color at (145, 75)" - assert acc.char_brightness.value == 50 - + assert acc.char_brightness.value == 100 hk_driver.set_characteristics( { HAP_REPR_CHARS: [ { HAP_REPR_AID: acc.aid, HAP_REPR_IID: char_color_temp_iid, - HAP_REPR_VALUE: 2700, + HAP_REPR_VALUE: 500, }, ] }, @@ -803,11 +995,221 @@ async def test_light_rgb_with_white_switch_to_temp( await _wait_for_light_coalesce(hass) assert call_turn_on assert call_turn_on[-1].data[ATTR_ENTITY_ID] == entity_id - for k, v in turn_on_props_with_brightness.items(): - assert call_turn_on[-1].data[k] == v + assert call_turn_on[-1].data[ATTR_COLOR_TEMP] == 500 assert len(events) == 2 - assert events[-1].data[ATTR_VALUE] == "color temperature at 2700" - assert acc.char_brightness.value == 50 + assert events[-1].data[ATTR_VALUE] == "color temperature at 500" + assert acc.char_brightness.value == 100 + + +async def test_light_rgbww_with_color_temp_conversion( + hass, + hk_driver, + events, +): + """Test lights with RGBWW convert color temp as expected.""" + entity_id = "light.demo" + + hass.states.async_set( + entity_id, + STATE_ON, + { + ATTR_SUPPORTED_COLOR_MODES: [COLOR_MODE_RGBWW], + ATTR_RGBWW_COLOR: (128, 50, 0, 255, 255), + ATTR_RGB_COLOR: (128, 50, 0), + ATTR_HS_COLOR: (23.438, 100.0), + ATTR_BRIGHTNESS: 255, + ATTR_COLOR_MODE: COLOR_MODE_RGBWW, + }, + ) + await hass.async_block_till_done() + acc = Light(hass, hk_driver, "Light", entity_id, 1, None) + hk_driver.add_accessory(acc) + + assert acc.char_hue.value == 23 + assert acc.char_saturation.value == 100 + + await acc.run() + await hass.async_block_till_done() + assert acc.char_hue.value == 23 + assert acc.char_saturation.value == 100 + assert acc.char_brightness.value == 100 + + # Set from HomeKit + call_turn_on = async_mock_service(hass, DOMAIN, "turn_on") + + char_hue_iid = acc.char_hue.to_HAP()[HAP_REPR_IID] + char_saturation_iid = acc.char_saturation.to_HAP()[HAP_REPR_IID] + char_color_temp_iid = acc.char_color_temp.to_HAP()[HAP_REPR_IID] + char_brightness_iid = acc.char_brightness.to_HAP()[HAP_REPR_IID] + + hk_driver.set_characteristics( + { + HAP_REPR_CHARS: [ + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_hue_iid, + HAP_REPR_VALUE: 145, + }, + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_saturation_iid, + HAP_REPR_VALUE: 75, + }, + ] + }, + "mock_addr", + ) + await _wait_for_light_coalesce(hass) + assert call_turn_on + assert call_turn_on[-1].data[ATTR_ENTITY_ID] == entity_id + assert call_turn_on[-1].data[ATTR_HS_COLOR] == (145, 75) + assert len(events) == 1 + assert events[-1].data[ATTR_VALUE] == "set color at (145, 75)" + assert acc.char_brightness.value == 100 + + hk_driver.set_characteristics( + { + HAP_REPR_CHARS: [ + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_color_temp_iid, + HAP_REPR_VALUE: 200, + }, + ] + }, + "mock_addr", + ) + await _wait_for_light_coalesce(hass) + assert call_turn_on + assert call_turn_on[-1].data[ATTR_ENTITY_ID] == entity_id + assert call_turn_on[-1].data[ATTR_RGBWW_COLOR] == (0, 0, 0, 220, 35) + assert len(events) == 2 + assert events[-1].data[ATTR_VALUE] == "color temperature at 200" + assert acc.char_brightness.value == 100 + + hass.states.async_set( + entity_id, + STATE_ON, + { + ATTR_SUPPORTED_COLOR_MODES: [COLOR_MODE_RGBWW], + ATTR_RGBWW_COLOR: (0, 0, 0, 128, 255), + ATTR_RGB_COLOR: (255, 163, 79), + ATTR_HS_COLOR: (28.636, 69.02), + ATTR_BRIGHTNESS: 180, + ATTR_COLOR_MODE: COLOR_MODE_RGBWW, + }, + ) + await hass.async_block_till_done() + + hk_driver.set_characteristics( + { + HAP_REPR_CHARS: [ + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_brightness_iid, + HAP_REPR_VALUE: 100, + }, + ] + }, + "mock_addr", + ) + await _wait_for_light_coalesce(hass) + assert call_turn_on + assert call_turn_on[-1].data[ATTR_ENTITY_ID] == entity_id + assert call_turn_on[-1].data[ATTR_BRIGHTNESS_PCT] == 100 + assert len(events) == 3 + assert events[-1].data[ATTR_VALUE] == "brightness at 100%" + assert acc.char_brightness.value == 100 + + +async def test_light_rgbw_with_color_temp_conversion( + hass, + hk_driver, + events, +): + """Test lights with RGBW convert color temp as expected.""" + entity_id = "light.demo" + + hass.states.async_set( + entity_id, + STATE_ON, + { + ATTR_SUPPORTED_COLOR_MODES: [COLOR_MODE_RGBW], + ATTR_RGBWW_COLOR: (128, 50, 0, 255, 255), + ATTR_RGB_COLOR: (128, 50, 0), + ATTR_HS_COLOR: (23.438, 100.0), + ATTR_BRIGHTNESS: 255, + ATTR_COLOR_MODE: COLOR_MODE_RGBW, + }, + ) + await hass.async_block_till_done() + acc = Light(hass, hk_driver, "Light", entity_id, 1, None) + hk_driver.add_accessory(acc) + + assert acc.char_hue.value == 23 + assert acc.char_saturation.value == 100 + + await acc.run() + await hass.async_block_till_done() + assert acc.char_hue.value == 23 + assert acc.char_saturation.value == 100 + assert acc.char_brightness.value == 100 + + # Set from HomeKit + call_turn_on = async_mock_service(hass, DOMAIN, "turn_on") + + char_hue_iid = acc.char_hue.to_HAP()[HAP_REPR_IID] + char_saturation_iid = acc.char_saturation.to_HAP()[HAP_REPR_IID] + char_color_temp_iid = acc.char_color_temp.to_HAP()[HAP_REPR_IID] + assert ( + acc.char_color_temp.properties[PROP_MIN_VALUE] + == acc.char_color_temp.properties[PROP_MAX_VALUE] + ) + + hk_driver.set_characteristics( + { + HAP_REPR_CHARS: [ + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_hue_iid, + HAP_REPR_VALUE: 145, + }, + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_saturation_iid, + HAP_REPR_VALUE: 75, + }, + ] + }, + "mock_addr", + ) + await _wait_for_light_coalesce(hass) + assert call_turn_on + assert call_turn_on[-1].data[ATTR_ENTITY_ID] == entity_id + assert call_turn_on[-1].data[ATTR_HS_COLOR] == (145, 75) + assert len(events) == 1 + assert events[-1].data[ATTR_VALUE] == "set color at (145, 75)" + assert acc.char_brightness.value == 100 + + hk_driver.set_characteristics( + { + HAP_REPR_CHARS: [ + { + HAP_REPR_AID: acc.aid, + HAP_REPR_IID: char_color_temp_iid, + HAP_REPR_VALUE: 153, + }, + ] + }, + "mock_addr", + ) + await _wait_for_light_coalesce(hass) + assert call_turn_on + assert call_turn_on[-1].data[ATTR_ENTITY_ID] == entity_id + assert call_turn_on[-1].data[ATTR_RGBW_COLOR] == (0, 0, 0, 255) + assert len(events) == 2 + assert events[-1].data[ATTR_VALUE] == "color temperature at 153" + assert acc.char_brightness.value == 100 async def test_light_set_brightness_and_color(hass, hk_driver, events): diff --git a/tests/components/nest/common.py b/tests/components/nest/common.py index ca761e8987f..e80ca84d58f 100644 --- a/tests/components/nest/common.py +++ b/tests/components/nest/common.py @@ -102,6 +102,24 @@ TEST_CONFIG_HYBRID = NestTestConfig( }, ) +TEST_CONFIG_LEGACY = NestTestConfig( + config={ + "nest": { + "client_id": "some-client-id", + "client_secret": "some-client-secret", + }, + }, + config_entry_data={ + "auth_implementation": "local", + "tokens": { + "expires_at": time.time() + 86400, + "access_token": { + "token": "some-token", + }, + }, + }, +) + class FakeSubscriber(GoogleNestSubscriber): """Fake subscriber that supplies a FakeDeviceManager.""" diff --git a/tests/components/nest/test_config_flow_legacy.py b/tests/components/nest/test_config_flow_legacy.py index d21920b9e6f..843c9b582ae 100644 --- a/tests/components/nest/test_config_flow_legacy.py +++ b/tests/components/nest/test_config_flow_legacy.py @@ -6,9 +6,11 @@ from homeassistant import config_entries, data_entry_flow from homeassistant.components.nest import DOMAIN, config_flow from homeassistant.setup import async_setup_component +from .common import TEST_CONFIG_LEGACY + from tests.common import MockConfigEntry -CONFIG = {DOMAIN: {"client_id": "bla", "client_secret": "bla"}} +CONFIG = TEST_CONFIG_LEGACY.config async def test_abort_if_no_implementation_registered(hass): @@ -59,7 +61,7 @@ async def test_full_flow_implementation(hass): assert ( result["description_placeholders"] .get("url") - .startswith("https://home.nest.com/login/oauth2?client_id=bla") + .startswith("https://home.nest.com/login/oauth2?client_id=some-client-id") ) def mock_login(auth): diff --git a/tests/components/nest/test_diagnostics.py b/tests/components/nest/test_diagnostics.py index b603019da81..cf6c9c5b20f 100644 --- a/tests/components/nest/test_diagnostics.py +++ b/tests/components/nest/test_diagnostics.py @@ -2,54 +2,45 @@ from unittest.mock import patch -from google_nest_sdm.device import Device from google_nest_sdm.exceptions import SubscriberException +import pytest -from homeassistant.components.nest import DOMAIN from homeassistant.config_entries import ConfigEntryState -from homeassistant.setup import async_setup_component -from .common import CONFIG, async_setup_sdm_platform, create_config_entry +from .common import TEST_CONFIG_LEGACY from tests.components.diagnostics import get_diagnostics_for_config_entry -THERMOSTAT_TYPE = "sdm.devices.types.THERMOSTAT" - -async def test_entry_diagnostics(hass, hass_client): +async def test_entry_diagnostics( + hass, hass_client, create_device, setup_platform, config_entry +): """Test config entry diagnostics.""" - devices = { - "some-device-id": Device.MakeDevice( - { - "name": "enterprises/project-id/devices/device-id", - "type": "sdm.devices.types.THERMOSTAT", - "assignee": "enterprises/project-id/structures/structure-id/rooms/room-id", - "traits": { - "sdm.devices.traits.Info": { - "customName": "My Sensor", - }, - "sdm.devices.traits.Temperature": { - "ambientTemperatureCelsius": 25.1, - }, - "sdm.devices.traits.Humidity": { - "ambientHumidityPercent": 35.0, - }, + create_device.create( + raw_data={ + "name": "enterprises/project-id/devices/device-id", + "type": "sdm.devices.types.THERMOSTAT", + "assignee": "enterprises/project-id/structures/structure-id/rooms/room-id", + "traits": { + "sdm.devices.traits.Info": { + "customName": "My Sensor", + }, + "sdm.devices.traits.Temperature": { + "ambientTemperatureCelsius": 25.1, + }, + "sdm.devices.traits.Humidity": { + "ambientHumidityPercent": 35.0, }, - "parentRelations": [ - { - "parent": "enterprises/project-id/structures/structure-id/rooms/room-id", - "displayName": "Lobby", - } - ], }, - auth=None, - ) - } - assert await async_setup_sdm_platform(hass, platform=None, devices=devices) - - entries = hass.config_entries.async_entries(DOMAIN) - assert len(entries) == 1 - config_entry = entries[0] + "parentRelations": [ + { + "parent": "enterprises/project-id/structures/structure-id/rooms/room-id", + "displayName": "Lobby", + } + ], + } + ) + await setup_platform() assert config_entry.state is ConfigEntryState.LOADED # Test that only non identifiable device information is returned @@ -76,20 +67,32 @@ async def test_entry_diagnostics(hass, hass_client): } -async def test_setup_susbcriber_failure(hass, hass_client): +async def test_setup_susbcriber_failure( + hass, hass_client, config_entry, setup_base_platform +): """Test configuration error.""" - config_entry = create_config_entry() - config_entry.add_to_hass(hass) with patch( "homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation" ), patch( "homeassistant.components.nest.api.GoogleNestSubscriber.start_async", side_effect=SubscriberException(), ): - assert await async_setup_component(hass, DOMAIN, CONFIG) + await setup_base_platform() assert config_entry.state is ConfigEntryState.SETUP_RETRY assert await get_diagnostics_for_config_entry(hass, hass_client, config_entry) == { "error": "No subscriber configured" } + + +@pytest.mark.parametrize("nest_test_config", [TEST_CONFIG_LEGACY]) +async def test_legacy_config_entry_diagnostics( + hass, hass_client, config_entry, setup_base_platform +): + """Test config entry diagnostics for legacy integration doesn't fail.""" + + with patch("homeassistant.components.nest.legacy.Nest"): + await setup_base_platform() + + assert await get_diagnostics_for_config_entry(hass, hass_client, config_entry) == {} diff --git a/tests/components/nest/test_init_legacy.py b/tests/components/nest/test_init_legacy.py index 3a78877a235..cbf1bfe2d48 100644 --- a/tests/components/nest/test_init_legacy.py +++ b/tests/components/nest/test_init_legacy.py @@ -1,30 +1,18 @@ """Test basic initialization for the Legacy Nest API using mocks for the Nest python library.""" -import time from unittest.mock import MagicMock, PropertyMock, patch -from homeassistant.setup import async_setup_component +import pytest -from tests.common import MockConfigEntry +from .common import TEST_CONFIG_LEGACY DOMAIN = "nest" -CONFIG = { - "nest": { - "client_id": "some-client-id", - "client_secret": "some-client-secret", - }, -} -CONFIG_ENTRY_DATA = { - "auth_implementation": "local", - "tokens": { - "expires_at": time.time() + 86400, - "access_token": { - "token": "some-token", - }, - }, -} +@pytest.fixture +def nest_test_config(): + """Fixture to specify the overall test fixture configuration.""" + return TEST_CONFIG_LEGACY def make_thermostat(): @@ -45,7 +33,7 @@ def make_thermostat(): return device -async def test_thermostat(hass): +async def test_thermostat(hass, setup_base_platform): """Test simple initialization for thermostat entities.""" thermostat = make_thermostat() @@ -58,8 +46,6 @@ async def test_thermostat(hass): nest = MagicMock() type(nest).structures = PropertyMock(return_value=[structure]) - config_entry = MockConfigEntry(domain=DOMAIN, data=CONFIG_ENTRY_DATA) - config_entry.add_to_hass(hass) with patch("homeassistant.components.nest.legacy.Nest", return_value=nest), patch( "homeassistant.components.nest.legacy.sensor._VALID_SENSOR_TYPES", ["humidity", "temperature"], @@ -67,8 +53,7 @@ async def test_thermostat(hass): "homeassistant.components.nest.legacy.binary_sensor._VALID_BINARY_SENSOR_TYPES", {"fan": None}, ): - assert await async_setup_component(hass, DOMAIN, CONFIG) - await hass.async_block_till_done() + await setup_base_platform() climate = hass.states.get("climate.my_thermostat") assert climate is not None diff --git a/tests/components/simplisafe/conftest.py b/tests/components/simplisafe/conftest.py index d9e6d46c2eb..d4517717434 100644 --- a/tests/components/simplisafe/conftest.py +++ b/tests/components/simplisafe/conftest.py @@ -18,11 +18,12 @@ USER_ID = "12345" @pytest.fixture(name="api") -def api_fixture(system_v3, websocket): +def api_fixture(data_subscription, system_v3, websocket): """Define a fixture for a simplisafe-python API object.""" return Mock( async_get_systems=AsyncMock(return_value={SYSTEM_ID: system_v3}), refresh_token=REFRESH_TOKEN, + subscription_data=data_subscription, user_id=USER_ID, websocket=websocket, ) diff --git a/tests/components/simplisafe/test_diagnostics.py b/tests/components/simplisafe/test_diagnostics.py index d2c2866bf5b..13d5c778e89 100644 --- a/tests/components/simplisafe/test_diagnostics.py +++ b/tests/components/simplisafe/test_diagnostics.py @@ -7,7 +7,96 @@ from tests.components.diagnostics import get_diagnostics_for_config_entry async def test_entry_diagnostics(hass, config_entry, hass_client, setup_simplisafe): """Test config entry diagnostics.""" assert await get_diagnostics_for_config_entry(hass, hass_client, config_entry) == { - "entry": {"options": {}}, + "entry": { + "options": {}, + }, + "subscription_data": { + "system_123": { + "uid": REDACTED, + "sid": REDACTED, + "sStatus": 20, + "activated": 1445034752, + "planSku": "SSEDSM2", + "planName": "Interactive Monitoring", + "price": 24.99, + "currency": "USD", + "country": "US", + "expires": REDACTED, + "canceled": 0, + "extraTime": 0, + "creditCard": REDACTED, + "time": 2628000, + "paymentProfileId": REDACTED, + "features": { + "monitoring": True, + "alerts": True, + "online": True, + "hazard": True, + "video": True, + "cameras": 10, + "dispatch": True, + "proInstall": False, + "discount": 0, + "vipCS": False, + "medical": True, + "careVisit": False, + "storageDays": 30, + }, + "status": { + "hasBaseStation": True, + "isActive": True, + "monitoring": "Active", + }, + "subscriptionFeatures": { + "monitoredSensorsTypes": [ + "Entry", + "Motion", + "GlassBreak", + "Smoke", + "CO", + "Freeze", + "Water", + ], + "monitoredPanicConditions": ["Fire", "Medical", "Duress"], + "dispatchTypes": ["Police", "Fire", "Medical", "Guard"], + "remoteControl": [ + "ArmDisarm", + "LockUnlock", + "ViewSettings", + "ConfigureSettings", + ], + "cameraFeatures": { + "liveView": True, + "maxRecordingCameras": 10, + "recordingStorageDays": 30, + "videoVerification": True, + }, + "support": { + "level": "Basic", + "annualVisit": False, + "professionalInstall": False, + }, + "cellCommunicationBackup": True, + "alertChannels": ["Push", "SMS", "Email"], + "alertTypes": ["Alarm", "Error", "Activity", "Camera"], + "alarmModes": ["Alarm", "SecretAlert", "Disabled"], + "supportedIntegrations": [ + "GoogleAssistant", + "AmazonAlexa", + "AugustLock", + ], + "timeline": {}, + }, + "dispatcher": "cops", + "dcid": 0, + "location": REDACTED, + "pinUnlocked": True, + "billDate": 1602887552, + "billInterval": 2628000, + "pinUnlockedBy": "pin", + "autoActivation": None, + } + }, "systems": [ { "address": REDACTED, @@ -183,7 +272,7 @@ async def test_entry_diagnostics(hass, config_entry, hass_client, setup_simplisa "shutter_open_when_off": False, "status": "online", "subscription_enabled": True, - }, + } ], "chime_volume": 2, "entry_delay_away": 30, diff --git a/tests/util/test_color.py b/tests/util/test_color.py index 0b1b8f7d17f..eff71ddef4e 100644 --- a/tests/util/test_color.py +++ b/tests/util/test_color.py @@ -405,6 +405,49 @@ def test_color_rgb_to_rgbww(): assert color_util.color_rgb_to_rgbww(255, 255, 255, 1, 5) == (103, 69, 0, 255, 255) +def test_color_rgbww_to_rgb(): + """Test color_rgbww_to_rgb conversions.""" + assert color_util.color_rgbww_to_rgb(0, 54, 98, 255, 255, 154, 370) == ( + 255, + 255, + 255, + ) + assert color_util.color_rgbww_to_rgb(255, 255, 255, 0, 0, 154, 370) == ( + 255, + 255, + 255, + ) + assert color_util.color_rgbww_to_rgb(0, 118, 241, 255, 255, 154, 370) == ( + 163, + 204, + 255, + ) + assert color_util.color_rgbww_to_rgb(0, 27, 49, 128, 128, 154, 370) == ( + 128, + 128, + 128, + ) + assert color_util.color_rgbww_to_rgb(0, 14, 25, 64, 64, 154, 370) == (64, 64, 64) + assert color_util.color_rgbww_to_rgb(9, 64, 0, 38, 38, 154, 370) == (32, 64, 16) + assert color_util.color_rgbww_to_rgb(0, 0, 0, 0, 0, 154, 370) == (0, 0, 0) + assert color_util.color_rgbww_to_rgb(103, 69, 0, 255, 255, 153, 370) == ( + 255, + 193, + 112, + ) + assert color_util.color_rgbww_to_rgb(255, 255, 255, 0, 0, 0, 0) == (255, 255, 255) + assert color_util.color_rgbww_to_rgb(255, 255, 255, 255, 255, 0, 0) == ( + 255, + 161, + 128, + ) + assert color_util.color_rgbww_to_rgb(255, 255, 255, 255, 255, 0, 370) == ( + 255, + 245, + 237, + ) + + def test_color_temperature_to_rgbww(): """Test color temp to warm, cold conversion.