mpflash 1.26.3__py3-none-any.whl → 1.26.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.
mpflash/__init__.py CHANGED
@@ -1,5 +1 @@
1
1
  """MPFlash - MicroPython firmware flashing tool."""
2
-
3
- from .logger import configure_safe_logging, setup_external_logger_safety
4
-
5
- __all__ = ["configure_safe_logging", "setup_external_logger_safety"]
mpflash/ask_input.py CHANGED
@@ -5,7 +5,7 @@ Note: The prompts can use "{version}" and "{action}" to insert the version and a
5
5
  The values are provided from the answers dictionary.
6
6
  """
7
7
 
8
- from typing import List, Sequence, Tuple, Union
8
+ from typing import Dict, List, Sequence, Tuple, Union
9
9
 
10
10
  from loguru import logger as log
11
11
 
@@ -43,7 +43,7 @@ def ask_missing_params(
43
43
  action = "download" if isinstance(params, DownloadParams) else "flash"
44
44
 
45
45
  questions = []
46
- answers: dict[str, Union[str, List]] = {"action": action}
46
+ answers: Dict[str, Union[str, List]] = {"action": action}
47
47
  if not multi_select:
48
48
  if not params.serial or "?" in params.serial:
49
49
  questions.append(ask_serialport(multi_select=False, bluetooth=False))
@@ -59,7 +59,14 @@ def ask_missing_params(
59
59
  if not params.boards or "?" in params.boards:
60
60
  questions.extend(ask_port_board(multi_select=multi_select, action=action))
61
61
  if questions:
62
- answers = inquirer.prompt(questions, answers=answers) # type: ignore
62
+ # Store the pre-existing answers before prompting
63
+ pre_existing_answers = dict(answers)
64
+ prompted_answers = inquirer.prompt(questions, answers=answers) # type: ignore
65
+ if not prompted_answers:
66
+ # input cancelled by user
67
+ return [] # type: ignore
68
+ # Merge pre-existing answers with prompted answers
69
+ answers = {**pre_existing_answers, **prompted_answers}
63
70
  if not answers:
64
71
  # input cancelled by user
65
72
  return [] # type: ignore
@@ -226,7 +233,7 @@ def ask_serialport(*, multi_select: bool = False, bluetooth: bool = False):
226
233
  # import only when needed to reduce load time
227
234
  import inquirer
228
235
 
229
- comports = MPRemoteBoard.connected_boards(bluetooth=bluetooth, description=True) + ["auto"]
236
+ comports = MPRemoteBoard.connected_comports(bluetooth=bluetooth, description=True) + ["auto"]
230
237
  return inquirer.List(
231
238
  "serial",
232
239
  message="Which serial port do you want to {action} ?",
mpflash/cli_flash.py CHANGED
@@ -11,8 +11,8 @@ from mpflash.cli_group import cli
11
11
  from mpflash.cli_list import show_mcus
12
12
  from mpflash.common import BootloaderMethod, FlashParams, filtered_comports
13
13
  from mpflash.errors import MPFlashError
14
- from mpflash.flash import flash_list
15
- from mpflash.flash.worklist import WorkList, full_auto_worklist, manual_worklist, single_auto_worklist
14
+ from mpflash.flash import flash_tasks
15
+ from mpflash.flash.worklist import FlashTaskList, create_worklist
16
16
  from mpflash.mpremoteboard import MPRemoteBoard
17
17
  from mpflash.versions import clean_version
18
18
 
@@ -193,10 +193,10 @@ def cli_flash_board(**kwargs) -> int:
193
193
  raise MPFlashError("Only one version can be flashed at a time")
194
194
 
195
195
  params.versions = [clean_version(v) for v in params.versions]
196
- worklist: WorkList = []
196
+ tasks: FlashTaskList = []
197
197
 
198
198
  if len(params.versions) == 1 and len(params.boards) == 1 and params.serial == ["*"]:
199
- # A one or more serial port including the board / variant
199
+ # One or more serial ports including the board / variant (auto-detect ports)
200
200
  comports = filtered_comports(
201
201
  ignore=params.ignore,
202
202
  include=params.serial,
@@ -205,50 +205,49 @@ def cli_flash_board(**kwargs) -> int:
205
205
  board_id = f"{params.boards[0]}-{params.variant}" if params.variant else params.boards[0]
206
206
  log.info(f"Flashing {board_id} {params.versions[0]} to {len(comports)} serial ports")
207
207
  log.info(f"Target ports: {', '.join(comports)}")
208
- worklist = manual_worklist(
209
- comports,
208
+ tasks = create_worklist(
209
+ params.versions[0],
210
+ serial_ports=comports,
210
211
  board_id=board_id,
211
- version=params.versions[0],
212
- custom=params.custom,
212
+ custom_firmware=params.custom,
213
213
  )
214
- # if serial port == auto and there are one or more specified/detected boards
215
214
  elif params.serial == ["*"] and params.boards:
215
+ # Auto mode on detected boards with optional include/ignore filtering
216
216
  if not all_boards:
217
217
  log.trace("No boards detected yet, scanning for connected boards")
218
218
  _, _, _, all_boards = connected_ports_boards_variants(include=params.ports, ignore=params.ignore)
219
- # if variant id provided on the cmdline, treat is as an override
220
219
  if params.variant:
221
220
  for b in all_boards:
222
221
  b.variant = params.variant if (params.variant.lower() not in {"-", "none"}) else ""
223
-
224
- worklist = full_auto_worklist(
225
- all_boards=all_boards,
226
- version=params.versions[0],
227
- include=params.serial,
228
- ignore=params.ignore,
222
+ tasks = create_worklist(
223
+ params.versions[0],
224
+ connected_comports=all_boards,
225
+ include_ports=params.serial,
226
+ ignore_ports=params.ignore,
229
227
  )
230
- elif params.versions[0] and params.boards[0] and params.serial:
231
- # A one or more serial port including the board / variant
228
+ elif params.versions[0] and params.boards and params.serial:
229
+ # Manual specification of serial ports + board
232
230
  comports = filtered_comports(
233
231
  ignore=params.ignore,
234
- include=params.ports,
232
+ include=params.serial,
235
233
  bluetooth=params.bluetooth,
236
234
  )
237
- worklist = manual_worklist(
238
- comports,
235
+ tasks = create_worklist(
236
+ params.versions[0],
237
+ serial_ports=comports,
239
238
  board_id=params.boards[0],
240
- version=params.versions[0],
241
239
  )
242
240
  else:
243
- # just this serial port on auto
244
- worklist = single_auto_worklist(
245
- serial=params.serial[0],
246
- version=params.versions[0],
241
+ # Single serial port auto-detection
242
+ connected_comports = [MPRemoteBoard(params.serial[0])]
243
+ tasks = create_worklist(
244
+ params.versions[0],
245
+ connected_comports=connected_comports,
247
246
  )
248
247
  if not params.custom:
249
- jid.ensure_firmware_downloaded(worklist, version=params.versions[0], force=params.force)
250
- if flashed := flash_list(
251
- worklist,
248
+ jid.ensure_firmware_downloaded_tasks(tasks, version=params.versions[0], force=params.force)
249
+ if flashed := flash_tasks(
250
+ tasks,
252
251
  params.erase,
253
252
  params.bootloader,
254
253
  flash_mode=params.flash_mode,
mpflash/connected.py CHANGED
@@ -47,8 +47,6 @@ def list_mcus(*, ignore: List[str], include: List[str], bluetooth: bool = False)
47
47
  Raises:
48
48
  ConnectionError: If there is an error connecting to a board.
49
49
  """
