micropython-stubber 1.17.5__py3-none-any.whl → 1.19.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.
Files changed (74) hide show
  1. {micropython_stubber-1.17.5.dist-info → micropython_stubber-1.19.0.dist-info}/METADATA +7 -6
  2. {micropython_stubber-1.17.5.dist-info → micropython_stubber-1.19.0.dist-info}/RECORD +71 -52
  3. mpflash/README.md +22 -3
  4. mpflash/libusb_flash.ipynb +203 -0
  5. mpflash/mpflash/ask_input.py +234 -0
  6. mpflash/mpflash/cli_download.py +107 -0
  7. mpflash/mpflash/cli_flash.py +165 -0
  8. mpflash/mpflash/cli_group.py +41 -8
  9. mpflash/mpflash/cli_list.py +41 -0
  10. mpflash/mpflash/cli_main.py +13 -8
  11. mpflash/mpflash/common.py +33 -122
  12. mpflash/mpflash/config.py +9 -0
  13. mpflash/mpflash/{downloader.py → download.py} +112 -120
  14. mpflash/mpflash/downloaded.py +108 -0
  15. mpflash/mpflash/errors.py +5 -0
  16. mpflash/mpflash/flash.py +69 -0
  17. mpflash/mpflash/flash_esp.py +17 -23
  18. mpflash/mpflash/flash_stm32.py +16 -113
  19. mpflash/mpflash/flash_stm32_cube.py +111 -0
  20. mpflash/mpflash/flash_stm32_dfu.py +101 -0
  21. mpflash/mpflash/flash_uf2.py +8 -8
  22. mpflash/mpflash/flash_uf2_linux.py +25 -12
  23. mpflash/mpflash/flash_uf2_windows.py +24 -12
  24. mpflash/mpflash/list.py +34 -37
  25. mpflash/mpflash/logger.py +12 -13
  26. mpflash/mpflash/mpboard_id/__init__.py +96 -0
  27. mpflash/mpflash/mpboard_id/board_id.py +63 -0
  28. mpflash/mpflash/mpboard_id/board_info.csv +2213 -0
  29. mpflash/mpflash/mpboard_id/board_info.json +19910 -0
  30. mpflash/mpflash/mpremoteboard/__init__.py +208 -0
  31. mpflash/mpflash/mpremoteboard/mpy_fw_info.py +141 -0
  32. {stubber/bulk → mpflash/mpflash/mpremoteboard}/runner.py +22 -5
  33. mpflash/mpflash/vendor/dfu.py +164 -0
  34. mpflash/mpflash/vendor/pydfu.py +605 -0
  35. mpflash/mpflash/vendor/readme.md +3 -0
  36. mpflash/mpflash/vendor/versions.py +113 -0
  37. mpflash/mpflash/worklist.py +147 -0
  38. mpflash/poetry.lock +411 -595
  39. mpflash/pyproject.toml +24 -8
  40. mpflash/stm32_udev_rules.md +63 -0
  41. stubber/__init__.py +1 -1
  42. stubber/basicgit.py +1 -0
  43. stubber/board/createstubs.py +10 -4
  44. stubber/board/createstubs_db.py +11 -5
  45. stubber/board/createstubs_db_min.py +61 -58
  46. stubber/board/createstubs_db_mpy.mpy +0 -0
  47. stubber/board/createstubs_mem.py +11 -5
  48. stubber/board/createstubs_mem_min.py +56 -53
  49. stubber/board/createstubs_mem_mpy.mpy +0 -0
  50. stubber/board/createstubs_min.py +54 -51
  51. stubber/board/createstubs_mpy.mpy +0 -0
  52. stubber/bulk/mcu_stubber.py +9 -5
  53. stubber/codemod/_partials/db_main.py +14 -25
  54. stubber/codemod/_partials/lvgl_main.py +2 -2
  55. stubber/codemod/board.py +10 -3
  56. stubber/commands/clone_cmd.py +7 -7
  57. stubber/commands/config_cmd.py +3 -0
  58. stubber/freeze/get_frozen.py +0 -2
  59. stubber/publish/candidates.py +1 -1
  60. stubber/publish/package.py +1 -1
  61. stubber/publish/pathnames.py +1 -1
  62. stubber/publish/stubpackage.py +1 -0
  63. stubber/rst/lookup.py +1 -1
  64. stubber/tools/manifestfile.py +5 -3
  65. stubber/utils/config.py +26 -36
  66. stubber/utils/repos.py +2 -2
  67. stubber/utils/versions.py +1 -0
  68. mpflash/mpflash/flasher.py +0 -287
  69. stubber/bulk/board_id.py +0 -40
  70. stubber/bulk/mpremoteboard.py +0 -141
  71. {micropython_stubber-1.17.5.dist-info → micropython_stubber-1.19.0.dist-info}/LICENSE +0 -0
  72. {micropython_stubber-1.17.5.dist-info → micropython_stubber-1.19.0.dist-info}/WHEEL +0 -0
  73. {micropython_stubber-1.17.5.dist-info → micropython_stubber-1.19.0.dist-info}/entry_points.txt +0 -0
  74. /mpflash/mpflash/{uf2_boardid.py → flash_uf2_boardid.py} +0 -0
