|
1 | 1 | #!/usr/bin/env python3
|
2 | 2 |
|
3 | 3 | # Wrapper for Arduino core / others that can call esptool.py possibly multiple times
|
4 |
| -# Adds pyserial to sys.path automatically based on the path of the current file |
5 |
| - |
6 |
| -# First parameter is pyserial path, second is esptool path, then a series of command arguments |
7 |
| -# i.e. upload.py tools/pyserial tools/esptool write_flash file 0x0 |
| 4 | +# Adds pyserial & esptool that are in the same directory as the script to sys.path |
8 | 5 |
|
9 | 6 | import os
|
| 7 | +import atexit |
| 8 | +import pathlib |
10 | 9 | import sys
|
11 | 10 | import tempfile
|
| 11 | +import traceback |
| 12 | + |
| 13 | +from typing import List |
| 14 | + |
| 15 | +# Add neighbouring pyserial & esptool to search path |
| 16 | +MODULES = [ |
| 17 | + "pyserial", |
| 18 | + "esptool", |
| 19 | +] |
12 | 20 |
|
13 |
| -sys.argv.pop(0) # Remove executable name |
14 |
| -toolspath = os.path.dirname(os.path.realpath(__file__)) |
| 21 | +PWD = pathlib.Path(__file__).resolve().parent |
| 22 | +for m in MODULES: |
| 23 | + sys.path.insert(0, (PWD / m).as_posix()) |
| 24 | + |
| 25 | +# If this fails, we can't continue and will bomb below |
15 | 26 | try:
|
16 |
| - sys.path.insert(0, os.path.join(toolspath, "pyserial")) # Add pyserial dir to search path |
17 |
| - sys.path.insert(0, os.path.join(toolspath, "esptool")) # Add esptool dir to search path |
18 |
| - import esptool # If this fails, we can't continue and will bomb below |
| 27 | + import esptool |
19 | 28 | except ImportError:
|
20 |
| - sys.stderr.write("pyserial or esptool directories not found next to this upload.py tool.\n") |
| 29 | + sys.stderr.write( |
| 30 | + "\n*** pyserial or esptool directories not found next to upload.py tool (this script) ***\n" |
| 31 | + ) |
| 32 | + traceback.print_exc(file=sys.stderr) |
| 33 | + sys.stderr.flush() |
| 34 | + |
21 | 35 | sys.exit(1)
|
22 | 36 |
|
23 |
| -cmdline = [] |
24 |
| -write_option = '' |
25 |
| -write_addr = '0x0' |
26 |
| -erase_addr = '' |
27 |
| -erase_len = '' |
28 | 37 |
|
29 |
| -while sys.argv: |
30 |
| - thisarg = sys.argv.pop(0) |
| 38 | +def make_erase_pair(addr: str, dest_size: int, block_size=2**16): |
| 39 | + dest, path = tempfile.mkstemp() |
| 40 | + |
| 41 | + buffer = bytearray(b"\xff" * block_size) |
| 42 | + while dest_size: |
| 43 | + remainder = dest_size % block_size |
| 44 | + |
| 45 | + if remainder: |
| 46 | + src = buffer[:remainder] |
| 47 | + src_size = remainder |
| 48 | + else: |
| 49 | + src = buffer |
| 50 | + src_size = block_size |
| 51 | + |
| 52 | + os.write(dest, src) |
| 53 | + dest_size -= src_size |
| 54 | + |
| 55 | + os.close(dest) |
| 56 | + |
| 57 | + def maybe_remove(path): |
| 58 | + try: |
| 59 | + os.remove(path) |
| 60 | + except Exception: |
| 61 | + pass |
| 62 | + |
| 63 | + atexit.register(maybe_remove, path) |
| 64 | + return [addr, path] |
| 65 | + |
31 | 66 |
|
32 |
| - # We silently replace the 921kbaud setting with 460k to enable backward |
| 67 | +argv = sys.argv[1:] # Remove executable name |
| 68 | + |
| 69 | +cmdline: List[str] = [] |
| 70 | +write_options: List[str] = ["--flash_size", "detect"] |
| 71 | +erase_options: List[str] = [] |
| 72 | + |
| 73 | +thisarg = "" |
| 74 | +lastarg = "" |
| 75 | +while argv: |
| 76 | + lastarg = thisarg |
| 77 | + thisarg = argv.pop(0) |
| 78 | + |
| 79 | + # We silently replace the high-speed setting with 460k to enable backward |
33 | 80 | # compatibility with the old esptool-ck.exe. Esptool.py doesn't seem
|
34 |
| - # work reliably at 921k, but is still significantly faster at 460kbaud. |
35 |
| - if thisarg == "921600": |
| 81 | + # work reliably, but 460kbaud is still plenty fast. |
| 82 | + if lastarg == "--baud" and thisarg in ("921600", "3000000"): |
36 | 83 | thisarg = "460800"
|
37 | 84 |
|
38 | 85 | # 'erase_flash' command is translated to the write_flash --erase-all option
|
39 | 86 | # https://github.com/esp8266/Arduino/issues/6755#issuecomment-553208688
|
40 | 87 | if thisarg == "erase_flash":
|
41 |
| - write_option = '--erase-all' |
42 |
| - elif thisarg == 'erase_region': |
43 |
| - erase_addr = sys.argv.pop(0) |
44 |
| - erase_len = sys.argv.pop(0) |
45 |
| - elif thisarg == 'write_flash': |
46 |
| - write_addr = sys.argv.pop(0) |
47 |
| - binary = sys.argv.pop(0) |
| 88 | + write_options.append("--erase-all") |
| 89 | + |
| 90 | + # instead of providing esptool with separate targets, |
| 91 | + # everything below becomes 'write_flash' [<addr> <path>] pairs |
| 92 | + |
| 93 | + # 'erase_region' becomes a temporary file filled with 0xff |
| 94 | + # this pair is appended *after* 'write_flash' pairs |
| 95 | + elif thisarg == "erase_region": |
| 96 | + addr = argv.pop(0) |
| 97 | + size = int(argv.pop(0), 0) |
| 98 | + erase_options.extend(make_erase_pair(addr, size)) |
| 99 | + |
| 100 | + # 'write_flash' pair taken in order it was specified |
| 101 | + elif thisarg == "write_flash": |
| 102 | + addr = argv.pop(0) |
| 103 | + path = argv.pop(0) |
| 104 | + write_options.extend([addr, path]) |
| 105 | + |
| 106 | + # everything else is used as-is |
48 | 107 | elif thisarg:
|
49 |
| - cmdline = cmdline + [thisarg] |
50 |
| - |
51 |
| -cmdline = cmdline + ['write_flash'] |
52 |
| -if write_option: |
53 |
| - cmdline = cmdline + [write_option] |
54 |
| -cmdline = cmdline + ['--flash_size', 'detect'] |
55 |
| -cmdline = cmdline + [write_addr, binary] |
56 |
| - |
57 |
| -erase_file = '' |
58 |
| -if erase_addr: |
59 |
| - # Generate temporary empty (0xff) file |
60 |
| - eraser = tempfile.mkstemp() |
61 |
| - erase_file = eraser[1] |
62 |
| - os.write(eraser[0], bytearray([0xff] * int(erase_len, 0))) |
63 |
| - os.close(eraser[0]) |
64 |
| - cmdline = cmdline + [erase_addr, erase_file] |
| 108 | + cmdline.append(thisarg) |
| 109 | + |
| 110 | + |
| 111 | +cmdline.append("write_flash") |
| 112 | +for opts in (write_options, erase_options): |
| 113 | + if opts: |
| 114 | + cmdline.extend(opts) |
65 | 115 |
|
66 | 116 | try:
|
67 | 117 | esptool.main(cmdline)
|
68 |
| -except Exception as e: |
69 |
| - sys.stderr.write('\nA fatal esptool.py error occurred: %s' % e) |
70 |
| -finally: |
71 |
| - if erase_file: |
72 |
| - os.remove(erase_file) |
73 |
| - if any(sys.exc_info()): |
74 |
| - sys.exit(2) |
| 118 | +except Exception: |
| 119 | + etype, evalue, _ = sys.exc_info() |
| 120 | + estring = "\n".join(traceback.format_exception_only(etype, value=evalue)) |
| 121 | + |
| 122 | + sys.stderr.write("\n*** upload.py fatal error ***\n") |
| 123 | + sys.stderr.write(estring) |
| 124 | + sys.stderr.flush() |
| 125 | + |
| 126 | + sys.exit(2) |
0 commit comments