micropython-stubber 1.23.1.post1__py3-none-any.whl → 1.23.3__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 (149) hide show
  1. {micropython_stubber-1.23.1.post1.dist-info → micropython_stubber-1.23.3.dist-info}/LICENSE +30 -30
  2. {micropython_stubber-1.23.1.post1.dist-info → micropython_stubber-1.23.3.dist-info}/METADATA +5 -5
  3. micropython_stubber-1.23.3.dist-info/RECORD +158 -0
  4. mpflash/README.md +220 -220
  5. mpflash/libusb_flash.ipynb +203 -203
  6. mpflash/mpflash/add_firmware.py +98 -98
  7. mpflash/mpflash/ask_input.py +236 -236
  8. mpflash/mpflash/basicgit.py +304 -284
  9. mpflash/mpflash/bootloader/__init__.py +2 -2
  10. mpflash/mpflash/bootloader/activate.py +60 -60
  11. mpflash/mpflash/bootloader/detect.py +82 -82
  12. mpflash/mpflash/bootloader/manual.py +101 -101
  13. mpflash/mpflash/bootloader/micropython.py +12 -12
  14. mpflash/mpflash/bootloader/touch1200.py +36 -36
  15. mpflash/mpflash/cli_download.py +129 -129
  16. mpflash/mpflash/cli_flash.py +224 -216
  17. mpflash/mpflash/cli_group.py +111 -111
  18. mpflash/mpflash/cli_list.py +87 -87
  19. mpflash/mpflash/cli_main.py +39 -39
  20. mpflash/mpflash/common.py +207 -166
  21. mpflash/mpflash/config.py +44 -44
  22. mpflash/mpflash/connected.py +96 -77
  23. mpflash/mpflash/download.py +364 -364
  24. mpflash/mpflash/downloaded.py +130 -130
  25. mpflash/mpflash/errors.py +9 -9
  26. mpflash/mpflash/flash/__init__.py +55 -55
  27. mpflash/mpflash/flash/esp.py +59 -59
  28. mpflash/mpflash/flash/stm32.py +19 -19
  29. mpflash/mpflash/flash/stm32_dfu.py +104 -104
  30. mpflash/mpflash/flash/uf2/__init__.py +88 -88
  31. mpflash/mpflash/flash/uf2/boardid.py +15 -15
  32. mpflash/mpflash/flash/uf2/linux.py +136 -130
  33. mpflash/mpflash/flash/uf2/macos.py +42 -42
  34. mpflash/mpflash/flash/uf2/uf2disk.py +12 -12
  35. mpflash/mpflash/flash/uf2/windows.py +43 -43
  36. mpflash/mpflash/flash/worklist.py +170 -170
  37. mpflash/mpflash/list.py +106 -106
  38. mpflash/mpflash/logger.py +41 -41
  39. mpflash/mpflash/mpboard_id/__init__.py +93 -93
  40. mpflash/mpflash/mpboard_id/add_boards.py +251 -251
  41. mpflash/mpflash/mpboard_id/board.py +37 -37
  42. mpflash/mpflash/mpboard_id/board_id.py +86 -86
  43. mpflash/mpflash/mpboard_id/store.py +48 -43
  44. mpflash/mpflash/mpremoteboard/__init__.py +266 -266
  45. mpflash/mpflash/mpremoteboard/mpy_fw_info.py +152 -141
  46. mpflash/mpflash/mpremoteboard/runner.py +140 -140
  47. mpflash/mpflash/vendor/click_aliases.py +91 -91
  48. mpflash/mpflash/vendor/dfu.py +165 -165
  49. mpflash/mpflash/vendor/pydfu.py +605 -605
  50. mpflash/mpflash/vendor/readme.md +2 -2
  51. mpflash/mpflash/versions.py +135 -135
  52. mpflash/poetry.lock +1687 -1599
  53. mpflash/pyproject.toml +65 -65
  54. mpflash/stm32_udev_rules.md +62 -62
  55. stubber/__init__.py +3 -3
  56. stubber/board/board_info.csv +193 -193
  57. stubber/board/boot.py +34 -34
  58. stubber/board/createstubs.py +986 -986
  59. stubber/board/createstubs_db.py +825 -825
  60. stubber/board/createstubs_db_min.py +331 -331
  61. stubber/board/createstubs_lvgl.py +741 -741
  62. stubber/board/createstubs_lvgl_min.py +741 -741
  63. stubber/board/createstubs_mem.py +766 -766
  64. stubber/board/createstubs_mem_min.py +306 -306
  65. stubber/board/createstubs_min.py +294 -294
  66. stubber/board/fw_info.py +141 -141
  67. stubber/board/info.py +183 -183
  68. stubber/board/main.py +19 -19
  69. stubber/board/modulelist.txt +247 -247
  70. stubber/board/pyrightconfig.json +34 -34
  71. stubber/bulk/mcu_stubber.py +437 -437
  72. stubber/codemod/_partials/__init__.py +48 -48
  73. stubber/codemod/_partials/db_main.py +147 -147
  74. stubber/codemod/_partials/lvgl_main.py +77 -77
  75. stubber/codemod/_partials/modules_reader.py +80 -80
  76. stubber/codemod/add_comment.py +53 -53
  77. stubber/codemod/add_method.py +65 -65
  78. stubber/codemod/board.py +317 -317
  79. stubber/codemod/enrich.py +145 -145
  80. stubber/codemod/merge_docstub.py +284 -284
  81. stubber/codemod/modify_list.py +54 -54
  82. stubber/codemod/utils.py +56 -56
  83. stubber/commands/build_cmd.py +94 -94
  84. stubber/commands/cli.py +49 -49
  85. stubber/commands/clone_cmd.py +78 -78
  86. stubber/commands/config_cmd.py +29 -29
  87. stubber/commands/enrich_folder_cmd.py +71 -71
  88. stubber/commands/get_core_cmd.py +71 -71
  89. stubber/commands/get_docstubs_cmd.py +92 -92
  90. stubber/commands/get_frozen_cmd.py +117 -117
  91. stubber/commands/get_mcu_cmd.py +102 -102
  92. stubber/commands/merge_cmd.py +66 -66
  93. stubber/commands/publish_cmd.py +118 -118
  94. stubber/commands/stub_cmd.py +31 -31
  95. stubber/commands/switch_cmd.py +62 -62
  96. stubber/commands/variants_cmd.py +48 -48
  97. stubber/cst_transformer.py +178 -178
  98. stubber/data/board_info.csv +193 -193
  99. stubber/data/board_info.json +1729 -1729
  100. stubber/data/micropython_tags.csv +15 -15
  101. stubber/data/requirements-core-micropython.txt +38 -38
  102. stubber/data/requirements-core-pycopy.txt +39 -39
  103. stubber/downloader.py +37 -37
  104. stubber/freeze/common.py +72 -72
  105. stubber/freeze/freeze_folder.py +69 -69
  106. stubber/freeze/freeze_manifest_2.py +126 -126
  107. stubber/freeze/get_frozen.py +131 -131
  108. stubber/get_cpython.py +112 -112
  109. stubber/get_lobo.py +59 -59
  110. stubber/minify.py +423 -423
  111. stubber/publish/bump.py +86 -86
  112. stubber/publish/candidates.py +275 -275
  113. stubber/publish/database.py +18 -18
  114. stubber/publish/defaults.py +40 -40
  115. stubber/publish/enums.py +24 -24
  116. stubber/publish/helpers.py +29 -29
  117. stubber/publish/merge_docstubs.py +132 -132
  118. stubber/publish/missing_class_methods.py +51 -51
  119. stubber/publish/package.py +150 -150
  120. stubber/publish/pathnames.py +51 -51
  121. stubber/publish/publish.py +120 -120
  122. stubber/publish/pypi.py +42 -42
  123. stubber/publish/stubpackage.py +1051 -1051
  124. stubber/rst/__init__.py +9 -9
  125. stubber/rst/classsort.py +78 -78
  126. stubber/rst/lookup.py +531 -531
  127. stubber/rst/output_dict.py +401 -401
  128. stubber/rst/reader.py +814 -814
  129. stubber/rst/report_return.py +77 -77
  130. stubber/rst/rst_utils.py +541 -541
  131. stubber/stubber.py +38 -38
  132. stubber/stubs_from_docs.py +90 -90
  133. stubber/tools/manifestfile.py +654 -654
  134. stubber/tools/readme.md +6 -6
  135. stubber/update_fallback.py +117 -117
  136. stubber/update_module_list.py +123 -123
  137. stubber/utils/__init__.py +6 -6
  138. stubber/utils/config.py +137 -137
  139. stubber/utils/makeversionhdr.py +54 -54
  140. stubber/utils/manifest.py +90 -90
  141. stubber/utils/post.py +80 -80
  142. stubber/utils/repos.py +156 -156
  143. stubber/utils/stubmaker.py +139 -139
  144. stubber/utils/typed_config_toml.py +80 -80
  145. stubber/variants.py +106 -106
  146. micropython_stubber-1.23.1.post1.dist-info/RECORD +0 -159
  147. mpflash/basicgit.py +0 -288
  148. {micropython_stubber-1.23.1.post1.dist-info → micropython_stubber-1.23.3.dist-info}/WHEEL +0 -0
  149. {micropython_stubber-1.23.1.post1.dist-info → micropython_stubber-1.23.3.dist-info}/entry_points.txt +0 -0
