|
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