#!/usr/bin/env python
#
# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton, Espressif Systems (Shanghai) CO LTD, other contributors as noted.
#
# SPDX-License-Identifier: GPL-2.0-or-later
from __future__ import division, print_function
import argparse
import base64
import binascii
import copy
import hashlib
import inspect
import io
import itertools
import os
import re
import shlex
import string
import struct
import sys
import time
import zlib
try:
import serial
except ImportError:
print("Pyserial is not installed for %s. Check the README for installation instructions." % (sys.executable))
raise
# check 'serial' is 'pyserial' and not 'serial' https://github.com/espressif/esptool/issues/269
try:
if "serialization" in serial.__doc__ and "deserialization" in serial.__doc__:
raise ImportError("""
esptool.py depends on pyserial, but there is a conflict with a currently installed package named 'serial'.
You may be able to work around this by 'pip uninstall serial; pip install pyserial' \
but this may break other installed Python software that depends on 'serial'.
There is no good fix for this right now, apart from configuring virtualenvs. \
See https://github.com/espressif/esptool/issues/269#issuecomment-385298196 for discussion of the underlying issue(s).""")
except TypeError:
pass # __doc__ returns None for pyserial
try:
import serial.tools.list_ports as list_ports
except ImportError:
print("The installed version (%s) of pyserial appears to be too old for esptool.py (Python interpreter %s). "
"Check the README for installation instructions." % (sys.VERSION, sys.executable))
raise
except Exception:
if sys.platform == "darwin":
# swallow the exception, this is a known issue in pyserial+macOS Big Sur preview ref https://github.com/espressif/esptool/issues/540
list_ports = None
else:
raise
__version__ = "3.3-dev"
MAX_UINT32 = 0xffffffff
MAX_UINT24 = 0xffffff
DEFAULT_TIMEOUT = 3 # timeout for most flash operations
START_FLASH_TIMEOUT = 20 # timeout for starting flash (may perform erase)
CHIP_ERASE_TIMEOUT = 120 # timeout for full chip erase
MAX_TIMEOUT = CHIP_ERASE_TIMEOUT * 2 # longest any command can run
SYNC_TIMEOUT = 0.1 # timeout for syncing with bootloader
MD5_TIMEOUT_PER_MB = 8 # timeout (per megabyte) for calculating md5sum
ERASE_REGION_TIMEOUT_PER_MB = 30 # timeout (per megabyte) for erasing a region
ERASE_WRITE_TIMEOUT_PER_MB = 40 # timeout (per megabyte) for erasing and writing data
MEM_END_ROM_TIMEOUT = 0.05 # special short timeout for ESP_MEM_END, as it may never respond
DEFAULT_SERIAL_WRITE_TIMEOUT = 10 # timeout for serial port write
DEFAULT_CONNECT_ATTEMPTS = 7 # default number of times to try connection
SUPPORTED_CHIPS = ['esp8266', 'esp32', 'esp32s2', 'esp32s3beta2', 'esp32s3', 'esp32c3', 'esp32c6beta', 'esp32h2beta1', 'esp32h2beta2', 'esp32c2']
def timeout_per_mb(seconds_per_mb, size_bytes):
""" Scales timeouts which are size-specific """
result = seconds_per_mb * (size_bytes / 1e6)
if result < DEFAULT_TIMEOUT:
return DEFAULT_TIMEOUT
return result
def _chip_to_rom_loader(chip):
return {
'esp8266': ESP8266ROM,
'esp32': ESP32ROM,
'esp32s2': ESP32S2ROM,
'esp32s3beta2': ESP32S3BETA2ROM,
'esp32s3': ESP32S3ROM,
'esp32c3': ESP32C3ROM,
'esp32c6beta': ESP32C6BETAROM,
'esp32h2beta1': ESP32H2BETA1ROM,
'esp32h2beta2': ESP32H2BETA2ROM,
'esp32c2': ESP32C2ROM,
}[chip]
def get_default_connected_device(serial_list, port, connect_attempts, initial_baud, chip='auto', trace=False,
before='default_reset'):
_esp = None
for each_port in reversed(serial_list):
print("Serial port %s" % each_port)
try:
if chip == 'auto':
_esp = ESPLoader.detect_chip(each_port, initial_baud, before, trace,
connect_attempts)
else:
chip_class = _chip_to_rom_loader(chip)
_esp = chip_class(each_port, initial_baud, trace)
_esp.connect(before, connect_attempts)
break
except (FatalError, OSError) as err:
if port is not None:
raise
print("%s failed to connect: %s" % (each_port, err))
_esp = None
return _esp
DETECTED_FLASH_SIZES = {0x12: '256KB', 0x13: '512KB', 0x14: '1MB',
0x15: '2MB', 0x16: '4MB', 0x17: '8MB',
0x18: '16MB', 0x19: '32MB', 0x1a: '64MB', 0x21: '128MB'}
def check_supported_function(func, check_func):
"""
Decorator implementation that wraps a check around an ESPLoader
bootloader function to check if it's supported.
This is used to capture the multidimensional differences in
functionality between the ESP8266 & ESP32 (and later chips) ROM loaders, and the
software stub that runs on these. Not possible to do this cleanly
via inheritance alone.
"""
def inner(*args, **kwargs):
obj = args[0]
if check_func(obj):
return func(*args, **kwargs)
else:
raise NotImplementedInROMError(obj, func)
return inner
def esp8266_function_only(func):
""" Attribute for a function only supported on ESP8266 """
return check_supported_function(func, lambda o: o.CHIP_NAME == "ESP8266")
def stub_function_only(func):
""" Attribute for a function only supported in the software stub loader """
return check_supported_function(func, lambda o: o.IS_STUB)
def stub_and_esp32_function_only(func):
""" Attribute for a function only supported by software stubs or ESP32 and later chips ROM """
return check_supported_function(func, lambda o: o.IS_STUB or isinstance(o, ESP32ROM))
def esp32s3_or_newer_function_only(func):
""" Attribute for a function only supported by ESP32S3 and later chips ROM """
return check_supported_function(func, lambda o: isinstance(o, ESP32S3ROM) or isinstance(o, ESP32C3ROM))
PYTHON2 = sys.version_info[0] < 3 # True if on pre-Python 3
# Function to return nth byte of a bitstring
# Different behaviour on Python 2 vs 3
if PYTHON2:
def byte(bitstr, index):
return ord(bitstr[index])
else:
def byte(bitstr, index):
return bitstr[index]
# Provide a 'basestring' class on Python 3
try:
basestring
except NameError:
basestring = str
def print_overwrite(message, last_line=False):
""" Print a message, overwriting the currently printed line.
If last_line is False, don't append a newline at the end (expecting another subsequent call will overwrite this one.)
After a sequence of calls with last_line=False, call once with last_line=True.
If output is not a TTY (for example redirected a pipe), no overwriting happens and this function is the same as print().
"""
if sys.stdout.isatty():
print("\r%s" % message, end='\n' if last_line else '')
else:
print(message)
def _mask_to_shift(mask):
""" Return the index of the least significant bit in the mask """
shift = 0
while mask & 0x1 == 0:
shift += 1
mask >>= 1
return shift
class ESPLoader(object):
""" Base class providing access to ESP ROM & software stub bootloaders.
Subclasses provide ESP8266 & ESP32 Family specific functionality.
Don't instantiate this base class directly, either instantiate a subclass or
call ESPLoader.detect_chip() which will interrogate the chip and return the
appropriate subclass instance.
"""
CHIP_NAME = "Espressif device"
IS_STUB = False
FPGA_SLOW_BOOT = False
DEFAULT_PORT = "/dev/ttyUSB0"
USES_RFC2217 = False
# Commands supported by ESP8266 ROM bootloader
ESP_FLASH_BEGIN = 0x02
ESP_FLASH_DATA