Skip to content

Commit

Permalink
ZHA radio migration: reset the old adapter (home-assistant#79663)
Browse files Browse the repository at this point in the history
  • Loading branch information
puddly authored and piotrbulinski committed Oct 6, 2022
1 parent 97dadc2 commit aeed765
Show file tree
Hide file tree
Showing 4 changed files with 216 additions and 65 deletions.
84 changes: 69 additions & 15 deletions homeassistant/components/zha/config_flow.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Config flow for ZHA."""
from __future__ import annotations

import asyncio
import collections
import contextlib
import copy
Expand Down Expand Up @@ -65,8 +66,16 @@
CHOOSE_AUTOMATIC_BACKUP = "choose_automatic_backup"
OVERWRITE_COORDINATOR_IEEE = "overwrite_coordinator_ieee"

OPTIONS_INTENT_MIGRATE = "intent_migrate"
OPTIONS_INTENT_RECONFIGURE = "intent_reconfigure"

UPLOADED_BACKUP_FILE = "uploaded_backup_file"

DEFAULT_ZHA_ZEROCONF_PORT = 6638
ESPHOME_API_PORT = 6053

CONNECT_DELAY_S = 1.0

_LOGGER = logging.getLogger(__name__)


Expand Down Expand Up @@ -159,6 +168,7 @@ async def _connect_zigpy_app(self) -> ControllerApplication:
yield app
finally:
await app.disconnect()
await asyncio.sleep(CONNECT_DELAY_S)

async def _restore_backup(
self, backup: zigpy.backups.NetworkBackup, **kwargs: Any
Expand Down Expand Up @@ -628,14 +638,21 @@ async def async_step_zeroconf(

# Hostname is format: livingroom.local.
local_name = discovery_info.hostname[:-1]
radio_type = discovery_info.properties.get("radio_type") or local_name
port = discovery_info.port or DEFAULT_ZHA_ZEROCONF_PORT

# Fix incorrect port for older TubesZB devices
if "tube" in local_name and port == ESPHOME_API_PORT:
port = DEFAULT_ZHA_ZEROCONF_PORT

if "radio_type" in discovery_info.properties:
self._radio_type = RadioType[discovery_info.properties["radio_type"]]
elif "efr32" in local_name:
self._radio_type = RadioType.ezsp
else:
self._radio_type = RadioType.znp

node_name = local_name[: -len(".local")]
host = discovery_info.host
port = discovery_info.port
if local_name.startswith("tube") or "efr32" in local_name:
# This is hard coded to work with legacy devices
port = 6638
device_path = f"socket://{host}:{port}"
device_path = f"socket://{discovery_info.host}:{port}"

if current_entry := await self.async_set_unique_id(node_name):
self._abort_if_unique_id_configured(
Expand All @@ -651,13 +668,6 @@ async def async_step_zeroconf(
self._title = device_path
self._device_path = device_path

if "efr32" in radio_type:
self._radio_type = RadioType.ezsp
elif "zigate" in radio_type:
self._radio_type = RadioType.zigate
else:
self._radio_type = RadioType.znp

return await self.async_step_confirm()

async def async_step_hardware(
Expand Down Expand Up @@ -720,10 +730,54 @@ async def async_step_init(
# ZHA is not running
pass

return await self.async_step_choose_serial_port()
return await self.async_step_prompt_migrate_or_reconfigure()

return self.async_show_form(step_id="init")

async def async_step_prompt_migrate_or_reconfigure(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Confirm if we are migrating adapters or just re-configuring."""

return self.async_show_menu(
step_id="prompt_migrate_or_reconfigure",
menu_options=[
OPTIONS_INTENT_RECONFIGURE,
OPTIONS_INTENT_MIGRATE,
],
)

async def async_step_intent_reconfigure(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Virtual step for when the user is reconfiguring the integration."""
return await self.async_step_choose_serial_port()

async def async_step_intent_migrate(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Confirm the user wants to reset their current radio."""

if user_input is not None:
# Reset the current adapter
async with self._connect_zigpy_app() as app:
await app.reset_network_info()

return await self.async_step_instruct_unplug()

return self.async_show_form(step_id="intent_migrate")

async def async_step_instruct_unplug(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Instruct the user to unplug the current radio, if possible."""

if user_input is not None:
# Now that the old radio is gone, we can scan for serial ports again
return await self.async_step_choose_serial_port()

return self.async_show_form(step_id="instruct_unplug")

async def _async_create_radio_entity(self):
"""Re-implementation of the base flow's final step to update the config."""
device_settings = self._device_settings.copy()
Expand Down
16 changes: 16 additions & 0 deletions homeassistant/components/zha/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,22 @@
"title": "Reconfigure ZHA",
"description": "ZHA will be stopped. Do you wish to continue?"
},
"prompt_migrate_or_reconfigure": {
"title": "Migrate or re-configure",
"description": "Are you migrating to a new radio or re-configuring the current radio?",
"menu_options": {
"intent_migrate": "Migrate to a new radio",
"intent_reconfigure": "Re-configure the current radio"
}
},
"intent_migrate": {
"title": "Migrate to a new radio",
"description": "Your old radio will be factory reset. If you are using a combined Z-Wave and Zigbee adapter like the HUSBZB-1, this will only reset the Zigbee portion.\n\nDo you wish to continue?"
},
"instruct_unplug": {
"title": "Unplug your old radio",
"description": "Your old radio has been reset. If the hardware is no longer needed, you can now unplug it."
},
"choose_serial_port": {
"title": "[%key:component::zha::config::step::choose_serial_port::title%]",
"data": {
Expand Down
39 changes: 16 additions & 23 deletions homeassistant/components/zha/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,35 +64,12 @@
"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.",
"title": "Overwrite Radio IEEE Address"
},
"pick_radio": {
"data": {
"radio_type": "Radio Type"
},
"description": "Pick a type of your Zigbee radio",
"title": "Radio Type"
},
"port_config": {
"data": {
"baudrate": "port speed",
"flow_control": "data flow control",
"path": "Serial device path"
},
"description": "Enter port specific settings",
"title": "Settings"
},
"upload_manual_backup": {
"data": {
"uploaded_backup_file": "Upload a file"
},
"description": "Restore your network settings from an uploaded backup JSON file. You can download one from a different ZHA installation from **Network Settings**, or use a Zigbee2MQTT `coordinator_backup.json` file.",
"title": "Upload a Manual Backup"
},
"user": {
"data": {
"path": "Serial Device Path"
},
"description": "Select serial port for Zigbee radio",
"title": "ZHA"
}
}
},
Expand Down Expand Up @@ -212,6 +189,14 @@
"description": "ZHA will be stopped. Do you wish to continue?",
"title": "Reconfigure ZHA"
},
"instruct_unplug": {
"description": "Your old radio has been reset. If the hardware is no longer needed, you can now unplug it.",
"title": "Unplug your old radio"
},
"intent_migrate": {
"description": "Your old radio will be factory reset. If you are using a combined Z-Wave and Zigbee adapter like the HUSBZB-1, this will only reset the Zigbee portion.\n\nDo you wish to continue?",
"title": "Migrate to a new radio"
},
"manual_pick_radio_type": {
"data": {
"radio_type": "Radio Type"
Expand All @@ -235,6 +220,14 @@
"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.",
"title": "Overwrite Radio IEEE Address"
},
"prompt_migrate_or_reconfigure": {
"description": "Are you migrating to a new radio or re-configuring the current radio?",
"menu_options": {
"intent_migrate": "Migrate to a new radio",
"intent_reconfigure": "Re-configure the current radio"
},
"title": "Migrate or re-configure"
},
"upload_manual_backup": {
"data": {
"uploaded_backup_file": "Upload a file"
Expand Down
Loading

0 comments on commit aeed765

Please sign in to comment.