diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e7a72f3..000553d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,7 +11,6 @@ jobs: with: CODE_FOLDER: universal_silabs_flasher CACHE_VERSION: 2 - PYTHON_VERSION_DEFAULT: 3.8.14 PRE_COMMIT_CACHE_PATH: ~/.cache/pre-commit MINIMUM_COVERAGE_PERCENTAGE: 40 secrets: diff --git a/tests/firmwares/skyconnect_zigbee_ncp_7.4.4.0.gbl b/tests/firmwares/skyconnect_zigbee_ncp_7.4.4.0.gbl new file mode 100644 index 0000000..b02447f Binary files /dev/null and b/tests/firmwares/skyconnect_zigbee_ncp_7.4.4.0.gbl differ diff --git a/tests/test_firmware.py b/tests/test_firmware.py index 9918f34..d1a8fc5 100644 --- a/tests/test_firmware.py +++ b/tests/test_firmware.py @@ -19,6 +19,19 @@ def test_firmware_ebl_valid(): fw.get_nabucasa_metadata() +def test_firmware_gbl_valid_no_metadata(): + data = ( + FIRMWARES_DIR / "NabuCasa_EZSP_v6.10.3.0_PB32_ncp-uart-hw_115200.gbl" + ).read_bytes() + fw = firmware.parse_firmware_image(data) + + assert isinstance(fw, firmware.GBLImage) + assert fw.serialize() == data + + with pytest.raises(KeyError): + fw.get_nabucasa_metadata() + + def test_firmware_gbl_valid_with_metadata(): data = ( FIRMWARES_DIR / "NabuCasa_SkyConnect_RCP_v4.1.3_rcp-uart-hw-802154_115200.gbl" @@ -32,7 +45,8 @@ def test_firmware_gbl_valid_with_metadata(): sdk_version=Version("4.1.3"), ezsp_version=None, cpc_version=None, - fw_type=firmware.FirmwareImageType.RCP_UART_802154, + fw_type=firmware.FirmwareImageType.MULTIPAN, + fw_variant=None, ot_rcp_version=None, baudrate=None, original_json={ @@ -43,14 +57,27 @@ def test_firmware_gbl_valid_with_metadata(): ) -def test_firmware_gbl_valid_no_metadata(): - data = ( - FIRMWARES_DIR / "NabuCasa_EZSP_v6.10.3.0_PB32_ncp-uart-hw_115200.gbl" - ).read_bytes() +def test_firmware_gbl_valid_with_metadata_v2(): + data = (FIRMWARES_DIR / "skyconnect_zigbee_ncp_7.4.4.0.gbl").read_bytes() fw = firmware.parse_firmware_image(data) assert isinstance(fw, firmware.GBLImage) assert fw.serialize() == data - - with pytest.raises(KeyError): - fw.get_nabucasa_metadata() + assert fw.get_nabucasa_metadata() == firmware.NabuCasaMetadata( + metadata_version=2, + sdk_version=Version("4.4.4"), + ezsp_version=Version("7.4.4.0"), + cpc_version=None, + fw_type=firmware.FirmwareImageType.ZIGBEE_NCP, + fw_variant=None, + ot_rcp_version=None, + baudrate=115200, + original_json={ + "baudrate": 115200, + "ezsp_version": "7.4.4.0", + "fw_type": "zigbee_ncp", + "fw_variant": None, + "metadata_version": 2, + "sdk_version": "4.4.4", + }, + ) diff --git a/universal_silabs_flasher/const.py b/universal_silabs_flasher/const.py index 799f84e..201797a 100644 --- a/universal_silabs_flasher/const.py +++ b/universal_silabs_flasher/const.py @@ -2,23 +2,23 @@ class FirmwareImageType(enum.Enum): - # EmberZNet Zigbee firmware - NCP_UART_HW = "ncp-uart-hw" - - # Multi-PAN RCP Multiprotocol (via zigbeed) - RCP_UART_802154 = "rcp-uart-802154" - - # Zigbee NCP + OpenThread RCP - ZIGBEE_NCP_RCP_UART_802154 = "zigbee-ncp-rcp-uart-802154" - - # OpenThread RCP - OT_RCP = "ot-rcp" - - # Z-Wave - Z_WAVE = "z-wave" - - # Gecko Bootloader - GECKO_BOOTLOADER = "gecko-bootloader" + ZIGBEE_NCP = "zigbee_ncp" + OPENTHREAD_RCP = "openthread_rcp" + ZWAVE_NCP = "zwave_ncp" + BOOTLOADER = "bootloader" + MULTIPAN = "multipan" + + UNKNOWN = "unknown" + + +LEGACY_FIRMWARE_TYPE_REMAPPING = { + "ncp-uart-hw": FirmwareImageType.ZIGBEE_NCP, + "ncp-uart-sw": FirmwareImageType.ZIGBEE_NCP, + "rcp-uart-802154": FirmwareImageType.MULTIPAN, + "ot-rcp": FirmwareImageType.OPENTHREAD_RCP, + "z-wave": FirmwareImageType.ZWAVE_NCP, + "gecko-bootloader": FirmwareImageType.BOOTLOADER, +} class ApplicationType(enum.Enum): @@ -29,11 +29,10 @@ class ApplicationType(enum.Enum): FW_IMAGE_TYPE_TO_APPLICATION_TYPE = { - FirmwareImageType.NCP_UART_HW: ApplicationType.EZSP, - FirmwareImageType.RCP_UART_802154: ApplicationType.CPC, - FirmwareImageType.ZIGBEE_NCP_RCP_UART_802154: ApplicationType.CPC, - FirmwareImageType.OT_RCP: ApplicationType.SPINEL, - FirmwareImageType.GECKO_BOOTLOADER: ApplicationType.GECKO_BOOTLOADER, + FirmwareImageType.ZIGBEE_NCP: ApplicationType.EZSP, + FirmwareImageType.MULTIPAN: ApplicationType.CPC, + FirmwareImageType.OPENTHREAD_RCP: ApplicationType.SPINEL, + FirmwareImageType.BOOTLOADER: ApplicationType.GECKO_BOOTLOADER, } diff --git a/universal_silabs_flasher/firmware.py b/universal_silabs_flasher/firmware.py index b9dde68..93ef0c6 100644 --- a/universal_silabs_flasher/firmware.py +++ b/universal_silabs_flasher/firmware.py @@ -9,11 +9,11 @@ import zigpy.types as zigpy_t from .common import Version, pad_to_multiple -from .const import FirmwareImageType +from .const import LEGACY_FIRMWARE_TYPE_REMAPPING, FirmwareImageType _LOGGER = logging.getLogger(__name__) -NABUCASA_METADATA_VERSION = 1 +NABUCASA_METADATA_VERSION = 2 class GBLTagId(zigpy_t.enum32): @@ -72,6 +72,7 @@ class NabuCasaMetadata: cpc_version: Version | None fw_type: FirmwareImageType | None + fw_variant: str | None baudrate: int | None original_json: dict[str, typing.Any] = dataclasses.field(repr=False) @@ -108,7 +109,17 @@ def from_json(cls, obj: dict[str, typing.Any]) -> NabuCasaMetadata: cpc_version = Version(cpc_version) if fw_type := obj.pop("fw_type", None): - fw_type = FirmwareImageType(fw_type) + if fw_type in LEGACY_FIRMWARE_TYPE_REMAPPING: + fw_type = LEGACY_FIRMWARE_TYPE_REMAPPING[fw_type] + + try: + fw_type = FirmwareImageType(fw_type) + except ValueError: + _LOGGER.warning("Unknown firmware type: %r", fw_type) + fw_type = None + + if fw_variant := obj.pop("fw_variant", None): + fw_variant = fw_variant baudrate = obj.pop("baudrate", None) @@ -122,6 +133,7 @@ def from_json(cls, obj: dict[str, typing.Any]) -> NabuCasaMetadata: ot_rcp_version=ot_rcp_version, cpc_version=cpc_version, fw_type=fw_type, + fw_variant=fw_variant, baudrate=baudrate, original_json=original_json, ) diff --git a/universal_silabs_flasher/flash.py b/universal_silabs_flasher/flash.py index 43fde2c..7576386 100644 --- a/universal_silabs_flasher/flash.py +++ b/universal_silabs_flasher/flash.py @@ -295,7 +295,8 @@ async def flash( try: metadata = fw_image.get_nabucasa_metadata() - except KeyError: + except Exception: + _LOGGER.info("Failed to read firmware metadata: {exc!r}") metadata = None else: _LOGGER.info("Extracted GBL metadata: %s", metadata) @@ -343,12 +344,12 @@ async def flash( raise click.ClickException(str(e)) from e if flasher.app_type == ApplicationType.EZSP: - running_image_type = FirmwareImageType.NCP_UART_HW + running_image_type = FirmwareImageType.ZIGBEE_NCP elif flasher.app_type == ApplicationType.SPINEL: - running_image_type = FirmwareImageType.OT_RCP + running_image_type = FirmwareImageType.OPENTHREAD_RCP elif flasher.app_type == ApplicationType.CPC: # TODO: how do you distinguish RCP_UART_802154 from ZIGBEE_NCP_RCP_UART_802154? - running_image_type = FirmwareImageType.RCP_UART_802154 + running_image_type = FirmwareImageType.MULTIPAN elif flasher.app_type == ApplicationType.GECKO_BOOTLOADER: running_image_type = None else: