Skip to content

Commit f9e06ac

Browse files
puddlyTheJulianJES
authored andcommitted
Add progress to ZHA migration steps (home-assistant#155764)
Co-authored-by: TheJulianJES <[email protected]>
1 parent 901558b commit f9e06ac

File tree

3 files changed

+333
-88
lines changed

3 files changed

+333
-88
lines changed

homeassistant/components/zha/config_flow.py

Lines changed: 76 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from __future__ import annotations
44

55
from abc import abstractmethod
6+
import asyncio
67
import collections
78
from contextlib import suppress
89
from enum import StrEnum
@@ -38,7 +39,7 @@
3839
)
3940
from homeassistant.const import CONF_NAME
4041
from homeassistant.core import HomeAssistant, callback
41-
from homeassistant.data_entry_flow import AbortFlow
42+
from homeassistant.data_entry_flow import AbortFlow, progress_step
4243
from homeassistant.exceptions import HomeAssistantError
4344
from homeassistant.helpers.hassio import is_hassio
4445
from homeassistant.helpers.selector import FileSelector, FileSelectorConfig
@@ -179,6 +180,7 @@ class BaseZhaFlow(ConfigEntryBaseFlow):
179180
"""Mixin for common ZHA flow steps and forms."""
180181

181182
_flow_strategy: ZigbeeFlowStrategy | None = None
183+
_overwrite_ieee_during_restore: bool = False
182184
_hass: HomeAssistant
183185
_title: str
184186

@@ -188,6 +190,7 @@ def __init__(self) -> None:
188190

189191
self._hass = None # type: ignore[assignment]
190192
self._radio_mgr = ZhaRadioManager()
193+
self._restore_backup_task: asyncio.Task[None] | None = None
191194

