Skip to content

Commit b91b395

Browse files
authored
Add migrate options to ZBT protocol picker (home-assistant#152532)
1 parent 472d70b commit b91b395

File tree

6 files changed

+227
-14
lines changed

6 files changed

+227
-14
lines changed

homeassistant/components/homeassistant_connect_zbt2/strings.json

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,15 @@
5353
"description": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::description%]",
5454
"menu_options": {
5555
"pick_firmware_zigbee": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::menu_options::pick_firmware_zigbee%]",
56-
"pick_firmware_thread": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::menu_options::pick_firmware_thread%]"
56+
"pick_firmware_thread": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::menu_options::pick_firmware_thread%]",
57+
"pick_firmware_zigbee_migrate": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::menu_options::pick_firmware_zigbee_migrate%]",
58+
"pick_firmware_thread_migrate": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::menu_options::pick_firmware_thread_migrate%]"
5759
},
5860
"menu_option_descriptions": {
5961
"pick_firmware_zigbee": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::menu_option_descriptions::pick_firmware_zigbee%]",
60-
"pick_firmware_thread": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::menu_option_descriptions::pick_firmware_thread%]"
62+
"pick_firmware_thread": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::menu_option_descriptions::pick_firmware_thread%]",
63+
"pick_firmware_zigbee_migrate": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::menu_option_descriptions::pick_firmware_zigbee_migrate%]",
64+
"pick_firmware_thread_migrate": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::menu_option_descriptions::pick_firmware_thread_migrate%]"
6165
}
6266
},
6367
"confirm_zigbee": {
@@ -138,11 +142,15 @@
138142
"description": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::description%]",
139143
"menu_options": {
140144
"pick_firmware_zigbee": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::menu_options::pick_firmware_zigbee%]",
141-
"pick_firmware_thread": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::menu_options::pick_firmware_thread%]"
145+
"pick_firmware_thread": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::menu_options::pick_firmware_thread%]",
146+
"pick_firmware_zigbee_migrate": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::menu_options::pick_firmware_zigbee_migrate%]",
147+
"pick_firmware_thread_migrate": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::menu_options::pick_firmware_thread_migrate%]"
142148
},
143149
"menu_option_descriptions": {
144150
"pick_firmware_zigbee": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::menu_option_descriptions::pick_firmware_zigbee%]",
145-
"pick_firmware_thread": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::menu_option_descriptions::pick_firmware_thread%]"
151+
"pick_firmware_thread": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::menu_option_descriptions::pick_firmware_thread%]",
152+
"pick_firmware_zigbee_migrate": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::menu_option_descriptions::pick_firmware_zigbee_migrate%]",
153+
"pick_firmware_thread_migrate": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::menu_option_descriptions::pick_firmware_thread_migrate%]"
146154
}
147155
},
148156
"confirm_zigbee": {

homeassistant/components/homeassistant_hardware/firmware_config_flow.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@
5050

5151
STEP_PICK_FIRMWARE_THREAD = "pick_firmware_thread"
5252
STEP_PICK_FIRMWARE_ZIGBEE = "pick_firmware_zigbee"
53+
STEP_PICK_FIRMWARE_THREAD_MIGRATE = "pick_firmware_thread_migrate"
54+
STEP_PICK_FIRMWARE_ZIGBEE_MIGRATE = "pick_firmware_zigbee_migrate"
5355

5456

5557
class PickedFirmwareType(StrEnum):
@@ -124,11 +126,23 @@ async def async_step_pick_firmware(
124126
self, user_input: dict[str, Any] | None = None
125127
) -> ConfigFlowResult:
126128
"""Pick Thread or Zigbee firmware."""
129+
# Determine if ZHA or Thread are already configured to present migrate options
130+
zha_entries = self.hass.config_entries.async_entries(ZHA_DOMAIN)
131+
otbr_entries = self.hass.config_entries.async_entries(OTBR_DOMAIN)
132+
127133
return self.async_show_menu(
128134
step_id="pick_firmware",
129135
menu_options=[
130-
STEP_PICK_FIRMWARE_ZIGBEE,
131-
STEP_PICK_FIRMWARE_THREAD,
136+
(
137+
STEP_PICK_FIRMWARE_ZIGBEE_MIGRATE
138+
if zha_entries
139+
else STEP_PICK_FIRMWARE_ZIGBEE
140+
),
141+
(
142+
STEP_PICK_FIRMWARE_THREAD_MIGRATE
143+
if otbr_entries
144+
else STEP_PICK_FIRMWARE_THREAD
145+
),
132146
],
133147
description_placeholders=self._get_translation_placeholders(),
134148
)
@@ -374,6 +388,12 @@ async def async_step_pick_firmware_zigbee(
374388
self._picked_firmware_type = PickedFirmwareType.ZIGBEE
375389
return await self.async_step_zigbee_installation_type()
376390

391+
async def async_step_pick_firmware_zigbee_migrate(
392+
self, user_input: dict[str, Any] | None = None
393+
) -> ConfigFlowResult:
394+
"""Pick Zigbee firmware. Migration is automatic."""
395+
return await self.async_step_pick_firmware_zigbee()
396+
377397
async def async_step_install_zigbee_firmware(
378398
self, user_input: dict[str, Any] | None = None
379399
) -> ConfigFlowResult:
@@ -476,6 +496,12 @@ async def async_step_pick_firmware_thread(
476496
self._picked_firmware_type = PickedFirmwareType.THREAD
477497
return await self._async_continue_picked_firmware()
478498

499+
async def async_step_pick_firmware_thread_migrate(
500+
self, user_input: dict[str, Any] | None = None
501+
) -> ConfigFlowResult:
502+
"""Pick Thread firmware. Migration is automatic."""
503+
return await self.async_step_pick_firmware_thread()
504+
479505
async def async_step_install_thread_firmware(
480506
self, user_input: dict[str, Any] | None = None
481507
) -> ConfigFlowResult:

homeassistant/components/homeassistant_hardware/strings.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,15 @@
77
"description": "You can use your {model} for a Zigbee or Thread network. Please check what type of devices you want to add to Home Assistant. You can always change this later.",
88
"menu_options": {
99
"pick_firmware_zigbee": "Use as Zigbee adapter",
10-
"pick_firmware_thread": "Use as Thread adapter"
10+
"pick_firmware_thread": "Use as Thread adapter",
11+
"pick_firmware_zigbee_migrate": "Migrate Zigbee to a new adapter",
12+
"pick_firmware_thread_migrate": "Migrate Thread to a new adapter"
1113
},
1214
"menu_option_descriptions": {
1315
"pick_firmware_zigbee": "Most common protocol.",
14-
"pick_firmware_thread": "Often used for Matter over Thread devices."
16+
"pick_firmware_thread": "Often used for Matter over Thread devices.",
17+
"pick_firmware_zigbee_migrate": "This will move your Zigbee network to the new adapter.",
18+
"pick_firmware_thread_migrate": "This will migrate your Thread Border Router to the new adapter."
1519
}
1620
},
1721
"confirm_zigbee": {

homeassistant/components/homeassistant_sky_connect/strings.json

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,15 @@
5353
"description": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::description%]",
5454
"menu_options": {
5555
"pick_firmware_zigbee": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::menu_options::pick_firmware_zigbee%]",
56-
"pick_firmware_thread": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::menu_options::pick_firmware_thread%]"
56+
"pick_firmware_thread": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::menu_options::pick_firmware_thread%]",
57+
"pick_firmware_zigbee_migrate": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::menu_options::pick_firmware_zigbee_migrate%]",
58+
"pick_firmware_thread_migrate": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::menu_options::pick_firmware_thread_migrate%]"
5759
},
5860
"menu_option_descriptions": {
5961
"pick_firmware_zigbee": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::menu_option_descriptions::pick_firmware_zigbee%]",
60-
"pick_firmware_thread": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::menu_option_descriptions::pick_firmware_thread%]"
62+
"pick_firmware_thread": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::menu_option_descriptions::pick_firmware_thread%]",
63+
"pick_firmware_zigbee_migrate": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::menu_option_descriptions::pick_firmware_zigbee_migrate%]",
64+
"pick_firmware_thread_migrate": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::menu_option_descriptions::pick_firmware_thread_migrate%]"
6165
}
6266
},
6367
"confirm_zigbee": {
@@ -138,11 +142,15 @@
138142
"description": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::description%]",
139143
"menu_options": {
140144
"pick_firmware_zigbee": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::menu_options::pick_firmware_zigbee%]",
141-
"pick_firmware_thread": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::menu_options::pick_firmware_thread%]"
145+
"pick_firmware_thread": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::menu_options::pick_firmware_thread%]",
146+
"pick_firmware_zigbee_migrate": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::menu_options::pick_firmware_zigbee_migrate%]",
147+
"pick_firmware_thread_migrate": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::menu_options::pick_firmware_thread_migrate%]"
142148
},
143149
"menu_option_descriptions": {
144150
"pick_firmware_zigbee": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::menu_option_descriptions::pick_firmware_zigbee%]",
145-
"pick_firmware_thread": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::menu_option_descriptions::pick_firmware_thread%]"
151+
"pick_firmware_thread": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::menu_option_descriptions::pick_firmware_thread%]",
152+
"pick_firmware_zigbee_migrate": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::menu_option_descriptions::pick_firmware_zigbee_migrate%]",
153+
"pick_firmware_thread_migrate": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::menu_option_descriptions::pick_firmware_thread_migrate%]"
146154
}
147155
},
148156
"confirm_zigbee": {

homeassistant/components/homeassistant_yellow/strings.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,15 @@
7676
"description": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::description%]",
7777
"menu_options": {
7878
"pick_firmware_zigbee": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::menu_options::pick_firmware_zigbee%]",
79-
"pick_firmware_thread": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::menu_options::pick_firmware_thread%]"
79+
"pick_firmware_thread": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::menu_options::pick_firmware_thread%]",
80+
"pick_firmware_zigbee_migrate": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::menu_options::pick_firmware_zigbee_migrate%]",
81+
"pick_firmware_thread_migrate": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::menu_options::pick_firmware_thread_migrate%]"
8082
},
8183
"menu_option_descriptions": {
8284
"pick_firmware_zigbee": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::menu_option_descriptions::pick_firmware_zigbee%]",
83-
"pick_firmware_thread": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::menu_option_descriptions::pick_firmware_thread%]"
85+
"pick_firmware_thread": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::menu_option_descriptions::pick_firmware_thread%]",
86+
"pick_firmware_zigbee_migrate": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::menu_option_descriptions::pick_firmware_zigbee_migrate%]",
87+
"pick_firmware_thread_migrate": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::menu_option_descriptions::pick_firmware_thread_migrate%]"
8488
}
8589
},
8690
"confirm_zigbee": {

tests/components/homeassistant_hardware/test_config_flow.py

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -976,3 +976,166 @@ async def test_options_flow_thread_to_zigbee(hass: HomeAssistant) -> None:
976976

977977
# The firmware type has been updated
978978
assert config_entry.data["firmware"] == "ezsp"
979+
980+
981+
async def test_config_flow_pick_firmware_shows_migrate_options_with_existing_zha(
982+
hass: HomeAssistant,
983+
) -> None:
984+
"""Test that migrate options are shown when ZHA entries exist."""
985+
# Create a ZHA config entry
986+
zha_entry = MockConfigEntry(
987+
domain="zha",
988+
data={"device": {"path": "/dev/ttyUSB1"}},
989+
title="ZHA",
990+
)
991+
zha_entry.add_to_hass(hass)
992+
993+
init_result = await hass.config_entries.flow.async_init(
994+
TEST_DOMAIN, context={"source": "hardware"}
995+
)
996+
997+
assert init_result["type"] is FlowResultType.MENU
998+
assert init_result["step_id"] == "pick_firmware"
999+
1000+
# Should show migrate option for Zigbee since ZHA exists (migrating from ZHA to Zigbee)
1001+
menu_options = init_result["menu_options"]
1002+
assert "pick_firmware_zigbee_migrate" in menu_options
1003+
assert "pick_firmware_thread" in menu_options # Normal option for Thread
1004+
1005+
1006+
async def test_config_flow_pick_firmware_shows_migrate_options_with_existing_otbr(
1007+
hass: HomeAssistant,
1008+
) -> None:
1009+
"""Test that migrate options are shown when OTBR entries exist."""
1010+
# Create an OTBR config entry
1011+
otbr_entry = MockConfigEntry(
1012+
domain="otbr",
1013+
data={"url": "http://192.168.1.100:8081"},
1014+
title="OpenThread Border Router",
1015+
)
1016+
otbr_entry.add_to_hass(hass)
1017+
1018+
init_result = await hass.config_entries.flow.async_init(
1019+
TEST_DOMAIN, context={"source": "hardware"}
1020+
)
1021+
1022+
assert init_result["type"] is FlowResultType.MENU
1023+
assert init_result["step_id"] == "pick_firmware"
1024+
1025+
# Should show migrate option for Thread since OTBR exists (migrating from OTBR to Thread)
1026+
menu_options = init_result["menu_options"]
1027+
assert "pick_firmware_thread_migrate" in menu_options
1028+
assert "pick_firmware_zigbee" in menu_options # Normal option for Zigbee
1029+
1030+
1031+
async def test_config_flow_pick_firmware_shows_migrate_options_with_both_existing(
1032+
hass: HomeAssistant,
1033+
) -> None:
1034+
"""Test that migrate options are shown when both ZHA and OTBR entries exist."""
1035+
# Create both ZHA and OTBR config entries
1036+
zha_entry = MockConfigEntry(
1037+
domain="zha",
1038+
data={"device": {"path": "/dev/ttyUSB1"}},
1039+
title="ZHA",
1040+
)
1041+
zha_entry.add_to_hass(hass)
1042+
1043+
otbr_entry = MockConfigEntry(
1044+
domain="otbr",
1045+
data={"url": "http://192.168.1.100:8081"},
1046+
title="OpenThread Border Router",
1047+
)
1048+
otbr_entry.add_to_hass(hass)
1049+
1050+
init_result = await hass.config_entries.flow.async_init(
1051+
TEST_DOMAIN, context={"source": "hardware"}
1052+
)
1053+
1054+
assert init_result["type"] is FlowResultType.MENU
1055+
assert init_result["step_id"] == "pick_firmware"
1056+
1057+
# Should show migrate options for both since both exist
1058+
menu_options = init_result["menu_options"]
1059+
assert "pick_firmware_zigbee_migrate" in menu_options
1060+
assert "pick_firmware_thread_migrate" in menu_options
1061+
1062+
1063+
async def test_config_flow_pick_firmware_shows_normal_options_without_existing(
1064+
hass: HomeAssistant,
1065+
) -> None:
1066+
"""Test that normal options are shown when no ZHA or OTBR entries exist."""
1067+
init_result = await hass.config_entries.flow.async_init(
1068+
TEST_DOMAIN, context={"source": "hardware"}
1069+
)
1070+
1071+
assert init_result["type"] is FlowResultType.MENU
1072+
assert init_result["step_id"] == "pick_firmware"
1073+
1074+
# Should show normal options since no existing entries
1075+
menu_options = init_result["menu_options"]
1076+
assert "pick_firmware_zigbee" in menu_options
1077+
assert "pick_firmware_thread" in menu_options
1078+
assert "pick_firmware_zigbee_migrate" not in menu_options
1079+
assert "pick_firmware_thread_migrate" not in menu_options
1080+
1081+
1082+
async def test_config_flow_zigbee_migrate_handler(hass: HomeAssistant) -> None:
1083+
"""Test that the Zigbee migrate handler works correctly."""
1084+
# Ensure Zigbee migrate option is available by adding a ZHA entry
1085+
zha_entry = MockConfigEntry(
1086+
domain="zha",
1087+
data={"device": {"path": "/dev/ttyUSB1"}},
1088+
title="ZHA",
1089+
)
1090+
zha_entry.add_to_hass(hass)
1091+
1092+
init_result = await hass.config_entries.flow.async_init(
1093+
TEST_DOMAIN, context={"source": "hardware"}
1094+
)
1095+
1096+
with mock_firmware_info(
1097+
hass,
1098+
probe_app_type=ApplicationType.SPINEL,
1099+
flash_app_type=ApplicationType.EZSP,
1100+
):
1101+
# Test the migrate handler directly
1102+
result = await hass.config_entries.flow.async_configure(
1103+
init_result["flow_id"],
1104+
user_input={"next_step_id": "pick_firmware_zigbee_migrate"},
1105+
)
1106+
1107+
# Should proceed to zigbee installation type (same as normal zigbee flow)
1108+
assert result["type"] is FlowResultType.MENU
1109+
assert result["step_id"] == "zigbee_installation_type"
1110+
1111+
1112+
@pytest.mark.usefixtures("addon_store_info")
1113+
async def test_config_flow_thread_migrate_handler(hass: HomeAssistant) -> None:
1114+
"""Test that the Thread migrate handler works correctly."""
1115+
# Ensure Thread migrate option is available by adding an OTBR entry
1116+
otbr_entry = MockConfigEntry(
1117+
domain="otbr",
1118+
data={"url": "http://192.168.1.100:8081"},
1119+
title="OpenThread Border Router",
1120+
)
1121+
otbr_entry.add_to_hass(hass)
1122+
1123+
init_result = await hass.config_entries.flow.async_init(
1124+
TEST_DOMAIN, context={"source": "hardware"}
1125+
)
1126+
1127+
with mock_firmware_info(
1128+
hass,
1129+
probe_app_type=ApplicationType.EZSP,
1130+
flash_app_type=ApplicationType.SPINEL,
1131+
) as (_, _):
1132+
# Test the migrate handler directly
1133+
result = await hass.config_entries.flow.async_configure(
1134+
init_result["flow_id"],
1135+
user_input={"next_step_id": "pick_firmware_thread_migrate"},
1136+
)
1137+
1138+
# Should proceed to OTBR addon installation (same as normal thread flow)
1139+
assert result["type"] is FlowResultType.SHOW_PROGRESS
1140+
assert result["progress_action"] == "install_addon"
1141+
assert result["step_id"] == "install_otbr_addon"

0 commit comments

Comments
 (0)