mpflash 0.4.0.post3__py3-none-any.whl → 0.5.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/common.py CHANGED
@@ -1,7 +1,9 @@
1
+ import os
1
2
  import time
3
+ from functools import lru_cache
2
4
  from typing import TypedDict
3
5
 
4
- from github import Github
6
+ from github import Auth, Github
5
7
  from loguru import logger as log
6
8
  from packaging.version import parse
7
9
  from rich.progress import track
@@ -19,6 +21,14 @@ PORT_FWTYPES = {
19
21
  "renesas-ra": [".hex"],
20
22
  }
21
23
 
24
+ # Token with no permissions to avoid throttling
25
+ # https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api?apiVersion=2022-11-28#getting-a-higher-rate-limit
26
+ PAT_NO_ACCESS = (
27
+ "github_pat" + "_11AAHPVFQ0qAkDnSUaMKSp" + "_ZkDl5NRRwBsUN6EYg9ahp1Dvj4FDDONnXVgimxC2EtpY7Q7BUKBoQ0Jq72X"
28
+ )
29
+ PAT = os.environ.get("GITHUB_TOKEN") or PAT_NO_ACCESS
30
+ GH_CLIENT = Github(auth=Auth.Token(PAT))
31
+
22
32
 
23
33
  class FWInfo(TypedDict):
24
34
  filename: str
@@ -27,6 +37,7 @@ class FWInfo(TypedDict):
27
37
  variant: str
28
38
  preview: bool
29
39
  version: str
40
+ build: str
30
41
 
31
42
 
32
43
  #############################################################
