mpflash 0.8.2__py3-none-any.whl → 0.8.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,36 @@
1
+ import time
2
+
3
+ from mpflash.common import BootloaderMethod
4
+ from mpflash.errors import MPFlashError
5
+ from mpflash.logger import log
6
+ from mpflash.mpremoteboard import MPRemoteBoard
7
+
8
+ from .manual import enter_bootloader_manual
9
+ from .micropython import enter_bootloader_mpy
10
+ from .touch1200 import enter_bootloader_cdc_1200bps
11
+
12
+
13
+ def enter_bootloader(
14
+ mcu: MPRemoteBoard,
15
+ method: BootloaderMethod = BootloaderMethod.MPY,
16
+ timeout: int = 10,
17
+ wait_after: int = 2,
18
+ ):
19
+ """Enter the bootloader mode for the board"""
20
+ if method == BootloaderMethod.NONE:
21
+ # NO bootloader requested, so must be OK to flash
22
+ return True
23
+
24
+ log.info(f"Entering bootloader on {mcu.board} on {mcu.serialport} using method: {method.value}")
25
+ if method == BootloaderMethod.MPY:
26
+ result = enter_bootloader_mpy(mcu, timeout=timeout)
27
+ elif method == BootloaderMethod.MANUAL:
28
+ result = enter_bootloader_manual(mcu, timeout=timeout)
29
+ elif method == BootloaderMethod.TOUCH_1200:
30
+ result = enter_bootloader_cdc_1200bps(mcu, timeout=timeout)
31
+ else:
32
+ raise MPFlashError(f"Unknown bootloader method {method}")
33
+ if result:
34
+ time.sleep(wait_after)
35
+ log.error(f"Failed to enter bootloader on {mcu.serialport}")
36
+ return result
@@ -0,0 +1,102 @@
1
+ """Manual bootloader mode entry for various MCUs."""
2
+
3
+ from click.exceptions import Abort
4
+ from rich.console import Console
5
+ from rich.highlighter import RegexHighlighter
6
+ from rich.panel import Panel
7
+ from rich.prompt import Confirm
8
+ from rich.theme import Theme
9
+
10
+ # from mpflash.logger import console, log
11
+ from mpflash.mpremoteboard import MPRemoteBoard
12
+
13
+
14
+ class MCUHighlighter(RegexHighlighter):
15
+ """Apply style to anything that looks like an email."""
16
+
17
+ base_style = "mcu."
18
+ highlights = [
19
+ r"(?P<bold>Method[\s\d\:]*)",
20
+ r"(?P<bold> \d.)", # numbered items
21
+ r"(?P<bold> - )", # bullets
22
+ r"(?P<pad>GPIO[\d]*)",
23
+ r"(?P<pad>GPI[\d]*)",
24
+ r"(?P<pad>IO[\d]*)",
25
+ r"(?P<pad>RUN)",
26
+ r"(?P<pad>GND)",
27
+ r"(?P<pad>VCC)",
28
+ r"(?P<pad>3.3V)",
29
+ r"(?P<pad>5V)",
30
+ # buttons
31
+ r"(?P<button>BOOTSEL)",
32
+ r"(?P<button>RESET)",
33
+ r"(?P<button>reset)",
34
+ # other
35
+ r"(?P<cable>USB)",
36
+ # r"(?P<mcu>SAMD[\d]*)",
37
+ # r"(?P<mcu>ESP[\d]*)",
38
+ # r"(?P<mcu>rp2)",
39
+ # r"(?P<mcu>rp2040)",
40
+ ]
41
+
42
+
43
+ # https://rich.readthedocs.io/en/stable/appendix/colors.html?highlight=colors#standard-colors
44
+ mcu_theme = Theme(
45
+ {
46
+ "mcu.bold": "orange3",
47
+ "mcu.mcu": "orange3",
48
+ "mcu.button": "bold green",
49
+ "mcu.pad": "dodger_blue2",
50
+ "mcu.cable": "dodger_blue2",
51
+ }
52
+ )
53
+
54
+
55
+ def enter_bootloader_manual(mcu: MPRemoteBoard, timeout: int = 10):
56
+
57
+ message: str
58
+ if mcu.port == "rp2":
59
+ message = f"""\
60
+ Please put your {" ".join([mcu.port,mcu.board])} device into bootloader mode by either:
61
+ Method 1:
62
+ 1. Unplug the USB cable,
63
+ 2. Press and hold the BOOTSEL button on the device,
64
+ 3. Plug the USB cable back in.
65
+ 4. Release the BOOTSEL button.
66
+
67
+ Method 2:
68
+ 1. Press and hold the BOOTSEL button on the device,
69
+ 2. Reset the device by either:
70
+ - pressing the RESET button on the device
71
+ - by power-cycling the device,
72
+ - by briefly connecting the RUN pin to GND
73
+ 3. Release the BOOTSEL button.
74
+ """
75
+ elif mcu.port == "samd":
76
+ message = f"""\
77
+ Please put your {mcu.port.upper()} device into bootloader mode by:
78
+ - Pressing or sliding the RESET button twice in fast succession
79
+ """
80
+ else:
81
+ message = f"""\
82
+ Please put your {mcu.port.upper()} device into bootloader mode by:
83
+ - Pressing the RESET button on the device
84
+ """
85
+
86
+ # todo: would be nice to re-use the console instance from logger
87
+ console = Console(highlighter=MCUHighlighter(), theme=mcu_theme)
88
+ message += "\nIf you are unsure how to enter bootloader mode, please refer to the device documentation."
89
+ console.print(
90
+ Panel(
91
+ message,
92
+ highlight=True,
93
+ title="Manual Bootloader",
94
+ title_align="left",
95
+ expand=False,
96
+ )
97
+ )
98
+ try:
99
+ answer = Confirm.ask("Press Enter to continue", default="y")
100
+ except Abort:
101
+ return False
102
+ return answer in ["y", "Y", True]
@@ -0,0 +1,10 @@
1
+ """Module for handling the bootloader mode for micropython boards"""
2
+
3
+ from mpflash.mpremoteboard import MPRemoteBoard
4
+
5
+
6
+ def enter_bootloader_mpy(mcu: MPRemoteBoard, timeout: int = 10):
7
+ """Enter the bootloader mode for the board using mpremote and micropython on the board"""
8
+ mcu.run_command("bootloader", timeout=timeout)
9
+ # todo: check if mpremote command was successful
10
+ return True
@@ -0,0 +1,45 @@
1
+ import sys
2
+ import time
3
+
4
+ import serial
5
+
6
+ from mpflash.errors import MPFlashError
7
+ from mpflash.logger import log
8
+ from mpflash.mpremoteboard import MPRemoteBoard
9
+
10
+ from .manual import enter_bootloader_manual
11
+
12
+
13
+ def enter_bootloader_cdc_1200bps(mcu: MPRemoteBoard, timeout: int = 10):
14
+ if sys.platform == "win32":
15
+ log.warning("Touch 1200bps method is currently not supported on Windows, switching to manual")
16
+ return enter_bootloader_manual(mcu, timeout=timeout)
17
+
18
+ log.info(f"Entering bootloader on {mcu.board} on {mcu.serialport} using CDC 1200bps")
19
+ # if port argument is present perform soft reset
20
+ if not mcu.serialport:
21
+ raise MPFlashError("No serial port specified")
22
+ # try to initiate serial port connection on PORT with 1200 baudrate
23
+ try:
24
+ with serial.Serial(
25
+ port=mcu.serialport,
26
+ baudrate=1200,
27
+ parity=serial.PARITY_NONE,
28
+ stopbits=serial.STOPBITS_ONE,
29
+ bytesize=serial.EIGHTBITS,
30
+ rtscts=True,
31
+ ) as connection:
32
+ print("Connection established")
33
+ connection.rts = True
34
+ connection.dtr = False
35
+ time.sleep(0.4)
36
+
37
+ except serial.SerialException as e:
38
+ log.exception(e)
39
+ raise MPFlashError("pySerial error: " + str(e) + "\n") from e
40
+ except Exception as e:
41
+ log.exception(e)
42
+ raise MPFlashError("Error: " + str(e) + "\n") from e
43
+
44
+ # be optimistic
45
+ return True
mpflash/cli_download.py CHANGED
@@ -80,6 +80,7 @@ from .download import download
80
80
  )
