浏览代码

add 64m and 128m flash support (#1367)

* update esptool.py to v1.2-dev

* add 64 Mbit and 128 Mbit support
moononournation 8 年之前
父节点
当前提交
4aad34158b
共有 3 个文件被更改,包括 996 次插入387 次删除
  1. 14 4
      app/platform/flash_api.c
  2. 3 0
      app/platform/flash_api.h
  3. 979 383
      tools/esptool.py

+ 14 - 4
app/platform/flash_api.c

@@ -118,6 +118,18 @@ uint32_t flash_rom_get_size_byte(void)
             // 32Mbit, 4MByte
             flash_size = 4 * 1024 * 1024;
             break;
+        case SIZE_32MBIT_16M_16M:
+            // 32Mbit, 4MByte
+            flash_size = 4 * 1024 * 1024;
+            break;
+        case SIZE_64MBIT:
+            // 64Mbit, 8MByte
+            flash_size = 8 * 1024 * 1024;
+            break;
+        case SIZE_128MBIT:
+            // 128Mbit, 16MByte
+            flash_size = 16 * 1024 * 1024;
+            break;
         default:
             // Unknown flash size, fall back mode.
             flash_size = 512 * 1024;
@@ -184,18 +196,16 @@ bool flash_rom_set_size_byte(uint32_t size)
         flash_size = SIZE_32MBIT;
         flash_rom_set_size_type(flash_size);
         break;
-        /*
     case 8 * 1024 * 1024:
         // 64Mbit, 8MByte
-        flash_size = SIZE_16MBIT_8M_8M;
+        flash_size = SIZE_64MBIT;
         flash_rom_set_size_type(flash_size);
         break;
     case 16 * 1024 * 1024:
         // 128Mbit, 16MByte
-        flash_size = SIZE_32MBIT_8M_8M;
+        flash_size = SIZE_128MBIT;
         flash_rom_set_size_type(flash_size);
         break;
-        */
     default:
         // Unknown flash size.
         result = false;

+ 3 - 0
app/platform/flash_api.h

@@ -80,6 +80,9 @@ typedef struct
         SIZE_32MBIT = 4,
         SIZE_16MBIT_8M_8M = 5,
         SIZE_32MBIT_8M_8M = 6,
+        SIZE_32MBIT_16M_16M = 7,
+        SIZE_64MBIT = 8,
+        SIZE_128MBIT = 9,
     } size : 4;
     uint32_t entry_point;
     uint32_t memory_offset;

+ 979 - 383
tools/esptool.py

@@ -1,15 +1,16 @@
 #!/usr/bin/env python
+# NB: Before sending a PR to change the above line to '#!/usr/bin/env python2', please read https://github.com/themadinventor/esptool/issues/21
 #
 # ESP8266 ROM Bootloader Utility
 # https://github.com/themadinventor/esptool
 #
-# Copyright (C) 2014 Fredrik Ahlberg
+# Copyright (C) 2014-2016 Fredrik Ahlberg, Angus Gratton, 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 
+#
+# 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.
 #
@@ -17,17 +18,23 @@
 # this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
 # Street, Fifth Floor, Boston, MA 02110-1301 USA.
 
-import sys
-import struct
-import serial
-import math
-import time
 import argparse
+import hashlib
+import inspect
+import json
 import os
+import serial
+import struct
 import subprocess
+import sys
+import tempfile
+import time
+
+
+__version__ = "1.2-dev"
 
-class ESPROM:
 
+class ESPROM(object):
     # These are the currently known commands supported by the ROM
     ESP_FLASH_BEGIN = 0x02
     ESP_FLASH_DATA  = 0x03
@@ -55,72 +62,64 @@ class ESPROM:
     # OTP ROM addresses
     ESP_OTP_MAC0    = 0x3ff00050
     ESP_OTP_MAC1    = 0x3ff00054
+    ESP_OTP_MAC3    = 0x3ff0005c
 