192195
@property
193196
def hass(self) -> HomeAssistant:
@@ -460,6 +463,7 @@ async def async_step_migration_strategy_recommended(
460463
self._radio_mgr.chosen_backup = self._radio_mgr.backups[0]
461464
return await self.async_step_maybe_reset_old_radio()
462465

466+
@progress_step()
463467
async def async_step_maybe_reset_old_radio(
464468
self, user_input: dict[str, Any] | None = None
465469
) -> ConfigFlowResult:
@@ -493,7 +497,7 @@ async def async_step_maybe_reset_old_radio(
493497
# Old adapter not found or cannot connect, show prompt to plug back in
494498
return await self.async_step_plug_in_old_radio()
495499

496-
return await self.async_step_maybe_confirm_ezsp_restore()
500+
return await self.async_step_restore_backup()
497501

498502
async def async_step_plug_in_old_radio(
499503
self, user_input: dict[str, Any] | None = None
@@ -506,7 +510,7 @@ async def async_step_plug_in_old_radio(
506510
# Unless the user removes the config entry whilst we try to reset the old radio
507511
# for a few seconds and then also unplugs it, we will basically never hit this
508512
if not config_entries:
509-
return await self.async_step_maybe_confirm_ezsp_restore()
513+
return await self.async_step_restore_backup()
510514

511515
config_entry = config_entries[0]
512516
old_device_path = config_entry.data[CONF_DEVICE][CONF_DEVICE_PATH]
@@ -527,15 +531,22 @@ async def async_step_skip_reset_old_radio(
527531
self, user_input: dict[str, Any] | None = None
528532
) -> ConfigFlowResult:
529533
"""Skip resetting the old radio and continue with migration."""
530-
return await self.async_step_maybe_confirm_ezsp_restore()
534+
return await self.async_step_restore_backup()
535+
536+
async def async_step_pre_plug_in_new_radio(
537+
self, user_input: dict[str, Any] | None = None
538+
) -> ConfigFlowResult:
539+
"""Strip user_input before showing "plug in new radio" form."""
540+
# This step is necessary to prevent `user_input` from being passed through
541+
return await self.async_step_plug_in_new_radio()
531542

532543
async def async_step_plug_in_new_radio(
533544
self, user_input: dict[str, Any] | None = None
534545
) -> ConfigFlowResult:
535546
"""Prompt user to plug in the new radio if connection fails."""
536547
if user_input is not None:
537548
# User confirmed, retry now
538-
return await self.async_step_maybe_confirm_ezsp_restore()
549+
return await self.async_step_restore_backup()
539550

540551
assert self._radio_mgr.device_path is not None
541552

@@ -606,6 +617,7 @@ async def async_step_form_initial_network(
606617
# This step exists only for translations, it does nothing new
607618
return await self.async_step_form_new_network(user_input)
608619

620+
@progress_step()
609621
async def async_step_form_new_network(
610622
self, user_input: dict[str, Any] | None = None
611623
) -> ConfigFlowResult:
@@ -691,53 +703,78 @@ async def async_step_choose_automatic_backup(
691703
),
692704
)
693705

694-
async def async_step_maybe_confirm_ezsp_restore(
706+
async def async_step_restore_backup(
695707
self, user_input: dict[str, Any] | None = None
696708
) -> ConfigFlowResult:
697-
"""Confirm restore for EZSP radios that require permanent IEEE writes."""
698-
if user_input is not None:
699-
if user_input[OVERWRITE_COORDINATOR_IEEE]:
700-
# On confirmation, overwrite destructively
701-
try:
702-
await self._radio_mgr.restore_backup(overwrite_ieee=True)
703-
except HomeAssistantError:
704-
# User unplugged the new adapter, allow retry
705-
return await self.async_step_plug_in_new_radio()
706-
except CannotWriteNetworkSettings as exc:
707-
return self.async_abort(
708-
reason="cannot_restore_backup",
709-
description_placeholders={"error": str(exc)},
710-
)
711-
712-
return await self._async_create_radio_entry()
709+
"""Restore network backup to new radio."""
710+
if self._restore_backup_task is None:
711+
self._restore_backup_task = self.hass.async_create_task(
712+
self._radio_mgr.restore_backup(
713+
overwrite_ieee=self._overwrite_ieee_during_restore
714+
),
715+
"Restore backup",
716+
)
713717

714-
# On rejection, explain why we can't restore
715-
return self.async_abort(reason="cannot_restore_backup_no_ieee_confirm")
718+
if not self._restore_backup_task.done():
719+
return self.async_show_progress(
720+
step_id="restore_backup",
721+
progress_action="restore_backup",
722+
progress_task=self._restore_backup_task,
723+
)
716724

717-
# On first attempt, just try to restore nondestructively
718725
try:
719-
await self._radio_mgr.restore_backup()
726+
await self._restore_backup_task
720727
except DestructiveWriteNetworkSettings:
721-
# Restore cannot happen automatically, we need to ask for permission
722-
pass
728+
# If we cannot restore without overwriting the IEEE, ask for confirmation
729+
return self.async_show_progress_done(
730+
next_step_id="pre_confirm_ezsp_ieee_overwrite"
731+
)
723732
except HomeAssistantError:
724733
# User unplugged the new adapter, allow retry
725-
return await self.async_step_plug_in_new_radio()
734+
return self.async_show_progress_done(next_step_id="pre_plug_in_new_radio")
726735
except CannotWriteNetworkSettings as exc:
727736
return self.async_abort(
728737
reason="cannot_restore_backup",
729738
description_placeholders={"error": str(exc)},
730739
)
731-
else:
732-
return await self._async_create_radio_entry()
740+
finally:
741+
self._restore_backup_task = None
733742

734-
# If it fails, show the form
735-
return self.async_show_form(
736-
step_id="maybe_confirm_ezsp_restore",
737-
data_schema=vol.Schema(
738-
{vol.Required(OVERWRITE_COORDINATOR_IEEE, default=True): bool}
739-
),
740-
)
743+
# Otherwise, proceed to entry creation
744+
return self.async_show_progress_done(next_step_id="create_entry")
745+
746+
async def async_step_pre_confirm_ezsp_ieee_overwrite(
747+
self, user_input: dict[str, Any] | None = None
748+
) -> ConfigFlowResult:
749+
"""Strip user_input before showing confirmation form."""
750+
# This step is necessary to prevent `user_input` from being passed through
751+
return await self.async_step_confirm_ezsp_ieee_overwrite()
752+
753+
async def async_step_confirm_ezsp_ieee_overwrite(
754+
self, user_input: dict[str, Any] | None = None
755+
) -> ConfigFlowResult:
756+
"""Show confirmation form for EZSP IEEE address overwrite."""
757+
if user_input is None:
758+
return self.async_show_form(
759+
step_id="confirm_ezsp_ieee_overwrite",
760+
data_schema=vol.Schema(
761+
{vol.Required(OVERWRITE_COORDINATOR_IEEE, default=True): bool}
762+
),
763+
)
764+
765+
if not user_input[OVERWRITE_COORDINATOR_IEEE]:
766+
return self.async_abort(reason="cannot_restore_backup_no_ieee_confirm")
767+
768+
self._overwrite_ieee_during_restore = True
769+
return await self.async_step_restore_backup()
770+
771+
async def async_step_create_entry(
772+
self, user_input: dict[str, Any] | None = None
773+
) -> ConfigFlowResult:
774+
"""Create the config entry after successful setup/migration."""
775+
776+
# This step only exists so that we can create entries from other steps
777+
return await self._async_create_radio_entry()
741778

742779

743780
class ZhaConfigFlowHandler(BaseZhaFlow, ConfigFlow, domain=DOMAIN):
@@ -1091,7 +1128,7 @@ async def async_step_maybe_reset_old_radio(
10911128

10921129
# If we are reconfiguring, the old radio will not be available
10931130
if self._migration_intent is OptionsMigrationIntent.RECONFIGURE:
1094-
return await self.async_step_maybe_confirm_ezsp_restore()
1131+
return await self.async_step_restore_backup()
10951132

10961133
return await super().async_step_maybe_reset_old_radio(user_input)
10971134

homeassistant/components/zha/strings.json

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@
1616
"invalid_backup_json": "Invalid backup JSON"
1717
},
1818
"flow_title": "{name}",
19+
"progress": {
20+
"form_new_network": "Forming a new Zigbee network.\n\nWe scan for a clear network channel as part of this process, this can take a minute.",
21+
"maybe_reset_old_radio": "Resetting old adapter.\n\nYour old adapter has been backed up and is being factory reset.",
22+
"restore_backup": "Restoring network settings to new adapter.\n\nThis will take a minute."
23+
},
1924
"step": {
2025
"choose_automatic_backup": {
2126
"data": {
@@ -76,8 +81,15 @@
7681
"confirm": {
7782
"description": "Do you want to set up {name}?"
7883
},
79-
"confirm_hardware": {
80-
"description": "Do you want to set up {name}?"
84+
"confirm_ezsp_ieee_overwrite": {
85+
"data": {
86+
"overwrite_coordinator_ieee": "Permanently replace the adapter IEEE address"
87+
},
88+
"description": "Your backup has a different IEEE address than your adapter. For your network to function properly, the IEEE address of your adapter should also be changed.\n\nThis is a permanent operation.",
89+
"title": "Overwrite adapter IEEE address"
90+
},
91+
"form_new_network": {
92+
"title": "Forming new network"
8193
},
8294
"manual_pick_radio_type": {
8395
"data": {
@@ -100,15 +112,7 @@
100112
"description": "ZHA was not able to automatically detect serial port settings for your adapter. This usually is an issue with the firmware or permissions.\n\nIf you are using firmware with nonstandard settings, enter the serial port settings",
101113
"title": "Serial port settings"
102114
},
103-
"maybe_confirm_ezsp_restore": {
104-
"data": {
105-
"overwrite_coordinator_ieee": "Permanently replace the adapter IEEE address"
106-
},
107-
"description": "Your backup has a different IEEE address than your adapter. For your network to function properly, the IEEE address of your adapter should also be changed.\n\nThis is a permanent operation.",
108-
"title": "Overwrite adapter IEEE address"
109-
},
110115
"maybe_reset_old_radio": {
111-
"description": "A backup was created earlier and your old adapter is being reset as part of the migration.",
112116
"title": "Resetting old adapter"
113117
},
114118
"plug_in_new_radio": {
@@ -127,6 +131,9 @@
127131
},
128132
"title": "Old adapter not found"
129133
},
134+
"restore_backup": {
135+
"title": "Restoring network to new adapter"
136+
},
130137
"upload_manual_backup": {
131138
"data": {
132139
"uploaded_backup_file": "Upload a file"
@@ -1885,6 +1892,11 @@
18851892
"invalid_backup_json": "[%key:component::zha::config::error::invalid_backup_json%]"
18861893
},
18871894
"flow_title": "[%key:component::zha::config::flow_title%]",
1895+
"progress": {
1896+
"form_new_network": "[%key:component::zha::config::progress::form_new_network%]",
1897+
"maybe_reset_old_radio": "[%key:component::zha::config::progress::maybe_reset_old_radio%]",
1898+
"restore_backup": "[%key:component::zha::config::progress::restore_backup%]"
1899+
},
18881900
"step": {
18891901
"choose_automatic_backup": {
18901902
"data": {
@@ -1930,6 +1942,16 @@
19301942
"description": "[%key:component::zha::config::step::choose_serial_port::description%]",
19311943
"title": "[%key:component::zha::config::step::choose_serial_port::title%]"
19321944
},
1945+
"confirm_ezsp_ieee_overwrite": {
1946+
"data": {
1947+
"overwrite_coordinator_ieee": "[%key:component::zha::config::step::confirm_ezsp_ieee_overwrite::data::overwrite_coordinator_ieee%]"
1948+
},
1949+
"description": "[%key:component::zha::config::step::confirm_ezsp_ieee_overwrite::description%]",
1950+
"title": "[%key:component::zha::config::step::confirm_ezsp_ieee_overwrite::title%]"
1951+
},
1952+
"form_new_network": {
1953+
"title": "[%key:component::zha::config::step::form_new_network::title%]"
1954+
},
19331955
"init": {
19341956
"description": "A backup will be performed and ZHA will be stopped. Do you wish to continue?",
19351957
"title": "Reconfigure ZHA"
@@ -1954,12 +1976,8 @@
19541976
"description": "[%key:component::zha::config::step::manual_port_config::description%]",
19551977
"title": "[%key:component::zha::config::step::manual_port_config::title%]"
19561978
},
1957-
"maybe_confirm_ezsp_restore": {
1958-
"data": {
1959-
"overwrite_coordinator_ieee": "[%key:component::zha::config::step::maybe_confirm_ezsp_restore::data::overwrite_coordinator_ieee%]"
1960-
},
1961-
"description": "[%key:component::zha::config::step::maybe_confirm_ezsp_restore::description%]",
1962-
"title": "[%key:component::zha::config::step::maybe_confirm_ezsp_restore::title%]"
1979+
"maybe_reset_old_radio": {
1980+
"title": "[%key:component::zha::config::step::maybe_reset_old_radio::title%]"
19631981
},
19641982
"plug_in_new_radio": {
19651983
"description": "[%key:component::zha::config::step::plug_in_new_radio::description%]",
@@ -1989,6 +2007,9 @@
19892007
},
19902008
"title": "Migrate or re-configure"
19912009
},
2010+
"restore_backup": {
2011+
"title": "[%key:component::zha::config::step::restore_backup::title%]"
2012+
},
19922013
"upload_manual_backup": {
19932014
"data": {
19942015
"uploaded_backup_file": "[%key:component::zha::config::step::upload_manual_backup::data::uploaded_backup_file%]"

0 commit comments

Comments
 (0)