From d63b5e11cd83614286769fa9f62266a5161451f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20St=C3=B6r?= Date: Sat, 31 Dec 2016 00:17:59 +0100 Subject: [PATCH] Change PyInstaller setup --- .gitignore | 2 +- build.bat | 7 + build.sh | 16 +- esptool-py-why-here.txt | 12 + esptool.py | 1311 +++++++++++++++++++++++++++++++++++++++ nodemcu-pyflasher.spec | 21 +- 6 files changed, 1350 insertions(+), 19 deletions(-) create mode 100755 build.bat create mode 100644 esptool-py-why-here.txt create mode 100755 esptool.py diff --git a/.gitignore b/.gitignore index e8ace72..dbb6aea 100644 --- a/.gitignore +++ b/.gitignore @@ -30,7 +30,7 @@ var/ # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest -#*.spec +*.spec # Installer logs pip-log.txt diff --git a/build.bat b/build.bat new file mode 100755 index 0000000..80679e3 --- /dev/null +++ b/build.bat @@ -0,0 +1,7 @@ +pyinstaller --log-level=DEBUG ^ + --windowed ^ + --icon=.\images\icon-256.png ^ + --name="NodeMCU-PyFlasher" ^ + --noconfirm ^ + --onefile ^ + nodemcu-pyflasher.py diff --git a/build.sh b/build.sh index 99f6c39..6db8f04 100755 --- a/build.sh +++ b/build.sh @@ -1,8 +1,10 @@ #!/usr/bin/env bash -pyinstaller --noconfirm --log-level=INFO \ - --onefile --windowed \ - --name="NodeMCU PyFlasher" \ - --icon=./images/icon-256.icns \ - --osx-bundle-identifier=com.frightanic.nodemcu-pyflasher \ - --upx-dir=/usr/local/share/ \ - nodemcu-pyflasher.spec +rm -fr build dist +pyinstaller --log-level=DEBUG \ + --windowed \ + --icon=./images/icon-256.icns \ + --name="NodeMCU-PyFlasher" \ + --noconfirm \ + --onefile \ + --osx-bundle-identifier=com.frightanic.nodemcu-pyflasher \ + nodemcu-pyflasher.py diff --git a/esptool-py-why-here.txt b/esptool-py-why-here.txt new file mode 100644 index 0000000..4ff2b2d --- /dev/null +++ b/esptool-py-why-here.txt @@ -0,0 +1,12 @@ +If esptool.py is installed using python setup.py` from a checked out version it creates something like the following +in the Python environment + +esptool.py file containing + +#!/usr/local/opt/python/bin/python2.7 +# EASY-INSTALL-SCRIPT: 'esptool==1.3.dev0','esptool.py' +__requires__ = 'esptool==1.3.dev0' +__import__('pkg_resources').run_script('esptool==1.3.dev0', 'esptool.py') + +PyInstaller (and cx_Freeze) doesn't support pkg_resources and complains about 'ImportError: "No module named +pkg_resources"'. This can be avoided if the application maintains a local copy of esptool.py. \ No newline at end of file diff --git a/esptool.py b/esptool.py new file mode 100755 index 0000000..131c634 --- /dev/null +++ b/esptool.py @@ -0,0 +1,1311 @@ +#!/usr/bin/env python +# NB: Before sending a PR to change the above line to '#!/usr/bin/env python2', please read https://github.com/espressif/esptool/issues/21 +# +# ESP8266 ROM Bootloader Utility +# https://github.com/espressif/esptool +# +# Copyright (C) 2014-2016 Fredrik Ahlberg, Angus Gratton, Espressif Systems, other contributors as noted. +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., 51 Franklin +# Street, Fifth Floor, Boston, MA 02110-1301 USA. + +from __future__ import print_function, division + +import argparse +import hashlib +import inspect +import json +import os +import serial +import struct +import subprocess +import sys +import tempfile +import time + + +__version__ = "1.3-dev" + +PYTHON2 = sys.version_info[0] < 3 # True if on pre-Python 3 + + +class ESPROM(object): + # These are the currently known commands supported by the ROM + ESP_FLASH_BEGIN = 0x02 + ESP_FLASH_DATA = 0x03 + ESP_FLASH_END = 0x04 + ESP_MEM_BEGIN = 0x05 + ESP_MEM_END = 0x06 + ESP_MEM_DATA = 0x07 + ESP_SYNC = 0x08 + ESP_WRITE_REG = 0x09 + ESP_READ_REG = 0x0a + + # Maximum block sized for RAM and Flash writes, respectively. + ESP_RAM_BLOCK = 0x1800 + ESP_FLASH_BLOCK = 0x400 + + # Default baudrate. The ROM auto-bauds, so we can use more or less whatever we want. + ESP_ROM_BAUD = 115200 + + # First byte of the application image + ESP_IMAGE_MAGIC = 0xe9 + + # Initial state for the checksum routine + ESP_CHECKSUM_MAGIC = 0xef + + # OTP ROM addresses + ESP_OTP_MAC0 = 0x3ff00050 + ESP_OTP_MAC1 = 0x3ff00054 + ESP_OTP_MAC3 = 0x3ff0005c + + # Flash sector size, minimum unit of erase. + ESP_FLASH_SECTOR = 0x1000 + + def __init__(self, port=0, baud=ESP_ROM_BAUD): + self._port = serial.serial_for_url(port) + self._slip_reader = slip_reader(self._port) + # setting baud rate in a separate step is a workaround for + # CH341 driver on some Linux versions (this opens at 9600 then + # sets), shouldn't matter for other platforms/drivers. See + # https://github.com/espressif/esptool/issues/44#issuecomment-107094446 + self._port.baudrate = baud + + """ Read a SLIP packet from the serial port """ + def read(self): + return next(self._slip_reader) + + """ Write bytes to the serial port while performing SLIP escaping """ + def write(self, packet): + buf = b'\xc0' \ + + (packet.replace(b'\xdb',b'\xdb\xdd').replace(b'\xc0',b'\xdb\xdc')) \ + + b'\xc0' + self._port.write(buf) + + """ Calculate checksum of a blob, as it is defined by the ROM """ + @staticmethod + def checksum(data, state=ESP_CHECKSUM_MAGIC): + for b in data: + if type(b) is int: # python 2/3 compat + state ^= b + else: + state ^= ord(b) + + return state + + """ Send a request and read the response """ + def command(self, op=None, data=None, chk=0): + if op is not None: + pkt = struct.pack(b'> 16) & 0xff, (mac3 >> 8) & 0xff, mac3 & 0xff) + elif ((mac1 >> 16) & 0xff) == 0: + oui = (0x18, 0xfe, 0x34) + elif ((mac1 >> 16) & 0xff) == 1: + oui = (0xac, 0xd0, 0x74) + else: + raise FatalError("Unknown OUI") + return oui + ((mac1 >> 8) & 0xff, mac1 & 0xff, (mac0 >> 24) & 0xff) + + """ Read Chip ID from OTP ROM - see http://esp8266-re.foogod.com/wiki/System_get_chip_id_%28IoT_RTOS_SDK_0.9.9%29 """ + def chip_id(self): + id0 = self.read_reg(self.ESP_OTP_MAC0) + id1 = self.read_reg(self.ESP_OTP_MAC1) + return (id0 >> 24) | ((id1 & 0xffffff) << 8) + + """ Read SPI flash manufacturer and device id """ + def flash_id(self): + self.flash_begin(0, 0) + self.write_reg(0x60000240, 0x0, 0xffffffff) + self.write_reg(0x60000200, 0x10000000, 0xffffffff) + flash_id = self.read_reg(0x60000240) + return flash_id + + """ Abuse the loader protocol to force flash to be left in write mode """ + def flash_unlock_dio(self): + # Enable flash write mode + self.flash_begin(0, 0) + # Reset the chip rather than call flash_finish(), which would have + # write protected the chip again (why oh why does it do that?!) + self.mem_begin(0,0,0,0x40100000) + self.mem_finish(0x40000080) + + """ Perform a chip erase of SPI flash """ + def flash_erase(self): + # Trick ROM to initialize SFlash + self.flash_begin(0, 0) + + # This is hacky: we don't have a custom stub, instead we trick + # the bootloader to jump to the SPIEraseChip() routine and then halt/crash + # when it tries to boot an unconfigured system. + self.mem_begin(0,0,0,0x40100000) + self.mem_finish(0x40004984) + + # Yup - there's no good way to detect if we succeeded. + # It it on the other hand unlikely to fail. + + def run_stub(self, stub, params, read_output=True): + stub = dict(stub) + stub['code'] = unhexify(stub['code']) + if 'data' in stub: + stub['data'] = unhexify(stub['data']) + + if stub['num_params'] != len(params): + raise FatalError('Stub requires %d params, %d provided' + % (stub['num_params'], len(params))) + + params = struct.pack(b'<' + (b'I' * stub['num_params']), *params) + + pc = params + stub['code'] + + # Upload + self.mem_begin(len(pc), 1, len(pc), stub['params_start']) + self.mem_block(pc, 0) + if 'data' in stub: + self.mem_begin(len(stub['data']), 1, len(stub['data']), stub['data_start']) + self.mem_block(stub['data'], 0) + self.mem_finish(stub['entry']) + + if read_output: + print('Stub executed, reading response:') + while True: + p = self.read() + print(hexify(p)) + if p == '': + return + + +class ESPBOOTLOADER(object): + """ These are constants related to software ESP bootloader, working with 'v2' image files """ + + # First byte of the "v2" application image + IMAGE_V2_MAGIC = 0xea + + # First 'segment' value in a "v2" application image, appears to be a constant version value? + IMAGE_V2_SEGMENT = 4 + + +def LoadFirmwareImage(filename): + """ Load a firmware image, without knowing what kind of file (v1 or v2) it is. + + Returns a BaseFirmwareImage subclass, either ESPFirmwareImage (v1) or OTAFirmwareImage (v2). + """ + with open(filename, 'rb') as f: + magic = ord(f.read(1)) + f.seek(0) + if magic == ESPROM.ESP_IMAGE_MAGIC: + return ESPFirmwareImage(f) + elif magic == ESPBOOTLOADER.IMAGE_V2_MAGIC: + return OTAFirmwareImage(f) + else: + raise FatalError("Invalid image magic number: %d" % magic) + + +class BaseFirmwareImage(object): + """ Base class with common firmware image functions """ + def __init__(self): + self.segments = [] + self.entrypoint = 0 + + def add_segment(self, addr, data, pad_to=4): + """ Add a segment to the image, with specified address & data + (padded to a boundary of pad_to size) """ + # Data should be aligned on word boundary + l = len(data) + if l % pad_to: + data += b"\x00" * (pad_to - l % pad_to) + if l > 0: + self.segments.append((addr, len(data), data)) + + def load_segment(self, f, is_irom_segment=False): + """ Load the next segment from the image file """ + (offset, size) = struct.unpack(' 0x40200000 or offset < 0x3ffe0000 or size > 65536: + raise FatalError('Suspicious segment 0x%x, length %d' % (offset, size)) + segment_data = f.read(size) + if len(segment_data) < size: + raise FatalError('End of file reading segment 0x%x, length %d (actual length %d)' % (offset, size, len(segment_data))) + segment = (offset, size, segment_data) + self.segments.append(segment) + return segment + + def save_segment(self, f, segment, checksum=None): + """ Save the next segment to the image file, return next checksum value if provided """ + (offset, size, data) = segment + f.write(struct.pack(b' 16: + raise FatalError('Invalid firmware image magic=%d segments=%d' % (magic, segments)) + + for i in range(segments): + self.load_segment(load_file) + self.checksum = self.read_checksum(load_file) + + def save(self, filename): + with open(filename, 'wb') as f: + self.write_v1_header(f, self.segments) + checksum = ESPROM.ESP_CHECKSUM_MAGIC + for segment in self.segments: + checksum = self.save_segment(f, segment, checksum) + self.append_checksum(f, checksum) + + +class OTAFirmwareImage(BaseFirmwareImage): + """ 'Version 2' firmware image, segments loaded by software bootloader stub + (ie Espressif bootloader or rboot) + """ + def __init__(self, load_file=None): + super(OTAFirmwareImage, self).__init__() + self.version = 2 + if load_file is not None: + (magic, segments, first_flash_mode, first_flash_size_freq, first_entrypoint) = struct.unpack(' 16: + raise FatalError('Invalid V2 second header magic=%d segments=%d' % (magic, segments)) + + # load all the usual segments + for _ in range(segments): + self.load_segment(load_file) + self.checksum = self.read_checksum(load_file) + + def save(self, filename): + with open(filename, 'wb') as f: + # Save first header for irom0 segment + f.write(struct.pack(b' 0: + esp._port.baudrate = baud_rate + # Read the greeting. + p = esp.read() + if p != b'OHAI': + raise FatalError('Failed to connect to the flasher (got %s)' % hexify(p)) + + def flash_write(self, addr, data, show_progress=False): + assert addr % self._esp.ESP_FLASH_SECTOR == 0, 'Address must be sector-aligned' + assert len(data) % self._esp.ESP_FLASH_SECTOR == 0, 'Length must be sector-aligned' + sys.stdout.write('Writing %d @ 0x%x... ' % (len(data), addr)) + sys.stdout.flush() + self._esp.write(struct.pack(b' length: + raise FatalError('Read more than expected') + p = self._esp.read() + if len(p) != 16: + raise FatalError('Expected digest, got: %s' % hexify(p)) + expected_digest = hexify(p).upper() + digest = hashlib.md5(data).hexdigest().upper() + print + if digest != expected_digest: + raise FatalError('Digest mismatch: expected %s, got %s' % (expected_digest, digest)) + p = self._esp.read() + if len(p) != 1: + raise FatalError('Expected status, got: %s' % hexify(p)) + status_code = struct.unpack(', ) or a single +# argument. + +def load_ram(esp, args): + image = LoadFirmwareImage(args.filename) + + print('RAM boot...') + for (offset, size, data) in image.segments: + print('Downloading %d bytes at %08x...' % (size, offset), end=' ') + sys.stdout.flush() + esp.mem_begin(size, div_roundup(size, esp.ESP_RAM_BLOCK), esp.ESP_RAM_BLOCK, offset) + + seq = 0 + while len(data) > 0: + esp.mem_block(data[0:esp.ESP_RAM_BLOCK], seq) + data = data[esp.ESP_RAM_BLOCK:] + seq += 1 + print('done!') + + print('All segments done, executing at %08x' % image.entrypoint) + esp.mem_finish(image.entrypoint) + + +def read_mem(esp, args): + print('0x%08x = 0x%08x' % (args.address, esp.read_reg(args.address))) + + +def write_mem(esp, args): + esp.write_reg(args.address, args.value, args.mask, 0) + print('Wrote %08x, mask %08x to %08x' % (args.value, args.mask, args.address)) + + +def dump_mem(esp, args): + f = open(args.filename, 'wb') + for i in range(args.size / 4): + d = esp.read_reg(args.address + (i * 4)) + f.write(struct.pack(b'> 16 + args.flash_size = {18: '2m', 19: '4m', 20: '8m', 21: '16m', 22: '32m'}.get(size_id) + if args.flash_size is None: + print('Warning: Could not auto-detect Flash size (FlashID=0x%x, SizeID=0x%x), defaulting to 4m' % (flash_id, size_id)) + args.flash_size = '4m' + else: + print('Auto-detected Flash size:', args.flash_size) + + +def write_flash(esp, args): + detect_flash_size(esp, args) + flash_mode = {'qio':0, 'qout':1, 'dio':2, 'dout': 3}[args.flash_mode] + flash_size_freq = {'4m':0x00, '2m':0x10, '8m':0x20, '16m':0x30, '32m':0x40, '16m-c1': 0x50, '32m-c1':0x60, '32m-c2':0x70}[args.flash_size] + flash_size_freq += {'40m':0, '26m':1, '20m':2, '80m': 0xf}[args.flash_freq] + flash_params = struct.pack(b'BB', flash_mode, flash_size_freq) + + flasher = CesantaFlasher(esp, args.baud) + + for address, argfile in args.addr_filename: + image = argfile.read() + argfile.seek(0) # rewind in case we need it again + if address + len(image) > int(args.flash_size.split('m')[0]) * (1 << 17): + print('WARNING: Unlikely to work as data goes beyond end of flash. Hint: Use --flash_size') + # Fix sflash config data. + if address == 0 and (image[0] == b'\xe9' or image[0] == 0xE9): # python 2/3 compat + print('Flash params set to 0x%02x%02x' % (flash_mode, flash_size_freq)) + image = image[0:2] + flash_params + image[4:] + # Pad to sector size, which is the minimum unit of writing (erasing really). + if len(image) % esp.ESP_FLASH_SECTOR != 0: + image += b'\xff' * (esp.ESP_FLASH_SECTOR - (len(image) % esp.ESP_FLASH_SECTOR)) + t = time.time() + flasher.flash_write(address, image, not args.no_progress) + t = time.time() - t + print('\rWrote %d bytes at 0x%x in %.1f seconds (%.1f kbit/s)...' + % (len(image), address, t, len(image) / t * 8 / 1000)) + print('Leaving...') + if args.verify: + print('Verifying just-written flash...') + _verify_flash(flasher, args, flash_params) + flasher.boot_fw() + + +def image_info(args): + image = LoadFirmwareImage(args.filename) + print('Image version: %d' % image.version) + print('Entry point: %08x' % image.entrypoint if image.entrypoint != 0 else 'Entry point not set') + print('%d segments' % len(image.segments)) + print + checksum = ESPROM.ESP_CHECKSUM_MAGIC + for (idx, (offset, size, data)) in enumerate(image.segments): + if image.version == 2 and idx == 0: + print('Segment 1: %d bytes IROM0 (no load address)' % size) + else: + print('Segment %d: %5d bytes at %08x' % (idx + 1, size, offset)) + checksum = ESPROM.checksum(data, checksum) + print + print('Checksum: %02x (%s)' % (image.checksum, 'valid' if image.checksum == checksum else 'invalid!')) + + +def make_image(args): + image = ESPFirmwareImage() + if len(args.segfile) == 0: + raise FatalError('No segments specified') + if len(args.segfile) != len(args.segaddr): + raise FatalError('Number of specified files does not match number of specified addresses') + for (seg, addr) in zip(args.segfile, args.segaddr): + data = open(seg, 'rb').read() + image.add_segment(addr, data) + image.entrypoint = args.entrypoint + image.save(args.output) + + +def elf2image(args): + e = ELFFile(args.input) + if args.version == '1': + image = ESPFirmwareImage() + else: + image = OTAFirmwareImage() + irom_data = e.load_section('.irom0.text') + if len(irom_data) == 0: + raise FatalError(".irom0.text section not found in ELF file - can't create V2 image.") + image.add_segment(0, irom_data, 16) + image.entrypoint = e.get_entry_point() + for section, start in ((".text", "_text_start"), (".data", "_data_start"), (".rodata", "_rodata_start")): + data = e.load_section(section) + image.add_segment(e.get_symbol_addr(start), data) + + image.flash_mode = {'qio':0, 'qout':1, 'dio':2, 'dout': 3}[args.flash_mode] + image.flash_size_freq = {'4m':0x00, '2m':0x10, '8m':0x20, '16m':0x30, '32m':0x40, '16m-c1': 0x50, '32m-c1':0x60, '32m-c2':0x70}[args.flash_size] + image.flash_size_freq += {'40m':0, '26m':1, '20m':2, '80m': 0xf}[args.flash_freq] + + irom_offs = e.get_symbol_addr("_irom0_text_start") - 0x40200000 + + if args.version == '1': + if args.output is None: + args.output = args.input + '-' + image.save(args.output + "0x00000.bin") + data = e.load_section(".irom0.text") + if irom_offs < 0: + raise FatalError('Address of symbol _irom0_text_start in ELF is located before flash mapping address. Bad linker script?') + if (irom_offs & 0xFFF) != 0: # irom0 isn't flash sector aligned + print("WARNING: irom0 section offset is 0x%08x. ELF is probably linked for 'elf2image --version=2'" % irom_offs) + with open(args.output + "0x%05x.bin" % irom_offs, "wb") as f: + f.write(data) + f.close() + else: # V2 OTA image + if args.output is None: + args.output = "%s-0x%05x.bin" % (os.path.splitext(args.input)[0], irom_offs & ~(ESPROM.ESP_FLASH_SECTOR - 1)) + image.save(args.output) + + +def read_mac(esp, args): + mac = esp.read_mac() + print('MAC: %s' % ':'.join(map(lambda x: '%02x' % x, mac))) + + +def chip_id(esp, args): + chipid = esp.chip_id() + print('Chip ID: 0x%08x' % chipid) + + +def erase_flash(esp, args): + flasher = CesantaFlasher(esp, args.baud) + print('Erasing flash (this may take a while)...') + t = time.time() + flasher.flash_erase_chip() + t = time.time() - t + print('Erase took %.1f seconds' % t) + + +def run(esp, args): + esp.run() + + +def flash_id(esp, args): + flash_id = esp.flash_id() + esp.flash_finish(False) + print('Manufacturer: %02x' % (flash_id & 0xff)) + print('Device: %02x%02x' % ((flash_id >> 8) & 0xff, (flash_id >> 16) & 0xff)) + + +def read_flash(esp, args): + flasher = CesantaFlasher(esp, args.baud) + t = time.time() + data = flasher.flash_read(args.address, args.size, not args.no_progress) + t = time.time() - t + print('\rRead %d bytes at 0x%x in %.1f seconds (%.1f kbit/s)...' + % (len(data), args.address, t, len(data) / t * 8 / 1000)) + open(args.filename, 'wb').write(data) + + +def _verify_flash(flasher, args, flash_params=None): + differences = False + for address, argfile in args.addr_filename: + image = argfile.read() + argfile.seek(0) # rewind in case we need it again + if address == 0 and (image[0] == b'\xe9' or image[0] == 0xE9) and flash_params is not None: + image = image[0:2] + flash_params + image[4:] + + image_size = len(image) + print('Verifying 0x%x (%d) bytes @ 0x%08x in flash against %s...' % (image_size, image_size, address, argfile.name)) + # Try digest first, only read if there are differences. + digest, _ = flasher.flash_digest(address, image_size) + digest = hexify(digest).upper() + expected_digest = hashlib.md5(image).hexdigest().upper() + + if digest == expected_digest: + print('-- verify OK (digest matched)') + continue + else: + differences = True + if getattr(args, 'diff', 'no') != 'yes': + print('-- verify FAILED (digest mismatch)') + continue + + flash = flasher.flash_read(address, image_size) + assert flash != image + diff = [i for i in range(image_size) if flash[i] != image[i]] + print('-- verify FAILED: %d differences, first @ 0x%08x' % (len(diff), address + diff[0])) + for d in diff: + flash_byte = flash[d] + image_byte = image[d] + if PYTHON2: + flash_byte = ord(flash_byte) + image_byte = ord(image_byte) + print(' %08x %02x %02x' % (address + d, flash_byte, image_byte)) + if differences: + raise FatalError("Verify failed.") + + +def verify_flash(esp, args, flash_params=None): + flasher = CesantaFlasher(esp) + _verify_flash(flasher, args, flash_params) + + +def version(args): + print(__version__) + +# +# End of operations functions +# + + +def main(): + parser = argparse.ArgumentParser(description='esptool.py v%s - ESP8266 ROM Bootloader Utility' % __version__, prog='esptool') + + parser.add_argument( + '--port', '-p', + help='Serial port device', + default=os.environ.get('ESPTOOL_PORT', '/dev/ttyUSB0')) + + parser.add_argument( + '--baud', '-b', + help='Serial port baud rate used when flashing/reading', + type=arg_auto_int, + default=os.environ.get('ESPTOOL_BAUD', ESPROM.ESP_ROM_BAUD)) + + subparsers = parser.add_subparsers( + dest='operation', + help='Run esptool {command} -h for additional help') + + parser_load_ram = subparsers.add_parser( + 'load_ram', + help='Download an image to RAM and execute') + parser_load_ram.add_argument('filename', help='Firmware image') + + parser_dump_mem = subparsers.add_parser( + 'dump_mem', + help='Dump arbitrary memory to disk') + parser_dump_mem.add_argument('address', help='Base address', type=arg_auto_int) + parser_dump_mem.add_argument('size', help='Size of region to dump', type=arg_auto_int) + parser_dump_mem.add_argument('filename', help='Name of binary dump') + + parser_read_mem = subparsers.add_parser( + 'read_mem', + help='Read arbitrary memory location') + parser_read_mem.add_argument('address', help='Address to read', type=arg_auto_int) + + parser_write_mem = subparsers.add_parser( + 'write_mem', + help='Read-modify-write to arbitrary memory location') + parser_write_mem.add_argument('address', help='Address to write', type=arg_auto_int) + parser_write_mem.add_argument('value', help='Value', type=arg_auto_int) + parser_write_mem.add_argument('mask', help='Mask of bits to write', type=arg_auto_int) + + def add_spi_flash_subparsers(parent, auto_detect=False): + """ Add common parser arguments for SPI flash properties """ + parent.add_argument('--flash_freq', '-ff', help='SPI Flash frequency', + choices=['40m', '26m', '20m', '80m'], + default=os.environ.get('ESPTOOL_FF', '40m')) + parent.add_argument('--flash_mode', '-fm', help='SPI Flash mode', + choices=['qio', 'qout', 'dio', 'dout'], + default=os.environ.get('ESPTOOL_FM', 'qio')) + choices = ['4m', '2m', '8m', '16m', '32m', '16m-c1', '32m-c1', '32m-c2'] + default = '4m' + if auto_detect: + default = 'detect' + choices.insert(0, 'detect') + parent.add_argument('--flash_size', '-fs', help='SPI Flash size in Mbit', type=lambda s: s.lower(), + choices=choices, + default=os.environ.get('ESPTOOL_FS', default)) + + parser_write_flash = subparsers.add_parser( + 'write_flash', + help='Write a binary blob to flash') + parser_write_flash.add_argument('addr_filename', metavar='
', help='Address followed by binary filename, separated by space', + action=AddrFilenamePairAction) + add_spi_flash_subparsers(parser_write_flash, auto_detect=True) + parser_write_flash.add_argument('--no-progress', '-p', help='Suppress progress output', action="store_true") + parser_write_flash.add_argument('--verify', help='Verify just-written data (only necessary if very cautious, data is already CRCed', action='store_true') + + subparsers.add_parser( + 'run', + help='Run application code in flash') + + parser_image_info = subparsers.add_parser( + 'image_info', + help='Dump headers from an application image') + parser_image_info.add_argument('filename', help='Image file to parse') + + parser_make_image = subparsers.add_parser( + 'make_image', + help='Create an application image from binary files') + parser_make_image.add_argument('output', help='Output image file') + parser_make_image.add_argument('--segfile', '-f', action='append', help='Segment input file') + parser_make_image.add_argument('--segaddr', '-a', action='append', help='Segment base address', type=arg_auto_int) + parser_make_image.add_argument('--entrypoint', '-e', help='Address of entry point', type=arg_auto_int, default=0) + + parser_elf2image = subparsers.add_parser( + 'elf2image', + help='Create an application image from ELF file') + parser_elf2image.add_argument('input', help='Input ELF file') + parser_elf2image.add_argument('--output', '-o', help='Output filename prefix (for version 1 image), or filename (for version 2 single image)', type=str) + parser_elf2image.add_argument('--version', '-e', help='Output image version', choices=['1','2'], default='1') + add_spi_flash_subparsers(parser_elf2image) + + subparsers.add_parser( + 'read_mac', + help='Read MAC address from OTP ROM') + + subparsers.add_parser( + 'chip_id', + help='Read Chip ID from OTP ROM') + + subparsers.add_parser( + 'flash_id', + help='Read SPI flash manufacturer and device ID') + + parser_read_flash = subparsers.add_parser( + 'read_flash', + help='Read SPI flash content') + parser_read_flash.add_argument('address', help='Start address', type=arg_auto_int) + parser_read_flash.add_argument('size', help='Size of region to dump', type=arg_auto_int) + parser_read_flash.add_argument('filename', help='Name of binary dump') + parser_read_flash.add_argument('--no-progress', '-p', help='Suppress progress output', action="store_true") + + parser_verify_flash = subparsers.add_parser( + 'verify_flash', + help='Verify a binary blob against flash') + parser_verify_flash.add_argument('addr_filename', help='Address and binary file to verify there, separated by space', + action=AddrFilenamePairAction) + parser_verify_flash.add_argument('--diff', '-d', help='Show differences', + choices=['no', 'yes'], default='no') + + subparsers.add_parser( + 'erase_flash', + help='Perform Chip Erase on SPI flash') + + subparsers.add_parser( + 'version', help='Print esptool version') + + # internal sanity check - every operation matches a module function of the same name + for operation in subparsers.choices.keys(): + assert operation in globals(), "%s should be a module function" % operation + + args = parser.parse_args() + + print('esptool.py v%s' % __version__) + + # operation function can take 1 arg (args), 2 args (esp, arg) + # or be a member function of the ESPROM class. + + if args.operation is None: + parser.print_help() + sys.exit(1) + + operation_func = globals()[args.operation] + operation_args,_,_,_ = inspect.getargspec(operation_func) + if operation_args[0] == 'esp': # operation function takes an ESPROM connection object + initial_baud = min(ESPROM.ESP_ROM_BAUD, args.baud) # don't sync faster than the default baud rate + esp = ESPROM(args.port, initial_baud) + esp.connect() + operation_func(esp, args) + else: + operation_func(args) + + +class AddrFilenamePairAction(argparse.Action): + """ Custom parser class for the address/filename pairs passed as arguments """ + def __init__(self, option_strings, dest, nargs='+', **kwargs): + super(AddrFilenamePairAction, self).__init__(option_strings, dest, nargs, **kwargs) + + def __call__(self, parser, namespace, values, option_string=None): + # validate pair arguments + pairs = [] + for i in range(0,len(values),2): + try: + address = int(values[i],0) + except ValueError as e: + raise argparse.ArgumentError(self,'Address "%s" must be a number' % values[i]) + try: + argfile = open(values[i + 1], 'rb') + except IOError as e: + raise argparse.ArgumentError(self, e) + except IndexError: + raise argparse.ArgumentError(self,'Must be pairs of an address and the binary filename to write there') + pairs.append((address, argfile)) + setattr(namespace, self.dest, pairs) + + +# This is "wrapped" stub_flasher.c, to be loaded using run_stub. +_CESANTA_FLASHER_STUB = """\ +{"code_start": 1074790404, "code": "080000601C000060000000601000006031FCFF71FCFF\ +81FCFFC02000680332D218C020004807404074DCC48608005823C0200098081BA5A9239245005803\ +1B555903582337350129230B446604DFC6F3FF21EEFFC0200069020DF0000000010078480040004A\ +0040B449004012C1F0C921D911E901DD0209312020B4ED033C2C56C2073020B43C3C56420701F5FF\ +C000003C4C569206CD0EEADD860300202C4101F1FFC0000056A204C2DCF0C02DC0CC6CCAE2D1EAFF\ +0606002030F456D3FD86FBFF00002020F501E8FFC00000EC82D0CCC0C02EC0C73DEB2ADC46030020\ +2C4101E1FFC00000DC42C2DCF0C02DC056BCFEC602003C5C8601003C6C4600003C7C08312D0CD811\ +C821E80112C1100DF0000C180000140010400C0000607418000064180000801800008C1800008418\ +0000881800009018000018980040880F0040A80F0040349800404C4A0040740F0040800F0040980F\ +00400099004012C1E091F5FFC961CD0221EFFFE941F9310971D9519011C01A223902E2D1180C0222\ +6E1D21E4FF31E9FF2AF11A332D0F42630001EAFFC00000C030B43C2256A31621E1FF1A2228022030\ +B43C3256B31501ADFFC00000DD023C4256ED1431D6FF4D010C52D90E192E126E0101DDFFC0000021\ +D2FF32A101C020004802303420C0200039022C0201D7FFC00000463300000031CDFF1A333803D023\ +C03199FF27B31ADC7F31CBFF1A3328030198FFC0000056C20E2193FF2ADD060E000031C6FF1A3328\ +030191FFC0000056820DD2DD10460800000021BEFF1A2228029CE231BCFFC020F51A33290331BBFF\ +C02C411A332903C0F0F4222E1D22D204273D9332A3FFC02000280E27B3F721ABFF381E1A2242A400\ +01B5FFC00000381E2D0C42A40001B3FFC0000056120801B2FFC00000C02000280EC2DC0422D2FCC0\ +2000290E01ADFFC00000222E1D22D204226E1D281E22D204E7B204291E860000126E012198FF32A0\ +042A21C54C003198FF222E1D1A33380337B202C6D6FF2C02019FFFC000002191FF318CFF1A223A31\ +019CFFC00000218DFF1C031A22C549000C02060300003C528601003C624600003C72918BFF9A1108\ +71C861D851E841F83112C1200DF00010000068100000581000007010000074100000781000007C10\ +0000801000001C4B0040803C004091FDFF12C1E061F7FFC961E941F9310971D9519011C01A662906\ +21F3FFC2D1101A22390231F2FF0C0F1A33590331EAFFF26C1AED045C2247B3028636002D0C016DFF\ +C0000021E5FF41EAFF2A611A4469040622000021E4FF1A222802F0D2C0D7BE01DD0E31E0FF4D0D1A\ +3328033D0101E2FFC00000561209D03D2010212001DFFFC000004D0D2D0C3D01015DFFC0000041D5\ +FFDAFF1A444804D0648041D2FF1A4462640061D1FF106680622600673F1331D0FF10338028030C43\ +853A002642164613000041CAFF222C1A1A444804202FC047328006F6FF222C1A273F3861C2FF222C\ +1A1A6668066732B921BDFF3D0C1022800148FFC0000021BAFF1C031A2201BFFFC000000C02460300\ +5C3206020000005C424600005C5291B7FF9A110871C861D851E841F83112C1200DF0B0100000C010\ +0000D010000012C1E091FEFFC961D951E9410971F931CD039011C0ED02DD0431A1FF9C1422A06247\ +B302062D0021F4FF1A22490286010021F1FF1A223902219CFF2AF12D0F011FFFC00000461C0022D1\ +10011CFFC0000021E9FFFD0C1A222802C7B20621E6FF1A22F8022D0E3D014D0F0195FFC000008C52\ +22A063C6180000218BFF3D01102280F04F200111FFC00000AC7D22D1103D014D0F010DFFC0000021\ +D6FF32D110102280010EFFC0000021D3FF1C031A220185FFC00000FAEEF0CCC056ACF821CDFF317A\ +FF1A223A310105FFC0000021C9FF1C031A22017CFFC000002D0C91C8FF9A110871C861D851E841F8\ +3112C1200DF0000200600000001040020060FFFFFF0012C1E00C02290131FAFF21FAFF026107C961\ +C02000226300C02000C80320CC10564CFF21F5FFC02000380221F4FF20231029010C432D010163FF\ +C0000008712D0CC86112C1200DF00080FE3F8449004012C1D0C9A109B17CFC22C1110C13C51C0026\ +1202463000220111C24110B68202462B0031F5FF3022A02802A002002D011C03851A0066820A2801\ +32210105A6FF0607003C12C60500000010212032A01085180066A20F2221003811482105B3FF2241\ +10861A004C1206FDFF2D011C03C5160066B20E280138114821583185CFFF06F7FF005C1286F5FF00\ +10212032A01085140066A20D2221003811482105E1FF06EFFF0022A06146EDFF45F0FFC6EBFF0000\ +01D2FFC0000006E9FF000C022241100C1322C110C50F00220111060600000022C1100C13C50E0022\ +011132C2FA303074B6230206C8FF08B1C8A112C1300DF0000000000010404F484149007519031027\ +000000110040A8100040BC0F0040583F0040CC2E00401CE20040D83900408000004021F4FF12C1E0\ +C961C80221F2FF097129010C02D951C91101F4FFC0000001F3FFC00000AC2C22A3E801F2FFC00000\ +21EAFFC031412A233D0C01EFFFC000003D0222A00001EDFFC00000C1E4FF2D0C01E8FFC000002D01\ +32A004450400C5E7FFDD022D0C01E3FFC00000666D1F4B2131DCFF4600004B22C0200048023794F5\ +31D9FFC0200039023DF08601000001DCFFC000000871C861D85112C1200DF000000012C1F0026103\ +01EAFEC00000083112C1100DF000643B004012C1D0E98109B1C9A1D991F97129013911E2A0C001FA\ +FFC00000CD02E792F40C0DE2A0C0F2A0DB860D00000001F4FFC00000204220E71240F7921C226102\ +01EFFFC0000052A0DC482157120952A0DD571205460500004D0C3801DA234242001BDD3811379DC5\ +C6000000000C0DC2A0C001E3FFC00000C792F608B12D0DC8A1D891E881F87112C1300DF00000", "\ +entry": 1074792180, "num_params": 1, "params_start": 1074790400, "data": "FE0510\ +401A0610403B0610405A0610407A061040820610408C0610408C061040", "data_start": 10736\ +43520} +""" + +if __name__ == '__main__': + try: + main() + except FatalError as e: + print('\nA fatal error occurred: %s' % e) + sys.exit(2) diff --git a/nodemcu-pyflasher.spec b/nodemcu-pyflasher.spec index 643d126..02f9b6d 100644 --- a/nodemcu-pyflasher.spec +++ b/nodemcu-pyflasher.spec @@ -3,7 +3,7 @@ block_cipher = None -a = Analysis(['nodemcu-pyflasher.py', 'Main.py'], +a = Analysis(['nodemcu-pyflasher.py'], pathex=['/Users/marcelstoer/Data/Python/NodeMCU-PyFlasher'], binaries=None, datas=None, @@ -18,16 +18,15 @@ pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) exe = EXE(pyz, a.scripts, - exclude_binaries=True, - name='nodemcu-pyflasher', + a.binaries, + a.zipfiles, + a.datas, + name='NodeMCU-PyFlasher', debug=False, strip=False, upx=True, - console=True ) -coll = COLLECT(exe, - a.binaries, - a.zipfiles, - a.datas, - strip=False, - upx=True, - name='nodemcu-pyflasher') + console=False , icon='images/icon-256.icns') +app = BUNDLE(exe, + name='NodeMCU-PyFlasher.app', + icon='./images/icon-256.icns', + bundle_identifier='com.frightanic.nodemcu-pyflasher')