Skip to content

Commit 40ebce4

Browse files
Improve Home Assistant Hardware flow (home-assistant#152451)
Co-authored-by: puddly <[email protected]>
1 parent 29914d6 commit 40ebce4

File tree

12 files changed

+1023
-183
lines changed

12 files changed

+1023
-183
lines changed

homeassistant/components/homeassistant_connect_zbt2/config_flow.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ class HomeAssistantConnectZBT2ConfigFlow(
103103

104104
VERSION = 1
105105
MINOR_VERSION = 1
106+
ZIGBEE_BAUDRATE = 460800
106107

107108
def __init__(self, *args: Any, **kwargs: Any) -> None:
108109
"""Initialize the config flow."""

homeassistant/components/homeassistant_connect_zbt2/strings.json

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,12 @@
5252
"title": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::title%]",
5353
"description": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::description%]",
5454
"menu_options": {
55-
"pick_firmware_thread": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::menu_options::pick_firmware_thread%]",
56-
"pick_firmware_zigbee": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::menu_options::pick_firmware_zigbee%]"
55+
"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%]"
57+
},
58+
"menu_option_descriptions": {
59+
"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%]"
5761
}
5862
},
5963
"confirm_zigbee": {
@@ -75,6 +79,29 @@
7579
"confirm_otbr": {
7680
"title": "[%key:component::homeassistant_hardware::firmware_picker::options::step::confirm_otbr::title%]",
7781
"description": "[%key:component::homeassistant_hardware::firmware_picker::options::step::confirm_otbr::description%]"
82+
},
83+
"zigbee_installation_type": {
84+
"title": "[%key:component::homeassistant_hardware::firmware_picker::options::step::zigbee_installation_type::title%]",
85+
"description": "[%key:component::homeassistant_hardware::firmware_picker::options::step::zigbee_installation_type::description%]",
86+
"menu_options": {
87+
"zigbee_intent_recommended": "[%key:component::homeassistant_hardware::firmware_picker::options::step::zigbee_installation_type::menu_options::zigbee_intent_recommended%]",
88+
"zigbee_intent_custom": "[%key:component::homeassistant_hardware::firmware_picker::options::step::zigbee_installation_type::menu_options::zigbee_intent_custom%]"
89+
},
90+
"menu_option_descriptions": {
91+
"zigbee_intent_recommended": "[%key:component::homeassistant_hardware::firmware_picker::options::step::zigbee_installation_type::menu_option_descriptions::zigbee_intent_recommended%]",
92+
"zigbee_intent_custom": "[%key:component::homeassistant_hardware::firmware_picker::options::step::zigbee_installation_type::menu_option_descriptions::zigbee_intent_custom%]"
93+
}
94+
},
95+
"zigbee_integration": {
96+
"title": "[%key:component::homeassistant_hardware::firmware_picker::options::step::zigbee_integration::title%]",
97+
"menu_options": {
98+
"zigbee_integration_zha": "[%key:component::homeassistant_hardware::firmware_picker::options::step::zigbee_integration::menu_options::zigbee_integration_zha%]",
99+
"zigbee_integration_other": "[%key:component::homeassistant_hardware::firmware_picker::options::step::zigbee_integration::menu_options::zigbee_integration_other%]"
100+
},
101+
"menu_option_descriptions": {
102+
"zigbee_integration_zha": "[%key:component::homeassistant_hardware::firmware_picker::options::step::zigbee_integration::menu_option_descriptions::zigbee_integration_zha%]",
103+
"zigbee_integration_other": "[%key:component::homeassistant_hardware::firmware_picker::options::step::zigbee_integration::menu_option_descriptions::zigbee_integration_other%]"
104+
}
78105
}
79106
},
80107
"error": {
@@ -112,6 +139,10 @@
112139
"menu_options": {
113140
"pick_firmware_zigbee": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::menu_options::pick_firmware_zigbee%]",
114141
"pick_firmware_thread": "[%key:component::homeassistant_hardware::firmware_picker::options::step::pick_firmware::menu_options::pick_firmware_thread%]"
142+
},
143+
"menu_option_descriptions": {
144+
"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%]"
115146
}
116147
},
117148
"confirm_zigbee": {
@@ -133,6 +164,29 @@
133164
"confirm_otbr": {
134165
"title": "[%key:component::homeassistant_hardware::firmware_picker::options::step::confirm_otbr::title%]",
135166
"description": "[%key:component::homeassistant_hardware::firmware_picker::options::step::confirm_otbr::description%]"
167+
},
168+
"zigbee_installation_type": {
169+
"title": "[%key:component::homeassistant_hardware::firmware_picker::options::step::zigbee_installation_type::title%]",
170+
"description": "[%key:component::homeassistant_hardware::firmware_picker::options::step::zigbee_installation_type::description%]",
171+
"menu_options": {
172+
"zigbee_intent_recommended": "[%key:component::homeassistant_hardware::firmware_picker::options::step::zigbee_installation_type::menu_options::zigbee_intent_recommended%]",
173+
"zigbee_intent_custom": "[%key:component::homeassistant_hardware::firmware_picker::options::step::zigbee_installation_type::menu_options::zigbee_intent_custom%]"
174+
},
175+
"menu_option_descriptions": {
176+
"zigbee_intent_recommended": "[%key:component::homeassistant_hardware::firmware_picker::options::step::zigbee_installation_type::menu_option_descriptions::zigbee_intent_recommended%]",
177+
"zigbee_intent_custom": "[%key:component::homeassistant_hardware::firmware_picker::options::step::zigbee_installation_type::menu_option_descriptions::zigbee_intent_custom%]"
178+
}
179+
},
180+
"zigbee_integration": {
181+
"title": "[%key:component::homeassistant_hardware::firmware_picker::options::step::zigbee_integration::title%]",
182+
"menu_options": {
183+
"zigbee_integration_zha": "[%key:component::homeassistant_hardware::firmware_picker::options::step::zigbee_integration::menu_options::zigbee_integration_zha%]",
184+
"zigbee_integration_other": "[%key:component::homeassistant_hardware::firmware_picker::options::step::zigbee_integration::menu_options::zigbee_integration_other%]"
185+
},
186+
"menu_option_descriptions": {
187+
"zigbee_integration_zha": "[%key:component::homeassistant_hardware::firmware_picker::options::step::zigbee_integration::menu_option_descriptions::zigbee_integration_zha%]",
188+
"zigbee_integration_other": "[%key:component::homeassistant_hardware::firmware_picker::options::step::zigbee_integration::menu_option_descriptions::zigbee_integration_other%]"
189+
}
136190
}
137191
},
138192
"abort": {

homeassistant/components/homeassistant_hardware/firmware_config_flow.py

Lines changed: 121 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from abc import ABC, abstractmethod
66
import asyncio
7+
from enum import StrEnum
78
import logging
89
from typing import Any
910

@@ -23,6 +24,7 @@
2324
ConfigEntryBaseFlow,
2425
ConfigFlow,
2526
ConfigFlowResult,
27+
FlowType,
2628
OptionsFlow,
2729
)
2830
from homeassistant.core import callback
@@ -50,11 +52,27 @@
5052
STEP_PICK_FIRMWARE_ZIGBEE = "pick_firmware_zigbee"
5153