81
81
  @click.option(
82
82
  "--force",
83
+ "-f",
83
84
  default=False,
84
85
  is_flag=True,
85
86
  show_default=True,
mpflash/cli_flash.py CHANGED
@@ -4,7 +4,7 @@ from typing import List
4
4
  import rich_click as click
5
5
  from loguru import logger as log
6
6
 
7
- # from mpflash.common import filtered_comports
7
+ from mpflash.common import BootloaderMethod
8
8
  from mpflash.errors import MPFlashError
9
9
  from mpflash.mpboard_id import find_known_board
10
10
  from mpflash.mpremoteboard import MPRemoteBoard
@@ -101,11 +101,13 @@ from .worklist import WorkList, full_auto_worklist, manual_worklist, single_auto
101
101
  help="""Erase flash before writing new firmware. (Not supported on UF2 boards)""",
102
102
  )
103
103
  @click.option(
104
- "--bootloader/--no-bootloader",
105
- default=True,
106
- is_flag=True,
104
+ "--bootloader",
105
+ "-b",
106
+ "bootloader",
107
+ type=click.Choice([e.value for e in BootloaderMethod]),
108
+ default="mpy",
107
109
  show_default=True,
108
- help="""Enter micropython bootloader mode before flashing.""",
110
+ help="""How to enter the (MicroPython) bootloader before flashing.""",
109
111
  )