50
- # conn_mcus = [MPRemoteBoard(sp) for sp in MPRemoteBoard.connected_boards(bluetooth) if sp not in config.ignore_ports]
51
- vid_pid = True
52
50
  comports = filtered_portinfos(
53
51
  ignore=ignore,
54
52
  include=include,
@@ -0,0 +1 @@
1
+ v1.27.0-preview
mpflash/db/core.py CHANGED
@@ -9,7 +9,6 @@ from sqlalchemy.orm import sessionmaker
9
9
  from mpflash.config import config
10
10
  from mpflash.errors import MPFlashError
11
11
 
12
- # TODO: lazy import to avoid slowdowns ?
13
12
  from .models import Base
14
13
 
15
14
  TRACE = False
@@ -1,6 +1,9 @@
1
1
  from os import path
2
2
  from pathlib import Path
3
- from typing import List
3
+ from typing import List, Optional, Tuple
4
+
5
+ import click
6
+ from typing_extensions import TypeAlias
4
7
 
5
8
  import mpflash.basicgit as git
6
9
  from mpflash.logger import log
@@ -8,6 +11,8 @@ from mpflash.mpremoteboard import HERE
8
11
  from mpflash.vendor.board_database import Database
9
12
  from mpflash.versions import micropython_versions
10
13
 
14
+ BoardList: TypeAlias = List[Tuple[str, ...]]
15
+
11
16
  HERE = Path(__file__).parent.resolve()
12
17
 
13
18
 
@@ -47,7 +52,7 @@ def boardlist_from_repo(
47
52
  versions: List[str],
48
53
  mpy_dir: Path,
49
54
  ):
50
- longlist = []
55
+ longlist: BoardList = []
51
56
  if not mpy_dir.is_dir():
52
57
  log.error(f"Directory {mpy_dir} not found")
53
58
  return longlist
@@ -71,38 +76,63 @@ def boardlist_from_repo(
71
76
  log.info(f"{git.get_git_describe(mpy_dir)} - {build_nr}")
72
77
  # un-cached database
73
78
  db = Database(mpy_dir)
74
- shortlist = list(iter_boards(db, version=version))
79
+ shortlist: BoardList = list(iter_boards(db, version=version))
75
80
  log.info(f"boards found {len(db.boards.keys())}")
76
81
  log.info(f"boards-variants found {len(shortlist) - len(db.boards.keys())}")
77
82
  longlist.extend(shortlist)
78
83
  return longlist
79
84
 
80
85
 
81
- def create_zip_file(longlist, zip_file: Path):
82
- """Create a ZIP file containing the CSV data."""
83
- # lazy import
84
- import zipfile
86
+ def create_zip_file(longlist: BoardList, zip_file: Path):
87
+ """Create a ZIP file containing the CSV data without external deps.
85
88
 
86
- import pandas as pd
89
+ Uses the standard library csv module to minimize dependencies while
90
+ preserving identical column ordering to the prior pandas implementation.
91
+ """
92
+ import csv
93
+ import io
94
+ import zipfile # lazy import
87
95
 
88
96
  csv_filename = "micropython_boards.csv"
97
+ columns = [
98
+ "version",
99
+ "board_id",
100
+ "board_name",
101
+ "mcu",
102
+ "variant",
103
+ "port",
104
+ "path",
105
+ "description",
106
+ "family",
107
+ ]
108
+
109
+ buf = io.StringIO()
110
+ writer = csv.writer(buf, lineterminator="\n")
111
+ writer.writerow(columns)
112
+ # rows already in correct order matching columns
113
+ for row in longlist:
114
+ writer.writerow(row)
115
+ csv_data = buf.getvalue()
89
116
 
90
- columns = ["version", "board_id", "board_name", "mcu", "variant", "port", "path", "description", "family"]
91
- df = pd.DataFrame(longlist, columns=columns)
92
-
93
- # Create the ZIP file and add the CSV data directly without creating an intermediate file
94
117
  with zipfile.ZipFile(zip_file, "w", zipfile.ZIP_DEFLATED) as zipf:
95
- # Create a temporary in-memory CSV string
96
- csv_data = df.to_csv(index=False)
97
- # Write the CSV data directly to the zip file
98
118
  zipf.writestr(csv_filename, csv_data)
99
119
 
100
120
 
121
+ def write_version_file(version: str, output_path: Path):
122
+ version_file = output_path / "boards_version.txt"
123
+ with version_file.open("w", encoding="utf-8") as vf:
124
+ vf.write(version + "\n")
125
+ log.info(f"Wrote version file {version_file}")
126
+
127
+
101
128
  def package_repo(mpy_path: Path):
102
129
  mpy_path = mpy_path or Path("../repos/micropython")
103
130
  log.info(f"Packaging Micropython boards from {mpy_path}")
104
131
  mp_versions = micropython_versions(minver="1.18")
105
- # checkput
132
+ if not mp_versions:
133
+ log.error("No Micropython versions found")
134
+ return
135
+ # checkout
106
136
  longlist = boardlist_from_repo(
107
137
  versions=mp_versions,
108
138
  mpy_dir=mpy_path,
@@ -110,9 +140,29 @@ def package_repo(mpy_path: Path):
110
140
  log.info(f"Total boards-variants: {len(longlist)}")
111
141
  zip_file = HERE / "micropython_boards.zip"
112
142
  create_zip_file(longlist, zip_file=zip_file)
143
+ log.info(f"Created {zip_file} with {len(longlist)} entries")
144
+ boards_version = mp_versions[-1]
145
+ write_version_file(boards_version, HERE)
113
146
 
114
147
  assert zip_file.is_file(), f"Failed to create {zip_file}"
115
148
 
116
149
 
150
+ @click.command()
151
+ @click.option(
152
+ "--mpy-path",
153
+ "mpy_path",
154
+ type=click.Path(path_type=Path),
155
+ default=None,
156
+ help="Path to local micropython repo (default: ../repos/micropython).",
157
+ )
158
+ def cli(mpy_path: Optional[Path]):
159
+ """Package board metadata into a compressed archive.
160
+
161
+ Enumerates boards and variants from a Micropython repo, builds CSV, and
162
+ writes it into a zip archive for fast loading and distribution.
163
+ """
164
+ package_repo(mpy_path if mpy_path else Path("../repos/micropython"))
165
+
166
+
117
167
  if __name__ == "__main__":
118
- package_repo(Path("D:\\mypython\\mpflash\\repos\\micropython"))
168
+ cli()
mpflash/db/loader.py CHANGED
@@ -4,7 +4,6 @@ import json
4
4
  import re
5
5
  import zipfile
6
6
  from pathlib import Path
7
- from turtle import up
8
7
 
9
8
  from loguru import logger as log
10
9
 
@@ -108,8 +107,22 @@ def load_jsonl_to_db(jsonl_path: Path):
108
107
  return num_records
109
108
 
110
109
 
110
+ def get_boards_version() -> str:
111
+ version_file = HERE / "boards_version.txt"
112
+ if version_file.is_file():
113
+ with version_file.open("r", encoding="utf-8") as vf:
114
+ version = vf.read().strip()
115
+ log.debug(f"Boards version from file: {version}")
116
+ return version
117
+ log.warning(f"Boards version file not found: {version_file}")
118
+ return "unknown"
119
+
120
+
111
121
  def update_boards():
112
- boards_version = "v1.25.2"
122
+ # todo: check if update is needed
123
+ # load board_versions.txt
124
+
125
+ boards_version = get_boards_version()
113
126
  try:
114
127
  meta = get_metadata()
115
128
  log.debug(f"Metadata: {meta}")
Binary file
@@ -12,16 +12,16 @@ from typing import Dict, List, Optional
12
12
  # make sure that jsonlines does not mistake the MicroPython ujson for the CPython ujson
13
13
  import jsonlines
14
14
  from loguru import logger as log
15
- from rich.progress import track
16
-
17
15
  from mpflash.common import PORT_FWTYPES
18
16
  from mpflash.config import config
19
- from mpflash.db.core import Session
20
- from mpflash.db.models import Firmware, Board
21
17
  from mpflash.downloaded import clean_downloaded_firmwares
22
18
  from mpflash.errors import MPFlashError
23
19
  from mpflash.mpboard_id.alternate import add_renamed_boards
24
20
  from mpflash.versions import clean_version
21
+ from rich.progress import track
22
+
23
+ from mpflash.db.core import Session
24
+ from mpflash.db.models import Board, Firmware
25
25
 
26
26
  from .from_web import fetch_firmware_files, get_boards
27
27
  from .fwinfo import FWInfo
@@ -111,11 +111,7 @@ def download_firmwares(
111
111
 
112
112
  downloaded = 0
113
113
  versions = [] if versions is None else [clean_version(v) for v in versions]
114
-
115
- # remove the known variant suffixes from the boards
116
- # TODO: IS THIS REALLY NEEDED ?
117
- # boards = [strip_variant(b) for b in boards]
118
-
114
+
119
115
  # handle downloading firmware for renamed boards
120
116
  boards = add_renamed_boards(boards)
121
117
 
mpflash/download/jid.py CHANGED
@@ -1,56 +1,50 @@
1
1
  # Just In-time Download of firmware if not already available
2
+ import warnings
3
+
2
4
  from loguru import logger as log
3
5
 
4
- from mpflash.common import Params
5
6
  from mpflash.download import download
6
7
  from mpflash.downloaded import find_downloaded_firmware
7
8
  from mpflash.errors import MPFlashError
8
- from mpflash.flash.worklist import WorkList
9
+ from mpflash.flash.worklist import FlashTaskList
9
10
  from mpflash.mpboard_id.alternate import alternate_board_names
10
11
 
11
12
 
12
- def ensure_firmware_downloaded(worklist: WorkList, version: str, force: bool) -> None:
13
- """
14
- Ensure all firmware in the worklist is downloaded for the given version.
15
-
16
- Iterates over the worklist, downloads missing firmware, and updates the worklist
17
- with the downloaded firmware.
13
+ def ensure_firmware_downloaded_tasks(tasks: FlashTaskList, version: str, force: bool) -> None:
14
+ """Ensure firmware present for each FlashTask, updating in-place.
18
15
 
19
- Raises MPFlashError if download fails.
16
+ Mirrors ensure_firmware_downloaded logic but works directly on FlashTaskList.
20
17
  """
21
- # iterate over the worklist ann update missing firmware
22
- newlist: WorkList = []
23
- for mcu, firmware in worklist:
24
- if force:
25
- board_firmwares = []
26
- else:
27
- if firmware:
28
- # firmware is already downloaded
29
- newlist.append((mcu, firmware))
30
- continue
31
- # check if the firmware is already downloaded
32
- board_firmwares = find_downloaded_firmware(
33
- board_id=f"{mcu.board}-{mcu.variant}" if mcu.variant else mcu.board,
34
- version=version,
35
- port=mcu.port,
18
+ updated: FlashTaskList = []
19
+ for task in tasks:
20
+ mcu = task.board
21
+ fw = task.firmware
22
+ if not force and fw:
23
+ updated.append(task)
24
+ continue
25
+ # find already downloaded firmware unless forcing
26
+ if force or not fw:
27
+ found = (
28
+ find_downloaded_firmware(
29
+ board_id=f"{mcu.board}-{mcu.variant}" if mcu.variant else mcu.board,
30
+ version=version,
31
+ port=mcu.port,
32
+ )
33
+ if not force
34
+ else []
36
35
  )
37
- if not board_firmwares:
38
- # download the firmware
39
- log.info(f"Downloading {version} firmware for {mcu.board} on {mcu.serialport}.")
40
- download(ports=[mcu.port], boards=alternate_board_names(mcu.board, mcu.port), versions=[version], force=True, clean=True)
41
- new_firmware = find_downloaded_firmware(
42
- board_id=f"{mcu.board}-{mcu.variant}" if mcu.variant else mcu.board,
43
- version=version,
44
- port=mcu.port,
45
- )
46
- if not new_firmware:
47
- raise MPFlashError(f"Failed to download {version} firmware for {mcu.board} on {mcu.serialport}.")
48
- newlist.append((mcu, new_firmware[0]))
49
- else:
50
- log.info(f"Found {version} firmware {board_firmwares[-1].firmware_file} for {mcu.board} on {mcu.serialport}.")
51
- newlist.append((mcu, board_firmwares[0]))
52
-
53
- worklist.clear()
54
- worklist.extend(newlist)
55
-
56
- pass
36
+ if not found:
37
+ log.info(f"Downloading {version} firmware for {mcu.board} on {mcu.serialport}.")
38
+ download(ports=[mcu.port], boards=alternate_board_names(mcu.board, mcu.port), versions=[version], force=True, clean=True)
39
+ found = find_downloaded_firmware(
40
+ board_id=f"{mcu.board}-{mcu.variant}" if mcu.variant else mcu.board,
41
+ version=version,
42
+ port=mcu.port,
43
+ )
44
+ if not found:
45
+ raise MPFlashError(f"Failed to download {version} firmware for {mcu.board} on {mcu.serialport}.")
46
+ # choose last/newest
47
+ task.firmware = found[-1]
48
+ updated.append(task)
49
+ tasks.clear()
50
+ tasks.extend(updated)
mpflash/flash/__init__.py CHANGED
@@ -1,8 +1,6 @@
1
1
  from pathlib import Path
2
2
 
3
3
  from loguru import logger as log
4
-
5
- from mpflash.bootloader.activate import enter_bootloader
6
4
  from mpflash.common import PORT_FWTYPES, UF2_PORTS, BootloaderMethod
7
5
  from mpflash.config import config
8
6
  from mpflash.errors import MPFlashError
@@ -10,28 +8,29 @@ from mpflash.errors import MPFlashError
10
8
  from .esp import flash_esp
11
9
  from .stm32 import flash_stm32
12
10
  from .uf2 import flash_uf2
13
- from .worklist import WorkList
11
+ from .worklist import FlashTaskList
14
12
 
15
13
  # #########################################################################################################
16
14
 
17
- def flash_list(
18
- todo: WorkList,
15
+
16
+ def flash_tasks(
17
+ tasks: FlashTaskList,
19
18
  erase: bool,
20
19
  bootloader: BootloaderMethod,
21
- **kwargs
22
- ): # sourcery skip: use-named-expression
23
- """Flash a list of boards with the specified firmware."""
20
+ **kwargs,
21
+ ):
22
+ """Flash a list of FlashTask items directly."""
24
23
  flashed = []
25
- for mcu, fw_info in todo:
24
+ for task in tasks:
25
+ mcu = task.board
26
+ fw_info = task.firmware
26
27
  if not fw_info:
27
28
  log.error(f"Firmware not found for {mcu.board} on {mcu.serialport}, skipping")
28
29
  continue
29
-
30
30
  fw_file = config.firmware_folder / fw_info.firmware_file
31
31
  if not fw_file.exists():
32
32
  log.error(f"File {fw_file} does not exist, skipping {mcu.board} on {mcu.serialport}")
33
33
  continue
34
-
35
34
  log.info(f"Updating {mcu.board} on {mcu.serialport} to {fw_info.version}")
36
35
  try:
37
36
  updated = flash_mcu(mcu, fw_file=fw_file, erase=erase, bootloader=bootloader, **kwargs)
@@ -40,14 +39,13 @@ def flash_list(
40
39
  continue
41
40
  if updated:
42
41
  if fw_info.custom:
43
- # Add / Update board_info.toml with the custom_id and Description
44
42
  mcu.get_board_info_toml()
45
43
  if fw_info.description:
46
44
  mcu.toml["description"] = fw_info.description
45
+ mcu.toml.setdefault("mpflash", {})
47
46
  mcu.toml["mpflash"]["board_id"] = fw_info.board_id
48
47
  mcu.toml["mpflash"]["custom_id"] = fw_info.custom_id
49
48
  mcu.set_board_info_toml()
50
-
51
49
  flashed.append(updated)
52
50
  else:
53
51
  log.error(f"Failed to flash {mcu.board} on {mcu.serialport}")
@@ -63,6 +61,8 @@ def flash_mcu(
63
61
  **kwargs
64
62
  ):
65
63
  """Flash a single MCU with the specified firmware."""
64
+ from mpflash.bootloader.activate import enter_bootloader
65
+
66
66
  updated = None
67
67
  try:
68
68
  if mcu.port in UF2_PORTS and fw_file.suffix == ".uf2":