5254

55+
class PickedFirmwareType(StrEnum):
56+
"""Firmware types that can be picked."""
57+
58+
THREAD = "thread"
59+
ZIGBEE = "zigbee"
60+
61+
62+
class ZigbeeIntegration(StrEnum):
63+
"""Zigbee integrations that can be picked."""
64+
65+
OTHER = "other"
66+
ZHA = "zha"
67+
68+
5369
class BaseFirmwareInstallFlow(ConfigEntryBaseFlow, ABC):
5470
"""Base flow to install firmware."""
5571

72+
ZIGBEE_BAUDRATE = 115200 # Default, subclasses may override
5673
_failed_addon_name: str
5774
_failed_addon_reason: str
75+
_picked_firmware_type: PickedFirmwareType
5876

5977
def __init__(self, *args: Any, **kwargs: Any) -> None:
6078
"""Instantiate base flow."""
@@ -63,6 +81,7 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:
6381
self._probed_firmware_info: FirmwareInfo | None = None
6482
self._device: str | None = None # To be set in a subclass
6583
self._hardware_name: str = "unknown" # To be set in a subclass
84+
self._zigbee_integration = ZigbeeIntegration.ZHA
6685

6786
self.addon_install_task: asyncio.Task | None = None
6887
self.addon_start_task: asyncio.Task | None = None
@@ -281,17 +300,79 @@ async def async_step_firmware_install_failed(
281300
},
282301
)
283302

