mirror of
https://github.com/home-assistant/core.git
synced 2025-11-13 04:50:17 +00:00
Compare commits
3 Commits
copilot/ad
...
improve-zh
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c1b799856a | ||
|
|
2f4e3b98f3 | ||
|
|
f2c354eb3d |
@@ -25,7 +25,9 @@ from homeassistant.components.homeassistant_hardware.firmware_config_flow import
|
||||
)
|
||||
from homeassistant.components.homeassistant_yellow import hardware as yellow_hardware
|
||||
from homeassistant.config_entries import (
|
||||
SOURCE_HARDWARE,
|
||||
SOURCE_IGNORE,
|
||||
SOURCE_USB,
|
||||
SOURCE_ZEROCONF,
|
||||
ConfigEntry,
|
||||
ConfigEntryBaseFlow,
|
||||
@@ -37,7 +39,7 @@ from homeassistant.config_entries import (
|
||||
)
|
||||
from homeassistant.const import CONF_NAME
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.data_entry_flow import AbortFlow
|
||||
from homeassistant.data_entry_flow import AbortFlow, progress_step
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.hassio import is_hassio
|
||||
from homeassistant.helpers.selector import FileSelector, FileSelectorConfig
|
||||
@@ -205,7 +207,7 @@ class BaseZhaFlow(ConfigEntryBaseFlow):
|
||||
}
|
||||
|
||||
@abstractmethod
|
||||
async def _async_create_radio_entry(self) -> ConfigFlowResult:
|
||||
async def async_step_create_radio_entry(self) -> ConfigFlowResult:
|
||||
"""Create a config entry with the current flow state."""
|
||||
|
||||
async def async_step_choose_serial_port(
|
||||
@@ -233,16 +235,6 @@ class BaseZhaFlow(ConfigEntryBaseFlow):
|
||||
port = ports[list_of_ports.index(user_selection)]
|
||||
self._radio_mgr.device_path = port.device
|
||||
|
||||
probe_result = await self._radio_mgr.detect_radio_type()
|
||||
if probe_result == ProbeResult.WRONG_FIRMWARE_INSTALLED:
|
||||
return self.async_abort(
|
||||
reason="wrong_firmware_installed",
|
||||
description_placeholders={"repair_url": REPAIR_MY_URL},
|
||||
)
|
||||
if probe_result == ProbeResult.PROBING_FAILED:
|
||||
# Did not autodetect anything, proceed to manual selection
|
||||
return await self.async_step_manual_pick_radio_type()
|
||||
|
||||
self._title = (
|
||||
f"{port.description}{', s/n: ' + port.serial_number if port.serial_number else ''}"
|
||||
f" - {port.manufacturer}"
|
||||
@@ -250,7 +242,7 @@ class BaseZhaFlow(ConfigEntryBaseFlow):
|
||||
else ""
|
||||
)
|
||||
|
||||
return await self.async_step_verify_radio()
|
||||
return await self.async_step_detect_radio_type()
|
||||
|
||||
# Pre-select the currently configured port
|
||||
default_port: vol.Undefined | str = vol.UNDEFINED
|
||||
@@ -272,6 +264,35 @@ class BaseZhaFlow(ConfigEntryBaseFlow):
|
||||
)
|
||||
return self.async_show_form(step_id="choose_serial_port", data_schema=schema)
|
||||
|
||||
@progress_step()
|
||||
async def async_step_detect_radio_type(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Detect the radio type."""
|
||||
# Probe the radio type if we don't have one yet
|
||||
if self._radio_mgr.radio_type is None:
|
||||
probe_result = await self._radio_mgr.detect_radio_type()
|
||||
else:
|
||||
probe_result = ProbeResult.RADIO_TYPE_DETECTED
|
||||
|
||||
if probe_result == ProbeResult.WRONG_FIRMWARE_INSTALLED:
|
||||
return self.async_abort(
|
||||
reason="wrong_firmware_installed",
|
||||
description_placeholders={"repair_url": REPAIR_MY_URL},
|
||||
)
|
||||
if probe_result == ProbeResult.PROBING_FAILED:
|
||||
# This path probably will not happen now that we have
|
||||
# more precise USB matching unless there is a problem
|
||||
# with the device
|
||||
if self.source in {SOURCE_HARDWARE, SOURCE_USB, SOURCE_ZEROCONF}:
|
||||
return self.async_abort(reason="usb_probe_failed")
|
||||
return await self.async_step_manual_pick_radio_type()
|
||||
|
||||
if self._radio_mgr.device_settings is None:
|
||||
return await self.async_step_manual_port_config()
|
||||
|
||||
return await self.async_step_verify_radio()
|
||||
|
||||
async def async_step_manual_pick_radio_type(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
@@ -353,10 +374,10 @@ class BaseZhaFlow(ConfigEntryBaseFlow):
|
||||
) -> ConfigFlowResult:
|
||||
"""Add a warning step to dissuade the use of deprecated radios."""
|
||||
assert self._radio_mgr.radio_type is not None
|
||||
await self._radio_mgr.async_read_backups_from_database()
|
||||
|
||||
# Skip this step if we are using a recommended radio
|
||||
if user_input is not None or self._radio_mgr.radio_type in RECOMMENDED_RADIOS:
|
||||
await self._radio_mgr.async_read_backups_from_database()
|
||||
# ZHA disables the single instance check and will decide at runtime if we
|
||||
# are migrating or setting up from scratch
|
||||
if self.hass.config_entries.async_entries(DOMAIN, include_ignore=False):
|
||||
@@ -410,7 +431,7 @@ class BaseZhaFlow(ConfigEntryBaseFlow):
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Advanced setup strategy: let the user choose."""
|
||||
return await self.async_step_choose_formation_strategy()
|
||||
return await self.async_step_load_network_settings()
|
||||
|
||||
async def async_step_choose_migration_strategy(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
@@ -439,6 +460,7 @@ class BaseZhaFlow(ConfigEntryBaseFlow):
|
||||
self._radio_mgr.chosen_backup = self._radio_mgr.backups[0]
|
||||
return await self.async_step_maybe_reset_old_radio()
|
||||
|
||||
@progress_step()
|
||||
async def async_step_maybe_reset_old_radio(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
@@ -468,20 +490,27 @@ class BaseZhaFlow(ConfigEntryBaseFlow):
|
||||
|
||||
await temp_radio_mgr.async_reset_adapter()
|
||||
|
||||
return await self.async_step_maybe_confirm_ezsp_restore()
|
||||
return await self.async_step_try_ezsp_restore()
|
||||
|
||||
async def async_step_migration_strategy_advanced(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Advanced migration strategy: let the user choose."""
|
||||
return await self.async_step_load_network_settings()
|
||||
|
||||
@progress_step()
|
||||
async def async_step_load_network_settings(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Load the current network settings from the radio."""
|
||||
await self._radio_mgr.async_load_network_settings()
|
||||
|
||||
return await self.async_step_choose_formation_strategy()
|
||||
|
||||
async def async_step_choose_formation_strategy(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Choose how to deal with the current radio's settings."""
|
||||
await self._radio_mgr.async_load_network_settings()
|
||||
|
||||
strategies = []
|
||||
|
||||
# Check if we have any automatic backups *and* if the backups differ from
|
||||
@@ -523,7 +552,7 @@ class BaseZhaFlow(ConfigEntryBaseFlow):
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Reuse the existing network settings on the stick."""
|
||||
return await self._async_create_radio_entry()
|
||||
return await self.async_step_create_radio_entry()
|
||||
|
||||
async def async_step_form_initial_network(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
@@ -532,12 +561,13 @@ class BaseZhaFlow(ConfigEntryBaseFlow):
|
||||
# This step exists only for translations, it does nothing new
|
||||
return await self.async_step_form_new_network(user_input)
|
||||
|
||||
@progress_step()
|
||||
async def async_step_form_new_network(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Form a brand-new network."""
|
||||
await self._radio_mgr.async_form_network()
|
||||
return await self._async_create_radio_entry()
|
||||
return await self.async_step_create_radio_entry()
|
||||
|
||||
def _parse_uploaded_backup(
|
||||
self, uploaded_file_id: str
|
||||
@@ -615,7 +645,25 @@ class BaseZhaFlow(ConfigEntryBaseFlow):
|
||||
),
|
||||
)
|
||||
|
||||
async def async_step_maybe_confirm_ezsp_restore(
|
||||
@progress_step()
|
||||
async def async_step_try_ezsp_restore(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Try to restore EZSP radios nondestructively."""
|
||||
# On first attempt, just try to restore nondestructively
|
||||
try:
|
||||
await self._radio_mgr.restore_backup()
|
||||
except DestructiveWriteNetworkSettings:
|
||||
# Restore cannot happen automatically, we need to ask for permission
|
||||
return await self.async_step_confirm_ezsp_restore()
|
||||
except CannotWriteNetworkSettings as exc:
|
||||
return self.async_abort(
|
||||
reason="cannot_restore_backup",
|
||||
description_placeholders={"error": str(exc)},
|
||||
)
|
||||
return await self.async_step_create_radio_entry()
|
||||
|
||||
async def async_step_confirm_ezsp_restore(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Confirm restore for EZSP radios that require permanent IEEE writes."""
|
||||
@@ -630,28 +678,14 @@ class BaseZhaFlow(ConfigEntryBaseFlow):
|
||||
description_placeholders={"error": str(exc)},
|
||||
)
|
||||
|
||||
return await self._async_create_radio_entry()
|
||||
return await self.async_step_create_radio_entry()
|
||||
|
||||
# On rejection, explain why we can't restore
|
||||
return self.async_abort(reason="cannot_restore_backup_no_ieee_confirm")
|
||||
|
||||
# On first attempt, just try to restore nondestructively
|
||||
try:
|
||||
await self._radio_mgr.restore_backup()
|
||||
except DestructiveWriteNetworkSettings:
|
||||
# Restore cannot happen automatically, we need to ask for permission
|
||||
pass
|
||||
except CannotWriteNetworkSettings as exc:
|
||||
return self.async_abort(
|
||||
reason="cannot_restore_backup",
|
||||
description_placeholders={"error": str(exc)},
|
||||
)
|
||||
else:
|
||||
return await self._async_create_radio_entry()
|
||||
|
||||
# If it fails, show the form
|
||||
return self.async_show_form(
|
||||
step_id="maybe_confirm_ezsp_restore",
|
||||
step_id="confirm_ezsp_restore",
|
||||
data_schema=vol.Schema(
|
||||
{vol.Required(OVERWRITE_COORDINATOR_IEEE, default=True): bool}
|
||||
),
|
||||
@@ -740,27 +774,7 @@ class ZhaConfigFlowHandler(BaseZhaFlow, ConfigFlow, domain=DOMAIN):
|
||||
if user_input is not None or (
|
||||
not onboarding.async_is_onboarded(self.hass) and not zha_config_entries
|
||||
):
|
||||
# Probe the radio type if we don't have one yet
|
||||
if self._radio_mgr.radio_type is None:
|
||||
probe_result = await self._radio_mgr.detect_radio_type()
|
||||
else:
|
||||
probe_result = ProbeResult.RADIO_TYPE_DETECTED
|
||||
|
||||
if probe_result == ProbeResult.WRONG_FIRMWARE_INSTALLED:
|
||||
return self.async_abort(
|
||||
reason="wrong_firmware_installed",
|
||||
description_placeholders={"repair_url": REPAIR_MY_URL},
|
||||
)
|
||||
if probe_result == ProbeResult.PROBING_FAILED:
|
||||
# This path probably will not happen now that we have
|
||||
# more precise USB matching unless there is a problem
|
||||
# with the device
|
||||
return self.async_abort(reason="usb_probe_failed")
|
||||
|
||||
if self._radio_mgr.device_settings is None:
|
||||
return await self.async_step_manual_port_config()
|
||||
|
||||
return await self.async_step_verify_radio()
|
||||
return await self.async_step_detect_radio_type()
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="confirm",
|
||||
@@ -898,7 +912,7 @@ class ZhaConfigFlowHandler(BaseZhaFlow, ConfigFlow, domain=DOMAIN):
|
||||
|
||||
return await self.async_step_confirm()
|
||||
|
||||
async def _async_create_radio_entry(self) -> ConfigFlowResult:
|
||||
async def async_step_create_radio_entry(self) -> ConfigFlowResult:
|
||||
"""Create a config entry with the current flow state."""
|
||||
|
||||
# ZHA is still single instance only, even though we use discovery to allow for
|
||||
@@ -1003,7 +1017,8 @@ class ZhaOptionsFlowHandler(BaseZhaFlow, OptionsFlow):
|
||||
|
||||
return self.async_show_form(step_id="instruct_unplug")
|
||||
|
||||
async def _async_create_radio_entry(self):
|
||||
@progress_step()
|
||||
async def async_step_create_radio_entry(self) -> ConfigFlowResult:
|
||||
"""Re-implementation of the base flow's final step to update the config."""
|
||||
|
||||
# Avoid creating both `.options` and `.data` by directly writing `data` here
|
||||
|
||||
@@ -15,6 +15,18 @@
|
||||
"confirm_hardware": {
|
||||
"description": "Do you want to set up {name}?"
|
||||
},
|
||||
"create_radio_entry": {
|
||||
"title": "Configuring Zigbee network"
|
||||
},
|
||||
"detect_radio_type": {
|
||||
"title": "Configuring Zigbee network"
|
||||
},
|
||||
"form_new_network": {
|
||||
"title": "Configuring Zigbee network"
|
||||
},
|
||||
"load_network_settings": {
|
||||
"title": "Configuring Zigbee network"
|
||||
},
|
||||
"manual_pick_radio_type": {
|
||||
"title": "Select a radio type",
|
||||
"description": "Pick your Zigbee radio type",
|
||||
@@ -65,8 +77,7 @@
|
||||
}
|
||||
},
|
||||
"maybe_reset_old_radio": {
|
||||
"title": "Resetting old adapter",
|
||||
"description": "A backup was created earlier and your old adapter is being reset as part of the migration."
|
||||
"title": "Configuring Zigbee network"
|
||||
},
|
||||
"choose_formation_strategy": {
|
||||
"title": "Network formation",
|
||||
@@ -100,12 +111,15 @@
|
||||
"uploaded_backup_file": "Upload a file"
|
||||
}
|
||||
},
|
||||
"maybe_confirm_ezsp_restore": {
|
||||
"confirm_ezsp_restore": {
|
||||
"title": "Overwrite radio IEEE address",
|
||||
"description": "Your backup has a different IEEE address than your radio. For your network to function properly, the IEEE address of your radio should also be changed.\n\nThis is a permanent operation.",
|
||||
"data": {
|
||||
"overwrite_coordinator_ieee": "Permanently replace the radio IEEE address"
|
||||
}
|
||||
},
|
||||
"try_ezsp_restore": {
|
||||
"title": "Configuring Zigbee network"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -122,15 +136,38 @@
|
||||
"cannot_restore_backup": "The adapter you are restoring to does not properly support backup restoration. Please upgrade the firmware.\n\nError: {error}",
|
||||
"cannot_restore_backup_no_ieee_confirm": "The adapter you are restoring to has outdated firmware and cannot write the adapter IEEE address multiple times. Please upgrade the firmware or confirm permanent overwrite in the previous step.",
|
||||
"reconfigure_successful": "ZHA has successfully migrated from your old adapter to the new one. Give your Zigbee network a few minutes to stabilize.\n\nIf you no longer need the old adapter, you can now unplug it."
|
||||
},
|
||||
"progress": {
|
||||
"create_radio_entry": "Finalizing configuration",
|
||||
"detect_radio_type": "Detecting the radio type",
|
||||
"form_new_network": "Creating a new Zigbee network",
|
||||
"load_network_settings": "Loading the current network settings from the radio",
|
||||
"maybe_reset_old_radio": "Resetting your old radio",
|
||||
"try_ezsp_restore": "Restoring the network settings to your radio"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"flow_title": "[%key:component::zha::config::flow_title%]",
|
||||
"step": {
|
||||
"create_radio_entry": {
|
||||
"title": "[%key:component::zha::config::step::create_radio_entry::title%]"
|
||||
},
|
||||
"detect_radio_type": {
|
||||
"title": "[%key:component::zha::config::step::detect_radio_type::title%]"
|
||||
},
|
||||
"form_new_network": {
|
||||
"title": "[%key:component::zha::config::step::form_new_network::title%]"
|
||||
},
|
||||
"init": {
|
||||
"title": "Reconfigure ZHA",
|
||||
"description": "A backup will be performed and ZHA will be stopped. Do you wish to continue?"
|
||||
},
|
||||
"load_network_settings": {
|
||||
"title": "[%key:component::zha::config::step::load_network_settings::title%]"
|
||||
},
|
||||
"maybe_reset_old_radio": {
|
||||
"title": "[%key:component::zha::config::step::maybe_reset_old_radio::title%]"
|
||||
},
|
||||
"prompt_migrate_or_reconfigure": {
|
||||
"title": "Migrate or re-configure",
|
||||
"description": "Are you migrating to a new radio or re-configuring the current radio?",
|
||||
@@ -143,6 +180,9 @@
|
||||
"intent_reconfigure": "This will let you change the serial port for your current Zigbee adapter."
|
||||
}
|
||||
},
|
||||
"try_ezsp_restore": {
|
||||
"title": "[%key:component::zha::config::step::try_ezsp_restore::title%]"
|
||||
},
|
||||
"intent_migrate": {
|
||||
"title": "[%key:component::zha::options::step::prompt_migrate_or_reconfigure::menu_options::intent_migrate%]",
|
||||
"description": "Before plugging in your new adapter, your old adapter needs to be reset. An automatic backup will be performed. If you are using a combined Z-Wave and Zigbee adapter like the HUSBZB-1, this will only reset the Zigbee portion.\n\n*Note: if you are migrating from a **ConBee/RaspBee**, make sure it is running firmware `0x26720700` or newer! Otherwise, some devices may not be controllable after migrating until they are power cycled.*\n\nDo you wish to continue?"
|
||||
@@ -222,11 +262,11 @@
|
||||
"uploaded_backup_file": "[%key:component::zha::config::step::upload_manual_backup::data::uploaded_backup_file%]"
|
||||
}
|
||||
},
|
||||
"maybe_confirm_ezsp_restore": {
|
||||
"title": "[%key:component::zha::config::step::maybe_confirm_ezsp_restore::title%]",
|
||||
"description": "[%key:component::zha::config::step::maybe_confirm_ezsp_restore::description%]",
|
||||
"confirm_ezsp_restore": {
|
||||
"title": "[%key:component::zha::config::step::confirm_ezsp_restore::title%]",
|
||||
"description": "[%key:component::zha::config::step::confirm_ezsp_restore::description%]",
|
||||
"data": {
|
||||
"overwrite_coordinator_ieee": "[%key:component::zha::config::step::maybe_confirm_ezsp_restore::data::overwrite_coordinator_ieee%]"
|
||||
"overwrite_coordinator_ieee": "[%key:component::zha::config::step::confirm_ezsp_restore::data::overwrite_coordinator_ieee%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -241,6 +281,14 @@
|
||||
"wrong_firmware_installed": "[%key:component::zha::config::abort::wrong_firmware_installed%]",
|
||||
"cannot_restore_backup": "[%key:component::zha::config::abort::cannot_restore_backup%]",
|
||||
"cannot_restore_backup_no_ieee_confirm": "[%key:component::zha::config::abort::cannot_restore_backup_no_ieee_confirm%]"
|
||||
},
|
||||
"progress": {
|
||||
"create_radio_entry": "[%key:component::zha::config::progress::create_radio_entry%]",
|
||||
"detect_radio_type": "[%key:component::zha::config::progress::detect_radio_type%]",
|
||||
"form_new_network": "[%key:component::zha::config::progress::form_new_network%]",
|
||||
"load_network_settings": "[%key:component::zha::config::progress::load_network_settings%]",
|
||||
"maybe_reset_old_radio": "[%key:component::zha::config::progress::maybe_reset_old_radio%]",
|
||||
"try_ezsp_restore": "[%key:component::zha::config::progress::try_ezsp_restore%]"
|
||||
}
|
||||
},
|
||||
"config_panel": {
|
||||
|
||||
@@ -799,14 +799,15 @@ class FlowHandler(Generic[_FlowContextT, _FlowResultT, _HandlerT]):
|
||||
without using async_show_progress_done.
|
||||
If no next step is set, abort the flow.
|
||||
"""
|
||||
if self._progress_step_data["next_step_result"] is None:
|
||||
if (next_step_result := self._progress_step_data["next_step_result"]) is None:
|
||||
return self.async_abort(
|
||||
reason=self._progress_step_data["abort_reason"],
|
||||
description_placeholders=self._progress_step_data[
|
||||
"abort_description_placeholders"
|
||||
],
|
||||
)
|
||||
return self._progress_step_data["next_step_result"]
|
||||
self._progress_step_data["next_step_result"] = None
|
||||
return next_step_result
|
||||
|
||||
@callback
|
||||
def async_external_step(
|
||||
@@ -1044,7 +1045,7 @@ def progress_step[
|
||||
|
||||
# Task is done or this is a subsequent call
|
||||
try:
|
||||
self._progress_step_data["next_step_result"] = await progress_task
|
||||
progress_task_result = await progress_task
|
||||
except AbortFlow as err:
|
||||
self._progress_step_data["abort_reason"] = err.reason
|
||||
self._progress_step_data["abort_description_placeholders"] = (
|
||||
@@ -1057,6 +1058,9 @@ def progress_step[
|
||||
# Clean up task reference
|
||||
self._progress_step_data["tasks"].pop(step_id, None)
|
||||
|
||||
if progress_task_result["type"] != FlowResultType.SHOW_PROGRESS_DONE:
|
||||
self._progress_step_data["next_step_result"] = progress_task_result
|
||||
|
||||
return self.async_show_progress_done(
|
||||
next_step_id="_progress_step_progress_done"
|
||||
)
|
||||
|
||||
@@ -50,7 +50,7 @@ from homeassistant.config_entries import (
|
||||
)
|
||||
from homeassistant.const import CONF_SOURCE
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.data_entry_flow import FlowResultType
|
||||
from homeassistant.data_entry_flow import BaseServiceInfo, FlowResultType
|
||||
from homeassistant.helpers.service_info.ssdp import (
|
||||
ATTR_UPNP_MANUFACTURER_URL,
|
||||
ATTR_UPNP_SERIAL,
|
||||
@@ -61,9 +61,7 @@ from homeassistant.helpers.service_info.zeroconf import ZeroconfServiceInfo
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
type RadioPicker = Callable[
|
||||
[RadioType], Coroutine[Any, Any, tuple[ConfigFlowResult, ListPortInfo]]
|
||||
]
|
||||
type RadioPicker = Callable[[RadioType], Coroutine[Any, Any, ConfigFlowResult]]
|
||||
PROBE_FUNCTION_PATH = "zigbee.application.ControllerApplication.probe"
|
||||
|
||||
|
||||
@@ -1529,13 +1527,19 @@ def advanced_pick_radio(
|
||||
) -> Generator[RadioPicker]:
|
||||
"""Fixture for the first step of the config flow (where a radio is picked)."""
|
||||
|
||||
async def wrapper(radio_type: RadioType) -> tuple[ConfigFlowResult, ListPortInfo]:
|
||||
async def wrapper(radio_type: RadioType) -> ConfigFlowResult:
|
||||
port = com_port()
|
||||
port_select = f"{port}, s/n: {port.serial_number} - {port.manufacturer}"
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.zha.radio_manager.ZhaRadioManager.detect_radio_type",
|
||||
mock_detect_radio_type(radio_type=radio_type),
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.zha.radio_manager.ZhaRadioManager.detect_radio_type",
|
||||
mock_detect_radio_type(radio_type=radio_type),
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.zha.radio_manager.ZhaRadioManager.async_load_network_settings",
|
||||
AsyncMock(return_value=None),
|
||||
),
|
||||
):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
@@ -1545,6 +1549,12 @@ def advanced_pick_radio(
|
||||
},
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.SHOW_PROGRESS
|
||||
assert result["step_id"] == "detect_radio_type"
|
||||
|
||||
await hass.async_block_till_done()
|
||||
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
||||
|
||||
assert result["type"] is FlowResultType.MENU
|
||||
assert result["step_id"] == "choose_setup_strategy"
|
||||
|
||||
@@ -1553,10 +1563,18 @@ def advanced_pick_radio(
|
||||
user_input={"next_step_id": config_flow.SETUP_STRATEGY_ADVANCED},
|
||||
)
|
||||
|
||||
assert advanced_strategy_result["type"] == FlowResultType.MENU
|
||||
assert advanced_strategy_result["step_id"] == "choose_formation_strategy"
|
||||
assert advanced_strategy_result["type"] is FlowResultType.SHOW_PROGRESS
|
||||
assert advanced_strategy_result["step_id"] == "load_network_settings"
|
||||
|
||||
return advanced_strategy_result
|
||||
await hass.async_block_till_done()
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
advanced_strategy_result["flow_id"]
|
||||
)
|
||||
|
||||
assert result["type"] == FlowResultType.MENU
|
||||
assert result["step_id"] == "choose_formation_strategy"
|
||||
|
||||
return result
|
||||
|
||||
p1 = patch("serial.tools.list_ports.comports", MagicMock(return_value=[com_port()]))
|
||||
p2 = patch("homeassistant.components.zha.async_setup_entry")
|
||||
@@ -1686,7 +1704,7 @@ def test_parse_uploaded_backup(process_mock) -> None:
|
||||
|
||||
@patch("homeassistant.components.zha.radio_manager._allow_overwrite_ezsp_ieee")
|
||||
async def test_formation_strategy_restore_manual_backup_non_ezsp(
|
||||
allow_overwrite_ieee_mock,
|
||||
allow_overwrite_ieee_mock: MagicMock,
|
||||
advanced_pick_radio: RadioPicker,
|
||||
mock_app: AsyncMock,
|
||||
hass: HomeAssistant,
|
||||
@@ -1694,50 +1712,54 @@ async def test_formation_strategy_restore_manual_backup_non_ezsp(
|
||||
"""Test restoring a manual backup on non-EZSP coordinators."""
|
||||
result = await advanced_pick_radio(RadioType.znp)
|
||||
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={"next_step_id": config_flow.FORMATION_UPLOAD_MANUAL_BACKUP},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result2["type"] is FlowResultType.FORM
|
||||
assert result2["step_id"] == "upload_manual_backup"
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "upload_manual_backup"
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.zha.config_flow.ZhaConfigFlowHandler._parse_uploaded_backup",
|
||||
return_value=zigpy.backups.NetworkBackup(),
|
||||
):
|
||||
result3 = await hass.config_entries.flow.async_configure(
|
||||
result2["flow_id"],
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={config_flow.UPLOADED_BACKUP_FILE: str(uuid.uuid4())},
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.SHOW_PROGRESS
|
||||
assert result["step_id"] == "try_ezsp_restore"
|
||||
|
||||
await hass.async_block_till_done()
|
||||
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
||||
|
||||
mock_app.backups.restore_backup.assert_called_once()
|
||||
allow_overwrite_ieee_mock.assert_not_called()
|
||||
|
||||
assert result3["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result3["data"][CONF_RADIO_TYPE] == "znp"
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result["data"][CONF_RADIO_TYPE] == "znp"
|
||||
|
||||
|
||||
@patch("homeassistant.components.zha.radio_manager._allow_overwrite_ezsp_ieee")
|
||||
async def test_formation_strategy_restore_manual_backup_overwrite_ieee_ezsp(
|
||||
allow_overwrite_ieee_mock,
|
||||
allow_overwrite_ieee_mock: MagicMock,
|
||||
advanced_pick_radio: RadioPicker,
|
||||
mock_app: AsyncMock,
|
||||
backup,
|
||||
backup: zigpy.backups.NetworkBackup,
|
||||
hass: HomeAssistant,
|
||||
) -> None:
|
||||
"""Test restoring a manual backup on EZSP coordinators (overwrite IEEE)."""
|
||||
result = await advanced_pick_radio(RadioType.ezsp)
|
||||
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={"next_step_id": config_flow.FORMATION_UPLOAD_MANUAL_BACKUP},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result2["type"] is FlowResultType.FORM
|
||||
assert result2["step_id"] == "upload_manual_backup"
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "upload_manual_backup"
|
||||
|
||||
with (
|
||||
patch(
|
||||
@@ -1752,8 +1774,8 @@ async def test_formation_strategy_restore_manual_backup_overwrite_ieee_ezsp(
|
||||
],
|
||||
) as mock_restore_backup,
|
||||
):
|
||||
result3 = await hass.config_entries.flow.async_configure(
|
||||
result2["flow_id"],
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={config_flow.UPLOADED_BACKUP_FILE: str(uuid.uuid4())},
|
||||
)
|
||||
|
||||
@@ -1762,16 +1784,16 @@ async def test_formation_strategy_restore_manual_backup_overwrite_ieee_ezsp(
|
||||
mock_restore_backup.reset_mock()
|
||||
|
||||
# The radio requires user confirmation for restore
|
||||
assert result3["type"] is FlowResultType.FORM
|
||||
assert result3["step_id"] == "maybe_confirm_ezsp_restore"
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "confirm_ezsp_restore"
|
||||
|
||||
result4 = await hass.config_entries.flow.async_configure(
|
||||
result3["flow_id"],
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={config_flow.OVERWRITE_COORDINATOR_IEEE: True},
|
||||
)
|
||||
|
||||
assert result4["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result4["data"][CONF_RADIO_TYPE] == "ezsp"
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result["data"][CONF_RADIO_TYPE] == "ezsp"
|
||||
|
||||
assert mock_restore_backup.call_count == 1
|
||||
assert mock_restore_backup.mock_calls[0].kwargs["overwrite_ieee"] is True
|
||||
@@ -1779,7 +1801,7 @@ async def test_formation_strategy_restore_manual_backup_overwrite_ieee_ezsp(
|
||||
|
||||
@patch("homeassistant.components.zha.radio_manager._allow_overwrite_ezsp_ieee")
|
||||
async def test_formation_strategy_restore_manual_backup_ezsp(
|
||||
allow_overwrite_ieee_mock,
|
||||
allow_overwrite_ieee_mock: MagicMock,
|
||||
advanced_pick_radio: RadioPicker,
|
||||
mock_app: AsyncMock,
|
||||
hass: HomeAssistant,
|
||||
@@ -1787,14 +1809,13 @@ async def test_formation_strategy_restore_manual_backup_ezsp(
|
||||
"""Test restoring a manual backup on EZSP coordinators (don't overwrite IEEE)."""
|
||||
result = await advanced_pick_radio(RadioType.ezsp)
|
||||
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={"next_step_id": config_flow.FORMATION_UPLOAD_MANUAL_BACKUP},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result2["type"] is FlowResultType.FORM
|
||||
assert result2["step_id"] == "upload_manual_backup"
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "upload_manual_backup"
|
||||
|
||||
with (
|
||||
patch(
|
||||
@@ -1809,8 +1830,8 @@ async def test_formation_strategy_restore_manual_backup_ezsp(
|
||||
],
|
||||
) as mock_restore_backup,
|
||||
):
|
||||
result3 = await hass.config_entries.flow.async_configure(
|
||||
result2["flow_id"],
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={config_flow.UPLOADED_BACKUP_FILE: str(uuid.uuid4())},
|
||||
)
|
||||
|
||||
@@ -1819,17 +1840,17 @@ async def test_formation_strategy_restore_manual_backup_ezsp(
|
||||
mock_restore_backup.reset_mock()
|
||||
|
||||
# The radio requires user confirmation for restore
|
||||
assert result3["type"] is FlowResultType.FORM
|
||||
assert result3["step_id"] == "maybe_confirm_ezsp_restore"
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "confirm_ezsp_restore"
|
||||
|
||||
result4 = await hass.config_entries.flow.async_configure(
|
||||
result3["flow_id"],
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
# We do not accept
|
||||
user_input={config_flow.OVERWRITE_COORDINATOR_IEEE: False},
|
||||
)
|
||||
|
||||
assert result4["type"] is FlowResultType.ABORT
|
||||
assert result4["reason"] == "cannot_restore_backup_no_ieee_confirm"
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
assert result["reason"] == "cannot_restore_backup_no_ieee_confirm"
|
||||
assert mock_restore_backup.call_count == 0
|
||||
|
||||
|
||||
@@ -1888,7 +1909,7 @@ def test_format_backup_choice() -> None:
|
||||
async def test_formation_strategy_restore_automatic_backup_ezsp(
|
||||
advanced_pick_radio: RadioPicker,
|
||||
mock_app: AsyncMock,
|
||||
make_backup,
|
||||
make_backup: Callable[..., zigpy.backups.NetworkBackup],
|
||||
hass: HomeAssistant,
|
||||
) -> None:
|
||||
"""Test restoring an automatic backup (EZSP radio)."""
|
||||
@@ -1901,26 +1922,31 @@ async def test_formation_strategy_restore_automatic_backup_ezsp(
|
||||
backup.is_compatible_with = MagicMock(return_value=False)
|
||||
|
||||
result = await advanced_pick_radio(RadioType.ezsp)
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={"next_step_id": (config_flow.FORMATION_CHOOSE_AUTOMATIC_BACKUP)},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result2["type"] is FlowResultType.FORM
|
||||
assert result2["step_id"] == "choose_automatic_backup"
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "choose_automatic_backup"
|
||||
|
||||
result3 = await hass.config_entries.flow.async_configure(
|
||||
result2["flow_id"],
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={
|
||||
config_flow.CHOOSE_AUTOMATIC_BACKUP: "choice:" + repr(backup),
|
||||
},
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.SHOW_PROGRESS
|
||||
assert result["step_id"] == "try_ezsp_restore"
|
||||
|
||||
await hass.async_block_till_done()
|
||||
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
||||
|
||||
mock_app.backups.restore_backup.assert_called_once()
|
||||
|
||||
assert result3["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result3["data"][CONF_RADIO_TYPE] == "ezsp"
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result["data"][CONF_RADIO_TYPE] == "ezsp"
|
||||
|
||||
|
||||
@patch(
|
||||
@@ -1930,7 +1956,7 @@ async def test_formation_strategy_restore_automatic_backup_ezsp(
|
||||
@patch("homeassistant.components.zha.async_setup_entry", AsyncMock(return_value=True))
|
||||
@pytest.mark.parametrize("is_advanced", [True, False])
|
||||
async def test_formation_strategy_restore_automatic_backup_non_ezsp(
|
||||
is_advanced,
|
||||
is_advanced: bool,
|
||||
advanced_pick_radio: RadioPicker,
|
||||
mock_app: AsyncMock,
|
||||
make_backup,
|
||||
@@ -1951,38 +1977,45 @@ async def test_formation_strategy_restore_automatic_backup_non_ezsp(
|
||||
"homeassistant.config_entries.ConfigFlow.show_advanced_options",
|
||||
new_callable=PropertyMock(return_value=is_advanced),
|
||||
):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={
|
||||
"next_step_id": (config_flow.FORMATION_CHOOSE_AUTOMATIC_BACKUP)
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result2["type"] is FlowResultType.FORM
|
||||
assert result2["step_id"] == "choose_automatic_backup"
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "choose_automatic_backup"
|
||||
|
||||
# We don't prompt for overwriting the IEEE address, since only EZSP needs this
|
||||
assert config_flow.OVERWRITE_COORDINATOR_IEEE not in result2["data_schema"].schema
|
||||
data_schema = result["data_schema"]
|
||||
assert data_schema is not None
|
||||
assert config_flow.OVERWRITE_COORDINATOR_IEEE not in data_schema.schema
|
||||
|
||||
# The backup choices are ordered by date
|
||||
assert result2["data_schema"].schema["choose_automatic_backup"].container == [
|
||||
assert data_schema.schema["choose_automatic_backup"].container == [
|
||||
f"choice:{mock_app.backups.backups[0]!r}",
|
||||
f"choice:{mock_app.backups.backups[2]!r}",
|
||||
f"choice:{mock_app.backups.backups[1]!r}",
|
||||
]
|
||||
|
||||
result3 = await hass.config_entries.flow.async_configure(
|
||||
result2["flow_id"],
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={
|
||||
config_flow.CHOOSE_AUTOMATIC_BACKUP: f"choice:{backup!r}",
|
||||
},
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.SHOW_PROGRESS
|
||||
assert result["step_id"] == "try_ezsp_restore"
|
||||
|
||||
await hass.async_block_till_done()
|
||||
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
||||
|
||||
mock_app.backups.restore_backup.assert_called_once_with(backup)
|
||||
|
||||
assert result3["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result3["data"][CONF_RADIO_TYPE] == "znp"
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result["data"][CONF_RADIO_TYPE] == "znp"
|
||||
|
||||
|
||||
@patch("homeassistant.components.zha.async_setup_entry", return_value=True)
|
||||
@@ -2047,7 +2080,9 @@ async def test_options_flow_creates_backup(
|
||||
)
|
||||
@patch("homeassistant.components.zha.async_setup_entry", return_value=True)
|
||||
async def test_options_flow_defaults(
|
||||
async_setup_entry, async_unload_effect, hass: HomeAssistant
|
||||
async_setup_entry: AsyncMock,
|
||||
async_unload_effect: Exception | bool,
|
||||
hass: HomeAssistant,
|
||||
) -> None:
|
||||
"""Test options flow defaults match radio defaults."""
|
||||
|
||||
@@ -2065,20 +2100,18 @@ async def test_options_flow_defaults(
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
result = await hass.config_entries.options.async_init(entry.entry_id)
|
||||
|
||||
flow = await hass.config_entries.options.async_init(entry.entry_id)
|
||||
|
||||
async_setup_entry.reset_mock()
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "init"
|
||||
|
||||
# ZHA gets unloaded
|
||||
with patch(
|
||||
"homeassistant.config_entries.ConfigEntries.async_unload",
|
||||
side_effect=[async_unload_effect],
|
||||
) as mock_async_unload:
|
||||
result1 = await hass.config_entries.options.async_configure(
|
||||
flow["flow_id"], user_input={}
|
||||
result = await hass.config_entries.options.async_configure(
|
||||
result["flow_id"], user_input={}
|
||||
)
|
||||
|
||||
mock_async_unload.assert_called_once_with(entry.entry_id)
|
||||
@@ -2087,41 +2120,76 @@ async def test_options_flow_defaults(
|
||||
entry.mock_state(hass, ConfigEntryState.NOT_LOADED)
|
||||
|
||||
# Reconfigure ZHA
|
||||
assert result1["step_id"] == "prompt_migrate_or_reconfigure"
|
||||
result2 = await hass.config_entries.options.async_configure(
|
||||
flow["flow_id"],
|
||||
assert result["type"] is FlowResultType.MENU
|
||||
assert result["step_id"] == "prompt_migrate_or_reconfigure"
|
||||
|
||||
result = await hass.config_entries.options.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={"next_step_id": config_flow.OPTIONS_INTENT_RECONFIGURE},
|
||||
)
|
||||
|
||||
# Current path is the default
|
||||
assert result2["step_id"] == "choose_serial_port"
|
||||
assert "/dev/ttyUSB0" in result2["data_schema"]({})[CONF_DEVICE_PATH]
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "choose_serial_port"
|
||||
data_schema = result["data_schema"]
|
||||
assert data_schema is not None
|
||||
assert "/dev/ttyUSB0" in data_schema({})[CONF_DEVICE_PATH]
|
||||
|
||||
# Autoprobing fails, we have to manually choose the radio type
|
||||
result3 = await hass.config_entries.options.async_configure(
|
||||
flow["flow_id"], user_input={}
|
||||
# Abort the current flow, we'll start a new one
|
||||
hass.config_entries.options.async_abort(result["flow_id"])
|
||||
|
||||
result = await hass.config_entries.options.async_init(entry.entry_id)
|
||||
|
||||
async_setup_entry.reset_mock()
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "init"
|
||||
|
||||
result = await hass.config_entries.options.async_configure(
|
||||
result["flow_id"], user_input={}
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.MENU
|
||||
assert result["step_id"] == "prompt_migrate_or_reconfigure"
|
||||
|
||||
result = await hass.config_entries.options.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={"next_step_id": config_flow.OPTIONS_INTENT_RECONFIGURE},
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "choose_serial_port"
|
||||
|
||||
result = await hass.config_entries.options.async_configure(
|
||||
result["flow_id"], user_input={"path": "Enter Manually"}
|
||||
)
|
||||
|
||||
# Current radio type is the default
|
||||
assert result3["step_id"] == "manual_pick_radio_type"
|
||||
assert result3["data_schema"]({})[CONF_RADIO_TYPE] == RadioType.znp.description
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "manual_pick_radio_type"
|
||||
data_schema = result["data_schema"]
|
||||
assert data_schema is not None
|
||||
assert data_schema({})[CONF_RADIO_TYPE] == RadioType.znp.description
|
||||
|
||||
# Continue on to port settings
|
||||
result4 = await hass.config_entries.options.async_configure(
|
||||
flow["flow_id"],
|
||||
result = await hass.config_entries.options.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={
|
||||
CONF_RADIO_TYPE: RadioType.znp.description,
|
||||
},
|
||||
)
|
||||
|
||||
# The defaults match our current settings
|
||||
assert result4["step_id"] == "manual_port_config"
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "manual_port_config"
|
||||
assert entry.data[CONF_DEVICE] == {
|
||||
"path": "/dev/ttyUSB0",
|
||||
"baudrate": 12345,
|
||||
"flow_control": None,
|
||||
}
|
||||
assert result4["data_schema"]({}) == {
|
||||
data_schema = result["data_schema"]
|
||||
assert data_schema is not None
|
||||
assert data_schema({}) == {
|
||||
"path": "/dev/ttyUSB0",
|
||||
"baudrate": 12345,
|
||||
"flow_control": "none",
|
||||
@@ -2129,8 +2197,8 @@ async def test_options_flow_defaults(
|
||||
|
||||
with patch(f"zigpy_znp.{PROBE_FUNCTION_PATH}", AsyncMock(return_value=True)):
|
||||
# Change the serial port path
|
||||
result5 = await hass.config_entries.options.async_configure(
|
||||
flow["flow_id"],
|
||||
result = await hass.config_entries.options.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={
|
||||
# Change everything
|
||||
CONF_DEVICE_PATH: "/dev/new_serial_port",
|
||||
@@ -2140,24 +2208,32 @@ async def test_options_flow_defaults(
|
||||
)
|
||||
|
||||
# The radio has been detected, we can move on to creating the config entry
|
||||
assert result5["step_id"] == "choose_migration_strategy"
|
||||
assert result["step_id"] == "choose_migration_strategy"
|
||||
|
||||
async_setup_entry.assert_not_called()
|
||||
|
||||
result6 = await hass.config_entries.options.async_configure(
|
||||
result5["flow_id"],
|
||||
result = await hass.config_entries.options.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={"next_step_id": config_flow.MIGRATION_STRATEGY_ADVANCED},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
result7 = await hass.config_entries.options.async_configure(
|
||||
result6["flow_id"],
|
||||
assert result["type"] is FlowResultType.SHOW_PROGRESS
|
||||
assert result["step_id"] == "load_network_settings"
|
||||
|
||||
await hass.async_block_till_done()
|
||||
result = await hass.config_entries.options.async_configure(result["flow_id"])
|
||||
|
||||
assert result["type"] is FlowResultType.MENU
|
||||
assert result["step_id"] == "choose_formation_strategy"
|
||||
|
||||
result = await hass.config_entries.options.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={"next_step_id": config_flow.FORMATION_REUSE_SETTINGS},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result7["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result7["data"] == {}
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result["data"] == {}
|
||||
|
||||
# The updated entry contains correct settings
|
||||
assert entry.data == {
|
||||
@@ -2457,12 +2533,13 @@ async def test_config_flow_port_no_multiprotocol(hass: HomeAssistant) -> None:
|
||||
|
||||
|
||||
@patch("serial.tools.list_ports.comports", MagicMock(return_value=[com_port()]))
|
||||
@patch(f"zigpy_znp.{PROBE_FUNCTION_PATH}", AsyncMock(return_value=False))
|
||||
async def test_probe_wrong_firmware_installed(hass: HomeAssistant) -> None:
|
||||
"""Test auto-probing failing because the wrong firmware is installed."""
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.zha.radio_manager.ZhaRadioManager.detect_radio_type",
|
||||
return_value=ProbeResult.WRONG_FIRMWARE_INSTALLED,
|
||||
"homeassistant.components.zha.repairs.wrong_silabs_firmware.warn_on_wrong_silabs_firmware",
|
||||
return_value=True,
|
||||
):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
@@ -2474,28 +2551,65 @@ async def test_probe_wrong_firmware_installed(hass: HomeAssistant) -> None:
|
||||
},
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.SHOW_PROGRESS
|
||||
assert result["step_id"] == "detect_radio_type"
|
||||
|
||||
await hass.async_block_till_done()
|
||||
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
||||
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
assert result["reason"] == "wrong_firmware_installed"
|
||||
|
||||
|
||||
async def test_discovery_wrong_firmware_installed(hass: HomeAssistant) -> None:
|
||||
@pytest.mark.parametrize(
|
||||
("source", "discover_info"),
|
||||
[
|
||||
(
|
||||
"usb",
|
||||
UsbServiceInfo(
|
||||
device="/dev/ttyZIGBEE",
|
||||
pid="AAAA",
|
||||
vid="AAAA",
|
||||
serial_number="1234",
|
||||
description="zigbee radio",
|
||||
manufacturer="test",
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
@patch(f"zigpy_znp.{PROBE_FUNCTION_PATH}", AsyncMock(return_value=False))
|
||||
async def test_discovery_wrong_firmware_installed(
|
||||
hass: HomeAssistant,
|
||||
source: str,
|
||||
discover_info: BaseServiceInfo,
|
||||
) -> None:
|
||||
"""Test auto-probing failing because the wrong firmware is installed."""
|
||||
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.zha.radio_manager.ZhaRadioManager.detect_radio_type",
|
||||
return_value=ProbeResult.WRONG_FIRMWARE_INSTALLED,
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.onboarding.async_is_onboarded", return_value=False
|
||||
"homeassistant.components.zha.repairs.wrong_silabs_firmware.warn_on_wrong_silabs_firmware",
|
||||
return_value=True,
|
||||
),
|
||||
):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={CONF_SOURCE: "confirm"},
|
||||
data={},
|
||||
context={CONF_SOURCE: source},
|
||||
data=discover_info,
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "confirm"
|
||||
|
||||
result_confirm = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], user_input={}
|
||||
)
|
||||
|
||||
assert result_confirm["type"] is FlowResultType.SHOW_PROGRESS
|
||||
assert result_confirm["step_id"] == "detect_radio_type"
|
||||
|
||||
await hass.async_block_till_done()
|
||||
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
||||
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
assert result["reason"] == "wrong_firmware_installed"
|
||||
|
||||
@@ -2529,7 +2643,9 @@ async def test_migration_ti_cc_to_znp(
|
||||
@patch(f"zigpy_znp.{PROBE_FUNCTION_PATH}", AsyncMock(return_value=True))
|
||||
@patch("homeassistant.components.zha.async_setup_entry", AsyncMock(return_value=True))
|
||||
async def test_migration_resets_old_radio(
|
||||
hass: HomeAssistant, backup, mock_app
|
||||
hass: HomeAssistant,
|
||||
backup: zigpy.backups.NetworkBackup,
|
||||
mock_app: AsyncMock,
|
||||
) -> None:
|
||||
"""Test that the old radio is reset during migration."""
|
||||
entry = MockConfigEntry(
|
||||
@@ -2572,19 +2688,39 @@ async def test_migration_resets_old_radio(
|
||||
DOMAIN, context={"source": SOURCE_USB}, data=discovery_info
|
||||
)
|
||||
|
||||
assert result_init["type"] is FlowResultType.FORM
|
||||
assert result_init["step_id"] == "confirm"
|
||||
|
||||
result_confirm = await hass.config_entries.flow.async_configure(
|
||||
result_init["flow_id"], user_input={}
|
||||
)
|
||||
|
||||
assert result_confirm["step_id"] == "choose_migration_strategy"
|
||||
assert result_confirm["type"] is FlowResultType.SHOW_PROGRESS
|
||||
assert result_confirm["step_id"] == "detect_radio_type"
|
||||
|
||||
await hass.async_block_till_done()
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result_confirm["flow_id"]
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.MENU
|
||||
assert result["step_id"] == "choose_migration_strategy"
|
||||
|
||||
result_recommended = await hass.config_entries.flow.async_configure(
|
||||
result_confirm["flow_id"],
|
||||
user_input={"next_step_id": config_flow.MIGRATION_STRATEGY_RECOMMENDED},
|
||||
)
|
||||
|
||||
assert result_recommended["type"] is FlowResultType.ABORT
|
||||
assert result_recommended["reason"] == "reconfigure_successful"
|
||||
assert result_recommended["type"] is FlowResultType.SHOW_PROGRESS
|
||||
assert result_recommended["step_id"] == "try_ezsp_restore"
|
||||
|
||||
await hass.async_block_till_done()
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result_recommended["flow_id"]
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
assert result["reason"] == "reconfigure_successful"
|
||||
|
||||
# We reset the old radio
|
||||
assert mock_temp_radio_mgr.async_reset_adapter.call_count == 1
|
||||
@@ -2645,7 +2781,6 @@ async def test_formation_strategy_restore_manual_backup_overwrite_ieee_ezsp_writ
|
||||
advanced_strategy_result["flow_id"],
|
||||
user_input={"next_step_id": config_flow.FORMATION_UPLOAD_MANUAL_BACKUP},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert upload_backup_result["type"] is FlowResultType.FORM
|
||||
assert upload_backup_result["step_id"] == "upload_manual_backup"
|
||||
@@ -2674,7 +2809,7 @@ async def test_formation_strategy_restore_manual_backup_overwrite_ieee_ezsp_writ
|
||||
|
||||
# The radio requires user confirmation for restore
|
||||
assert confirm_restore_result["type"] is FlowResultType.FORM
|
||||
assert confirm_restore_result["step_id"] == "maybe_confirm_ezsp_restore"
|
||||
assert confirm_restore_result["step_id"] == "confirm_ezsp_restore"
|
||||
|
||||
final_result = await hass.config_entries.flow.async_configure(
|
||||
confirm_restore_result["flow_id"],
|
||||
@@ -2721,20 +2856,28 @@ async def test_migrate_setup_options_with_ignored_discovery(
|
||||
description="zigbee radio",
|
||||
manufacturer="test manufacturer",
|
||||
)
|
||||
discovery_result = await hass.config_entries.flow.async_init(
|
||||
result_discovery = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_USB}, data=discovery_info
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result_discovery["type"] is FlowResultType.FORM
|
||||
assert result_discovery["step_id"] == "confirm"
|
||||
|
||||
# Progress the discovery
|
||||
confirm_result = await hass.config_entries.flow.async_configure(
|
||||
discovery_result["flow_id"], user_input={}
|
||||
result_confirm = await hass.config_entries.flow.async_configure(
|
||||
result_discovery["flow_id"], user_input={}
|
||||
)
|
||||
|
||||
assert result_confirm["type"] is FlowResultType.SHOW_PROGRESS
|
||||
assert result_confirm["step_id"] == "detect_radio_type"
|
||||
|
||||
await hass.async_block_till_done()
|
||||
result = await hass.config_entries.flow.async_configure(result_confirm["flow_id"])
|
||||
|
||||
# We only show "setup" options, not "migrate"
|
||||
assert confirm_result["step_id"] == "choose_setup_strategy"
|
||||
assert confirm_result["menu_options"] == [
|
||||
assert result["type"] is FlowResultType.MENU
|
||||
assert result["step_id"] == "choose_setup_strategy"
|
||||
assert result["menu_options"] == [
|
||||
"setup_strategy_recommended",
|
||||
"setup_strategy_advanced",
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user