110
112
  def cli_flash_board(**kwargs) -> int:
111
113
  # version to versions, board to boards
@@ -121,6 +123,7 @@ def cli_flash_board(**kwargs) -> int:
121
123
  params.boards = list(params.boards)
122
124
  params.serial = list(params.serial)
123
125
  params.ignore = list(params.ignore)
126
+ params.bootloader = BootloaderMethod(params.bootloader)
124
127
 
125
128
  # make it simple for the user to flash one board by asking for the serial port if not specified
126
129
  if params.boards == ["?"] and params.serial == "*":
@@ -129,14 +132,16 @@ def cli_flash_board(**kwargs) -> int:
129
132
  # Detect connected boards if not specified,
130
133
  # and ask for input if boards cannot be detected
131
134
  all_boards: List[MPRemoteBoard] = []
132
- if not params.boards or params.boards == []:
135
+ if not params.boards:
133
136
  # nothing specified - detect connected boards
134
137
  params.ports, params.boards, all_boards = connected_ports_boards(include=params.ports, ignore=params.ignore)
135
138
  if params.boards == []:
136
- # No MicroPython boards detected, but it could be unflashed or not in bootloader mode
139
+ # No MicroPython boards detected, but it could be unflashed or in bootloader mode
137
140
  # Ask for serial port and board_id to flash
138
141
  params.serial = ["?"]
139
142
  params.boards = ["?"]
143
+ # assume manual mode if no board is detected
144
+ params.bootloader = BootloaderMethod("manual")
140
145
  else:
141
146
  resolve_board_ids(params)
142
147
 
mpflash/cli_group.py CHANGED
@@ -5,6 +5,8 @@ Additional comands are added in the submodules.
5
5
 
6
6
  import rich_click as click
7
7
 
8
+ from mpflash.vendor.click_aliases import ClickAliasedGroup
9
+
8
10
  from .config import __version__, config
9
11
  from .logger import log, make_quiet, set_loglevel
10
12
 
@@ -42,7 +44,8 @@ def cb_quiet(ctx, param, value):
42
44
  return value
43
45
 
44
46
 
45
- @click.group()
47
+ @click.group(cls=ClickAliasedGroup)
48
+ # @click.group()
46
49
  @click.version_option(package_name="mpflash")
47
50
  @click.option(
48
51
  "--quiet",
mpflash/cli_list.py CHANGED
@@ -5,12 +5,16 @@ import rich_click as click
5
5
  from rich import print
6
6
 
7
7
  from .cli_group import cli
8
+ from .connected import list_mcus
8
9
  from .list import show_mcus
9
10
  from .logger import make_quiet
10
- from .connected import list_mcus
11
11
 
12
12
 
13
- @cli.command("list", help="List the connected MCU boards.")
13
+ @cli.command(
14
+ "list",
15
+ help="List the connected MCU boards. alias: devs",
16
+ aliases=["devs"],
17
+ )
14
18
  @click.option(
15
19
  "--json",
16
20
  "-j",
mpflash/cli_main.py CHANGED
@@ -19,12 +19,12 @@ def mpflash():
19
19
  cli.add_command(cli_flash_board)
20
20
 
21
21
  # cli(auto_envvar_prefix="MPFLASH")
22
- if False and os.environ.get("COMPUTERNAME") == "JOSVERL-S4":
22
+ if False and os.environ.get("COMPUTERNAME").startswith("JOSVERL"):
23
23
  # intentional less error suppression on dev machine
24
24
  result = cli(standalone_mode=False)
25
25
  else:
26
26
  try:
27
- result = cli(standalone_mode=False)
27
+ result = cli(standalone_mode=True)
28
28
  exit(result)
29
29
  except AttributeError as e:
30
30
  log.error(f"Error: {e}")
@@ -32,6 +32,9 @@ def mpflash():
32
32
  except click.exceptions.ClickException as e:
33
33
  log.error(f"Error: {e}")
34
34
  exit(-2)
35
+ except click.exceptions.Abort as e:
36
+ # Aborted - Ctrl-C
37
+ exit(-3)
35
38
 
36
39
 
37
40
  if __name__ == "__main__":
mpflash/common.py CHANGED
@@ -2,6 +2,7 @@ import fnmatch
2
2
  import os
3
3
  import sys
4
4
  from dataclasses import dataclass, field
5
+ from enum import Enum
5
6
  from pathlib import Path
6
7
  from typing import List, Optional, Union
7
8
 
@@ -90,14 +91,26 @@ class DownloadParams(Params):
90
91
  force: bool = False
91
92
 
92
93
 
94
+ class BootloaderMethod(Enum):
95
+ MANUAL = "manual"
96
+ MPY = "mpy"
97
+ TOUCH_1200 = "touch1200"
98
+ NONE = "none"
99
+
100
+
101
+
93
102
  @dataclass
94
103
  class FlashParams(Params):
95
104
  """Parameters for flashing a board"""
96
105
 
97
106
  erase: bool = True
98
- bootloader: bool = True
107
+ bootloader: BootloaderMethod = BootloaderMethod.NONE
99
108
  cpu: str = ""
100
109
 
110
+ def __post_init__(self):
111
+ if isinstance(self.bootloader, str):
112
+ self.bootloader = BootloaderMethod(self.bootloader)
113
+
101
114
 
102
115
  ParamType = Union[DownloadParams, FlashParams]
103
116
 
mpflash/download.py CHANGED
@@ -19,7 +19,6 @@ from loguru import logger as log
19
19
  from rich.progress import track
20
20
 
21
21
  from mpflash.common import PORT_FWTYPES, FWInfo
22
- from mpflash.downloaded import clean_downloaded_firmwares
23
22
  from mpflash.errors import MPFlashError
24
23
  from mpflash.mpboard_id import get_known_ports
25
24
  from mpflash.vendor.versions import clean_version
@@ -246,8 +245,8 @@ def download_firmwares(
246
245
  continue
247
246
  writer.write(board.to_dict())
248
247
  downloaded += 1
249
- if downloaded > 0:
250
- clean_downloaded_firmwares(firmware_folder)
248
+ # if downloaded > 0:
249
+ # clean_downloaded_firmwares(firmware_folder)
251
250
  log.success(f"Downloaded {downloaded} firmwares, skipped {skipped} existing files.")
252
251
  return downloaded + skipped
253
252
 
mpflash/downloaded.py CHANGED
@@ -39,6 +39,7 @@ def clean_downloaded_firmwares(fw_folder: Path) -> None:
39
39
  writer.write(fw.to_dict())
40
40
  log.info(f"Removed duplicate entries from firmware.jsonl in {fw_folder}")
41
41
 
42
+
42
43
  def find_downloaded_firmware(
43
44
  *,
44
45
  board_id: str,
@@ -53,6 +54,7 @@ def find_downloaded_firmware(
53
54
  selector = {}
54
55
  fw_folder = fw_folder or config.firmware_folder
55
56
  # Use the information in firmwares.jsonl to find the firmware file
57
+ log.debug(f"{trie}] Looking for firmware for {board_id} {version} ")
56
58
  fw_list = downloaded_firmwares(fw_folder)
57
59
  if not fw_list:
58
60
  log.error("No firmware files found. Please download the firmware first.")
@@ -101,13 +103,14 @@ def filter_downloaded_fwlist(
101
103
  # never get a preview for an older version
102
104
  fw_list = [fw for fw in fw_list if fw.preview]
103
105
  else:
104
- # FWInfo version has no v1.2.3 prefix
105
- _version = clean_version(version, drop_v=True)
106
- fw_list = [fw for fw in fw_list if fw.version == _version]
107
-
106
+ # older FWInfo version has no v1.2.3 prefix
107
+ either = [clean_version(version, drop_v=False), clean_version(version, drop_v=True)]
108
+ fw_list = [fw for fw in fw_list if fw.version in either]
109
+ log.trace(f"Filtering firmware for {version} : {len(fw_list)} found.")
108
110
  # filter by port
109
111
  if port:
110
112
  fw_list = [fw for fw in fw_list if fw.port == port]
113
+ log.trace(f"Filtering firmware for {port} : {len(fw_list)} found.")
111
114
 
112
115
  if board_id:
113
116
  if variants:
@@ -116,6 +119,7 @@ def filter_downloaded_fwlist(
116
119
  else:
117
120
  # the firmware variant should match exactly the board_id
118
121
  fw_list = [fw for fw in fw_list if fw.variant == board_id]
122
+ log.trace(f"Filtering firmware for {board_id} : {len(fw_list)} found.")
119
123
  if selector and port in selector:
120
124
  fw_list = [fw for fw in fw_list if fw.filename.endswith(selector[port])]
121
125
  return fw_list
mpflash/errors.py CHANGED
@@ -2,4 +2,8 @@
2
2
 
3
3
 
4
4
  class MPFlashError(Exception):
5
- pass
5
+ """Base class for exceptions in this module."""
6
+
7
+ def __init__(self, message: str):
8
+ self.message = message
9
+ super().__init__(message)
mpflash/flash.py CHANGED
@@ -1,10 +1,9 @@
1
- import time
2
1
  from pathlib import Path
3
2
 
4
3
  from loguru import logger as log
5
4
 
6
- from mpflash.common import PORT_FWTYPES
7
- from mpflash.mpremoteboard import MPRemoteBoard
5
+ from mpflash.bootloader import enter_bootloader
6
+ from mpflash.common import PORT_FWTYPES, BootloaderMethod
8
7
 
9
8
  from .flash_esp import flash_esp
10
9
  from .flash_stm32 import flash_stm32
@@ -14,13 +13,15 @@ from .worklist import WorkList
14
13
  # #########################################################################################################
15
14
 
16
15
 
16
+
17
17
  def flash_list(
18
18
  todo: WorkList,
19
19
  fw_folder: Path,
20
20
  erase: bool,
21
- bootloader: bool,
21
+ bootloader: BootloaderMethod,
22
22
  ):
23
23
  """Flash a list of boards with the specified firmware."""
24
+ UF2_PORTS = [port for port, exts in PORT_FWTYPES.items() if ".uf2" in exts]
24
25
  flashed = []
25
26
  for mcu, fw_info in todo:
26
27
  fw_file = fw_folder / fw_info.filename
@@ -30,14 +31,13 @@ def flash_list(
30
31
  log.info(f"Updating {mcu.board} on {mcu.serialport} to {fw_info.version}")
31
32
  updated = None
32
33
  # try:
33
- if mcu.port in [port for port, exts in PORT_FWTYPES.items() if ".uf2" in exts] and fw_file.suffix == ".uf2":
34
- # any .uf2 port ["samd", "rp2", "nrf"]:
35
- if bootloader:
36
- enter_bootloader(mcu)
34
+ if mcu.port in UF2_PORTS and fw_file.suffix == ".uf2":
35
+ if not enter_bootloader(mcu, bootloader):
36
+ continue
37
37
  updated = flash_uf2(mcu, fw_file=fw_file, erase=erase)
38
38
  elif mcu.port in ["stm32"]:
39
- if bootloader:
40
- enter_bootloader(mcu)
39
+ if not enter_bootloader(mcu, bootloader):
40
+ continue
41
41
  updated = flash_stm32(mcu, fw_file, erase=erase)
42
42
  elif mcu.port in ["esp32", "esp8266"]:
43
43
  # bootloader is handled by esptool for esp32/esp8266
@@ -50,20 +50,3 @@ def flash_list(
50
50
  else:
51
51
  log.error(f"Failed to flash {mcu.board} on {mcu.serialport}")
52
52
  return flashed
53
-
54
-
55
- def enter_bootloader(mcu: MPRemoteBoard, timeout: int = 10, wait_after: int = 2):
56
- """Enter the bootloader mode for the board"""
57
- log.info(f"Entering bootloader on {mcu.board} on {mcu.serialport}")
58
- mcu.run_command("bootloader", timeout=timeout)
59
- time.sleep(wait_after)
60
-
61
-
62
- # TODO:
63
- # flash from some sort of queue to allow different images to be flashed to the same board
64
- # - flash variant 1
65
- # - stub variant 1
66
- # - flash variant 2
67
- # - stub variant 2
68
- #
69
- # JIT download / download any missing firmwares based on the detected boards
@@ -1,4 +1,5 @@
1
1
  from pathlib import Path
2
+
2
3
  from loguru import logger as log
3
4
 
4
5
 
@@ -10,5 +11,5 @@ def get_board_id(path: Path):
10
11
  for line in data:
11
12
  if line.startswith("Board-ID"):
12
13
  board_id = line[9:].strip()
13
- log.trace(f"Found Board-ID={board_id}")
14
+ log.debug(f"INFO_UF2.TXT Board-ID={board_id}")
14
15
  return board_id
@@ -3,15 +3,12 @@
3
3
  # sourcery skip: snake-case-functions
4
4
  from __future__ import annotations
5
5
 
6
- import sys
7
6
  import time
8
7
  from pathlib import Path
9
8
  from typing import Optional
10
9
 
11
- from loguru import logger as log
12
10
  from rich.progress import track
13
11
 
14
- from .flash_uf2_boardid import get_board_id
15
12
 
16
13
 
17
14
  def wait_for_UF2_macos(s_max: int = 10) -> Optional[Path]:
@@ -0,0 +1,91 @@
1
+ # Copyright (c) 2016 Robbin Bonthond
2
+
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+
10
+ # The above copyright notice and this permission notice shall be included in all
11
+ # copies or substantial portions of the Software.
12
+ # ------------------------------------------------------------------------------------
13
+ # modified to avoid conflcts with rich_click
14
+
15
+ # sourcery skip: assign-if-exp, use-named-expression
16
+
17
+ import rich_click as click
18
+
19
+ _click7 = click.__version__[0] >= "7"
20
+
21
+
22
+ class ClickAliasedGroup(click.RichGroup):
23
+ def __init__(self, *args, **kwargs):
24
+ super().__init__(*args, **kwargs)
25
+ self._commands = {}
26
+ self._aliases = {}
27
+
28
+ def add_command(self, *args, **kwargs):
29
+ aliases = kwargs.pop("aliases", [])
30
+ super().add_command(*args, **kwargs)
31
+ if aliases:
32
+ cmd = args[0]
33
+ name = args[1] if len(args) > 1 else None
34
+ name = name or cmd.name
35
+ if name is None:
36
+ raise TypeError("Command has no name.")
37
+
38
+ self._commands[name] = aliases
39
+ for alias in aliases:
40
+ self._aliases[alias] = cmd.name
41
+
42
+ def command(self, *args, **kwargs):
43
+ aliases = kwargs.pop("aliases", [])
44
+ decorator = super().command(*args, **kwargs)
45
+ if not aliases:
46
+ return decorator
47
+
48
+ def _decorator(f):
49
+ cmd = decorator(f)
50
+ if aliases:
51
+ self._commands[cmd.name] = aliases
52
+ for alias in aliases:
53
+ self._aliases[alias] = cmd.name
54
+ return cmd
55
+
56
+ return _decorator
57
+
58
+ def group(self, *args, **kwargs):
59
+ aliases = kwargs.pop("aliases", [])
60
+ decorator = super().group(*args, **kwargs)
61
+ if not aliases:
62
+ return decorator
63
+
64
+ def _decorator(f):
65
+ cmd = decorator(f)
66
+ if aliases:
67
+ self._commands[cmd.name] = aliases
68
+ for alias in aliases:
69
+ self._aliases[alias] = cmd.name
70
+ return cmd
71
+
72
+ return _decorator
73
+
74
+ def resolve_alias(self, cmd_name):
75
+ if cmd_name in self._aliases:
76
+ return self._aliases[cmd_name]
77
+ return cmd_name
78
+
79
+ def get_command(self, ctx, cmd_name):
80
+ cmd_name = self.resolve_alias(cmd_name)
81
+ command = super().get_command(ctx, cmd_name)
82
+ if command:
83
+ return command
84
+ return None
85
+
86
+ # def format_commands(self, ctx, formatter):
87
+ # TODO: output alias with commands - but that is a significant re-write
88
+ # for now add alias to help text
89
+
90
+
91
+ # ------------------------------------------------------------------------------------
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mpflash
3
- Version: 0.8.2
3
+ Version: 0.8.4
4
4
  Summary: Flash and download tool for MicroPython firmwares
5
5
  Home-page: https://github.com/Josverl/micropython-stubber/blob/main/src/mpflash/README.md
6
6
  License: MIT
@@ -1,26 +1,30 @@
1
1
  mpflash/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  mpflash/add_firmware.py,sha256=u_g9mID557fptLEJ1Nld9n27V1R1og8uEkadm0O3YTw,3435
3
3
  mpflash/ask_input.py,sha256=e54WfgktG7Ff8dBUknXioROq3lPNZ-eacUjfmUvFfS0,8496
4
- mpflash/cli_download.py,sha256=6ctC4ga6q2LcpHUHMrz1trbePziQpuIFPbjcJnc3K7E,3645
5
- mpflash/cli_flash.py,sha256=QHEb-e2ABjISfuGCWpTxOM7kkcTZSQxBXmVaWh3WVnE,7074
6
- mpflash/cli_group.py,sha256=nrihkm74X8pX1jPM72W8ljoc5rYL6ggGOQFRaOldhLo,2038
7
- mpflash/cli_list.py,sha256=Mdaf13gKZCoLp8Y2ja0L5rYMzkE_t3d4r62bF7isI3E,1997
8
- mpflash/cli_main.py,sha256=yABFFf45TpPMcC1qEVARAWe4EI9zui2pUXjoPms0mq8,1030
9
- mpflash/common.py,sha256=MmyLadb_z7bhVy6U8jb4f3pnBK8n7x28smFBcA9ZfGg,5608
4
+ mpflash/bootloader/__init__.py,sha256=NRi_zWLNhstoUeisXlknrNyrCcEaHrzEkTxnrmUg1m0,1304
5
+ mpflash/bootloader/manual.py,sha256=4FNQg0JpyRmfISrDDfZ5j9iQBK-Jyrnz8nLqKf1ZEuc,3104
6
+ mpflash/bootloader/micropython.py,sha256=QQJlXUnyVA4cRS2O4GbrQhpKhFFvxtQC5G6WQcSCbp8,409
7
+ mpflash/bootloader/touch1200.py,sha256=AJ4GPLtvCR5LDDe5Uu_5Rg8OI2JHxEB-blzCcO5x9Tc,1511
8
+ mpflash/cli_download.py,sha256=zufBC0xNPZCq28mG_fepupziizbm86wTpfNEszkei9Q,3656
9
+ mpflash/cli_flash.py,sha256=ITd9eJzvdtr6SeeB3PO5Ja2jG4TFxsW7JfL3pCNJBn0,7288
10
+ mpflash/cli_group.py,sha256=gvUaQM-4wk6fV50YxkMJeYEt3D0ZsQJM7t86T8xOpWE,2139
11
+ mpflash/cli_list.py,sha256=_3XzR8RyjlojbOqGKm_TLwcQojCb4OgCYi7mcjjoflI,2046
12
+ mpflash/cli_main.py,sha256=MHhU1cAYEhwOpHG2p3OEfwpSrn9-J2foGVZqby_HO8k,1134
13
+ mpflash/common.py,sha256=gkj71hGibMEcmThJ5LSrAV5vp6SpJAJNLHWO4GjB040,5929
10
14
  mpflash/config.py,sha256=R7PomStIFHSTnLhpWhw9P2DL_b-8a8Wrja3aY-dPm6A,762
11
15
  mpflash/connected.py,sha256=CBG_DJ33OPWAPbX-ICQpL1LcFOhNYpLUSB0Q5v7gi9s,3029
12
- mpflash/download.py,sha256=NErA2SJEhXnTElvq6z4BJl-L-5M_ETJpqX6pM0oubsg,14290
13
- mpflash/downloaded.py,sha256=lxwRP-7gE9TO23jKG5Ov6TCsRA1kM8rzZEdceA163Uk,4520
14
- mpflash/errors.py,sha256=Q5LR12Wo8iUCg5n_qq4GjdBdBflbvCOdKsRJ5InYRfI,96
15
- mpflash/flash.py,sha256=JoskuwaHVYqeG4YW8kgbv26vPFnqDmkTz1VRs-pTRiY,2468
16
+ mpflash/download.py,sha256=VFV0XP8mTnBXOeo_JbyHBjIQJ0PUUiXu8hoO0lAxid8,14235
17
+ mpflash/downloaded.py,sha256=cHq_fWUuZ0sXOCs5d3NIf3etUDsAXtnwnO7tkRqJE9A,4870
18
+ mpflash/errors.py,sha256=IAidY3qkZsXy6Pm1rdmVFmGyg81ywHhse3itaPctA2w,247
19
+ mpflash/flash.py,sha256=U17kp3ftG-noqhgA1HSYxK945sLhIR-Rh3xZgckT4H8,1927
16
20
  mpflash/flash_esp.py,sha256=dEc_B7-f1BMUGBMrwIm83ulcCqaS5MlrPAp3FCNgNfk,2311
17
21
  mpflash/flash_stm32.py,sha256=d4BoQl3a9Tchnvn2ZTuq2MpYBB4MTaRukwtEncI95k0,823
18
22
  mpflash/flash_stm32_cube.py,sha256=w7aGWjReeWUKl0Q3ZjXH8BRqNO1Tk9AO7gtRNUg1c9Y,3970
19
23
  mpflash/flash_stm32_dfu.py,sha256=G70EZodWb-aRi507Jxbys-VEwbBGU1oZacow3_nq-d4,2972
20
24
  mpflash/flash_uf2.py,sha256=2IKgEOnlsB9bILJWHILMKbGo9b39NKazBWp1-T_eKKs,2463
21
- mpflash/flash_uf2_boardid.py,sha256=WZKucGu_hJ8ymb236uuZbiR6pD6AA_l4LA-7LwtQhq8,414
25
+ mpflash/flash_uf2_boardid.py,sha256=U5wGM8VA3wEpUxQCMtuXpMZZomdVH8J_Zd5_GekUMuU,423
22
26
  mpflash/flash_uf2_linux.py,sha256=Oy9V4g7JSuks2hHFeO_OHdBKSGktbqZOtsivuxfl-xg,4055
23
- mpflash/flash_uf2_macos.py,sha256=HGUg2HSw4qalfSP7npLYgos2WlVRxtOVTDcAnBL7qPY,1078
27
+ mpflash/flash_uf2_macos.py,sha256=fGz-LGoYKX772sqJb4pAvcMw-2Orlc83ecOIk0-1GYc,987
24
28
  mpflash/flash_uf2_windows.py,sha256=a-iIGPkwIoUXA7atCz0_uZp-kipSL24P-IE5ka1B1Mk,1025
25
29
  mpflash/list.py,sha256=0TawTkwhjKPPj7nTHoDn8nQ54WOkGRurP1BJVeg956g,2963
26
30
  mpflash/logger.py,sha256=dI_H_a7EOdQJyvoeRHQuYeZuTKYVUS3DUPTLhE9rkdM,1098
@@ -35,13 +39,14 @@ mpflash/mpremoteboard/mpy_fw_info.py,sha256=6AQbN3jtQgllqWQYl4e-63KeEtV08EXk8_Jn
35
39
  mpflash/mpremoteboard/runner.py,sha256=-PgzAeBGbyXaAUlwyiw4mcINsP2U1XRRjP1_QdBrxpg,4786
36
40
  mpflash/uf2disk.py,sha256=4_P2l-kedM7VSliA2u706LQLxvu3xWSod1-lj-xjZis,298
37
41
  mpflash/vendor/basicgit.py,sha256=sflgCv7apLbV2w8F6gmhc-3kuqDnnS4tdGol6JT2uTM,9545
42
+ mpflash/vendor/click_aliases.py,sha256=c853EHSlkE2DvFqeFvFpwXKuJj3_jsXDP7iotVOKaAw,3156
38
43
  mpflash/vendor/dfu.py,sha256=ZXMcE6aH4-43Wh4tbQT4U-q-BU3RUiL3JAxmP_QAK2s,5755
39
44
  mpflash/vendor/pydfu.py,sha256=_MdBRo1EeNeKDqFPSTB5tNL1jGSBJgsVeVjE5e7Pb8s,20542
40
45
  mpflash/vendor/readme.md,sha256=iIIZxuLUIGHQ0KODzYVtMezsztvyxCXcNJp_AzwTIPk,86
41
46
  mpflash/vendor/versions.py,sha256=thk1a5wEEhXIQoL0zZ7oooeFyQeSoI00CIUbZF0b3rI,3783
42
47
  mpflash/worklist.py,sha256=MKHDynttVP3lsHSfb7DEqeQ2mRV0da96lD4lIcS_zQY,5962
43
- mpflash-0.8.2.dist-info/entry_points.txt,sha256=Jk_visOhYOsZIcSP2Ms9hKqfKy1iorR-6dYltSoWCpY,52
44
- mpflash-0.8.2.dist-info/LICENSE,sha256=mWpNhsIxWzetYNnTpr4eb3HtgsxGIC8KcYWxXEcxQvE,1077
45
- mpflash-0.8.2.dist-info/METADATA,sha256=N1kDSCr4LilmXiMUdcs190wEJgNm_X8Vshh5D4GCRLo,15275
46
- mpflash-0.8.2.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
47
- mpflash-0.8.2.dist-info/RECORD,,
48
+ mpflash-0.8.4.dist-info/entry_points.txt,sha256=Jk_visOhYOsZIcSP2Ms9hKqfKy1iorR-6dYltSoWCpY,52
49
+ mpflash-0.8.4.dist-info/LICENSE,sha256=mWpNhsIxWzetYNnTpr4eb3HtgsxGIC8KcYWxXEcxQvE,1077
50
+ mpflash-0.8.4.dist-info/METADATA,sha256=wjudSFDz_BU3eYbqjjnxd68dCHfzaZpnR37MD2UCh9s,15275
51
+ mpflash-0.8.4.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
52
+ mpflash-0.8.4.dist-info/RECORD,,