@@ -0,0 +1,208 @@
1
+ """
2
+ Module to run mpremote commands, and retry on failure or timeout
3
+ """
4
+
5
+ import sys
6
+ import time
7
+ from pathlib import Path
8
+ from typing import List, Optional, Union
9
+
10
+ import serial.tools.list_ports
11
+ from loguru import logger as log
12
+ from rich.progress import track
13
+ from tenacity import retry, stop_after_attempt, wait_fixed
14
+
15
+ from mpflash.errors import MPFlashError
16
+ from mpflash.mpboard_id.board_id import find_board_id
17
+ from mpflash.mpremoteboard.runner import run
18
+
19
+ ###############################################################################################
20
+ # TODO : make this a bit nicer
21
+ HERE = Path(__file__).parent
22
+
23
+ OK = 0
24
+ ERROR = -1
25
+ RETRIES = 3
26
+ ###############################################################################################
27
+
28
+
29
+ class MPRemoteBoard:
30
+ """Class to run mpremote commands"""
31
+
32
+ def __init__(self, serialport: str = "", update: bool = False):
33
+ """
34
+ Initialize MPRemoteBoard object.
35
+
36
+ Parameters:
37
+ - serialport (str): The serial port to connect to. Default is an empty string.
38
+ - update (bool): Whether to update the MCU information. Default is False.
39
+ """
40
+ self.serialport = serialport
41
+ self.firmware = {}
42
+
43
+ self.connected = False
44
+ self.path: Optional[Path] = None
45
+ self.family = "unknown"
46
+ self.description = ""
47
+ self.version = ""
48
+ self.port = ""
49
+ self.board = ""
50
+ self.cpu = ""
51
+ self.arch = ""
52
+ self.mpy = ""
53
+ self.build = ""
54
+ if update:
55
+ self.get_mcu_info()
56
+
57
+ def __str__(self):
58
+ """
59
+ Return a string representation of the MPRemoteBoard object.
60
+
61
+ Returns:
62
+ - str: The string representation of the object.
63
+ """
64
+ return f"MPRemoteBoard({self.serialport}, {self.family} {self.port}, {self.board}, {self.version})"
65
+
66
+ @staticmethod
67
+ def connected_boards(bluetooth: bool = False) -> List[str]:
68
+ """
69
+ Get a list of connected boards.
70
+
71
+ Parameters:
72
+ - bluetooth (bool): Whether to include Bluetooth ports. Default is False.
73
+
74
+ Returns:
75
+ - List[str]: A list of connected board ports.
76
+ """
77
+ ports = serial.tools.list_ports.comports()
78
+
79
+ if not bluetooth:
80
+ # filter out bluetooth ports
81
+ ports = [p for p in ports if "bluetooth" not in p.description.lower()]
82
+ ports = [p for p in ports if "BTHENUM" not in p.hwid]
83
+
84
+ return sorted([p.device for p in ports])
85
+
86
+ @retry(stop=stop_after_attempt(RETRIES), wait=wait_fixed(1), reraise=True) # type: ignore ## retry_error_cls=ConnectionError,
87
+ def get_mcu_info(self, timeout: int = 2):
88
+ """
89
+ Get MCU information from the connected board.
90
+
91
+ Parameters:
92
+ - timeout (int): The timeout value in seconds. Default is 2.
93
+
94
+ Raises:
95
+ - ConnectionError: If failed to get mcu_info for the serial port.
96
+ """
97
+ rc, result = self.run_command(
98
+ ["run", str(HERE / "mpy_fw_info.py")],
99
+ no_info=True,
100
+ timeout=timeout,
101
+ )
102
+ if rc != OK:
103
+ raise ConnectionError(f"Failed to get mcu_info for {self.serialport}")
104
+ # Ok we have the info, now parse it
105
+ s = result[0].strip()
106
+ if s.startswith("{") and s.endswith("}"):
107
+ info = eval(s)
108
+ self.family = info["family"]
109
+ self.version = info["version"]
110
+ self.build = info["build"]
111
+ self.port = info["port"]
112
+ self.cpu = info["cpu"]
113
+ self.arch = info["arch"]
114
+ self.mpy = info["mpy"]
115
+ self.description = descr = info["board"]
116
+ pos = descr.rfind(" with")
117
+ short_descr = descr[:pos].strip() if pos != -1 else ""
118
+ if board_name := find_board_id(descr, short_descr):
119
+ self.board = board_name
120
+ else:
121
+ self.board = "UNKNOWN"
122
+
123
+ def disconnect(self) -> bool:
124
+ """
125
+ Disconnect from a board.
126
+
127
+ Returns:
128
+ - bool: True if successfully disconnected, False otherwise.
129
+ """
130
+ if not self.connected:
131
+ return True
132
+ if not self.serialport:
133
+ log.error("No port connected")
134
+ self.connected = False
135
+ return False
136
+ log.info(f"Disconnecting from {self.serialport}")
137
+ result = self.run_command(["disconnect"])[0] == OK
138
+ self.connected = False
139
+ return result
140
+
141
+ @retry(stop=stop_after_attempt(RETRIES), wait=wait_fixed(2), reraise=True)
142
+ def run_command(
143
+ self,
144
+ cmd: Union[str, List[str]],
145
+ *,
146
+ log_errors: bool = True,
147
+ no_info: bool = False,
148
+ timeout: int = 60,
149
+ **kwargs,
150
+ ):
151
+ """
152
+ Run mpremote with the given command.
153
+
154
+ Parameters:
155
+ - cmd (Union[str, List[str]]): The command to run, either a string or a list of strings.
156
+ - log_errors (bool): Whether to log errors. Default is True.
157
+ - no_info (bool): Whether to skip printing info. Default is False.
158
+ - timeout (int): The timeout value in seconds. Default is 60.
159
+
160
+ Returns:
161
+ - bool: True if the command succeeded, False otherwise.
162
+ """
163
+ if isinstance(cmd, str):
164
+ cmd = cmd.split(" ")
165
+ prefix = [sys.executable, "-m", "mpremote"]
166
+ if self.serialport:
167
+ prefix += ["connect", self.serialport]
168
+ # if connected add resume to keep state between commands
169
+ if self.connected:
170
+ prefix += ["resume"]
171
+ cmd = prefix + cmd
172
+ log.debug(" ".join(cmd))
173
+ result = run(cmd, timeout, log_errors, no_info, **kwargs)
174
+ self.connected = result[0] == OK
175
+ return result
176
+
177
+ @retry(stop=stop_after_attempt(RETRIES), wait=wait_fixed(1))
178
+ def mip_install(self, name: str) -> bool:
179
+ """
180
+ Install a micropython package.
181
+
182
+ Parameters:
183
+ - name (str): The name of the package to install.
184
+
185
+ Returns:
186
+ - bool: True if the installation succeeded, False otherwise.
187
+ """
188
+ # install createstubs to the board
189
+ cmd = ["mip", "install", name]
190
+ result = self.run_command(cmd)[0] == OK
191
+ self.connected = True
192
+ return result
193
+
194
+ def wait_for_restart(self, timeout: int = 10):
195
+ """wait for the board to restart"""
196
+ for _ in track(
197
+ range(timeout),
198
+ description="Waiting for the board to restart",
199
+ transient=True,
200
+ get_time=lambda: time.time(),
201
+ show_speed=False,
202
+ ):
203
+ time.sleep(1)
204
+ try:
205
+ self.get_mcu_info()
206
+ break
207
+ except (ConnectionError, MPFlashError):
208
+ pass
@@ -0,0 +1,141 @@
1
+ # %%micropython
2
+ import os
3
+ import sys
4
+
5
+
6
+ def _build(s):
7
+ # extract build from sys.version or os.uname().version if available
8
+ # sys.version: 'MicroPython v1.23.0-preview.6.g3d0b6276f'
9
+ # sys.implementation.version: 'v1.13-103-gb137d064e'
10
+ if not s:
11
+ return ""
12
+ s = s.split(" on ", 1)[0] if " on " in s else s
13
+ if s.startswith("v"):
14
+ if not "-" in s:
15
+ return ""
16
+ b = s.split("-")[1]
17
+ return b
18
+ if not "-preview" in s:
19
+ return ""
20
+ b = s.split("-preview")[1].split(".")[1]
21
+ return b
22
+
23
+
24
+ def _version_str(version: tuple): # -> str:
25
+ v_str = ".".join([str(n) for n in version[:3]])
26
+ if len(version) > 3 and version[3]:
27
+ v_str += "-" + version[3]
28
+ return v_str
29
+
30
+
31
+ def _info(): # type:() -> dict[str, str]
32
+ # sourcery skip: use-contextlib-suppress, use-fstring-for-formatting, use-named-expression
33
+ info = dict(
34
+ {
35
+ "family": sys.implementation[0], # type: ignore
36
+ "version": "",
37
+ "build": "",
38
+ "ver": "",
39
+ "port": "stm32" if sys.platform.startswith("pyb") else sys.platform, # port: esp32 / win32 / linux / stm32
40
+ "board": "GENERIC",
41
+ "cpu": "",
42
+ "mpy": "",
43
+ "arch": "",
44
+ }
45
+ )
46
+ try:
47
+ info["version"] = _version_str(sys.implementation.version)
48
+ except AttributeError:
49
+ pass
50
+ try:
51
+ machine = sys.implementation._machine if "_machine" in dir(sys.implementation) else os.uname().machine
52
+ info["board"] = machine.strip()
53
+ info["cpu"] = machine.split("with")[-1].strip() if "with" in machine else ""
54
+ info["mpy"] = (
55
+ sys.implementation._mpy
56
+ if "_mpy" in dir(sys.implementation)
57
+ else sys.implementation.mpy if "mpy" in dir(sys.implementation) else ""
58
+ )
59
+ except (AttributeError, IndexError):
60
+ pass
61
+
62
+ try:
63
+ if hasattr(sys, "version"):
64
+ info["build"] = _build(sys.version)
65
+ elif hasattr(os, "uname"):
66
+ info["build"] = _build(os.uname()[3])
67
+ if not info["build"]:
68
+ # extract build from uname().release if available
69
+ info["build"] = _build(os.uname()[2])
70
+ except (AttributeError, IndexError):
71
+ pass
72
+ # avoid build hashes
73
+ if info["build"] and len(info["build"]) > 5:
74
+ info["build"] = ""
75
+
76
+ if info["version"] == "" and sys.platform not in ("unix", "win32"):
77
+ try:
78
+ u = os.uname()
79
+ info["version"] = u.release
80
+ except (IndexError, AttributeError, TypeError):
81
+ pass
82
+ # detect families
83
+ for fam_name, mod_name, mod_thing in [
84
+ ("pycopy", "pycopy", "const"),
85
+ ("pycom", "pycom", "FAT"),
86
+ ("ev3-pybricks", "pybricks.hubs", "EV3Brick"),
87
+ ]:
88
+ try:
89
+ _t = __import__(mod_name, None, None, (mod_thing))
90
+ info["family"] = fam_name
91
+ del _t
92
+ break
93
+ except (ImportError, KeyError):
94
+ pass
95
+
96
+ if info["family"] == "ev3-pybricks":
97
+ info["release"] = "2.0.0"
98
+
99
+ if info["family"] == "micropython":
100
+ if (
101
+ info["version"]
102
+ and info["version"].endswith(".0")
103
+ and info["version"] >= "1.10.0" # versions from 1.10.0 to 1.20.0 do not have a micro .0
104
+ and info["version"] <= "1.19.9"
105
+ ):
106
+ # drop the .0 for newer releases
107
+ info["version"] = info["version"][:-2]
108
+
109
+ # spell-checker: disable
110
+ if "mpy" in info and info["mpy"]: # mpy on some v1.11+ builds
111
+ sys_mpy = int(info["mpy"])
112
+ # .mpy architecture
113
+ arch = [
114
+ None,
115
+ "x86",
116
+ "x64",
117
+ "armv6",
118
+ "armv6m",
119
+ "armv7m",
120
+ "armv7em",
121
+ "armv7emsp",
122
+ "armv7emdp",
123
+ "xtensa",
124
+ "xtensawin",
125
+ ][sys_mpy >> 10]
126
+ if arch:
127
+ info["arch"] = arch
128
+ # .mpy version.minor
129
+ info["mpy"] = "v{}.{}".format(sys_mpy & 0xFF, sys_mpy >> 8 & 3)
130
+ # simple to use version[-build] string avoiding f-strings for backward compat
131
+ info["ver"] = (
132
+ "v{version}-{build}".format(version=info["version"], build=info["build"])
133
+ if info["build"]
134
+ else "v{version}".format(version=info["version"])
135
+ )
136
+
137
+ return info
138
+
139
+
140
+ print(_info())
141
+ del _info, _build, _version_str
@@ -11,6 +11,7 @@ from loguru import logger as log
11
11
 