@@ -1,87 +1,87 @@
1
- import json
2
- from typing import List
3
-
4
- import rich_click as click
5
- from rich import print
6
-
7
- from .cli_group import cli
8
- from .connected import list_mcus
9
- from .list import show_mcus
10
- from .logger import make_quiet
11
-
12
-
13
- @cli.command(
14
- "list",
15
- help="List the connected MCU boards. alias: devs",
16
- aliases=["devs"],
17
- )
18
- @click.option(
19
- "--json",
20
- "-j",
21
- "as_json",
22
- is_flag=True,
23
- default=False,
24
- show_default=True,
25
- help="""Output in json format""",
26
- )
27
- @click.option(
28
- "--serial",
29
- "--serial-port",
30
- "-s",
31
- "serial",
32
- default=["*"],
33
- multiple=True,
34
- show_default=True,
35
- help="Serial port(s) (or globs) to list. ",
36
- metavar="SERIALPORT",
37
- )
38
- @click.option(
39
- "--ignore",
40
- "-i",
41
- is_eager=True,
42
- help="Serial port(s) (or globs) to ignore. Defaults to MPFLASH_IGNORE.",
43
- multiple=True,
44
- default=[],
45
- envvar="MPFLASH_IGNORE",
46
- show_default=True,
47
- metavar="SERIALPORT",
48
- )
49
- @click.option(
50
- "--bluetooth/--no-bluetooth",
51
- "-b/-nb",
52
- is_flag=True,
53
- default=False,
54
- show_default=True,
55
- help="""Include bluetooth ports in the list""",
56
- )
57
- @click.option(
58
- "--progress/--no-progress",
59
- # "-p/-np", -p is already used for --port
60
- "progress",
61
- is_flag=True,
62
- default=True,
63
- show_default=True,
64
- help="""Show progress""",
65
- )
66
- def cli_list_mcus(serial: List[str], ignore: List[str], bluetooth: bool, as_json: bool, progress: bool = True) -> int:
67
- """List the connected MCU boards, and output in a nice table or json."""
68
- serial = list(serial)
69
- ignore = list(ignore)
70
- if as_json:
71
- # avoid noise in json output
72
- make_quiet()
73
- # TODO? Ask user to select a serialport if [?] is given ?
74
-
75
- conn_mcus = list_mcus(ignore=ignore, include=serial, bluetooth=bluetooth)
76
- # ignore boards that have the [micropython-stubber] ignore flag set
77
- conn_mcus = [item for item in conn_mcus if not (item.toml.get("mpflash", {}).get("ignore", False))]
78
- if as_json:
79
- # remove the path and firmware attibutes from the json output as they are always empty
80
- for mcu in conn_mcus:
81
- del mcu.path
82
- del mcu.firmware
83
- print(json.dumps([mcu.__dict__ for mcu in conn_mcus], indent=4))
84
- progress = False
85
- if progress:
86
- show_mcus(conn_mcus, refresh=False)
87
- return 0 if conn_mcus else 1
1
+ import json
2
+ from typing import List
3
+
4
+ import rich_click as click
5
+ from rich import print
6
+
7
+ from .cli_group import cli
8
+ from .connected import list_mcus
9
+ from .list import show_mcus
10
+ from .logger import make_quiet
11
+
12
+
13
+ @cli.command(
14
+ "list",
15
+ help="List the connected MCU boards. alias: devs",
16
+ aliases=["devs"],
17
+ )
18
+ @click.option(
19
+ "--json",
20
+ "-j",
21
+ "as_json",
22
+ is_flag=True,
23
+ default=False,
24
+ show_default=True,
25
+ help="""Output in json format""",
26
+ )
27
+ @click.option(
28
+ "--serial",
29
+ "--serial-port",
30
+ "-s",
31
+ "serial",
32
+ default=["*"],
33
+ multiple=True,
34
+ show_default=True,
35
+ help="Serial port(s) (or globs) to list. ",
36
+ metavar="SERIALPORT",
37
+ )
38
+ @click.option(
39
+ "--ignore",
40
+ "-i",
41
+ is_eager=True,
42
+ help="Serial port(s) (or globs) to ignore. Defaults to MPFLASH_IGNORE.",
43
+ multiple=True,
44
+ default=[],
45
+ envvar="MPFLASH_IGNORE",
46
+ show_default=True,
47
+ metavar="SERIALPORT",
48
+ )
49
+ @click.option(
50
+ "--bluetooth/--no-bluetooth",
51
+ "-b/-nb",
52
+ is_flag=True,
53
+ default=False,
54
+ show_default=True,
55
+ help="""Include bluetooth ports in the list""",
56
+ )
57
+ @click.option(
58
+ "--progress/--no-progress",
59
+ # "-p/-np", -p is already used for --port
60
+ "progress",
61
+ is_flag=True,
62
+ default=True,
63
+ show_default=True,
64
+ help="""Show progress""",
65
+ )
66
+ def cli_list_mcus(serial: List[str], ignore: List[str], bluetooth: bool, as_json: bool, progress: bool = True) -> int:
67
+ """List the connected MCU boards, and output in a nice table or json."""
68
+ serial = list(serial)
69
+ ignore = list(ignore)
70
+ if as_json:
71
+ # avoid noise in json output
72
+ make_quiet()
73
+ # TODO? Ask user to select a serialport if [?] is given ?
74
+
75
+ conn_mcus = list_mcus(ignore=ignore, include=serial, bluetooth=bluetooth)
76
+ # ignore boards that have the [micropython-stubber] ignore flag set
77
+ conn_mcus = [item for item in conn_mcus if not (item.toml.get("mpflash", {}).get("ignore", False))]
78
+ if as_json:
79
+ # remove the path and firmware attibutes from the json output as they are always empty
80
+ for mcu in conn_mcus:
81
+ del mcu.path
82
+ del mcu.firmware
83
+ print(json.dumps([mcu.__dict__ for mcu in conn_mcus], indent=4))
84
+ progress = False
85
+ if progress:
86
+ show_mcus(conn_mcus, refresh=False)
87
+ return 0 if conn_mcus else 1
@@ -1,39 +1,39 @@
1
- """mpflash is a CLI to download and flash MicroPython firmware to various boards."""
2
-
3
- import os
4
-
5
- import click.exceptions as click_exceptions
6
- from loguru import logger as log
7
-
8
- from .cli_download import cli_download
9
- from .cli_flash import cli_flash_board
10
- from .cli_group import cli
11
- from .cli_list import cli_list_mcus
12
-
13
-
14
- def mpflash():
15
- cli.add_command(cli_list_mcus)
16
- cli.add_command(cli_download)
17
- cli.add_command(cli_flash_board)
18
-
19
- # cli(auto_envvar_prefix="MPFLASH")
20
- if False and os.environ.get("COMPUTERNAME").startswith("JOSVERL"):
21
- # intentional less error suppression on dev machine
22
- result = cli(standalone_mode=False)
23
- else:
24
- try:
25
- result = cli(standalone_mode=True)
26
- exit(result)
27
- except AttributeError as e:
28
- log.error(f"Error: {e}")
29
- exit(-1)
30
- except click_exceptions.ClickException as e:
31
- log.error(f"Error: {e}")
32
- exit(-2)
33
- except click_exceptions.Abort as e:
34
- # Aborted - Ctrl-C
35
- exit(-3)
36
-
37
-
38
- if __name__ == "__main__":
39
- mpflash()
1
+ """mpflash is a CLI to download and flash MicroPython firmware to various boards."""
2
+
3
+ import os
4
+
5
+ import click.exceptions as click_exceptions
6
+ from loguru import logger as log
7
+
8
+ from .cli_download import cli_download
9
+ from .cli_flash import cli_flash_board
10
+ from .cli_group import cli
11
+ from .cli_list import cli_list_mcus
12
+
13
+
14
+ def mpflash():
15
+ cli.add_command(cli_list_mcus)
16
+ cli.add_command(cli_download)
17
+ cli.add_command(cli_flash_board)
18
+
19
+ # cli(auto_envvar_prefix="MPFLASH")
20
+ if False and os.environ.get("COMPUTERNAME").startswith("JOSVERL"):
21
+ # intentional less error suppression on dev machine
22
+ result = cli(standalone_mode=False)
23
+ else:
24
+ try:
25
+ result = cli(standalone_mode=True)
26
+ exit(result)
27
+ except AttributeError as e:
28
+ log.error(f"Error: {e}")
29
+ exit(-1)
30
+ except click_exceptions.ClickException as e:
31
+ log.error(f"Error: {e}")
32
+ exit(-2)
33
+ except click_exceptions.Abort as e:
34
+ # Aborted - Ctrl-C
35
+ exit(-3)
36
+
37
+
38
+ if __name__ == "__main__":
39
+ mpflash()
mpflash/mpflash/common.py CHANGED
@@ -1,166 +1,207 @@
1
- import fnmatch
2
- import os
3
- import sys
4
- from dataclasses import dataclass, field
5
- from enum import Enum
6
- from pathlib import Path
7
- from typing import List, Optional, Union
8
-
9
- from github import Auth, Github
10
- from serial.tools import list_ports
11
- from serial.tools.list_ports_common import ListPortInfo
12
-
13
- from .logger import log
14
-
15
- # from mpflash.mpremoteboard import MPRemoteBoard
16
-
17
- PORT_FWTYPES = {
18
- "stm32": [".dfu"], # need .dfu for pydfu.py - .hex for cube cli/GUI
19
- "esp32": [".bin"],
20
- "esp8266": [".bin"],
21
- "rp2": [".uf2"],
22
- "samd": [".uf2"],
23
- # below this not yet implemented / tested
24
- "mimxrt": [".hex"],
25
- "nrf": [".uf2"],
26
- "renesas-ra": [".hex"],
27
- }
28
-
29
- # Token with no permissions to avoid throttling
30
- # 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
31
- PAT_NO_ACCESS = (
32
- "github_pat" + "_11AAHPVFQ0qAkDnSUaMKSp" + "_ZkDl5NRRwBsUN6EYg9ahp1Dvj4FDDONnXVgimxC2EtpY7Q7BUKBoQ0Jq72X"
33
- )
34
- PAT = os.environ.get("GITHUB_TOKEN") or PAT_NO_ACCESS
35
- GH_CLIENT = Github(auth=Auth.Token(PAT))
36
-
37
-
38
- @dataclass
39
- class FWInfo:
40
- """
41
- Downloaded Firmware information
42
- is somewhat related to the BOARD class in the mpboard_id module
43
- """
44
-
45
- port: str # MicroPython port
46
- board: str # MicroPython board
47
- filename: str = field(default="") # relative filename of the firmware image
48
- firmware: str = field(default="") # url or path to original firmware image
49
- variant: str = field(default="") # MicroPython variant
50
- preview: bool = field(default=False) # True if the firmware is a preview version
51
- version: str = field(default="") # MicroPython version (NO v prefix)
52
- url: str = field(default="") # url to the firmware image download folder
53
- build: str = field(default="0") # The build = number of commits since the last release
54
- ext: str = field(default="") # the file extension of the firmware
55
- family: str = field(default="micropython") # The family of the firmware
56
- custom: bool = field(default=False) # True if the firmware is a custom build
57
- description: str = field(default="") # Description used by this firmware (custom only)
58
-
59
- def to_dict(self) -> dict:
60
- """Convert the object to a dictionary"""
61
- return self.__dict__
62
-
63
- @classmethod
64
- def from_dict(cls, data: dict) -> "FWInfo":
65
- """Create a FWInfo object from a dictionary"""
66
- # add missing keys
67
- if "ext" not in data:
68
- data["ext"] = Path(data["firmware"]).suffix
69
- if "family" not in data:
70
- data["family"] = "micropython"
71
- return cls(**data)
72
-
73
-
74
- @dataclass
75
- class Params:
76
- """Common parameters for downloading and flashing firmware"""
77
-
78
- ports: List[str] = field(default_factory=list)
79
- boards: List[str] = field(default_factory=list)
80
- versions: List[str] = field(default_factory=list)
81
- fw_folder: Path = Path()
82
- serial: List[str] = field(default_factory=list)
83
- ignore: List[str] = field(default_factory=list)
84
-
85
-
86
- @dataclass
87
- class DownloadParams(Params):
88
- """Parameters for downloading firmware"""
89
-
90
- clean: bool = False
91
- force: bool = False
92
-
93
-
94
- class BootloaderMethod(Enum):
95
- AUTO = "auto"
96
- MANUAL = "manual"
97
- MPY = "mpy"
98
- TOUCH_1200 = "touch1200"
99
- NONE = "none"
100
-
101
-
102
-
103
- @dataclass
104
- class FlashParams(Params):
105
- """Parameters for flashing a board"""
106
-
107
- erase: bool = True
108
- bootloader: BootloaderMethod = BootloaderMethod.NONE
109
- cpu: str = ""
110
-
111
- def __post_init__(self):
112
- if isinstance(self.bootloader, str):
113
- self.bootloader = BootloaderMethod(self.bootloader)
114
-
115
-
116
- ParamType = Union[DownloadParams, FlashParams]
117
-
118
-
119
- def filtered_comports(
120
- ignore: Optional[List[str]] = None,
121
- include: Optional[List[str]] = None,
122
- bluetooth: bool = False,
123
- ) -> List[ListPortInfo]: # sourcery skip: assign-if-exp
124
- """
125
- Get a list of filtered comports using the include and ignore lists.
126
- both can be globs (e.g. COM*) or exact port names (e.g. COM1)
127
- """
128
- if not ignore:
129
- ignore = []
130
- elif not isinstance(ignore, list): # type: ignore
131
- ignore = list(ignore)
132
- if not include:
133
- include = ["*"]
134
- elif not isinstance(include, list): # type: ignore
135
- include = list(include)
136
-
137
- # remove ports that are to be ignored
138
- log.trace(f"{include=}, {ignore=}, {bluetooth=}")
139
- comports = [p for p in list_ports.comports() if not any(fnmatch.fnmatch(p.device, i) for i in ignore)]
140
- log.trace(f"comports: {[p.device for p in comports]}")
141
- # remove bluetooth ports
142
-
143
- if include != ["*"]:
144
- # if there are explicit ports to include, add them to the list
145
- explicit = [p for p in list_ports.comports() if any(fnmatch.fnmatch(p.device, i) for i in include)]
146
- log.trace(f"explicit: {[p.device for p in explicit]}")
147
- if ignore == []:
148
- # if nothing to ignore, just use the explicit list as a sinple sane default
149
- comports = explicit
150
- else:
151
- # if there are ports to ignore, add the explicit list to the filtered list
152
- comports = list(set(explicit) | set(comports))
153
- if not bluetooth:
154
- # filter out bluetooth ports
155
- comports = [p for p in comports if "bluetooth" not in p.description.lower()]
156
- comports = [p for p in comports if "BTHENUM" not in p.hwid]
157
- if sys.platform == "darwin":
158
- comports = [p for p in comports if ".Bluetooth" not in p.device]
159
- log.trace(f"no Bluetooth: {[p.device for p in comports]}")
160
- log.debug(f"filtered_comports: {[p.device for p in comports]}")
161
- # sort
162
- if sys.platform == "win32":
163
- # Windows sort of comports by number - but fallback to device name
164
- return sorted(comports, key=lambda x: int(x.device.split()[0][3:]) if x.device.split()[0][3:].isdigit() else x)
165
- # sort by device name
166
- return sorted(comports, key=lambda x: x.device)
1
+ import fnmatch
2
+ import glob
3
+ import os
4
+ import platform
5
+ import sys
6
+ from dataclasses import dataclass, field
7
+ from enum import Enum
8
+ from pathlib import Path
9
+ from typing import List, Optional, Union
10
+
11
+ from github import Auth, Github
12
+ from serial.tools import list_ports
13
+ from serial.tools.list_ports_common import ListPortInfo
14
+
15
+ from .logger import log
16
+
17
+ # from mpflash.mpremoteboard import MPRemoteBoard
18
+
19
+ PORT_FWTYPES = {
20
+ "stm32": [".dfu"], # need .dfu for pydfu.py - .hex for cube cli/GUI
21
+ "esp32": [".bin"],
22
+ "esp8266": [".bin"],
23
+ "rp2": [".uf2"],
24
+ "samd": [".uf2"],
25
+ # below this not yet implemented / tested
26
+ "mimxrt": [".hex"],
27
+ "nrf": [".uf2"],
28
+ "renesas-ra": [".hex"],
29
+ }
30
+
31
+ # Token with no permissions to avoid throttling
32
+ # 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
33
+ PAT_NO_ACCESS = "github_pat_"+"11AAHPVFQ0G4NTaQ73Bw5J"+"_fAp7K9sZ1qL8VFnI9g78eUlCdmOXHB3WzSdj2jtEYb4XF3N7PDJBl32qIxq"
34
+
35
+ PAT = os.environ.get("GITHUB_TOKEN") or PAT_NO_ACCESS
36
+ GH_CLIENT = Github(auth=Auth.Token(PAT))
37
+
38
+
39
+ @dataclass
40
+ class FWInfo:
41
+ """
42
+ Downloaded Firmware information
43
+ is somewhat related to the BOARD class in the mpboard_id module
44
+ """
45
+
46
+ port: str # MicroPython port
47
+ board: str # MicroPython board
48
+ filename: str = field(default="") # relative filename of the firmware image
49
+ firmware: str = field(default="") # url or path to original firmware image
50
+ variant: str = field(default="") # MicroPython variant
51
+ preview: bool = field(default=False) # True if the firmware is a preview version
52
+ version: str = field(default="") # MicroPython version (NO v prefix)
53
+ url: str = field(default="") # url to the firmware image download folder
54
+ build: str = field(default="0") # The build = number of commits since the last release
55
+ ext: str = field(default="") # the file extension of the firmware
56
+ family: str = field(default="micropython") # The family of the firmware
57
+ custom: bool = field(default=False) # True if the firmware is a custom build
58
+ description: str = field(default="") # Description used by this firmware (custom only)
59
+
60
+ def to_dict(self) -> dict:
61
+ """Convert the object to a dictionary"""
62
+ return self.__dict__
63
+
64
+ @classmethod
65
+ def from_dict(cls, data: dict) -> "FWInfo":
66
+ """Create a FWInfo object from a dictionary"""
67
+ # add missing keys
68
+ if "ext" not in data:
69
+ data["ext"] = Path(data["firmware"]).suffix
70
+ if "family" not in data:
71
+ data["family"] = "micropython"
72
+ return cls(**data)
73
+
74
+
75
+ @dataclass
76
+ class Params:
77
+ """Common parameters for downloading and flashing firmware"""
78
+
79
+ ports: List[str] = field(default_factory=list)
80
+ boards: List[str] = field(default_factory=list)
81
+ versions: List[str] = field(default_factory=list)
82
+ fw_folder: Path = Path()
83
+ serial: List[str] = field(default_factory=list)
84
+ ignore: List[str] = field(default_factory=list)
85
+ bluetooth: bool = False
86
+
87
+
88
+ @dataclass
89
+ class DownloadParams(Params):
90
+ """Parameters for downloading firmware"""
91
+
92
+ clean: bool = False
93
+ force: bool = False
94
+
95
+
96
+ class BootloaderMethod(Enum):
97
+ AUTO = "auto"
98
+ MANUAL = "manual"
99
+ MPY = "mpy"
100
+ TOUCH_1200 = "touch1200"
101
+ NONE = "none"
102
+
103
+
104
+ @dataclass
105
+ class FlashParams(Params):
106
+ """Parameters for flashing a board"""
107
+
108
+ erase: bool = True
109
+ bootloader: BootloaderMethod = BootloaderMethod.NONE
110
+ cpu: str = ""
111
+
112
+ def __post_init__(self):
113
+ if isinstance(self.bootloader, str):
114
+ self.bootloader = BootloaderMethod(self.bootloader)
115
+
116
+
117
+ ParamType = Union[DownloadParams, FlashParams]
118
+
119
+
120
+ def filtered_comports(
121
+ ignore: Optional[List[str]] = None,
122
+ include: Optional[List[str]] = None,
123
+ bluetooth: bool = False,
124
+ ) -> List[ListPortInfo]: # sourcery skip: assign-if-exp
125
+ """
126
+ Get a list of filtered comports using the include and ignore lists.
127
+ both can be globs (e.g. COM*) or exact port names (e.g. COM1)
128
+ """
129
+ if not ignore:
130
+ ignore = []
131
+ elif not isinstance(ignore, list): # type: ignore
132
+ ignore = list(ignore)
133
+ if not include:
134
+ include = ["*"]
135
+ elif not isinstance(include, list): # type: ignore
136
+ include = list(include)
137
+
138
+ # remove ports that are to be ignored
139
+ log.trace(f"{include=}, {ignore=}, {bluetooth=}")
140
+
141
+ comports = [
142
+ p for p in list_ports.comports() if not any(fnmatch.fnmatch(p.device, i) for i in ignore)
143
+ ]
144
+ if platform.system() == "Linux":
145
+ # use p.location to filter out the bogus ports on newer Linux kernels
146
+ # filter out the bogus ports on newer Linux kernels
147
+ comports = [p for p in comports if p.location]
148
+
149
+ log.trace(f"comports: {[p.device for p in comports]}")
150
+ # remove bluetooth ports
151
+
152
+ if include != ["*"]:
153
+ # if there are explicit ports to include, add them to the list
154
+ explicit = [
155
+ p for p in list_ports.comports() if any(fnmatch.fnmatch(p.device, i) for i in include)
156
+ ]
157
+ log.trace(f"explicit: {[p.device for p in explicit]}")
158
+ if ignore == []:
159
+ # if nothing to ignore, just use the explicit list as a sinple sane default
160
+ comports = explicit
161
+ else:
162
+ # if there are ports to ignore, add the explicit list to the filtered list
163
+ comports = list(set(explicit) | set(comports))
164
+ if not bluetooth:
165
+ # filter out bluetooth ports
166
+ comports = [p for p in comports if "bluetooth" not in p.description.lower()]
167
+ comports = [p for p in comports if "BTHENUM" not in p.hwid]
168
+ if sys.platform == "darwin":
169
+ comports = [p for p in comports if ".Bluetooth" not in p.device]
170
+ log.trace(f"no Bluetooth: {[p.device for p in comports]}")
171
+ log.debug(f"filtered_comports: {[p.device for p in comports]}")
172
+ # sort
173
+ if sys.platform == "win32":
174
+ # Windows sort of comports by number - but fallback to device name
175
+ return sorted(
176
+ comports,
177
+ key=lambda x: int(x.device.split()[0][3:]) if x.device.split()[0][3:].isdigit() else x,
178
+ )
179
+ # sort by device name
180
+ return sorted(comports, key=lambda x: x.device)
181
+
182
+
183
+ def find_serial_by_path(target_port: str):
184
+ """Find the symbolic link path of a serial port by its device path."""
185
+ # sourcery skip: use-next
186
+
187
+ if os.name == "nt":
188
+ return None
189
+ # List all available serial ports
190
+ available_ports = list_ports.comports()
191
+ # Filter to get the device path of the target port
192
+ target_device_path = None
193
+ for port in available_ports:
194
+ if port.device == target_port:
195
+ target_device_path = port.device
196
+ break
197
+
198
+ if not target_device_path:
199
+ return None # Target port not found among available ports
200
+
201
+ # Search for all symbolic links in /dev/serial/by-path/
202
+ for symlink in glob.glob("/dev/serial/by-path/*"):
203
+ # Resolve the symbolic link to its target
204
+ if os.path.realpath(symlink) == target_device_path:
205
+ return symlink # Return the matching symlink path
206
+
207
+ return None # Return None if no match is found