mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 13:17:32 +00:00
2024.9.2 (#126062)
This commit is contained in:
commit
b69b5aa82a
@ -24,5 +24,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/august",
|
"documentation": "https://www.home-assistant.io/integrations/august",
|
||||||
"iot_class": "cloud_push",
|
"iot_class": "cloud_push",
|
||||||
"loggers": ["pubnub", "yalexs"],
|
"loggers": ["pubnub", "yalexs"],
|
||||||
"requirements": ["yalexs==8.6.3", "yalexs-ble==2.4.3"]
|
"requirements": ["yalexs==8.6.4", "yalexs-ble==2.4.3"]
|
||||||
}
|
}
|
||||||
|
@ -60,6 +60,9 @@ COLOR_MODE_MAP = {
|
|||||||
class BleBoxLightEntity(BleBoxEntity[blebox_uniapi.light.Light], LightEntity):
|
class BleBoxLightEntity(BleBoxEntity[blebox_uniapi.light.Light], LightEntity):
|
||||||
"""Representation of BleBox lights."""
|
"""Representation of BleBox lights."""
|
||||||
|
|
||||||
|
_attr_max_mireds = 370 # 1,000,000 divided by 2700 Kelvin = 370 Mireds
|
||||||
|
_attr_min_mireds = 154 # 1,000,000 divided by 6500 Kelvin = 154 Mireds
|
||||||
|
|
||||||
def __init__(self, feature: blebox_uniapi.light.Light) -> None:
|
def __init__(self, feature: blebox_uniapi.light.Light) -> None:
|
||||||
"""Initialize a BleBox light."""
|
"""Initialize a BleBox light."""
|
||||||
super().__init__(feature)
|
super().__init__(feature)
|
||||||
@ -87,12 +90,7 @@ class BleBoxLightEntity(BleBoxEntity[blebox_uniapi.light.Light], LightEntity):
|
|||||||
|
|
||||||
Set values to _attr_ibutes if needed.
|
Set values to _attr_ibutes if needed.
|
||||||
"""
|
"""
|
||||||
color_mode_tmp = COLOR_MODE_MAP.get(self._feature.color_mode, ColorMode.ONOFF)
|
return COLOR_MODE_MAP.get(self._feature.color_mode, ColorMode.ONOFF)
|
||||||
if color_mode_tmp == ColorMode.COLOR_TEMP:
|
|
||||||
self._attr_min_mireds = 1
|
|
||||||
self._attr_max_mireds = 255
|
|
||||||
|
|
||||||
return color_mode_tmp
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supported_color_modes(self):
|
def supported_color_modes(self):
|
||||||
|
@ -20,5 +20,8 @@ async def async_get_config_entry_diagnostics(
|
|||||||
return {
|
return {
|
||||||
"info": data.info.to_dict(),
|
"info": data.info.to_dict(),
|
||||||
"device": data.device.to_dict(),
|
"device": data.device.to_dict(),
|
||||||
|
"coordinator_data": {
|
||||||
"state": data.coordinator.data.state.to_dict(),
|
"state": data.coordinator.data.state.to_dict(),
|
||||||
|
},
|
||||||
|
"static": data.static.to_dict(),
|
||||||
}
|
}
|
||||||
|
@ -22,10 +22,10 @@ class BSBLanEntity(CoordinatorEntity[BSBLanUpdateCoordinator]):
|
|||||||
def __init__(self, coordinator: BSBLanUpdateCoordinator, data: BSBLanData) -> None:
|
def __init__(self, coordinator: BSBLanUpdateCoordinator, data: BSBLanData) -> None:
|
||||||
"""Initialize BSBLan entity."""
|
"""Initialize BSBLan entity."""
|
||||||
super().__init__(coordinator, data)
|
super().__init__(coordinator, data)
|
||||||
host = self.coordinator.config_entry.data["host"]
|
host = coordinator.config_entry.data["host"]
|
||||||
mac = self.coordinator.config_entry.data["mac"]
|
mac = data.device.MAC
|
||||||
self._attr_device_info = DeviceInfo(
|
self._attr_device_info = DeviceInfo(
|
||||||
identifiers={(DOMAIN, data.device.MAC)},
|
identifiers={(DOMAIN, mac)},
|
||||||
connections={(CONNECTION_NETWORK_MAC, format_mac(mac))},
|
connections={(CONNECTION_NETWORK_MAC, format_mac(mac))},
|
||||||
name=data.device.name,
|
name=data.device.name,
|
||||||
manufacturer="BSBLAN Inc.",
|
manufacturer="BSBLAN Inc.",
|
||||||
|
@ -100,7 +100,7 @@ class ElevenLabsTTSEntity(TextToSpeechEntity):
|
|||||||
"""Load tts audio file from the engine."""
|
"""Load tts audio file from the engine."""
|
||||||
_LOGGER.debug("Getting TTS audio for %s", message)
|
_LOGGER.debug("Getting TTS audio for %s", message)
|
||||||
_LOGGER.debug("Options: %s", options)
|
_LOGGER.debug("Options: %s", options)
|
||||||
voice_id = options[ATTR_VOICE]
|
voice_id = options.get(ATTR_VOICE, self._default_voice_id)
|
||||||
try:
|
try:
|
||||||
audio = await self._client.generate(
|
audio = await self._client.generate(
|
||||||
text=message,
|
text=message,
|
||||||
|
@ -20,5 +20,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/frontend",
|
"documentation": "https://www.home-assistant.io/integrations/frontend",
|
||||||
"integration_type": "system",
|
"integration_type": "system",
|
||||||
"quality_scale": "internal",
|
"quality_scale": "internal",
|
||||||
"requirements": ["home-assistant-frontend==20240906.0"]
|
"requirements": ["home-assistant-frontend==20240909.1"]
|
||||||
}
|
}
|
||||||
|
@ -6,5 +6,5 @@
|
|||||||
"dependencies": ["network"],
|
"dependencies": ["network"],
|
||||||
"documentation": "https://www.home-assistant.io/integrations/govee_light_local",
|
"documentation": "https://www.home-assistant.io/integrations/govee_light_local",
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"requirements": ["govee-local-api==1.5.1"]
|
"requirements": ["govee-local-api==1.5.2"]
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,8 @@
|
|||||||
"not_hassio_thread": "The OpenThread Border Router addon can only be installed with Home Assistant OS. If you would like to use the {model} as an Thread border router, please flash the firmware manually using the [web flasher]({docs_web_flasher_url}) and set up OpenThread Border Router to communicate with it.",
|
"not_hassio_thread": "The OpenThread Border Router addon can only be installed with Home Assistant OS. If you would like to use the {model} as an Thread border router, please flash the firmware manually using the [web flasher]({docs_web_flasher_url}) and set up OpenThread Border Router to communicate with it.",
|
||||||
"otbr_addon_already_running": "The OpenThread Border Router add-on is already running, it cannot be installed again.",
|
"otbr_addon_already_running": "The OpenThread Border Router add-on is already running, it cannot be installed again.",
|
||||||
"zha_still_using_stick": "This {model} is in use by the Zigbee Home Automation integration. Please migrate your Zigbee network to another adapter or delete the integration and try again.",
|
"zha_still_using_stick": "This {model} is in use by the Zigbee Home Automation integration. Please migrate your Zigbee network to another adapter or delete the integration and try again.",
|
||||||
"otbr_still_using_stick": "This {model} is in use by the OpenThread Border Router add-on. If you use the Thread network, make sure you have alternative border routers. Uninstall the add-on and try again."
|
"otbr_still_using_stick": "This {model} is in use by the OpenThread Border Router add-on. If you use the Thread network, make sure you have alternative border routers. Uninstall the add-on and try again.",
|
||||||
|
"unsupported_firmware": "The radio firmware on your {model} could not be determined. Make sure that no other integration or addon is currently trying to communicate with the device. If you are running Home Assistant OS in a virtual machine or in Docker, please make sure that permissions are set correctly for the device."
|
||||||
},
|
},
|
||||||
"progress": {
|
"progress": {
|
||||||
"install_zigbee_flasher_addon": "The Silicon Labs Flasher addon is installed, this may take a few minutes.",
|
"install_zigbee_flasher_addon": "The Silicon Labs Flasher addon is installed, this may take a few minutes.",
|
||||||
|
@ -113,7 +113,8 @@
|
|||||||
"not_hassio_thread": "[%key:component::homeassistant_hardware::firmware_picker::options::abort::not_hassio_thread%]",
|
"not_hassio_thread": "[%key:component::homeassistant_hardware::firmware_picker::options::abort::not_hassio_thread%]",
|
||||||
"otbr_addon_already_running": "[%key:component::homeassistant_hardware::firmware_picker::options::abort::otbr_addon_already_running%]",
|
"otbr_addon_already_running": "[%key:component::homeassistant_hardware::firmware_picker::options::abort::otbr_addon_already_running%]",
|
||||||
"zha_still_using_stick": "[%key:component::homeassistant_hardware::firmware_picker::options::abort::zha_still_using_stick%]",
|
"zha_still_using_stick": "[%key:component::homeassistant_hardware::firmware_picker::options::abort::zha_still_using_stick%]",
|
||||||
"otbr_still_using_stick": "[%key:component::homeassistant_hardware::firmware_picker::options::abort::otbr_still_using_stick%]"
|
"otbr_still_using_stick": "[%key:component::homeassistant_hardware::firmware_picker::options::abort::otbr_still_using_stick%]",
|
||||||
|
"unsupported_firmware": "[%key:component::homeassistant_hardware::firmware_picker::options::abort::unsupported_firmware%]"
|
||||||
},
|
},
|
||||||
"progress": {
|
"progress": {
|
||||||
"install_addon": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::progress::install_addon%]",
|
"install_addon": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::progress::install_addon%]",
|
||||||
@ -181,7 +182,10 @@
|
|||||||
"zha_migration_failed": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::abort::zha_migration_failed%]",
|
"zha_migration_failed": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::abort::zha_migration_failed%]",
|
||||||
"not_hassio": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::abort::not_hassio%]",
|
"not_hassio": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::abort::not_hassio%]",
|
||||||
"not_hassio_thread": "[%key:component::homeassistant_hardware::firmware_picker::options::abort::not_hassio_thread%]",
|
"not_hassio_thread": "[%key:component::homeassistant_hardware::firmware_picker::options::abort::not_hassio_thread%]",
|
||||||
"otbr_addon_already_running": "[%key:component::homeassistant_hardware::firmware_picker::options::abort::otbr_addon_already_running%]"
|
"otbr_addon_already_running": "[%key:component::homeassistant_hardware::firmware_picker::options::abort::otbr_addon_already_running%]",
|
||||||
|
"zha_still_using_stick": "[%key:component::homeassistant_hardware::firmware_picker::options::abort::zha_still_using_stick%]",
|
||||||
|
"otbr_still_using_stick": "[%key:component::homeassistant_hardware::firmware_picker::options::abort::otbr_still_using_stick%]",
|
||||||
|
"unsupported_firmware": "[%key:component::homeassistant_hardware::firmware_picker::options::abort::unsupported_firmware%]"
|
||||||
},
|
},
|
||||||
"progress": {
|
"progress": {
|
||||||
"install_addon": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::progress::install_addon%]",
|
"install_addon": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::progress::install_addon%]",
|
||||||
|
@ -138,7 +138,8 @@
|
|||||||
"not_hassio_thread": "[%key:component::homeassistant_hardware::firmware_picker::options::abort::not_hassio_thread%]",
|
"not_hassio_thread": "[%key:component::homeassistant_hardware::firmware_picker::options::abort::not_hassio_thread%]",
|
||||||
"otbr_addon_already_running": "[%key:component::homeassistant_hardware::firmware_picker::options::abort::otbr_addon_already_running%]",
|
"otbr_addon_already_running": "[%key:component::homeassistant_hardware::firmware_picker::options::abort::otbr_addon_already_running%]",
|
||||||
"zha_still_using_stick": "[%key:component::homeassistant_hardware::firmware_picker::options::abort::zha_still_using_stick%]",
|
"zha_still_using_stick": "[%key:component::homeassistant_hardware::firmware_picker::options::abort::zha_still_using_stick%]",
|
||||||
"otbr_still_using_stick": "[%key:component::homeassistant_hardware::firmware_picker::options::abort::otbr_still_using_stick%]"
|
"otbr_still_using_stick": "[%key:component::homeassistant_hardware::firmware_picker::options::abort::otbr_still_using_stick%]",
|
||||||
|
"unsupported_firmware": "The radio firmware on your {model} could not be determined. Make sure that no other integration or addon is currently trying to communicate with the device."
|
||||||
},
|
},
|
||||||
"progress": {
|
"progress": {
|
||||||
"install_addon": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::progress::install_addon%]",
|
"install_addon": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::progress::install_addon%]",
|
||||||
|
@ -90,8 +90,10 @@ class InComfortClimate(IncomfortEntity, ClimateEntity):
|
|||||||
|
|
||||||
As we set the override, we report back the override. The actual set point is
|
As we set the override, we report back the override. The actual set point is
|
||||||
is returned at a later time.
|
is returned at a later time.
|
||||||
|
Some older thermostats return 0.0 as override, in that case we fallback to
|
||||||
|
the actual setpoint.
|
||||||
"""
|
"""
|
||||||
return self._room.override
|
return self._room.override or self._room.setpoint
|
||||||
|
|
||||||
async def async_set_temperature(self, **kwargs: Any) -> None:
|
async def async_set_temperature(self, **kwargs: Any) -> None:
|
||||||
"""Set a new target temperature for this zone."""
|
"""Set a new target temperature for this zone."""
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
"requirements": [
|
"requirements": [
|
||||||
"xknx==3.1.1",
|
"xknx==3.1.1",
|
||||||
"xknxproject==3.7.1",
|
"xknxproject==3.7.1",
|
||||||
"knx-frontend==2024.9.4.64538"
|
"knx-frontend==2024.9.10.221729"
|
||||||
],
|
],
|
||||||
"single_config_entry": true
|
"single_config_entry": true
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,10 @@ def parse_invalid(exc: vol.Invalid) -> _ErrorDescription:
|
|||||||
|
|
||||||
|
|
||||||
def validate_entity_data(entity_data: dict) -> dict:
|
def validate_entity_data(entity_data: dict) -> dict:
|
||||||
"""Validate entity data. Return validated data or raise EntityStoreValidationException."""
|
"""Validate entity data.
|
||||||
|
|
||||||
|
Return validated data or raise EntityStoreValidationException.
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
# return so defaults are applied
|
# return so defaults are applied
|
||||||
return ENTITY_STORE_DATA_SCHEMA(entity_data) # type: ignore[no-any-return]
|
return ENTITY_STORE_DATA_SCHEMA(entity_data) # type: ignore[no-any-return]
|
||||||
|
@ -22,5 +22,5 @@
|
|||||||
"integration_type": "device",
|
"integration_type": "device",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["lmcloud"],
|
"loggers": ["lmcloud"],
|
||||||
"requirements": ["lmcloud==1.2.2"]
|
"requirements": ["lmcloud==1.2.3"]
|
||||||
}
|
}
|
||||||
|
@ -48,8 +48,8 @@
|
|||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["aiolifx", "aiolifx_effects", "bitstring"],
|
"loggers": ["aiolifx", "aiolifx_effects", "bitstring"],
|
||||||
"requirements": [
|
"requirements": [
|
||||||
"aiolifx==1.0.9",
|
"aiolifx==1.1.1",
|
||||||
"aiolifx-effects==0.3.2",
|
"aiolifx-effects==0.3.2",
|
||||||
"aiolifx-themes==0.5.0"
|
"aiolifx-themes==0.5.5"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -208,10 +208,7 @@ class LyricClimate(LyricDeviceEntity, ClimateEntity):
|
|||||||
if LYRIC_HVAC_MODE_COOL in device.allowed_modes:
|
if LYRIC_HVAC_MODE_COOL in device.allowed_modes:
|
||||||
self._attr_hvac_modes.append(HVACMode.COOL)
|
self._attr_hvac_modes.append(HVACMode.COOL)
|
||||||
|
|
||||||
if (
|
if LYRIC_HVAC_MODE_HEAT_COOL in device.allowed_modes:
|
||||||
LYRIC_HVAC_MODE_HEAT in device.allowed_modes
|
|
||||||
and LYRIC_HVAC_MODE_COOL in device.allowed_modes
|
|
||||||
):
|
|
||||||
self._attr_hvac_modes.append(HVACMode.HEAT_COOL)
|
self._attr_hvac_modes.append(HVACMode.HEAT_COOL)
|
||||||
|
|
||||||
# Setup supported features
|
# Setup supported features
|
||||||
|
@ -26,7 +26,13 @@ async def async_setup_entry(
|
|||||||
coordinator = hass.data[DOMAIN][config_entry.entry_id][KEY_COORDINATOR]
|
coordinator = hass.data[DOMAIN][config_entry.entry_id][KEY_COORDINATOR]
|
||||||
|
|
||||||
for blind in motion_gateway.device_list.values():
|
for blind in motion_gateway.device_list.values():
|
||||||
if blind.limit_status == LimitStatus.Limit3Detected.name:
|
if blind.limit_status in (
|
||||||
|
LimitStatus.Limit3Detected.name,
|
||||||
|
{
|
||||||
|
"T": LimitStatus.Limit3Detected.name,
|
||||||
|
"B": LimitStatus.Limit3Detected.name,
|
||||||
|
},
|
||||||
|
):
|
||||||
entities.append(MotionGoFavoriteButton(coordinator, blind))
|
entities.append(MotionGoFavoriteButton(coordinator, blind))
|
||||||
entities.append(MotionSetFavoriteButton(coordinator, blind))
|
entities.append(MotionSetFavoriteButton(coordinator, blind))
|
||||||
|
|
||||||
|
@ -21,5 +21,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/motion_blinds",
|
"documentation": "https://www.home-assistant.io/integrations/motion_blinds",
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["motionblinds"],
|
"loggers": ["motionblinds"],
|
||||||
"requirements": ["motionblinds==0.6.24"]
|
"requirements": ["motionblinds==0.6.25"]
|
||||||
}
|
}
|
||||||
|
@ -20,5 +20,5 @@
|
|||||||
"iot_class": "cloud_push",
|
"iot_class": "cloud_push",
|
||||||
"loggers": ["google_nest_sdm"],
|
"loggers": ["google_nest_sdm"],
|
||||||
"quality_scale": "platinum",
|
"quality_scale": "platinum",
|
||||||
"requirements": ["google-nest-sdm==5.0.0"]
|
"requirements": ["google-nest-sdm==5.0.1"]
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ class RenaultBinarySensorEntityDescription(
|
|||||||
"""Class describing Renault binary sensor entities."""
|
"""Class describing Renault binary sensor entities."""
|
||||||
|
|
||||||
on_key: str
|
on_key: str
|
||||||
on_value: StateType
|
on_value: StateType | list[StateType]
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
@ -58,6 +58,9 @@ class RenaultBinarySensor(
|
|||||||
"""Return true if the binary sensor is on."""
|
"""Return true if the binary sensor is on."""
|
||||||
if (data := self._get_data_attr(self.entity_description.on_key)) is None:
|
if (data := self._get_data_attr(self.entity_description.on_key)) is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
if isinstance(self.entity_description.on_value, list):
|
||||||
|
return data in self.entity_description.on_value
|
||||||
return data == self.entity_description.on_value
|
return data == self.entity_description.on_value
|
||||||
|
|
||||||
|
|
||||||
@ -68,7 +71,10 @@ BINARY_SENSOR_TYPES: tuple[RenaultBinarySensorEntityDescription, ...] = tuple(
|
|||||||
coordinator="battery",
|
coordinator="battery",
|
||||||
device_class=BinarySensorDeviceClass.PLUG,
|
device_class=BinarySensorDeviceClass.PLUG,
|
||||||
on_key="plugStatus",
|
on_key="plugStatus",
|
||||||
on_value=PlugState.PLUGGED.value,
|
on_value=[
|
||||||
|
PlugState.PLUGGED.value,
|
||||||
|
PlugState.PLUGGED_WAITING_FOR_CHARGE.value,
|
||||||
|
],
|
||||||
),
|
),
|
||||||
RenaultBinarySensorEntityDescription(
|
RenaultBinarySensorEntityDescription(
|
||||||
key="charging",
|
key="charging",
|
||||||
|
@ -197,7 +197,13 @@ SENSOR_TYPES: tuple[RenaultSensorEntityDescription[Any], ...] = (
|
|||||||
translation_key="plug_state",
|
translation_key="plug_state",
|
||||||
device_class=SensorDeviceClass.ENUM,
|
device_class=SensorDeviceClass.ENUM,
|
||||||
entity_class=RenaultSensor[KamereonVehicleBatteryStatusData],
|
entity_class=RenaultSensor[KamereonVehicleBatteryStatusData],
|
||||||
options=["unplugged", "plugged", "plug_error", "plug_unknown"],
|
options=[
|
||||||
|
"unplugged",
|
||||||
|
"plugged",
|
||||||
|
"plugged_waiting_for_charge",
|
||||||
|
"plug_error",
|
||||||
|
"plug_unknown",
|
||||||
|
],
|
||||||
value_lambda=_get_plug_state_formatted,
|
value_lambda=_get_plug_state_formatted,
|
||||||
),
|
),
|
||||||
RenaultSensorEntityDescription(
|
RenaultSensorEntityDescription(
|
||||||
|
@ -141,6 +141,7 @@
|
|||||||
"state": {
|
"state": {
|
||||||
"unplugged": "Unplugged",
|
"unplugged": "Unplugged",
|
||||||
"plugged": "Plugged in",
|
"plugged": "Plugged in",
|
||||||
|
"plugged_waiting_for_charge": "Plugged in, waiting for charge",
|
||||||
"plug_error": "Plug error",
|
"plug_error": "Plug error",
|
||||||
"plug_unknown": "Plug unknown"
|
"plug_unknown": "Plug unknown"
|
||||||
}
|
}
|
||||||
|
@ -7,5 +7,5 @@
|
|||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["aiorussound"],
|
"loggers": ["aiorussound"],
|
||||||
"quality_scale": "silver",
|
"quality_scale": "silver",
|
||||||
"requirements": ["aiorussound==3.0.4"]
|
"requirements": ["aiorussound==3.0.5"]
|
||||||
}
|
}
|
||||||
|
@ -5,5 +5,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/russound_rnet",
|
"documentation": "https://www.home-assistant.io/integrations/russound_rnet",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["russound"],
|
"loggers": ["russound"],
|
||||||
"requirements": ["russound==0.1.9"]
|
"requirements": ["russound==0.2.0"]
|
||||||
}
|
}
|
||||||
|
@ -96,7 +96,13 @@ class RussoundRNETDevice(MediaPlayerEntity):
|
|||||||
# Updated this function to make a single call to get_zone_info, so that
|
# Updated this function to make a single call to get_zone_info, so that
|
||||||
# with a single call we can get On/Off, Volume and Source, reducing the
|
# with a single call we can get On/Off, Volume and Source, reducing the
|
||||||
# amount of traffic and speeding up the update process.
|
# amount of traffic and speeding up the update process.
|
||||||
|
try:
|
||||||
ret = self._russ.get_zone_info(self._controller_id, self._zone_id, 4)
|
ret = self._russ.get_zone_info(self._controller_id, self._zone_id, 4)
|
||||||
|
except BrokenPipeError:
|
||||||
|
_LOGGER.error("Broken Pipe Error, trying to reconnect to Russound RNET")
|
||||||
|
self._russ.connect()
|
||||||
|
ret = self._russ.get_zone_info(self._controller_id, self._zone_id, 4)
|
||||||
|
|
||||||
_LOGGER.debug("ret= %s", ret)
|
_LOGGER.debug("ret= %s", ret)
|
||||||
if ret is not None:
|
if ret is not None:
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
|
@ -42,5 +42,4 @@ class SchlageEntity(CoordinatorEntity[SchlageDataUpdateCoordinator]):
|
|||||||
@property
|
@property
|
||||||
def available(self) -> bool:
|
def available(self) -> bool:
|
||||||
"""Return if entity is available."""
|
"""Return if entity is available."""
|
||||||
# When is_locked is None the lock is unavailable.
|
return super().available and self.device_id in self.coordinator.data.locks
|
||||||
return super().available and self._lock.is_locked is not None
|
|
||||||
|
@ -42,8 +42,9 @@ class SchlageLockEntity(SchlageEntity, LockEntity):
|
|||||||
@callback
|
@callback
|
||||||
def _handle_coordinator_update(self) -> None:
|
def _handle_coordinator_update(self) -> None:
|
||||||
"""Handle updated data from the coordinator."""
|
"""Handle updated data from the coordinator."""
|
||||||
|
if self.device_id in self.coordinator.data.locks:
|
||||||
self._update_attrs()
|
self._update_attrs()
|
||||||
return super()._handle_coordinator_update()
|
super()._handle_coordinator_update()
|
||||||
|
|
||||||
def _update_attrs(self) -> None:
|
def _update_attrs(self) -> None:
|
||||||
"""Update our internal state attributes."""
|
"""Update our internal state attributes."""
|
||||||
|
@ -64,5 +64,6 @@ class SchlageBatterySensor(SchlageEntity, SensorEntity):
|
|||||||
@callback
|
@callback
|
||||||
def _handle_coordinator_update(self) -> None:
|
def _handle_coordinator_update(self) -> None:
|
||||||
"""Handle updated data from the coordinator."""
|
"""Handle updated data from the coordinator."""
|
||||||
|
if self.device_id in self.coordinator.data.locks:
|
||||||
self._attr_native_value = getattr(self._lock, self.entity_description.key)
|
self._attr_native_value = getattr(self._lock, self.entity_description.key)
|
||||||
return super()._handle_coordinator_update()
|
super()._handle_coordinator_update()
|
||||||
|
@ -6,5 +6,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/sfr_box",
|
"documentation": "https://www.home-assistant.io/integrations/sfr_box",
|
||||||
"integration_type": "device",
|
"integration_type": "device",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"requirements": ["sfrbox-api==0.0.10"]
|
"requirements": ["sfrbox-api==0.0.11"]
|
||||||
}
|
}
|
||||||
|
@ -94,8 +94,13 @@ class SmlightConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
mac = discovery_info.properties.get("mac")
|
mac = discovery_info.properties.get("mac")
|
||||||
# fallback for legacy firmware
|
# fallback for legacy firmware
|
||||||
if mac is None:
|
if mac is None:
|
||||||
|
try:
|
||||||
info = await self.client.get_info()
|
info = await self.client.get_info()
|
||||||
|
except SmlightConnectionError:
|
||||||
|
# User is likely running unsupported ESPHome firmware
|
||||||
|
return self.async_abort(reason="cannot_connect")
|
||||||
mac = info.MAC
|
mac = info.MAC
|
||||||
|
|
||||||
await self.async_set_unique_id(format_mac(mac))
|
await self.async_set_unique_id(format_mac(mac))
|
||||||
self._abort_if_unique_id_configured()
|
self._abort_if_unique_id_configured()
|
||||||
|
|
||||||
|
@ -84,6 +84,7 @@ REPEAT_TO_SONOS = {
|
|||||||
SONOS_TO_REPEAT = {meaning: mode for mode, meaning in REPEAT_TO_SONOS.items()}
|
SONOS_TO_REPEAT = {meaning: mode for mode, meaning in REPEAT_TO_SONOS.items()}
|
||||||
|
|
||||||
UPNP_ERRORS_TO_IGNORE = ["701", "711", "712"]
|
UPNP_ERRORS_TO_IGNORE = ["701", "711", "712"]
|
||||||
|
ANNOUNCE_NOT_SUPPORTED_ERRORS: list[str] = ["globalError"]
|
||||||
|
|
||||||
SERVICE_SNAPSHOT = "snapshot"
|
SERVICE_SNAPSHOT = "snapshot"
|
||||||
SERVICE_RESTORE = "restore"
|
SERVICE_RESTORE = "restore"
|
||||||
@ -556,10 +557,23 @@ class SonosMediaPlayerEntity(SonosEntity, MediaPlayerEntity):
|
|||||||
) from exc
|
) from exc
|
||||||
if response.get("success"):
|
if response.get("success"):
|
||||||
return
|
return
|
||||||
|
if response.get("type") in ANNOUNCE_NOT_SUPPORTED_ERRORS:
|
||||||
|
# If the speaker does not support announce do not raise and
|
||||||
|
# fall through to_play_media to play the clip directly.
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Speaker %s does not support announce, media_id %s response %s",
|
||||||
|
self.speaker.zone_name,
|
||||||
|
media_id,
|
||||||
|
response,
|
||||||
|
)
|
||||||
|
else:
|
||||||
raise HomeAssistantError(
|
raise HomeAssistantError(
|
||||||
translation_domain=SONOS_DOMAIN,
|
translation_domain=SONOS_DOMAIN,
|
||||||
translation_key="announce_media_error",
|
translation_key="announce_media_error",
|
||||||
translation_placeholders={"media_id": media_id, "response": response},
|
translation_placeholders={
|
||||||
|
"media_id": media_id,
|
||||||
|
"response": response,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
if spotify.is_spotify_media_type(media_type):
|
if spotify.is_spotify_media_type(media_type):
|
||||||
|
@ -26,6 +26,7 @@ from homeassistant.config_entries import ConfigEntry
|
|||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_ALIAS,
|
CONF_ALIAS,
|
||||||
CONF_AUTHENTICATION,
|
CONF_AUTHENTICATION,
|
||||||
|
CONF_DEVICE,
|
||||||
CONF_HOST,
|
CONF_HOST,
|
||||||
CONF_MAC,
|
CONF_MAC,
|
||||||
CONF_MODEL,
|
CONF_MODEL,
|
||||||
@ -44,8 +45,12 @@ from homeassistant.helpers.event import async_track_time_interval
|
|||||||
from homeassistant.helpers.typing import ConfigType
|
from homeassistant.helpers.typing import ConfigType
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
|
CONF_AES_KEYS,
|
||||||
|
CONF_CONFIG_ENTRY_MINOR_VERSION,
|
||||||
|
CONF_CONNECTION_PARAMETERS,
|
||||||
CONF_CREDENTIALS_HASH,
|
CONF_CREDENTIALS_HASH,
|
||||||
CONF_DEVICE_CONFIG,
|
CONF_DEVICE_CONFIG,
|
||||||
|
CONF_USES_HTTP,
|
||||||
CONNECT_TIMEOUT,
|
CONNECT_TIMEOUT,
|
||||||
DISCOVERY_TIMEOUT,
|
DISCOVERY_TIMEOUT,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
@ -85,9 +90,7 @@ def async_trigger_discovery(
|
|||||||
CONF_ALIAS: device.alias or mac_alias(device.mac),
|
CONF_ALIAS: device.alias or mac_alias(device.mac),
|
||||||
CONF_HOST: device.host,
|
CONF_HOST: device.host,
|
||||||
CONF_MAC: formatted_mac,
|
CONF_MAC: formatted_mac,
|
||||||
CONF_DEVICE_CONFIG: device.config.to_dict(
|
CONF_DEVICE: device,
|
||||||
exclude_credentials=True,
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -136,25 +139,27 @@ async def async_setup_entry(hass: HomeAssistant, entry: TPLinkConfigEntry) -> bo
|
|||||||
host: str = entry.data[CONF_HOST]
|
host: str = entry.data[CONF_HOST]
|
||||||
credentials = await get_credentials(hass)
|
credentials = await get_credentials(hass)
|
||||||
entry_credentials_hash = entry.data.get(CONF_CREDENTIALS_HASH)
|
entry_credentials_hash = entry.data.get(CONF_CREDENTIALS_HASH)
|
||||||
|
entry_use_http = entry.data.get(CONF_USES_HTTP, False)
|
||||||
|
entry_aes_keys = entry.data.get(CONF_AES_KEYS)
|
||||||
|
|
||||||
config: DeviceConfig | None = None
|
conn_params: Device.ConnectionParameters | None = None
|
||||||
if config_dict := entry.data.get(CONF_DEVICE_CONFIG):
|
if conn_params_dict := entry.data.get(CONF_CONNECTION_PARAMETERS):
|
||||||
try:
|
try:
|
||||||
config = DeviceConfig.from_dict(config_dict)
|
conn_params = Device.ConnectionParameters.from_dict(conn_params_dict)
|
||||||
except KasaException:
|
except KasaException:
|
||||||
_LOGGER.warning(
|
_LOGGER.warning(
|
||||||
"Invalid connection type dict for %s: %s", host, config_dict
|
"Invalid connection parameters dict for %s: %s", host, conn_params_dict
|
||||||
)
|
)
|
||||||
|
|
||||||
if not config:
|
client = create_async_tplink_clientsession(hass) if entry_use_http else None
|
||||||
config = DeviceConfig(host)
|
config = DeviceConfig(
|
||||||
else:
|
host,
|
||||||
config.host = host
|
timeout=CONNECT_TIMEOUT,
|
||||||
|
http_client=client,
|
||||||
config.timeout = CONNECT_TIMEOUT
|
aes_keys=entry_aes_keys,
|
||||||
if config.uses_http is True:
|
)
|
||||||
config.http_client = create_async_tplink_clientsession(hass)
|
if conn_params:
|
||||||
|
config.connection_type = conn_params
|
||||||
# If we have in memory credentials use them otherwise check for credentials_hash
|
# If we have in memory credentials use them otherwise check for credentials_hash
|
||||||
if credentials:
|
if credentials:
|
||||||
config.credentials = credentials
|
config.credentials = credentials
|
||||||
@ -173,14 +178,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: TPLinkConfigEntry) -> bo
|
|||||||
raise ConfigEntryNotReady from ex
|
raise ConfigEntryNotReady from ex
|
||||||
|
|
||||||
device_credentials_hash = device.credentials_hash
|
device_credentials_hash = device.credentials_hash
|
||||||
device_config_dict = device.config.to_dict(exclude_credentials=True)
|
|
||||||
# Do not store the credentials hash inside the device_config
|
# We not need to update the connection parameters or the use_http here
|
||||||
device_config_dict.pop(CONF_CREDENTIALS_HASH, None)
|
# because if they were wrong we would have failed to connect.
|
||||||
|
# Discovery will update those if necessary.
|
||||||
updates: dict[str, Any] = {}
|
updates: dict[str, Any] = {}
|
||||||
if device_credentials_hash and device_credentials_hash != entry_credentials_hash:
|
if device_credentials_hash and device_credentials_hash != entry_credentials_hash:
|
||||||
updates[CONF_CREDENTIALS_HASH] = device_credentials_hash
|
updates[CONF_CREDENTIALS_HASH] = device_credentials_hash
|
||||||
if device_config_dict != config_dict:
|
if entry_aes_keys != device.config.aes_keys:
|
||||||
updates[CONF_DEVICE_CONFIG] = device_config_dict
|
updates[CONF_AES_KEYS] = device.config.aes_keys
|
||||||
if entry.data.get(CONF_ALIAS) != device.alias:
|
if entry.data.get(CONF_ALIAS) != device.alias:
|
||||||
updates[CONF_ALIAS] = device.alias
|
updates[CONF_ALIAS] = device.alias
|
||||||
if entry.data.get(CONF_MODEL) != device.model:
|
if entry.data.get(CONF_MODEL) != device.model:
|
||||||
@ -307,12 +313,20 @@ def _device_id_is_mac_or_none(mac: str, device_ids: Iterable[str]) -> str | None
|
|||||||
|
|
||||||
async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
||||||
"""Migrate old entry."""
|
"""Migrate old entry."""
|
||||||
version = config_entry.version
|
entry_version = config_entry.version
|
||||||
minor_version = config_entry.minor_version
|
entry_minor_version = config_entry.minor_version
|
||||||
|
# having a condition to check for the current version allows
|
||||||
|
# tests to be written per migration step.
|
||||||
|
config_flow_minor_version = CONF_CONFIG_ENTRY_MINOR_VERSION
|
||||||
|
|
||||||
_LOGGER.debug("Migrating from version %s.%s", version, minor_version)
|
new_minor_version = 3
|
||||||
|
if (
|
||||||
if version == 1 and minor_version < 3:
|
entry_version == 1
|
||||||
|
and entry_minor_version < new_minor_version <= config_flow_minor_version
|
||||||
|
):
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Migrating from version %s.%s", entry_version, entry_minor_version
|
||||||
|
)
|
||||||
# Previously entities on child devices added themselves to the parent
|
# Previously entities on child devices added themselves to the parent
|
||||||
# device and set their device id as identifiers along with mac
|
# device and set their device id as identifiers along with mac
|
||||||
# as a connection which creates a single device entry linked by all
|
# as a connection which creates a single device entry linked by all
|
||||||
@ -359,12 +373,19 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
|
|||||||
new_identifiers,
|
new_identifiers,
|
||||||
)
|
)
|
||||||
|
|
||||||
minor_version = 3
|
hass.config_entries.async_update_entry(
|
||||||
hass.config_entries.async_update_entry(config_entry, minor_version=3)
|
config_entry, minor_version=new_minor_version
|
||||||
|
)
|
||||||
|
|
||||||
_LOGGER.debug("Migration to version %s.%s complete", version, minor_version)
|
_LOGGER.debug(
|
||||||
|
"Migration to version %s.%s complete", entry_version, new_minor_version
|
||||||
|
)
|
||||||
|
|
||||||
if version == 1 and minor_version == 3:
|
new_minor_version = 4
|
||||||
|
if (
|
||||||
|
entry_version == 1
|
||||||
|
and entry_minor_version < new_minor_version <= config_flow_minor_version
|
||||||
|
):
|
||||||
# credentials_hash stored in the device_config should be moved to data.
|
# credentials_hash stored in the device_config should be moved to data.
|
||||||
updates: dict[str, Any] = {}
|
updates: dict[str, Any] = {}
|
||||||
if config_dict := config_entry.data.get(CONF_DEVICE_CONFIG):
|
if config_dict := config_entry.data.get(CONF_DEVICE_CONFIG):
|
||||||
@ -372,15 +393,44 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
|
|||||||
if credentials_hash := config_dict.pop(CONF_CREDENTIALS_HASH, None):
|
if credentials_hash := config_dict.pop(CONF_CREDENTIALS_HASH, None):
|
||||||
updates[CONF_CREDENTIALS_HASH] = credentials_hash
|
updates[CONF_CREDENTIALS_HASH] = credentials_hash
|
||||||
updates[CONF_DEVICE_CONFIG] = config_dict
|
updates[CONF_DEVICE_CONFIG] = config_dict
|
||||||
minor_version = 4
|
|
||||||
hass.config_entries.async_update_entry(
|
hass.config_entries.async_update_entry(
|
||||||
config_entry,
|
config_entry,
|
||||||
data={
|
data={
|
||||||
**config_entry.data,
|
**config_entry.data,
|
||||||
**updates,
|
**updates,
|
||||||
},
|
},
|
||||||
minor_version=minor_version,
|
minor_version=new_minor_version,
|
||||||
|
)
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Migration to version %s.%s complete", entry_version, new_minor_version
|
||||||
)
|
)
|
||||||
_LOGGER.debug("Migration to version %s.%s complete", version, minor_version)
|
|
||||||
|
|
||||||
|
new_minor_version = 5
|
||||||
|
if (
|
||||||
|
entry_version == 1
|
||||||
|
and entry_minor_version < new_minor_version <= config_flow_minor_version
|
||||||
|
):
|
||||||
|
# complete device config no longer to be stored, only required
|
||||||
|
# attributes like connection parameters and aes_keys
|
||||||
|
updates = {}
|
||||||
|
entry_data = {
|
||||||
|
k: v for k, v in config_entry.data.items() if k != CONF_DEVICE_CONFIG
|
||||||
|
}
|
||||||
|
if config_dict := config_entry.data.get(CONF_DEVICE_CONFIG):
|
||||||
|
assert isinstance(config_dict, dict)
|
||||||
|
if connection_parameters := config_dict.get("connection_type"):
|
||||||
|
updates[CONF_CONNECTION_PARAMETERS] = connection_parameters
|
||||||
|
if (use_http := config_dict.get(CONF_USES_HTTP)) is not None:
|
||||||
|
updates[CONF_USES_HTTP] = use_http
|
||||||
|
hass.config_entries.async_update_entry(
|
||||||
|
config_entry,
|
||||||
|
data={
|
||||||
|
**entry_data,
|
||||||
|
**updates,
|
||||||
|
},
|
||||||
|
minor_version=new_minor_version,
|
||||||
|
)
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Migration to version %s.%s complete", entry_version, new_minor_version
|
||||||
|
)
|
||||||
return True
|
return True
|
||||||
|
@ -46,9 +46,11 @@ from . import (
|
|||||||
set_credentials,
|
set_credentials,
|
||||||
)
|
)
|
||||||
from .const import (
|
from .const import (
|
||||||
CONF_CONNECTION_TYPE,
|
CONF_AES_KEYS,
|
||||||
|
CONF_CONFIG_ENTRY_MINOR_VERSION,
|
||||||
|
CONF_CONNECTION_PARAMETERS,
|
||||||
CONF_CREDENTIALS_HASH,
|
CONF_CREDENTIALS_HASH,
|
||||||
CONF_DEVICE_CONFIG,
|
CONF_USES_HTTP,
|
||||||
CONNECT_TIMEOUT,
|
CONNECT_TIMEOUT,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
)
|
)
|
||||||
@ -64,7 +66,7 @@ class TPLinkConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
"""Handle a config flow for tplink."""
|
"""Handle a config flow for tplink."""
|
||||||
|
|
||||||
VERSION = 1
|
VERSION = 1
|
||||||
MINOR_VERSION = 4
|
MINOR_VERSION = CONF_CONFIG_ENTRY_MINOR_VERSION
|
||||||
reauth_entry: ConfigEntry | None = None
|
reauth_entry: ConfigEntry | None = None
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
@ -87,38 +89,43 @@ class TPLinkConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
return await self._async_handle_discovery(
|
return await self._async_handle_discovery(
|
||||||
discovery_info[CONF_HOST],
|
discovery_info[CONF_HOST],
|
||||||
discovery_info[CONF_MAC],
|
discovery_info[CONF_MAC],
|
||||||
discovery_info[CONF_DEVICE_CONFIG],
|
discovery_info[CONF_DEVICE],
|
||||||
)
|
)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _get_config_updates(
|
def _get_config_updates(
|
||||||
self, entry: ConfigEntry, host: str, config: dict
|
self, entry: ConfigEntry, host: str, device: Device | None
|
||||||
) -> dict | None:
|
) -> dict | None:
|
||||||
"""Return updates if the host or device config has changed."""
|
"""Return updates if the host or device config has changed."""
|
||||||
entry_data = entry.data
|
entry_data = entry.data
|
||||||
entry_config_dict = entry_data.get(CONF_DEVICE_CONFIG)
|
updates: dict[str, Any] = {}
|
||||||
if entry_config_dict == config and entry_data[CONF_HOST] == host:
|
new_connection_params = False
|
||||||
|
if entry_data[CONF_HOST] != host:
|
||||||
|
updates[CONF_HOST] = host
|
||||||
|
if device:
|
||||||
|
device_conn_params_dict = device.config.connection_type.to_dict()
|
||||||
|
entry_conn_params_dict = entry_data.get(CONF_CONNECTION_PARAMETERS)
|
||||||
|
if device_conn_params_dict != entry_conn_params_dict:
|
||||||
|
new_connection_params = True
|
||||||
|
updates[CONF_CONNECTION_PARAMETERS] = device_conn_params_dict
|
||||||
|
updates[CONF_USES_HTTP] = device.config.uses_http
|
||||||
|
if not updates:
|
||||||
return None
|
return None
|
||||||
updates = {**entry.data, CONF_DEVICE_CONFIG: config, CONF_HOST: host}
|
updates = {**entry.data, **updates}
|
||||||
# If the connection parameters have changed the credentials_hash will be invalid.
|
# If the connection parameters have changed the credentials_hash will be invalid.
|
||||||
if (
|
if new_connection_params:
|
||||||
entry_config_dict
|
|
||||||
and isinstance(entry_config_dict, dict)
|
|
||||||
and entry_config_dict.get(CONF_CONNECTION_TYPE)
|
|
||||||
!= config.get(CONF_CONNECTION_TYPE)
|
|
||||||
):
|
|
||||||
updates.pop(CONF_CREDENTIALS_HASH, None)
|
updates.pop(CONF_CREDENTIALS_HASH, None)
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"Connection type changed for %s from %s to: %s",
|
"Connection type changed for %s from %s to: %s",
|
||||||
host,
|
host,
|
||||||
entry_config_dict.get(CONF_CONNECTION_TYPE),
|
entry_conn_params_dict,
|
||||||
config.get(CONF_CONNECTION_TYPE),
|
device_conn_params_dict,
|
||||||
)
|
)
|
||||||
return updates
|
return updates
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _update_config_if_entry_in_setup_error(
|
def _update_config_if_entry_in_setup_error(
|
||||||
self, entry: ConfigEntry, host: str, config: dict
|
self, entry: ConfigEntry, host: str, device: Device | None
|
||||||
) -> ConfigFlowResult | None:
|
) -> ConfigFlowResult | None:
|
||||||
"""If discovery encounters a device that is in SETUP_ERROR or SETUP_RETRY update the device config."""
|
"""If discovery encounters a device that is in SETUP_ERROR or SETUP_RETRY update the device config."""
|
||||||
if entry.state not in (
|
if entry.state not in (
|
||||||
@ -126,7 +133,7 @@ class TPLinkConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
ConfigEntryState.SETUP_RETRY,
|
ConfigEntryState.SETUP_RETRY,
|
||||||
):
|
):
|
||||||
return None
|
return None
|
||||||
if updates := self._get_config_updates(entry, host, config):
|
if updates := self._get_config_updates(entry, host, device):
|
||||||
return self.async_update_reload_and_abort(
|
return self.async_update_reload_and_abort(
|
||||||
entry,
|
entry,
|
||||||
data=updates,
|
data=updates,
|
||||||
@ -135,19 +142,15 @@ class TPLinkConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
async def _async_handle_discovery(
|
async def _async_handle_discovery(
|
||||||
self, host: str, formatted_mac: str, config: dict | None = None
|
self, host: str, formatted_mac: str, device: Device | None = None
|
||||||
) -> ConfigFlowResult:
|
) -> ConfigFlowResult:
|
||||||
"""Handle any discovery."""
|
"""Handle any discovery."""
|
||||||
current_entry = await self.async_set_unique_id(
|
current_entry = await self.async_set_unique_id(
|
||||||
formatted_mac, raise_on_progress=False
|
formatted_mac, raise_on_progress=False
|
||||||
)
|
)
|
||||||
if (
|
if current_entry and (
|
||||||
config
|
|
||||||
and current_entry
|
|
||||||
and (
|
|
||||||
result := self._update_config_if_entry_in_setup_error(
|
result := self._update_config_if_entry_in_setup_error(
|
||||||
current_entry, host, config
|
current_entry, host, device
|
||||||
)
|
|
||||||
)
|
)
|
||||||
):
|
):
|
||||||
return result
|
return result
|
||||||
@ -159,6 +162,10 @@ class TPLinkConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
return self.async_abort(reason="already_in_progress")
|
return self.async_abort(reason="already_in_progress")
|
||||||
credentials = await get_credentials(self.hass)
|
credentials = await get_credentials(self.hass)
|
||||||
try:
|
try:
|
||||||
|
if device:
|
||||||
|
self._discovered_device = device
|
||||||
|
await self._async_try_connect(device, credentials)
|
||||||
|
else:
|
||||||
await self._async_try_discover_and_update(
|
await self._async_try_discover_and_update(
|
||||||
host, credentials, raise_on_progress=True
|
host, credentials, raise_on_progress=True
|
||||||
)
|
)
|
||||||
@ -381,14 +388,15 @@ class TPLinkConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
# This is only ever called after a successful device update so we know that
|
# This is only ever called after a successful device update so we know that
|
||||||
# the credential_hash is correct and should be saved.
|
# the credential_hash is correct and should be saved.
|
||||||
self._abort_if_unique_id_configured(updates={CONF_HOST: device.host})
|
self._abort_if_unique_id_configured(updates={CONF_HOST: device.host})
|
||||||
data = {
|
data: dict[str, Any] = {
|
||||||
CONF_HOST: device.host,
|
CONF_HOST: device.host,
|
||||||
CONF_ALIAS: device.alias,
|
CONF_ALIAS: device.alias,
|
||||||
CONF_MODEL: device.model,
|
CONF_MODEL: device.model,
|
||||||
CONF_DEVICE_CONFIG: device.config.to_dict(
|
CONF_CONNECTION_PARAMETERS: device.config.connection_type.to_dict(),
|
||||||
exclude_credentials=True,
|
CONF_USES_HTTP: device.config.uses_http,
|
||||||
),
|
|
||||||
}
|
}
|
||||||
|
if device.config.aes_keys:
|
||||||
|
data[CONF_AES_KEYS] = device.config.aes_keys
|
||||||
if device.credentials_hash:
|
if device.credentials_hash:
|
||||||
data[CONF_CREDENTIALS_HASH] = device.credentials_hash
|
data[CONF_CREDENTIALS_HASH] = device.credentials_hash
|
||||||
return self.async_create_entry(
|
return self.async_create_entry(
|
||||||
@ -494,8 +502,7 @@ class TPLinkConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
placeholders["error"] = str(ex)
|
placeholders["error"] = str(ex)
|
||||||
else:
|
else:
|
||||||
await set_credentials(self.hass, username, password)
|
await set_credentials(self.hass, username, password)
|
||||||
config = device.config.to_dict(exclude_credentials=True)
|
if updates := self._get_config_updates(reauth_entry, host, device):
|
||||||
if updates := self._get_config_updates(reauth_entry, host, config):
|
|
||||||
self.hass.config_entries.async_update_entry(
|
self.hass.config_entries.async_update_entry(
|
||||||
reauth_entry, data=updates
|
reauth_entry, data=updates
|
||||||
)
|
)
|
||||||
|
@ -21,7 +21,11 @@ ATTR_TOTAL_ENERGY_KWH: Final = "total_energy_kwh"
|
|||||||
|
|
||||||
CONF_DEVICE_CONFIG: Final = "device_config"
|
CONF_DEVICE_CONFIG: Final = "device_config"
|
||||||
CONF_CREDENTIALS_HASH: Final = "credentials_hash"
|
CONF_CREDENTIALS_HASH: Final = "credentials_hash"
|
||||||
CONF_CONNECTION_TYPE: Final = "connection_type"
|
CONF_CONNECTION_PARAMETERS: Final = "connection_parameters"
|
||||||
|
CONF_USES_HTTP: Final = "uses_http"
|
||||||
|
CONF_AES_KEYS: Final = "aes_keys"
|
||||||
|
|
||||||
|
CONF_CONFIG_ENTRY_MINOR_VERSION: Final = 5
|
||||||
|
|
||||||
PLATFORMS: Final = [
|
PLATFORMS: Final = [
|
||||||
Platform.BINARY_SENSOR,
|
Platform.BINARY_SENSOR,
|
||||||
|
@ -301,5 +301,5 @@
|
|||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["kasa"],
|
"loggers": ["kasa"],
|
||||||
"quality_scale": "platinum",
|
"quality_scale": "platinum",
|
||||||
"requirements": ["python-kasa[speedups]==0.7.2"]
|
"requirements": ["python-kasa[speedups]==0.7.3"]
|
||||||
}
|
}
|
||||||
|
@ -13,5 +13,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/yale",
|
"documentation": "https://www.home-assistant.io/integrations/yale",
|
||||||
"iot_class": "cloud_push",
|
"iot_class": "cloud_push",
|
||||||
"loggers": ["socketio", "engineio", "yalexs"],
|
"loggers": ["socketio", "engineio", "yalexs"],
|
||||||
"requirements": ["yalexs==8.6.3", "yalexs-ble==2.4.3"]
|
"requirements": ["yalexs==8.6.4", "yalexs-ble==2.4.3"]
|
||||||
}
|
}
|
||||||
|
@ -154,10 +154,15 @@ class YaleDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
|||||||
except YALE_BASE_ERRORS as error:
|
except YALE_BASE_ERRORS as error:
|
||||||
raise UpdateFailed from error
|
raise UpdateFailed from error
|
||||||
|
|
||||||
|
cycle = data.cycle["data"] if data.cycle else None
|
||||||
|
status = data.status["data"] if data.status else None
|
||||||
|
online = data.online["data"] if data.online else None
|
||||||
|
panel_info = data.panel_info["data"] if data.panel_info else None
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"arm_status": arm_status,
|
"arm_status": arm_status,
|
||||||
"cycle": data.cycle,
|
"cycle": cycle,
|
||||||
"status": data.status,
|
"status": status,
|
||||||
"online": data.online,
|
"online": online,
|
||||||
"panel_info": data.panel_info,
|
"panel_info": panel_info,
|
||||||
}
|
}
|
||||||
|
@ -1166,7 +1166,8 @@ CONF_ZHA_OPTIONS_SCHEMA = vol.Schema(
|
|||||||
CONF_CONSIDER_UNAVAILABLE_BATTERY,
|
CONF_CONSIDER_UNAVAILABLE_BATTERY,
|
||||||
default=CONF_DEFAULT_CONSIDER_UNAVAILABLE_BATTERY,
|
default=CONF_DEFAULT_CONSIDER_UNAVAILABLE_BATTERY,
|
||||||
): cv.positive_int,
|
): cv.positive_int,
|
||||||
}
|
},
|
||||||
|
extra=vol.REMOVE_EXTRA,
|
||||||
)
|
)
|
||||||
|
|
||||||
CONF_ZHA_ALARM_SCHEMA = vol.Schema(
|
CONF_ZHA_ALARM_SCHEMA = vol.Schema(
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
"zha",
|
"zha",
|
||||||
"universal_silabs_flasher"
|
"universal_silabs_flasher"
|
||||||
],
|
],
|
||||||
"requirements": ["universal-silabs-flasher==0.0.22", "zha==0.0.32"],
|
"requirements": ["universal-silabs-flasher==0.0.22", "zha==0.0.33"],
|
||||||
"usb": [
|
"usb": [
|
||||||
{
|
{
|
||||||
"vid": "10C4",
|
"vid": "10C4",
|
||||||
|
@ -24,7 +24,7 @@ if TYPE_CHECKING:
|
|||||||
APPLICATION_NAME: Final = "HomeAssistant"
|
APPLICATION_NAME: Final = "HomeAssistant"
|
||||||
MAJOR_VERSION: Final = 2024
|
MAJOR_VERSION: Final = 2024
|
||||||
MINOR_VERSION: Final = 9
|
MINOR_VERSION: Final = 9
|
||||||
PATCH_VERSION: Final = "1"
|
PATCH_VERSION: Final = "2"
|
||||||
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||||
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
||||||
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 12, 0)
|
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 12, 0)
|
||||||
|
@ -31,7 +31,7 @@ habluetooth==3.4.0
|
|||||||
hass-nabucasa==0.81.1
|
hass-nabucasa==0.81.1
|
||||||
hassil==1.7.4
|
hassil==1.7.4
|
||||||
home-assistant-bluetooth==1.12.2
|
home-assistant-bluetooth==1.12.2
|
||||||
home-assistant-frontend==20240906.0
|
home-assistant-frontend==20240909.1
|
||||||
home-assistant-intents==2024.9.4
|
home-assistant-intents==2024.9.4
|
||||||
httpx==0.27.0
|
httpx==0.27.0
|
||||||
ifaddr==0.2.0
|
ifaddr==0.2.0
|
||||||
@ -185,3 +185,9 @@ tuf>=4.0.0
|
|||||||
|
|
||||||
# https://github.com/jd/tenacity/issues/471
|
# https://github.com/jd/tenacity/issues/471
|
||||||
tenacity!=8.4.0
|
tenacity!=8.4.0
|
||||||
|
|
||||||
|
# pyasn1.compat.octets was removed in pyasn1 0.6.1 and breaks some integrations
|
||||||
|
# and tests that import it directly
|
||||||
|
# https://github.com/pyasn1/pyasn1/pull/60
|
||||||
|
# https://github.com/lextudio/pysnmp/issues/114
|
||||||
|
pyasn1==0.6.0
|
||||||
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "homeassistant"
|
name = "homeassistant"
|
||||||
version = "2024.9.1"
|
version = "2024.9.2"
|
||||||
license = {text = "Apache-2.0"}
|
license = {text = "Apache-2.0"}
|
||||||
description = "Open-source home automation platform running on Python 3."
|
description = "Open-source home automation platform running on Python 3."
|
||||||
readme = "README.rst"
|
readme = "README.rst"
|
||||||
|
@ -273,10 +273,10 @@ aiokef==0.2.16
|
|||||||
aiolifx-effects==0.3.2
|
aiolifx-effects==0.3.2
|
||||||
|
|
||||||
# homeassistant.components.lifx
|
# homeassistant.components.lifx
|
||||||
aiolifx-themes==0.5.0
|
aiolifx-themes==0.5.5
|
||||||
|
|
||||||
# homeassistant.components.lifx
|
# homeassistant.components.lifx
|
||||||
aiolifx==1.0.9
|
aiolifx==1.1.1
|
||||||
|
|
||||||
# homeassistant.components.livisi
|
# homeassistant.components.livisi
|
||||||
aiolivisi==0.0.19
|
aiolivisi==0.0.19
|
||||||
@ -350,7 +350,7 @@ aioridwell==2024.01.0
|
|||||||
aioruckus==0.41
|
aioruckus==0.41
|
||||||
|
|
||||||
# homeassistant.components.russound_rio
|
# homeassistant.components.russound_rio
|
||||||
aiorussound==3.0.4
|
aiorussound==3.0.5
|
||||||
|
|
||||||
# homeassistant.components.ruuvi_gateway
|
# homeassistant.components.ruuvi_gateway
|
||||||
aioruuvigateway==0.1.0
|
aioruuvigateway==0.1.0
|
||||||
@ -992,7 +992,7 @@ google-cloud-texttospeech==2.16.3
|
|||||||
google-generativeai==0.6.0
|
google-generativeai==0.6.0
|
||||||
|
|
||||||
# homeassistant.components.nest
|
# homeassistant.components.nest
|
||||||
google-nest-sdm==5.0.0
|
google-nest-sdm==5.0.1
|
||||||
|
|
||||||
# homeassistant.components.google_travel_time
|
# homeassistant.components.google_travel_time
|
||||||
googlemaps==2.5.1
|
googlemaps==2.5.1
|
||||||
@ -1007,7 +1007,7 @@ gotailwind==0.2.3
|
|||||||
govee-ble==0.40.0
|
govee-ble==0.40.0
|
||||||
|
|
||||||
# homeassistant.components.govee_light_local
|
# homeassistant.components.govee_light_local
|
||||||
govee-local-api==1.5.1
|
govee-local-api==1.5.2
|
||||||
|
|
||||||
# homeassistant.components.remote_rpi_gpio
|
# homeassistant.components.remote_rpi_gpio
|
||||||
gpiozero==1.6.2
|
gpiozero==1.6.2
|
||||||
@ -1102,7 +1102,7 @@ hole==0.8.0
|
|||||||
holidays==0.56
|
holidays==0.56
|
||||||
|
|
||||||
# homeassistant.components.frontend
|
# homeassistant.components.frontend
|
||||||
home-assistant-frontend==20240906.0
|
home-assistant-frontend==20240909.1
|
||||||
|
|
||||||
# homeassistant.components.conversation
|
# homeassistant.components.conversation
|
||||||
home-assistant-intents==2024.9.4
|
home-assistant-intents==2024.9.4
|
||||||
@ -1225,7 +1225,7 @@ kiwiki-client==0.1.1
|
|||||||
knocki==0.3.1
|
knocki==0.3.1
|
||||||
|
|
||||||
# homeassistant.components.knx
|
# homeassistant.components.knx
|
||||||
knx-frontend==2024.9.4.64538
|
knx-frontend==2024.9.10.221729
|
||||||
|
|
||||||
# homeassistant.components.konnected
|
# homeassistant.components.konnected
|
||||||
konnected==1.2.0
|
konnected==1.2.0
|
||||||
@ -1282,7 +1282,7 @@ linear-garage-door==0.2.9
|
|||||||
linode-api==4.1.9b1
|
linode-api==4.1.9b1
|
||||||
|
|
||||||
# homeassistant.components.lamarzocco
|
# homeassistant.components.lamarzocco
|
||||||
lmcloud==1.2.2
|
lmcloud==1.2.3
|
||||||
|
|
||||||
# homeassistant.components.google_maps
|
# homeassistant.components.google_maps
|
||||||
locationsharinglib==5.0.1
|
locationsharinglib==5.0.1
|
||||||
@ -1366,7 +1366,7 @@ monzopy==1.3.2
|
|||||||
mopeka-iot-ble==0.8.0
|
mopeka-iot-ble==0.8.0
|
||||||
|
|
||||||
# homeassistant.components.motion_blinds
|
# homeassistant.components.motion_blinds
|
||||||
motionblinds==0.6.24
|
motionblinds==0.6.25
|
||||||
|
|
||||||
# homeassistant.components.motionblinds_ble
|
# homeassistant.components.motionblinds_ble
|
||||||
motionblindsble==0.1.1
|
motionblindsble==0.1.1
|
||||||
@ -2313,7 +2313,7 @@ python-join-api==0.0.9
|
|||||||
python-juicenet==1.1.0
|
python-juicenet==1.1.0
|
||||||
|
|
||||||
# homeassistant.components.tplink
|
# homeassistant.components.tplink
|
||||||
python-kasa[speedups]==0.7.2
|
python-kasa[speedups]==0.7.3
|
||||||
|
|
||||||
# homeassistant.components.linkplay
|
# homeassistant.components.linkplay
|
||||||
python-linkplay==0.0.9
|
python-linkplay==0.0.9
|
||||||
@ -2546,7 +2546,7 @@ rpi-bad-power==0.1.0
|
|||||||
rtsp-to-webrtc==0.5.1
|
rtsp-to-webrtc==0.5.1
|
||||||
|
|
||||||
# homeassistant.components.russound_rnet
|
# homeassistant.components.russound_rnet
|
||||||
russound==0.1.9
|
russound==0.2.0
|
||||||
|
|
||||||
# homeassistant.components.ruuvitag_ble
|
# homeassistant.components.ruuvitag_ble
|
||||||
ruuvitag-ble==0.1.2
|
ruuvitag-ble==0.1.2
|
||||||
@ -2595,7 +2595,7 @@ sensorpush-ble==1.6.2
|
|||||||
sentry-sdk==1.40.3
|
sentry-sdk==1.40.3
|
||||||
|
|
||||||
# homeassistant.components.sfr_box
|
# homeassistant.components.sfr_box
|
||||||
sfrbox-api==0.0.10
|
sfrbox-api==0.0.11
|
||||||
|
|
||||||
# homeassistant.components.sharkiq
|
# homeassistant.components.sharkiq
|
||||||
sharkiq==1.0.2
|
sharkiq==1.0.2
|
||||||
@ -2976,7 +2976,7 @@ yalexs-ble==2.4.3
|
|||||||
|
|
||||||
# homeassistant.components.august
|
# homeassistant.components.august
|
||||||
# homeassistant.components.yale
|
# homeassistant.components.yale
|
||||||
yalexs==8.6.3
|
yalexs==8.6.4
|
||||||
|
|
||||||
# homeassistant.components.yeelight
|
# homeassistant.components.yeelight
|
||||||
yeelight==0.7.14
|
yeelight==0.7.14
|
||||||
@ -3009,7 +3009,7 @@ zeroconf==0.133.0
|
|||||||
zeversolar==0.3.1
|
zeversolar==0.3.1
|
||||||
|
|
||||||
# homeassistant.components.zha
|
# homeassistant.components.zha
|
||||||
zha==0.0.32
|
zha==0.0.33
|
||||||
|
|
||||||
# homeassistant.components.zhong_hong
|
# homeassistant.components.zhong_hong
|
||||||
zhong-hong-hvac==1.0.12
|
zhong-hong-hvac==1.0.12
|
||||||
|
@ -255,10 +255,10 @@ aiokafka==0.10.0
|
|||||||
aiolifx-effects==0.3.2
|
aiolifx-effects==0.3.2
|
||||||
|
|
||||||
# homeassistant.components.lifx
|
# homeassistant.components.lifx
|
||||||
aiolifx-themes==0.5.0
|
aiolifx-themes==0.5.5
|
||||||
|
|
||||||
# homeassistant.components.lifx
|
# homeassistant.components.lifx
|
||||||
aiolifx==1.0.9
|
aiolifx==1.1.1
|
||||||
|
|
||||||
# homeassistant.components.livisi
|
# homeassistant.components.livisi
|
||||||
aiolivisi==0.0.19
|
aiolivisi==0.0.19
|
||||||
@ -332,7 +332,7 @@ aioridwell==2024.01.0
|
|||||||
aioruckus==0.41
|
aioruckus==0.41
|
||||||
|
|
||||||
# homeassistant.components.russound_rio
|
# homeassistant.components.russound_rio
|
||||||
aiorussound==3.0.4
|
aiorussound==3.0.5
|
||||||
|
|
||||||
# homeassistant.components.ruuvi_gateway
|
# homeassistant.components.ruuvi_gateway
|
||||||
aioruuvigateway==0.1.0
|
aioruuvigateway==0.1.0
|
||||||
@ -839,7 +839,7 @@ google-cloud-pubsub==2.13.11
|
|||||||
google-generativeai==0.6.0
|
google-generativeai==0.6.0
|
||||||
|
|
||||||
# homeassistant.components.nest
|
# homeassistant.components.nest
|
||||||
google-nest-sdm==5.0.0
|
google-nest-sdm==5.0.1
|
||||||
|
|
||||||
# homeassistant.components.google_travel_time
|
# homeassistant.components.google_travel_time
|
||||||
googlemaps==2.5.1
|
googlemaps==2.5.1
|
||||||
@ -851,7 +851,7 @@ gotailwind==0.2.3
|
|||||||
govee-ble==0.40.0
|
govee-ble==0.40.0
|
||||||
|
|
||||||
# homeassistant.components.govee_light_local
|
# homeassistant.components.govee_light_local
|
||||||
govee-local-api==1.5.1
|
govee-local-api==1.5.2
|
||||||
|
|
||||||
# homeassistant.components.gpsd
|
# homeassistant.components.gpsd
|
||||||
gps3==0.33.3
|
gps3==0.33.3
|
||||||
@ -925,7 +925,7 @@ hole==0.8.0
|
|||||||
holidays==0.56
|
holidays==0.56
|
||||||
|
|
||||||
# homeassistant.components.frontend
|
# homeassistant.components.frontend
|
||||||
home-assistant-frontend==20240906.0
|
home-assistant-frontend==20240909.1
|
||||||
|
|
||||||
# homeassistant.components.conversation
|
# homeassistant.components.conversation
|
||||||
home-assistant-intents==2024.9.4
|
home-assistant-intents==2024.9.4
|
||||||
@ -1021,7 +1021,7 @@ kegtron-ble==0.4.0
|
|||||||
knocki==0.3.1
|
knocki==0.3.1
|
||||||
|
|
||||||
# homeassistant.components.knx
|
# homeassistant.components.knx
|
||||||
knx-frontend==2024.9.4.64538
|
knx-frontend==2024.9.10.221729
|
||||||
|
|
||||||
# homeassistant.components.konnected
|
# homeassistant.components.konnected
|
||||||
konnected==1.2.0
|
konnected==1.2.0
|
||||||
@ -1060,7 +1060,7 @@ libsoundtouch==0.8
|
|||||||
linear-garage-door==0.2.9
|
linear-garage-door==0.2.9
|
||||||
|
|
||||||
# homeassistant.components.lamarzocco
|
# homeassistant.components.lamarzocco
|
||||||
lmcloud==1.2.2
|
lmcloud==1.2.3
|
||||||
|
|
||||||
# homeassistant.components.london_underground
|
# homeassistant.components.london_underground
|
||||||
london-tube-status==0.5
|
london-tube-status==0.5
|
||||||
@ -1132,7 +1132,7 @@ monzopy==1.3.2
|
|||||||
mopeka-iot-ble==0.8.0
|
mopeka-iot-ble==0.8.0
|
||||||
|
|
||||||
# homeassistant.components.motion_blinds
|
# homeassistant.components.motion_blinds
|
||||||
motionblinds==0.6.24
|
motionblinds==0.6.25
|
||||||
|
|
||||||
# homeassistant.components.motionblinds_ble
|
# homeassistant.components.motionblinds_ble
|
||||||
motionblindsble==0.1.1
|
motionblindsble==0.1.1
|
||||||
@ -1831,7 +1831,7 @@ python-izone==1.2.9
|
|||||||
python-juicenet==1.1.0
|
python-juicenet==1.1.0
|
||||||
|
|
||||||
# homeassistant.components.tplink
|
# homeassistant.components.tplink
|
||||||
python-kasa[speedups]==0.7.2
|
python-kasa[speedups]==0.7.3
|
||||||
|
|
||||||
# homeassistant.components.linkplay
|
# homeassistant.components.linkplay
|
||||||
python-linkplay==0.0.9
|
python-linkplay==0.0.9
|
||||||
@ -2053,7 +2053,7 @@ sensorpush-ble==1.6.2
|
|||||||
sentry-sdk==1.40.3
|
sentry-sdk==1.40.3
|
||||||
|
|
||||||
# homeassistant.components.sfr_box
|
# homeassistant.components.sfr_box
|
||||||
sfrbox-api==0.0.10
|
sfrbox-api==0.0.11
|
||||||
|
|
||||||
# homeassistant.components.sharkiq
|
# homeassistant.components.sharkiq
|
||||||
sharkiq==1.0.2
|
sharkiq==1.0.2
|
||||||
@ -2356,7 +2356,7 @@ yalexs-ble==2.4.3
|
|||||||
|
|
||||||
# homeassistant.components.august
|
# homeassistant.components.august
|
||||||
# homeassistant.components.yale
|
# homeassistant.components.yale
|
||||||
yalexs==8.6.3
|
yalexs==8.6.4
|
||||||
|
|
||||||
# homeassistant.components.yeelight
|
# homeassistant.components.yeelight
|
||||||
yeelight==0.7.14
|
yeelight==0.7.14
|
||||||
@ -2383,7 +2383,7 @@ zeroconf==0.133.0
|
|||||||
zeversolar==0.3.1
|
zeversolar==0.3.1
|
||||||
|
|
||||||
# homeassistant.components.zha
|
# homeassistant.components.zha
|
||||||
zha==0.0.32
|
zha==0.0.33
|
||||||
|
|
||||||
# homeassistant.components.zwave_js
|
# homeassistant.components.zwave_js
|
||||||
zwave-js-server-python==0.57.0
|
zwave-js-server-python==0.57.0
|
||||||
|
@ -206,6 +206,12 @@ tuf>=4.0.0
|
|||||||
|
|
||||||
# https://github.com/jd/tenacity/issues/471
|
# https://github.com/jd/tenacity/issues/471
|
||||||
tenacity!=8.4.0
|
tenacity!=8.4.0
|
||||||
|
|
||||||
|
# pyasn1.compat.octets was removed in pyasn1 0.6.1 and breaks some integrations
|
||||||
|
# and tests that import it directly
|
||||||
|
# https://github.com/pyasn1/pyasn1/pull/60
|
||||||
|
# https://github.com/lextudio/pysnmp/issues/114
|
||||||
|
pyasn1==0.6.0
|
||||||
"""
|
"""
|
||||||
|
|
||||||
GENERATED_MESSAGE = (
|
GENERATED_MESSAGE = (
|
||||||
|
@ -160,7 +160,6 @@ EXCEPTIONS = {
|
|||||||
"pyvera", # https://github.com/maximvelichko/pyvera/pull/164
|
"pyvera", # https://github.com/maximvelichko/pyvera/pull/164
|
||||||
"pyxeoma", # https://github.com/jeradM/pyxeoma/pull/11
|
"pyxeoma", # https://github.com/jeradM/pyxeoma/pull/11
|
||||||
"repoze.lru",
|
"repoze.lru",
|
||||||
"russound", # https://github.com/laf/russound/pull/14 # codespell:ignore laf
|
|
||||||
"ruuvitag-ble", # https://github.com/Bluetooth-Devices/ruuvitag-ble/pull/10
|
"ruuvitag-ble", # https://github.com/Bluetooth-Devices/ruuvitag-ble/pull/10
|
||||||
"sensirion-ble", # https://github.com/akx/sensirion-ble/pull/9
|
"sensirion-ble", # https://github.com/akx/sensirion-ble/pull/9
|
||||||
"sharp_aquos_rc", # https://github.com/jmoore987/sharp_aquos_rc/pull/14
|
"sharp_aquos_rc", # https://github.com/jmoore987/sharp_aquos_rc/pull/14
|
||||||
|
@ -1,35 +1,7 @@
|
|||||||
# serializer version: 1
|
# serializer version: 1
|
||||||
# name: test_diagnostics
|
# name: test_diagnostics
|
||||||
dict({
|
dict({
|
||||||
'device': dict({
|
'coordinator_data': dict({
|
||||||
'MAC': '00:80:41:19:69:90',
|
|
||||||
'name': 'BSB-LAN',
|
|
||||||
'uptime': 969402857,
|
|
||||||
'version': '1.0.38-20200730234859',
|
|
||||||
}),
|
|
||||||
'info': dict({
|
|
||||||
'controller_family': dict({
|
|
||||||
'data_type': 0,
|
|
||||||
'desc': '',
|
|
||||||
'name': 'Device family',
|
|
||||||
'unit': '',
|
|
||||||
'value': '211',
|
|
||||||
}),
|
|
||||||
'controller_variant': dict({
|
|
||||||
'data_type': 0,
|
|
||||||
'desc': '',
|
|
||||||
'name': 'Device variant',
|
|
||||||
'unit': '',
|
|
||||||
'value': '127',
|
|
||||||
}),
|
|
||||||
'device_identification': dict({
|
|
||||||
'data_type': 7,
|
|
||||||
'desc': '',
|
|
||||||
'name': 'Gerte-Identifikation',
|
|
||||||
'unit': '',
|
|
||||||
'value': 'RVS21.831F/127',
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
'state': dict({
|
'state': dict({
|
||||||
'current_temperature': dict({
|
'current_temperature': dict({
|
||||||
'data_type': 0,
|
'data_type': 0,
|
||||||
@ -74,5 +46,51 @@
|
|||||||
'value': '18.5',
|
'value': '18.5',
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
}),
|
||||||
|
'device': dict({
|
||||||
|
'MAC': '00:80:41:19:69:90',
|
||||||
|
'name': 'BSB-LAN',
|
||||||
|
'uptime': 969402857,
|
||||||
|
'version': '1.0.38-20200730234859',
|
||||||
|
}),
|
||||||
|
'info': dict({
|
||||||
|
'controller_family': dict({
|
||||||
|
'data_type': 0,
|
||||||
|
'desc': '',
|
||||||
|
'name': 'Device family',
|
||||||
|
'unit': '',
|
||||||
|
'value': '211',
|
||||||
|
}),
|
||||||
|
'controller_variant': dict({
|
||||||
|
'data_type': 0,
|
||||||
|
'desc': '',
|
||||||
|
'name': 'Device variant',
|
||||||
|
'unit': '',
|
||||||
|
'value': '127',
|
||||||
|
}),
|
||||||
|
'device_identification': dict({
|
||||||
|
'data_type': 7,
|
||||||
|
'desc': '',
|
||||||
|
'name': 'Gerte-Identifikation',
|
||||||
|
'unit': '',
|
||||||
|
'value': 'RVS21.831F/127',
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'static': dict({
|
||||||
|
'max_temp': dict({
|
||||||
|
'data_type': 0,
|
||||||
|
'desc': '',
|
||||||
|
'name': 'Summer/winter changeover temp heat circuit 1',
|
||||||
|
'unit': '°C',
|
||||||
|
'value': '20.0',
|
||||||
|
}),
|
||||||
|
'min_temp': dict({
|
||||||
|
'data_type': 0,
|
||||||
|
'desc': '',
|
||||||
|
'name': 'Room temp frost protection setpoint',
|
||||||
|
'unit': '°C',
|
||||||
|
'value': '8.0',
|
||||||
|
}),
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
|
@ -268,3 +268,49 @@ async def test_tts_service_speak_error(
|
|||||||
tts_entity._client.generate.assert_called_once_with(
|
tts_entity._client.generate.assert_called_once_with(
|
||||||
text="There is a person at the front door.", voice="voice1", model="model1"
|
text="There is a person at the front door.", voice="voice1", model="model1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("setup", "tts_service", "service_data"),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
"mock_config_entry_setup",
|
||||||
|
"speak",
|
||||||
|
{
|
||||||
|
ATTR_ENTITY_ID: "tts.mock_title",
|
||||||
|
tts.ATTR_MEDIA_PLAYER_ENTITY_ID: "media_player.something",
|
||||||
|
tts.ATTR_MESSAGE: "There is a person at the front door.",
|
||||||
|
tts.ATTR_OPTIONS: {},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
indirect=["setup"],
|
||||||
|
)
|
||||||
|
async def test_tts_service_speak_without_options(
|
||||||
|
setup: AsyncMock,
|
||||||
|
hass: HomeAssistant,
|
||||||
|
hass_client: ClientSessionGenerator,
|
||||||
|
calls: list[ServiceCall],
|
||||||
|
tts_service: str,
|
||||||
|
service_data: dict[str, Any],
|
||||||
|
) -> None:
|
||||||
|
"""Test service call say with http response 200."""
|
||||||
|
tts_entity = hass.data[tts.DOMAIN].get_entity(service_data[ATTR_ENTITY_ID])
|
||||||
|
tts_entity._client.generate.reset_mock()
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
tts.DOMAIN,
|
||||||
|
tts_service,
|
||||||
|
service_data,
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert len(calls) == 1
|
||||||
|
assert (
|
||||||
|
await retrieve_media(hass, hass_client, calls[0].data[ATTR_MEDIA_CONTENT_ID])
|
||||||
|
== HTTPStatus.OK
|
||||||
|
)
|
||||||
|
|
||||||
|
tts_entity._client.generate.assert_called_once_with(
|
||||||
|
text="There is a person at the front door.", voice="voice1", model="model1"
|
||||||
|
)
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# serializer version: 1
|
# serializer version: 1
|
||||||
# name: test_setup_platform[climate.thermostat_1-entry]
|
# name: test_setup_platform[legacy_thermostat][climate.thermostat_1-entry]
|
||||||
EntityRegistryEntrySnapshot({
|
EntityRegistryEntrySnapshot({
|
||||||
'aliases': set({
|
'aliases': set({
|
||||||
}),
|
}),
|
||||||
@ -38,7 +38,73 @@
|
|||||||
'unit_of_measurement': None,
|
'unit_of_measurement': None,
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_setup_platform[climate.thermostat_1-state]
|
# name: test_setup_platform[legacy_thermostat][climate.thermostat_1-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'current_temperature': 21.4,
|
||||||
|
'friendly_name': 'Thermostat 1',
|
||||||
|
'hvac_action': <HVACAction.IDLE: 'idle'>,
|
||||||
|
'hvac_modes': list([
|
||||||
|
<HVACMode.HEAT: 'heat'>,
|
||||||
|
]),
|
||||||
|
'max_temp': 30.0,
|
||||||
|
'min_temp': 5.0,
|
||||||
|
'status': dict({
|
||||||
|
'override': 0.0,
|
||||||
|
'room_temp': 21.42,
|
||||||
|
'setpoint': 18.0,
|
||||||
|
}),
|
||||||
|
'supported_features': <ClimateEntityFeature: 1>,
|
||||||
|
'temperature': 18.0,
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'climate.thermostat_1',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': 'heat',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_setup_platform[new_thermostat][climate.thermostat_1-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'hvac_modes': list([
|
||||||
|
<HVACMode.HEAT: 'heat'>,
|
||||||
|
]),
|
||||||
|
'max_temp': 30.0,
|
||||||
|
'min_temp': 5.0,
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'climate',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'climate.thermostat_1',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
}),
|
||||||
|
'original_device_class': None,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': None,
|
||||||
|
'platform': 'incomfort',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'supported_features': <ClimateEntityFeature: 1>,
|
||||||
|
'translation_key': None,
|
||||||
|
'unique_id': 'c0ffeec0ffee_1',
|
||||||
|
'unit_of_measurement': None,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_setup_platform[new_thermostat][climate.thermostat_1-state]
|
||||||
StateSnapshot({
|
StateSnapshot({
|
||||||
'attributes': ReadOnlyDict({
|
'attributes': ReadOnlyDict({
|
||||||
'current_temperature': 21.4,
|
'current_temperature': 21.4,
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
from syrupy import SnapshotAssertion
|
from syrupy import SnapshotAssertion
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
@ -13,6 +14,14 @@ from tests.common import snapshot_platform
|
|||||||
|
|
||||||
|
|
||||||
@patch("homeassistant.components.incomfort.PLATFORMS", [Platform.CLIMATE])
|
@patch("homeassistant.components.incomfort.PLATFORMS", [Platform.CLIMATE])
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"mock_room_status",
|
||||||
|
[
|
||||||
|
{"room_temp": 21.42, "setpoint": 18.0, "override": 18.0},
|
||||||
|
{"room_temp": 21.42, "setpoint": 18.0, "override": 0.0},
|
||||||
|
],
|
||||||
|
ids=["new_thermostat", "legacy_thermostat"],
|
||||||
|
)
|
||||||
async def test_setup_platform(
|
async def test_setup_platform(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
mock_incomfort: MagicMock,
|
mock_incomfort: MagicMock,
|
||||||
@ -20,6 +29,10 @@ async def test_setup_platform(
|
|||||||
snapshot: SnapshotAssertion,
|
snapshot: SnapshotAssertion,
|
||||||
mock_config_entry: ConfigEntry,
|
mock_config_entry: ConfigEntry,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test the incomfort entities are set up correctly."""
|
"""Test the incomfort entities are set up correctly.
|
||||||
|
|
||||||
|
Legacy thermostats report 0.0 as override if no override is set,
|
||||||
|
but new thermostat sync the override with the actual setpoint instead.
|
||||||
|
"""
|
||||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||||
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
|
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
|
||||||
|
@ -65,9 +65,12 @@ class MockLifxCommand:
|
|||||||
"""Init command."""
|
"""Init command."""
|
||||||
self.bulb = bulb
|
self.bulb = bulb
|
||||||
self.calls = []
|
self.calls = []
|
||||||
self.msg_kwargs = kwargs
|
self.msg_kwargs = {
|
||||||
|
k.removeprefix("msg_"): v for k, v in kwargs.items() if k.startswith("msg_")
|
||||||
|
}
|
||||||
for k, v in kwargs.items():
|
for k, v in kwargs.items():
|
||||||
if k != "callb":
|
if k.startswith("msg_") or k == "callb":
|
||||||
|
continue
|
||||||
setattr(self.bulb, k, v)
|
setattr(self.bulb, k, v)
|
||||||
|
|
||||||
def __call__(self, *args, **kwargs):
|
def __call__(self, *args, **kwargs):
|
||||||
@ -156,9 +159,16 @@ def _mocked_infrared_bulb() -> Light:
|
|||||||
def _mocked_light_strip() -> Light:
|
def _mocked_light_strip() -> Light:
|
||||||
bulb = _mocked_bulb()
|
bulb = _mocked_bulb()
|
||||||
bulb.product = 31 # LIFX Z
|
bulb.product = 31 # LIFX Z
|
||||||
bulb.color_zones = [MagicMock(), MagicMock()]
|
bulb.zones_count = 3
|
||||||
|
bulb.color_zones = [MagicMock()] * 3
|
||||||
bulb.effect = {"effect": "MOVE", "speed": 3, "duration": 0, "direction": "RIGHT"}
|
bulb.effect = {"effect": "MOVE", "speed": 3, "duration": 0, "direction": "RIGHT"}
|
||||||
bulb.get_color_zones = MockLifxCommand(bulb)
|
bulb.get_color_zones = MockLifxCommand(
|
||||||
|
bulb,
|
||||||
|
msg_seq_num=bulb.seq_next(),
|
||||||
|
msg_count=bulb.zones_count,
|
||||||
|
msg_index=0,
|
||||||
|
msg_color=bulb.color_zones,
|
||||||
|
)
|
||||||
bulb.set_color_zones = MockLifxCommand(bulb)
|
bulb.set_color_zones = MockLifxCommand(bulb)
|
||||||
bulb.get_multizone_effect = MockLifxCommand(bulb)
|
bulb.get_multizone_effect = MockLifxCommand(bulb)
|
||||||
bulb.set_multizone_effect = MockLifxCommand(bulb)
|
bulb.set_multizone_effect = MockLifxCommand(bulb)
|
||||||
|
@ -9,6 +9,7 @@ from . import (
|
|||||||
DEFAULT_ENTRY_TITLE,
|
DEFAULT_ENTRY_TITLE,
|
||||||
IP_ADDRESS,
|
IP_ADDRESS,
|
||||||
SERIAL,
|
SERIAL,
|
||||||
|
MockLifxCommand,
|
||||||
_mocked_bulb,
|
_mocked_bulb,
|
||||||
_mocked_clean_bulb,
|
_mocked_clean_bulb,
|
||||||
_mocked_infrared_bulb,
|
_mocked_infrared_bulb,
|
||||||
@ -188,6 +189,22 @@ async def test_legacy_multizone_bulb_diagnostics(
|
|||||||
)
|
)
|
||||||
config_entry.add_to_hass(hass)
|
config_entry.add_to_hass(hass)
|
||||||
bulb = _mocked_light_strip()
|
bulb = _mocked_light_strip()
|
||||||
|
bulb.get_color_zones = MockLifxCommand(
|
||||||
|
bulb,
|
||||||
|
msg_seq_num=0,
|
||||||
|
msg_count=8,
|
||||||
|
msg_color=[
|
||||||
|
(54612, 65535, 65535, 3500),
|
||||||
|
(54612, 65535, 65535, 3500),
|
||||||
|
(54612, 65535, 65535, 3500),
|
||||||
|
(54612, 65535, 65535, 3500),
|
||||||
|
(46420, 65535, 65535, 3500),
|
||||||
|
(46420, 65535, 65535, 3500),
|
||||||
|
(46420, 65535, 65535, 3500),
|
||||||
|
(46420, 65535, 65535, 3500),
|
||||||
|
],
|
||||||
|
msg_index=0,
|
||||||
|
)
|
||||||
bulb.zones_count = 8
|
bulb.zones_count = 8
|
||||||
bulb.color_zones = [
|
bulb.color_zones = [
|
||||||
(54612, 65535, 65535, 3500),
|
(54612, 65535, 65535, 3500),
|
||||||
@ -302,6 +319,22 @@ async def test_multizone_bulb_diagnostics(
|
|||||||
config_entry.add_to_hass(hass)
|
config_entry.add_to_hass(hass)
|
||||||
bulb = _mocked_light_strip()
|
bulb = _mocked_light_strip()
|
||||||
bulb.product = 38
|
bulb.product = 38
|
||||||
|
bulb.get_color_zones = MockLifxCommand(
|
||||||
|
bulb,
|
||||||
|
msg_seq_num=0,
|
||||||
|
msg_count=8,
|
||||||
|
msg_color=[
|
||||||
|
(54612, 65535, 65535, 3500),
|
||||||
|
(54612, 65535, 65535, 3500),
|
||||||
|
(54612, 65535, 65535, 3500),
|
||||||
|
(54612, 65535, 65535, 3500),
|
||||||
|
(46420, 65535, 65535, 3500),
|
||||||
|
(46420, 65535, 65535, 3500),
|
||||||
|
(46420, 65535, 65535, 3500),
|
||||||
|
(46420, 65535, 65535, 3500),
|
||||||
|
],
|
||||||
|
msg_index=0,
|
||||||
|
)
|
||||||
bulb.zones_count = 8
|
bulb.zones_count = 8
|
||||||
bulb.color_zones = [
|
bulb.color_zones = [
|
||||||
(54612, 65535, 65535, 3500),
|
(54612, 65535, 65535, 3500),
|
||||||
|
@ -192,15 +192,7 @@ async def test_light_strip(hass: HomeAssistant) -> None:
|
|||||||
{ATTR_ENTITY_ID: entity_id, ATTR_BRIGHTNESS: 100},
|
{ATTR_ENTITY_ID: entity_id, ATTR_BRIGHTNESS: 100},
|
||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
call_dict = bulb.set_color_zones.calls[0][1]
|
assert len(bulb.set_color_zones.calls) == 0
|
||||||
call_dict.pop("callb")
|
|
||||||
assert call_dict == {
|
|
||||||
"apply": 0,
|
|
||||||
"color": [],
|
|
||||||
"duration": 0,
|
|
||||||
"end_index": 0,
|
|
||||||
"start_index": 0,
|
|
||||||
}
|
|
||||||
bulb.set_color_zones.reset_mock()
|
bulb.set_color_zones.reset_mock()
|
||||||
|
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
@ -209,15 +201,7 @@ async def test_light_strip(hass: HomeAssistant) -> None:
|
|||||||
{ATTR_ENTITY_ID: entity_id, ATTR_HS_COLOR: (10, 30)},
|
{ATTR_ENTITY_ID: entity_id, ATTR_HS_COLOR: (10, 30)},
|
||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
call_dict = bulb.set_color_zones.calls[0][1]
|
assert len(bulb.set_color_zones.calls) == 0
|
||||||
call_dict.pop("callb")
|
|
||||||
assert call_dict == {
|
|
||||||
"apply": 0,
|
|
||||||
"color": [],
|
|
||||||
"duration": 0,
|
|
||||||
"end_index": 0,
|
|
||||||
"start_index": 0,
|
|
||||||
}
|
|
||||||
bulb.set_color_zones.reset_mock()
|
bulb.set_color_zones.reset_mock()
|
||||||
|
|
||||||
bulb.color_zones = [
|
bulb.color_zones = [
|
||||||
@ -238,7 +222,7 @@ async def test_light_strip(hass: HomeAssistant) -> None:
|
|||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
# Single color uses the fast path
|
# Single color uses the fast path
|
||||||
assert bulb.set_color.calls[0][0][0] == [1820, 19660, 65535, 3500]
|
assert bulb.set_color.calls[1][0][0] == [1820, 19660, 65535, 3500]
|
||||||
bulb.set_color.reset_mock()
|
bulb.set_color.reset_mock()
|
||||||
assert len(bulb.set_color_zones.calls) == 0
|
assert len(bulb.set_color_zones.calls) == 0
|
||||||
|
|
||||||
@ -422,7 +406,9 @@ async def test_light_strip(hass: HomeAssistant) -> None:
|
|||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
bulb.get_color_zones = MockLifxCommand(bulb)
|
bulb.get_color_zones = MockLifxCommand(
|
||||||
|
bulb, msg_seq_num=0, msg_color=[0, 0, 65535, 3500] * 3, msg_index=0, msg_count=3
|
||||||
|
)
|
||||||
bulb.get_color = MockFailingLifxCommand(bulb)
|
bulb.get_color = MockFailingLifxCommand(bulb)
|
||||||
|
|
||||||
with pytest.raises(HomeAssistantError):
|
with pytest.raises(HomeAssistantError):
|
||||||
@ -587,14 +573,14 @@ async def test_extended_multizone_messages(hass: HomeAssistant) -> None:
|
|||||||
bulb.set_extended_color_zones.reset_mock()
|
bulb.set_extended_color_zones.reset_mock()
|
||||||
|
|
||||||
bulb.color_zones = [
|
bulb.color_zones = [
|
||||||
(0, 65535, 65535, 3500),
|
[0, 65535, 65535, 3500],
|
||||||
(54612, 65535, 65535, 3500),
|
[54612, 65535, 65535, 3500],
|
||||||
(54612, 65535, 65535, 3500),
|
[54612, 65535, 65535, 3500],
|
||||||
(54612, 65535, 65535, 3500),
|
[54612, 65535, 65535, 3500],
|
||||||
(46420, 65535, 65535, 3500),
|
[46420, 65535, 65535, 3500],
|
||||||
(46420, 65535, 65535, 3500),
|
[46420, 65535, 65535, 3500],
|
||||||
(46420, 65535, 65535, 3500),
|
[46420, 65535, 65535, 3500],
|
||||||
(46420, 65535, 65535, 3500),
|
[46420, 65535, 65535, 3500],
|
||||||
]
|
]
|
||||||
|
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
@ -1308,7 +1294,11 @@ async def test_config_zoned_light_strip_fails(
|
|||||||
def __call__(self, callb=None, *args, **kwargs):
|
def __call__(self, callb=None, *args, **kwargs):
|
||||||
"""Call command."""
|
"""Call command."""
|
||||||
self.call_count += 1
|
self.call_count += 1
|
||||||
response = None if self.call_count >= 2 else MockMessage()
|
response = (
|
||||||
|
None
|
||||||
|
if self.call_count >= 2
|
||||||
|
else MockMessage(seq_num=0, color=[], index=0, count=0)
|
||||||
|
)
|
||||||
if callb:
|
if callb:
|
||||||
callb(self.bulb, response)
|
callb(self.bulb, response)
|
||||||
|
|
||||||
@ -1349,7 +1339,15 @@ async def test_legacy_zoned_light_strip(
|
|||||||
self.call_count += 1
|
self.call_count += 1
|
||||||
self.bulb.color_zones = [None] * 12
|
self.bulb.color_zones = [None] * 12
|
||||||
if callb:
|
if callb:
|
||||||
callb(self.bulb, MockMessage())
|
callb(
|
||||||
|
self.bulb,
|
||||||
|
MockMessage(
|
||||||
|
seq_num=0,
|
||||||
|
index=0,
|
||||||
|
count=self.bulb.zones_count,
|
||||||
|
color=self.bulb.color_zones,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
get_color_zones_mock = MockPopulateLifxZonesCommand(light_strip)
|
get_color_zones_mock = MockPopulateLifxZonesCommand(light_strip)
|
||||||
light_strip.get_color_zones = get_color_zones_mock
|
light_strip.get_color_zones = get_color_zones_mock
|
||||||
@ -1946,6 +1944,33 @@ async def test_light_strip_zones_not_populated_yet(hass: HomeAssistant) -> None:
|
|||||||
bulb.power_level = 65535
|
bulb.power_level = 65535
|
||||||
bulb.color_zones = None
|
bulb.color_zones = None
|
||||||
bulb.color = [65535, 65535, 65535, 65535]
|
bulb.color = [65535, 65535, 65535, 65535]
|
||||||
|
bulb.get_color_zones = next(
|
||||||
|
iter(
|
||||||
|
[
|
||||||
|
MockLifxCommand(
|
||||||
|
bulb,
|
||||||
|
msg_seq_num=0,
|
||||||
|
msg_color=[0, 0, 65535, 3500] * 8,
|
||||||
|
msg_index=0,
|
||||||
|
msg_count=16,
|
||||||
|
),
|
||||||
|
MockLifxCommand(
|
||||||
|
bulb,
|
||||||
|
msg_seq_num=1,
|
||||||
|
msg_color=[0, 0, 65535, 3500] * 8,
|
||||||
|
msg_index=0,
|
||||||
|
msg_count=16,
|
||||||
|
),
|
||||||
|
MockLifxCommand(
|
||||||
|
bulb,
|
||||||
|
msg_seq_num=2,
|
||||||
|
msg_color=[0, 0, 65535, 3500] * 8,
|
||||||
|
msg_index=8,
|
||||||
|
msg_count=16,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
assert bulb.get_color_zones.calls == []
|
assert bulb.get_color_zones.calls == []
|
||||||
|
|
||||||
with (
|
with (
|
||||||
|
@ -246,7 +246,13 @@ MOCK_VEHICLES = {
|
|||||||
ATTR_DEVICE_CLASS: SensorDeviceClass.ENUM,
|
ATTR_DEVICE_CLASS: SensorDeviceClass.ENUM,
|
||||||
ATTR_ENTITY_ID: "sensor.reg_number_plug_state",
|
ATTR_ENTITY_ID: "sensor.reg_number_plug_state",
|
||||||
ATTR_ICON: "mdi:power-plug",
|
ATTR_ICON: "mdi:power-plug",
|
||||||
ATTR_OPTIONS: ["unplugged", "plugged", "plug_error", "plug_unknown"],
|
ATTR_OPTIONS: [
|
||||||
|
"unplugged",
|
||||||
|
"plugged",
|
||||||
|
"plugged_waiting_for_charge",
|
||||||
|
"plug_error",
|
||||||
|
"plug_unknown",
|
||||||
|
],
|
||||||
ATTR_STATE: "plugged",
|
ATTR_STATE: "plugged",
|
||||||
ATTR_UNIQUE_ID: "vf1aaaaa555777999_plug_state",
|
ATTR_UNIQUE_ID: "vf1aaaaa555777999_plug_state",
|
||||||
},
|
},
|
||||||
@ -487,7 +493,13 @@ MOCK_VEHICLES = {
|
|||||||
ATTR_DEVICE_CLASS: SensorDeviceClass.ENUM,
|
ATTR_DEVICE_CLASS: SensorDeviceClass.ENUM,
|
||||||
ATTR_ENTITY_ID: "sensor.reg_number_plug_state",
|
ATTR_ENTITY_ID: "sensor.reg_number_plug_state",
|
||||||
ATTR_ICON: "mdi:power-plug-off",
|
ATTR_ICON: "mdi:power-plug-off",
|
||||||
ATTR_OPTIONS: ["unplugged", "plugged", "plug_error", "plug_unknown"],
|
ATTR_OPTIONS: [
|
||||||
|
"unplugged",
|
||||||
|
"plugged",
|
||||||
|
"plugged_waiting_for_charge",
|
||||||
|
"plug_error",
|
||||||
|
"plug_unknown",
|
||||||
|
],
|
||||||
ATTR_STATE: "unplugged",
|
ATTR_STATE: "unplugged",
|
||||||
ATTR_UNIQUE_ID: "vf1aaaaa555777999_plug_state",
|
ATTR_UNIQUE_ID: "vf1aaaaa555777999_plug_state",
|
||||||
},
|
},
|
||||||
@ -725,7 +737,13 @@ MOCK_VEHICLES = {
|
|||||||
ATTR_DEVICE_CLASS: SensorDeviceClass.ENUM,
|
ATTR_DEVICE_CLASS: SensorDeviceClass.ENUM,
|
||||||
ATTR_ENTITY_ID: "sensor.reg_number_plug_state",
|
ATTR_ENTITY_ID: "sensor.reg_number_plug_state",
|
||||||
ATTR_ICON: "mdi:power-plug",
|
ATTR_ICON: "mdi:power-plug",
|
||||||
ATTR_OPTIONS: ["unplugged", "plugged", "plug_error", "plug_unknown"],
|
ATTR_OPTIONS: [
|
||||||
|
"unplugged",
|
||||||
|
"plugged",
|
||||||
|
"plugged_waiting_for_charge",
|
||||||
|
"plug_error",
|
||||||
|
"plug_unknown",
|
||||||
|
],
|
||||||
ATTR_STATE: "plugged",
|
ATTR_STATE: "plugged",
|
||||||
ATTR_UNIQUE_ID: "vf1aaaaa555777123_plug_state",
|
ATTR_UNIQUE_ID: "vf1aaaaa555777123_plug_state",
|
||||||
},
|
},
|
||||||
|
@ -494,6 +494,7 @@
|
|||||||
'options': list([
|
'options': list([
|
||||||
'unplugged',
|
'unplugged',
|
||||||
'plugged',
|
'plugged',
|
||||||
|
'plugged_waiting_for_charge',
|
||||||
'plug_error',
|
'plug_error',
|
||||||
'plug_unknown',
|
'plug_unknown',
|
||||||
]),
|
]),
|
||||||
@ -921,6 +922,7 @@
|
|||||||
'options': list([
|
'options': list([
|
||||||
'unplugged',
|
'unplugged',
|
||||||
'plugged',
|
'plugged',
|
||||||
|
'plugged_waiting_for_charge',
|
||||||
'plug_error',
|
'plug_error',
|
||||||
'plug_unknown',
|
'plug_unknown',
|
||||||
]),
|
]),
|
||||||
@ -1249,6 +1251,7 @@
|
|||||||
'options': list([
|
'options': list([
|
||||||
'unplugged',
|
'unplugged',
|
||||||
'plugged',
|
'plugged',
|
||||||
|
'plugged_waiting_for_charge',
|
||||||
'plug_error',
|
'plug_error',
|
||||||
'plug_unknown',
|
'plug_unknown',
|
||||||
]),
|
]),
|
||||||
@ -1674,6 +1677,7 @@
|
|||||||
'options': list([
|
'options': list([
|
||||||
'unplugged',
|
'unplugged',
|
||||||
'plugged',
|
'plugged',
|
||||||
|
'plugged_waiting_for_charge',
|
||||||
'plug_error',
|
'plug_error',
|
||||||
'plug_unknown',
|
'plug_unknown',
|
||||||
]),
|
]),
|
||||||
@ -2000,6 +2004,7 @@
|
|||||||
'options': list([
|
'options': list([
|
||||||
'unplugged',
|
'unplugged',
|
||||||
'plugged',
|
'plugged',
|
||||||
|
'plugged_waiting_for_charge',
|
||||||
'plug_error',
|
'plug_error',
|
||||||
'plug_unknown',
|
'plug_unknown',
|
||||||
]),
|
]),
|
||||||
@ -2456,6 +2461,7 @@
|
|||||||
'options': list([
|
'options': list([
|
||||||
'unplugged',
|
'unplugged',
|
||||||
'plugged',
|
'plugged',
|
||||||
|
'plugged_waiting_for_charge',
|
||||||
'plug_error',
|
'plug_error',
|
||||||
'plug_unknown',
|
'plug_unknown',
|
||||||
]),
|
]),
|
||||||
@ -3104,6 +3110,7 @@
|
|||||||
'options': list([
|
'options': list([
|
||||||
'unplugged',
|
'unplugged',
|
||||||
'plugged',
|
'plugged',
|
||||||
|
'plugged_waiting_for_charge',
|
||||||
'plug_error',
|
'plug_error',
|
||||||
'plug_unknown',
|
'plug_unknown',
|
||||||
]),
|
]),
|
||||||
@ -3531,6 +3538,7 @@
|
|||||||
'options': list([
|
'options': list([
|
||||||
'unplugged',
|
'unplugged',
|
||||||
'plugged',
|
'plugged',
|
||||||
|
'plugged_waiting_for_charge',
|
||||||
'plug_error',
|
'plug_error',
|
||||||
'plug_unknown',
|
'plug_unknown',
|
||||||
]),
|
]),
|
||||||
@ -3859,6 +3867,7 @@
|
|||||||
'options': list([
|
'options': list([
|
||||||
'unplugged',
|
'unplugged',
|
||||||
'plugged',
|
'plugged',
|
||||||
|
'plugged_waiting_for_charge',
|
||||||
'plug_error',
|
'plug_error',
|
||||||
'plug_unknown',
|
'plug_unknown',
|
||||||
]),
|
]),
|
||||||
@ -4284,6 +4293,7 @@
|
|||||||
'options': list([
|
'options': list([
|
||||||
'unplugged',
|
'unplugged',
|
||||||
'plugged',
|
'plugged',
|
||||||
|
'plugged_waiting_for_charge',
|
||||||
'plug_error',
|
'plug_error',
|
||||||
'plug_unknown',
|
'plug_unknown',
|
||||||
]),
|
]),
|
||||||
@ -4610,6 +4620,7 @@
|
|||||||
'options': list([
|
'options': list([
|
||||||
'unplugged',
|
'unplugged',
|
||||||
'plugged',
|
'plugged',
|
||||||
|
'plugged_waiting_for_charge',
|
||||||
'plug_error',
|
'plug_error',
|
||||||
'plug_unknown',
|
'plug_unknown',
|
||||||
]),
|
]),
|
||||||
@ -5066,6 +5077,7 @@
|
|||||||
'options': list([
|
'options': list([
|
||||||
'unplugged',
|
'unplugged',
|
||||||
'plugged',
|
'plugged',
|
||||||
|
'plugged_waiting_for_charge',
|
||||||
'plug_error',
|
'plug_error',
|
||||||
'plug_unknown',
|
'plug_unknown',
|
||||||
]),
|
]),
|
||||||
|
@ -3,37 +3,56 @@
|
|||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from unittest.mock import Mock
|
from unittest.mock import Mock
|
||||||
|
|
||||||
|
from freezegun.api import FrozenDateTimeFactory
|
||||||
from pyschlage.exceptions import UnknownError
|
from pyschlage.exceptions import UnknownError
|
||||||
|
|
||||||
from homeassistant.components.binary_sensor import BinarySensorDeviceClass
|
from homeassistant.components.binary_sensor import BinarySensorDeviceClass
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.const import STATE_ON, STATE_UNAVAILABLE
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.util.dt import utcnow
|
|
||||||
|
|
||||||
from tests.common import async_fire_time_changed
|
from tests.common import async_fire_time_changed
|
||||||
|
|
||||||
|
|
||||||
async def test_keypad_disabled_binary_sensor(
|
async def test_keypad_disabled_binary_sensor(
|
||||||
hass: HomeAssistant, mock_lock: Mock, mock_added_config_entry: ConfigEntry
|
hass: HomeAssistant,
|
||||||
|
mock_schlage: Mock,
|
||||||
|
mock_lock: Mock,
|
||||||
|
mock_added_config_entry: ConfigEntry,
|
||||||
|
freezer: FrozenDateTimeFactory,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test the keypad_disabled binary_sensor."""
|
"""Test the keypad_disabled binary_sensor."""
|
||||||
mock_lock.keypad_disabled.reset_mock()
|
mock_lock.keypad_disabled.reset_mock()
|
||||||
mock_lock.keypad_disabled.return_value = True
|
mock_lock.keypad_disabled.return_value = True
|
||||||
|
|
||||||
# Make the coordinator refresh data.
|
# Make the coordinator refresh data.
|
||||||
async_fire_time_changed(hass, utcnow() + timedelta(seconds=31))
|
freezer.tick(timedelta(seconds=30))
|
||||||
|
async_fire_time_changed(hass)
|
||||||
await hass.async_block_till_done(wait_background_tasks=True)
|
await hass.async_block_till_done(wait_background_tasks=True)
|
||||||
|
|
||||||
keypad = hass.states.get("binary_sensor.vault_door_keypad_disabled")
|
keypad = hass.states.get("binary_sensor.vault_door_keypad_disabled")
|
||||||
assert keypad is not None
|
assert keypad is not None
|
||||||
assert keypad.state == "on"
|
assert keypad.state == STATE_ON
|
||||||
assert keypad.attributes["device_class"] == BinarySensorDeviceClass.PROBLEM
|
assert keypad.attributes["device_class"] == BinarySensorDeviceClass.PROBLEM
|
||||||
|
|
||||||
mock_lock.keypad_disabled.assert_called_once_with([])
|
mock_lock.keypad_disabled.assert_called_once_with([])
|
||||||
|
|
||||||
|
mock_schlage.locks.return_value = []
|
||||||
|
# Make the coordinator refresh data.
|
||||||
|
freezer.tick(timedelta(seconds=30))
|
||||||
|
async_fire_time_changed(hass)
|
||||||
|
await hass.async_block_till_done(wait_background_tasks=True)
|
||||||
|
keypad = hass.states.get("binary_sensor.vault_door_keypad_disabled")
|
||||||
|
assert keypad is not None
|
||||||
|
assert keypad.state == STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
|
||||||
async def test_keypad_disabled_binary_sensor_use_previous_logs_on_failure(
|
async def test_keypad_disabled_binary_sensor_use_previous_logs_on_failure(
|
||||||
hass: HomeAssistant, mock_lock: Mock, mock_added_config_entry: ConfigEntry
|
hass: HomeAssistant,
|
||||||
|
mock_schlage: Mock,
|
||||||
|
mock_lock: Mock,
|
||||||
|
mock_added_config_entry: ConfigEntry,
|
||||||
|
freezer: FrozenDateTimeFactory,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test the keypad_disabled binary_sensor."""
|
"""Test the keypad_disabled binary_sensor."""
|
||||||
mock_lock.keypad_disabled.reset_mock()
|
mock_lock.keypad_disabled.reset_mock()
|
||||||
@ -42,12 +61,13 @@ async def test_keypad_disabled_binary_sensor_use_previous_logs_on_failure(
|
|||||||
mock_lock.logs.side_effect = UnknownError("Cannot load logs")
|
mock_lock.logs.side_effect = UnknownError("Cannot load logs")
|
||||||
|
|
||||||
# Make the coordinator refresh data.
|
# Make the coordinator refresh data.
|
||||||
async_fire_time_changed(hass, utcnow() + timedelta(seconds=31))
|
freezer.tick(timedelta(seconds=30))
|
||||||
|
async_fire_time_changed(hass)
|
||||||
await hass.async_block_till_done(wait_background_tasks=True)
|
await hass.async_block_till_done(wait_background_tasks=True)
|
||||||
|
|
||||||
keypad = hass.states.get("binary_sensor.vault_door_keypad_disabled")
|
keypad = hass.states.get("binary_sensor.vault_door_keypad_disabled")
|
||||||
assert keypad is not None
|
assert keypad is not None
|
||||||
assert keypad.state == "on"
|
assert keypad.state == STATE_ON
|
||||||
assert keypad.attributes["device_class"] == BinarySensorDeviceClass.PROBLEM
|
assert keypad.attributes["device_class"] == BinarySensorDeviceClass.PROBLEM
|
||||||
|
|
||||||
mock_lock.keypad_disabled.assert_called_once_with([])
|
mock_lock.keypad_disabled.assert_called_once_with([])
|
||||||
|
@ -3,12 +3,20 @@
|
|||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from unittest.mock import Mock
|
from unittest.mock import Mock
|
||||||
|
|
||||||
|
from freezegun.api import FrozenDateTimeFactory
|
||||||
|
|
||||||
from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN
|
from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import ATTR_ENTITY_ID, SERVICE_LOCK, SERVICE_UNLOCK
|
from homeassistant.const import (
|
||||||
|
ATTR_ENTITY_ID,
|
||||||
|
SERVICE_LOCK,
|
||||||
|
SERVICE_UNLOCK,
|
||||||
|
STATE_JAMMED,
|
||||||
|
STATE_UNAVAILABLE,
|
||||||
|
STATE_UNLOCKED,
|
||||||
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers import device_registry as dr
|
from homeassistant.helpers import device_registry as dr
|
||||||
from homeassistant.util.dt import utcnow
|
|
||||||
|
|
||||||
from tests.common import async_fire_time_changed
|
from tests.common import async_fire_time_changed
|
||||||
|
|
||||||
@ -26,6 +34,40 @@ async def test_lock_device_registry(
|
|||||||
assert device.manufacturer == "Schlage"
|
assert device.manufacturer == "Schlage"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_lock_attributes(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_added_config_entry: ConfigEntry,
|
||||||
|
mock_schlage: Mock,
|
||||||
|
mock_lock: Mock,
|
||||||
|
freezer: FrozenDateTimeFactory,
|
||||||
|
) -> None:
|
||||||
|
"""Test lock attributes."""
|
||||||
|
lock = hass.states.get("lock.vault_door")
|
||||||
|
assert lock is not None
|
||||||
|
assert lock.state == STATE_UNLOCKED
|
||||||
|
assert lock.attributes["changed_by"] == "thumbturn"
|
||||||
|
|
||||||
|
mock_lock.is_locked = False
|
||||||
|
mock_lock.is_jammed = True
|
||||||
|
# Make the coordinator refresh data.
|
||||||
|
freezer.tick(timedelta(seconds=30))
|
||||||
|
async_fire_time_changed(hass)
|
||||||
|
await hass.async_block_till_done(wait_background_tasks=True)
|
||||||
|
lock = hass.states.get("lock.vault_door")
|
||||||
|
assert lock is not None
|
||||||
|
assert lock.state == STATE_JAMMED
|
||||||
|
|
||||||
|
mock_schlage.locks.return_value = []
|
||||||
|
# Make the coordinator refresh data.
|
||||||
|
freezer.tick(timedelta(seconds=30))
|
||||||
|
async_fire_time_changed(hass)
|
||||||
|
await hass.async_block_till_done(wait_background_tasks=True)
|
||||||
|
lock = hass.states.get("lock.vault_door")
|
||||||
|
assert lock is not None
|
||||||
|
assert lock.state == STATE_UNAVAILABLE
|
||||||
|
assert "changed_by" not in lock.attributes
|
||||||
|
|
||||||
|
|
||||||
async def test_lock_services(
|
async def test_lock_services(
|
||||||
hass: HomeAssistant, mock_lock: Mock, mock_added_config_entry: ConfigEntry
|
hass: HomeAssistant, mock_lock: Mock, mock_added_config_entry: ConfigEntry
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -52,14 +94,18 @@ async def test_lock_services(
|
|||||||
|
|
||||||
|
|
||||||
async def test_changed_by(
|
async def test_changed_by(
|
||||||
hass: HomeAssistant, mock_lock: Mock, mock_added_config_entry: ConfigEntry
|
hass: HomeAssistant,
|
||||||
|
mock_lock: Mock,
|
||||||
|
mock_added_config_entry: ConfigEntry,
|
||||||
|
freezer: FrozenDateTimeFactory,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test population of the changed_by attribute."""
|
"""Test population of the changed_by attribute."""
|
||||||
mock_lock.last_changed_by.reset_mock()
|
mock_lock.last_changed_by.reset_mock()
|
||||||
mock_lock.last_changed_by.return_value = "access code - foo"
|
mock_lock.last_changed_by.return_value = "access code - foo"
|
||||||
|
|
||||||
# Make the coordinator refresh data.
|
# Make the coordinator refresh data.
|
||||||
async_fire_time_changed(hass, utcnow() + timedelta(seconds=31))
|
freezer.tick(timedelta(seconds=30))
|
||||||
|
async_fire_time_changed(hass)
|
||||||
await hass.async_block_till_done(wait_background_tasks=True)
|
await hass.async_block_till_done(wait_background_tasks=True)
|
||||||
mock_lock.last_changed_by.assert_called_once_with()
|
mock_lock.last_changed_by.assert_called_once_with()
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
'product_id': 'NB6VAC-FXC-r0',
|
'product_id': 'NB6VAC-FXC-r0',
|
||||||
'refclient': '',
|
'refclient': '',
|
||||||
'serial_number': '**REDACTED**',
|
'serial_number': '**REDACTED**',
|
||||||
'temperature': 27560.0,
|
'temperature': 27560,
|
||||||
'uptime': 2353575,
|
'uptime': 2353575,
|
||||||
'version_bootloader': 'NB6VAC-BOOTLOADER-R4.0.8',
|
'version_bootloader': 'NB6VAC-BOOTLOADER-R4.0.8',
|
||||||
'version_dsldriver': 'NB6VAC-XDSL-A2pv6F039p',
|
'version_dsldriver': 'NB6VAC-XDSL-A2pv6F039p',
|
||||||
@ -90,7 +90,7 @@
|
|||||||
'product_id': 'NB6VAC-FXC-r0',
|
'product_id': 'NB6VAC-FXC-r0',
|
||||||
'refclient': '',
|
'refclient': '',
|
||||||
'serial_number': '**REDACTED**',
|
'serial_number': '**REDACTED**',
|
||||||
'temperature': 27560.0,
|
'temperature': 27560,
|
||||||
'uptime': 2353575,
|
'uptime': 2353575,
|
||||||
'version_bootloader': 'NB6VAC-BOOTLOADER-R4.0.8',
|
'version_bootloader': 'NB6VAC-BOOTLOADER-R4.0.8',
|
||||||
'version_dsldriver': 'NB6VAC-XDSL-A2pv6F039p',
|
'version_dsldriver': 'NB6VAC-XDSL-A2pv6F039p',
|
||||||
|
@ -336,6 +336,22 @@ async def test_zeroconf_cannot_connect(
|
|||||||
assert result2["reason"] == "cannot_connect"
|
assert result2["reason"] == "cannot_connect"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_zeroconf_legacy_cannot_connect(
|
||||||
|
hass: HomeAssistant, mock_smlight_client: MagicMock
|
||||||
|
) -> None:
|
||||||
|
"""Test we abort flow on zeroconf discovery unsupported firmware."""
|
||||||
|
mock_smlight_client.get_info.side_effect = SmlightConnectionError
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={"source": SOURCE_ZEROCONF},
|
||||||
|
data=DISCOVERY_INFO_LEGACY,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] is FlowResultType.ABORT
|
||||||
|
assert result["reason"] == "cannot_connect"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("mock_smlight_client")
|
@pytest.mark.usefixtures("mock_smlight_client")
|
||||||
async def test_zeroconf_legacy_mac(
|
async def test_zeroconf_legacy_mac(
|
||||||
hass: HomeAssistant, mock_smlight_client: MagicMock, mock_setup_entry: AsyncMock
|
hass: HomeAssistant, mock_smlight_client: MagicMock, mock_setup_entry: AsyncMock
|
||||||
|
@ -1123,6 +1123,27 @@ async def test_play_media_announce(
|
|||||||
)
|
)
|
||||||
assert sonos_websocket.play_clip.call_count == 1
|
assert sonos_websocket.play_clip.call_count == 1
|
||||||
|
|
||||||
|
# Test speakers that do not support announce. This
|
||||||
|
# will result in playing the clip directly via play_uri
|
||||||
|
sonos_websocket.play_clip.reset_mock()
|
||||||
|
sonos_websocket.play_clip.side_effect = None
|
||||||
|
retval = {"success": 0, "type": "globalError"}
|
||||||
|
sonos_websocket.play_clip.return_value = [retval, {}]
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
MP_DOMAIN,
|
||||||
|
SERVICE_PLAY_MEDIA,
|
||||||
|
{
|
||||||
|
ATTR_ENTITY_ID: "media_player.zone_a",
|
||||||
|
ATTR_MEDIA_CONTENT_TYPE: "music",
|
||||||
|
ATTR_MEDIA_CONTENT_ID: content_id,
|
||||||
|
ATTR_MEDIA_ANNOUNCE: True,
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
assert sonos_websocket.play_clip.call_count == 1
|
||||||
|
soco.play_uri.assert_called_with(content_id, force_radio=False)
|
||||||
|
|
||||||
|
|
||||||
async def test_media_get_queue(
|
async def test_media_get_queue(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
|
@ -21,11 +21,13 @@ from kasa.protocol import BaseProtocol
|
|||||||
from syrupy import SnapshotAssertion
|
from syrupy import SnapshotAssertion
|
||||||
|
|
||||||
from homeassistant.components.tplink import (
|
from homeassistant.components.tplink import (
|
||||||
|
CONF_AES_KEYS,
|
||||||
CONF_ALIAS,
|
CONF_ALIAS,
|
||||||
|
CONF_CONNECTION_PARAMETERS,
|
||||||
CONF_CREDENTIALS_HASH,
|
CONF_CREDENTIALS_HASH,
|
||||||
CONF_DEVICE_CONFIG,
|
|
||||||
CONF_HOST,
|
CONF_HOST,
|
||||||
CONF_MODEL,
|
CONF_MODEL,
|
||||||
|
CONF_USES_HTTP,
|
||||||
Credentials,
|
Credentials,
|
||||||
)
|
)
|
||||||
from homeassistant.components.tplink.const import DOMAIN
|
from homeassistant.components.tplink.const import DOMAIN
|
||||||
@ -54,35 +56,42 @@ DHCP_FORMATTED_MAC_ADDRESS = MAC_ADDRESS.replace(":", "")
|
|||||||
MAC_ADDRESS2 = "11:22:33:44:55:66"
|
MAC_ADDRESS2 = "11:22:33:44:55:66"
|
||||||
DEFAULT_ENTRY_TITLE = f"{ALIAS} {MODEL}"
|
DEFAULT_ENTRY_TITLE = f"{ALIAS} {MODEL}"
|
||||||
CREDENTIALS_HASH_LEGACY = ""
|
CREDENTIALS_HASH_LEGACY = ""
|
||||||
|
CONN_PARAMS_LEGACY = DeviceConnectionParameters(
|
||||||
|
DeviceFamily.IotSmartPlugSwitch, DeviceEncryptionType.Xor
|
||||||
|
)
|
||||||
DEVICE_CONFIG_LEGACY = DeviceConfig(IP_ADDRESS)
|
DEVICE_CONFIG_LEGACY = DeviceConfig(IP_ADDRESS)
|
||||||
DEVICE_CONFIG_DICT_LEGACY = DEVICE_CONFIG_LEGACY.to_dict(exclude_credentials=True)
|
DEVICE_CONFIG_DICT_LEGACY = DEVICE_CONFIG_LEGACY.to_dict(exclude_credentials=True)
|
||||||
CREDENTIALS = Credentials("foo", "bar")
|
CREDENTIALS = Credentials("foo", "bar")
|
||||||
CREDENTIALS_HASH_AES = "AES/abcdefghijklmnopqrstuvabcdefghijklmnopqrstuv=="
|
CREDENTIALS_HASH_AES = "AES/abcdefghijklmnopqrstuvabcdefghijklmnopqrstuv=="
|
||||||
CREDENTIALS_HASH_KLAP = "KLAP/abcdefghijklmnopqrstuv=="
|
CREDENTIALS_HASH_KLAP = "KLAP/abcdefghijklmnopqrstuv=="
|
||||||
|
CONN_PARAMS_KLAP = DeviceConnectionParameters(
|
||||||
|
DeviceFamily.SmartTapoPlug, DeviceEncryptionType.Klap
|
||||||
|
)
|
||||||
DEVICE_CONFIG_KLAP = DeviceConfig(
|
DEVICE_CONFIG_KLAP = DeviceConfig(
|
||||||
IP_ADDRESS,
|
IP_ADDRESS,
|
||||||
credentials=CREDENTIALS,
|
credentials=CREDENTIALS,
|
||||||
connection_type=DeviceConnectionParameters(
|
connection_type=CONN_PARAMS_KLAP,
|
||||||
DeviceFamily.SmartTapoPlug, DeviceEncryptionType.Klap
|
|
||||||
),
|
|
||||||
uses_http=True,
|
uses_http=True,
|
||||||
)
|
)
|
||||||
|
CONN_PARAMS_AES = DeviceConnectionParameters(
|
||||||
|
DeviceFamily.SmartTapoPlug, DeviceEncryptionType.Aes
|
||||||
|
)
|
||||||
|
AES_KEYS = {"private": "foo", "public": "bar"}
|
||||||
DEVICE_CONFIG_AES = DeviceConfig(
|
DEVICE_CONFIG_AES = DeviceConfig(
|
||||||
IP_ADDRESS2,
|
IP_ADDRESS2,
|
||||||
credentials=CREDENTIALS,
|
credentials=CREDENTIALS,
|
||||||
connection_type=DeviceConnectionParameters(
|
connection_type=CONN_PARAMS_AES,
|
||||||
DeviceFamily.SmartTapoPlug, DeviceEncryptionType.Aes
|
|
||||||
),
|
|
||||||
uses_http=True,
|
uses_http=True,
|
||||||
|
aes_keys=AES_KEYS,
|
||||||
)
|
)
|
||||||
DEVICE_CONFIG_DICT_KLAP = DEVICE_CONFIG_KLAP.to_dict(exclude_credentials=True)
|
DEVICE_CONFIG_DICT_KLAP = DEVICE_CONFIG_KLAP.to_dict(exclude_credentials=True)
|
||||||
DEVICE_CONFIG_DICT_AES = DEVICE_CONFIG_AES.to_dict(exclude_credentials=True)
|
DEVICE_CONFIG_DICT_AES = DEVICE_CONFIG_AES.to_dict(exclude_credentials=True)
|
||||||
|
|
||||||
CREATE_ENTRY_DATA_LEGACY = {
|
CREATE_ENTRY_DATA_LEGACY = {
|
||||||
CONF_HOST: IP_ADDRESS,
|
CONF_HOST: IP_ADDRESS,
|
||||||
CONF_ALIAS: ALIAS,
|
CONF_ALIAS: ALIAS,
|
||||||
CONF_MODEL: MODEL,
|
CONF_MODEL: MODEL,
|
||||||
CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_LEGACY,
|
CONF_CONNECTION_PARAMETERS: CONN_PARAMS_LEGACY.to_dict(),
|
||||||
|
CONF_USES_HTTP: False,
|
||||||
}
|
}
|
||||||
|
|
||||||
CREATE_ENTRY_DATA_KLAP = {
|
CREATE_ENTRY_DATA_KLAP = {
|
||||||
@ -90,23 +99,18 @@ CREATE_ENTRY_DATA_KLAP = {
|
|||||||
CONF_ALIAS: ALIAS,
|
CONF_ALIAS: ALIAS,
|
||||||
CONF_MODEL: MODEL,
|
CONF_MODEL: MODEL,
|
||||||
CONF_CREDENTIALS_HASH: CREDENTIALS_HASH_KLAP,
|
CONF_CREDENTIALS_HASH: CREDENTIALS_HASH_KLAP,
|
||||||
CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_KLAP,
|
CONF_CONNECTION_PARAMETERS: CONN_PARAMS_KLAP.to_dict(),
|
||||||
|
CONF_USES_HTTP: True,
|
||||||
}
|
}
|
||||||
CREATE_ENTRY_DATA_AES = {
|
CREATE_ENTRY_DATA_AES = {
|
||||||
CONF_HOST: IP_ADDRESS2,
|
CONF_HOST: IP_ADDRESS2,
|
||||||
CONF_ALIAS: ALIAS,
|
CONF_ALIAS: ALIAS,
|
||||||
CONF_MODEL: MODEL,
|
CONF_MODEL: MODEL,
|
||||||
CONF_CREDENTIALS_HASH: CREDENTIALS_HASH_AES,
|
CONF_CREDENTIALS_HASH: CREDENTIALS_HASH_AES,
|
||||||
CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_AES,
|
CONF_CONNECTION_PARAMETERS: CONN_PARAMS_AES.to_dict(),
|
||||||
|
CONF_USES_HTTP: True,
|
||||||
|
CONF_AES_KEYS: AES_KEYS,
|
||||||
}
|
}
|
||||||
CONNECTION_TYPE_KLAP = DeviceConnectionParameters(
|
|
||||||
DeviceFamily.SmartTapoPlug, DeviceEncryptionType.Klap
|
|
||||||
)
|
|
||||||
CONNECTION_TYPE_KLAP_DICT = CONNECTION_TYPE_KLAP.to_dict()
|
|
||||||
CONNECTION_TYPE_AES = DeviceConnectionParameters(
|
|
||||||
DeviceFamily.SmartTapoPlug, DeviceEncryptionType.Aes
|
|
||||||
)
|
|
||||||
CONNECTION_TYPE_AES_DICT = CONNECTION_TYPE_AES.to_dict()
|
|
||||||
|
|
||||||
|
|
||||||
def _load_feature_fixtures():
|
def _load_feature_fixtures():
|
||||||
@ -452,11 +456,11 @@ MODULE_TO_MOCK_GEN = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def _patch_discovery(device=None, no_device=False):
|
def _patch_discovery(device=None, no_device=False, ip_address=IP_ADDRESS):
|
||||||
async def _discovery(*args, **kwargs):
|
async def _discovery(*args, **kwargs):
|
||||||
if no_device:
|
if no_device:
|
||||||
return {}
|
return {}
|
||||||
return {IP_ADDRESS: _mocked_device()}
|
return {ip_address: device if device else _mocked_device()}
|
||||||
|
|
||||||
return patch("homeassistant.components.tplink.Discover.discover", new=_discovery)
|
return patch("homeassistant.components.tplink.Discover.discover", new=_discovery)
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
"""tplink conftest."""
|
"""tplink conftest."""
|
||||||
|
|
||||||
from collections.abc import Generator
|
from collections.abc import Generator
|
||||||
import copy
|
|
||||||
from unittest.mock import DEFAULT, AsyncMock, patch
|
from unittest.mock import DEFAULT, AsyncMock, patch
|
||||||
|
|
||||||
|
from kasa import DeviceConfig
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components.tplink import DOMAIN
|
from homeassistant.components.tplink import DOMAIN
|
||||||
@ -34,13 +34,13 @@ def mock_discovery():
|
|||||||
discover_single=DEFAULT,
|
discover_single=DEFAULT,
|
||||||
) as mock_discovery:
|
) as mock_discovery:
|
||||||
device = _mocked_device(
|
device = _mocked_device(
|
||||||
device_config=copy.deepcopy(DEVICE_CONFIG_KLAP),
|
device_config=DeviceConfig.from_dict(DEVICE_CONFIG_KLAP.to_dict()),
|
||||||
credentials_hash=CREDENTIALS_HASH_KLAP,
|
credentials_hash=CREDENTIALS_HASH_KLAP,
|
||||||
alias=None,
|
alias=None,
|
||||||
)
|
)
|
||||||
devices = {
|
devices = {
|
||||||
"127.0.0.1": _mocked_device(
|
"127.0.0.1": _mocked_device(
|
||||||
device_config=copy.deepcopy(DEVICE_CONFIG_KLAP),
|
device_config=DeviceConfig.from_dict(DEVICE_CONFIG_KLAP.to_dict()),
|
||||||
credentials_hash=CREDENTIALS_HASH_KLAP,
|
credentials_hash=CREDENTIALS_HASH_KLAP,
|
||||||
alias=None,
|
alias=None,
|
||||||
)
|
)
|
||||||
@ -57,12 +57,12 @@ def mock_connect():
|
|||||||
with patch("homeassistant.components.tplink.Device.connect") as mock_connect:
|
with patch("homeassistant.components.tplink.Device.connect") as mock_connect:
|
||||||
devices = {
|
devices = {
|
||||||
IP_ADDRESS: _mocked_device(
|
IP_ADDRESS: _mocked_device(
|
||||||
device_config=DEVICE_CONFIG_KLAP,
|
device_config=DeviceConfig.from_dict(DEVICE_CONFIG_KLAP.to_dict()),
|
||||||
credentials_hash=CREDENTIALS_HASH_KLAP,
|
credentials_hash=CREDENTIALS_HASH_KLAP,
|
||||||
ip_address=IP_ADDRESS,
|
ip_address=IP_ADDRESS,
|
||||||
),
|
),
|
||||||
IP_ADDRESS2: _mocked_device(
|
IP_ADDRESS2: _mocked_device(
|
||||||
device_config=DEVICE_CONFIG_AES,
|
device_config=DeviceConfig.from_dict(DEVICE_CONFIG_AES.to_dict()),
|
||||||
credentials_hash=CREDENTIALS_HASH_AES,
|
credentials_hash=CREDENTIALS_HASH_AES,
|
||||||
mac=MAC_ADDRESS2,
|
mac=MAC_ADDRESS2,
|
||||||
ip_address=IP_ADDRESS2,
|
ip_address=IP_ADDRESS2,
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
"""Test the tplink config flow."""
|
"""Test the tplink config flow."""
|
||||||
|
|
||||||
|
from contextlib import contextmanager
|
||||||
import logging
|
import logging
|
||||||
from unittest.mock import AsyncMock, patch
|
from unittest.mock import AsyncMock, patch
|
||||||
|
|
||||||
@ -17,7 +18,7 @@ from homeassistant.components.tplink import (
|
|||||||
KasaException,
|
KasaException,
|
||||||
)
|
)
|
||||||
from homeassistant.components.tplink.const import (
|
from homeassistant.components.tplink.const import (
|
||||||
CONF_CONNECTION_TYPE,
|
CONF_CONNECTION_PARAMETERS,
|
||||||
CONF_CREDENTIALS_HASH,
|
CONF_CREDENTIALS_HASH,
|
||||||
CONF_DEVICE_CONFIG,
|
CONF_DEVICE_CONFIG,
|
||||||
)
|
)
|
||||||
@ -34,17 +35,21 @@ from homeassistant.core import HomeAssistant
|
|||||||
from homeassistant.data_entry_flow import FlowResultType
|
from homeassistant.data_entry_flow import FlowResultType
|
||||||
|
|
||||||
from . import (
|
from . import (
|
||||||
|
AES_KEYS,
|
||||||
ALIAS,
|
ALIAS,
|
||||||
CONNECTION_TYPE_KLAP_DICT,
|
CONN_PARAMS_AES,
|
||||||
|
CONN_PARAMS_KLAP,
|
||||||
|
CONN_PARAMS_LEGACY,
|
||||||
CREATE_ENTRY_DATA_AES,
|
CREATE_ENTRY_DATA_AES,
|
||||||
CREATE_ENTRY_DATA_KLAP,
|
CREATE_ENTRY_DATA_KLAP,
|
||||||
CREATE_ENTRY_DATA_LEGACY,
|
CREATE_ENTRY_DATA_LEGACY,
|
||||||
CREDENTIALS_HASH_AES,
|
CREDENTIALS_HASH_AES,
|
||||||
CREDENTIALS_HASH_KLAP,
|
CREDENTIALS_HASH_KLAP,
|
||||||
DEFAULT_ENTRY_TITLE,
|
DEFAULT_ENTRY_TITLE,
|
||||||
DEVICE_CONFIG_DICT_AES,
|
DEVICE_CONFIG_AES,
|
||||||
DEVICE_CONFIG_DICT_KLAP,
|
DEVICE_CONFIG_DICT_KLAP,
|
||||||
DEVICE_CONFIG_DICT_LEGACY,
|
DEVICE_CONFIG_KLAP,
|
||||||
|
DEVICE_CONFIG_LEGACY,
|
||||||
DHCP_FORMATTED_MAC_ADDRESS,
|
DHCP_FORMATTED_MAC_ADDRESS,
|
||||||
IP_ADDRESS,
|
IP_ADDRESS,
|
||||||
MAC_ADDRESS,
|
MAC_ADDRESS,
|
||||||
@ -59,9 +64,44 @@ from . import (
|
|||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
|
||||||
async def test_discovery(hass: HomeAssistant) -> None:
|
@contextmanager
|
||||||
|
def override_side_effect(mock: AsyncMock, effect):
|
||||||
|
"""Temporarily override a mock side effect and replace afterwards."""
|
||||||
|
try:
|
||||||
|
default_side_effect = mock.side_effect
|
||||||
|
mock.side_effect = effect
|
||||||
|
yield mock
|
||||||
|
finally:
|
||||||
|
mock.side_effect = default_side_effect
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("device_config", "expected_entry_data", "credentials_hash"),
|
||||||
|
[
|
||||||
|
pytest.param(
|
||||||
|
DEVICE_CONFIG_KLAP, CREATE_ENTRY_DATA_KLAP, CREDENTIALS_HASH_KLAP, id="KLAP"
|
||||||
|
),
|
||||||
|
pytest.param(
|
||||||
|
DEVICE_CONFIG_AES, CREATE_ENTRY_DATA_AES, CREDENTIALS_HASH_AES, id="AES"
|
||||||
|
),
|
||||||
|
pytest.param(DEVICE_CONFIG_LEGACY, CREATE_ENTRY_DATA_LEGACY, None, id="Legacy"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_discovery(
|
||||||
|
hass: HomeAssistant, device_config, expected_entry_data, credentials_hash
|
||||||
|
) -> None:
|
||||||
"""Test setting up discovery."""
|
"""Test setting up discovery."""
|
||||||
with _patch_discovery(), _patch_single_discovery(), _patch_connect():
|
ip_address = device_config.host
|
||||||
|
device = _mocked_device(
|
||||||
|
device_config=device_config,
|
||||||
|
credentials_hash=credentials_hash,
|
||||||
|
ip_address=ip_address,
|
||||||
|
)
|
||||||
|
with (
|
||||||
|
_patch_discovery(device, ip_address=ip_address),
|
||||||
|
_patch_single_discovery(device),
|
||||||
|
_patch_connect(device),
|
||||||
|
):
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
)
|
)
|
||||||
@ -91,9 +131,9 @@ async def test_discovery(hass: HomeAssistant) -> None:
|
|||||||
assert not result2["errors"]
|
assert not result2["errors"]
|
||||||
|
|
||||||
with (
|
with (
|
||||||
_patch_discovery(),
|
_patch_discovery(device, ip_address=ip_address),
|
||||||
_patch_single_discovery(),
|
_patch_single_discovery(device),
|
||||||
_patch_connect(),
|
_patch_connect(device),
|
||||||
patch(f"{MODULE}.async_setup", return_value=True) as mock_setup,
|
patch(f"{MODULE}.async_setup", return_value=True) as mock_setup,
|
||||||
patch(f"{MODULE}.async_setup_entry", return_value=True) as mock_setup_entry,
|
patch(f"{MODULE}.async_setup_entry", return_value=True) as mock_setup_entry,
|
||||||
):
|
):
|
||||||
@ -105,7 +145,7 @@ async def test_discovery(hass: HomeAssistant) -> None:
|
|||||||
|
|
||||||
assert result3["type"] is FlowResultType.CREATE_ENTRY
|
assert result3["type"] is FlowResultType.CREATE_ENTRY
|
||||||
assert result3["title"] == DEFAULT_ENTRY_TITLE
|
assert result3["title"] == DEFAULT_ENTRY_TITLE
|
||||||
assert result3["data"] == CREATE_ENTRY_DATA_LEGACY
|
assert result3["data"] == expected_entry_data
|
||||||
mock_setup.assert_called_once()
|
mock_setup.assert_called_once()
|
||||||
mock_setup_entry.assert_called_once()
|
mock_setup_entry.assert_called_once()
|
||||||
|
|
||||||
@ -130,8 +170,10 @@ async def test_discovery_auth(
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Test authenticated discovery."""
|
"""Test authenticated discovery."""
|
||||||
|
|
||||||
mock_discovery["mock_device"].update.side_effect = AuthenticationError
|
mock_device = mock_connect["mock_devices"][IP_ADDRESS]
|
||||||
|
assert mock_device.config == DEVICE_CONFIG_KLAP
|
||||||
|
|
||||||
|
with override_side_effect(mock_connect["connect"], AuthenticationError):
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY},
|
context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY},
|
||||||
@ -139,7 +181,7 @@ async def test_discovery_auth(
|
|||||||
CONF_HOST: IP_ADDRESS,
|
CONF_HOST: IP_ADDRESS,
|
||||||
CONF_MAC: MAC_ADDRESS,
|
CONF_MAC: MAC_ADDRESS,
|
||||||
CONF_ALIAS: ALIAS,
|
CONF_ALIAS: ALIAS,
|
||||||
CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_KLAP,
|
CONF_DEVICE: mock_device,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
@ -147,7 +189,6 @@ async def test_discovery_auth(
|
|||||||
assert result["step_id"] == "discovery_auth_confirm"
|
assert result["step_id"] == "discovery_auth_confirm"
|
||||||
assert not result["errors"]
|
assert not result["errors"]
|
||||||
|
|
||||||
mock_discovery["mock_device"].update.reset_mock(side_effect=True)
|
|
||||||
result2 = await hass.config_entries.flow.async_configure(
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
result["flow_id"],
|
result["flow_id"],
|
||||||
user_input={
|
user_input={
|
||||||
@ -172,18 +213,20 @@ async def test_discovery_auth(
|
|||||||
)
|
)
|
||||||
async def test_discovery_auth_errors(
|
async def test_discovery_auth_errors(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
mock_discovery: AsyncMock,
|
|
||||||
mock_connect: AsyncMock,
|
mock_connect: AsyncMock,
|
||||||
mock_init,
|
mock_init,
|
||||||
error_type,
|
error_type,
|
||||||
errors_msg,
|
errors_msg,
|
||||||
error_placement,
|
error_placement,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test handling of discovery authentication errors."""
|
"""Test handling of discovery authentication errors.
|
||||||
mock_discovery["mock_device"].update.side_effect = AuthenticationError
|
|
||||||
default_connect_side_effect = mock_connect["connect"].side_effect
|
|
||||||
mock_connect["connect"].side_effect = error_type
|
|
||||||
|
|
||||||
|
Tests for errors received during credential
|
||||||
|
entry during discovery_auth_confirm.
|
||||||
|
"""
|
||||||
|
mock_device = mock_connect["mock_devices"][IP_ADDRESS]
|
||||||
|
|
||||||
|
with override_side_effect(mock_connect["connect"], AuthenticationError):
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY},
|
context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY},
|
||||||
@ -191,7 +234,7 @@ async def test_discovery_auth_errors(
|
|||||||
CONF_HOST: IP_ADDRESS,
|
CONF_HOST: IP_ADDRESS,
|
||||||
CONF_MAC: MAC_ADDRESS,
|
CONF_MAC: MAC_ADDRESS,
|
||||||
CONF_ALIAS: ALIAS,
|
CONF_ALIAS: ALIAS,
|
||||||
CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_KLAP,
|
CONF_DEVICE: mock_device,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
@ -199,6 +242,7 @@ async def test_discovery_auth_errors(
|
|||||||
assert result["step_id"] == "discovery_auth_confirm"
|
assert result["step_id"] == "discovery_auth_confirm"
|
||||||
assert not result["errors"]
|
assert not result["errors"]
|
||||||
|
|
||||||
|
with override_side_effect(mock_connect["connect"], error_type):
|
||||||
result2 = await hass.config_entries.flow.async_configure(
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
result["flow_id"],
|
result["flow_id"],
|
||||||
user_input={
|
user_input={
|
||||||
@ -213,7 +257,6 @@ async def test_discovery_auth_errors(
|
|||||||
|
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
mock_connect["connect"].side_effect = default_connect_side_effect
|
|
||||||
result3 = await hass.config_entries.flow.async_configure(
|
result3 = await hass.config_entries.flow.async_configure(
|
||||||
result2["flow_id"],
|
result2["flow_id"],
|
||||||
{
|
{
|
||||||
@ -228,13 +271,13 @@ async def test_discovery_auth_errors(
|
|||||||
|
|
||||||
async def test_discovery_new_credentials(
|
async def test_discovery_new_credentials(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
mock_discovery: AsyncMock,
|
|
||||||
mock_connect: AsyncMock,
|
mock_connect: AsyncMock,
|
||||||
mock_init,
|
mock_init,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test setting up discovery with new credentials."""
|
"""Test setting up discovery with new credentials."""
|
||||||
mock_discovery["mock_device"].update.side_effect = AuthenticationError
|
mock_device = mock_connect["mock_devices"][IP_ADDRESS]
|
||||||
|
|
||||||
|
with override_side_effect(mock_connect["connect"], AuthenticationError):
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY},
|
context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY},
|
||||||
@ -242,7 +285,7 @@ async def test_discovery_new_credentials(
|
|||||||
CONF_HOST: IP_ADDRESS,
|
CONF_HOST: IP_ADDRESS,
|
||||||
CONF_MAC: MAC_ADDRESS,
|
CONF_MAC: MAC_ADDRESS,
|
||||||
CONF_ALIAS: ALIAS,
|
CONF_ALIAS: ALIAS,
|
||||||
CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_KLAP,
|
CONF_DEVICE: mock_device,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
@ -250,7 +293,7 @@ async def test_discovery_new_credentials(
|
|||||||
assert result["step_id"] == "discovery_auth_confirm"
|
assert result["step_id"] == "discovery_auth_confirm"
|
||||||
assert not result["errors"]
|
assert not result["errors"]
|
||||||
|
|
||||||
assert mock_connect["connect"].call_count == 0
|
assert mock_connect["connect"].call_count == 1
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.tplink.config_flow.get_credentials",
|
"homeassistant.components.tplink.config_flow.get_credentials",
|
||||||
@ -260,7 +303,7 @@ async def test_discovery_new_credentials(
|
|||||||
result["flow_id"],
|
result["flow_id"],
|
||||||
)
|
)
|
||||||
|
|
||||||
assert mock_connect["connect"].call_count == 1
|
assert mock_connect["connect"].call_count == 2
|
||||||
assert result2["type"] is FlowResultType.FORM
|
assert result2["type"] is FlowResultType.FORM
|
||||||
assert result2["step_id"] == "discovery_confirm"
|
assert result2["step_id"] == "discovery_confirm"
|
||||||
|
|
||||||
@ -277,16 +320,20 @@ async def test_discovery_new_credentials(
|
|||||||
|
|
||||||
async def test_discovery_new_credentials_invalid(
|
async def test_discovery_new_credentials_invalid(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
mock_discovery: AsyncMock,
|
|
||||||
mock_connect: AsyncMock,
|
mock_connect: AsyncMock,
|
||||||
mock_init,
|
mock_init,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test setting up discovery with new invalid credentials."""
|
"""Test setting up discovery with new invalid credentials."""
|
||||||
mock_discovery["mock_device"].update.side_effect = AuthenticationError
|
mock_device = mock_connect["mock_devices"][IP_ADDRESS]
|
||||||
default_connect_side_effect = mock_connect["connect"].side_effect
|
|
||||||
|
|
||||||
mock_connect["connect"].side_effect = AuthenticationError
|
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch("homeassistant.components.tplink.Discover.discover", return_value={}),
|
||||||
|
patch(
|
||||||
|
"homeassistant.components.tplink.config_flow.get_credentials",
|
||||||
|
return_value=None,
|
||||||
|
),
|
||||||
|
override_side_effect(mock_connect["connect"], AuthenticationError),
|
||||||
|
):
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY},
|
context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY},
|
||||||
@ -294,7 +341,7 @@ async def test_discovery_new_credentials_invalid(
|
|||||||
CONF_HOST: IP_ADDRESS,
|
CONF_HOST: IP_ADDRESS,
|
||||||
CONF_MAC: MAC_ADDRESS,
|
CONF_MAC: MAC_ADDRESS,
|
||||||
CONF_ALIAS: ALIAS,
|
CONF_ALIAS: ALIAS,
|
||||||
CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_KLAP,
|
CONF_DEVICE: mock_device,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
@ -302,23 +349,25 @@ async def test_discovery_new_credentials_invalid(
|
|||||||
assert result["step_id"] == "discovery_auth_confirm"
|
assert result["step_id"] == "discovery_auth_confirm"
|
||||||
assert not result["errors"]
|
assert not result["errors"]
|
||||||
|
|
||||||
assert mock_connect["connect"].call_count == 0
|
assert mock_connect["connect"].call_count == 1
|
||||||
|
|
||||||
with patch(
|
with (
|
||||||
|
patch(
|
||||||
"homeassistant.components.tplink.config_flow.get_credentials",
|
"homeassistant.components.tplink.config_flow.get_credentials",
|
||||||
return_value=Credentials("fake_user", "fake_pass"),
|
return_value=Credentials("fake_user", "fake_pass"),
|
||||||
|
),
|
||||||
|
override_side_effect(mock_connect["connect"], AuthenticationError),
|
||||||
):
|
):
|
||||||
result2 = await hass.config_entries.flow.async_configure(
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
result["flow_id"],
|
result["flow_id"],
|
||||||
)
|
)
|
||||||
|
|
||||||
assert mock_connect["connect"].call_count == 1
|
assert mock_connect["connect"].call_count == 2
|
||||||
assert result2["type"] is FlowResultType.FORM
|
assert result2["type"] is FlowResultType.FORM
|
||||||
assert result2["step_id"] == "discovery_auth_confirm"
|
assert result2["step_id"] == "discovery_auth_confirm"
|
||||||
|
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
mock_connect["connect"].side_effect = default_connect_side_effect
|
|
||||||
result3 = await hass.config_entries.flow.async_configure(
|
result3 = await hass.config_entries.flow.async_configure(
|
||||||
result2["flow_id"],
|
result2["flow_id"],
|
||||||
{
|
{
|
||||||
@ -577,9 +626,8 @@ async def test_manual_auth_errors(
|
|||||||
assert not result["errors"]
|
assert not result["errors"]
|
||||||
|
|
||||||
mock_discovery["mock_device"].update.side_effect = AuthenticationError
|
mock_discovery["mock_device"].update.side_effect = AuthenticationError
|
||||||
default_connect_side_effect = mock_connect["connect"].side_effect
|
|
||||||
mock_connect["connect"].side_effect = error_type
|
|
||||||
|
|
||||||
|
with override_side_effect(mock_connect["connect"], error_type):
|
||||||
result2 = await hass.config_entries.flow.async_configure(
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
result["flow_id"], user_input={CONF_HOST: IP_ADDRESS}
|
result["flow_id"], user_input={CONF_HOST: IP_ADDRESS}
|
||||||
)
|
)
|
||||||
@ -588,7 +636,7 @@ async def test_manual_auth_errors(
|
|||||||
assert not result2["errors"]
|
assert not result2["errors"]
|
||||||
|
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
with override_side_effect(mock_connect["connect"], error_type):
|
||||||
result3 = await hass.config_entries.flow.async_configure(
|
result3 = await hass.config_entries.flow.async_configure(
|
||||||
result2["flow_id"],
|
result2["flow_id"],
|
||||||
user_input={
|
user_input={
|
||||||
@ -602,7 +650,6 @@ async def test_manual_auth_errors(
|
|||||||
assert result3["errors"] == {error_placement: errors_msg}
|
assert result3["errors"] == {error_placement: errors_msg}
|
||||||
assert result3["description_placeholders"]["error"] == str(error_type)
|
assert result3["description_placeholders"]["error"] == str(error_type)
|
||||||
|
|
||||||
mock_connect["connect"].side_effect = default_connect_side_effect
|
|
||||||
result4 = await hass.config_entries.flow.async_configure(
|
result4 = await hass.config_entries.flow.async_configure(
|
||||||
result3["flow_id"],
|
result3["flow_id"],
|
||||||
{
|
{
|
||||||
@ -628,7 +675,7 @@ async def test_discovered_by_discovery_and_dhcp(hass: HomeAssistant) -> None:
|
|||||||
CONF_HOST: IP_ADDRESS,
|
CONF_HOST: IP_ADDRESS,
|
||||||
CONF_MAC: MAC_ADDRESS,
|
CONF_MAC: MAC_ADDRESS,
|
||||||
CONF_ALIAS: ALIAS,
|
CONF_ALIAS: ALIAS,
|
||||||
CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_LEGACY,
|
CONF_DEVICE: _mocked_device(device_config=DEVICE_CONFIG_LEGACY),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
@ -691,7 +738,7 @@ async def test_discovered_by_discovery_and_dhcp(hass: HomeAssistant) -> None:
|
|||||||
CONF_HOST: IP_ADDRESS,
|
CONF_HOST: IP_ADDRESS,
|
||||||
CONF_MAC: MAC_ADDRESS,
|
CONF_MAC: MAC_ADDRESS,
|
||||||
CONF_ALIAS: ALIAS,
|
CONF_ALIAS: ALIAS,
|
||||||
CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_LEGACY,
|
CONF_DEVICE: _mocked_device(device_config=DEVICE_CONFIG_LEGACY),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -745,7 +792,7 @@ async def test_discovered_by_dhcp_or_discovery(
|
|||||||
CONF_HOST: IP_ADDRESS,
|
CONF_HOST: IP_ADDRESS,
|
||||||
CONF_MAC: MAC_ADDRESS,
|
CONF_MAC: MAC_ADDRESS,
|
||||||
CONF_ALIAS: ALIAS,
|
CONF_ALIAS: ALIAS,
|
||||||
CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_LEGACY,
|
CONF_DEVICE: _mocked_device(device_config=DEVICE_CONFIG_LEGACY),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -775,9 +822,11 @@ async def test_integration_discovery_with_ip_change(
|
|||||||
mock_connect: AsyncMock,
|
mock_connect: AsyncMock,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test reauth flow."""
|
"""Test reauth flow."""
|
||||||
mock_connect["connect"].side_effect = KasaException()
|
|
||||||
mock_config_entry.add_to_hass(hass)
|
mock_config_entry.add_to_hass(hass)
|
||||||
with patch("homeassistant.components.tplink.Discover.discover", return_value={}):
|
with (
|
||||||
|
patch("homeassistant.components.tplink.Discover.discover", return_value={}),
|
||||||
|
override_side_effect(mock_connect["connect"], KasaException()),
|
||||||
|
):
|
||||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
@ -785,9 +834,14 @@ async def test_integration_discovery_with_ip_change(
|
|||||||
|
|
||||||
flows = hass.config_entries.flow.async_progress()
|
flows = hass.config_entries.flow.async_progress()
|
||||||
assert len(flows) == 0
|
assert len(flows) == 0
|
||||||
assert mock_config_entry.data[CONF_DEVICE_CONFIG] == DEVICE_CONFIG_DICT_LEGACY
|
assert (
|
||||||
assert mock_config_entry.data[CONF_DEVICE_CONFIG].get(CONF_HOST) == "127.0.0.1"
|
mock_config_entry.data[CONF_CONNECTION_PARAMETERS]
|
||||||
|
== CONN_PARAMS_LEGACY.to_dict()
|
||||||
|
)
|
||||||
|
assert mock_config_entry.data[CONF_HOST] == "127.0.0.1"
|
||||||
|
|
||||||
|
mocked_device = _mocked_device(device_config=DEVICE_CONFIG_KLAP)
|
||||||
|
with override_side_effect(mock_connect["connect"], lambda *_, **__: mocked_device):
|
||||||
discovery_result = await hass.config_entries.flow.async_init(
|
discovery_result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY},
|
context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY},
|
||||||
@ -795,29 +849,42 @@ async def test_integration_discovery_with_ip_change(
|
|||||||
CONF_HOST: "127.0.0.2",
|
CONF_HOST: "127.0.0.2",
|
||||||
CONF_MAC: MAC_ADDRESS,
|
CONF_MAC: MAC_ADDRESS,
|
||||||
CONF_ALIAS: ALIAS,
|
CONF_ALIAS: ALIAS,
|
||||||
CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_KLAP,
|
CONF_DEVICE: mocked_device,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert discovery_result["type"] is FlowResultType.ABORT
|
assert discovery_result["type"] is FlowResultType.ABORT
|
||||||
assert discovery_result["reason"] == "already_configured"
|
assert discovery_result["reason"] == "already_configured"
|
||||||
assert mock_config_entry.data[CONF_DEVICE_CONFIG] == DEVICE_CONFIG_DICT_KLAP
|
assert (
|
||||||
|
mock_config_entry.data[CONF_CONNECTION_PARAMETERS] == CONN_PARAMS_KLAP.to_dict()
|
||||||
|
)
|
||||||
assert mock_config_entry.data[CONF_HOST] == "127.0.0.2"
|
assert mock_config_entry.data[CONF_HOST] == "127.0.0.2"
|
||||||
|
|
||||||
config = DeviceConfig.from_dict(DEVICE_CONFIG_DICT_KLAP)
|
config = DeviceConfig.from_dict(DEVICE_CONFIG_DICT_KLAP)
|
||||||
|
|
||||||
|
# Do a reload here and check that the
|
||||||
|
# new config is picked up in setup_entry
|
||||||
mock_connect["connect"].reset_mock(side_effect=True)
|
mock_connect["connect"].reset_mock(side_effect=True)
|
||||||
bulb = _mocked_device(
|
bulb = _mocked_device(
|
||||||
device_config=config,
|
device_config=config,
|
||||||
mac=mock_config_entry.unique_id,
|
mac=mock_config_entry.unique_id,
|
||||||
)
|
)
|
||||||
mock_connect["connect"].return_value = bulb
|
|
||||||
|
with (
|
||||||
|
patch(
|
||||||
|
"homeassistant.components.tplink.async_create_clientsession",
|
||||||
|
return_value="Foo",
|
||||||
|
),
|
||||||
|
override_side_effect(mock_connect["connect"], lambda *_, **__: bulb),
|
||||||
|
):
|
||||||
await hass.config_entries.async_reload(mock_config_entry.entry_id)
|
await hass.config_entries.async_reload(mock_config_entry.entry_id)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert mock_config_entry.state is ConfigEntryState.LOADED
|
assert mock_config_entry.state is ConfigEntryState.LOADED
|
||||||
# Check that init set the new host correctly before calling connect
|
# Check that init set the new host correctly before calling connect
|
||||||
assert config.host == "127.0.0.1"
|
assert config.host == "127.0.0.1"
|
||||||
config.host = "127.0.0.2"
|
config.host = "127.0.0.2"
|
||||||
|
config.uses_http = False # Not passed in to new config class
|
||||||
|
config.http_client = "Foo"
|
||||||
mock_connect["connect"].assert_awaited_once_with(config=config)
|
mock_connect["connect"].assert_awaited_once_with(config=config)
|
||||||
|
|
||||||
|
|
||||||
@ -831,8 +898,6 @@ async def test_integration_discovery_with_connection_change(
|
|||||||
|
|
||||||
And that connection_hash is removed as it will be invalid.
|
And that connection_hash is removed as it will be invalid.
|
||||||
"""
|
"""
|
||||||
mock_connect["connect"].side_effect = KasaException()
|
|
||||||
|
|
||||||
mock_config_entry = MockConfigEntry(
|
mock_config_entry = MockConfigEntry(
|
||||||
title="TPLink",
|
title="TPLink",
|
||||||
domain=DOMAIN,
|
domain=DOMAIN,
|
||||||
@ -840,7 +905,10 @@ async def test_integration_discovery_with_connection_change(
|
|||||||
unique_id=MAC_ADDRESS2,
|
unique_id=MAC_ADDRESS2,
|
||||||
)
|
)
|
||||||
mock_config_entry.add_to_hass(hass)
|
mock_config_entry.add_to_hass(hass)
|
||||||
with patch("homeassistant.components.tplink.Discover.discover", return_value={}):
|
with (
|
||||||
|
patch("homeassistant.components.tplink.Discover.discover", return_value={}),
|
||||||
|
override_side_effect(mock_connect["connect"], KasaException()),
|
||||||
|
):
|
||||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||||
await hass.async_block_till_done(wait_background_tasks=True)
|
await hass.async_block_till_done(wait_background_tasks=True)
|
||||||
|
|
||||||
@ -854,24 +922,32 @@ async def test_integration_discovery_with_connection_change(
|
|||||||
== 0
|
== 0
|
||||||
)
|
)
|
||||||
assert mock_config_entry.data[CONF_HOST] == "127.0.0.2"
|
assert mock_config_entry.data[CONF_HOST] == "127.0.0.2"
|
||||||
assert mock_config_entry.data[CONF_DEVICE_CONFIG] == DEVICE_CONFIG_DICT_AES
|
assert (
|
||||||
assert mock_config_entry.data[CONF_DEVICE_CONFIG].get(CONF_HOST) == "127.0.0.2"
|
mock_config_entry.data[CONF_CONNECTION_PARAMETERS] == CONN_PARAMS_AES.to_dict()
|
||||||
|
)
|
||||||
assert mock_config_entry.data[CONF_CREDENTIALS_HASH] == CREDENTIALS_HASH_AES
|
assert mock_config_entry.data[CONF_CREDENTIALS_HASH] == CREDENTIALS_HASH_AES
|
||||||
|
|
||||||
|
mock_connect["connect"].reset_mock()
|
||||||
NEW_DEVICE_CONFIG = {
|
NEW_DEVICE_CONFIG = {
|
||||||
**DEVICE_CONFIG_DICT_KLAP,
|
**DEVICE_CONFIG_DICT_KLAP,
|
||||||
CONF_CONNECTION_TYPE: CONNECTION_TYPE_KLAP_DICT,
|
"connection_type": CONN_PARAMS_KLAP.to_dict(),
|
||||||
CONF_HOST: "127.0.0.2",
|
CONF_HOST: "127.0.0.2",
|
||||||
}
|
}
|
||||||
config = DeviceConfig.from_dict(NEW_DEVICE_CONFIG)
|
config = DeviceConfig.from_dict(NEW_DEVICE_CONFIG)
|
||||||
# Reset the connect mock so when the config flow reloads the entry it succeeds
|
# Reset the connect mock so when the config flow reloads the entry it succeeds
|
||||||
mock_connect["connect"].reset_mock(side_effect=True)
|
|
||||||
bulb = _mocked_device(
|
bulb = _mocked_device(
|
||||||
device_config=config,
|
device_config=config,
|
||||||
mac=mock_config_entry.unique_id,
|
mac=mock_config_entry.unique_id,
|
||||||
)
|
)
|
||||||
mock_connect["connect"].return_value = bulb
|
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch(
|
||||||
|
"homeassistant.components.tplink.async_create_clientsession",
|
||||||
|
return_value="Foo",
|
||||||
|
),
|
||||||
|
override_side_effect(mock_connect["connect"], lambda *_, **__: bulb),
|
||||||
|
):
|
||||||
discovery_result = await hass.config_entries.flow.async_init(
|
discovery_result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY},
|
context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY},
|
||||||
@ -879,18 +955,24 @@ async def test_integration_discovery_with_connection_change(
|
|||||||
CONF_HOST: "127.0.0.2",
|
CONF_HOST: "127.0.0.2",
|
||||||
CONF_MAC: MAC_ADDRESS2,
|
CONF_MAC: MAC_ADDRESS2,
|
||||||
CONF_ALIAS: ALIAS,
|
CONF_ALIAS: ALIAS,
|
||||||
CONF_DEVICE_CONFIG: NEW_DEVICE_CONFIG,
|
CONF_DEVICE: bulb,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done(wait_background_tasks=True)
|
await hass.async_block_till_done(wait_background_tasks=True)
|
||||||
assert discovery_result["type"] is FlowResultType.ABORT
|
assert discovery_result["type"] is FlowResultType.ABORT
|
||||||
assert discovery_result["reason"] == "already_configured"
|
assert discovery_result["reason"] == "already_configured"
|
||||||
assert mock_config_entry.data[CONF_DEVICE_CONFIG] == NEW_DEVICE_CONFIG
|
assert (
|
||||||
|
mock_config_entry.data[CONF_CONNECTION_PARAMETERS] == CONN_PARAMS_KLAP.to_dict()
|
||||||
|
)
|
||||||
assert mock_config_entry.data[CONF_HOST] == "127.0.0.2"
|
assert mock_config_entry.data[CONF_HOST] == "127.0.0.2"
|
||||||
assert CREDENTIALS_HASH_AES not in mock_config_entry.data
|
assert CREDENTIALS_HASH_AES not in mock_config_entry.data
|
||||||
|
|
||||||
assert mock_config_entry.state is ConfigEntryState.LOADED
|
assert mock_config_entry.state is ConfigEntryState.LOADED
|
||||||
|
|
||||||
|
config.host = "127.0.0.2"
|
||||||
|
config.uses_http = False # Not passed in to new config class
|
||||||
|
config.http_client = "Foo"
|
||||||
|
config.aes_keys = AES_KEYS
|
||||||
mock_connect["connect"].assert_awaited_once_with(config=config)
|
mock_connect["connect"].assert_awaited_once_with(config=config)
|
||||||
|
|
||||||
|
|
||||||
@ -901,17 +983,18 @@ async def test_dhcp_discovery_with_ip_change(
|
|||||||
mock_connect: AsyncMock,
|
mock_connect: AsyncMock,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test dhcp discovery with an IP change."""
|
"""Test dhcp discovery with an IP change."""
|
||||||
mock_connect["connect"].side_effect = KasaException()
|
|
||||||
mock_config_entry.add_to_hass(hass)
|
mock_config_entry.add_to_hass(hass)
|
||||||
with patch("homeassistant.components.tplink.Discover.discover", return_value={}):
|
with (
|
||||||
|
patch("homeassistant.components.tplink.Discover.discover", return_value={}),
|
||||||
|
override_side_effect(mock_connect["connect"], KasaException()),
|
||||||
|
):
|
||||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY
|
assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY
|
||||||
|
|
||||||
flows = hass.config_entries.flow.async_progress()
|
flows = hass.config_entries.flow.async_progress()
|
||||||
assert len(flows) == 0
|
assert len(flows) == 0
|
||||||
assert mock_config_entry.data[CONF_DEVICE_CONFIG] == DEVICE_CONFIG_DICT_LEGACY
|
assert mock_config_entry.data[CONF_HOST] == "127.0.0.1"
|
||||||
assert mock_config_entry.data[CONF_DEVICE_CONFIG].get(CONF_HOST) == "127.0.0.1"
|
|
||||||
|
|
||||||
discovery_result = await hass.config_entries.flow.async_init(
|
discovery_result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
@ -966,8 +1049,7 @@ async def test_reauth_update_with_encryption_change(
|
|||||||
caplog: pytest.LogCaptureFixture,
|
caplog: pytest.LogCaptureFixture,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test reauth flow."""
|
"""Test reauth flow."""
|
||||||
orig_side_effect = mock_connect["connect"].side_effect
|
|
||||||
mock_connect["connect"].side_effect = AuthenticationError()
|
|
||||||
mock_config_entry = MockConfigEntry(
|
mock_config_entry = MockConfigEntry(
|
||||||
title="TPLink",
|
title="TPLink",
|
||||||
domain=DOMAIN,
|
domain=DOMAIN,
|
||||||
@ -975,10 +1057,15 @@ async def test_reauth_update_with_encryption_change(
|
|||||||
unique_id=MAC_ADDRESS2,
|
unique_id=MAC_ADDRESS2,
|
||||||
)
|
)
|
||||||
mock_config_entry.add_to_hass(hass)
|
mock_config_entry.add_to_hass(hass)
|
||||||
assert mock_config_entry.data[CONF_DEVICE_CONFIG] == DEVICE_CONFIG_DICT_AES
|
assert (
|
||||||
|
mock_config_entry.data[CONF_CONNECTION_PARAMETERS] == CONN_PARAMS_AES.to_dict()
|
||||||
|
)
|
||||||
assert mock_config_entry.data[CONF_CREDENTIALS_HASH] == CREDENTIALS_HASH_AES
|
assert mock_config_entry.data[CONF_CREDENTIALS_HASH] == CREDENTIALS_HASH_AES
|
||||||
|
|
||||||
with patch("homeassistant.components.tplink.Discover.discover", return_value={}):
|
with (
|
||||||
|
patch("homeassistant.components.tplink.Discover.discover", return_value={}),
|
||||||
|
override_side_effect(mock_connect["connect"], AuthenticationError()),
|
||||||
|
):
|
||||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert mock_config_entry.state is ConfigEntryState.SETUP_ERROR
|
assert mock_config_entry.state is ConfigEntryState.SETUP_ERROR
|
||||||
@ -988,7 +1075,9 @@ async def test_reauth_update_with_encryption_change(
|
|||||||
assert len(flows) == 1
|
assert len(flows) == 1
|
||||||
[result] = flows
|
[result] = flows
|
||||||
assert result["step_id"] == "reauth_confirm"
|
assert result["step_id"] == "reauth_confirm"
|
||||||
assert mock_config_entry.data[CONF_DEVICE_CONFIG] == DEVICE_CONFIG_DICT_AES
|
assert (
|
||||||
|
mock_config_entry.data[CONF_CONNECTION_PARAMETERS] == CONN_PARAMS_AES.to_dict()
|
||||||
|
)
|
||||||
assert CONF_CREDENTIALS_HASH not in mock_config_entry.data
|
assert CONF_CREDENTIALS_HASH not in mock_config_entry.data
|
||||||
|
|
||||||
new_config = DeviceConfig(
|
new_config = DeviceConfig(
|
||||||
@ -1005,7 +1094,6 @@ async def test_reauth_update_with_encryption_change(
|
|||||||
mock_connect["mock_devices"]["127.0.0.2"].config = new_config
|
mock_connect["mock_devices"]["127.0.0.2"].config = new_config
|
||||||
mock_connect["mock_devices"]["127.0.0.2"].credentials_hash = CREDENTIALS_HASH_KLAP
|
mock_connect["mock_devices"]["127.0.0.2"].credentials_hash = CREDENTIALS_HASH_KLAP
|
||||||
|
|
||||||
mock_connect["connect"].side_effect = orig_side_effect
|
|
||||||
result2 = await hass.config_entries.flow.async_configure(
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
result["flow_id"],
|
result["flow_id"],
|
||||||
user_input={
|
user_input={
|
||||||
@ -1023,10 +1111,10 @@ async def test_reauth_update_with_encryption_change(
|
|||||||
assert result2["type"] is FlowResultType.ABORT
|
assert result2["type"] is FlowResultType.ABORT
|
||||||
assert result2["reason"] == "reauth_successful"
|
assert result2["reason"] == "reauth_successful"
|
||||||
assert mock_config_entry.state is ConfigEntryState.LOADED
|
assert mock_config_entry.state is ConfigEntryState.LOADED
|
||||||
assert mock_config_entry.data[CONF_DEVICE_CONFIG] == {
|
assert (
|
||||||
**DEVICE_CONFIG_DICT_KLAP,
|
mock_config_entry.data[CONF_CONNECTION_PARAMETERS] == CONN_PARAMS_KLAP.to_dict()
|
||||||
CONF_HOST: "127.0.0.2",
|
)
|
||||||
}
|
assert mock_config_entry.data[CONF_HOST] == "127.0.0.2"
|
||||||
assert mock_config_entry.data[CONF_CREDENTIALS_HASH] == CREDENTIALS_HASH_KLAP
|
assert mock_config_entry.data[CONF_CREDENTIALS_HASH] == CREDENTIALS_HASH_KLAP
|
||||||
|
|
||||||
|
|
||||||
@ -1037,9 +1125,11 @@ async def test_reauth_update_from_discovery(
|
|||||||
mock_connect: AsyncMock,
|
mock_connect: AsyncMock,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test reauth flow."""
|
"""Test reauth flow."""
|
||||||
mock_connect["connect"].side_effect = AuthenticationError
|
|
||||||
mock_config_entry.add_to_hass(hass)
|
mock_config_entry.add_to_hass(hass)
|
||||||
with patch("homeassistant.components.tplink.Discover.discover", return_value={}):
|
with (
|
||||||
|
patch("homeassistant.components.tplink.Discover.discover", return_value={}),
|
||||||
|
override_side_effect(mock_connect["connect"], AuthenticationError()),
|
||||||
|
):
|
||||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
@ -1049,8 +1139,16 @@ async def test_reauth_update_from_discovery(
|
|||||||
assert len(flows) == 1
|
assert len(flows) == 1
|
||||||
[result] = flows
|
[result] = flows
|
||||||
assert result["step_id"] == "reauth_confirm"
|
assert result["step_id"] == "reauth_confirm"
|
||||||
assert mock_config_entry.data[CONF_DEVICE_CONFIG] == DEVICE_CONFIG_DICT_LEGACY
|
assert (
|
||||||
|
mock_config_entry.data[CONF_CONNECTION_PARAMETERS]
|
||||||
|
== CONN_PARAMS_LEGACY.to_dict()
|
||||||
|
)
|
||||||
|
|
||||||
|
device = _mocked_device(
|
||||||
|
device_config=DEVICE_CONFIG_KLAP,
|
||||||
|
mac=mock_config_entry.unique_id,
|
||||||
|
)
|
||||||
|
with override_side_effect(mock_connect["connect"], lambda *_, **__: device):
|
||||||
discovery_result = await hass.config_entries.flow.async_init(
|
discovery_result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY},
|
context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY},
|
||||||
@ -1058,13 +1156,15 @@ async def test_reauth_update_from_discovery(
|
|||||||
CONF_HOST: IP_ADDRESS,
|
CONF_HOST: IP_ADDRESS,
|
||||||
CONF_MAC: MAC_ADDRESS,
|
CONF_MAC: MAC_ADDRESS,
|
||||||
CONF_ALIAS: ALIAS,
|
CONF_ALIAS: ALIAS,
|
||||||
CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_KLAP,
|
CONF_DEVICE: device,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert discovery_result["type"] is FlowResultType.ABORT
|
assert discovery_result["type"] is FlowResultType.ABORT
|
||||||
assert discovery_result["reason"] == "already_configured"
|
assert discovery_result["reason"] == "already_configured"
|
||||||
assert mock_config_entry.data[CONF_DEVICE_CONFIG] == DEVICE_CONFIG_DICT_KLAP
|
assert (
|
||||||
|
mock_config_entry.data[CONF_CONNECTION_PARAMETERS] == CONN_PARAMS_KLAP.to_dict()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def test_reauth_update_from_discovery_with_ip_change(
|
async def test_reauth_update_from_discovery_with_ip_change(
|
||||||
@ -1074,9 +1174,11 @@ async def test_reauth_update_from_discovery_with_ip_change(
|
|||||||
mock_connect: AsyncMock,
|
mock_connect: AsyncMock,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test reauth flow."""
|
"""Test reauth flow."""
|
||||||
mock_connect["connect"].side_effect = AuthenticationError()
|
|
||||||
mock_config_entry.add_to_hass(hass)
|
mock_config_entry.add_to_hass(hass)
|
||||||
with patch("homeassistant.components.tplink.Discover.discover", return_value={}):
|
with (
|
||||||
|
patch("homeassistant.components.tplink.Discover.discover", return_value={}),
|
||||||
|
override_side_effect(mock_connect["connect"], AuthenticationError()),
|
||||||
|
):
|
||||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert mock_config_entry.state is ConfigEntryState.SETUP_ERROR
|
assert mock_config_entry.state is ConfigEntryState.SETUP_ERROR
|
||||||
@ -1085,8 +1187,16 @@ async def test_reauth_update_from_discovery_with_ip_change(
|
|||||||
assert len(flows) == 1
|
assert len(flows) == 1
|
||||||
[result] = flows
|
[result] = flows
|
||||||
assert result["step_id"] == "reauth_confirm"
|
assert result["step_id"] == "reauth_confirm"
|
||||||
assert mock_config_entry.data[CONF_DEVICE_CONFIG] == DEVICE_CONFIG_DICT_LEGACY
|
assert (
|
||||||
|
mock_config_entry.data[CONF_CONNECTION_PARAMETERS]
|
||||||
|
== CONN_PARAMS_LEGACY.to_dict()
|
||||||
|
)
|
||||||
|
|
||||||
|
device = _mocked_device(
|
||||||
|
device_config=DEVICE_CONFIG_KLAP,
|
||||||
|
mac=mock_config_entry.unique_id,
|
||||||
|
)
|
||||||
|
with override_side_effect(mock_connect["connect"], lambda *_, **__: device):
|
||||||
discovery_result = await hass.config_entries.flow.async_init(
|
discovery_result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY},
|
context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY},
|
||||||
@ -1094,13 +1204,15 @@ async def test_reauth_update_from_discovery_with_ip_change(
|
|||||||
CONF_HOST: "127.0.0.2",
|
CONF_HOST: "127.0.0.2",
|
||||||
CONF_MAC: MAC_ADDRESS,
|
CONF_MAC: MAC_ADDRESS,
|
||||||
CONF_ALIAS: ALIAS,
|
CONF_ALIAS: ALIAS,
|
||||||
CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_KLAP,
|
CONF_DEVICE: device,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert discovery_result["type"] is FlowResultType.ABORT
|
assert discovery_result["type"] is FlowResultType.ABORT
|
||||||
assert discovery_result["reason"] == "already_configured"
|
assert discovery_result["reason"] == "already_configured"
|
||||||
assert mock_config_entry.data[CONF_DEVICE_CONFIG] == DEVICE_CONFIG_DICT_KLAP
|
assert (
|
||||||
|
mock_config_entry.data[CONF_CONNECTION_PARAMETERS] == CONN_PARAMS_KLAP.to_dict()
|
||||||
|
)
|
||||||
assert mock_config_entry.data[CONF_HOST] == "127.0.0.2"
|
assert mock_config_entry.data[CONF_HOST] == "127.0.0.2"
|
||||||
|
|
||||||
|
|
||||||
@ -1111,8 +1223,8 @@ async def test_reauth_no_update_if_config_and_ip_the_same(
|
|||||||
mock_connect: AsyncMock,
|
mock_connect: AsyncMock,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test reauth discovery does not update when the host and config are the same."""
|
"""Test reauth discovery does not update when the host and config are the same."""
|
||||||
mock_connect["connect"].side_effect = AuthenticationError()
|
|
||||||
mock_config_entry.add_to_hass(hass)
|
mock_config_entry.add_to_hass(hass)
|
||||||
|
|
||||||
hass.config_entries.async_update_entry(
|
hass.config_entries.async_update_entry(
|
||||||
mock_config_entry,
|
mock_config_entry,
|
||||||
data={
|
data={
|
||||||
@ -1120,6 +1232,7 @@ async def test_reauth_no_update_if_config_and_ip_the_same(
|
|||||||
CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_KLAP,
|
CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_KLAP,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
with override_side_effect(mock_connect["connect"], AuthenticationError()):
|
||||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert mock_config_entry.state is ConfigEntryState.SETUP_ERROR
|
assert mock_config_entry.state is ConfigEntryState.SETUP_ERROR
|
||||||
@ -1128,8 +1241,15 @@ async def test_reauth_no_update_if_config_and_ip_the_same(
|
|||||||
assert len(flows) == 1
|
assert len(flows) == 1
|
||||||
[result] = flows
|
[result] = flows
|
||||||
assert result["step_id"] == "reauth_confirm"
|
assert result["step_id"] == "reauth_confirm"
|
||||||
assert mock_config_entry.data[CONF_DEVICE_CONFIG] == DEVICE_CONFIG_DICT_KLAP
|
assert (
|
||||||
|
mock_config_entry.data[CONF_CONNECTION_PARAMETERS] == CONN_PARAMS_KLAP.to_dict()
|
||||||
|
)
|
||||||
|
|
||||||
|
device = _mocked_device(
|
||||||
|
device_config=DEVICE_CONFIG_KLAP,
|
||||||
|
mac=mock_config_entry.unique_id,
|
||||||
|
)
|
||||||
|
with override_side_effect(mock_connect["connect"], lambda *_, **__: device):
|
||||||
discovery_result = await hass.config_entries.flow.async_init(
|
discovery_result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY},
|
context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY},
|
||||||
@ -1137,13 +1257,15 @@ async def test_reauth_no_update_if_config_and_ip_the_same(
|
|||||||
CONF_HOST: IP_ADDRESS,
|
CONF_HOST: IP_ADDRESS,
|
||||||
CONF_MAC: MAC_ADDRESS,
|
CONF_MAC: MAC_ADDRESS,
|
||||||
CONF_ALIAS: ALIAS,
|
CONF_ALIAS: ALIAS,
|
||||||
CONF_DEVICE_CONFIG: DEVICE_CONFIG_DICT_KLAP,
|
CONF_DEVICE: device,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert discovery_result["type"] is FlowResultType.ABORT
|
assert discovery_result["type"] is FlowResultType.ABORT
|
||||||
assert discovery_result["reason"] == "already_configured"
|
assert discovery_result["reason"] == "already_configured"
|
||||||
assert mock_config_entry.data[CONF_DEVICE_CONFIG] == DEVICE_CONFIG_DICT_KLAP
|
assert (
|
||||||
|
mock_config_entry.data[CONF_CONNECTION_PARAMETERS] == CONN_PARAMS_KLAP.to_dict()
|
||||||
|
)
|
||||||
assert mock_config_entry.data[CONF_HOST] == IP_ADDRESS
|
assert mock_config_entry.data[CONF_HOST] == IP_ADDRESS
|
||||||
|
|
||||||
|
|
||||||
@ -1241,8 +1363,7 @@ async def test_pick_device_errors(
|
|||||||
assert result2["step_id"] == "pick_device"
|
assert result2["step_id"] == "pick_device"
|
||||||
assert not result2["errors"]
|
assert not result2["errors"]
|
||||||
|
|
||||||
default_connect_side_effect = mock_connect["connect"].side_effect
|
with override_side_effect(mock_connect["connect"], error_type):
|
||||||
mock_connect["connect"].side_effect = error_type
|
|
||||||
result3 = await hass.config_entries.flow.async_configure(
|
result3 = await hass.config_entries.flow.async_configure(
|
||||||
result2["flow_id"],
|
result2["flow_id"],
|
||||||
{CONF_DEVICE: MAC_ADDRESS},
|
{CONF_DEVICE: MAC_ADDRESS},
|
||||||
@ -1251,7 +1372,6 @@ async def test_pick_device_errors(
|
|||||||
assert result3["type"] == expected_flow
|
assert result3["type"] == expected_flow
|
||||||
|
|
||||||
if expected_flow != FlowResultType.ABORT:
|
if expected_flow != FlowResultType.ABORT:
|
||||||
mock_connect["connect"].side_effect = default_connect_side_effect
|
|
||||||
result4 = await hass.config_entries.flow.async_configure(
|
result4 = await hass.config_entries.flow.async_configure(
|
||||||
result3["flow_id"],
|
result3["flow_id"],
|
||||||
user_input={
|
user_input={
|
||||||
@ -1300,13 +1420,13 @@ async def test_discovery_timeout_connect_legacy_error(
|
|||||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
)
|
)
|
||||||
mock_discovery["discover_single"].side_effect = TimeoutError
|
mock_discovery["discover_single"].side_effect = TimeoutError
|
||||||
mock_connect["connect"].side_effect = KasaException
|
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert result["type"] is FlowResultType.FORM
|
assert result["type"] is FlowResultType.FORM
|
||||||
assert result["step_id"] == "user"
|
assert result["step_id"] == "user"
|
||||||
assert not result["errors"]
|
assert not result["errors"]
|
||||||
assert mock_connect["connect"].call_count == 0
|
assert mock_connect["connect"].call_count == 0
|
||||||
|
|
||||||
|
with override_side_effect(mock_connect["connect"], KasaException):
|
||||||
result2 = await hass.config_entries.flow.async_configure(
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
result["flow_id"], {CONF_HOST: IP_ADDRESS}
|
result["flow_id"], {CONF_HOST: IP_ADDRESS}
|
||||||
)
|
)
|
||||||
@ -1334,17 +1454,17 @@ async def test_reauth_update_other_flows(
|
|||||||
data={**CREATE_ENTRY_DATA_AES},
|
data={**CREATE_ENTRY_DATA_AES},
|
||||||
unique_id=MAC_ADDRESS2,
|
unique_id=MAC_ADDRESS2,
|
||||||
)
|
)
|
||||||
default_side_effect = mock_connect["connect"].side_effect
|
|
||||||
mock_connect["connect"].side_effect = AuthenticationError()
|
|
||||||
mock_config_entry.add_to_hass(hass)
|
mock_config_entry.add_to_hass(hass)
|
||||||
mock_config_entry2.add_to_hass(hass)
|
mock_config_entry2.add_to_hass(hass)
|
||||||
with patch("homeassistant.components.tplink.Discover.discover", return_value={}):
|
with (
|
||||||
|
patch("homeassistant.components.tplink.Discover.discover", return_value={}),
|
||||||
|
override_side_effect(mock_connect["connect"], AuthenticationError()),
|
||||||
|
):
|
||||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert mock_config_entry2.state is ConfigEntryState.SETUP_ERROR
|
assert mock_config_entry2.state is ConfigEntryState.SETUP_ERROR
|
||||||
assert mock_config_entry.state is ConfigEntryState.SETUP_ERROR
|
assert mock_config_entry.state is ConfigEntryState.SETUP_ERROR
|
||||||
mock_connect["connect"].side_effect = default_side_effect
|
|
||||||
|
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
@ -1353,7 +1473,9 @@ async def test_reauth_update_other_flows(
|
|||||||
flows_by_entry_id = {flow["context"]["entry_id"]: flow for flow in flows}
|
flows_by_entry_id = {flow["context"]["entry_id"]: flow for flow in flows}
|
||||||
result = flows_by_entry_id[mock_config_entry.entry_id]
|
result = flows_by_entry_id[mock_config_entry.entry_id]
|
||||||
assert result["step_id"] == "reauth_confirm"
|
assert result["step_id"] == "reauth_confirm"
|
||||||
assert mock_config_entry.data[CONF_DEVICE_CONFIG] == DEVICE_CONFIG_DICT_KLAP
|
assert (
|
||||||
|
mock_config_entry.data[CONF_CONNECTION_PARAMETERS] == CONN_PARAMS_KLAP.to_dict()
|
||||||
|
)
|
||||||
result2 = await hass.config_entries.flow.async_configure(
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
result["flow_id"],
|
result["flow_id"],
|
||||||
user_input={
|
user_input={
|
||||||
|
@ -4,6 +4,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import copy
|
import copy
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
from typing import Any
|
||||||
from unittest.mock import AsyncMock, MagicMock, PropertyMock, patch
|
from unittest.mock import AsyncMock, MagicMock, PropertyMock, patch
|
||||||
|
|
||||||
from freezegun.api import FrozenDateTimeFactory
|
from freezegun.api import FrozenDateTimeFactory
|
||||||
@ -13,14 +14,18 @@ import pytest
|
|||||||
from homeassistant import setup
|
from homeassistant import setup
|
||||||
from homeassistant.components import tplink
|
from homeassistant.components import tplink
|
||||||
from homeassistant.components.tplink.const import (
|
from homeassistant.components.tplink.const import (
|
||||||
|
CONF_AES_KEYS,
|
||||||
|
CONF_CONNECTION_PARAMETERS,
|
||||||
CONF_CREDENTIALS_HASH,
|
CONF_CREDENTIALS_HASH,
|
||||||
CONF_DEVICE_CONFIG,
|
CONF_DEVICE_CONFIG,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
)
|
)
|
||||||
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState
|
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
|
CONF_ALIAS,
|
||||||
CONF_AUTHENTICATION,
|
CONF_AUTHENTICATION,
|
||||||
CONF_HOST,
|
CONF_HOST,
|
||||||
|
CONF_MODEL,
|
||||||
CONF_PASSWORD,
|
CONF_PASSWORD,
|
||||||
CONF_USERNAME,
|
CONF_USERNAME,
|
||||||
STATE_ON,
|
STATE_ON,
|
||||||
@ -33,13 +38,20 @@ from homeassistant.setup import async_setup_component
|
|||||||
from homeassistant.util import dt as dt_util
|
from homeassistant.util import dt as dt_util
|
||||||
|
|
||||||
from . import (
|
from . import (
|
||||||
|
ALIAS,
|
||||||
|
CREATE_ENTRY_DATA_AES,
|
||||||
CREATE_ENTRY_DATA_KLAP,
|
CREATE_ENTRY_DATA_KLAP,
|
||||||
CREATE_ENTRY_DATA_LEGACY,
|
CREATE_ENTRY_DATA_LEGACY,
|
||||||
|
CREDENTIALS_HASH_AES,
|
||||||
|
CREDENTIALS_HASH_KLAP,
|
||||||
|
DEVICE_CONFIG_AES,
|
||||||
DEVICE_CONFIG_KLAP,
|
DEVICE_CONFIG_KLAP,
|
||||||
|
DEVICE_CONFIG_LEGACY,
|
||||||
DEVICE_ID,
|
DEVICE_ID,
|
||||||
DEVICE_ID_MAC,
|
DEVICE_ID_MAC,
|
||||||
IP_ADDRESS,
|
IP_ADDRESS,
|
||||||
MAC_ADDRESS,
|
MAC_ADDRESS,
|
||||||
|
MODEL,
|
||||||
_mocked_device,
|
_mocked_device,
|
||||||
_patch_connect,
|
_patch_connect,
|
||||||
_patch_discovery,
|
_patch_discovery,
|
||||||
@ -207,16 +219,21 @@ async def test_config_entry_with_stored_credentials(
|
|||||||
|
|
||||||
hass.data.setdefault(DOMAIN, {})[CONF_AUTHENTICATION] = auth
|
hass.data.setdefault(DOMAIN, {})[CONF_AUTHENTICATION] = auth
|
||||||
mock_config_entry.add_to_hass(hass)
|
mock_config_entry.add_to_hass(hass)
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.tplink.async_create_clientsession", return_value="Foo"
|
||||||
|
):
|
||||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert mock_config_entry.state is ConfigEntryState.LOADED
|
assert mock_config_entry.state is ConfigEntryState.LOADED
|
||||||
config = DEVICE_CONFIG_KLAP
|
config = DeviceConfig.from_dict(DEVICE_CONFIG_KLAP.to_dict())
|
||||||
|
config.uses_http = False
|
||||||
|
config.http_client = "Foo"
|
||||||
assert config.credentials != stored_credentials
|
assert config.credentials != stored_credentials
|
||||||
config.credentials = stored_credentials
|
config.credentials = stored_credentials
|
||||||
mock_connect["connect"].assert_called_once_with(config=config)
|
mock_connect["connect"].assert_called_once_with(config=config)
|
||||||
|
|
||||||
|
|
||||||
async def test_config_entry_device_config_invalid(
|
async def test_config_entry_conn_params_invalid(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
mock_discovery: AsyncMock,
|
mock_discovery: AsyncMock,
|
||||||
mock_connect: AsyncMock,
|
mock_connect: AsyncMock,
|
||||||
@ -224,7 +241,7 @@ async def test_config_entry_device_config_invalid(
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Test that an invalid device config logs an error and loads the config entry."""
|
"""Test that an invalid device config logs an error and loads the config entry."""
|
||||||
entry_data = copy.deepcopy(CREATE_ENTRY_DATA_KLAP)
|
entry_data = copy.deepcopy(CREATE_ENTRY_DATA_KLAP)
|
||||||
entry_data[CONF_DEVICE_CONFIG] = {"foo": "bar"}
|
entry_data[CONF_CONNECTION_PARAMETERS] = {"foo": "bar"}
|
||||||
mock_config_entry = MockConfigEntry(
|
mock_config_entry = MockConfigEntry(
|
||||||
title="TPLink",
|
title="TPLink",
|
||||||
domain=DOMAIN,
|
domain=DOMAIN,
|
||||||
@ -237,7 +254,7 @@ async def test_config_entry_device_config_invalid(
|
|||||||
assert mock_config_entry.state is ConfigEntryState.LOADED
|
assert mock_config_entry.state is ConfigEntryState.LOADED
|
||||||
|
|
||||||
assert (
|
assert (
|
||||||
f"Invalid connection type dict for {IP_ADDRESS}: {entry_data.get(CONF_DEVICE_CONFIG)}"
|
f"Invalid connection parameters dict for {IP_ADDRESS}: {entry_data.get(CONF_CONNECTION_PARAMETERS)}"
|
||||||
in caplog.text
|
in caplog.text
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -495,6 +512,7 @@ async def test_unlink_devices(
|
|||||||
}
|
}
|
||||||
assert device_entries[0].identifiers == set(test_identifiers)
|
assert device_entries[0].identifiers == set(test_identifiers)
|
||||||
|
|
||||||
|
with patch("homeassistant.components.tplink.CONF_CONFIG_ENTRY_MINOR_VERSION", 3):
|
||||||
await hass.config_entries.async_setup(entry.entry_id)
|
await hass.config_entries.async_setup(entry.entry_id)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
@ -504,7 +522,7 @@ async def test_unlink_devices(
|
|||||||
|
|
||||||
assert device_entries[0].identifiers == set(expected_identifiers)
|
assert device_entries[0].identifiers == set(expected_identifiers)
|
||||||
assert entry.version == 1
|
assert entry.version == 1
|
||||||
assert entry.minor_version == 4
|
assert entry.minor_version == 3
|
||||||
|
|
||||||
assert update_msg in caplog.text
|
assert update_msg in caplog.text
|
||||||
assert "Migration to version 1.3 complete" in caplog.text
|
assert "Migration to version 1.3 complete" in caplog.text
|
||||||
@ -545,6 +563,7 @@ async def test_move_credentials_hash(
|
|||||||
with (
|
with (
|
||||||
patch("homeassistant.components.tplink.Device.connect", new=_connect),
|
patch("homeassistant.components.tplink.Device.connect", new=_connect),
|
||||||
patch("homeassistant.components.tplink.PLATFORMS", []),
|
patch("homeassistant.components.tplink.PLATFORMS", []),
|
||||||
|
patch("homeassistant.components.tplink.CONF_CONFIG_ENTRY_MINOR_VERSION", 4),
|
||||||
):
|
):
|
||||||
await hass.config_entries.async_setup(entry.entry_id)
|
await hass.config_entries.async_setup(entry.entry_id)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
@ -589,6 +608,7 @@ async def test_move_credentials_hash_auth_error(
|
|||||||
side_effect=AuthenticationError,
|
side_effect=AuthenticationError,
|
||||||
),
|
),
|
||||||
patch("homeassistant.components.tplink.PLATFORMS", []),
|
patch("homeassistant.components.tplink.PLATFORMS", []),
|
||||||
|
patch("homeassistant.components.tplink.CONF_CONFIG_ENTRY_MINOR_VERSION", 4),
|
||||||
):
|
):
|
||||||
entry.add_to_hass(hass)
|
entry.add_to_hass(hass)
|
||||||
await hass.config_entries.async_setup(entry.entry_id)
|
await hass.config_entries.async_setup(entry.entry_id)
|
||||||
@ -631,6 +651,7 @@ async def test_move_credentials_hash_other_error(
|
|||||||
"homeassistant.components.tplink.Device.connect", side_effect=KasaException
|
"homeassistant.components.tplink.Device.connect", side_effect=KasaException
|
||||||
),
|
),
|
||||||
patch("homeassistant.components.tplink.PLATFORMS", []),
|
patch("homeassistant.components.tplink.PLATFORMS", []),
|
||||||
|
patch("homeassistant.components.tplink.CONF_CONFIG_ENTRY_MINOR_VERSION", 4),
|
||||||
):
|
):
|
||||||
entry.add_to_hass(hass)
|
entry.add_to_hass(hass)
|
||||||
await hass.config_entries.async_setup(entry.entry_id)
|
await hass.config_entries.async_setup(entry.entry_id)
|
||||||
@ -647,10 +668,8 @@ async def test_credentials_hash(
|
|||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test credentials_hash used to call connect."""
|
"""Test credentials_hash used to call connect."""
|
||||||
device_config = {**DEVICE_CONFIG_KLAP.to_dict(exclude_credentials=True)}
|
|
||||||
entry_data = {
|
entry_data = {
|
||||||
**CREATE_ENTRY_DATA_KLAP,
|
**CREATE_ENTRY_DATA_KLAP,
|
||||||
CONF_DEVICE_CONFIG: device_config,
|
|
||||||
CONF_CREDENTIALS_HASH: "theHash",
|
CONF_CREDENTIALS_HASH: "theHash",
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -674,9 +693,7 @@ async def test_credentials_hash(
|
|||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert entry.state is ConfigEntryState.LOADED
|
assert entry.state is ConfigEntryState.LOADED
|
||||||
assert CONF_CREDENTIALS_HASH not in entry.data[CONF_DEVICE_CONFIG]
|
|
||||||
assert CONF_CREDENTIALS_HASH in entry.data
|
assert CONF_CREDENTIALS_HASH in entry.data
|
||||||
assert entry.data[CONF_DEVICE_CONFIG] == device_config
|
|
||||||
assert entry.data[CONF_CREDENTIALS_HASH] == "theHash"
|
assert entry.data[CONF_CREDENTIALS_HASH] == "theHash"
|
||||||
|
|
||||||
|
|
||||||
@ -684,10 +701,8 @@ async def test_credentials_hash_auth_error(
|
|||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test credentials_hash is deleted after an auth failure."""
|
"""Test credentials_hash is deleted after an auth failure."""
|
||||||
device_config = {**DEVICE_CONFIG_KLAP.to_dict(exclude_credentials=True)}
|
|
||||||
entry_data = {
|
entry_data = {
|
||||||
**CREATE_ENTRY_DATA_KLAP,
|
**CREATE_ENTRY_DATA_KLAP,
|
||||||
CONF_DEVICE_CONFIG: device_config,
|
|
||||||
CONF_CREDENTIALS_HASH: "theHash",
|
CONF_CREDENTIALS_HASH: "theHash",
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -700,6 +715,10 @@ async def test_credentials_hash_auth_error(
|
|||||||
|
|
||||||
with (
|
with (
|
||||||
patch("homeassistant.components.tplink.PLATFORMS", []),
|
patch("homeassistant.components.tplink.PLATFORMS", []),
|
||||||
|
patch(
|
||||||
|
"homeassistant.components.tplink.async_create_clientsession",
|
||||||
|
return_value="Foo",
|
||||||
|
),
|
||||||
patch(
|
patch(
|
||||||
"homeassistant.components.tplink.Device.connect",
|
"homeassistant.components.tplink.Device.connect",
|
||||||
side_effect=AuthenticationError,
|
side_effect=AuthenticationError,
|
||||||
@ -712,6 +731,76 @@ async def test_credentials_hash_auth_error(
|
|||||||
expected_config = DeviceConfig.from_dict(
|
expected_config = DeviceConfig.from_dict(
|
||||||
DEVICE_CONFIG_KLAP.to_dict(exclude_credentials=True, credentials_hash="theHash")
|
DEVICE_CONFIG_KLAP.to_dict(exclude_credentials=True, credentials_hash="theHash")
|
||||||
)
|
)
|
||||||
|
expected_config.uses_http = False
|
||||||
|
expected_config.http_client = "Foo"
|
||||||
connect_mock.assert_called_with(config=expected_config)
|
connect_mock.assert_called_with(config=expected_config)
|
||||||
assert entry.state is ConfigEntryState.SETUP_ERROR
|
assert entry.state is ConfigEntryState.SETUP_ERROR
|
||||||
assert CONF_CREDENTIALS_HASH not in entry.data
|
assert CONF_CREDENTIALS_HASH not in entry.data
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("device_config", "expected_entry_data", "credentials_hash"),
|
||||||
|
[
|
||||||
|
pytest.param(
|
||||||
|
DEVICE_CONFIG_KLAP, CREATE_ENTRY_DATA_KLAP, CREDENTIALS_HASH_KLAP, id="KLAP"
|
||||||
|
),
|
||||||
|
pytest.param(
|
||||||
|
DEVICE_CONFIG_AES, CREATE_ENTRY_DATA_AES, CREDENTIALS_HASH_AES, id="AES"
|
||||||
|
),
|
||||||
|
pytest.param(DEVICE_CONFIG_LEGACY, CREATE_ENTRY_DATA_LEGACY, None, id="Legacy"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_migrate_remove_device_config(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_connect: AsyncMock,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
device_config: DeviceConfig,
|
||||||
|
expected_entry_data: dict[str, Any],
|
||||||
|
credentials_hash: str,
|
||||||
|
) -> None:
|
||||||
|
"""Test credentials hash moved to parent.
|
||||||
|
|
||||||
|
As async_setup_entry will succeed the hash on the parent is updated
|
||||||
|
from the device.
|
||||||
|
"""
|
||||||
|
OLD_CREATE_ENTRY_DATA = {
|
||||||
|
CONF_HOST: expected_entry_data[CONF_HOST],
|
||||||
|
CONF_ALIAS: ALIAS,
|
||||||
|
CONF_MODEL: MODEL,
|
||||||
|
CONF_DEVICE_CONFIG: device_config.to_dict(exclude_credentials=True),
|
||||||
|
}
|
||||||
|
|
||||||
|
entry = MockConfigEntry(
|
||||||
|
title="TPLink",
|
||||||
|
domain=DOMAIN,
|
||||||
|
data=OLD_CREATE_ENTRY_DATA,
|
||||||
|
entry_id="123456",
|
||||||
|
unique_id=MAC_ADDRESS,
|
||||||
|
version=1,
|
||||||
|
minor_version=4,
|
||||||
|
)
|
||||||
|
entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
async def _connect(config):
|
||||||
|
config.credentials_hash = credentials_hash
|
||||||
|
config.aes_keys = expected_entry_data.get(CONF_AES_KEYS)
|
||||||
|
return _mocked_device(device_config=config, credentials_hash=credentials_hash)
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch("homeassistant.components.tplink.Device.connect", new=_connect),
|
||||||
|
patch("homeassistant.components.tplink.PLATFORMS", []),
|
||||||
|
patch(
|
||||||
|
"homeassistant.components.tplink.async_create_clientsession",
|
||||||
|
return_value="Foo",
|
||||||
|
),
|
||||||
|
patch("homeassistant.components.tplink.CONF_CONFIG_ENTRY_MINOR_VERSION", 5),
|
||||||
|
):
|
||||||
|
await hass.config_entries.async_setup(entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert entry.minor_version == 5
|
||||||
|
assert entry.state is ConfigEntryState.LOADED
|
||||||
|
assert CONF_DEVICE_CONFIG not in entry.data
|
||||||
|
assert entry.data == expected_entry_data
|
||||||
|
|
||||||
|
assert "Migration to version 1.5 complete" in caplog.text
|
||||||
|
@ -82,10 +82,10 @@ def get_fixture_data() -> dict[str, Any]:
|
|||||||
def get_update_data(loaded_fixture: dict[str, Any]) -> YaleSmartAlarmData:
|
def get_update_data(loaded_fixture: dict[str, Any]) -> YaleSmartAlarmData:
|
||||||
"""Load update data and return."""
|
"""Load update data and return."""
|
||||||
|
|
||||||
status = loaded_fixture["STATUS"]
|
status = {"data": loaded_fixture["STATUS"]}
|
||||||
cycle = loaded_fixture["CYCLE"]
|
cycle = {"data": loaded_fixture["CYCLE"]}
|
||||||
online = loaded_fixture["ONLINE"]
|
online = {"data": loaded_fixture["ONLINE"]}
|
||||||
panel_info = loaded_fixture["PANEL INFO"]
|
panel_info = {"data": loaded_fixture["PANEL INFO"]}
|
||||||
return YaleSmartAlarmData(
|
return YaleSmartAlarmData(
|
||||||
status=status,
|
status=status,
|
||||||
cycle=cycle,
|
cycle=cycle,
|
||||||
@ -98,14 +98,14 @@ def get_update_data(loaded_fixture: dict[str, Any]) -> YaleSmartAlarmData:
|
|||||||
def get_diag_data(loaded_fixture: dict[str, Any]) -> YaleSmartAlarmData:
|
def get_diag_data(loaded_fixture: dict[str, Any]) -> YaleSmartAlarmData:
|
||||||
"""Load all data and return."""
|
"""Load all data and return."""
|
||||||
|
|
||||||
devices = loaded_fixture["DEVICES"]
|
devices = {"data": loaded_fixture["DEVICES"]}
|
||||||
mode = loaded_fixture["MODE"]
|
mode = {"data": loaded_fixture["MODE"]}
|
||||||
status = loaded_fixture["STATUS"]
|
status = {"data": loaded_fixture["STATUS"]}
|
||||||
cycle = loaded_fixture["CYCLE"]
|
cycle = {"data": loaded_fixture["CYCLE"]}
|
||||||
online = loaded_fixture["ONLINE"]
|
online = {"data": loaded_fixture["ONLINE"]}
|
||||||
history = loaded_fixture["HISTORY"]
|
history = {"data": loaded_fixture["HISTORY"]}
|
||||||
panel_info = loaded_fixture["PANEL INFO"]
|
panel_info = {"data": loaded_fixture["PANEL INFO"]}
|
||||||
auth_check = loaded_fixture["AUTH CHECK"]
|
auth_check = {"data": loaded_fixture["AUTH CHECK"]}
|
||||||
return YaleSmartAlarmData(
|
return YaleSmartAlarmData(
|
||||||
devices=devices,
|
devices=devices,
|
||||||
mode=mode,
|
mode=mode,
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
# name: test_diagnostics
|
# name: test_diagnostics
|
||||||
dict({
|
dict({
|
||||||
'auth_check': dict({
|
'auth_check': dict({
|
||||||
|
'data': dict({
|
||||||
'agent': False,
|
'agent': False,
|
||||||
'dealer_group': 'yale',
|
'dealer_group': 'yale',
|
||||||
'dealer_id': '605',
|
'dealer_id': '605',
|
||||||
@ -16,7 +17,9 @@
|
|||||||
'user_id': '**REDACTED**',
|
'user_id': '**REDACTED**',
|
||||||
'xml_version': '2',
|
'xml_version': '2',
|
||||||
}),
|
}),
|
||||||
|
}),
|
||||||
'cycle': dict({
|
'cycle': dict({
|
||||||
|
'data': dict({
|
||||||
'alarm_event_latest': None,
|
'alarm_event_latest': None,
|
||||||
'capture_latest': None,
|
'capture_latest': None,
|
||||||
'device_status': list([
|
'device_status': list([
|
||||||
@ -650,7 +653,9 @@
|
|||||||
'utc_event_time': None,
|
'utc_event_time': None,
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
'devices': list([
|
}),
|
||||||
|
'devices': dict({
|
||||||
|
'data': list([
|
||||||
dict({
|
dict({
|
||||||
'address': '**REDACTED**',
|
'address': '**REDACTED**',
|
||||||
'area': '1',
|
'area': '1',
|
||||||
@ -1249,7 +1254,9 @@
|
|||||||
'type_no': '40',
|
'type_no': '40',
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
'history': list([
|
}),
|
||||||
|
'history': dict({
|
||||||
|
'data': list([
|
||||||
dict({
|
dict({
|
||||||
'area': 1,
|
'area': 1,
|
||||||
'cid': '18180701000',
|
'cid': '18180701000',
|
||||||
@ -1391,14 +1398,20 @@
|
|||||||
'zone': 1,
|
'zone': 1,
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
'mode': list([
|
}),
|
||||||
|
'mode': dict({
|
||||||
|
'data': list([
|
||||||
dict({
|
dict({
|
||||||
'area': '1',
|
'area': '1',
|
||||||
'mode': 'disarm',
|
'mode': 'disarm',
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
'online': 'online',
|
}),
|
||||||
|
'online': dict({
|
||||||
|
'data': 'online',
|
||||||
|
}),
|
||||||
'panel_info': dict({
|
'panel_info': dict({
|
||||||
|
'data': dict({
|
||||||
'SMS_Balance': '50',
|
'SMS_Balance': '50',
|
||||||
'contact': '',
|
'contact': '',
|
||||||
'dealer_name': 'Poland',
|
'dealer_name': 'Poland',
|
||||||
@ -1416,7 +1429,9 @@
|
|||||||
'zb_version': '4.1.2.6.2',
|
'zb_version': '4.1.2.6.2',
|
||||||
'zw_version': '',
|
'zw_version': '',
|
||||||
}),
|
}),
|
||||||
|
}),
|
||||||
'status': dict({
|
'status': dict({
|
||||||
|
'data': dict({
|
||||||
'acfail': 'main.normal',
|
'acfail': 'main.normal',
|
||||||
'battery': 'main.normal',
|
'battery': 'main.normal',
|
||||||
'gsm_rssi': '0',
|
'gsm_rssi': '0',
|
||||||
@ -1426,5 +1441,6 @@
|
|||||||
'rssi': '1',
|
'rssi': '1',
|
||||||
'tamper': 'main.normal',
|
'tamper': 'main.normal',
|
||||||
}),
|
}),
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
|
@ -55,7 +55,7 @@ async def test_lock_service_calls(
|
|||||||
client = load_config_entry[1]
|
client = load_config_entry[1]
|
||||||
|
|
||||||
data = deepcopy(get_data.cycle)
|
data = deepcopy(get_data.cycle)
|
||||||
data["data"] = data.pop("device_status")
|
data["data"] = data["data"].pop("device_status")
|
||||||
|
|
||||||
client.auth.get_authenticated = Mock(return_value=data)
|
client.auth.get_authenticated = Mock(return_value=data)
|
||||||
client.auth.post_authenticated = Mock(return_value={"code": "000"})
|
client.auth.post_authenticated = Mock(return_value={"code": "000"})
|
||||||
@ -109,7 +109,7 @@ async def test_lock_service_call_fails(
|
|||||||
client = load_config_entry[1]
|
client = load_config_entry[1]
|
||||||
|
|
||||||
data = deepcopy(get_data.cycle)
|
data = deepcopy(get_data.cycle)
|
||||||
data["data"] = data.pop("device_status")
|
data["data"] = data["data"].pop("device_status")
|
||||||
|
|
||||||
client.auth.get_authenticated = Mock(return_value=data)
|
client.auth.get_authenticated = Mock(return_value=data)
|
||||||
client.auth.post_authenticated = Mock(side_effect=UnknownError("test_side_effect"))
|
client.auth.post_authenticated = Mock(side_effect=UnknownError("test_side_effect"))
|
||||||
@ -161,7 +161,7 @@ async def test_lock_service_call_fails_with_incorrect_status(
|
|||||||
client = load_config_entry[1]
|
client = load_config_entry[1]
|
||||||
|
|
||||||
data = deepcopy(get_data.cycle)
|
data = deepcopy(get_data.cycle)
|
||||||
data["data"] = data.pop("device_status")
|
data["data"] = data["data"].pop("device_status")
|
||||||
|
|
||||||
client.auth.get_authenticated = Mock(return_value=data)
|
client.auth.get_authenticated = Mock(return_value=data)
|
||||||
client.auth.post_authenticated = Mock(return_value={"code": "FFF"})
|
client.auth.post_authenticated = Mock(return_value={"code": "FFF"})
|
||||||
|
@ -5,16 +5,23 @@ from typing import Any
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import voluptuous_serialize
|
import voluptuous_serialize
|
||||||
|
from zigpy.application import ControllerApplication
|
||||||
from zigpy.types.basic import uint16_t
|
from zigpy.types.basic import uint16_t
|
||||||
from zigpy.zcl.clusters import lighting
|
from zigpy.zcl.clusters import lighting
|
||||||
|
|
||||||
|
import homeassistant.components.zha.const as zha_const
|
||||||
from homeassistant.components.zha.helpers import (
|
from homeassistant.components.zha.helpers import (
|
||||||
cluster_command_schema_to_vol_schema,
|
cluster_command_schema_to_vol_schema,
|
||||||
convert_to_zcl_values,
|
convert_to_zcl_values,
|
||||||
|
create_zha_config,
|
||||||
exclude_none_values,
|
exclude_none_values,
|
||||||
|
get_zha_data,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -177,3 +184,35 @@ def test_exclude_none_values(
|
|||||||
|
|
||||||
for key in expected_output:
|
for key in expected_output:
|
||||||
assert expected_output[key] == obj[key]
|
assert expected_output[key] == obj[key]
|
||||||
|
|
||||||
|
|
||||||
|
async def test_create_zha_config_remove_unused(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config_entry: MockConfigEntry,
|
||||||
|
mock_zigpy_connect: ControllerApplication,
|
||||||
|
) -> None:
|
||||||
|
"""Test creating ZHA config data with unused keys."""
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
options = config_entry.options.copy()
|
||||||
|
options["custom_configuration"]["zha_options"]["some_random_key"] = "a value"
|
||||||
|
|
||||||
|
hass.config_entries.async_update_entry(config_entry, options=options)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
config_entry.options["custom_configuration"]["zha_options"]["some_random_key"]
|
||||||
|
== "a value"
|
||||||
|
)
|
||||||
|
|
||||||
|
status = await async_setup_component(
|
||||||
|
hass,
|
||||||
|
zha_const.DOMAIN,
|
||||||
|
{zha_const.DOMAIN: {zha_const.CONF_ENABLE_QUIRKS: False}},
|
||||||
|
)
|
||||||
|
assert status is True
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
ha_zha_data = get_zha_data(hass)
|
||||||
|
|
||||||
|
# Does not error out
|
||||||
|
create_zha_config(hass, ha_zha_data)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user