-    # Sflash stub: an assembly routine to read from spi flash and send to host
-    SFLASH_STUB     = "\x80\x3c\x00\x40\x1c\x4b\x00\x40\x21\x11\x00\x40\x00\x80" \
-            "\xfe\x3f\xc1\xfb\xff\xd1\xf8\xff\x2d\x0d\x31\xfd\xff\x41\xf7\xff\x4a" \
-            "\xdd\x51\xf9\xff\xc0\x05\x00\x21\xf9\xff\x31\xf3\xff\x41\xf5\xff\xc0" \
-            "\x04\x00\x0b\xcc\x56\xec\xfd\x06\xff\xff\x00\x00"
-
-    def __init__(self, port = 0, baud = ESP_ROM_BAUD):
-        self._port = serial.Serial(port, baud)
-
-    """ Read bytes from the serial port while performing SLIP unescaping """
-    def read(self, length = 1):
-        b = ''
-        while len(b) < length:
-            c = self._port.read(1)
-            if c == '\xdb':
-                c = self._port.read(1)
-                if c == '\xdc':
-                    b = b + '\xc0'
-                elif c == '\xdd':
-                    b = b + '\xdb'
-                else:
-                    raise Exception('Invalid SLIP escape')
-            else:
-                b = b + c
-        return b
+    # Flash sector size, minimum unit of erase.
+    ESP_FLASH_SECTOR = 0x1000
+
+    def __init__(self, port=0, baud=ESP_ROM_BAUD):
+        self._port = serial.Serial(port)
+        self._slip_reader = slip_reader(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/themadinventor/esptool/issues/44#issuecomment-107094446
+        self._port.baudrate = baud
+
+    """ Read a SLIP packet from the serial port """
+    def read(self):
+        return self._slip_reader.next()
 
     """ Write bytes to the serial port while performing SLIP escaping """
     def write(self, packet):
-        buf = '\xc0'+(packet.replace('\xdb','\xdb\xdd').replace('\xc0','\xdb\xdc'))+'\xc0'
+        buf = '\xc0' \
+              + (packet.replace('\xdb','\xdb\xdd').replace('\xc0','\xdb\xdc')) \
+              + '\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):
+    def checksum(data, state=ESP_CHECKSUM_MAGIC):
         for b in data:
             state ^= ord(b)
         return state
 
     """ Send a request and read the response """
-    def command(self, op = None, data = None, chk = 0):
-        if op:
-            # Construct and send request
+    def command(self, op=None, data=None, chk=0):
+        if op is not None:
             pkt = struct.pack('<BBHI', 0x00, op, len(data), chk) + data
             self.write(pkt)
 
-        # Read header of response and parse
-        if self._port.read(1) != '\xc0':
-            raise Exception('Invalid head of packet')
-        hdr = self.read(8)
-        (resp, op_ret, len_ret, val) = struct.unpack('<BBHI', hdr)
-        if resp != 0x01 or (op and op_ret != op):
-            raise Exception('Invalid response')
-
-        # The variable-length body
-        body = self.read(len_ret)
-
-        # Terminating byte
-        if self._port.read(1) != chr(0xc0):
-            raise Exception('Invalid end of packet')
-
-        return val, body
+        # tries to get a response until that response has the
+        # same operation as the request or a retries limit has
+        # exceeded. This is needed for some esp8266s that
+        # reply with more sync responses than expected.
+        for retry in xrange(100):
+            p = self.read()
+            if len(p) < 8:
+                continue
+            (resp, op_ret, len_ret, val) = struct.unpack('<BBHI', p[:8])
+            if resp != 1:
+                continue
+            body = p[8:]
+            if op is None or op_ret == op:
+                return val, body  # valid response received
+
+        raise FatalError("Response doesn't match request")
 
     """ Perform a connection test """
     def sync(self):
-        self.command(ESPROM.ESP_SYNC, '\x07\x07\x12\x20'+32*'\x55')
+        self.command(ESPROM.ESP_SYNC, '\x07\x07\x12\x20' + 32 * '\x55')
         for i in xrange(7):
             self.command()
 
@@ -140,128 +139,131 @@ class ESPROM:
             time.sleep(0.05)
             self._port.setDTR(False)
 
-            self._port.timeout = 0.3 # worst-case latency timer should be 255ms (probably <20ms)
+            # worst-case latency timer should be 255ms (probably <20ms)
+            self._port.timeout = 0.3
             for _ in xrange(4):
                 try:
                     self._port.flushInput()
+                    self._slip_reader = slip_reader(self._port)
                     self._port.flushOutput()
                     self.sync()
                     self._port.timeout = 5
                     return
                 except:
                     time.sleep(0.05)
-            # this is a workaround for the CH340 serial driver on current versions of Linux,
-            # which seems to sometimes set the serial port up with wrong parameters
-            self._port.close()
-            self._port.open()
-        raise Exception('Failed to connect')
+        raise FatalError('Failed to connect to ESP8266')
 
     """ Read memory address in target """
     def read_reg(self, addr):
         res = self.command(ESPROM.ESP_READ_REG, struct.pack('<I', addr))
         if res[1] != "\0\0":
-            raise Exception('Failed to read target memory')
+            raise FatalError('Failed to read target memory')
         return res[0]
 
     """ Write to memory address in target """
-    def write_reg(self, addr, value, mask, delay_us = 0):
+    def write_reg(self, addr, value, mask, delay_us=0):
         if self.command(ESPROM.ESP_WRITE_REG,
-                struct.pack('<IIII', addr, value, mask, delay_us))[1] != "\0\0":
-            raise Exception('Failed to write target memory')
+                        struct.pack('<IIII', addr, value, mask, delay_us))[1] != "\0\0":
+            raise FatalError('Failed to write target memory')
 
     """ Start downloading an application image to RAM """
     def mem_begin(self, size, blocks, blocksize, offset):
         if self.command(ESPROM.ESP_MEM_BEGIN,
-                struct.pack('<IIII', size, blocks, blocksize, offset))[1] != "\0\0":
-            raise Exception('Failed to enter RAM download mode')
+                        struct.pack('<IIII', size, blocks, blocksize, offset))[1] != "\0\0":
+            raise FatalError('Failed to enter RAM download mode')
 
     """ Send a block of an image to RAM """
     def mem_block(self, data, seq):
         if self.command(ESPROM.ESP_MEM_DATA,
-                struct.pack('<IIII', len(data), seq, 0, 0)+data, ESPROM.checksum(data))[1] != "\0\0":
-            raise Exception('Failed to write to target RAM')
+                        struct.pack('<IIII', len(data), seq, 0, 0) + data,
+                        ESPROM.checksum(data))[1] != "\0\0":
+            raise FatalError('Failed to write to target RAM')
 
     """ Leave download mode and run the application """
