Handle all firmware types for ZBT-1 and Yellow update entities (#141674)

Handle other firmware types
This commit is contained in:
puddly 2025-03-28 15:38:16 -04:00 committed by GitHub
parent 17c56208ee
commit 5283e1a39f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 163 additions and 7 deletions

View File

@ -199,7 +199,7 @@ class BaseFirmwareUpdateEntity(
# This entity is not currently associated with a device so we must manually
# give it a name
self._attr_name = f"{self._config_entry.title} Update"
self._attr_title = self.entity_description.firmware_name or "unknown"
self._attr_title = self.entity_description.firmware_name or "Unknown"
if (
self._current_firmware_info is None

View File

@ -64,6 +64,28 @@ FIRMWARE_ENTITY_DESCRIPTIONS: dict[
expected_firmware_type=ApplicationType.SPINEL,
firmware_name="OpenThread RCP",
),
ApplicationType.CPC: FirmwareUpdateEntityDescription(
key="firmware",
display_precision=0,
device_class=UpdateDeviceClass.FIRMWARE,
entity_category=EntityCategory.CONFIG,
version_parser=lambda fw: fw,
fw_type="skyconnect_multipan",
version_key="cpc_version",
expected_firmware_type=ApplicationType.CPC,
firmware_name="Multiprotocol",
),
ApplicationType.GECKO_BOOTLOADER: FirmwareUpdateEntityDescription(
key="firmware",
display_precision=0,
device_class=UpdateDeviceClass.FIRMWARE,
entity_category=EntityCategory.CONFIG,
version_parser=lambda fw: fw,
fw_type=None, # We don't want to update the bootloader
version_key="gecko_bootloader_version",
expected_firmware_type=ApplicationType.GECKO_BOOTLOADER,
firmware_name="Gecko Bootloader",
),
None: FirmwareUpdateEntityDescription(
key="firmware",
display_precision=0,
@ -86,9 +108,16 @@ def _async_create_update_entity(
) -> FirmwareUpdateEntity:
"""Create an update entity that handles firmware type changes."""
firmware_type = config_entry.data[FIRMWARE]
try:
entity_description = FIRMWARE_ENTITY_DESCRIPTIONS[
ApplicationType(firmware_type) if firmware_type is not None else None
ApplicationType(firmware_type)
]
except (KeyError, ValueError):
_LOGGER.debug(
"Unknown firmware type %r, using default entity description", firmware_type
)
entity_description = FIRMWARE_ENTITY_DESCRIPTIONS[None]
entity = FirmwareUpdateEntity(
device=config_entry.data["device"],

View File

@ -64,6 +64,28 @@ FIRMWARE_ENTITY_DESCRIPTIONS: dict[
expected_firmware_type=ApplicationType.SPINEL,
firmware_name="OpenThread RCP",
),
ApplicationType.CPC: FirmwareUpdateEntityDescription(
key="firmware",
display_precision=0,
device_class=UpdateDeviceClass.FIRMWARE,
entity_category=EntityCategory.CONFIG,
version_parser=lambda fw: fw,
fw_type="yellow_multipan",
version_key="cpc_version",
expected_firmware_type=ApplicationType.CPC,
firmware_name="Multiprotocol",
),
ApplicationType.GECKO_BOOTLOADER: FirmwareUpdateEntityDescription(
key="firmware",
display_precision=0,
device_class=UpdateDeviceClass.FIRMWARE,
entity_category=EntityCategory.CONFIG,
version_parser=lambda fw: fw,
fw_type=None, # We don't want to update the bootloader
version_key="gecko_bootloader_version",
expected_firmware_type=ApplicationType.GECKO_BOOTLOADER,
firmware_name="Gecko Bootloader",
),
None: FirmwareUpdateEntityDescription(
key="radio_firmware",
display_precision=0,
@ -86,9 +108,16 @@ def _async_create_update_entity(
) -> FirmwareUpdateEntity:
"""Create an update entity that handles firmware type changes."""
firmware_type = config_entry.data[FIRMWARE]
try:
entity_description = FIRMWARE_ENTITY_DESCRIPTIONS[
ApplicationType(firmware_type) if firmware_type is not None else None
ApplicationType(firmware_type)
]
except (KeyError, ValueError):
_LOGGER.debug(
"Unknown firmware type %r, using default entity description", firmware_type
)
entity_description = FIRMWARE_ENTITY_DESCRIPTIONS[None]
entity = FirmwareUpdateEntity(
device=RADIO_DEVICE,

View File

@ -1,5 +1,7 @@
"""Test SkyConnect firmware update entity."""
import pytest
from homeassistant.components.homeassistant_hardware.helpers import (
async_notify_firmware_info,
)
@ -84,3 +86,47 @@ async def test_zbt1_update_entity(hass: HomeAssistant) -> None:
assert state_spinel.attributes["title"] == "OpenThread RCP"
assert state_spinel.attributes["installed_version"] == "2.4.4.0"
assert state_spinel.attributes["latest_version"] is None
@pytest.mark.parametrize(
("firmware", "version", "expected"),
[
("ezsp", "7.3.1.0 build 0", "EmberZNet Zigbee 7.3.1.0"),
("spinel", "SL-OPENTHREAD/2.4.4.0_GitHub-7074a43e4", "OpenThread RCP 2.4.4.0"),
("bootloader", "2.4.2", "Gecko Bootloader 2.4.2"),
("cpc", "4.3.2", "Multiprotocol 4.3.2"),
("router", "1.2.3.4", "Unknown 1.2.3.4"), # Not supported but still shown
],
)
async def test_zbt1_update_entity_state(
hass: HomeAssistant, firmware: str, version: str, expected: str
) -> None:
"""Test the ZBT-1 firmware update entity with different firmware types."""
await async_setup_component(hass, "homeassistant", {})
zbt1_config_entry = MockConfigEntry(
domain="homeassistant_sky_connect",
data={
"firmware": firmware,
"firmware_version": version,
"device": USB_DATA_ZBT1.device,
"manufacturer": USB_DATA_ZBT1.manufacturer,
"pid": USB_DATA_ZBT1.pid,
"product": USB_DATA_ZBT1.description,
"serial_number": USB_DATA_ZBT1.serial_number,
"vid": USB_DATA_ZBT1.vid,
},
version=1,
minor_version=3,
)
zbt1_config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(zbt1_config_entry.entry_id)
await hass.async_block_till_done()
state = hass.states.get(UPDATE_ENTITY_ID)
assert state is not None
assert (
f"{state.attributes['title']} {state.attributes['installed_version']}"
== expected
)

View File

@ -2,6 +2,8 @@
from unittest.mock import patch
import pytest
from homeassistant.components.homeassistant_hardware.helpers import (
async_notify_firmware_info,
)
@ -90,3 +92,53 @@ async def test_yellow_update_entity(hass: HomeAssistant) -> None:
assert state_spinel.attributes["title"] == "OpenThread RCP"
assert state_spinel.attributes["installed_version"] == "2.4.4.0"
assert state_spinel.attributes["latest_version"] is None
@pytest.mark.parametrize(
("firmware", "version", "expected"),
[
("ezsp", "7.3.1.0 build 0", "EmberZNet Zigbee 7.3.1.0"),
("spinel", "SL-OPENTHREAD/2.4.4.0_GitHub-7074a43e4", "OpenThread RCP 2.4.4.0"),
("bootloader", "2.4.2", "Gecko Bootloader 2.4.2"),
("cpc", "4.3.2", "Multiprotocol 4.3.2"),
("router", "1.2.3.4", "Unknown 1.2.3.4"), # Not supported but still shown
],
)
async def test_yellow_update_entity_state(
hass: HomeAssistant, firmware: str, version: str, expected: str
) -> None:
"""Test the Yellow firmware update entity with different firmware types."""
await async_setup_component(hass, "homeassistant", {})
# Set up the Yellow integration
yellow_config_entry = MockConfigEntry(
title="Home Assistant Yellow",
domain="homeassistant_yellow",
data={
"firmware": firmware,
"firmware_version": version,
"device": RADIO_DEVICE,
},
version=1,
minor_version=3,
)
yellow_config_entry.add_to_hass(hass)
with (
patch(
"homeassistant.components.homeassistant_yellow.is_hassio", return_value=True
),
patch(
"homeassistant.components.homeassistant_yellow.get_os_info",
return_value={"board": "yellow"},
),
):
assert await hass.config_entries.async_setup(yellow_config_entry.entry_id)
await hass.async_block_till_done()
state = hass.states.get(UPDATE_ENTITY_ID)
assert state is not None
assert (
f"{state.attributes['title']} {state.attributes['installed_version']}"
== expected
)