284-
async def async_step_pick_firmware_zigbee(
303+
async def async_step_zigbee_installation_type(
285304
self, user_input: dict[str, Any] | None = None
286305
) -> ConfigFlowResult:
287-
"""Pick Zigbee firmware."""
306+
"""Handle the installation type step."""
307+
return self.async_show_menu(
308+
step_id="zigbee_installation_type",
309+
menu_options=[
310+
"zigbee_intent_recommended",
311+
"zigbee_intent_custom",
312+
],
313+
)
314+
315+
async def async_step_zigbee_intent_recommended(
316+
self, user_input: dict[str, Any] | None = None
317+
) -> ConfigFlowResult:
318+
"""Select recommended installation type."""
319+
self._zigbee_integration = ZigbeeIntegration.ZHA
320+
return await self._async_continue_picked_firmware()
321+
322+
async def async_step_zigbee_intent_custom(
323+
self, user_input: dict[str, Any] | None = None
324+
) -> ConfigFlowResult:
325+
"""Select custom installation type."""
326+
return await self.async_step_zigbee_integration()
327+
328+
async def async_step_zigbee_integration(
329+
self, user_input: dict[str, Any] | None = None
330+
) -> ConfigFlowResult:
331+
"""Select Zigbee integration."""
332+
return self.async_show_menu(
333+
step_id="zigbee_integration",
334+
menu_options=[
335+
"zigbee_integration_zha",
336+
"zigbee_integration_other",
337+
],
338+
)
339+
340+
async def async_step_zigbee_integration_zha(
341+
self, user_input: dict[str, Any] | None = None
342+
) -> ConfigFlowResult:
343+
"""Select ZHA integration."""
344+
self._zigbee_integration = ZigbeeIntegration.ZHA
345+
return await self._async_continue_picked_firmware()
346+
347+
async def async_step_zigbee_integration_other(
348+
self, user_input: dict[str, Any] | None = None
349+
) -> ConfigFlowResult:
350+
"""Select other Zigbee integration."""
351+
self._zigbee_integration = ZigbeeIntegration.OTHER
352+
return await self._async_continue_picked_firmware()
353+
354+
async def _async_continue_picked_firmware(self) -> ConfigFlowResult:
355+
"""Continue to the picked firmware step."""
288356
if not await self._probe_firmware_info():
289357
return self.async_abort(
290358
reason="unsupported_firmware",
291359
description_placeholders=self._get_translation_placeholders(),
292360
)
293361

294-
return await self.async_step_install_zigbee_firmware()
362+
if self._picked_firmware_type == PickedFirmwareType.ZIGBEE:
363+
return await self.async_step_install_zigbee_firmware()
364+
365+
if result := await self._ensure_thread_addon_setup():
366+
return result
367+
368+
return await self.async_step_install_thread_firmware()
369+
370+
async def async_step_pick_firmware_zigbee(
371+
self, user_input: dict[str, Any] | None = None
372+
) -> ConfigFlowResult:
373+
"""Pick Zigbee firmware."""
374+
self._picked_firmware_type = PickedFirmwareType.ZIGBEE
375+
return await self.async_step_zigbee_installation_type()
295376

296377
async def async_step_install_zigbee_firmware(
297378
self, user_input: dict[str, Any] | None = None
@@ -317,42 +398,43 @@ async def async_step_pre_confirm_zigbee(
317398
"""Pre-confirm Zigbee setup."""
318399

319400
# This step is necessary to prevent `user_input` from being passed through
320-
return await self.async_step_confirm_zigbee()
401+
return await self.async_step_continue_zigbee()
321402

322-
async def async_step_confirm_zigbee(
403+
async def async_step_continue_zigbee(
323404
self, user_input: dict[str, Any] | None = None
324405
) -> ConfigFlowResult:
325-
"""Confirm Zigbee setup."""
406+
"""Continue Zigbee setup."""
326407
assert self._device is not None
327408
assert self._hardware_name is not None
328409

329-
if user_input is None:
330-
return self.async_show_form(
331-
step_id="confirm_zigbee",
332-
description_placeholders=self._get_translation_placeholders(),
333-
)
334-
335410
if not await self._probe_firmware_info(probe_methods=(ApplicationType.EZSP,)):
336411
return self.async_abort(
337412
reason="unsupported_firmware",
338413
description_placeholders=self._get_translation_placeholders(),
339414
)
340415

341-
await self.hass.config_entries.flow.async_init(
416+
if self._zigbee_integration == ZigbeeIntegration.OTHER:
417+
return self._async_flow_finished()
418+
419+
result = await self.hass.config_entries.flow.async_init(
342420
ZHA_DOMAIN,
343421
context={"source": "hardware"},
344422
data={
345423
"name": self._hardware_name,
346424
"port": {
347425
"path": self._device,
348-
"baudrate": 115200,
426+
"baudrate": self.ZIGBEE_BAUDRATE,
349427
"flow_control": "hardware",
350428
},
351429
"radio_type": "ezsp",
352430
},
353431
)
432+
return self._continue_zha_flow(result)
354433

355-
return self._async_flow_finished()
434+
@callback
435+
def _continue_zha_flow(self, zha_result: ConfigFlowResult) -> ConfigFlowResult:
436+
"""Continue the ZHA flow."""
437+
raise NotImplementedError
356438

357439
async def _ensure_thread_addon_setup(self) -> ConfigFlowResult | None:
358440
"""Ensure the OTBR addon is set up and not running."""
@@ -391,16 +473,8 @@ async def async_step_pick_firmware_thread(
391473
self, user_input: dict[str, Any] | None = None
392474
) -> ConfigFlowResult:
393475
"""Pick Thread firmware."""
394-
if not await self._probe_firmware_info():
395-
return self.async_abort(
396-
reason="unsupported_firmware",
397-
description_placeholders=self._get_translation_placeholders(),
398-
)
399-
400-
if result := await self._ensure_thread_addon_setup():
401-
return result
402-
403-
return await self.async_step_install_thread_firmware()
476+
self._picked_firmware_type = PickedFirmwareType.THREAD
477+
return await self._async_continue_picked_firmware()
404478

405479
async def async_step_install_thread_firmware(
406480
self, user_input: dict[str, Any] | None = None
@@ -572,6 +646,21 @@ async def async_step_confirm(
572646

573647
return await self.async_step_pick_firmware()
574648

649+
@callback
650+
def _continue_zha_flow(self, zha_result: ConfigFlowResult) -> ConfigFlowResult:
651+
"""Continue the ZHA flow."""
652+
next_flow_id = zha_result["flow_id"]
653+
654+
result = self._async_flow_finished()
655+
return (
656+
self.async_create_entry(
657+
title=result["title"] or self._hardware_name,
658+
data=result["data"],
659+
next_flow=(FlowType.CONFIG_FLOW, next_flow_id),
660+
)
661+
| result # update all items with the child result
662+
)
663+
575664

576665
class BaseFirmwareOptionsFlow(BaseFirmwareInstallFlow, OptionsFlow):
577666
"""Zigbee and Thread options flow handlers."""
@@ -629,3 +718,10 @@ async def async_step_pick_firmware_thread(
629718
)
630719

631720
return await super().async_step_pick_firmware_thread(user_input)
721+
722+
@callback
723+
def _continue_zha_flow(self, zha_result: ConfigFlowResult) -> ConfigFlowResult:
724+
"""Continue the ZHA flow."""
725+
# The options flow cannot return a next_flow yet, so we just finish here.
726+
# The options flow should be changed to a reconfigure flow.
727+
return self._async_flow_finished()

homeassistant/components/homeassistant_hardware/strings.json

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,15 @@
33
"options": {
44
"step": {
55
"pick_firmware": {
6-
"title": "Pick your firmware",
7-
"description": "Let's get started with setting up your {model}. Do you want to use it to set up a Zigbee or Thread network?",
6+
"title": "Pick your protocol",
7+
"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": {
9-
"pick_firmware_zigbee": "Zigbee",
10-
"pick_firmware_thread": "Thread"
9+
"pick_firmware_zigbee": "Use as Zigbee adapter",
10+
"pick_firmware_thread": "Use as Thread adapter"
11+
},
12+
"menu_option_descriptions": {
13+
"pick_firmware_zigbee": "Most common protocol.",
14+
"pick_firmware_thread": "Often used for Matter over Thread devices."
1115
}
1216
},
1317
"confirm_zigbee": {
@@ -29,6 +33,29 @@
2933
"confirm_otbr": {
3034
"title": "OpenThread Border Router setup complete",
3135
"description": "Your {model} is now an OpenThread Border Router and will show up in the Thread integration."
36+
},
37+
"zigbee_installation_type": {
38+
"title": "Set up Zigbee",
39+
"description": "Choose the installation type for the Zigbee adapter.",
40+
"menu_options": {
41+
"zigbee_intent_recommended": "Recommended installation",
42+
"zigbee_intent_custom": "Custom"
43+
},
44+
"menu_option_descriptions": {
45+
"zigbee_intent_recommended": "Automatically install and configure Zigbee.",
46+
"zigbee_intent_custom": "Manually install and configure Zigbee, for example with Zigbee2MQTT."
47+
}
48+
},
49+
"zigbee_integration": {
50+
"title": "Select Zigbee method",
51+
"menu_options": {
52+
"zigbee_integration_zha": "Zigbee Home Automation",
53+
"zigbee_integration_other": "Other"
54+
},
55+
"menu_option_descriptions": {
56+
"zigbee_integration_zha": "Lets Home Assistant control a Zigbee network.",
57+
"zigbee_integration_other": "For example if you want to use the adapter with Zigbee2MQTT."
58+
}
3259
}
3360
},
3461
"abort": {

0 commit comments

Comments
 (0)