@@ -52,7 +63,7 @@ def clean_version(
52
63
  if version in {"", "-"}:
53
64
  return version
54
65
  if version.lower() == "stable":
55
- _v = get_stable_version()
66
+ _v = get_stable_mp_version()
56
67
  if not _v:
57
68
  log.warning("Could not determine the latest stable version")
58
69
  return "stable"
@@ -89,12 +100,12 @@ def clean_version(
89
100
  return version
90
101
 
91
102
 
92
- def micropython_versions(minver: str = "v1.9.2"):
103
+ @lru_cache(maxsize=10)
104
+ def micropython_versions(minver: str = "v1.20"):
93
105
  """Get the list of micropython versions from github tags"""
94
106
  try:
95
- g = Github()
96
- _ = 1 / 0
97
- repo = g.get_repo("micropython/micropython")
107
+ gh_client = GH_CLIENT
108
+ repo = gh_client.get_repo("micropython/micropython")
98
109
  versions = [tag.name for tag in repo.get_tags() if parse(tag.name) >= parse(minver)]
99
110
  except Exception:
100
111
  versions = [
@@ -116,19 +127,23 @@ def micropython_versions(minver: str = "v1.9.2"):
116
127
  "v1.12",
117
128
  "v1.11",
118
129
  "v1.10",
119
- "v1.9.4",
120
- "v1.9.3",
121
130
  ]
122
131
  versions = [v for v in versions if parse(v) >= parse(minver)]
123
132
  return sorted(versions)
124
133
 
125
134
 
126
- def get_stable_version() -> str:
135
+ def get_stable_mp_version() -> str:
127
136
  # read the versions from the git tags
128
137
  all_versions = micropython_versions(minver="v1.17")
129
138
  return [v for v in all_versions if not v.endswith(V_PREVIEW)][-1]
130
139
 
131
140
 
141
+ def get_preview_mp_version() -> str:
142
+ # read the versions from the git tags
143
+ all_versions = micropython_versions(minver="v1.17")
144
+ return [v for v in all_versions if v.endswith(V_PREVIEW)][-1]
145
+
146
+
132
147
  #############################################################
133
148
  def wait_for_restart(mcu: MPRemoteBoard, timeout: int = 10):
134
149
  """wait for the board to restart"""
mpflash/config.py CHANGED
@@ -1,16 +1,18 @@
1
1
  """centralized configuration for mpflash"""
2
2
 
3
- import platformdirs
4
-
5
- from typing import List
6
3
  from pathlib import Path
4
+ from typing import List
5
+
6
+ import platformdirs
7
7
 
8
8
 
9
9
  class MPtoolConfig:
10
10
  """Centralized configuration for mpflash"""
11
11
 
12
12
  quiet: bool = False
13
+ verbose: bool = False
13
14
  ignore_ports: List[str] = []
15
+ interactive: bool = True
14
16
  firmware_folder: Path = platformdirs.user_downloads_path() / "firmware"
15
17
 
16
18
 
mpflash/download.py CHANGED
@@ -5,19 +5,24 @@ Uses the micropython.org website to get the available versions and locations to
5
5
 
6
6
  import functools
7
7
  import itertools
8
- import json
9
8
  import re
10
9
  from pathlib import Path
11
10
  from typing import Dict, List, Optional
12
11
  from urllib.parse import urljoin
13
12
 
14
- from mpflash.common import PORT_FWTYPES
13
+ # #########################################################################################################
14
+ # make sure that jsonlines does not mistake the MicroPython ujson for the CPython ujson
15
+ import jsonlines
15
16
  import requests
16
17
  from bs4 import BeautifulSoup
17
18
  from loguru import logger as log
18
19
  from rich.progress import track
19
20
 
20
- # from .common import PORT_FWTYPES, clean_version
21
+ from mpflash.common import PORT_FWTYPES
22
+
23
+ jsonlines.ujson = None # type: ignore
24
+ # #########################################################################################################
25
+
21
26
 
22
27
  MICROPYTHON_ORG_URL = "https://micropython.org/"
23
28
 
@@ -78,6 +83,18 @@ FirmwareInfo = Dict[str, str]
78
83
  # The first run takes ~60 seconds to run for 4 ports , all boards
79
84
  # so it makes sense to cache the results and skip boards as soon as possible
80
85
  def get_boards(ports: List[str], boards: List[str], clean: bool) -> List[FirmwareInfo]:
86
+ """
87
+ Retrieves a list of firmware information for the specified ports and boards.
88
+
89
+ Args:
90
+ ports (List[str]): The list of ports to check for firmware.
91
+ boards (List[str]): The list of boards to retrieve firmware information for.
92
+ clean (bool): A flag indicating whether to perform a clean retrieval.
93
+
94
+ Returns:
95
+ List[FirmwareInfo]: A list of firmware information for the specified ports and boards.
96
+
97
+ """
81
98
  board_urls: List[FirmwareInfo] = []
82
99
  for port in ports:
83
100
  download_page_url = f"{MICROPYTHON_ORG_URL}download/?port={port}"
@@ -134,14 +151,13 @@ def download_firmwares(
134
151
  boards: List[str],
135
152
  versions: Optional[List[str]] = None,
136
153
  *,
137
- preview: bool = False,
138
154
  force: bool = False,
139
155
  clean: bool = True,
140
156
  ):
141
157
  skipped = downloaded = 0
142
158
  if versions is None:
143
159
  versions = []
144
- unique_boards = get_firmware_list(ports, boards, versions, preview, clean)
160
+ unique_boards = get_firmware_list(ports, boards, versions, clean)
145
161
 
146
162
  for b in unique_boards:
147
163
  log.debug(b["filename"])
@@ -151,7 +167,7 @@ def download_firmwares(
151
167
 
152
168
  firmware_folder.mkdir(exist_ok=True)
153
169
 
154
- with open(firmware_folder / "firmware.jsonl", "a", encoding="utf-8", buffering=1) as f_jsonl:
170
+ with jsonlines.open(firmware_folder / "firmware.jsonl", "a") as writer:
155
171
  for board in unique_boards:
156
172
  filename = firmware_folder / board["port"] / board["filename"]
157
173
  filename.parent.mkdir(exist_ok=True)
@@ -169,16 +185,28 @@ def download_firmwares(
169
185
  except requests.RequestException as e:
170
186
  log.exception(e)
171
187
  continue
172
- # add the firmware to the jsonl file
173
- json_str = json.dumps(board) + "\n"
174
- f_jsonl.write(json_str)
188
+ writer.write(board)
175
189
  downloaded += 1
176
190
  log.info(f"Downloaded {downloaded} firmwares, skipped {skipped} existing files.")
177
191
 
178
192
 
179
- def get_firmware_list(ports: List[str], boards: List[str], versions: List[str], preview: bool, clean: bool):
180
- log.trace("Checking MicroPython download pages")
193
+ def get_firmware_list(ports: List[str], boards: List[str], versions: List[str], clean: bool):
194
+ """
195
+ Retrieves a list of unique firmware information based on the specified ports, boards, versions, and clean flag.
181
196
 
197
+ Args:
198
+ ports : The list of ports to check for firmware.
199
+ boards : The list of boards to filter the firmware by.
200
+ versions : The list of versions to filter the firmware by.
201
+ clean : A flag indicating whether to perform a clean check.
202
+
203
+ Returns:
204
+ List[FirmwareInfo]: A list of unique firmware information.
205
+
206
+ """
207
+
208
+ log.trace("Checking MicroPython download pages")
209
+ preview = "preview" in versions
182
210
  board_urls = sorted(get_boards(ports, boards, clean), key=key_fw_ver_pre_ext_bld)
183
211
 
184
212
  log.debug(f"Total {len(board_urls)} firmwares")
@@ -206,8 +234,25 @@ def download(
206
234
  versions: List[str],
207
235
  force: bool,
208
236
  clean: bool,
209
- preview: bool,
210
237
  ):
238
+ """
239
+ Downloads firmware files based on the specified destination, ports, boards, versions, force flag, and clean flag.
240
+
241
+ Args:
242
+ destination : The destination folder to save the downloaded firmware files.
243
+ ports : The list of ports to check for firmware.
244
+ boards : The list of boards to download firmware for.
245
+ versions : The list of versions to download firmware for.
246
+ force : A flag indicating whether to force the download even if the firmware file already exists.
247
+ clean : A flag indicating whether to perform a clean download.
248
+
249
+ Returns:
250
+ None
251
+
252
+ Raises:
253
+ SystemExit: If no boards are found or specified.
254
+
255
+ """
211
256
  if not boards:
212
257
  log.critical("No boards found, please connect a board or specify boards to download firmware for.")
213
258
  exit(1)
@@ -217,4 +262,4 @@ def download(
217
262
  except (PermissionError, FileNotFoundError) as e:
218
263
  log.critical(f"Could not create folder {destination}\n{e}")
219
264
  exit(1)
220
- download_firmwares(destination, ports, boards, versions, preview=preview, force=force, clean=clean)
265
+ download_firmwares(destination, ports, boards, versions, force=force, clean=clean)
mpflash/flash.py CHANGED
@@ -5,9 +5,10 @@ from typing import Dict, List, Optional, Tuple
5
5
  import jsonlines
6
6
  from loguru import logger as log
7
7
 
8
+ from mpflash.mpremoteboard import MPRemoteBoard
9
+
8
10
  from .common import FWInfo, clean_version
9
11
  from .config import config
10
- from .mpremoteboard import MPRemoteBoard
11
12
 
12
13
  # #########################################################################################################
13
14
  WorkList = List[Tuple[MPRemoteBoard, FWInfo]]
@@ -32,12 +33,11 @@ def find_firmware(
32
33
  board: str,
33
34
  version: str = "",
34
35
  port: str = "",
35
- preview: bool = False,
36
36
  variants: bool = False,
37
37
  fw_folder: Optional[Path] = None,
38
38
  trie: int = 1,
39
39
  selector: Optional[Dict[str, str]] = None,
40
- ):
40
+ ) -> List[FWInfo]:
41
41
  if selector is None:
42
42
  selector = {}
43
43
  fw_folder = fw_folder or config.firmware_folder
@@ -48,24 +48,8 @@ def find_firmware(
48
48
  return []
49
49
  # filter by version
50
50
  version = clean_version(version, drop_v=True)
51
- if preview or "preview" in version:
52
- # never get a preview for an older version
53
- fw_list = [fw for fw in fw_list if fw["preview"]]
54
- else:
55
- fw_list = [fw for fw in fw_list if fw["version"] == version]
51
+ fw_list = filter_fwlist(fw_list, board, version, port, variants, selector)
56
52
 
57
- # filter by port
58
- if port:
59
- fw_list = [fw for fw in fw_list if fw["port"] == port]
60
-
61
- if board:
62
- if variants:
63
- fw_list = [fw for fw in fw_list if fw["board"] == board]
64
- else:
65
- # the variant should match exactly the board name
66
- fw_list = [fw for fw in fw_list if fw["variant"] == board]
67
- if selector and port in selector:
68
- fw_list = [fw for fw in fw_list if fw["filename"].endswith(selector[port])]
69
53
  if not fw_list and trie < 2:
70
54
  board_id = board.replace("_", "-")
71
55
  # ESP board naming conventions have changed by adding a PORT refix
@@ -75,13 +59,12 @@ def find_firmware(
75
59
  if port == "rp2" and not board_id.startswith("RPI_"):
76
60
  board_id = f"RPI_{board_id}"
77
61
 
78
- log.warning(f"Trying to find a firmware for the board {board_id}")
62
+ log.info(f"Try ({trie}) to find a firmware for the board {board_id}")
79
63
  fw_list = find_firmware(
80
64
  fw_folder=fw_folder,
81
65
  board=board_id,
82
66
  version=version,
83
67
  port=port,
84
- preview=preview,
85
68
  trie=trie + 1,
86
69
  selector=selector,
87
70
  )
@@ -91,6 +74,37 @@ def find_firmware(
91
74
  return fw_list
92
75
 
93
76
 
77
+ def filter_fwlist(
78
+ fw_list: List[FWInfo],
79
+ board: str,
80
+ version: str,
81
+ port: str,
82
+ # preview: bool,
83
+ variants: bool,
84
+ selector: dict,
85
+ ) -> List[FWInfo]:
86
+ """Filter the firmware list based on the provided parameters"""
87
+ if "preview" in version:
88
+ # never get a preview for an older version
89
+ fw_list = [fw for fw in fw_list if fw["preview"]]
90
+ else:
91
+ fw_list = [fw for fw in fw_list if fw["version"] == version]
92
+
93
+ # filter by port
94
+ if port:
95
+ fw_list = [fw for fw in fw_list if fw["port"] == port]
96
+
97
+ if board:
98
+ if variants:
99
+ fw_list = [fw for fw in fw_list if fw["board"] == board]
100
+ else:
101
+ # the variant should match exactly the board name
102
+ fw_list = [fw for fw in fw_list if fw["variant"] == board]
103
+ if selector and port in selector:
104
+ fw_list = [fw for fw in fw_list if fw["filename"].endswith(selector[port])]
105
+ return fw_list
106
+
107
+
94
108
  # #########################################################################################################
95
109
  #
96
110
 
@@ -100,7 +114,6 @@ def auto_update(
100
114
  target_version: str,
101
115
  fw_folder: Path,
102
116
  *,
103
- preview: bool = False,
104
117
  selector: Optional[Dict[str, str]] = None,
105
118
  ) -> WorkList:
106
119
  """Builds a list of boards to update based on the connected boards and the firmware available"""
@@ -118,7 +131,6 @@ def auto_update(
118
131
  board=mcu.board,
119
132
  version=target_version,
120
133
  port=mcu.port,
121
- preview=preview or "preview" in target_version,
122
134
  selector=selector,
123
135
  )
124
136
 
mpflash/flash_esp.py CHANGED
@@ -4,26 +4,28 @@
4
4
  # #########################################################################################################
5
5
  """
6
6
 
7
- import time
8
7
  from pathlib import Path
9
8
  from typing import List, Optional
10
9
 
11
10
  import esptool
12
11
  from loguru import logger as log
13
12
 
14
- from .mpremoteboard import MPRemoteBoard
13
+ from mpflash.mpboard_id.api import find_mp_board
14
+ from mpflash.mpremoteboard import MPRemoteBoard
15
+
15
16
  from .common import wait_for_restart
16
17
 
17
- def flash_esp(
18
- mcu: MPRemoteBoard, fw_file: Path, *, erase: bool = True
19
- ) -> Optional[MPRemoteBoard]:
18
+
19
+ def flash_esp(mcu: MPRemoteBoard, fw_file: Path, *, erase: bool = True) -> Optional[MPRemoteBoard]:
20
20
  if mcu.port not in ["esp32", "esp8266"] or mcu.board in ["ARDUINO_NANO_ESP32"]:
21
- log.error(
22
- f"esptool not supported for {mcu.port} {mcu.board} on {mcu.serialport}"
23
- )
21
+ log.error(f"esptool not supported for {mcu.port} {mcu.board} on {mcu.serialport}")
24
22
  return None
25
23
 
26
24
  log.info(f"Flashing {fw_file} on {mcu.board} on {mcu.serialport}")
25
+ if not mcu.cpu:
26
+ # Lookup CPU based on the board name
27
+ mcu.cpu = find_mp_board(mcu.board)["cpu"]
28
+
27
29
  if mcu.port == "esp8266":
28
30
  baud_rate = str(460_800)
29
31
  else:
@@ -31,9 +33,7 @@ def flash_esp(
31
33
  # baud_rate = str(115_200)
32
34
  cmds: List[List[str]] = []
33
35
  if erase:
34
- cmds.append(
35
- f"esptool --chip {mcu.cpu} --port {mcu.serialport} erase_flash".split()
36
- )
36
+ cmds.append(f"esptool --chip {mcu.cpu} --port {mcu.serialport} erase_flash".split())
37
37
 
38
38
  if mcu.cpu.upper() in ("ESP32", "ESP32S2"):
39
39
  start_addr = "0x1000"
mpflash/flash_stm32.py CHANGED
@@ -8,7 +8,7 @@ from mpflash.common import wait_for_restart
8
8
 
9
9
  # from .flash_stm32_cube import flash_stm32_cubecli
10
10
  from .flash_stm32_dfu import dfu_init, flash_stm32_dfu
11
- from .mpremoteboard import MPRemoteBoard
11
+ from mpflash.mpremoteboard import MPRemoteBoard
12
12
 
13
13
 
14
14
  def flash_stm32(mcu: MPRemoteBoard, fw_file: Path, *, erase: bool, stm32_dfu: bool = True):
@@ -1,26 +1,23 @@
1
1
  import platform
2
- import sys
3
- import time
4
2
  from pathlib import Path
5
3
  from typing import Optional
6
4
 
7
5
  from loguru import logger as log
8
6
 
9
- from .common import wait_for_restart
10
- from .mpremoteboard import MPRemoteBoard
7
+ from mpflash.mpremoteboard import MPRemoteBoard
11
8
 
12
9
 
13
- def init_libusb_windows():
10
+ def init_libusb_windows() -> bool:
14
11
  # on windows we need to initialze the libusb backend with the correct dll
15
- import libusb
12
+ import libusb # type: ignore
16
13
  import usb.backend.libusb1 as libusb1
17
14
 
18
15
  arch = "x64" if platform.architecture()[0] == "64bit" else "x86"
19
16
  libusb1_dll = Path(libusb.__file__).parent / f"_platform\\_windows\\{arch}\\libusb-1.0.dll"
20
17
  if not libusb1_dll.exists():
21
18
  raise FileNotFoundError(f"libusb1.dll not found at {libusb1_dll}")
22
-
23
19
  backend = libusb1.get_backend(find_library=lambda x: libusb1_dll.as_posix())
20
+ return backend is not None
24
21
 
25
22
 
26
23
  try:
mpflash/flash_uf2.py CHANGED
@@ -11,7 +11,7 @@ from typing import Optional
11
11
  from loguru import logger as log
12
12
  from rich.progress import track
13
13
 
14
- from .mpremoteboard import MPRemoteBoard
14
+ from mpflash.mpremoteboard import MPRemoteBoard
15
15
 
16
16
  from .common import PORT_FWTYPES
17
17
  from .flash_uf2_linux import dismount_uf2, wait_for_UF2_linux
mpflash/mpboard_id/api.py CHANGED
@@ -1,9 +1,9 @@
1
1
  import json
2
2
  from functools import lru_cache
3
3
  from pathlib import Path
4
- from typing import List, Tuple, TypedDict, Union
4
+ from typing import List, Optional, Tuple, TypedDict, Union
5
5
 
6
- from mpflash.common import PORT_FWTYPES
6
+ from mpflash.common import PORT_FWTYPES, clean_version
7
7
 
8
8
 
9
9
  # Board based on the dataclass Board but changed to TypedDict
@@ -18,31 +18,72 @@ class Board(TypedDict):
18
18
  mcu_name: str
19
19
  path: Union[Path, str]
20
20
  version: str
21
+ cpu: str
21
22
 
22
23
 
23
24
  @lru_cache(maxsize=None)
24
25
  def read_boardinfo() -> List[Board]:
26
+ """Reads the board_info.json file and returns the data as a list of Board objects"""
25
27
  with open(Path(__file__).parent / "board_info.json", "r") as file:
26
28
  return json.load(file)
27
29
 
28
30
 
29
31
  def known_mp_ports() -> List[str]:
30
32
  # TODO: Filter for Version
31
- info = read_boardinfo()
33
+ mp_boards = read_boardinfo()
32
34
  # select the unique ports from info
33
- ports = set({board["port"] for board in info if board["port"] in PORT_FWTYPES.keys()})
35
+ ports = set({board["port"] for board in mp_boards if board["port"] in PORT_FWTYPES.keys()})
34
36
  return sorted(list(ports))
35
37
 
36
38
 
37
- def known_mp_boards(port: str) -> List[Tuple[str, str]]:
38
- info = read_boardinfo()
39
- boards = set({(board["description"], board["board"]) for board in info if board["port"] == port})
39
+ def get_mp_boards_for_port(port: str, versions: Optional[List[str]] = None):
40
+ """
41
+ Returns a list of boards for the given port and version(s)
42
+
43
+ port : str : The Micropython port to filter for
44
+ versions : List[str] : The Micropython versions to filter for (actual versions required)"""
45
+ mp_boards = read_boardinfo()
46
+
47
+ # filter for 'preview' as they are not in the board_info.json
48
+ # instead use stable version
49
+ versions = versions or []
50
+ if "preview" in versions:
51
+ versions.remove("preview")
52
+ versions.append("stable")
53
+ if versions:
54
+ # make sure of the v prefix
55
+ versions = [clean_version(v) for v in versions]
56
+ # filter for the version(s)
57
+ mp_boards = [board for board in mp_boards if board["version"] in versions]
58
+ # filter for the port
59
+ mp_boards = [board for board in mp_boards if board["port"] == port]
60
+ return mp_boards
61
+
62
+
63
+ def known_mp_boards(port: str, versions: Optional[List[str]] = None) -> List[Tuple[str, str]]:
64
+ """
65
+ Returns a list of tuples with the description and board name for the given port and version
66
+
67
+ port : str : The Micropython port to filter for
68
+ versions : List[str] : The Micropython versions to filter for (actual versions required)
69
+ """
70
+ mp_boards = get_mp_boards_for_port(port, versions)
71
+
72
+ boards = set(
73
+ {(f'{board["description"]} [board["board"]] {board["version"]}', board["board"]) for board in mp_boards}
74
+ )
40
75
  return sorted(list(boards))
41
76
 
42
77
 
43
- def find_mp_port(board: str) -> str:
78
+ def find_mp_board(board: str) -> Board:
79
+ """Find the board for the given board"""
44
80
  info = read_boardinfo()
45
81
  for board_info in info:
46
82
  if board_info["board"] == board:
47
- return board_info["port"]
48
- return ""
83
+ if "cpu" not in board_info or not board_info["cpu"]:
84
+ if " with " in board_info["description"]:
85
+ board_info["cpu"] = board_info["description"].split(" with ")[-1]
86
+ else:
87
+ board_info["cpu"] = board_info["port"]
88
+ return board_info
89
+ raise LookupError(f"Board {board} not found")