micropython-stubber 1.20.1__py3-none-any.whl → 1.20.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.
Files changed (60) hide show
  1. {micropython_stubber-1.20.1.dist-info → micropython_stubber-1.20.4.dist-info}/METADATA +4 -3
  2. {micropython_stubber-1.20.1.dist-info → micropython_stubber-1.20.4.dist-info}/RECORD +58 -51
  3. {micropython_stubber-1.20.1.dist-info → micropython_stubber-1.20.4.dist-info}/WHEEL +1 -1
  4. mpflash/README.md +16 -5
  5. mpflash/mpflash/add_firmware.py +98 -0
  6. mpflash/mpflash/ask_input.py +97 -120
  7. mpflash/mpflash/cli_download.py +42 -25
  8. mpflash/mpflash/cli_flash.py +70 -32
  9. mpflash/mpflash/cli_group.py +17 -14
  10. mpflash/mpflash/cli_list.py +39 -3
  11. mpflash/mpflash/cli_main.py +17 -6
  12. mpflash/mpflash/common.py +125 -12
  13. mpflash/mpflash/config.py +12 -0
  14. mpflash/mpflash/connected.py +74 -0
  15. mpflash/mpflash/download.py +132 -51
  16. mpflash/mpflash/downloaded.py +36 -15
  17. mpflash/mpflash/flash.py +2 -2
  18. mpflash/mpflash/flash_esp.py +2 -2
  19. mpflash/mpflash/flash_uf2.py +14 -8
  20. mpflash/mpflash/flash_uf2_boardid.py +2 -1
  21. mpflash/mpflash/flash_uf2_linux.py +5 -16
  22. mpflash/mpflash/flash_uf2_macos.py +37 -0
  23. mpflash/mpflash/flash_uf2_windows.py +5 -5
  24. mpflash/mpflash/list.py +57 -57
  25. mpflash/mpflash/mpboard_id/__init__.py +41 -44
  26. mpflash/mpflash/mpboard_id/add_boards.py +255 -0
  27. mpflash/mpflash/mpboard_id/board.py +37 -0
  28. mpflash/mpflash/mpboard_id/board_id.py +54 -34
  29. mpflash/mpflash/mpboard_id/board_info.zip +0 -0
  30. mpflash/mpflash/mpboard_id/store.py +43 -0
  31. mpflash/mpflash/mpremoteboard/__init__.py +18 -6
  32. mpflash/mpflash/uf2disk.py +12 -0
  33. mpflash/mpflash/vendor/basicgit.py +288 -0
  34. mpflash/mpflash/vendor/dfu.py +1 -0
  35. mpflash/mpflash/vendor/versions.py +7 -3
  36. mpflash/mpflash/worklist.py +71 -48
  37. mpflash/poetry.lock +164 -138
  38. mpflash/pyproject.toml +18 -15
  39. stubber/__init__.py +1 -1
  40. stubber/board/createstubs.py +13 -3
  41. stubber/board/createstubs_db.py +5 -7
  42. stubber/board/createstubs_db_min.py +329 -825
  43. stubber/board/createstubs_db_mpy.mpy +0 -0
  44. stubber/board/createstubs_mem.py +6 -7
  45. stubber/board/createstubs_mem_min.py +304 -765
  46. stubber/board/createstubs_mem_mpy.mpy +0 -0
  47. stubber/board/createstubs_min.py +293 -975
  48. stubber/board/createstubs_mpy.mpy +0 -0
  49. stubber/board/modulelist.txt +10 -0
  50. stubber/commands/get_core_cmd.py +7 -6
  51. stubber/commands/get_docstubs_cmd.py +8 -3
  52. stubber/commands/get_frozen_cmd.py +5 -2
  53. stubber/publish/publish.py +18 -7
  54. stubber/update_module_list.py +2 -24
  55. stubber/utils/makeversionhdr.py +3 -2
  56. stubber/utils/versions.py +2 -1
  57. mpflash/mpflash/mpboard_id/board_info.csv +0 -2213
  58. mpflash/mpflash/mpboard_id/board_info.json +0 -19910
  59. {micropython_stubber-1.20.1.dist-info → micropython_stubber-1.20.4.dist-info}/LICENSE +0 -0
  60. {micropython_stubber-1.20.1.dist-info → micropython_stubber-1.20.4.dist-info}/entry_points.txt +0 -0
@@ -1,11 +1,13 @@
1
1
  import json
2
+ from typing import List
2
3
 
3
4
  import rich_click as click
4
5
  from rich import print
5
6
 
6
7
  from .cli_group import cli
7
- from .list import list_mcus, show_mcus
8
+ from .list import show_mcus
8
9
  from .logger import make_quiet
10
+ from .connected import list_mcus
9
11
 
10
12
 
11
13
  @cli.command("list", help="List the connected MCU boards.")
@@ -18,21 +20,55 @@ from .logger import make_quiet
18
20
  show_default=True,
19
21
  help="""Output in json format""",
20
22
  )
23
+ @click.option(
24
+ "--serial",
25
+ "--serial-port",
26
+ "-s",
27
+ "serial",
28
+ default=["*"],
29
+ multiple=True,
30
+ show_default=True,
31
+ help="Which serial port(s) to list. ",
32
+ metavar="SERIALPORT",
33
+ )
34
+ @click.option(
35
+ "--ignore",
36
+ "-i",
37
+ is_eager=True,
38
+ help="Serial port(s) to ignore. Defaults to MPFLASH_IGNORE.",
39
+ multiple=True,
40
+ default=[],
41
+ envvar="MPFLASH_IGNORE",
42
+ show_default=True,
43
+ metavar="SERIALPORT",
44
+ )
45
+ @click.option(
46
+ "--bluetooth/--no-bluetooth",
47
+ "-b/-nb",
48
+ is_flag=True,
49
+ default=False,
50
+ show_default=True,
51
+ help="""Include bluetooth ports in the list""",
52
+ )
21
53
  @click.option(
22
54
  "--progress/--no-progress",
55
+ # "-p/-np", -p is already used for --port
23
56
  "progress",
24
57
  is_flag=True,
25
58
  default=True,
26
59
  show_default=True,
27
60
  help="""Show progress""",
28
61
  )
29
- def cli_list_mcus(as_json: bool, progress: bool = True) -> int:
62
+ def cli_list_mcus(serial: List[str], ignore: List[str], bluetooth: bool, as_json: bool, progress: bool = True) -> int:
30
63
  """List the connected MCU boards, and output in a nice table or json."""
64
+ serial = list(serial)
65
+ ignore = list(ignore)
31
66
  if as_json:
32
67
  # avoid noise in json output
33
68
  make_quiet()
69
+ # TODO? Ask user to select a serialport if [?] is given ?
34
70
 
35
- conn_mcus = list_mcus()
71
+ conn_mcus = list_mcus(ignore=ignore, include=serial, bluetooth=bluetooth)
36
72
  if as_json:
37
73
  print(json.dumps([mcu.__dict__ for mcu in conn_mcus], indent=4))
38
74
  progress = False
@@ -2,7 +2,10 @@
2
2
 
3
3
  # import rich_click as click
4
4
 
5
+ import os
6
+
5
7
  import click
8
+ from loguru import logger as log
6
9
 
7
10
  from .cli_download import cli_download
8
11
  from .cli_flash import cli_flash_board
@@ -11,16 +14,24 @@ from .cli_list import cli_list_mcus
11
14
 
12
15
 
13
16
  def mpflash():
14
- cli.add_command(cli_flash_board)
15
17
  cli.add_command(cli_list_mcus)
16
18
  cli.add_command(cli_download)
19
+ cli.add_command(cli_flash_board)
20
+
17
21
  # cli(auto_envvar_prefix="MPFLASH")
18
- try:
22
+ if False and os.environ.get("COMPUTERNAME") == "JOSVERL-S4":
23
+ # intentional less error suppression on dev machine
19
24
  result = cli(standalone_mode=False)
20
- exit(result)
21
- except AttributeError as e:
22
- print(f"Error: {e}")
23
- exit(-1)
25
+ else:
26
+ try:
27
+ result = cli(standalone_mode=False)
28
+ exit(result)
29
+ except AttributeError as e:
30
+ log.error(f"Error: {e}")
31
+ exit(-1)
32
+ except click.exceptions.ClickException as e:
33
+ log.error(f"Error: {e}")
34
+ exit(-2)
24
35
 
25
36
 
26
37
  if __name__ == "__main__":
mpflash/mpflash/common.py CHANGED
@@ -1,11 +1,16 @@
1
+ import fnmatch
1
2
  import os
2
- import time
3
- from typing import TypedDict
3
+ import sys
4
+ from dataclasses import dataclass, field
5
+ from pathlib import Path
6
+ from typing import List, Optional, Union
4
7
 
5
8
  from github import Auth, Github
6
- from rich.progress import track
9
+ from serial.tools import list_ports
10
+ from serial.tools.list_ports_common import ListPortInfo
11
+
12
+ from .logger import log
7
13
 
8
- from mpflash.errors import MPFlashError
9
14
  # from mpflash.mpremoteboard import MPRemoteBoard
10
15
 