-    def mem_finish(self, entrypoint = 0):
+    def mem_finish(self, entrypoint=0):
         if self.command(ESPROM.ESP_MEM_END,
-                struct.pack('<II', int(entrypoint == 0), entrypoint))[1] != "\0\0":
-            raise Exception('Failed to leave RAM download mode')
+                        struct.pack('<II', int(entrypoint == 0), entrypoint))[1] != "\0\0":
+            raise FatalError('Failed to leave RAM download mode')
 
     """ Start downloading to Flash (performs an erase) """
     def flash_begin(self, size, offset):
         old_tmo = self._port.timeout
         num_blocks = (size + ESPROM.ESP_FLASH_BLOCK - 1) / ESPROM.ESP_FLASH_BLOCK
-        self._port.timeout = 10
-        if self.command(ESPROM.ESP_FLASH_BEGIN,
-                struct.pack('<IIII', size, num_blocks, ESPROM.ESP_FLASH_BLOCK, offset))[1] != "\0\0":
-            raise Exception('Failed to enter Flash download mode')
+
+        sectors_per_block = 16
+        sector_size = self.ESP_FLASH_SECTOR
+        num_sectors = (size + sector_size - 1) / sector_size
+        start_sector = offset / sector_size
+
+        head_sectors = sectors_per_block - (start_sector % sectors_per_block)
+        if num_sectors < head_sectors:
+            head_sectors = num_sectors
+
+        if num_sectors < 2 * head_sectors:
+            erase_size = (num_sectors + 1) / 2 * sector_size
+        else:
+            erase_size = (num_sectors - head_sectors) * sector_size
+
+        self._port.timeout = 20
+        t = time.time()
+        result = self.command(ESPROM.ESP_FLASH_BEGIN,
+                              struct.pack('<IIII', erase_size, num_blocks, ESPROM.ESP_FLASH_BLOCK, offset))[1]
+        if size != 0:
+            print "Took %.2fs to erase flash block" % (time.time() - t)
+        if result != "\0\0":
+            raise FatalError.WithResult('Failed to enter Flash download mode (result "%s")', result)
         self._port.timeout = old_tmo
 
     """ Write block to flash """
     def flash_block(self, data, seq):
-        if self.command(ESPROM.ESP_FLASH_DATA,
-                struct.pack('<IIII', len(data), seq, 0, 0)+data, ESPROM.checksum(data))[1] != "\0\0":
-            raise Exception('Failed to write to target Flash')
+        result = self.command(ESPROM.ESP_FLASH_DATA,
+                              struct.pack('<IIII', len(data), seq, 0, 0) + data,
+                              ESPROM.checksum(data))[1]
+        if result != "\0\0":
+            raise FatalError.WithResult('Failed to write to target Flash after seq %d (got result %%s)' % seq, result)
 
     """ Leave flash mode and run/reboot """
-    def flash_finish(self, reboot = False):
+    def flash_finish(self, reboot=False):
         pkt = struct.pack('<I', int(not reboot))
         if self.command(ESPROM.ESP_FLASH_END, pkt)[1] != "\0\0":
-            raise Exception('Failed to leave Flash mode')
+            raise FatalError('Failed to leave Flash mode')
 
     """ Run application code in flash """
-    def run(self, reboot = False):
+    def run(self, reboot=False):
         # Fake flash begin immediately followed by flash end
         self.flash_begin(0, 0)
         self.flash_finish(reboot)
 
     """ Read MAC from OTP ROM """
     def read_mac(self):
