From 088f2f60d7441549bc3d1aa57433462a93df7165 Mon Sep 17 00:00:00 2001 From: Tim Lunn Date: Thu, 14 Dec 2023 12:00:36 +1100 Subject: [PATCH 1/7] Remove duplicate ctx flasher setup --- universal_silabs_flasher/flash.py | 33 +++++++++++-------------------- 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/universal_silabs_flasher/flash.py b/universal_silabs_flasher/flash.py index ce5e05d..3b1f000 100644 --- a/universal_silabs_flasher/flash.py +++ b/universal_silabs_flasher/flash.py @@ -156,6 +156,17 @@ def main( " (see `--help`)" ) + # To maintain some backwards compatibility, make `--device` required only when we + # are actually invoking a command that interacts with a device + if ctx.get_parameter_source( + "device" + ) == click.core.ParameterSource.DEFAULT and ctx.invoked_subcommand not in ( + dump_gbl_metadata.name + ): + # Replicate the "Error: Missing option" traceback + param = next(p for p in ctx.command.params if p.name == "device") + raise click.MissingParameter(ctx=ctx, param=param) + ctx.obj = { "verbosity": verbose, "flasher": Flasher( @@ -169,28 +180,6 @@ def main( probe_methods=probe_method, ), } - # To maintain some backwards compatibility, make `--device` required only when we - # are actually invoking a command that interacts with a device - if ctx.get_parameter_source( - "device" - ) == click.core.ParameterSource.DEFAULT and ctx.invoked_subcommand not in ( - dump_gbl_metadata.name - ): - # Replicate the "Error: Missing option" traceback - param = next(p for p in ctx.command.params if p.name == "device") - raise click.MissingParameter(ctx=ctx, param=param) - - ctx.obj["flasher"] = Flasher( - device=device, - baudrates={ - ApplicationType.GECKO_BOOTLOADER: bootloader_baudrate, - ApplicationType.CPC: cpc_baudrate, - ApplicationType.EZSP: ezsp_baudrate, - ApplicationType.SPINEL: spinel_baudrate, - }, - probe_methods=probe_method, - ) - @main.command() @click.pass_context From ea86dfbef532f75147bae687e3362ed47de26c99 Mon Sep 17 00:00:00 2001 From: Tim Lunn Date: Fri, 1 Dec 2023 18:37:16 +1100 Subject: [PATCH 2/7] Add a --bootloader-reset option --- universal_silabs_flasher/const.py | 5 ++++- universal_silabs_flasher/flash.py | 9 ++++++++- universal_silabs_flasher/flasher.py | 7 ++++++- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/universal_silabs_flasher/const.py b/universal_silabs_flasher/const.py index 8644787..f5a0b6f 100644 --- a/universal_silabs_flasher/const.py +++ b/universal_silabs_flasher/const.py @@ -21,7 +21,6 @@ class ApplicationType(enum.Enum): EZSP = "ezsp" SPINEL = "spinel" - FW_IMAGE_TYPE_TO_APPLICATION_TYPE = { FirmwareImageType.NCP_UART_HW: ApplicationType.EZSP, FirmwareImageType.RCP_UART_802154: ApplicationType.CPC, @@ -36,3 +35,7 @@ class ApplicationType(enum.Enum): ApplicationType.EZSP: [115200], ApplicationType.SPINEL: [460800], } + +class ResetTarget(enum.Enum): + YELLOW = "yellow" + SONOFF = "sonoff" diff --git a/universal_silabs_flasher/flash.py b/universal_silabs_flasher/flash.py index 3b1f000..25a98e4 100644 --- a/universal_silabs_flasher/flash.py +++ b/universal_silabs_flasher/flash.py @@ -18,7 +18,7 @@ import zigpy.types from .common import CommaSeparatedNumbers, patch_pyserial_asyncio, put_first -from .const import DEFAULT_BAUDRATES, FW_IMAGE_TYPE_TO_APPLICATION_TYPE, ApplicationType +from .const import DEFAULT_BAUDRATES, FW_IMAGE_TYPE_TO_APPLICATION_TYPE, ApplicationType, ResetTarget from .flasher import Flasher from .gbl import FirmwareImageType, GBLImage from .xmodemcrc import BLOCK_SIZE as XMODEM_BLOCK_SIZE, ReceiverCancelled @@ -134,6 +134,11 @@ def convert(self, value: tuple | str, param: click.Parameter, ctx: click.Context callback=click_enum_validator_factory(ApplicationType), show_default=True, ) +@click.option( + "--bootloader-reset", + type=click.Choice([t.value for t in ResetTarget]), +) + @click.pass_context def main( ctx: click.Context, @@ -145,6 +150,7 @@ def main( ezsp_baudrate: list[int], spinel_baudrate: list[int], probe_method: list[ApplicationType], + bootloader_reset: str | None, ) -> None: coloredlogs.install(level=LOG_LEVELS[min(len(LOG_LEVELS) - 1, verbose)]) @@ -178,6 +184,7 @@ def main( ApplicationType.SPINEL: spinel_baudrate, }, probe_methods=probe_method, + bootloader_reset=bootloader_reset, ), } diff --git a/universal_silabs_flasher/flasher.py b/universal_silabs_flasher/flasher.py index 3a93682..91bb3fa 100644 --- a/universal_silabs_flasher/flasher.py +++ b/universal_silabs_flasher/flasher.py @@ -11,7 +11,7 @@ import bellows.types from .common import PROBE_TIMEOUT, SerialProtocol, Version, connect_protocol -from .const import DEFAULT_BAUDRATES, ApplicationType +from .const import DEFAULT_BAUDRATES, ApplicationType, ResetTarget from .cpc import CPCProtocol from .emberznet import connect_ezsp from .gbl import GBLImage @@ -42,6 +42,7 @@ def __init__( ApplicationType.SPINEL, ), device: str, + bootloader_reset: str, ): self._baudrates = baudrates self._probe_methods = probe_methods @@ -52,6 +53,10 @@ def __init__( self.app_baudrate: int | None = None self.bootloader_baudrate: int | None = None + self._reset_target: ResetTarget | None = ( + ResetTarget(bootloader_reset) if bootloader_reset else None + ) + async def enter_yellow_bootloader(self): _LOGGER.info("Triggering Yellow bootloader") From cff88f21c0e75cb00bb1fb8c5d97cae08c76d037 Mon Sep 17 00:00:00 2001 From: Tim Lunn Date: Thu, 21 Dec 2023 12:24:31 +1100 Subject: [PATCH 3/7] Move gpio config into dict --- universal_silabs_flasher/const.py | 14 ++++++++++++++ universal_silabs_flasher/flash.py | 7 ++++++- universal_silabs_flasher/flasher.py | 26 +++++++++++--------------- 3 files changed, 31 insertions(+), 16 deletions(-) diff --git a/universal_silabs_flasher/const.py b/universal_silabs_flasher/const.py index f5a0b6f..7e17a44 100644 --- a/universal_silabs_flasher/const.py +++ b/universal_silabs_flasher/const.py @@ -21,6 +21,7 @@ class ApplicationType(enum.Enum): EZSP = "ezsp" SPINEL = "spinel" + FW_IMAGE_TYPE_TO_APPLICATION_TYPE = { FirmwareImageType.NCP_UART_HW: ApplicationType.EZSP, FirmwareImageType.RCP_UART_802154: ApplicationType.CPC, @@ -36,6 +37,19 @@ class ApplicationType(enum.Enum): ApplicationType.SPINEL: [460800], } + class ResetTarget(enum.Enum): YELLOW = "yellow" SONOFF = "sonoff" + + +GPIO_CONFIGS = { + ResetTarget.YELLOW: { + "chip": "/dev/gpiochip0", + "pin_states": { + 24: [True, False, False, True], + 25: [True, False, True, True], + }, + "toggle_delay": 0.1, + }, +} diff --git a/universal_silabs_flasher/flash.py b/universal_silabs_flasher/flash.py index 25a98e4..8b42507 100644 --- a/universal_silabs_flasher/flash.py +++ b/universal_silabs_flasher/flash.py @@ -18,7 +18,12 @@ import zigpy.types from .common import CommaSeparatedNumbers, patch_pyserial_asyncio, put_first -from .const import DEFAULT_BAUDRATES, FW_IMAGE_TYPE_TO_APPLICATION_TYPE, ApplicationType, ResetTarget +from .const import ( + DEFAULT_BAUDRATES, + FW_IMAGE_TYPE_TO_APPLICATION_TYPE, + ApplicationType, + ResetTarget, +) from .flasher import Flasher from .gbl import FirmwareImageType, GBLImage from .xmodemcrc import BLOCK_SIZE as XMODEM_BLOCK_SIZE, ReceiverCancelled diff --git a/universal_silabs_flasher/flasher.py b/universal_silabs_flasher/flasher.py index 91bb3fa..d88db5e 100644 --- a/universal_silabs_flasher/flasher.py +++ b/universal_silabs_flasher/flasher.py @@ -11,7 +11,7 @@ import bellows.types from .common import PROBE_TIMEOUT, SerialProtocol, Version, connect_protocol -from .const import DEFAULT_BAUDRATES, ApplicationType, ResetTarget +from .const import DEFAULT_BAUDRATES, GPIO_CONFIGS, ApplicationType, ResetTarget from .cpc import CPCProtocol from .emberznet import connect_ezsp from .gbl import GBLImage @@ -57,21 +57,17 @@ def __init__( ResetTarget(bootloader_reset) if bootloader_reset else None ) - async def enter_yellow_bootloader(self): - _LOGGER.info("Triggering Yellow bootloader") - - await send_gpio_pattern( - chip="/dev/gpiochip0", - pin_states={ - 24: [True, False, False, True], - 25: [True, False, True, True], - }, - toggle_delay=0.1, - ) - - async def enter_sonoff_bootloader(self): - _LOGGER.info("Triggering Sonoff bootloader") + async def enter_bootloader_reset(self, target): + _LOGGER.info(f"Triggering {target.value} bootloader") + if target in GPIO_CONFIGS.keys(): + config = GPIO_CONFIGS[target] + await send_gpio_pattern( + config["chip"], config["pin_states"], config["toggle_delay"] + ) + else: + await self.enter_serial_bootloader() + async def enter_serial_bootloader(self): baudrate = self._baudrates[ApplicationType.GECKO_BOOTLOADER][0] async with connect_protocol(self._device, baudrate, SerialProtocol) as sonoff: serial = sonoff._transport.serial From 3ea81ad50b72bb8809bfea86faa9bfd7f69081c4 Mon Sep 17 00:00:00 2001 From: Tim Lunn Date: Fri, 1 Dec 2023 22:49:28 +1100 Subject: [PATCH 4/7] Replace individual reset flags with new bootloader-reset option --- universal_silabs_flasher/flash.py | 16 ++++++++++++---- universal_silabs_flasher/flasher.py | 17 +++++------------ 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/universal_silabs_flasher/flash.py b/universal_silabs_flasher/flash.py index 8b42507..401b3ba 100644 --- a/universal_silabs_flasher/flash.py +++ b/universal_silabs_flasher/flash.py @@ -317,12 +317,20 @@ async def flash( flasher._baudrates[app_type] = put_first( flasher._baudrates[app_type], [metadata.baudrate] ) + # Maintain backward compatibility with the deprecated reset flags + reset_msg = ( + "The '%s' flag is deprecated. Use '--bootloader-reset' " + "instead, see --help for details." + ) + if yellow_gpio_reset: + flasher._reset_target = ResetTarget.YELLOW + _LOGGER.info(reset_msg, "--yellow-gpio-reset") + elif sonoff_reset: + flasher._reset_target = ResetTarget.SONOFF + _LOGGER.info(reset_msg, "--sonoff-reset") try: - await flasher.probe_app_type( - yellow_gpio_reset=yellow_gpio_reset, - sonoff_reset=sonoff_reset, - ) + await flasher.probe_app_type() except RuntimeError as e: raise click.ClickException(str(e)) from e diff --git a/universal_silabs_flasher/flasher.py b/universal_silabs_flasher/flasher.py index d88db5e..24e9797 100644 --- a/universal_silabs_flasher/flasher.py +++ b/universal_silabs_flasher/flasher.py @@ -148,17 +148,13 @@ async def probe_spinel(self, baudrate: int) -> ProbeResult: async def probe_app_type( self, types: typing.Iterable[ApplicationType] | None = None, - *, - yellow_gpio_reset: bool = False, - sonoff_reset: bool = False, ) -> None: if types is None: types = self._probe_methods - if yellow_gpio_reset: - await self.enter_yellow_bootloader() - elif sonoff_reset: - await self.enter_sonoff_bootloader() + # Reset into bootloader + if self._reset_target: + await self.enter_bootloader_reset(self._reset_target) bootloader_probe = None @@ -207,13 +203,10 @@ async def probe_app_type( self.app_baudrate = result.baudrate break else: - if bootloader_probe and (yellow_gpio_reset or sonoff_reset): + if bootloader_probe and self._reset_target: # We have no valid application image but can still re-enter the # bootloader - if yellow_gpio_reset: - await self.enter_yellow_bootloader() - elif sonoff_reset: - await self.enter_sonoff_bootloader() + await self.enter_bootloader_reset(self._reset_target) self.app_type = ApplicationType.GECKO_BOOTLOADER self.app_version = bootloader_probe.version From 196624d1a7e33cf0fbff7499aef206bb7dc40aeb Mon Sep 17 00:00:00 2001 From: Tim Lunn Date: Fri, 1 Dec 2023 13:12:29 +1100 Subject: [PATCH 5/7] Add ihost bootloader reset --- universal_silabs_flasher/const.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/universal_silabs_flasher/const.py b/universal_silabs_flasher/const.py index 7e17a44..1cec91e 100644 --- a/universal_silabs_flasher/const.py +++ b/universal_silabs_flasher/const.py @@ -40,6 +40,7 @@ class ApplicationType(enum.Enum): class ResetTarget(enum.Enum): YELLOW = "yellow" + IHOST = "ihost" SONOFF = "sonoff" @@ -52,4 +53,12 @@ class ResetTarget(enum.Enum): }, "toggle_delay": 0.1, }, + ResetTarget.IHOST: { + "chip": "/dev/gpiochip1", + "pin_states": { + 27: [True, False, False, True], + 26: [True, False, True, True], + }, + "toggle_delay": 0.1, + }, } From 69ba1dc4f65130f6c8d00c22797f0b05b9ab4f0d Mon Sep 17 00:00:00 2001 From: Tim Lunn Date: Thu, 21 Dec 2023 12:06:20 +1100 Subject: [PATCH 6/7] Only run firmware from the bootloader if we have bootloader reset and other probe methods Fixes #52 --- universal_silabs_flasher/flasher.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/universal_silabs_flasher/flasher.py b/universal_silabs_flasher/flasher.py index 24e9797..d3be7c6 100644 --- a/universal_silabs_flasher/flasher.py +++ b/universal_silabs_flasher/flasher.py @@ -158,12 +158,14 @@ async def probe_app_type( bootloader_probe = None - # Only run firmware from the bootloader if we have other probe methods + # Only run firmware from the bootloader if we have bootloader reset and + # other probe methods only_probe_bootloader = types == [ApplicationType.GECKO_BOOTLOADER] + run_firmware = self._reset_target and not only_probe_bootloader probe_funcs = { ApplicationType.GECKO_BOOTLOADER: ( lambda baudrate: self.probe_gecko_bootloader( - run_firmware=(not only_probe_bootloader), baudrate=baudrate + run_firmware=run_firmware, baudrate=baudrate ) ), ApplicationType.CPC: self.probe_cpc, From 16622d24e7b77db6b0efff00a751c9ac06326a48 Mon Sep 17 00:00:00 2001 From: Tim Lunn Date: Sat, 23 Dec 2023 11:56:59 +1100 Subject: [PATCH 7/7] fix formatting --- universal_silabs_flasher/flash.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/universal_silabs_flasher/flash.py b/universal_silabs_flasher/flash.py index 401b3ba..2f41136 100644 --- a/universal_silabs_flasher/flash.py +++ b/universal_silabs_flasher/flash.py @@ -143,7 +143,6 @@ def convert(self, value: tuple | str, param: click.Parameter, ctx: click.Context "--bootloader-reset", type=click.Choice([t.value for t in ResetTarget]), ) - @click.pass_context def main( ctx: click.Context, @@ -193,6 +192,7 @@ def main( ), } + @main.command() @click.pass_context @click.option("--firmware", type=click.File("rb"), required=True, show_default=True)