11
16
  PORT_FWTYPES = {
@@ -14,6 +19,7 @@ PORT_FWTYPES = {
14
19
  "esp8266": [".bin"],
15
20
  "rp2": [".uf2"],
16
21
  "samd": [".uf2"],
22
+ # below this not yet implemented / tested
17
23
  "mimxrt": [".hex"],
18
24
  "nrf": [".uf2"],
19
25
  "renesas-ra": [".hex"],
@@ -28,11 +34,118 @@ PAT = os.environ.get("GITHUB_TOKEN") or PAT_NO_ACCESS
28
34
  GH_CLIENT = Github(auth=Auth.Token(PAT))
29
35
 
30
36
 
31
- class FWInfo(TypedDict):
32
- filename: str
33
- port: str
34
- board: str
35
- variant: str
36
- preview: bool
37
- version: str
38
- build: str
37
+ @dataclass
38
+ class FWInfo:
39
+ """
40
+ Downloaded Firmware information
41
+ is somewhat related to the BOARD class in the mpboard_id module
42
+ """
43
+
44
+ port: str # MicroPython port
45
+ board: str # MicroPython board
46
+ filename: str = field(default="") # relative filename of the firmware image
47
+ firmware: str = field(default="") # url or path to original firmware image
48
+ variant: str = field(default="") # MicroPython variant
49
+ preview: bool = field(default=False) # True if the firmware is a preview version
50
+ version: str = field(default="") # MicroPython version (NO v prefix)
51
+ url: str = field(default="") # url to the firmware image download folder
52
+ build: str = field(default="0") # The build = number of commits since the last release
53
+ ext: str = field(default="") # the file extension of the firmware
54
+ family: str = field(default="micropython") # The family of the firmware
55
+ custom: bool = field(default=False) # True if the firmware is a custom build
56
+ description: str = field(default="") # Description used by this firmware (custom only)
57
+
58
+ def to_dict(self) -> dict:
59
+ """Convert the object to a dictionary"""
60
+ return self.__dict__
61
+
62
+ @classmethod
63
+ def from_dict(cls, data: dict) -> "FWInfo":
64
+ """Create a FWInfo object from a dictionary"""
65
+ # add missing keys
66
+ if "ext" not in data:
67
+ data["ext"] = Path(data["firmware"]).suffix
68
+ if "family" not in data:
69
+ data["family"] = "micropython"
70
+ return cls(**data)
71
+
72
+
73
+ @dataclass
74
+ class Params:
75
+ """Common parameters for downloading and flashing firmware"""
76
+
77
+ ports: List[str] = field(default_factory=list)
78
+ boards: List[str] = field(default_factory=list)
79
+ versions: List[str] = field(default_factory=list)
80
+ fw_folder: Path = Path()
81
+ serial: List[str] = field(default_factory=list)
82
+ ignore: List[str] = field(default_factory=list)
83
+
84
+
85
+ @dataclass
86
+ class DownloadParams(Params):
87
+ """Parameters for downloading firmware"""
88
+
89
+ clean: bool = False
90
+ force: bool = False
91
+
92
+
93
+ @dataclass
94
+ class FlashParams(Params):
95
+ """Parameters for flashing a board"""
96
+
97
+ erase: bool = True
98
+ bootloader: bool = True
99
+ cpu: str = ""
100
+
101
+
102
+ ParamType = Union[DownloadParams, FlashParams]
103
+
104
+
105
+ def filtered_comports(
106
+ ignore: Optional[List[str]] = None,
107
+ include: Optional[List[str]] = None,
108
+ bluetooth: bool = False,
109
+ ) -> List[ListPortInfo]: # sourcery skip: assign-if-exp
110
+ """
111
+ Get a list of filtered comports.
112
+ """
113
+ if not ignore:
114
+ ignore = []
115
+ elif not isinstance(ignore, list): # type: ignore
116
+ ignore = list(ignore)
117
+ if not include:
118
+ include = ["*"]
119
+ elif not isinstance(include, list): # type: ignore
120
+ include = list(include)
121
+
122
+ # remove ports that are to be ignored
123
+ log.trace(f"{include=}, {ignore=}, {bluetooth=}")
124
+ comports = [p for p in list_ports.comports() if not any(fnmatch.fnmatch(p.device, i) for i in ignore)]
125
+ log.trace(f"comports: {[p.device for p in comports]}")
126
+ # remove bluetooth ports
127
+
128
+ if include != ["*"]:
129
+ # if there are explicit ports to include, add them to the list
130
+ explicit = [p for p in list_ports.comports() if any(fnmatch.fnmatch(p.device, i) for i in include)]
131
+ log.trace(f"explicit: {[p.device for p in explicit]}")
132
+ if ignore == []:
133
+ # if nothing to ignore, just use the explicit list as a sinple sane default
134
+ comports = explicit
135
+ else:
136
+ # if there are ports to ignore, add the explicit list to the filtered list
137
+ comports = list(set(explicit) | set(comports))
138
+ if not bluetooth:
139
+ # filter out bluetooth ports
140
+ comports = [p for p in comports if "bluetooth" not in p.description.lower()]
141
+ comports = [p for p in comports if "BTHENUM" not in p.hwid]
142
+ if sys.platform == "darwin":
143
+ comports = [p for p in comports if ".Bluetooth" not in p.device]
144
+ log.trace(f"no Bluetooth: {[p.device for p in comports]}")
145
+ log.debug(f"filtered_comports: {[p.device for p in comports]}")
146
+ # sort
147
+ if sys.platform == "win32":
148
+ # Windows sort of comports by number - but fallback to device name
149
+ return sorted(comports, key=lambda x: int(x.device.split()[0][3:]) if x.device.split()[0][3:].isdigit() else x)
150
+ # sort by device name
151
+ return sorted(comports, key=lambda x: x.device)
mpflash/mpflash/config.py CHANGED
@@ -3,9 +3,18 @@
3
3
  from pathlib import Path
4
4
  from typing import List
5
5
 
6
+ import pkg_resources
6
7
  import platformdirs
7
8
 
8
9
 
10
+ def get_version():
11
+ name = __package__ or "mpflash"
12
+ try:
13
+ return pkg_resources.get_distribution(name).version
14
+ except pkg_resources.DistributionNotFound:
15
+ return "Package not found"
16
+
17
+
9
18
  class MPtoolConfig:
10
19
  """Centralized configuration for mpflash"""
11
20
 
@@ -14,6 +23,9 @@ class MPtoolConfig:
14
23
  ignore_ports: List[str] = []
15
24
  interactive: bool = True
16
25
  firmware_folder: Path = platformdirs.user_downloads_path() / "firmware"
26
+ # test options specified on the commandline
27
+ tests: List[str] = []
17
28
 
18
29
 
19
30
  config = MPtoolConfig()
31
+ __version__ = get_version()
@@ -0,0 +1,74 @@
1
+ from typing import List, Tuple
2
+
3
+ from rich import print
4
+ from rich.progress import BarColumn, Progress, SpinnerColumn, TextColumn, TimeElapsedColumn
5
+ from rich.table import Column
6
+
7
+ from mpflash.mpremoteboard import MPRemoteBoard
8
+
9
+ from .common import filtered_comports
10
+
11
+
12
+ def connected_ports_boards(
13
+ *, include: List[str], ignore: List[str]
14
+ ) -> Tuple[List[str], List[str], List[MPRemoteBoard]]:
15
+ """
16
+ Returns a tuple containing lists of unique ports and boards from the connected MCUs.
17
+ Boards that are physically connected, but give no tangible response are ignored.
18
+
19
+ Returns:
20
+ A tuple containing three lists:
21
+ - A list of unique ports where MCUs are connected.
22
+ - A list of unique board names of the connected MCUs.
23
+ - A list of MPRemoteBoard instances of the connected MCUs.
24
+ """
25
+ mpr_boards = [b for b in list_mcus(include=include, ignore=ignore) if b.connected]
26
+ ports = list({b.port for b in mpr_boards})
27
+ boards = list({b.board for b in mpr_boards})
28
+ return (ports, boards, mpr_boards)
29
+
30
+
31
+ # #########################################################################################################
32
+ rp_spinner = SpinnerColumn(finished_text="✅")
33
+ rp_text = TextColumn("{task.description} {task.fields[device]}", table_column=Column())
34
+ rp_bar = BarColumn(bar_width=None, table_column=Column())
35
+
36
+
37
+ def list_mcus(*, ignore: List[str], include: List[str], bluetooth: bool = False):
38
+ """
39
+ Retrieves information about connected microcontroller boards.
40
+
41
+ Returns:
42
+ List[MPRemoteBoard]: A list of MPRemoteBoard instances with board information.
43
+ Raises:
44
+ ConnectionError: If there is an error connecting to a board.
45
+ """
46
+ # conn_mcus = [MPRemoteBoard(sp) for sp in MPRemoteBoard.connected_boards(bluetooth) if sp not in config.ignore_ports]
47
+
48
+ comports = filtered_comports(
49
+ ignore=ignore,
50
+ include=include,
51
+ bluetooth=bluetooth,
52
+ )
53
+ conn_mcus = [MPRemoteBoard(c.device) for c in comports]
54
+
55
+ # a lot of boilerplate to show a progress bar with the comport currently scanned
56
+ # low update rate to facilitate screen readers/narration
57
+ with Progress(rp_spinner, rp_text, rp_bar, TimeElapsedColumn(), refresh_per_second=2) as progress:
58
+ tsk_scan = progress.add_task("[green]Scanning", visible=False, total=None)
59
+ progress.tasks[tsk_scan].fields["device"] = "..."
60
+ progress.tasks[tsk_scan].visible = True
61
+ progress.start_task(tsk_scan)
62
+ try:
63
+ for mcu in conn_mcus:
64
+ progress.update(tsk_scan, device=mcu.serialport.replace("/dev/", ""))
65
+ try:
66
+ mcu.get_mcu_info()
67
+ except ConnectionError as e:
68
+ print(f"Error: {e}")
69
+ continue
70
+ finally:
71
+ # transient
72
+ progress.stop_task(tsk_scan)
73
+ progress.tasks[tsk_scan].visible = False
74
+ return conn_mcus