Add repair to migrate away from multiprotocol/Multi-PAN#168431
Add repair to migrate away from multiprotocol/Multi-PAN#168431
Conversation
Create a fixable repair issue when Yellow or SkyConnect is running multiprotocol (CPC) firmware. The repair flow reuses the existing options flow uninstall steps to revert the radio to Zigbee-only firmware and migrate ZHA automatically. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The firmware field in the config entry can be wrong (e.g. guessed as "spinel" instead of "cpc") since it is only set once during migration. Check the actual multiprotocol addon state via multi_pan_addon_using_device in addition to the firmware field to reliably detect multi-PAN usage. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The repairs platform discovery finds any module named repairs.py and tries to register it. Since homeassistant_hardware/repairs.py only contains shared helpers and the mixin (no async_create_fix_flow), the discovery fails with "Invalid repairs platform". Rename to repair_helpers.py so it is not discovered as a platform. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use universal-silabs-flasher directly instead of the flasher addon to flash Zigbee firmware during multi-PAN migration. This eliminates the dependency on the flasher addon and avoids issues with stale addon config (e.g. firmware_url being set from a previous use). The new flow: ZHA backup via multiprotocol socket, uninstall multiprotocol addon, download Zigbee firmware from Nabu Casa releases, flash directly via universal-silabs-flasher, restore ZHA backup. Also moves WaitingAddonManager from silabs_multiprotocol_addon to util to break a circular import and allow top-level imports of async_flash_silabs_firmware and ApplicationType. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Hey there @home-assistant/core, mind taking a look at this pull request as it has been labeled with an integration ( Code owner commandsCode owners of
|
|
Hey there @home-assistant/core, mind taking a look at this pull request as it has been labeled with an integration ( Code owner commandsCode owners of
|
|
Hey there @home-assistant/core, mind taking a look at this pull request as it has been labeled with an integration ( Code owner commandsCode owners of
|
The repair_helpers module imports RepairsFlow from the repairs integration. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
FYI, while not a likely some users are probably using the Multiprotocol app for Zigbee2MQTT instead of ZHA, so maybe add a note that this could in some circumstances potentially also break the user's Zigbee2MQTT setup if using this Multiprotocol app for it (as thus repair will not reconfigure Zigbee2MQTT configuration), and could also add that Zigbee2MQTT do not officially support Multiprotocol firmware https://www.zigbee2mqtt.io/guide/adapters/emberznet.html#emberznet-adapters-silicon-labs |
Update all tests that referenced the old flasher addon steps to match the new direct flashing flow (uninstall multiprotocol addon → flash zigbee firmware → flashing complete). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
| # Lazy import to avoid circular dependency | ||
| from .silabs_multiprotocol_addon import ( # noqa: PLC0415 | ||
| get_multiprotocol_addon_manager, | ||
| ) |
There was a problem hiding this comment.
Using async_flash_silabs_firmware from util created a circular dependency. I've moved WaitingAddonManager to util which resolved part of it, but that one remains.
But since it is multi-protocol related, which anyways will go at one point, seems acceptable to me. Getting rid of it would need further refactoring.
The repair flow init data is the issue context, not user form input. Passing it to async_step_uninstall_addon caused a KeyError: 'disable_multi_pan'. Drop the user_input forwarding so the uninstall confirmation form is rendered on first invocation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
@puddly @TheJulianJES I've tested this with SkyConnect/ZBT-1, but with it ZHA had a hard time to even connect to the Silicon Labs Multiprotocol app (zigbeed) 🙈 I wonder if anyone is using this on a recent Core version even. Anyhow, this PR zigpy/bellows#714 made ZHA reload work 100% reliable 🎉 . And also this migration flow. |
The Multi-PAN repair issue flow now flashes Zigbee firmware directly
instead of starting the flasher add-on. Replace the dead
`progress.start_flasher_addon` key with `progress.install_zigbee_firmware`
(using `{hardware_name}`), and add the `abort.fw_install_failed` alias
since the flow can now abort with that reason.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The shared `firmware_picker` `fw_install_failed` translation expects
`{firmware_name}`, not `{hardware_name}`, so the placeholder was never
substituted. Pass `firmware_name="Zigbee"` to align with how the
firmware_picker flow invokes the same translation.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`multi_pan_addon_using_device()` calls `async_get_addon_info()`, which can raise `AddonError` (a `HomeAssistantError`). Both Yellow and SkyConnect now invoke this unconditionally during `async_setup_entry`, so a transient Supervisor failure would crash setup with an uncaught exception. Wrap the calls and raise `ConfigEntryNotReady` instead, so setup retries cleanly — matching the existing pattern for `check_multi_pan_addon`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The options flow base class emits `install_zigbee_firmware` and `uninstall_multiprotocol_addon` as progress actions, but neither Yellow nor SkyConnect defined matching `options.progress.*` keys, so a direct options-flow run would render the dialog with unlocalized text. Add canonical translations under `silabs_multiprotocol_hardware.options .progress` and reference them from both `options.progress` and `fix_flow.progress` in Yellow and SkyConnect (replacing the inline literals previously committed for the fix flow). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
`async_flash_silabs_firmware` is documented as needing to run within a firmware update context. Both `firmware_config_flow` and the hardware update entity wrap their flash calls with `async_firmware_flashing_context`, which signals to other integrations (notably ZHA) that the hardware is in use and pauses any owners that would otherwise reclaim the serial device mid-flash. The Multi-PAN repair flow flashed the radio without that context, so a ZHA reload after the multiprotocol add-on stopped could race the flash. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`super().__init__(...)` works here because Python's MRO walks past the intervening `RepairsFlow` mixin (which has no `__init__`) and lands on the options-flow base, but it's not obvious from reading the code — Copilot already misread it as a TypeError. Calling the named options-flow class directly makes intent explicit: the diamond's options-flow side is the one that actually consumes `config_entry`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
MartinHjelmare
left a comment
There was a problem hiding this comment.
There's a related test failure.
|
Please take a look at the requested changes, and use the Ready for review button when you are done, thanks 👍 |
The Multi-PAN uninstall flow now wraps the Zigbee firmware flash in
`async_firmware_flashing_context`, which calls into
`homeassistant_hardware`'s firmware-update registry. The options-flow
tests don't load that integration, so the context manager raised
`KeyError('homeassistant_hardware')` and the flow ended in
`firmware_flash_failed` instead of completing. Patch the context to
no-op alongside the existing `async_flash_silabs_firmware` mock.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously the flow caught a bare `Exception` and logged a stack trace, which mixes intentional flow-abort signals with unexpected programmer errors and is noisier than other steps in this flow. Wrap the network-side fetch (`async_update_data`, `async_fetch_firmware`, firmware lookup) in a focused handler that converts realistic failures (`StopIteration`, `TimeoutError`, `ClientError`, `ManifestMissing`, `ValueError`) into `HomeAssistantError`, mirroring `firmware_config_flow`. `async_flash_silabs_firmware` already raises `HomeAssistantError`, so the outer handler can now catch only that and log a single line. Update the affected tests to use the matching exception types. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`async_create_multi_pan_migration_issue` always populates the issue data with `entry_id`, so reading it back with `dict.get` masks contract violations rather than surfacing them. Switch to `data["entry_id"]` in both Yellow and SkyConnect's `async_create_fix_flow`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
| async def _async_step_start_migration(self) -> ConfigFlowResult: | ||
| """Jump straight into the uninstall step of the migration flow. | ||
|
|
||
| The repair flow's init data is the issue context, not user form input, | ||
| so pass None to render the uninstall confirmation form. | ||
| """ | ||
| return await self.async_step_uninstall_addon() # type: ignore[attr-defined, no-any-return] |
There was a problem hiding this comment.
Ensure the repair flow cannot finish successfully unless the migration actually runs (otherwise the issue will be auto-deleted). Because RepairsFlowManager deletes the issue on any non-ABORT result, reusing the options-flow uninstall_addon step as-is allows the user to submit with disable_multi_pan=False and end the flow without fixing anything; consider overriding that step in the repair flow to abort/keep the issue open (or force a positive confirmation default).
|
Nikita said he had some comments for this PR in progress, right? Or did I mishear that? Edit: I mean, I think we should wait for those if that is the case. |
Proposed change
The Silicon Labs Multiprotocol app (add-on) has been deprecated since July 2025 (home-assistant/addons#3833) but still has ~4400 active installations. We would like to remove the app from the repository, but simply removing it would trigger a repair that just removes the app — potentially breaking the user's currently running Zigbee/Thread setup without guidance.
This PR adds a fixable repair issue for users still running the Multiprotocol app on Home Assistant Yellow and SkyConnect. The repair hooks into the existing migration flow so users can gracefully migrate back to single-protocol (Zigbee) firmware before the app is removed.
Detection works via two signals:
firmwarefield beingcpcmulti_pan_addon_using_device)Both are checked because the
firmwarefield can be stale/incorrect — it is only guessed once during config entry migration and is never re-evaluated. A system with the multiprotocol app running could havefirmware: spinelif OTBR was installed at migration time. This was the case in my test setup, and I guess it's likely that users manually installed apps as well. This secondary check shouldn't harm anyways.The PR also replaces the use of the Silicon Labs Flasher app during migration with direct flashing via
universal-silabs-flasher(the built-in flasher already used by the Yellow/SkyConnect firmware options flow). The old approach has issues on recent Home Assistant OS installations (Yellow not correctly detected) and stale app configuration (e.g. a previously setfirmware_urloption causing the wrong firmware to be flashed).The architecture (multiple inheritance mixin reusing the existing options flow steps in a repair flow context) is admittedly not the cleanest, but given that all of the multiprotocol code is deprecated and will eventually be removed, the goal here is just to get a guided migration path in front of users before we pull the app. Not worth a larger refactor for code that's on its way out.
Screenshots Yellow
Screenshots ZBT-1/SkyConnect
Type of change
Additional information
Checklist
ruff format homeassistant tests)If user exposed functionality or configuration variables are added/changed:
If the code communicates with devices, web services, or third-party tools:
Updated and included derived files by running:
python3 -m script.hassfest.requirements_all.txt.Updated by running
python3 -m script.gen_requirements_all.To help with the load of incoming pull requests: