mpflash 0.5.0__py3-none-any.whl → 0.7.0__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.
mpflash/ask_input.py CHANGED
@@ -1,15 +1,20 @@
1
- """Download input handling for mpflash."""
1
+ """
2
+ Interactive input for mpflash.
3
+
4
+ Note: The prompts can use "{version}" and "{action}" to insert the version and action in the prompt without needing an f-string.
5
+ The values are provided from the answers dictionary.
6
+ """
2
7
 
3
8
  from dataclasses import dataclass, field
4
9
  from pathlib import Path
5
- from typing import List, Sequence, Tuple, Union
10
+ from typing import Dict, List, Sequence, Tuple, Union
6
11
 
7
12
  from loguru import logger as log
8
13
 
9
- from mpflash.common import micropython_versions
10
14
  from mpflash.config import config
11
- from mpflash.mpboard_id.api import known_mp_boards, known_mp_ports
15
+ from mpflash.mpboard_id import known_stored_boards, local_mp_ports
12
16
  from mpflash.mpremoteboard import MPRemoteBoard
17
+ from mpflash.vendor.versions import micropython_versions
13
18
 
14
19
 
15
20
  @dataclass
@@ -42,6 +47,16 @@ def ask_missing_params(
42
47
  params: ParamType,
43
48
  action: str = "download",
44
49
  ) -> ParamType:
50
+ """
51
+ Asks the user for parameters that have not been supplied on the commandline and returns the updated params.
52
+
53
+ Args:
54
+ params (ParamType): The parameters to be updated.
55
+ action (str, optional): The action to be performed. Defaults to "download".
56
+
57
+ Returns:
58
+ ParamType: The updated parameters.
59
+ """
45
60
  if not config.interactive:
46
61
  # no interactivity allowed
47
62
  return params
@@ -49,25 +64,33 @@ def ask_missing_params(
49
64
  import inquirer
50
65
 
51
66
  questions = []
52
- if isinstance(params, FlashParams) and (not params.serial or "?" in params.versions):
53
- ask_serialport(questions, action=action)
67
+ answers = {"action": action}
68
+ if isinstance(params, FlashParams):
69
+ if not params.serial or "?" in params.serial:
70
+ ask_serialport(questions, action=action)
71
+ else:
72
+ answers["serial"] = params.serial
54
73
 
55
74
  if not params.versions or "?" in params.versions:
56
75
  ask_versions(questions, action=action)
76
+ else:
77
+ # versions is used to show only the boards for the selected versions
78
+ answers["versions"] = params.versions # type: ignore
57
79
 
58
80
  if not params.boards or "?" in params.boards:
59
81
  ask_port_board(questions, action=action)
60
82
 
61
- answers = inquirer.prompt(questions)
83
+ answers = inquirer.prompt(questions, answers=answers)
62
84
  if not answers:
63
- return params
85
+ # input cancelled by user
86
+ return [] # type: ignore
64
87
  # print(repr(answers))
65
88
  if isinstance(params, FlashParams) and "serial" in answers:
66
89
  params.serial = answers["serial"]
67
90
  if "port" in answers:
68
91
  params.ports = [answers["port"]]
69
92
  if "boards" in answers:
70
- params.boards = answers["boards"]
93
+ params.boards = answers["boards"] if isinstance(answers["boards"], list) else [answers["boards"]]
71
94
  if "versions" in answers:
72
95
  # make sure it is a list
73
96
  params.versions = answers["versions"] if isinstance(answers["versions"], list) else [answers["versions"]]
@@ -77,20 +100,29 @@ def ask_missing_params(
77
100
  return params
78
101
 
79
102
 
80
- def some_boards(answers: dict) -> Sequence[Tuple[str, str]]:
103
+ def filter_matching_boards(answers: dict) -> Sequence[Tuple[str, str]]:
104
+ """
105
+ Filters the known boards based on the selected versions and returns the filtered boards.
106
+
107
+ Args:
108
+ answers (dict): The user's answers.
109
+
110
+ Returns:
111
+ Sequence[Tuple[str, str]]: The filtered boards.
112
+ """
113
+ # if version is not asked ; then need to get the version from the inputs
81
114
  if "versions" in answers:
82
115
  _versions = list(answers["versions"])
83
116
  if "stable" in _versions:
84
117
  _versions.remove("stable")
85
- _versions.append(micropython_versions()[-2])
118
+ _versions.append(micropython_versions()[-2]) # latest stable
86
119
  if "preview" in _versions:
87
120
  _versions.remove("preview")
88
- _versions.append(micropython_versions()[-1])
89
- _versions.append(micropython_versions()[-2])
121
+ _versions.extend((micropython_versions()[-1], micropython_versions()[-2])) # latest preview and stable
90
122
 
91
- some_boards = known_mp_boards(answers["port"], _versions) # or known_mp_boards(answers["port"])
123
+ some_boards = known_stored_boards(answers["port"], _versions) # or known_mp_boards(answers["port"])
92
124
  else:
93
- some_boards = known_mp_boards(answers["port"])
125
+ some_boards = known_stored_boards(answers["port"])
94
126
 
95
127
  if some_boards:
96
128
  # Create a dictionary where the keys are the second elements of the tuples
@@ -104,21 +136,37 @@ def some_boards(answers: dict) -> Sequence[Tuple[str, str]]:
104
136
 
105
137
 
106
138
  def ask_port_board(questions: list, *, action: str):
139
+ """
140
+ Asks the user for the port and board selection.
141
+
142
+ Args:
143
+ questions (list): The list of questions to be asked.
144
+ action (str): The action to be performed.
145
+
146
+ Returns:
147
+ None
148
+ """
107
149
  # import only when needed to reduce load time
108
150
  import inquirer
109
151
 
152
+ # TODO: if action = flash, Use Inquirer.List for boards
153
+ inquirer_ux = inquirer.Checkbox if action == "download" else inquirer.List
110
154
  questions.extend(
111
155
  (
112
156
  inquirer.List(
113
157
  "port",
114
- message=f"What port do you want to {action}?",
115
- choices=known_mp_ports(),
158
+ message="Which port do you want to {action} " + "to {serial} ?" if action == "flash" else "?",
159
+ choices=local_mp_ports(),
116
160
  autocomplete=True,
117
161
  ),
118
- inquirer.Checkbox(
162
+ inquirer_ux(
119
163
  "boards",
120
- message=f"What board do you want to {action}?",
121
- choices=some_boards,
164
+ message=(
165
+ "Which {port} board firmware do you want to {action} " + "to {serial} ?"
166
+ if action == "flash"
167
+ else "?"
168
+ ),
169
+ choices=filter_matching_boards,
122
170
  validate=lambda _, x: True if x else "Please select at least one board", # type: ignore
123
171
  ),
124
172
  )
@@ -126,6 +174,16 @@ def ask_port_board(questions: list, *, action: str):
126
174
 
127
175
 
128
176
  def ask_versions(questions: list, *, action: str):
177
+ """
178
+ Asks the user for the version selection.
179
+
180
+ Args:
181
+ questions (list): The list of questions to be asked.
182
+ action (str): The action to be performed.
183
+
184
+ Returns:
185
+ None
186
+ """
129
187
  # import only when needed to reduce load time
130
188
  import inquirer
131
189
 
@@ -136,8 +194,11 @@ def ask_versions(questions: list, *, action: str):
136
194
  mp_versions.reverse() # newest first
137
195
  questions.append(
138
196
  input_ux(
197
+ # inquirer.List(
139
198
  "versions",
140
- message=f"What version(s) do you want to {action}?",
199
+ message="Which version(s) do you want to {action} " + ("to {serial} ?" if action == "flash" else "?"),
200
+ # Hints would be nice , but needs a hint for each and every option
201
+ # hints=["Use space to select multiple options"],
141
202
  choices=mp_versions,
142
203
  autocomplete=True,
143
204
  validate=lambda _, x: True if x else "Please select at least one version", # type: ignore
@@ -146,6 +207,16 @@ def ask_versions(questions: list, *, action: str):
146
207
 
147
208
 
148
209
  def ask_serialport(questions: list, *, action: str):
210
+ """
211
+ Asks the user for the serial port selection.
212
+
213
+ Args:
214
+ questions (list): The list of questions to be asked.
215
+ action (str): The action to be performed.
216
+
217
+ Returns:
218
+ None
219
+ """
149
220
  # import only when needed to reduce load time
150
221
  import inquirer
151
222
 
@@ -153,10 +224,10 @@ def ask_serialport(questions: list, *, action: str):
153
224
  questions.append(
154
225
  inquirer.List(
155
226
  "serial",
156
- message="What serial port do you want use ?",
157
- validate=lambda _, x: True if x else "Please enter a serial port", # type: ignore
227
+ message="Which serial port do you want to {action} ?",
158
228
  choices=serialports,
159
229
  other=True,
230
+ validate=lambda _, x: True if x else "Please select or enter a serial port", # type: ignore
160
231
  )
161
232
  )
162
233
 
mpflash/cli_download.py CHANGED
@@ -4,8 +4,10 @@ from pathlib import Path
4
4
  from typing import List, Tuple
5
5
 
6
6
  import rich_click as click
7
+ from loguru import logger as log
7
8
 
8
- from mpflash.common import clean_version
9
+ from mpflash.mpboard_id import find_stored_board
10
+ from mpflash.vendor.versions import clean_version
9
11
 
10
12
  from .ask_input import DownloadParams, ask_missing_params
11
13
  from .cli_group import cli
@@ -14,13 +16,6 @@ from .config import config
14
16
  from .download import download
15
17
 
16
18
 
17
- def connected_ports_boards() -> Tuple[List[str], List[str]]:
18
- mpr_boards = list_mcus()
19
- ports = list({b.port for b in mpr_boards})
20
- boards = list({b.board for b in mpr_boards})
21
- return ports, boards
22
-
23
-
24
19
  @cli.command(
25
20
  "download",
26
21
  help="Download MicroPython firmware for specific ports, boards and versions.",
@@ -49,7 +44,7 @@ def connected_ports_boards() -> Tuple[List[str], List[str]]:
49
44
  "-b",
50
45
  "boards",
51
46
  multiple=True,
52
- default=["?"],
47
+ default=[],
53
48
  show_default=True,
54
49
  help="The board(s) to download the firmware for.",
55
50
  metavar="BOARD_ID or ?",
@@ -71,16 +66,21 @@ def cli_download(
71
66
  **kwargs,
72
67
  ):
73
68
  params = DownloadParams(**kwargs)
74
-
75
- if not params.boards:
76
- # nothing specified - detect connected boards
69
+ params.versions = list(params.versions)
70
+ params.boards = list(params.boards)
71
+ if params.boards:
72
+ pass
73
+ # TODO Clean board - same as in cli_flash.py
74
+ else:
75
+ # no boards specified - detect connected boards
77
76
  params.ports, params.boards = connected_ports_boards()
78
- # ask for any remaining parameters
77
+
79
78
  params = ask_missing_params(params, action="download")
79
+ if not params: # Cancelled by user
80
+ exit(1)
81
+ params.versions = [clean_version(v, drop_v=True) for v in params.versions]
80
82
  assert isinstance(params, DownloadParams)
81
83
 
82
- params.versions = [clean_version(v, drop_v=True) for v in params.versions] # remove leading v from version
83
-
84
84
  download(
85
85
  params.fw_folder,
86
86
  params.ports,
@@ -89,3 +89,19 @@ def cli_download(
89
89
  params.force,
90
90
  params.clean,
91
91
  )
92
+
93
+
94
+ def connected_ports_boards() -> Tuple[List[str], List[str]]:
95
+ """
96
+ Returns a tuple containing lists of unique ports and boards from the connected MCUs.
97
+ Boards that are physically connected, but give no tangible response are ignored.
98
+
99
+ Returns:
100
+ A tuple containing two lists:
101
+ - A list of unique ports where MCUs are connected.
102
+ - A list of unique board names of the connected MCUs.
103
+ """
104
+ mpr_boards = [b for b in list_mcus() if b.connected]
105
+ ports = list({b.port for b in mpr_boards})
106
+ boards = list({b.board for b in mpr_boards})
107
+ return ports, boards
mpflash/cli_flash.py CHANGED
@@ -1,21 +1,19 @@
1
1
  from pathlib import Path
2
- from typing import List
3
2
 
4
3
  import rich_click as click
5
4
  from loguru import logger as log
6
5
 
6
+ from mpflash.errors import MPFlashError
7
+ from mpflash.mpboard_id import find_stored_board
8
+ from mpflash.vendor.versions import clean_version
9
+
7
10
  from .ask_input import FlashParams, ask_missing_params
8
11
  from .cli_download import connected_ports_boards
9
12
  from .cli_group import cli
10
13
  from .cli_list import show_mcus
11
- from .common import clean_version
12
14
  from .config import config
13
- from .flash import WorkList, auto_update, enter_bootloader, find_firmware
14
- from .flash_esp import flash_esp
15
- from .flash_stm32 import flash_stm32
16
- from .flash_uf2 import flash_uf2
17
- from .mpboard_id.api import find_mp_board
18
- from .mpremoteboard import MPRemoteBoard
15
+ from .flash import flash_list
16
+ from .worklist import MPRemoteBoard, WorkList, full_auto_worklist, manual_worklist, single_auto_worklist
19
17
 
20
18
  # #########################################################################################################
21
19
  # CLI
@@ -67,9 +65,8 @@ from .mpremoteboard import MPRemoteBoard
67
65
  @click.option(
68
66
  "--board",
69
67
  "-b",
70
- "boards",
68
+ "board", # single board
71
69
  multiple=False,
72
- default=[],
73
70
  help="The MicroPython board ID to flash. If not specified will try to read the BOARD_ID from the connected MCU.",
74
71
  metavar="BOARD_ID or ?",
75
72
  )
@@ -95,131 +92,79 @@ from .mpremoteboard import MPRemoteBoard
95
92
  help="""Enter micropython bootloader mode before flashing.""",
96
93
  )
97
94
  def cli_flash_board(**kwargs):
98
- todo: WorkList = []
95
+ # version to versions, board to boards
96
+ kwargs["versions"] = [kwargs.pop("version")] if kwargs["version"] != None else []
97
+ if kwargs["board"] is None:
98
+ kwargs["boards"] = []
99
+ kwargs.pop("board")
100
+ else:
101
+ kwargs["boards"] = [kwargs.pop("board")]
99
102
 
100
- # version to versions
101
- if "version" in kwargs:
102
- kwargs["versions"] = [kwargs.pop("version")]
103
103
  params = FlashParams(**kwargs)
104
- print(f"{params=}")
105
- # print(f"{params.version=}")
106
- print(f"{params.versions=}")
107
- if not params.boards:
104
+ if not params.boards or params.boards == []:
108
105
  # nothing specified - detect connected boards
109
106
  params.ports, params.boards = connected_ports_boards()
107
+ if params.boards == []:
108
+ # No MicroPython boards detected, but it could be unflashed or not in bootloader mode
109
+ # Ask for serial port and board_id to flash
110
+ params.serial = "?"
111
+ params.boards = ["?"]
112
+ else:
113
+ for board_id in params.boards:
114
+ if board_id == "":
115
+ params.boards.remove(board_id)
116
+ continue
117
+ if " " in board_id:
118
+ try:
119
+ info = find_stored_board(board_id)
120
+ if info:
121
+ log.info(f"Resolved board description: {info['board']}")
122
+ params.boards.remove(board_id)
123
+ params.boards.append(info["board"])
124
+ except Exception as e:
125
+ log.warning(f"unable to resolve board description: {e}")
126
+
110
127
  # Ask for missing input if needed
111
128
  params = ask_missing_params(params, action="flash")
129
+ if not params: # Cancelled by user
130
+ exit(1)
112
131
  # TODO: Just in time Download of firmware
113
132
 
114
133
  assert isinstance(params, FlashParams)
115
134
 
116
135
  if len(params.versions) > 1:
117
- print(repr(params.versions))
118
136
  log.error(f"Only one version can be flashed at a time, not {params.versions}")
119
- return
137
+ raise MPFlashError("Only one version can be flashed at a time")
138
+ # if len(params.boards) > 1:
139
+ # log.error(f"Only one board can be flashed at a time, not {params.boards}")
140
+ # raise MPFlashError("Only one board can be flashed at a time")
141
+
120
142
  params.versions = [clean_version(v) for v in params.versions]
121
- if params.versions[0] and params.boards[0] and params.serial:
122
- # update a single board
123
- todo = manual_worklist(
143
+ worklist: WorkList = []
144
+ # if serial port == auto and there are one or more specified/detected boards
145
+ if params.serial == "auto" and params.boards:
146
+ worklist = full_auto_worklist(version=params.versions[0], fw_folder=params.fw_folder)
147
+ elif params.versions[0] and params.boards[0] and params.serial:
148
+ # A single serial port including the board / variant
149
+ worklist = manual_worklist(
124
150
  params.versions[0],
125
151
  params.fw_folder,
126
152
  params.serial,
127
153
  params.boards[0],
128
- # params.ports[0],
129
154
  )
130
- elif params.serial:
131
- if params.serial == "auto":
132
- # Update all micropython boards to the latest version
133
- todo = auto_worklist(params.versions[0], params.fw_folder)
134
- else:
135
- # just this serial port on auto
136
- todo = oneport_worklist(
137
- params.versions[0],
138
- params.fw_folder,
139
- params.serial,
140
- )
155
+ else:
156
+ # just this serial port on auto
157
+ worklist = single_auto_worklist(
158
+ serial_port=params.serial,
159
+ version=params.versions[0],
160
+ fw_folder=params.fw_folder,
161
+ )
141
162
 
142
163
  if flashed := flash_list(
143
- todo,
164
+ worklist,
144
165
  params.fw_folder,
145
166
  params.erase,
146
167
  params.bootloader,
147
168
  ):
148
169
  log.info(f"Flashed {len(flashed)} boards")
149
- show_mcus(flashed, title="Connected boards after flashing")
150
-
151
-
152
- def oneport_worklist(
153
- version: str,
154
- fw_folder: Path,
155
- serial_port: str,
156
- # preview: bool,
157
- ) -> WorkList:
158
- """Create a worklist for a single serial-port."""
159
- conn_boards = [MPRemoteBoard(serial_port)]
160
- todo = auto_update(conn_boards, version, fw_folder) # type: ignore # List / list
161
- show_mcus(conn_boards) # type: ignore
162
- return todo
163
-
164
-
165
- def auto_worklist(version: str, fw_folder: Path) -> WorkList:
166
- conn_boards = [MPRemoteBoard(sp) for sp in MPRemoteBoard.connected_boards() if sp not in config.ignore_ports]
167
- return auto_update(conn_boards, version, fw_folder) # type: ignore
168
-
169
-
170
- def manual_worklist(
171
- version: str,
172
- fw_folder: Path,
173
- serial_port: str,
174
- board: str,
175
- # port: str,
176
- ) -> WorkList:
177
- mcu = MPRemoteBoard(serial_port)
178
- # TODO : Find a way to avoid needing to specify the port
179
- # Lookup the matching port and cpu in board_info based in the board name
180
- port = find_mp_board(board)["port"]
181
- mcu.port = port
182
- mcu.cpu = port if port.startswith("esp") else ""
183
- mcu.board = board
184
- firmwares = find_firmware(fw_folder=fw_folder, board=board, version=version, port=port)
185
- if not firmwares:
186
- log.error(f"No firmware found for {port} {board} version {version}")
187
- return []
188
- # use the most recent matching firmware
189
- return [(mcu, firmwares[-1])] # type: ignore
190
-
191
-
192
- def flash_list(
193
- todo: WorkList,
194
- fw_folder: Path,
195
- erase: bool,
196
- bootloader: bool,
197
- ):
198
- """Flash a list of boards with the specified firmware."""
199
- flashed = []
200
- for mcu, fw_info in todo:
201
- fw_file = fw_folder / fw_info["filename"] # type: ignore
202
- if not fw_file.exists():
203
- log.error(f"File {fw_file} does not exist, skipping {mcu.board} on {mcu.serialport}")
204
- continue
205
- log.info(f"Updating {mcu.board} on {mcu.serialport} to {fw_info['version']}")
206
- updated = None
207
- # try:
208
- if mcu.port in ["samd", "rp2", "nrf"]: # [k for k, v in PORT_FWTYPES.items() if v == ".uf2"]:
209
- if bootloader:
210
- enter_bootloader(mcu)
211
- updated = flash_uf2(mcu, fw_file=fw_file, erase=erase)
212
- elif mcu.port in ["stm32"]:
213
- if bootloader:
214
- enter_bootloader(mcu)
215
- updated = flash_stm32(mcu, fw_file, erase=erase)
216
- elif mcu.port in ["esp32", "esp8266"]:
217
- # bootloader is handled by esptool for esp32/esp8266
218
- updated = flash_esp(mcu, fw_file=fw_file, erase=erase)
219
- else:
220
- log.error(f"Don't (yet) know how to flash {mcu.port}-{mcu.board} on {mcu.serialport}")
221
-
222
- if updated:
223
- flashed.append(updated)
224
- else:
225
- log.error(f"Failed to flash {mcu.board} on {mcu.serialport}")
170
+ show_mcus(flashed, title="Updated boards after flashing")
mpflash/cli_group.py CHANGED
@@ -11,7 +11,7 @@ from .logger import make_quiet, set_loglevel
11
11
 
12
12
  def cb_verbose(ctx, param, value):
13
13
  """Callback to set the log level to DEBUG if verbose is set"""
14
- if value:
14
+ if value and not config.quiet:
15
15
  set_loglevel("DEBUG")
16
16
  config.verbose = True
17
17
  else:
mpflash/cli_list.py CHANGED
@@ -1,16 +1,11 @@
1
1
  import json
2
- from typing import List
3
2
 
4
3
  import rich_click as click
5
4
  from rich import print
6
- from rich.progress import track
7
- from rich.table import Table
8
-
9
- from mpflash.mpremoteboard import MPRemoteBoard
10
5
 
11
6
  from .cli_group import cli
12
- from .config import config
13
- from .logger import console, make_quiet
7
+ from .list import list_mcus, show_mcus
8
+ from .logger import make_quiet
14
9
 
15
10
 
16
11
  @cli.command("list", help="List the connected MCU boards.")
@@ -44,56 +39,3 @@ def cli_list_mcus(as_json: bool, progress: bool = True):
44
39
  if progress:
45
40
  show_mcus(conn_mcus, refresh=False)
46
41
  return conn_mcus
47
-
48
-
49
- def list_mcus():
50
- conn_mcus = [MPRemoteBoard(sp) for sp in MPRemoteBoard.connected_boards() if sp not in config.ignore_ports]
51
-
52
- for mcu in track(conn_mcus, description="Getting board info", transient=True, update_period=0.1):
53
- try:
54
- mcu.get_mcu_info()
55
- except ConnectionError as e:
56
- print(f"Error: {e}")
57
- continue
58
- return conn_mcus
59
-
60
-
61
- def show_mcus(
62
- conn_mcus: List[MPRemoteBoard],
63
- title: str = "Connected boards",
64
- refresh: bool = True,
65
- ): # sourcery skip: extract-duplicate-method
66
- """Show the list of connected boards in a nice table"""
67
- table = Table(
68
- title=title,
69
- header_style="bold blue",
70
- collapse_padding=True,
71
- width=110,
72
- row_styles=["blue", "yellow"],
73
- )
74
- table.add_column("Serial", overflow="fold")
75
- table.add_column("Family")
76
- table.add_column("Port")
77
- table.add_column("Board", overflow="fold")
78
- # table.add_column("Variant") # TODO: add variant
79
- table.add_column("CPU")
80
- table.add_column("Version")
81
- table.add_column("build", justify="right")
82
-
83
- for mcu in track(conn_mcus, description="Updating board info", transient=True, update_period=0.1):
84
- if refresh:
85
- try:
86
- mcu.get_mcu_info()
87
- except ConnectionError:
88
- continue
89
- table.add_row(
90
- mcu.serialport.replace("/dev/", ""),
91
- mcu.family,
92
- mcu.port,
93
- f"{mcu.board}\n{mcu.description}".strip(),
94
- # mcu.variant,
95
- mcu.cpu,
96
- mcu.version,
97
- mcu.build,
98
- )
99
- console.print(table)
mpflash/cli_main.py CHANGED
@@ -15,8 +15,12 @@ def mpflash():
15
15
  cli.add_command(cli_list_mcus)
16
16
  cli.add_command(cli_download)
17
17
  # cli(auto_envvar_prefix="MPFLASH")
18
- cli()
18
+ try:
19
+ exit(cli())
20
+ except AttributeError as e:
21
+ print(f"Error: {e}")
22
+ exit(-1)
19
23
 
20
24
 
21
- # if __name__ == "__main__":
22
- mpflash()
25
+ if __name__ == "__main__":
26
+ mpflash()