-        mac0 = esp.read_reg(esp.ESP_OTP_MAC0)
-        mac1 = esp.read_reg(esp.ESP_OTP_MAC1)
-        if ((mac1 >> 16) & 0xff) == 0:
+        mac0 = self.read_reg(self.ESP_OTP_MAC0)
+        mac1 = self.read_reg(self.ESP_OTP_MAC1)
+        mac3 = self.read_reg(self.ESP_OTP_MAC3)
+        if (mac3 != 0):
+            oui = ((mac3 >> 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 Exception("Unknown OUI")
+            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 = esp.read_reg(0x60000240)
+        flash_id = self.read_reg(0x60000240)
         self.flash_finish(False)
         return flash_id
 
-    """ Read SPI flash """
-    def flash_read(self, offset, size, count = 1):
-        # Create a custom stub
-        stub = struct.pack('<III', offset, size, count) + self.SFLASH_STUB
-
-        # Trick ROM to initialize SFlash
-        self.flash_begin(0, 0)
-
-        # Download stub
-        self.mem_begin(len(stub), 1, len(stub), 0x40100000)
-        self.mem_block(stub, 0)
-        self.mem_finish(0x4010001c)
-
-        # Fetch the data
-        data = ''
-        for _ in xrange(count):
-            if self._port.read(1) != '\xc0':
-                raise Exception('Invalid head of packet (sflash read)')
-
-            data += self.read(size)
-
-            if self._port.read(1) != chr(0xc0):
-                raise Exception('Invalid end of packet (sflash read)')
-
-        return data
-
     """ Abuse the loader protocol to force flash to be left in write mode """
     def flash_unlock_dio(self):
         # Enable flash write mode
@@ -285,62 +287,210 @@ class ESPROM:
         # Yup - there's no good way to detect if we succeeded.
         # It it on the other hand unlikely to fail.
 
-class ESPFirmwareImage:
-    
-    def __init__(self, filename = None):
+    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('<' + ('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('<II', f.read(8))
+        if not is_irom_segment:
+            if offset > 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('<II', offset, size))
+        f.write(data)
+        if checksum is not None:
+            return ESPROM.checksum(data, checksum)
+
+    def read_checksum(self, f):
+        """ Return ESPROM checksum from end of just-read image """
+        # Skip the padding. The checksum is stored in the last byte so that the
+        # file is a multiple of 16 bytes.
+        align_file_position(f, 16)
+        return ord(f.read(1))
+
+    def append_checksum(self, f, checksum):
+        """ Append ESPROM checksum to the just-written image """
+        align_file_position(f, 16)
+        f.write(struct.pack('B', checksum))
+
+    def write_v1_header(self, f, segments):
+        f.write(struct.pack('<BBBBI', ESPROM.ESP_IMAGE_MAGIC, len(segments),
+                            self.flash_mode, self.flash_size_freq, self.entrypoint))
+
+
+class ESPFirmwareImage(BaseFirmwareImage):
+    """ 'Version 1' firmware image, segments loaded directly by the ROM bootloader. """
+    def __init__(self, load_file=None):
+        super(ESPFirmwareImage, self).__init__()
         self.flash_mode = 0
         self.flash_size_freq = 0
+        self.version = 1
+
+        if load_file is not None:
+            (magic, segments, self.flash_mode, self.flash_size_freq, self.entrypoint) = struct.unpack('<BBBBI', load_file.read(8))
 
-        if filename is not None:
-            f = file(filename, 'rb')
-            (magic, segments, self.flash_mode, self.flash_size_freq, self.entrypoint) = struct.unpack('<BBBBI', f.read(8))
-            
             # some sanity check
             if magic != ESPROM.ESP_IMAGE_MAGIC or segments > 16:
-                raise Exception('Invalid firmware image')
-        
-            for i in xrange(segments):
-                (offset, size) = struct.unpack('<II', f.read(8))
-                if offset > 0x40200000 or offset < 0x3ffe0000 or size > 65536:
-                    raise Exception('Suspicious segment %x,%d' % (offset, size))
-                self.segments.append((offset, size, f.read(size)))
-
-            # Skip the padding. The checksum is stored in the last byte so that the
-            # file is a multiple of 16 bytes.
-            align = 15-(f.tell() % 16)
-            f.seek(align, 1)
+                raise FatalError('Invalid firmware image magic=%d segments=%d' % (magic, segments))
 
-            self.checksum = ord(f.read(1))
-
-    def add_segment(self, addr, data):
-        # Data should be aligned on word boundary
-        l = len(data)
-        if l % 4:
-            data += b"\x00" * (4 - l % 4)
-        self.segments.append((addr, len(data), data))
+            for i in xrange(segments):
+                self.load_segment(load_file)
+            self.checksum = self.read_checksum(load_file)
 
     def save(self, filename):
-        f = file(filename, 'wb')
-        f.write(struct.pack('<BBBBI', ESPROM.ESP_IMAGE_MAGIC, len(self.segments),
-            self.flash_mode, self.flash_size_freq, self.entrypoint))
-
-        checksum = ESPROM.ESP_CHECKSUM_MAGIC
-        for (offset, size, data) in self.segments:
-            f.write(struct.pack('<II', offset, size))
-            f.write(data)
-            checksum = ESPROM.checksum(data, checksum)
+        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('<BBBBI', load_file.read(8))
 
-        align = 15-(f.tell() % 16)
-        f.seek(align, 1)
-        f.write(struct.pack('B', checksum))
+            # some sanity check
+            if magic != ESPBOOTLOADER.IMAGE_V2_MAGIC:
+                raise FatalError('Invalid V2 image magic=%d' % (magic))
+            if segments != 4:
+                # segment count is not really segment count here, but we expect to see '4'
+                print 'Warning: V2 header has unexpected "segment" count %d (usually 4)' % segments
+
+            # irom segment comes before the second header
+            self.load_segment(load_file, True)
+
+            (magic, segments, self.flash_mode, self.flash_size_freq, self.entrypoint) = struct.unpack('<BBBBI', load_file.read(8))
+
+            if first_flash_mode != self.flash_mode:
+                print('WARNING: Flash mode value in first header (0x%02x) disagrees with second (0x%02x). Using second value.'
+                      % (first_flash_mode, self.flash_mode))
+            if first_flash_size_freq != self.flash_size_freq:
+                print('WARNING: Flash size/freq value in first header (0x%02x) disagrees with second (0x%02x). Using second value.'
+                      % (first_flash_size_freq, self.flash_size_freq))
+            if first_entrypoint != self.entrypoint:
+                print('WARNING: Enterypoint address in first header (0x%08x) disagrees with second header (0x%08x). Using second value.'
+                      % (first_entrypoint, self.entrypoint))
 
+            if magic != ESPROM.ESP_IMAGE_MAGIC or segments > 16:
+                raise FatalError('Invalid V2 second header magic=%d segments=%d' % (magic, segments))
 
-class ELFFile:
+            # load all the usual segments
+            for _ in xrange(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('<BBBBI', ESPBOOTLOADER.IMAGE_V2_MAGIC, ESPBOOTLOADER.IMAGE_V2_SEGMENT,
+                                self.flash_mode, self.flash_size_freq, self.entrypoint))
+
+            # irom0 segment identified by load address zero
+            irom_segments = [segment for segment in self.segments if segment[0] == 0]
+            if len(irom_segments) != 1:
+                raise FatalError('Found %d segments that could be irom0. Bad ELF file?' % len(irom_segments))
+            # save irom0 segment
+            irom_segment = irom_segments[0]
+            self.save_segment(f, irom_segment)
+
+            # second header, matches V1 header and contains loadable segments
+            normal_segments = [s for s in self.segments if s != irom_segment]
+            self.write_v1_header(f, normal_segments)
+            checksum = ESPROM.ESP_CHECKSUM_MAGIC
+            for segment in normal_segments:
+                checksum = self.save_segment(f, segment, checksum)
+            self.append_checksum(f, checksum)
+
+
+class ELFFile(object):
     def __init__(self, name):
-        self.name = name
+        self.name = binutils_safe_path(name)
         self.symbols = None
 
     def _fetch_symbols(self):
@@ -349,15 +499,23 @@ class ELFFile:
         self.symbols = {}
         try:
             tool_nm = "xtensa-lx106-elf-nm"
-            if os.getenv('XTENSA_CORE')=='lx106':
+            if os.getenv('XTENSA_CORE') == 'lx106':
                 tool_nm = "xt-nm"
             proc = subprocess.Popen([tool_nm, self.name], stdout=subprocess.PIPE)
         except OSError:
-            print "Error calling "+tool_nm+", do you have Xtensa toolchain in PATH?"
+            print "Error calling %s, do you have Xtensa toolchain in PATH?" % tool_nm
             sys.exit(1)
         for l in proc.stdout:
             fields = l.strip().split()
-            self.symbols[fields[2]] = int(fields[0], 16)
+            try:
+                if fields[0] == "U":
+                    print "Warning: ELF binary has undefined symbol %s" % fields[1]
+                    continue
+                if fields[0] == "w":
+                    continue  # can skip weak symbols
+                self.symbols[fields[2]] = int(fields[0], 16)
+            except ValueError:
+                raise FatalError("Failed to strip symbol output from nm: %s" % fields)
 
     def get_symbol_addr(self, sym):
         self._fetch_symbols()
@@ -365,276 +523,714 @@ class ELFFile:
 
     def get_entry_point(self):
         tool_readelf = "xtensa-lx106-elf-readelf"
-        if os.getenv('XTENSA_CORE')=='lx106':
-            tool_objcopy = "xt-readelf"
+        if os.getenv('XTENSA_CORE') == 'lx106':
+            tool_readelf = "xt-readelf"
         try:
             proc = subprocess.Popen([tool_readelf, "-h", self.name], stdout=subprocess.PIPE)
         except OSError:
-            print "Error calling "+tool_nm+", do you have Xtensa toolchain in PATH?"
+            print "Error calling %s, do you have Xtensa toolchain in PATH?" % tool_readelf
             sys.exit(1)
         for l in proc.stdout:
             fields = l.strip().split()
             if fields[0] == "Entry":
-                return int(fields[3], 0);
+                return int(fields[3], 0)
 
     def load_section(self, section):
         tool_objcopy = "xtensa-lx106-elf-objcopy"
-        if os.getenv('XTENSA_CORE')=='lx106':
+        if os.getenv('XTENSA_CORE') == 'lx106':
             tool_objcopy = "xt-objcopy"
-        subprocess.check_call([tool_objcopy, "--only-section", section, "-Obinary", self.name, ".tmp.section"])
-        f = open(".tmp.section", "rb")
-        data = f.read()
-        f.close()
-        os.remove(".tmp.section")
+        tmpsection = binutils_safe_path(tempfile.mktemp(suffix=".section"))
+        try:
+            subprocess.check_call([tool_objcopy, "--only-section", section, "-Obinary", self.name, tmpsection])
+            with open(tmpsection, "rb") as f:
+                data = f.read()
+        finally:
+            os.remove(tmpsection)
+        return data
+
+
+class CesantaFlasher(object):
+
+    # From stub_flasher.h
+    CMD_FLASH_WRITE = 1
+    CMD_FLASH_READ = 2
+    CMD_FLASH_DIGEST = 3
+    CMD_BOOT_FW = 6
+
+    def __init__(self, esp, baud_rate=0):
+        print 'Running Cesanta flasher stub...'
+        if baud_rate <= ESPROM.ESP_ROM_BAUD:  # don't change baud rates if we already synced at that rate
+            baud_rate = 0
+        self._esp = esp
+        esp.run_stub(json.loads(_CESANTA_FLASHER_STUB), [baud_rate], read_output=False)
+        if baud_rate > 0:
+            esp._port.baudrate = baud_rate
+        # Read the greeting.
+        p = esp.read()
+        if p != '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', self.CMD_FLASH_WRITE))
+        self._esp.write(struct.pack('<III', addr, len(data), 1))
+        num_sent, num_written = 0, 0
+        while num_written < len(data):
+            p = self._esp.read()
+            if len(p) == 4:
+                num_written = struct.unpack('<I', p)[0]
+            elif len(p) == 1:
+                status_code = struct.unpack('<B', p)[0]
+                raise FatalError('Write failure, status: %x' % status_code)
+            else:
+                raise FatalError('Unexpected packet while writing: %s' % hexify(p))
+            if show_progress:
+                progress = '%d (%d %%)' % (num_written, num_written * 100.0 / len(data))
+                sys.stdout.write(progress + '\b' * len(progress))
+                sys.stdout.flush()
+            while num_sent - num_written < 5120:
+                self._esp._port.write(data[num_sent:num_sent + 1024])
+                num_sent += 1024
+        p = self._esp.read()
+        if len(p) != 16:
+            raise FatalError('Expected digest, got: %s' % hexify(p))
+        digest = hexify(p).upper()
+        expected_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('<B', p)[0]
+        if status_code != 0:
+            raise FatalError('Write failure, status: %x' % status_code)
+
+    def flash_read(self, addr, length, show_progress=False):
+        sys.stdout.write('Reading %d @ 0x%x... ' % (length, addr))
+        sys.stdout.flush()
+        self._esp.write(struct.pack('<B', self.CMD_FLASH_READ))
+        # USB may not be able to keep up with the read rate, especially at
+        # higher speeds. Since we don't have flow control, this will result in
+        # data loss. Hence, we use small packet size and only allow small
+        # number of bytes in flight, which we can reasonably expect to fit in
+        # the on-chip FIFO. max_in_flight = 64 works for CH340G, other chips may
+        # have longer FIFOs and could benefit from increasing max_in_flight.
+        self._esp.write(struct.pack('<IIII', addr, length, 32, 64))
+        data = ''
+        while True:
+            p = self._esp.read()
+            data += p
+            self._esp.write(struct.pack('<I', len(data)))
+            if show_progress and (len(data) % 1024 == 0 or len(data) == length):
+                progress = '%d (%d %%)' % (len(data), len(data) * 100.0 / length)
+                sys.stdout.write(progress + '\b' * len(progress))
+                sys.stdout.flush()
+            if len(data) == length:
+                break
+            if len(data) > 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('<B', p)[0]
+        if status_code != 0:
+            raise FatalError('Write failure, status: %x' % status_code)
         return data
 
+    def flash_digest(self, addr, length, digest_block_size=0):
+        self._esp.write(struct.pack('<B', self.CMD_FLASH_DIGEST))
+        self._esp.write(struct.pack('<III', addr, length, digest_block_size))
+        digests = []
+        while True:
+            p = self._esp.read()
+            if len(p) == 16:
+                digests.append(p)
+            elif len(p) == 1:
+                status_code = struct.unpack('<B', p)[0]
+                if status_code != 0:
+                    raise FatalError('Write failure, status: %x' % status_code)
+                break
+            else:
+                raise FatalError('Unexpected packet: %s' % hexify(p))
+        return digests[-1], digests[:-1]
+
+    def boot_fw(self):
+        self._esp.write(struct.pack('<B', self.CMD_BOOT_FW))
+        p = self._esp.read()
+        if len(p) != 1:
+            raise FatalError('Expected status, got: %s' % hexify(p))
+        status_code = struct.unpack('<B', p)[0]
+        if status_code != 0:
+            raise FatalError('Boot failure, status: %x' % status_code)
+
+
+def slip_reader(port):
+    """Generator to read SLIP packets from a serial port.
+    Yields one full SLIP packet at a time, raises exception on timeout or invalid data.
+
+    Designed to avoid too many calls to serial.read(1), which can bog
+    down on slow systems.
+    """
+    partial_packet = None
+    in_escape = False
+    while True:
+        waiting = port.inWaiting()
+        read_bytes = port.read(1 if waiting == 0 else waiting)
+        if read_bytes == '':
+            raise FatalError("Timed out waiting for packet %s" % ("header" if partial_packet is None else "content"))
+
+        for b in read_bytes:
+            if partial_packet is None:  # waiting for packet header
+                if b == '\xc0':
+                    partial_packet = ""
+                else:
+                    raise FatalError('Invalid head of packet (%r)' % b)
+            elif in_escape:  # part-way through escape sequence
+                in_escape = False
+                if b == '\xdc':
+                    partial_packet += '\xc0'
+                elif b == '\xdd':
+                    partial_packet += '\xdb'
+                else:
+                    raise FatalError('Invalid SLIP escape (%r%r)' % ('\xdb', b))
+            elif b == '\xdb':  # start of escape sequence
+                in_escape = True
+            elif b == '\xc0':  # end of packet
+                yield partial_packet
+                partial_packet = None
+            else:  # normal byte in packet
+                partial_packet += b
+
 
 def arg_auto_int(x):
     return int(x, 0)
 
-if __name__ == '__main__':
-    parser = argparse.ArgumentParser(description = 'ESP8266 ROM Bootloader Utility', prog = 'esptool')
+
+def div_roundup(a, b):
+    """ Return a/b rounded up to nearest integer,
+    equivalent result to int(math.ceil(float(int(a)) / float(int(b))), only
+    without possible floating point accuracy errors.
+    """
+    return (int(a) + int(b) - 1) / int(b)
+
+
+def binutils_safe_path(p):
+    """Returns a 'safe' version of path 'p' to pass to binutils
+
+    Only does anything under Cygwin Python, where cygwin paths need to
+    be translated to Windows paths if the binutils wasn't compiled
+    using Cygwin (should also work with binutils compiled using
+    Cygwin, see #73.)
+    """
+    if sys.platform == "cygwin":
+        try:
+            return subprocess.check_output(["cygpath", "-w", p]).rstrip('\n')
+        except subprocess.CalledProcessError:
+            print "WARNING: Failed to call cygpath to sanitise Cygwin path."
+    return p
+
+
+def align_file_position(f, size):
+    """ Align the position in the file to the next block of specified size """
+    align = (size - 1) - (f.tell() % size)
+    f.seek(align, 1)
+
+
+def hexify(s):
+    return ''.join('%02X' % ord(c) for c in s)
+
+
+def unhexify(hs):
+    s = ''
+    for i in range(0, len(hs) - 1, 2):
+        s += chr(int(hs[i] + hs[i + 1], 16))
+    return s
+
+
+class FatalError(RuntimeError):
+    """
+    Wrapper class for runtime errors that aren't caused by internal bugs, but by
+    ESP8266 responses or input content.
+    """
+    def __init__(self, message):
+        RuntimeError.__init__(self, message)
+
+    @staticmethod
+    def WithResult(message, result):
+        """
+        Return a fatal error object that includes the hex values of
+        'result' as a string formatted argument.
+        """
+        return FatalError(message % ", ".join(hex(ord(x)) for x in result))
+
+
+# "Operation" commands, executable at command line. One function each
+#
+# Each function takes either two args (<ESPROM instance>, <args>) or a single <args>
+# 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),
+        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 = file(args.filename, 'wb')
+    for i in xrange(args.size / 4):
+        d = esp.read_reg(args.address + (i * 4))
+        f.write(struct.pack('<I', d))
+        if f.tell() % 1024 == 0:
+            print '\r%d bytes read... (%d %%)' % (f.tell(),
+                                                  f.tell() * 100 / args.size),
+        sys.stdout.flush()
+    print 'Done!'
+
+
+def write_flash(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, '64m':0x80, '128m':0x90}[args.flash_size]
+    flash_size_freq += {'40m':0, '26m':1, '20m':2, '80m': 0xf}[args.flash_freq]
+    flash_params = struct.pack('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
+        # Fix sflash config data.
+        if address == 0 and image[0] == '\xe9':
+            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 += '\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 = file(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, '64m':0x80, '128m':0x90}[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):
+    print 'Erasing flash (this may take a while)...'
+    esp.flash_erase()
+
+
+def run(esp, args):
+    esp.run()
+
+
+def flash_id(esp, args):
+    flash_id = esp.flash_id()
+    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))
+    file(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] == '\xe9' 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 xrange(image_size) if flash[i] != image[i]]
+        print '-- verify FAILED: %d differences, first @ 0x%08x' % (len(diff), address + diff[0])
+        for d in diff:
+            print '   %08x %02x %02x' % (address + d, ord(flash[d]), ord(image[d]))
+    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 = '/dev/ttyUSB0')
+        '--port', '-p',
+        help='Serial port device',
+        default=os.environ.get('ESPTOOL_PORT', '/dev/ttyUSB0'))
 
     parser.add_argument(
-            '--baud', '-b',
-            help = 'Serial port baud rate',
-            type = arg_auto_int,
-            default = ESPROM.ESP_ROM_BAUD)
+        '--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')
+        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')
+        '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')
+        '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)
+        '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)
+        '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):
+        """ 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'))
+        parent.add_argument('--flash_size', '-fs', help='SPI Flash size in Mbit', type=str.lower,
+                            choices=['4m', '2m', '8m', '16m', '32m', '16m-c1', '32m-c1', '32m-c2', '64m', '128m'],
+                            default=os.environ.get('ESPTOOL_FS', '4m'))
 
     parser_write_flash = subparsers.add_parser(
-            'write_flash',
-            help = 'Write a binary blob to flash')
-    parser_write_flash.add_argument('addr_filename', nargs = '+', help = 'Address and binary file to write there, separated by space')
-    parser_write_flash.add_argument('--flash_freq', '-ff', help = 'SPI Flash frequency',
-            choices = ['40m', '26m', '20m', '80m'], default = '40m')
-    parser_write_flash.add_argument('--flash_mode', '-fm', help = 'SPI Flash mode',
-            choices = ['qio', 'qout', 'dio', 'dout'], default = 'qio')
-    parser_write_flash.add_argument('--flash_size', '-fs', help = 'SPI Flash size in Mbit',
-            choices = ['4m', '2m', '8m', '16m', '32m'], default = '4m')
-
-    parser_run = subparsers.add_parser(
-            'run',
-            help = 'Run application code in flash')
+        'write_flash',
+        help='Write a binary blob to flash')
+    parser_write_flash.add_argument('addr_filename', metavar='<address> <filename>', help='Address followed by binary filename, separated by space',
+                                    action=AddrFilenamePairAction)
+    add_spi_flash_subparsers(parser_write_flash)
+    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')
+        '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)
+        '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', type = str)
-    parser_elf2image.add_argument('--flash_freq', '-ff', help = 'SPI Flash frequency',
-            choices = ['40m', '26m', '20m', '80m'], default = '40m')
-    parser_elf2image.add_argument('--flash_mode', '-fm', help = 'SPI Flash mode',
-            choices = ['qio', 'qout', 'dio', 'dout'], default = 'qio')
-    parser_elf2image.add_argument('--flash_size', '-fs', help = 'SPI Flash size in Mbit',
-            choices = ['4m', '2m', '8m', '16m', '32m'], default = '4m')
-
-    parser_read_mac = subparsers.add_parser(
-            'read_mac',
-            help = 'Read MAC address from OTP ROM')
-
-    parser_flash_id = 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_erase_flash = subparsers.add_parser(
-            'erase_flash',
-            help = 'Perform Chip Erase on SPI flash')
-
-    args = parser.parse_args()
-
-    # Create the ESPROM connection object, if needed
-    esp = None
-    if args.operation not in ('image_info','make_image','elf2image'):
-        esp = ESPROM(args.port, args.baud)
-        esp.connect()
+        '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)
 
-    # Do the actual work. Should probably be split into separate functions.
-    if args.operation == 'load_ram':
-        image = ESPFirmwareImage(args.filename)
-
-        print 'RAM boot...'
-        for (offset, size, data) in image.segments:
-            print 'Downloading %d bytes at %08x...' % (size, offset),
-            sys.stdout.flush()
-            esp.mem_begin(size, math.ceil(size / float(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)
-
-    elif args.operation == 'read_mem':
-        print '0x%08x = 0x%08x' % (args.address, esp.read_reg(args.address))
-
-    elif args.operation == 'write_mem':
-        esp.write_reg(args.address, args.value, args.mask, 0)
-        print 'Wrote %08x, mask %08x to %08x' % (args.value, args.mask, args.address)
-
-    elif args.operation == 'dump_mem':
-        f = file(args.filename, 'wb')
-        for i in xrange(args.size/4):
-            d = esp.read_reg(args.address+(i*4))
-            f.write(struct.pack('<I', d))
-            if f.tell() % 1024 == 0:
-                print '\r%d bytes read... (%d %%)' % (f.tell(), f.tell()*100/args.size),
-                sys.stdout.flush()
-        print 'Done!'
-
-    elif args.operation == 'write_flash':
-        assert len(args.addr_filename) % 2 == 0
-
-        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}[args.flash_size]
-        flash_size_freq += {'40m':0, '26m':1, '20m':2, '80m': 0xf}[args.flash_freq]
-        flash_info = struct.pack('BB', flash_mode, flash_size_freq)
-
-        while args.addr_filename:
-            address = int(args.addr_filename[0], 0)
-            filename = args.addr_filename[1]
-            args.addr_filename = args.addr_filename[2:]
-            image = file(filename, 'rb').read()
-            print 'Erasing flash...'
-            blocks = math.ceil(len(image)/float(esp.ESP_FLASH_BLOCK))
-            esp.flash_begin(blocks*esp.ESP_FLASH_BLOCK, address)
-            seq = 0
-            while len(image) > 0:
-                print '\rWriting at 0x%08x... (%d %%)' % (address + seq*esp.ESP_FLASH_BLOCK, 100*(seq+1)/blocks),
-                sys.stdout.flush()
-                block = image[0:esp.ESP_FLASH_BLOCK]
-                # Fix sflash config data
-                if address == 0 and seq == 0 and block[0] == '\xe9':
-                    block = block[0:2] + flash_info + block[4:]
-                # Pad the last block
-                block = block + '\xff' * (esp.ESP_FLASH_BLOCK-len(block))
-                esp.flash_block(block, seq)
-                image = image[esp.ESP_FLASH_BLOCK:]
-                seq += 1
-            print
-        print '\nLeaving...'
-        if args.flash_mode == 'dio':
-            esp.flash_unlock_dio()
-        else:
-            esp.flash_finish(False)
-
-    elif args.operation == 'run':
-        esp.run()
-
-    elif args.operation == 'image_info':
-        image = ESPFirmwareImage(args.filename)
-        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):
-            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!')
+    subparsers.add_parser(
+        'read_mac',
+        help='Read MAC address from OTP ROM')
 
-    elif args.operation == 'make_image':
-        image = ESPFirmwareImage()
-        if len(args.segfile) == 0:
-            raise Exception('No segments specified')
-        if len(args.segfile) != len(args.segaddr):
-            raise Exception('Number of specified files does not match number of specified addresses')
-        for (seg, addr) in zip(args.segfile, args.segaddr):
-            data = file(seg, 'rb').read()
-            image.add_segment(addr, data)
-        image.entrypoint = args.entrypoint
-        image.save(args.output)
+    subparsers.add_parser(
+        'chip_id',
+        help='Read Chip ID from OTP ROM')
 
-    elif args.operation == 'elf2image':
-        if args.output is None:
-            args.output = args.input + '-'
-        e = ELFFile(args.input)
-        image = ESPFirmwareImage()
-        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)
+    subparsers.add_parser(
+        'flash_id',
+        help='Read SPI flash manufacturer and device ID')
 
-        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}[args.flash_size]
-        image.flash_size_freq += {'40m':0, '26m':1, '20m':2, '80m': 0xf}[args.flash_freq]
+    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
 
-        image.save(args.output + "0x00000.bin")
-        data = e.load_section(".irom0.text")
-        off = e.get_symbol_addr("_irom0_text_start") - 0x40200000
-        assert off >= 0
-        f = open(args.output + "0x%05x.bin" % off, "wb")
-        f.write(data)
-        f.close()
+    args = parser.parse_args()
 
-    elif args.operation == 'read_mac':
-        mac = esp.read_mac()
-        print 'MAC: %s' % ':'.join(map(lambda x: '%02x'%x, mac))
+    print 'esptool.py v%s' % __version__
 
-    elif args.operation == 'flash_id':
-        flash_id = esp.flash_id()
-        print 'Manufacturer: %02x' % (flash_id & 0xff)
-        print 'Device: %02x%02x' % ((flash_id >> 8) & 0xff, (flash_id >> 16) & 0xff)
+    # operation function can take 1 arg (args), 2 args (esp, arg)
+    # or be a member function of the ESPROM class.
 
-    elif args.operation == 'read_flash':
-        print 'Please wait...'
-        file(args.filename, 'wb').write(esp.flash_read(args.address, 1024, int(math.ceil(args.size / 1024.)))[:args.size])
+    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}
+"""
 
-    elif args.operation == 'erase_flash':
-        esp.flash_erase()
+if __name__ == '__main__':
+    try:
+        main()
+    except FatalError as e:
+        print '\nA fatal error occurred: %s' % e
+        sys.exit(2)