From 151e06cff1f153c67f8a4edd71ef6bcd030c5920 Mon Sep 17 00:00:00 2001 From: Jos Verlinde Date: Tue, 12 Mar 2024 17:18:50 +0100 Subject: [PATCH 1/3] Update flasher.py and related files --- src/mpflash/libusb_flash.ipynb | 174 ++++++++++++++++++++++++ src/mpflash/mpflash/flash_stm32_cube.py | 16 ++- src/mpflash/mpflash/flash_stm32_dfu.py | 9 +- src/mpflash/mpflash/flash_uf2.py | 3 - src/mpflash/mpflash/flasher.py | 27 +++- src/mpflash/poetry.lock | 98 ++++++++++++- src/mpflash/pyproject.toml | 6 +- 7 files changed, 309 insertions(+), 24 deletions(-) create mode 100644 src/mpflash/libusb_flash.ipynb diff --git a/src/mpflash/libusb_flash.ipynb b/src/mpflash/libusb_flash.ipynb new file mode 100644 index 00000000..3d2496ef --- /dev/null +++ b/src/mpflash/libusb_flash.ipynb @@ -0,0 +1,174 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ,\n", + " ]" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import usb.core\n", + "import usb.util\n", + "import usb.backend.libusb1 as libusb1\n", + "from pathlib import Path\n", + "import platform\n", + "\n", + "if platform.system() == \"Windows\":\n", + " # on windows you need to use the libusb1 backend\n", + " import libusb\n", + "\n", + " arch = \"x64\" if platform.architecture()[0] == \"64bit\" else \"x86\"\n", + " libusb1_dll = Path(libusb.__file__).parent / f\"_platform\\\\_windows\\\\{arch}\\\\libusb-1.0.dll\"\n", + "\n", + " backend = libusb1.get_backend(find_library=lambda x: libusb1_dll.as_posix())\n", + "usb_devices = usb.core.find(backend=backend, find_all=True)\n", + "\n", + "list(usb_devices)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "No DFU devices found\n" + ] + } + ], + "source": [ + "from mpflash.vendored import pydfu as pydfu\n", + "\n", + "try:\n", + " pydfu.list_dfu_devices()\n", + "except SystemExit:\n", + " print(\"No DFU devices found\")" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "pydfu.init()" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Read DFU file...\n", + "File: C:\\Users\\josverl\\Downloads\\firmware\\stm32\\PYBV11-THREAD-v1.23.0-preview.203.dfu\n", + " b'DfuSe' v1, image size: 365621, targets: 1\n", + " b'Target' 0, alt setting: 0, name: \"ST...\", size: 365336, elements: 2\n", + " 0, address: 0x08000000, size: 14712\n", + " 1, address: 0x08020000, size: 350608\n", + " usb: 0483:df11, device: 0x0000, dfu: 0x011a, b'UFD', 16, 0xd114e190\n" + ] + } + ], + "source": [ + "dfu_file = Path(\"C:\\\\Users\\\\josverl\\\\Downloads\\\\firmware\\\\stm32\\\\PYBV11-THREAD-v1.23.0-preview.203.dfu\")\n", + "\n", + "print(\"Read DFU file...\")\n", + "elements = pydfu.read_dfu_file(dfu_file)\n", + "if not elements:\n", + " print(\"No data in dfu file\")" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Writing memory...\n", + "0x08000000 14712 [=========================] 100% \n", + "0x08020000 350608 [=========================] 100% \n" + ] + } + ], + "source": [ + "print(\"Writing memory...\")\n", + "pydfu.write_elements(elements, False, progress=pydfu.cli_progress)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Exiting DFU...\n" + ] + } + ], + "source": [ + "print(\"Exiting DFU...\")\n", + "pydfu.exit_dfu()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.7" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/src/mpflash/mpflash/flash_stm32_cube.py b/src/mpflash/mpflash/flash_stm32_cube.py index 4e35bc98..6d6e11af 100644 --- a/src/mpflash/mpflash/flash_stm32_cube.py +++ b/src/mpflash/mpflash/flash_stm32_cube.py @@ -13,6 +13,7 @@ import bincopy from loguru import logger as log +from rich.progress import track from strip_ansi import strip_ansi from .mpremoteboard.mpremoteboard import MPRemoteBoard @@ -56,9 +57,7 @@ def flash_stm32_cubecli(mcu: MPRemoteBoard, fw_file: Path, *, erase: bool = True ) return None - log.info(f"Entering STM bootloader on {mcu.board} on {mcu.serialport}") - mcu.run_command("bootloader") - time.sleep(2) + # run STM32_Programmer_CLI.exe --list cmd = [ STM32_CLI, @@ -110,6 +109,13 @@ def flash_stm32_cubecli(mcu: MPRemoteBoard, fw_file: Path, *, erase: bool = True ] results = subprocess.run(cmd, capture_output=True, text=True).stdout.splitlines() log.success("Done flashing, resetting the board and wait for it to restart") - time.sleep(5) - mcu.get_mcu_info() + + for _ in track(range(10), description="Waiting for the board to restart", transient=True): + time.sleep(1) + try: + mcu.get_mcu_info() + break + except ConnectionError: + pass + return mcu diff --git a/src/mpflash/mpflash/flash_stm32_dfu.py b/src/mpflash/mpflash/flash_stm32_dfu.py index 592abe65..5a23e464 100644 --- a/src/mpflash/mpflash/flash_stm32_dfu.py +++ b/src/mpflash/mpflash/flash_stm32_dfu.py @@ -1,7 +1,8 @@ import sys import time -from typing import Optional from pathlib import Path +from typing import Optional + from loguru import logger as log from .mpremoteboard.mpremoteboard import MPRemoteBoard @@ -19,7 +20,7 @@ def flash_stm32_dfu( erase: bool = True, ) -> Optional[MPRemoteBoard]: - if sys.platform != "linux": + if sys.platform == "win32": log.error(f"OS {sys.platform} not supported") return None @@ -35,10 +36,6 @@ def flash_stm32_dfu( log.error(f"File {fw_file} is not a .dfu file") return None - log.info(f"Entering STM bootloader on {mcu.board} on {mcu.serialport}") - mcu.run_command("bootloader") - time.sleep(2) - kwargs = {"idVendor": 0x0483, "idProduct": 0xDF11} log.debug("List SPECIFIED DFU devices...") try: diff --git a/src/mpflash/mpflash/flash_uf2.py b/src/mpflash/mpflash/flash_uf2.py index 8203dc72..90575224 100644 --- a/src/mpflash/mpflash/flash_uf2.py +++ b/src/mpflash/mpflash/flash_uf2.py @@ -37,9 +37,6 @@ def flash_uf2(mcu: MPRemoteBoard, fw_file: Path, erase: bool) -> Optional[MPRemo if erase: log.info("Erasing not yet implemented for UF2 flashing.") - log.info(f"Entering UF2 bootloader on {mcu.board} on {mcu.serialport}") - mcu.run_command("bootloader", timeout=10) - if sys.platform == "linux": destination = wait_for_UF2_linux() elif sys.platform == "win32": diff --git a/src/mpflash/mpflash/flasher.py b/src/mpflash/mpflash/flasher.py index 5e5df3ec..1209b486 100644 --- a/src/mpflash/mpflash/flasher.py +++ b/src/mpflash/mpflash/flasher.py @@ -1,13 +1,12 @@ +import time from pathlib import Path from typing import Dict, List, Optional, Tuple import jsonlines -from mpflash.flash_stm32 import flash_stm32 import rich_click as click from loguru import logger as log -# TODO: - refactor so that we do not need the entire stubber package -from .mpremoteboard.mpremoteboard import MPRemoteBoard +from mpflash.flash_stm32 import flash_stm32 from .cli_group import cli from .common import FWInfo, clean_version @@ -16,6 +15,9 @@ from .flash_uf2 import flash_uf2 from .list import show_mcus +# TODO: - refactor so that we do not need the entire stubber package +from .mpremoteboard.mpremoteboard import MPRemoteBoard + # ######################################################################################################### @@ -213,10 +215,18 @@ def auto_update( ) @click.option( "--stm32-hex/--stm32-dfu", + "--hex/--dfu", default=True, show_default=True, help="""Use .hex (STM32CubeProgrammer CLI) or .dfu (dfu-util) to flash STM32 boards.""", ) +@click.option( + "--bootloader/--no-bootloader", + default=True, + is_flag=True, + show_default=True, + help="""Enter micropython bootloader mode before flashing.""", +) @click.option( "--preview", default=False, @@ -234,6 +244,7 @@ def cli_flash_board( erase: bool = False, preview: bool = False, stm32_hex: bool = True, + bootloader: bool = True, ): todo: WorkList = [] # firmware type selector @@ -284,10 +295,14 @@ def cli_flash_board( updated = None # try: if mcu.port in ["samd", "rp2"]: + if bootloader: + enter_bootloader(mcu) updated = flash_uf2(mcu, fw_file=fw_file, erase=erase) elif mcu.port in ["esp32", "esp8266"]: updated = flash_esp(mcu, fw_file=fw_file, erase=erase) elif mcu.port in ["stm32"]: + if bootloader: + enter_bootloader(mcu) updated = flash_stm32(mcu, fw_file, erase=erase, stm32_hex=stm32_hex) else: log.error(f"Don't know how to flash {mcu.port}-{mcu.board} on {mcu.serialport}") @@ -308,7 +323,11 @@ def cli_flash_board( show_mcus(flashed, title="Connected boards after flashing") - +def enter_bootloader(mcu: MPRemoteBoard, timeout: int = 10, wait_after: int = 2): + """Enter the bootloader mode for the board""" + log.info(f"Entering bootloader on {mcu.board} on {mcu.serialport}") + mcu.run_command("bootloader", timeout=timeout) + time.sleep(wait_after) # TODO: diff --git a/src/mpflash/poetry.lock b/src/mpflash/poetry.lock index 2a860910..63a7e802 100644 --- a/src/mpflash/poetry.lock +++ b/src/mpflash/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.0 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. [[package]] name = "argparse-addons" @@ -577,6 +577,24 @@ docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.link perf = ["ipython"] testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"] +[[package]] +name = "importlib-resources" +version = "6.1.3" +description = "Read resources from Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "importlib_resources-6.1.3-py3-none-any.whl", hash = "sha256:4c0269e3580fe2634d364b39b38b961540a7738c02cb984e98add8b4221d793d"}, + {file = "importlib_resources-6.1.3.tar.gz", hash = "sha256:56fb4525197b78544a3354ea27793952ab93f935bb4bf746b846bb1015020f2b"}, +] + +[package.dependencies] +zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["jaraco.collections", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)", "zipp (>=3.17)"] + [[package]] name = "intelhex" version = "2.3.0" @@ -602,6 +620,82 @@ files = [ [package.dependencies] attrs = ">=19.2.0" +[[package]] +name = "libusb-package" +version = "1.0.26.2" +description = "Package containing libusb so it can be installed via Python package managers" +optional = false +python-versions = ">=3.7" +files = [ + {file = "libusb_package-1.0.26.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:75ab092f84ee6cf61d25cf8b4f491b2488d8596d035d8347663c5ef6d58219ab"}, + {file = "libusb_package-1.0.26.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:992481beecac68fb1978b62da83eae5e34c79e4ae16a9104826b4f9c5b121beb"}, + {file = "libusb_package-1.0.26.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c56fabcd61c27c731e3a2f682a51cf91608a908d83d93f69c2431412a01c70fb"}, + {file = "libusb_package-1.0.26.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9a6baa282fae2733389f16a3356e74a627a5d30d9b96e0a42f8080aa8799a513"}, + {file = "libusb_package-1.0.26.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f2409346c53d861cfef4663c003a217334fcd02a38b5521722ec385758a53db"}, + {file = "libusb_package-1.0.26.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8b5b8fc1d09daad512999ac4539cb224ec6379e08883f6d3f581875a98a94f05"}, + {file = "libusb_package-1.0.26.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:942217aac7364e6055b8b10b5186dd852e7fc167849adc3de067943b7c461cee"}, + {file = "libusb_package-1.0.26.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e566aa04957ae61c0c2eb4ec70e7767520fe79c5b9cae2ebbf5eaddbff73ea58"}, + {file = "libusb_package-1.0.26.2-cp310-cp310-win32.whl", hash = "sha256:32e03d0bfbb295cce3a37a18bca70b1f81b139fd8ae835fc0b91b1270b379e5b"}, + {file = "libusb_package-1.0.26.2-cp310-cp310-win_amd64.whl", hash = "sha256:b1e70b24d9a7aa10cc9ce52a7139037e22d4444b152d54770c7c9342cf5e8781"}, + {file = "libusb_package-1.0.26.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4aacc4fe1bf4cd892137f56c9783c81b388c80a249e8afacb1374ac52543e8f2"}, + {file = "libusb_package-1.0.26.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1f2f14051983c8869296a1e1659c68e1796a42b5723b2698a30d6aa0e295696a"}, + {file = "libusb_package-1.0.26.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:73e910401d9a9c127b039e16e8b6a1ba4ec8d21a405e6cf12d7ed5ba207058b0"}, + {file = "libusb_package-1.0.26.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b300f0ad23a986e7085731bb6a622619c550e5df6649463492d4c6c44f8c9d5"}, + {file = "libusb_package-1.0.26.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08fd35ee52119ae322b10288d9a411e82c9cd87e82ad1d13d63e024236b6d6ba"}, + {file = "libusb_package-1.0.26.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:16089823afced6e407eb39b4519e9c094d17be759a1b0bb1976d581e7b2382ae"}, + {file = "libusb_package-1.0.26.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b8a0208b1bc9b2e2b7097d33a7e24f4268e189b05ee5267f57e7346802653b9f"}, + {file = "libusb_package-1.0.26.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ec40b8a81df4ec972f494db1e0848d38b2a2c117f001aae0e4c739feec50205f"}, + {file = "libusb_package-1.0.26.2-cp311-cp311-win32.whl", hash = "sha256:f1bed2fe1ce230e37826dc25fc077ae892d9edfe9d312aea112f3cbeba2a8588"}, + {file = "libusb_package-1.0.26.2-cp311-cp311-win_amd64.whl", hash = "sha256:982a03c4cd4a5509540f18b127f5b873d54019d5cdc1438f07529dc9080be633"}, + {file = "libusb_package-1.0.26.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a76378f89cbdf457573b94a889666306f0fac2ab55d0e0b2aa26c8dcec9ac587"}, + {file = "libusb_package-1.0.26.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad8c5ed9a82efcffaa5bb216f8d39a8a244235a98fa14d2e837ef8088a068745"}, + {file = "libusb_package-1.0.26.2-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d95f3ddc12380ebd024e2c9a1ee4e0a34555275ddcc6658962df1379fedfaef4"}, + {file = "libusb_package-1.0.26.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8803e41bc1c5c5c1c16bb873308fc9cb4613ee51688084e94976d887e3d588b0"}, + {file = "libusb_package-1.0.26.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b6d74c3ba47d914c874bef8aa0efc0af99e07b44b7f735cde9ff93573ca33ad2"}, + {file = "libusb_package-1.0.26.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:62df8d3f38482864c35347978184c095556796e3af4005f24091dd5bcbed5b0a"}, + {file = "libusb_package-1.0.26.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:95cfd34bc9ddc4e4d2dbcec1ecf15c68ea0243d75518b13b69450d672189f3d7"}, + {file = "libusb_package-1.0.26.2-cp37-cp37m-win32.whl", hash = "sha256:818115cb123281fa54dc80cfa1033da95a0b0b481adb967e4147d726468a42a5"}, + {file = "libusb_package-1.0.26.2-cp37-cp37m-win_amd64.whl", hash = "sha256:9a109ab311327ce036b9ee0d984935009e0dad45a8a8c8387f580fa11ccfe6fc"}, + {file = "libusb_package-1.0.26.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b158e37423191bfa42d14366ed4dc4fea0f4b6fcc0ad3d180711b8e9609bcd02"}, + {file = "libusb_package-1.0.26.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:092ea7d50734750197459b7c37e024142cabd090df80cbda9254d8d93bd51ee5"}, + {file = "libusb_package-1.0.26.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9cb4e98e44efc87a0bbdcd231101797518a688cc2528355b9200a0f4b18522e"}, + {file = "libusb_package-1.0.26.2-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b655fded7314065a60e088d2e49b014155a700a96b01af081abb914ce1fa2e96"}, + {file = "libusb_package-1.0.26.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93a59c9fdb4d76f2c4bb93c4fbc3c7450c746082e9aa9ba71d2b8f87a958121e"}, + {file = "libusb_package-1.0.26.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f132290e962af25fd89a64c0960d5f5d2b46aab647bc52a959b09742d3a3a3b0"}, + {file = "libusb_package-1.0.26.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6a4875c15a3a6c19c999c7f11ff533bd7abadbc1e581f1c4135b0225344fdd94"}, + {file = "libusb_package-1.0.26.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:50a1c23c05a966094e1cc3876109228dc966e1d31cec6ffb69a6b2cde5d4049f"}, + {file = "libusb_package-1.0.26.2-cp38-cp38-win32.whl", hash = "sha256:40befc6b6b774cfc34d19b79ba04800494150f256709f0b8e76f5f05b1c1c9ac"}, + {file = "libusb_package-1.0.26.2-cp38-cp38-win_amd64.whl", hash = "sha256:1c68660a83d25a32fccc9aa2d300800a3b523508268f01edc322fb2f68cf04a7"}, + {file = "libusb_package-1.0.26.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:57f186097f3086b0fbaf91f823a70df0993f098c5780db2bfd941184a6e5fa42"}, + {file = "libusb_package-1.0.26.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:67069b698c79c53e704b67b10147b94ca8e7cfb55fb84010c3d38218bda51c03"}, + {file = "libusb_package-1.0.26.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51961a3b2e5eb7c276052d4a5d347de1b6d128751eae576e7162f95f6d1e466d"}, + {file = "libusb_package-1.0.26.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:21304366f16aa684f6b2fc49a8603e8e1eb3c61f2db60cdbdea71ef4d8ea166b"}, + {file = "libusb_package-1.0.26.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:613c5ff62c19c18b248de2159a6c4093e8adac8f9f5655e8f32e71a9cb36e5df"}, + {file = "libusb_package-1.0.26.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d69bf72ebdba7f892e9cf45fe55f8308b4b683db887728da28c3721cd6a624fd"}, + {file = "libusb_package-1.0.26.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:45e791e7083a7b4500cdaff0854ff3f74da31d0d375c6f86aab70ae629934829"}, + {file = "libusb_package-1.0.26.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8b4e7f01b359e8178d2112b2eefdccbfe2fd36a1e57dbcebf1678aef266e691a"}, + {file = "libusb_package-1.0.26.2-cp39-cp39-win32.whl", hash = "sha256:051feb717910fc868da437d271bcaa2d0b59fce951f304821a3139ababb3d69f"}, + {file = "libusb_package-1.0.26.2-cp39-cp39-win_amd64.whl", hash = "sha256:ad1d6b7e8e2323a222b4f968f16f27dbe91f48e4c8cc802bf27022e324b8b99b"}, + {file = "libusb_package-1.0.26.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bea164dbeff193a052a2d64896dd136c0bfccd8b3000a2d233d2d23cddf18436"}, + {file = "libusb_package-1.0.26.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18e77e0b2a30b72c963adb99c520f78414ab91940d97acba745ff791eb3477dd"}, + {file = "libusb_package-1.0.26.2-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cc14c8b37f9acebadbf7b1ab7c788826e67dfecf4a872c6a2698777716856333"}, + {file = "libusb_package-1.0.26.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fba4e8fc77865cbf7464f5a847129c282a8bd26868da160b2b90d61c67192d4b"}, + {file = "libusb_package-1.0.26.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:86ffd4480e578e86277f5d2c65aad30db21c02bb7b8a439c44585ca65bb7fe7c"}, + {file = "libusb_package-1.0.26.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:28e5867f8a290ca3a448954d1d34b875ba7d0e5f775e4c775f9eebdecc68dfb1"}, + {file = "libusb_package-1.0.26.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:229d40f83a1d8ae0cd9d111a829972224527573a98487d092496840b98a82af2"}, + {file = "libusb_package-1.0.26.2-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5186ed5c9bf04f50aec6b30d4795ff48f227137a2f6c92b2d6503a29b0b9761b"}, + {file = "libusb_package-1.0.26.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1e01a04d7115900b6fb864b822d04a06159bc1c57dbefd119fad09506f7d242"}, + {file = "libusb_package-1.0.26.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:960069cd1478996e7f00d9c98347bfb1804f14e309fa1f00cbd26136dc1074f8"}, + {file = "libusb_package-1.0.26.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0970e9e92bc13564fe75c3fc74e60601cb31866fc0345cb1f6461b9f983d6062"}, + {file = "libusb_package-1.0.26.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:75ec775bf3950ca2f9287ae86637a6de8a9a8addf463bfb176026403ea78c64b"}, + {file = "libusb_package-1.0.26.2-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74d0b0bd2a284d3ba9b1b99c334171230d88658ef2d64e2f83a71dc28c65f2da"}, + {file = "libusb_package-1.0.26.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a96262d51f2714a250cf89e063f354a621d81438a7019bca5035e3ab4a65988"}, + {file = "libusb_package-1.0.26.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:5fbd4ea17c36806c94e6c2fc4d6a6548c628c0ad874937b0953946d49f29f23b"}, +] + +[package.dependencies] +importlib-resources = "*" + [[package]] name = "loguru" version = "0.7.2" @@ -1202,4 +1296,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = ">=3.9,<3.12" -content-hash = "247f7ad9f52f2a0a349c8be3c0d577859fafd06a0b1eaa6e3ff83eb7ac30a88b" +content-hash = "ada67392cf2075ada1eacae60001ba1a583b929d5752692fab5a50584228aaed" diff --git a/src/mpflash/pyproject.toml b/src/mpflash/pyproject.toml index f9008720..31f89e23 100644 --- a/src/mpflash/pyproject.toml +++ b/src/mpflash/pyproject.toml @@ -31,13 +31,11 @@ psutil = "^5.9.8" blkinfo = "^0.2.0" pygithub = "^2.1.1" platformdirs = "^4.2.0" -pyusb = [ - {version = "^1.2.1", platform = "linux"}, - {version = "^1.2.1", platform = "darwin"} -] +pyusb = "^1.2.1" packaging = "23.2" tenacity = "8.2.3" mpremote = "^1.22.0" +libusb-package = {version = "^1.0.26.2", platform = "windows"} [tool.poetry.group.dev.dependencies] types-beautifulsoup4 = "^4.12.0.20240106" From c7a3d08385b3b4cea786ed7ef8909aaf7ec54136 Mon Sep 17 00:00:00 2001 From: Jos Verlinde Date: Tue, 12 Mar 2024 18:27:28 +0100 Subject: [PATCH 2/3] use unly pydfu in flash_stm32 and add wait_for_restart --- src/mpflash/mpflash/basic.py | 4 +- src/mpflash/mpflash/common.py | 32 +++- src/mpflash/mpflash/flash_esp.py | 7 +- src/mpflash/mpflash/flash_stm32.py | 27 ++- src/mpflash/mpflash/flash_stm32_cube.py | 233 ++++++++++++------------ src/mpflash/mpflash/flash_stm32_dfu.py | 32 +++- src/mpflash/mpflash/flasher.py | 43 ++--- 7 files changed, 203 insertions(+), 175 deletions(-) diff --git a/src/mpflash/mpflash/basic.py b/src/mpflash/mpflash/basic.py index c309fc93..f33ae5fe 100644 --- a/src/mpflash/mpflash/basic.py +++ b/src/mpflash/mpflash/basic.py @@ -1,7 +1,7 @@ -from .vendored import pydfu as pydfu -from argparse import Namespace from pathlib import Path +from .vendored import pydfu as pydfu + def main(): print("Hello, DFU!") diff --git a/src/mpflash/mpflash/common.py b/src/mpflash/mpflash/common.py index 435ef28e..de53ff07 100644 --- a/src/mpflash/mpflash/common.py +++ b/src/mpflash/mpflash/common.py @@ -1,12 +1,15 @@ +import time from typing import TypedDict from github import Github from loguru import logger as log from packaging.version import parse +from rich.progress import track +from mpflash.mpremoteboard.mpremoteboard import MPRemoteBoard PORT_FWTYPES = { - "stm32": [".hex", ".dfu"], # .hex for cube cli, but need .dfu for pydfu.py + "stm32": [".dfu"], # need .dfu for pydfu.py - .hex for cube cli/GUI "esp32": [".bin"], "esp8266": [".bin"], "rp2": [".uf2"], @@ -19,11 +22,12 @@ class FWInfo(TypedDict): filename: str - port:str - board:str - variant:str + port: str + board: str + variant: str preview: bool - version:str + version: str + ############################################################# # Version handling copied from stubber/utils/versions.py @@ -122,8 +126,22 @@ def micropython_versions(minver: str = "v1.9.2"): def get_stable_version() -> str: # read the versions from the git tags all_versions = micropython_versions(minver="v1.17") - stable_version = [v for v in all_versions if not v.endswith(V_PREVIEW)][-1] - return stable_version + return [v for v in all_versions if not v.endswith(V_PREVIEW)][-1] ############################################################# +def wait_for_restart(mcu: MPRemoteBoard, timeout: int = 10): + """wait for the board to restart""" + for _ in track( + range(timeout), + description="Waiting for the board to restart", + transient=True, + get_time=lambda: time.time(), + show_speed=False, + ): + time.sleep(1) + try: + mcu.get_mcu_info() + break + except ConnectionError: + pass diff --git a/src/mpflash/mpflash/flash_esp.py b/src/mpflash/mpflash/flash_esp.py index 60d9bb16..df7e373f 100644 --- a/src/mpflash/mpflash/flash_esp.py +++ b/src/mpflash/mpflash/flash_esp.py @@ -12,7 +12,7 @@ from loguru import logger as log from .mpremoteboard.mpremoteboard import MPRemoteBoard - +from .common import wait_for_restart def flash_esp( mcu: MPRemoteBoard, fw_file: Path, *, erase: bool = True @@ -59,7 +59,6 @@ def flash_esp( return None log.info("Done flashing, resetting the board and wait for it to restart") - time.sleep(5) - mcu.get_mcu_info() - log.success(f"Flashed {mcu.version} to {mcu.board} on {mcu.serialport} done") + wait_for_restart(mcu) + log.success(f"Flashed {mcu.version} to {mcu.board}") return mcu diff --git a/src/mpflash/mpflash/flash_stm32.py b/src/mpflash/mpflash/flash_stm32.py index 8b8d468f..29dd1503 100644 --- a/src/mpflash/mpflash/flash_stm32.py +++ b/src/mpflash/mpflash/flash_stm32.py @@ -1,17 +1,26 @@ """Flash STM32 boards using either STM32CubeProgrammer CLI or dfu-util""" from pathlib import Path -from .flash_stm32_dfu import flash_stm32_dfu -from .flash_stm32_cube import flash_stm32_cubecli +from loguru import logger as log + +from mpflash.common import wait_for_restart + +# from .flash_stm32_cube import flash_stm32_cubecli +from .flash_stm32_dfu import dfu_init, flash_stm32_dfu from .mpremoteboard.mpremoteboard import MPRemoteBoard -def flash_stm32(mcu: MPRemoteBoard, fw_file: Path, *, erase: bool, stm32_hex: bool): - """Flash STM32 boards using either STM32CubeProgrammer CLI or dfu-util""" - # TODO: Check installed utilities / Lazy import - if stm32_hex: - updated = flash_stm32_cubecli(mcu, fw_file=fw_file, erase=erase) - else: - updated = flash_stm32_dfu(mcu, fw_file=fw_file, erase=erase) +def flash_stm32(mcu: MPRemoteBoard, fw_file: Path, *, erase: bool, stm32_dfu: bool = True): + # sourcery skip: lift-return-into-if + log.info("Using dfu-util") + dfu_init() + updated = flash_stm32_dfu(mcu, fw_file=fw_file, erase=erase) + # if stm32_dfu: + # else: + # log.info("Using STM32CubeProgrammer CLI") + # updated = flash_stm32_cubecli(mcu, fw_file=fw_file, erase=erase) + + wait_for_restart(mcu) + log.success(f"Flashed {mcu.version} to {mcu.board}") return updated diff --git a/src/mpflash/mpflash/flash_stm32_cube.py b/src/mpflash/mpflash/flash_stm32_cube.py index 6d6e11af..2a464ea9 100644 --- a/src/mpflash/mpflash/flash_stm32_cube.py +++ b/src/mpflash/mpflash/flash_stm32_cube.py @@ -1,121 +1,112 @@ -""" -Flash STM32 using STM32CubeProgrammer -needs to be installed independenty from https://www.st.com/en/development-tools/stm32cubeprog.html - -On Linux needs to be run with sudo - because ? -""" - -import subprocess -import sys -import time -from pathlib import Path -from typing import Optional - -import bincopy -from loguru import logger as log -from rich.progress import track -from strip_ansi import strip_ansi - -from .mpremoteboard.mpremoteboard import MPRemoteBoard - -STM32_CLI_WIN = "C:\\Program Files\\STMicroelectronics\\STM32Cube\\STM32CubeProgrammer\\bin\\STM32_Programmer_CLI.exe" -STM32_CLI_LINUX = "~/STMicroelectronics/STM32Cube/STM32CubeProgrammer/bin/STM32_Programmer_CLI" - - -def get_stm32_start_address(fw_file: Path): - """ - Get the start address of the firmware file, to allow automatic restart from that address after flashing - """ - try: - fw_hex = bincopy.BinFile(str(fw_file)) - return f"0x{fw_hex.execution_start_address:08X}" - except Exception: - - return "" - - -def flash_stm32_cubecli(mcu: MPRemoteBoard, fw_file: Path, *, erase: bool = True) -> Optional[MPRemoteBoard]: - """ - Flash STM32 devices using STM32CubeProgrammer CLI - - Enter bootloader mode - - wait 2s for the device to be detected - - list the connected DFU devices - - On Linux: requires udev rules to allow access to the device as a regular user - """ - if sys.platform == "linux": - STM32_CLI = Path(STM32_CLI_LINUX).expanduser().as_posix() - elif sys.platform == "win32": - STM32_CLI = str(Path(STM32_CLI_WIN).expanduser()) - else: - log.error(f"OS {sys.platform} not supported") - return None - - if not Path(STM32_CLI).exists(): - log.error( - f"STM32CubeProgrammer not found at {STM32_CLI}\nPlease install it from https://www.st.com/en/development-tools/stm32cubeprog.html" - ) - return None - - - # run STM32_Programmer_CLI.exe --list - cmd = [ - STM32_CLI, - "--list", - ] - results = subprocess.run(cmd, capture_output=True, text=True).stdout.splitlines() - results = [strip_ansi(line) for line in results] - if not any(["Product ID : STM32 BOOTLOADER" in l for l in results]): - log.error("No STM32 BOOTLOADER detected") - return None - echo = False - for line in results: - if line.startswith("===== DFU Interface"): - echo = True - if line.startswith("===== STLink"): - echo = False - if echo: - print(line) - # Try to connect - no action - cmd = [ - STM32_CLI, - "--connect", - "port=USB1", - ] - results = subprocess.run(cmd, capture_output=True, text=True).stdout.splitlines() - if erase: - log.info("Erasing flash") - cmd = [ - STM32_CLI, - "--connect", - "port=USB1", - "--erase", - "all", - ] - results = subprocess.run(cmd, capture_output=True, text=True).stdout.splitlines() - results = [strip_ansi(line) for line in results] - log.info(f"Flashing {fw_file.name} using STM32CubeProgrammer CLI") - start_address = get_stm32_start_address(fw_file) - - log.trace(f"STM32_Programmer_CLI --connect port=USB1 --write {str(fw_file)} --go {start_address}") - cmd = [ - STM32_CLI, - "--connect", - "port=USB1", - "--write", - str(fw_file), - "--go", - start_address, - ] - results = subprocess.run(cmd, capture_output=True, text=True).stdout.splitlines() - log.success("Done flashing, resetting the board and wait for it to restart") - - for _ in track(range(10), description="Waiting for the board to restart", transient=True): - time.sleep(1) - try: - mcu.get_mcu_info() - break - except ConnectionError: - pass - - return mcu +# """ +# Flash STM32 using STM32CubeProgrammer +# needs to be installed independenty from https://www.st.com/en/development-tools/stm32cubeprog.html + +# On Linux needs to be run with sudo - unless udev rules are set to allow access to the device as a regular user +# """ + +# import subprocess +# import sys +# import time +# from pathlib import Path +# from typing import Optional + +# import bincopy +# from loguru import logger as log +# from rich.progress import track +# from strip_ansi import strip_ansi + +# from .common import wait_for_restart +# from .mpremoteboard.mpremoteboard import MPRemoteBoard + +# STM32_CLI_WIN = "C:\\Program Files\\STMicroelectronics\\STM32Cube\\STM32CubeProgrammer\\bin\\STM32_Programmer_CLI.exe" +# STM32_CLI_LINUX = "~/STMicroelectronics/STM32Cube/STM32CubeProgrammer/bin/STM32_Programmer_CLI" + + +# def get_stm32_start_address(fw_file: Path): +# """ +# Get the start address of the firmware file, to allow automatic restart from that address after flashing +# """ +# try: +# fw_hex = bincopy.BinFile(str(fw_file)) +# return f"0x{fw_hex.execution_start_address:08X}" +# except Exception: + +# return "" + + +# def flash_stm32_cubecli(mcu: MPRemoteBoard, fw_file: Path, *, erase: bool = True) -> Optional[MPRemoteBoard]: +# """ +# Flash STM32 devices using STM32CubeProgrammer CLI +# - Enter bootloader mode +# - wait 2s for the device to be detected +# - list the connected DFU devices + +# On Linux: requires udev rules to allow access to the device as a regular user +# """ +# if sys.platform == "linux": +# STM32_CLI = Path(STM32_CLI_LINUX).expanduser().as_posix() +# elif sys.platform == "win32": +# STM32_CLI = str(Path(STM32_CLI_WIN).expanduser()) +# else: +# log.error(f"OS {sys.platform} not supported") +# return None + +# if not Path(STM32_CLI).exists(): +# log.error( +# f"STM32CubeProgrammer not found at {STM32_CLI}\nPlease install it from https://www.st.com/en/development-tools/stm32cubeprog.html" +# ) +# return None + +# # run STM32_Programmer_CLI.exe --list +# cmd = [ +# STM32_CLI, +# "--list", +# ] +# results = subprocess.run(cmd, capture_output=True, text=True).stdout.splitlines() +# results = [strip_ansi(line) for line in results] +# if not any(["Product ID : STM32 BOOTLOADER" in l for l in results]): +# log.error("No STM32 BOOTLOADER detected") +# return None +# echo = False +# for line in results: +# if line.startswith("===== DFU Interface"): +# echo = True +# if line.startswith("===== STLink"): +# echo = False +# if echo: +# print(line) +# # Try to connect - no action +# cmd = [ +# STM32_CLI, +# "--connect", +# "port=USB1", +# ] +# results = subprocess.run(cmd, capture_output=True, text=True).stdout.splitlines() +# if erase: +# log.info("Erasing flash") +# cmd = [ +# STM32_CLI, +# "--connect", +# "port=USB1", +# "--erase", +# "all", +# ] +# results = subprocess.run(cmd, capture_output=True, text=True).stdout.splitlines() +# results = [strip_ansi(line) for line in results] +# log.info(f"Flashing {fw_file.name} using STM32CubeProgrammer CLI") +# start_address = get_stm32_start_address(fw_file) + +# log.trace(f"STM32_Programmer_CLI --connect port=USB1 --write {str(fw_file)} --go {start_address}") +# cmd = [ +# STM32_CLI, +# "--connect", +# "port=USB1", +# "--write", +# str(fw_file), +# "--go", +# start_address, +# ] +# results = subprocess.run(cmd, capture_output=True, text=True).stdout.splitlines() +# log.success("Done flashing, resetting the board and wait for it to restart") +# return mcu diff --git a/src/mpflash/mpflash/flash_stm32_dfu.py b/src/mpflash/mpflash/flash_stm32_dfu.py index 5a23e464..7a2b3505 100644 --- a/src/mpflash/mpflash/flash_stm32_dfu.py +++ b/src/mpflash/mpflash/flash_stm32_dfu.py @@ -1,3 +1,4 @@ +import platform import sys import time from pathlib import Path @@ -5,14 +6,37 @@ from loguru import logger as log +from .common import wait_for_restart from .mpremoteboard.mpremoteboard import MPRemoteBoard + +def init_libusb_windows(): + # on windows we need to initialze the libusb backend with the correct dll + import libusb + import usb.backend.libusb1 as libusb1 + + arch = "x64" if platform.architecture()[0] == "64bit" else "x86" + libusb1_dll = Path(libusb.__file__).parent / f"_platform\\_windows\\{arch}\\libusb-1.0.dll" + if not libusb1_dll.exists(): + raise FileNotFoundError(f"libusb1.dll not found at {libusb1_dll}") + + backend = libusb1.get_backend(find_library=lambda x: libusb1_dll.as_posix()) + + try: from .vendored import pydfu as pydfu except ImportError: pydfu = None +def dfu_init(): + if not pydfu: + log.error("pydfu not found") + return None + if platform.system() == "Windows": + init_libusb_windows() + + def flash_stm32_dfu( mcu: MPRemoteBoard, fw_file: Path, @@ -20,9 +44,9 @@ def flash_stm32_dfu( erase: bool = True, ) -> Optional[MPRemoteBoard]: - if sys.platform == "win32": - log.error(f"OS {sys.platform} not supported") - return None + # if sys.platform == "win32": + # log.error(f"OS {sys.platform} not supported") + # return None if not pydfu: log.error("pydfu not found, please install it with 'pip install pydfu' if supported") @@ -63,6 +87,4 @@ def flash_stm32_dfu( log.debug("Exiting DFU...") pydfu.exit_dfu() log.success("Done flashing, resetting the board and wait for it to restart") - time.sleep(5) - mcu.get_mcu_info() return mcu diff --git a/src/mpflash/mpflash/flasher.py b/src/mpflash/mpflash/flasher.py index 1209b486..a0d7c3fc 100644 --- a/src/mpflash/mpflash/flasher.py +++ b/src/mpflash/mpflash/flasher.py @@ -201,25 +201,19 @@ def auto_update( help="The CPU type to flash. If not specified will try to read the CPU from the connected MCU.", metavar="CPU", ) -# @click.option( -# "--variant", -# help="The variant of the board to flash. If not specified will try to read the VARIANT from the connected MCU.", -# metavar="VARIANT", -# default="", -# ) @click.option( "--erase/--no-erase", default=True, show_default=True, help="""Erase flash before writing new firmware. (not on UF2 boards)""", ) -@click.option( - "--stm32-hex/--stm32-dfu", - "--hex/--dfu", - default=True, - show_default=True, - help="""Use .hex (STM32CubeProgrammer CLI) or .dfu (dfu-util) to flash STM32 boards.""", -) +# @click.option( +# "--stm32-dfu/--stm32-hex", +# "--dfu/--hex", +# default=True, +# show_default=True, +# help="""Use .dfu (dfu-util) or .hex (using external STM32CubeProgrammer CLI) to flash STM32 boards.""", +# ) @click.option( "--bootloader/--no-bootloader", default=True, @@ -243,18 +237,18 @@ def cli_flash_board( cpu: Optional[str] = None, erase: bool = False, preview: bool = False, - stm32_hex: bool = True, + # stm32_dfu: bool = True, bootloader: bool = True, ): todo: WorkList = [] # firmware type selector - selector = {} - selector["stm32"] = ".hex" if stm32_hex else ".dfu" - + selector = { + "stm32": ".dfu", # if stm32_dfu else ".hex", + } target_version = clean_version(target_version) # Update all micropython boards to the latest version if target_version and port and board and serial_port: - # TODO : Find a way to avoud needing to specify the port + # TODO : Find a way to avoid needing to specify the port mcu = MPRemoteBoard(serial_port) mcu.port = port mcu.cpu = port if port.startswith("esp") else "" @@ -294,18 +288,19 @@ def cli_flash_board( updated = None # try: - if mcu.port in ["samd", "rp2"]: + if mcu.port in ["samd", "rp2", "nrf"]: if bootloader: enter_bootloader(mcu) updated = flash_uf2(mcu, fw_file=fw_file, erase=erase) elif mcu.port in ["esp32", "esp8266"]: + # bootloader is handles by esptool for esp32/esp8266 updated = flash_esp(mcu, fw_file=fw_file, erase=erase) elif mcu.port in ["stm32"]: if bootloader: enter_bootloader(mcu) - updated = flash_stm32(mcu, fw_file, erase=erase, stm32_hex=stm32_hex) + updated = flash_stm32(mcu, fw_file, erase=erase) else: - log.error(f"Don't know how to flash {mcu.port}-{mcu.board} on {mcu.serialport}") + log.error(f"Don't (yet) know how to flash {mcu.port}-{mcu.board} on {mcu.serialport}") if updated: flashed.append(updated) @@ -314,12 +309,6 @@ def cli_flash_board( if flashed: log.info(f"Flashed {len(flashed)} boards") - # conn_boards = [ - # MPRemoteBoard(sp) - # for sp in MPRemoteBoard.connected_boards() - # if sp not in config.ignore_ports - # ] - show_mcus(flashed, title="Connected boards after flashing") From b05527be7754c45904ea340bdce4afeea03c3b24 Mon Sep 17 00:00:00 2001 From: Jos Verlinde Date: Tue, 12 Mar 2024 18:33:07 +0100 Subject: [PATCH 3/3] bump version --- src/mpflash/pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mpflash/pyproject.toml b/src/mpflash/pyproject.toml index 31f89e23..049c8411 100644 --- a/src/mpflash/pyproject.toml +++ b/src/mpflash/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "mpflash" -version = "0.2.4" -description = "Download and flash tool for MicroPython firmwares" +version = "0.3.0" +description = "Flash and download tool for MicroPython firmwares" authors = ["Jos Verlinde "] license = "MIT" readme = "README.md"