12
12
  LogTagList = List[str]
13
13
 
14
+
14
15
  @dataclass
15
16
  class LogTags:
16
17
  reset_tags: LogTagList
@@ -26,6 +27,7 @@ def run(
26
27
  log_errors: bool = True,
27
28
  no_info: bool = False,
28
29
  *,
30
+ log_warnings: bool = False,
29
31
  reset_tags: Optional[LogTagList] = None,
30
32
  error_tags: Optional[LogTagList] = None,
31
33
  warning_tags: Optional[LogTagList] = None,
@@ -55,13 +57,24 @@ def run(
55
57
  The return code and the output as a list of strings
56
58
  """
57
59
  if not reset_tags:
58
- reset_tags = ["rst cause:1, boot mode:"]
60
+ reset_tags = [
61
+ "rst cause:1, boot mode:",
62
+ "rst cause:2, boot mode:",
63
+ "rst cause:3, boot mode:",
64
+ "rst cause:4, boot mode:",
65
+ ]
66
+ # 0 -> normal startup by power on
67
+ # 1 -> hardware watch dog reset
68
+ # 2 -> software watch dog reset (From an exception)
69
+ # 3 -> software watch dog reset system_restart (Possibly unfed watchdog got angry)
70
+ # 4 -> soft restart (Possibly with a restart command)
71
+ # 5 -> wake up from deep-sleep
59
72
  if not error_tags:
60
73
  error_tags = ["Traceback ", "Error: ", "Exception: ", "ERROR :", "CRIT :"]
61
74
  if not warning_tags:
62
- warning_tags = ["WARN :", "TRACE :"] # , "Module not found."
75
+ warning_tags = ["WARN :", "TRACE :"]
63
76
  if not success_tags:
64
- success_tags = ["Created stubs for", "Path: /remote"]
77
+ success_tags = []
65
78
  if not ignore_tags:
66
79
  ignore_tags = [' File "<stdin>",']
67
80
 
@@ -81,7 +94,8 @@ def run(
81
94
 
82
95
  def timed_out():
83
96
  proc.kill()
84
- log.warning(f"Command {cmd} timed out after {timeout} seconds")
97
+ if log_warnings:
98
+ log.warning(f"Command {cmd} timed out after {timeout} seconds")
85
99
 
86
100
  timer = Timer(timeout, timed_out)
87
101
  try:
@@ -111,13 +125,16 @@ def run(
111
125
  continue
112
126
  else:
113
127
  if not no_info:
128
+ if line.startswith(("INFO : ", "WARN : ", "ERROR : ")):
129
+ line = line[8:].lstrip()
114
130
  log.info(line)
115
131
  if proc.stderr and log_errors:
116
132
  for line in proc.stderr:
117
133
  log.warning(line)
134
+ except UnicodeDecodeError as e:
135
+ log.error(f"Failed to decode output: {e}")
118
136
  finally:
119
137
  timer.cancel()
120
138
 
121
139
  proc.wait(timeout=1)
122
140
  return proc.returncode or 0, output
123
-
@@ -0,0 +1,164 @@
1
+ # sourcery skip: require-parameter-annotation
2
+ # sourcery skip: replace-interpolation-with-fstring
3
+ #!/usr/bin/python
4
+
5
+ # Written by Antonio Galea - 2010/11/18
6
+ # Distributed under Gnu LGPL 3.0
7
+ # see http://www.gnu.org/licenses/lgpl-3.0.txt
8
+
9
+ import os
10
+ import struct
11
+ import sys
12
+ import zlib
13
+ from optparse import OptionParser
14
+
15
+ DEFAULT_DEVICE = "0x0483:0xdf11"
16
+
17
+
18
+ def named(tuple, names):
19
+ return dict(zip(names.split(), tuple))
20
+
21
+
22
+ def consume(fmt, data, names):
23
+ n = struct.calcsize(fmt)
24
+ return named(struct.unpack(fmt, data[:n]), names), data[n:]
25
+
26
+
27
+ def cstring(string):
28
+ return string.split(b"\0", 1)[0]
29
+
30
+
31
+ def compute_crc(data):
32
+ return 0xFFFFFFFF & -zlib.crc32(data) - 1
33
+
34
+
35
+ def parse(file, dump_images=False):
36
+ print('File: "%s"' % file)
37
+ data = open(file, "rb").read()
38
+ crc = compute_crc(data[:-4])
39
+ prefix, data = consume("<5sBIB", data, "signature version size targets")
40
+ print("%(signature)s v%(version)d, image size: %(size)d, targets: %(targets)d" % prefix)
41
+ for t in range(prefix["targets"]):
42
+ tprefix, data = consume("<6sBI255s2I", data, "signature altsetting named name size elements")
43
+ tprefix["num"] = t
44
+ if tprefix["named"]:
45
+ tprefix["name"] = cstring(tprefix["name"])
46
+ else:
47
+ tprefix["name"] = ""
48
+ print(
49
+ '%(signature)s %(num)d, alt setting: %(altsetting)s, name: "%(name)s", size: %(size)d, elements: %(elements)d'
50
+ % tprefix
51
+ )
52
+ tsize = tprefix["size"]
53
+ target, data = data[:tsize], data[tsize:]
54
+ for e in range(tprefix["elements"]):
55
+ eprefix, target = consume("<2I", target, "address size")
56
+ eprefix["num"] = e
57
+ print(" %(num)d, address: 0x%(address)08x, size: %(size)d" % eprefix)
58
+ esize = eprefix["size"]
59
+ image, target = target[:esize], target[esize:]
60
+ if dump_images:
61
+ out = "%s.target%d.image%d.bin" % (file, t, e)
62
+ open(out, "wb").write(image)
63
+ print(' DUMPED IMAGE TO "%s"' % out)
64
+ if len(target):
65
+ print("target %d: PARSE ERROR" % t)
66
+ suffix = named(struct.unpack("<4H3sBI", data[:16]), "device product vendor dfu ufd len crc")
67
+ print(
68
+ "usb: %(vendor)04x:%(product)04x, device: 0x%(device)04x, dfu: 0x%(dfu)04x, %(ufd)s, %(len)d, 0x%(crc)08x"
69
+ % suffix
70
+ )
71
+ if crc != suffix["crc"]:
72
+ print("CRC ERROR: computed crc32 is 0x%08x" % crc)
73
+ data = data[16:]
74
+ if data:
75
+ print("PARSE ERROR")
76
+
77
+
78
+ def build(file, targets, device=DEFAULT_DEVICE):
79
+ data = b""
80
+ for _, target in enumerate(targets):
81
+ tdata = b""
82
+ for image in target:
83
+ # pad image to 8 bytes (needed at least for L476)
84
+ pad = (8 - len(image["data"]) % 8) % 8
85
+ image["data"] = image["data"] + bytes(bytearray(8)[0:pad])
86
+ #
87
+ tdata += struct.pack("<2I", image["address"], len(image["data"])) + image["data"]
88
+ tdata = struct.pack("<6sBI255s2I", b"Target", 0, 1, b"ST...", len(tdata), len(target)) + tdata
89
+ data += tdata
90
+ data = struct.pack("<5sBIB", b"DfuSe", 1, len(data) + 11, len(targets)) + data
91
+ v, d = map(lambda x: int(x, 0) & 0xFFFF, device.split(":", 1))
92
+ data += struct.pack("<4H3sB", 0, d, v, 0x011A, b"UFD", 16)
93
+ crc = compute_crc(data)
94
+ data += struct.pack("<I", crc)
95
+ open(file, "wb").write(data)
96
+
97
+
98
+ if __name__ == "__main__":
99
+ usage = """
100
+ %prog [-d|--dump] infile.dfu
101
+ %prog {-b|--build} address:file.bin [-b address:file.bin ...] [{-D|--device}=vendor:device] outfile.dfu"""
102
+ parser = OptionParser(usage=usage)
103
+ parser.add_option(
104
+ "-b",
105
+ "--build",
106
+ action="append",
107
+ dest="binfiles",
108
+ help="build a DFU file from given BINFILES",
109
+ metavar="BINFILES",
110
+ )
111
+ parser.add_option(
112
+ "-D",
113
+ "--device",
114
+ action="store",
115
+ dest="device",
116
+ help="build for DEVICE, defaults to %s" % DEFAULT_DEVICE,
117
+ metavar="DEVICE",
118
+ )
119
+ parser.add_option(
120
+ "-d",
121
+ "--dump",
122
+ action="store_true",
123
+ dest="dump_images",
124
+ default=False,
125
+ help="dump contained images to current directory",
126
+ )
127
+ (options, args) = parser.parse_args()
128
+
129
+ if options.binfiles and len(args) == 1:
130
+ target = []
131
+ for arg in options.binfiles:
132
+ try:
133
+ address, binfile = arg.split(":", 1)
134
+ except ValueError:
135
+ print("Address:file couple '%s' invalid." % arg)
136
+ sys.exit(1)
137
+ try:
138
+ address = int(address, 0) & 0xFFFFFFFF
139
+ except ValueError:
140
+ print("Address %s invalid." % address)
141
+ sys.exit(1)
142
+ if not os.path.isfile(binfile):
143
+ print("Unreadable file '%s'." % binfile)
144
+ sys.exit(1)
145
+ target.append({"address": address, "data": open(binfile, "rb").read()})
146
+ outfile = args[0]
147
+ device = DEFAULT_DEVICE
148
+ if options.device:
149
+ device = options.device
150
+ try:
151
+ v, d = map(lambda x: int(x, 0) & 0xFFFF, device.split(":", 1))
152
+ except:
153
+ print("Invalid device '%s'." % device)
154
+ sys.exit(1)
155
+ build(outfile, [target], device)
156
+ elif len(args) == 1:
157
+ infile = args[0]
158
+ if not os.path.isfile(infile):
159
+ print("Unreadable file '%s'." % infile)
160
+ sys.exit(1)
161
+ parse(infile, dump_images=options.dump_images)
162
+ else:
163
+ parser.print_help()
164
+ sys.exit(1)