2024-02-10 17:13:25 +01:00
#!/usr/bin/env python
2022-01-10 02:07:12 +01:00
#
2024-02-10 17:13:25 +01:00
# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton, Espressif Systems (Shanghai) CO LTD, other contributors as noted.
2022-01-10 02:07:12 +01:00
#
2024-02-10 17:13:25 +01:00
# SPDX-License-Identifier: GPL-2.0-or-later
2022-01-10 02:07:12 +01:00
from __future__ import division , print_function
import argparse
import base64
import binascii
import copy
import hashlib
import inspect
import io
import itertools
import os
2024-02-10 17:13:25 +01:00
import re
2022-01-10 02:07:12 +01:00
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
2024-02-10 17:13:25 +01:00
__version__ = " 3.3.3 "
2022-01-10 02:07:12 +01:00
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
2024-02-10 17:13:25 +01:00
WRITE_BLOCK_ATTEMPTS = 3 # number of times to try writing a data block
SUPPORTED_CHIPS = [ ' esp8266 ' , ' esp32 ' , ' esp32s2 ' , ' esp32s3beta2 ' , ' esp32s3 ' , ' esp32c3 ' , ' esp32c6beta ' , ' esp32h2beta1 ' , ' esp32h2beta2 ' , ' esp32c2 ' ]
2022-01-10 02:07:12 +01:00
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 ,
2024-02-10 17:13:25 +01:00
' esp32s3 ' : ESP32S3ROM ,
2022-01-10 02:07:12 +01:00
' esp32c3 ' : ESP32C3ROM ,
' esp32c6beta ' : ESP32C6BETAROM ,
2024-02-10 17:13:25 +01:00
' esp32h2beta1 ' : ESP32H2BETA1ROM ,
' esp32h2beta2 ' : ESP32H2BETA2ROM ,
' esp32c2 ' : ESP32C2ROM ,
2022-01-10 02:07:12 +01:00
} [ 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 ) )
2024-02-10 17:13:25 +01:00
if _esp and _esp . _port :
_esp . _port . close ( )
2022-01-10 02:07:12 +01:00
_esp = None
return _esp
2024-02-10 17:13:25 +01:00
DETECTED_FLASH_SIZES = {
0x12 : " 256KB " ,
0x13 : " 512KB " ,
0x14 : " 1MB " ,
0x15 : " 2MB " ,
0x16 : " 4MB " ,
0x17 : " 8MB " ,
0x18 : " 16MB " ,
0x19 : " 32MB " ,
0x1A : " 64MB " ,
0x1B : " 128MB " ,
0x1C : " 256MB " ,
0x20 : " 64MB " ,
0x21 : " 128MB " ,
0x22 : " 256MB " ,
0x32 : " 256KB " ,
0x33 : " 512KB " ,
0x34 : " 1MB " ,
0x35 : " 2MB " ,
0x36 : " 4MB " ,
0x37 : " 8MB " ,
0x38 : " 16MB " ,
0x39 : " 32MB " ,
0x3A : " 64MB " ,
}
2022-01-10 02:07:12 +01:00
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
2024-02-10 17:13:25 +01:00
functionality between the ESP8266 & ESP32 ( and later chips ) ROM loaders , and the
software stub that runs on these . Not possible to do this cleanly
2022-01-10 02:07:12 +01:00
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
2024-02-10 17:13:25 +01:00
def esp8266_function_only ( func ) :
""" Attribute for a function only supported on ESP8266 """
return check_supported_function ( func , lambda o : o . CHIP_NAME == " ESP8266 " )
2022-01-10 02:07:12 +01:00
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 ) :
2024-02-10 17:13:25 +01:00
""" Attribute for a function only supported by software stubs or ESP32 and later chips ROM """
2022-01-10 02:07:12 +01:00
return check_supported_function ( func , lambda o : o . IS_STUB or isinstance ( o , ESP32ROM ) )
2024-02-10 17:13:25 +01:00
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 ) )
2022-01-10 02:07:12 +01:00
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.)
A fter 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.
2024-02-10 17:13:25 +01:00
Subclasses provide ESP8266 & ESP32 Family specific functionality .
2022-01-10 02:07:12 +01:00
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
2024-02-10 17:13:25 +01:00
FPGA_SLOW_BOOT = False
2022-01-10 02:07:12 +01:00
DEFAULT_PORT = " /dev/ttyUSB0 "
2024-02-10 17:13:25 +01:00
USES_RFC2217 = False
2022-01-10 02:07:12 +01:00
# Commands supported by ESP8266 ROM bootloader
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
2024-02-10 17:13:25 +01:00
# Some comands supported by ESP32 and later chips ROM bootloader (or -8266 w/ stub)
2022-01-10 02:07:12 +01:00
ESP_SPI_SET_PARAMS = 0x0B
ESP_SPI_ATTACH = 0x0D
ESP_READ_FLASH_SLOW = 0x0e # ROM only, much slower than the stub flash read
ESP_CHANGE_BAUDRATE = 0x0F
ESP_FLASH_DEFL_BEGIN = 0x10
ESP_FLASH_DEFL_DATA = 0x11
ESP_FLASH_DEFL_END = 0x12
ESP_SPI_FLASH_MD5 = 0x13
2024-02-10 17:13:25 +01:00
# Commands supported by ESP32-S2 and later chips ROM bootloader only
2022-01-10 02:07:12 +01:00
ESP_GET_SECURITY_INFO = 0x14
# Some commands supported by stub only
ESP_ERASE_FLASH = 0xD0
ESP_ERASE_REGION = 0xD1
ESP_READ_FLASH = 0xD2
ESP_RUN_USER_CODE = 0xD3
# Flash encryption encrypted data command
ESP_FLASH_ENCRYPT_DATA = 0xD4
# Response code(s) sent by ROM
ROM_INVALID_RECV_MSG = 0x05 # response if an invalid message is received
# Maximum block sized for RAM and Flash writes, respectively.
ESP_RAM_BLOCK = 0x1800
FLASH_WRITE_SIZE = 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
# Flash sector size, minimum unit of erase.
FLASH_SECTOR_SIZE = 0x1000
UART_DATE_REG_ADDR = 0x60000078
CHIP_DETECT_MAGIC_REG_ADDR = 0x40001000 # This ROM address has a different value on each chip model
UART_CLKDIV_MASK = 0xFFFFF
# Memory addresses
IROM_MAP_START = 0x40200000
IROM_MAP_END = 0x40300000
# The number of bytes in the UART response that signify command status
STATUS_BYTES_LENGTH = 2
# Response to ESP_SYNC might indicate that flasher stub is running instead of the ROM bootloader
sync_stub_detected = False
# Device PIDs
USB_JTAG_SERIAL_PID = 0x1001
2024-02-10 17:13:25 +01:00
# Chip IDs that are no longer supported by esptool
UNSUPPORTED_CHIPS = { 6 : " ESP32-S3(beta 3) " }
2022-01-10 02:07:12 +01:00
def __init__ ( self , port = DEFAULT_PORT , baud = ESP_ROM_BAUD , trace_enabled = False ) :
""" Base constructor for ESPLoader bootloader interaction
Don ' t call this constructor, either instantiate ESP8266ROM
or ESP32ROM , or use ESPLoader . detect_chip ( ) .
This base class has all of the instance methods for bootloader
functionality supported across various chips & stub
loaders . Subclasses replace the functions they don ' t support
with ones which throw NotImplementedInROMError ( ) .
"""
self . secure_download_mode = False # flag is set to True if esptool detects the ROM is in Secure Download Mode
2024-02-10 17:13:25 +01:00
self . stub_is_disabled = False # flag is set to True if esptool detects conditions which require the stub to be disabled
2022-01-10 02:07:12 +01:00
if isinstance ( port , basestring ) :
self . _port = serial . serial_for_url ( port )
else :
self . _port = port
self . _slip_reader = slip_reader ( self . _port , self . trace )
# 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 . _set_port_baudrate ( baud )
self . _trace_enabled = trace_enabled
# set write timeout, to prevent esptool blocked at write forever.
try :
self . _port . write_timeout = DEFAULT_SERIAL_WRITE_TIMEOUT
except NotImplementedError :
# no write timeout for RFC2217 ports
# need to set the property back to None or it will continue to fail
self . _port . write_timeout = None
@property
def serial_port ( self ) :
return self . _port . port
def _set_port_baudrate ( self , baud ) :
try :
self . _port . baudrate = baud
except IOError :
raise FatalError ( " Failed to set baud rate %d . The driver may not support this rate. " % baud )
@staticmethod
def detect_chip ( port = DEFAULT_PORT , baud = ESP_ROM_BAUD , connect_mode = ' default_reset ' , trace_enabled = False ,
connect_attempts = DEFAULT_CONNECT_ATTEMPTS ) :
""" Use serial access to detect the chip type.
2024-02-10 17:13:25 +01:00
First , get_security_info command is sent to detect the ID of the chip
( supported only by ESP32 - C3 and later , works even in the Secure Download Mode ) .
If this fails , we reconnect and fall - back to reading the magic number .
It ' s mapped at a specific ROM address and has a different value on each chip model.
This way we can use one memory read and compare it to the magic number for each chip type .
2022-01-10 02:07:12 +01:00
This routine automatically performs ESPLoader . connect ( ) ( passing
connect_mode parameter ) as part of querying the chip .
"""
2024-02-10 17:13:25 +01:00
inst = None
2022-01-10 02:07:12 +01:00
detect_port = ESPLoader ( port , baud , trace_enabled = trace_enabled )
2024-02-10 17:13:25 +01:00
if detect_port . serial_port . startswith ( " rfc2217: " ) :
detect_port . USES_RFC2217 = True
2022-01-10 02:07:12 +01:00
detect_port . connect ( connect_mode , connect_attempts , detecting = True )
try :
print ( ' Detecting chip type... ' , end = ' ' )
2024-02-10 17:13:25 +01:00
res = detect_port . check_command ( ' get security info ' , ESPLoader . ESP_GET_SECURITY_INFO , b ' ' )
res = struct . unpack ( " <IBBBBBBBBI " , res [ : 16 ] ) # 4b flags, 1b flash_crypt_cnt, 7*1b key_purposes, 4b chip_id
chip_id = res [ 9 ] # 2/4 status bytes invariant
2022-01-10 02:07:12 +01:00
2024-02-10 17:13:25 +01:00
for cls in [ ESP32S3BETA2ROM , ESP32S3ROM , ESP32C3ROM , ESP32C6BETAROM , ESP32H2BETA1ROM , ESP32C2ROM , ESP32H2BETA2ROM ] :
if chip_id == cls . IMAGE_CHIP_ID :
2022-01-10 02:07:12 +01:00
inst = cls ( detect_port . _port , baud , trace_enabled = trace_enabled )
inst . _post_connect ( )
2024-02-10 17:13:25 +01:00
try :
inst . read_reg ( ESPLoader . CHIP_DETECT_MAGIC_REG_ADDR ) # Dummy read to check Secure Download mode
except UnsupportedCommandError :
inst . secure_download_mode = True
except ( UnsupportedCommandError , struct . error , FatalError ) as e :
# UnsupportedCmdErr: ESP8266/ESP32 ROM | struct.err: ESP32-S2 | FatalErr: ESP8266/ESP32 STUB
print ( " Unsupported detection protocol, switching and trying again... " )
try :
# ESP32/ESP8266 are reset after an unsupported command, need to connect again (not needed on ESP32-S2)
if not isinstance ( e , struct . error ) :
detect_port . connect ( connect_mode , connect_attempts , detecting = True , warnings = False )
print ( ' Detecting chip type... ' , end = ' ' )
sys . stdout . flush ( )
chip_magic_value = detect_port . read_reg ( ESPLoader . CHIP_DETECT_MAGIC_REG_ADDR )
for cls in [ ESP8266ROM , ESP32ROM , ESP32S2ROM , ESP32S3BETA2ROM , ESP32S3ROM ,
ESP32C3ROM , ESP32C6BETAROM , ESP32H2BETA1ROM , ESP32C2ROM , ESP32H2BETA2ROM ] :
if chip_magic_value in cls . CHIP_DETECT_MAGIC_VALUE :
inst = cls ( detect_port . _port , baud , trace_enabled = trace_enabled )
inst . _post_connect ( )
inst . check_chip_id ( )
except UnsupportedCommandError :
raise FatalError ( " Unsupported Command Error received. Probably this means Secure Download Mode is enabled, "
" autodetection will not work. Need to manually specify the chip. " )
2022-01-10 02:07:12 +01:00
finally :
2024-02-10 17:13:25 +01:00
if inst is not None :
print ( ' %s ' % inst . CHIP_NAME , end = ' ' )
if detect_port . sync_stub_detected :
inst = inst . STUB_CLASS ( inst )
inst . sync_stub_detected = True
print ( ' ' ) # end line
return inst
2022-01-10 02:07:12 +01:00
raise FatalError ( " Unexpected CHIP magic value 0x %08x . Failed to autodetect chip type. " % ( chip_magic_value ) )
""" 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 . trace ( " Write %d bytes: %s " , len ( buf ) , HexFormatter ( buf ) )
self . _port . write ( buf )
def trace ( self , message , * format_args ) :
if self . _trace_enabled :
now = time . time ( )
try :
delta = now - self . _last_trace
except AttributeError :
delta = 0.0
self . _last_trace = now
prefix = " TRACE + %.3f " % delta
print ( prefix + ( message % format_args ) )
""" 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 = b " " , chk = 0 , wait_response = True , timeout = DEFAULT_TIMEOUT ) :
saved_timeout = self . _port . timeout
new_timeout = min ( timeout , MAX_TIMEOUT )
if new_timeout != saved_timeout :
self . _port . timeout = new_timeout
try :
if op is not None :
self . trace ( " command op=0x %02x data len= %s wait_response= %d timeout= %.3f data= %s " ,
op , len ( data ) , 1 if wait_response else 0 , timeout , HexFormatter ( data ) )
pkt = struct . pack ( b ' <BBHI ' , 0x00 , op , len ( data ) , chk ) + data
self . write ( pkt )
if not wait_response :
return
# 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 range ( 100 ) :
p = self . read ( )
if len ( p ) < 8 :
continue
( resp , op_ret , len_ret , val ) = struct . unpack ( ' <BBHI ' , p [ : 8 ] )
if resp != 1 :
continue
data = p [ 8 : ]
if op is None or op_ret == op :
return val , data
if byte ( data , 0 ) != 0 and byte ( data , 1 ) == self . ROM_INVALID_RECV_MSG :
self . flush_input ( ) # Unsupported read_reg can result in more than one error response for some reason
raise UnsupportedCommandError ( self , op )
finally :
if new_timeout != saved_timeout :
self . _port . timeout = saved_timeout
raise FatalError ( " Response doesn ' t match request " )
def check_command ( self , op_description , op = None , data = b ' ' , chk = 0 , timeout = DEFAULT_TIMEOUT ) :
"""
Execute a command with ' command ' , check the result code and throw an appropriate
FatalError if it fails .
Returns the " result " of a successful command .
"""
val , data = self . command ( op , data , chk , timeout = timeout )
# things are a bit weird here, bear with us
# the status bytes are the last 2/4 bytes in the data (depending on chip)
if len ( data ) < self . STATUS_BYTES_LENGTH :
raise FatalError ( " Failed to %s . Only got %d byte status response. " % ( op_description , len ( data ) ) )
status_bytes = data [ - self . STATUS_BYTES_LENGTH : ]
# we only care if the first one is non-zero. If it is, the second byte is a reason.
if byte ( status_bytes , 0 ) != 0 :
raise FatalError . WithResult ( ' Failed to %s ' % op_description , status_bytes )
# if we had more data than just the status bytes, return it as the result
# (this is used by the md5sum command, maybe other commands?)
if len ( data ) > self . STATUS_BYTES_LENGTH :
return data [ : - self . STATUS_BYTES_LENGTH ]
else : # otherwise, just return the 'val' field which comes from the reply header (this is used by read_reg)
return val
def flush_input ( self ) :
self . _port . flushInput ( )
self . _slip_reader = slip_reader ( self . _port , self . trace )
def sync ( self ) :
val , _ = self . command ( self . ESP_SYNC , b ' \x07 \x07 \x12 \x20 ' + 32 * b ' \x55 ' ,
timeout = SYNC_TIMEOUT )
# ROM bootloaders send some non-zero "val" response. The flasher stub sends 0. If we receive 0 then it
# probably indicates that the chip wasn't or couldn't be reseted properly and esptool is talking to the
# flasher stub.
self . sync_stub_detected = val == 0
for _ in range ( 7 ) :
val , _ = self . command ( )
self . sync_stub_detected & = val == 0
def _setDTR ( self , state ) :
self . _port . setDTR ( state )
def _setRTS ( self , state ) :
self . _port . setRTS ( state )
# Work-around for adapters on Windows using the usbser.sys driver:
# generate a dummy change to DTR so that the set-control-line-state
# request is sent with the updated RTS state and the same DTR state
self . _port . setDTR ( self . _port . dtr )
def _get_pid ( self ) :
if list_ports is None :
print ( " \n Listing all serial ports is currently not available. Can ' t get device PID. " )
return
active_port = self . _port . port
# Pyserial only identifies regular ports, URL handlers are not supported
2024-02-10 17:13:25 +01:00
if not active_port . lower ( ) . startswith ( ( " com " , " /dev/ " ) ) :
2022-01-10 02:07:12 +01:00
print ( " \n Device PID identification is only supported on COM and /dev/ serial ports. " )
return
# Return the real path if the active port is a symlink
if active_port . startswith ( " /dev/ " ) and os . path . islink ( active_port ) :
active_port = os . path . realpath ( active_port )
2024-02-10 17:13:25 +01:00
# The "cu" (call-up) device has to be used for outgoing communication on MacOS
if sys . platform == " darwin " and " tty " in active_port :
active_port = [ active_port , active_port . replace ( " tty " , " cu " ) ]
2022-01-10 02:07:12 +01:00
ports = list_ports . comports ( )
for p in ports :
2024-02-10 17:13:25 +01:00
if p . device in active_port :
2022-01-10 02:07:12 +01:00
return p . pid
print ( " \n Failed to get PID of a device on {} , using standard reset sequence. " . format ( active_port ) )
2024-02-10 17:13:25 +01:00
def bootloader_reset ( self , usb_jtag_serial = False , extra_delay = False ) :
""" Issue a reset-to-bootloader, with USB-JTAG-Serial custom reset sequence option
2022-01-10 02:07:12 +01:00
"""
# RTS = either CH_PD/EN or nRESET (both active low = chip in reset)
# DTR = GPIO0 (active low = boot to flasher)
#
# DTR & RTS are active low signals,
# ie True = pin @ 0V, False = pin @ VCC.
if usb_jtag_serial :
# Custom reset sequence, which is required when the device
# is connecting via its USB-JTAG-Serial peripheral
self . _setRTS ( False )
self . _setDTR ( False ) # Idle
time . sleep ( 0.1 )
self . _setDTR ( True ) # Set IO0
self . _setRTS ( False )
time . sleep ( 0.1 )
self . _setRTS ( True ) # Reset. Note dtr/rts calls inverted so we go through (1,1) instead of (0,0)
self . _setDTR ( False )
self . _setRTS ( True ) # Extra RTS set for RTS as Windows only propagates DTR on RTS setting
time . sleep ( 0.1 )
self . _setDTR ( False )
self . _setRTS ( False )
else :
2024-02-10 17:13:25 +01:00
# This fpga delay is for Espressif internal use
fpga_delay = True if self . FPGA_SLOW_BOOT and os . environ . get ( " ESPTOOL_ENV_FPGA " , " " ) . strip ( ) == " 1 " else False
delay = 7 if fpga_delay else 0.5 if extra_delay else 0.05 # 0.5 needed for ESP32 rev0 and rev1
2022-01-10 02:07:12 +01:00
self . _setDTR ( False ) # IO0=HIGH
self . _setRTS ( True ) # EN=LOW, chip in reset
time . sleep ( 0.1 )
self . _setDTR ( True ) # IO0=LOW
self . _setRTS ( False ) # EN=HIGH, chip out of reset
2024-02-10 17:13:25 +01:00
time . sleep ( delay )
2022-01-10 02:07:12 +01:00
self . _setDTR ( False ) # IO0=HIGH, done
2024-02-10 17:13:25 +01:00
def _connect_attempt ( self , mode = ' default_reset ' , usb_jtag_serial = False , extra_delay = False ) :
""" A single connection attempt """
2022-01-10 02:07:12 +01:00
last_error = None
2024-02-10 17:13:25 +01:00
boot_log_detected = False
download_mode = False
2022-01-10 02:07:12 +01:00
# If we're doing no_sync, we're likely communicating as a pass through
# with an intermediate device to the ESP32
if mode == " no_reset_no_sync " :
return last_error
if mode != ' no_reset ' :
2024-02-10 17:13:25 +01:00
if not self . USES_RFC2217 : # Might block on rfc2217 ports
self . _port . reset_input_buffer ( ) # Empty serial buffer to isolate boot log
self . bootloader_reset ( usb_jtag_serial , extra_delay )
# Detect the ROM boot log and check actual boot mode (ESP32 and later only)
waiting = self . _port . inWaiting ( )
read_bytes = self . _port . read ( waiting )
data = re . search ( b ' boot:(0x[0-9a-fA-F]+)(.*waiting for download)? ' , read_bytes , re . DOTALL )
if data is not None :
boot_log_detected = True
boot_mode = data . group ( 1 )
download_mode = data . group ( 2 ) is not None
2022-01-10 02:07:12 +01:00
for _ in range ( 5 ) :
try :
self . flush_input ( )
self . _port . flushOutput ( )
self . sync ( )
return None
except FatalError as e :
2024-02-10 17:13:25 +01:00
print ( ' . ' , end = ' ' )
2022-01-10 02:07:12 +01:00
sys . stdout . flush ( )
time . sleep ( 0.05 )
last_error = e
2024-02-10 17:13:25 +01:00
if boot_log_detected :
last_error = FatalError ( " Wrong boot mode detected ( {} )! The chip needs to be in download mode. " . format ( boot_mode . decode ( " utf-8 " ) ) )
if download_mode :
last_error = FatalError ( " Download mode successfully detected, but getting no sync reply: The serial TX path seems to be down. " )
2022-01-10 02:07:12 +01:00
return last_error
def get_memory_region ( self , name ) :
""" Returns a tuple of (start, end) for the memory map entry with the given name, or None if it doesn ' t exist
"""
try :
return [ ( start , end ) for ( start , end , n ) in self . MEMORY_MAP if n == name ] [ 0 ]
except IndexError :
return None
2024-02-10 17:13:25 +01:00
def connect ( self , mode = ' default_reset ' , attempts = DEFAULT_CONNECT_ATTEMPTS , detecting = False , warnings = True ) :
2022-01-10 02:07:12 +01:00
""" Try connecting repeatedly until successful, or giving up """
2024-02-10 17:13:25 +01:00
if warnings and mode in [ ' no_reset ' , ' no_reset_no_sync ' ] :
2022-01-10 02:07:12 +01:00
print ( ' WARNING: Pre-connection option " {} " was selected. ' . format ( mode ) ,
' Connection may fail if the chip is not in bootloader or flasher stub mode. ' )
print ( ' Connecting... ' , end = ' ' )
sys . stdout . flush ( )
last_error = None
usb_jtag_serial = ( mode == ' usb_reset ' ) or ( self . _get_pid ( ) == self . USB_JTAG_SERIAL_PID )
try :
2024-02-10 17:13:25 +01:00
for _ , extra_delay in zip ( range ( attempts ) if attempts > 0 else itertools . count ( ) , itertools . cycle ( ( False , True ) ) ) :
last_error = self . _connect_attempt ( mode = mode , usb_jtag_serial = usb_jtag_serial , extra_delay = extra_delay )
2022-01-10 02:07:12 +01:00
if last_error is None :
break
finally :
print ( ' ' ) # end 'Connecting...' line
if last_error is not None :
2024-02-10 17:13:25 +01:00
raise FatalError ( ' Failed to connect to {} : {} '
' \n For troubleshooting steps visit: '
' https://docs.espressif.com/projects/esptool/en/latest/troubleshooting.html ' . format ( self . CHIP_NAME , last_error ) )
2022-01-10 02:07:12 +01:00
if not detecting :
try :
# check the date code registers match what we expect to see
chip_magic_value = self . read_reg ( ESPLoader . CHIP_DETECT_MAGIC_REG_ADDR )
if chip_magic_value not in self . CHIP_DETECT_MAGIC_VALUE :
actually = None
2024-02-10 17:13:25 +01:00
for cls in [ ESP8266ROM , ESP32ROM , ESP32S2ROM , ESP32S3BETA2ROM , ESP32S3ROM ,
ESP32C3ROM , ESP32H2BETA1ROM , ESP32H2BETA2ROM , ESP32C2ROM , ESP32C6BETAROM ] :
2022-01-10 02:07:12 +01:00
if chip_magic_value in cls . CHIP_DETECT_MAGIC_VALUE :
actually = cls
break
2024-02-10 17:13:25 +01:00
if warnings and actually is None :
2022-01-10 02:07:12 +01:00
print ( ( " WARNING: This chip doesn ' t appear to be a %s (chip magic value 0x %08x ). "
" Probably it is unsupported by this version of esptool. " ) % ( self . CHIP_NAME , chip_magic_value ) )
else :
raise FatalError ( " This chip is %s not %s . Wrong --chip argument? " % ( actually . CHIP_NAME , self . CHIP_NAME ) )
except UnsupportedCommandError :
self . secure_download_mode = True
self . _post_connect ( )
2024-02-10 17:13:25 +01:00
self . check_chip_id ( )
2022-01-10 02:07:12 +01:00
def _post_connect ( self ) :
"""
Additional initialization hook , may be overridden by the chip - specific class .
Gets called after connect , and after auto - detection .
"""
pass
def read_reg ( self , addr , timeout = DEFAULT_TIMEOUT ) :
""" Read memory address in target """
# we don't call check_command here because read_reg() function is called
# when detecting chip type, and the way we check for success (STATUS_BYTES_LENGTH) is different
# for different chip types (!)
val , data = self . command ( self . ESP_READ_REG , struct . pack ( ' <I ' , addr ) , timeout = timeout )
if byte ( data , 0 ) != 0 :
raise FatalError . WithResult ( " Failed to read register address %08x " % addr , data )
return val
""" Write to memory address in target """
def write_reg ( self , addr , value , mask = 0xFFFFFFFF , delay_us = 0 , delay_after_us = 0 ) :
command = struct . pack ( ' <IIII ' , addr , value , mask , delay_us )
if delay_after_us > 0 :
# add a dummy write to a date register as an excuse to have a delay
command + = struct . pack ( ' <IIII ' , self . UART_DATE_REG_ADDR , 0 , 0 , delay_after_us )
return self . check_command ( " write target memory " , self . ESP_WRITE_REG , command )
def update_reg ( self , addr , mask , new_val ) :
""" Update register at ' addr ' , replace the bits masked out by ' mask '
with new_val . new_val is shifted left to match the LSB of ' mask '
Returns just - written value of register .
"""
shift = _mask_to_shift ( mask )
val = self . read_reg ( addr )
val & = ~ mask
val | = ( new_val << shift ) & mask
self . write_reg ( addr , val )
return val
""" Start downloading an application image to RAM """
def mem_begin ( self , size , blocks , blocksize , offset ) :
if self . IS_STUB : # check we're not going to overwrite a running stub with this data
stub = self . STUB_CODE
load_start = offset
load_end = offset + size
for ( start , end ) in [ ( stub [ " data_start " ] , stub [ " data_start " ] + len ( stub [ " data " ] ) ) ,
( stub [ " text_start " ] , stub [ " text_start " ] + len ( stub [ " text " ] ) ) ] :
if load_start < end and load_end > start :
raise FatalError ( ( " Software loader is resident at 0x %08x -0x %08x . "
" Can ' t load binary at overlapping address range 0x %08x -0x %08x . "
" Either change binary loading address, or use the --no-stub "
" option to disable the software loader. " ) % ( start , end , load_start , load_end ) )
return self . check_command ( " enter RAM download mode " , self . ESP_MEM_BEGIN ,
struct . pack ( ' <IIII ' , size , blocks , blocksize , offset ) )
""" Send a block of an image to RAM """
def mem_block ( self , data , seq ) :
return self . check_command ( " write to target RAM " , self . ESP_MEM_DATA ,
struct . pack ( ' <IIII ' , len ( data ) , seq , 0 , 0 ) + data ,
self . checksum ( data ) )
""" Leave download mode and run the application """
def mem_finish ( self , entrypoint = 0 ) :
# Sending ESP_MEM_END usually sends a correct response back, however sometimes
# (with ROM loader) the executed code may reset the UART or change the baud rate
# before the transmit FIFO is empty. So in these cases we set a short timeout and
# ignore errors.
timeout = DEFAULT_TIMEOUT if self . IS_STUB else MEM_END_ROM_TIMEOUT
data = struct . pack ( ' <II ' , int ( entrypoint == 0 ) , entrypoint )
try :
return self . check_command ( " leave RAM download mode " , self . ESP_MEM_END ,
data = data , timeout = timeout )
except FatalError :
if self . IS_STUB :
raise
pass
""" Start downloading to Flash (performs an erase)
Returns number of blocks ( of size self . FLASH_WRITE_SIZE ) to write .
"""
def flash_begin ( self , size , offset , begin_rom_encrypted = False ) :
num_blocks = ( size + self . FLASH_WRITE_SIZE - 1 ) / / self . FLASH_WRITE_SIZE
erase_size = self . get_erase_size ( offset , size )
t = time . time ( )
if self . IS_STUB :
timeout = DEFAULT_TIMEOUT
else :
timeout = timeout_per_mb ( ERASE_REGION_TIMEOUT_PER_MB , size ) # ROM performs the erase up front
params = struct . pack ( ' <IIII ' , erase_size , num_blocks , self . FLASH_WRITE_SIZE , offset )
2024-02-10 17:13:25 +01:00
if isinstance ( self , ( ESP32S2ROM , ESP32S3BETA2ROM , ESP32S3ROM , ESP32C3ROM ,
ESP32C6BETAROM , ESP32H2BETA1ROM , ESP32C2ROM , ESP32H2BETA2ROM ) ) and not self . IS_STUB :
2022-01-10 02:07:12 +01:00
params + = struct . pack ( ' <I ' , 1 if begin_rom_encrypted else 0 )
self . check_command ( " enter Flash download mode " , self . ESP_FLASH_BEGIN ,
params , timeout = timeout )
if size != 0 and not self . IS_STUB :
print ( " Took %.2f s to erase flash block " % ( time . time ( ) - t ) )
return num_blocks
def flash_block ( self , data , seq , timeout = DEFAULT_TIMEOUT ) :
2024-02-10 17:13:25 +01:00
""" Write block to flash, retry if fail """
for attempts_left in range ( WRITE_BLOCK_ATTEMPTS - 1 , - 1 , - 1 ) :
try :
self . check_command (
" write to target Flash after seq %d " % seq ,
self . ESP_FLASH_DATA ,
struct . pack ( " <IIII " , len ( data ) , seq , 0 , 0 ) + data ,
self . checksum ( data ) ,
timeout = timeout ,
)
break
except FatalError :
if attempts_left :
self . trace (
" Block write failed, "
" retrying with {} attempts left " . format ( attempts_left )
)
else :
raise
2022-01-10 02:07:12 +01:00
def flash_encrypt_block ( self , data , seq , timeout = DEFAULT_TIMEOUT ) :
2024-02-10 17:13:25 +01:00
""" Encrypt, write block to flash, retry if fail """
if isinstance ( self , ( ESP32S2ROM , ESP32C3ROM , ESP32S3ROM , ESP32H2BETA1ROM , ESP32C2ROM , ESP32H2BETA2ROM ) ) and not self . IS_STUB :
2022-01-10 02:07:12 +01:00
# ROM support performs the encrypted writes via the normal write command,
# triggered by flash_begin(begin_rom_encrypted=True)
return self . flash_block ( data , seq , timeout )
2024-02-10 17:13:25 +01:00
for attempts_left in range ( WRITE_BLOCK_ATTEMPTS - 1 , - 1 , - 1 ) :
try :
self . check_command (
" Write encrypted to target Flash after seq %d " % seq ,
self . ESP_FLASH_ENCRYPT_DATA ,
struct . pack ( " <IIII " , len ( data ) , seq , 0 , 0 ) + data ,
self . checksum ( data ) ,
timeout = timeout ,
)
break
except FatalError :
if attempts_left :
self . trace (
" Encrypted block write failed, "
" retrying with {} attempts left " . format ( attempts_left )
)
else :
raise
2022-01-10 02:07:12 +01:00
""" Leave flash mode and run/reboot """
def flash_finish ( self , reboot = False ) :
pkt = struct . pack ( ' <I ' , int ( not reboot ) )
# stub sends a reply to this command
self . check_command ( " leave Flash mode " , self . ESP_FLASH_END , pkt )
""" Run application code in flash """
def run ( self , reboot = False ) :
# Fake flash begin immediately followed by flash end
self . flash_begin ( 0 , 0 )
self . flash_finish ( reboot )
""" Read SPI flash manufacturer and device id """
def flash_id ( self ) :
SPIFLASH_RDID = 0x9F
return self . run_spiflash_command ( SPIFLASH_RDID , b " " , 24 )
def get_security_info ( self ) :
res = self . check_command ( ' get security info ' , self . ESP_GET_SECURITY_INFO , b ' ' )
2024-02-10 17:13:25 +01:00
esp32s2 = True if len ( res ) == 12 else False
res = struct . unpack ( " <IBBBBBBBB " if esp32s2 else " <IBBBBBBBBII " , res )
return {
" flags " : res [ 0 ] ,
" flash_crypt_cnt " : res [ 1 ] ,
" key_purposes " : res [ 2 : 9 ] ,
" chip_id " : None if esp32s2 else res [ 9 ] ,
" api_version " : None if esp32s2 else res [ 10 ] ,
}
@esp32s3_or_newer_function_only
def get_chip_id ( self ) :
res = self . check_command ( ' get security info ' , self . ESP_GET_SECURITY_INFO , b ' ' )
res = struct . unpack ( " <IBBBBBBBBI " , res [ : 16 ] ) # 4b flags, 1b flash_crypt_cnt, 7*1b key_purposes, 4b chip_id
chip_id = res [ 9 ] # 2/4 status bytes invariant
return chip_id
2022-01-10 02:07:12 +01:00
@classmethod
def parse_flash_size_arg ( cls , arg ) :
try :
return cls . FLASH_SIZES [ arg ]
except KeyError :
raise FatalError ( " Flash size ' %s ' is not supported by this chip type. Supported sizes: %s "
% ( arg , " , " . join ( cls . FLASH_SIZES . keys ( ) ) ) )
2024-02-10 17:13:25 +01:00
@classmethod
def parse_flash_freq_arg ( cls , arg ) :
try :
return cls . FLASH_FREQUENCY [ arg ]
except KeyError :
raise FatalError ( " Flash frequency ' %s ' is not supported by this chip type. Supported frequencies: %s "
% ( arg , " , " . join ( cls . FLASH_FREQUENCY . keys ( ) ) ) )
2022-01-10 02:07:12 +01:00
def run_stub ( self , stub = None ) :
if stub is None :
stub = self . STUB_CODE
if self . sync_stub_detected :
print ( " Stub is already running. No upload is necessary. " )
return self . STUB_CLASS ( self )
# Upload
print ( " Uploading stub... " )
for field in [ ' text ' , ' data ' ] :
if field in stub :
offs = stub [ field + " _start " ]
length = len ( stub [ field ] )
blocks = ( length + self . ESP_RAM_BLOCK - 1 ) / / self . ESP_RAM_BLOCK
self . mem_begin ( length , blocks , self . ESP_RAM_BLOCK , offs )
for seq in range ( blocks ) :
from_offs = seq * self . ESP_RAM_BLOCK
to_offs = from_offs + self . ESP_RAM_BLOCK
self . mem_block ( stub [ field ] [ from_offs : to_offs ] , seq )
print ( " Running stub... " )
self . mem_finish ( stub [ ' entry ' ] )
p = self . read ( )
if p != b ' OHAI ' :
raise FatalError ( " Failed to start stub. Unexpected response: %s " % p )
print ( " Stub running... " )
return self . STUB_CLASS ( self )
@stub_and_esp32_function_only
def flash_defl_begin ( self , size , compsize , offset ) :
""" Start downloading compressed data to Flash (performs an erase)
Returns number of blocks ( size self . FLASH_WRITE_SIZE ) to write .
"""
num_blocks = ( compsize + self . FLASH_WRITE_SIZE - 1 ) / / self . FLASH_WRITE_SIZE
erase_blocks = ( size + self . FLASH_WRITE_SIZE - 1 ) / / self . FLASH_WRITE_SIZE
t = time . time ( )
if self . IS_STUB :
write_size = size # stub expects number of bytes here, manages erasing internally
timeout = DEFAULT_TIMEOUT
else :
write_size = erase_blocks * self . FLASH_WRITE_SIZE # ROM expects rounded up to erase block size
timeout = timeout_per_mb ( ERASE_REGION_TIMEOUT_PER_MB , write_size ) # ROM performs the erase up front
print ( " Compressed %d bytes to %d ... " % ( size , compsize ) )
params = struct . pack ( ' <IIII ' , write_size , num_blocks , self . FLASH_WRITE_SIZE , offset )
2024-02-10 17:13:25 +01:00
if isinstance ( self , ( ESP32S2ROM , ESP32S3BETA2ROM , ESP32S3ROM , ESP32C3ROM ,
ESP32C6BETAROM , ESP32H2BETA1ROM , ESP32C2ROM , ESP32H2BETA2ROM ) ) and not self . IS_STUB :
2022-01-10 02:07:12 +01:00
params + = struct . pack ( ' <I ' , 0 ) # extra param is to enter encrypted flash mode via ROM (not supported currently)
self . check_command ( " enter compressed flash mode " , self . ESP_FLASH_DEFL_BEGIN , params , timeout = timeout )
if size != 0 and not self . IS_STUB :
# (stub erases as it writes, but ROM loaders erase on begin)
print ( " Took %.2f s to erase flash block " % ( time . time ( ) - t ) )
return num_blocks
@stub_and_esp32_function_only
def flash_defl_block ( self , data , seq , timeout = DEFAULT_TIMEOUT ) :
2024-02-10 17:13:25 +01:00
""" Write block to flash, send compressed, retry if fail """
for attempts_left in range ( WRITE_BLOCK_ATTEMPTS - 1 , - 1 , - 1 ) :
try :
self . check_command (
" write compressed data to flash after seq %d " % seq ,
self . ESP_FLASH_DEFL_DATA ,
struct . pack ( " <IIII " , len ( data ) , seq , 0 , 0 ) + data ,
self . checksum ( data ) ,
timeout = timeout ,
)
break
except FatalError :
if attempts_left :
self . trace (
" Compressed block write failed, "
" retrying with {} attempts left " . format ( attempts_left )
)
else :
raise
2022-01-10 02:07:12 +01:00
""" Leave compressed flash mode and run/reboot """
@stub_and_esp32_function_only
def flash_defl_finish ( self , reboot = False ) :
if not reboot and not self . IS_STUB :
# skip sending flash_finish to ROM loader, as this
# exits the bootloader. Stub doesn't do this.
return
pkt = struct . pack ( ' <I ' , int ( not reboot ) )
self . check_command ( " leave compressed flash mode " , self . ESP_FLASH_DEFL_END , pkt )
self . in_bootloader = False
@stub_and_esp32_function_only
def flash_md5sum ( self , addr , size ) :
# the MD5 command returns additional bytes in the standard
# command reply slot
timeout = timeout_per_mb ( MD5_TIMEOUT_PER_MB , size )
res = self . check_command ( ' calculate md5sum ' , self . ESP_SPI_FLASH_MD5 , struct . pack ( ' <IIII ' , addr , size , 0 , 0 ) ,
timeout = timeout )
if len ( res ) == 32 :
return res . decode ( " utf-8 " ) # already hex formatted
elif len ( res ) == 16 :
return hexify ( res ) . lower ( )
else :
raise FatalError ( " MD5Sum command returned unexpected result: %r " % res )
@stub_and_esp32_function_only
def change_baud ( self , baud ) :
print ( " Changing baud rate to %d " % baud )
# stub takes the new baud rate and the old one
second_arg = self . _port . baudrate if self . IS_STUB else 0
self . command ( self . ESP_CHANGE_BAUDRATE , struct . pack ( ' <II ' , baud , second_arg ) )
print ( " Changed. " )
self . _set_port_baudrate ( baud )
time . sleep ( 0.05 ) # get rid of crap sent during baud rate change
self . flush_input ( )
@stub_function_only
def erase_flash ( self ) :
# depending on flash chip model the erase may take this long (maybe longer!)
self . check_command ( " erase flash " , self . ESP_ERASE_FLASH ,
timeout = CHIP_ERASE_TIMEOUT )
@stub_function_only
def erase_region ( self , offset , size ) :
if offset % self . FLASH_SECTOR_SIZE != 0 :
raise FatalError ( " Offset to erase from must be a multiple of 4096 " )
if size % self . FLASH_SECTOR_SIZE != 0 :
raise FatalError ( " Size of data to erase must be a multiple of 4096 " )
timeout = timeout_per_mb ( ERASE_REGION_TIMEOUT_PER_MB , size )
self . check_command ( " erase region " , self . ESP_ERASE_REGION , struct . pack ( ' <II ' , offset , size ) , timeout = timeout )
def read_flash_slow ( self , offset , length , progress_fn ) :
raise NotImplementedInROMError ( self , self . read_flash_slow )
def read_flash ( self , offset , length , progress_fn = None ) :
if not self . IS_STUB :
return self . read_flash_slow ( offset , length , progress_fn ) # ROM-only routine
# issue a standard bootloader command to trigger the read
self . check_command ( " read flash " , self . ESP_READ_FLASH ,
struct . pack ( ' <IIII ' ,
offset ,
length ,
self . FLASH_SECTOR_SIZE ,
64 ) )
# now we expect (length // block_size) SLIP frames with the data
data = b ' '
while len ( data ) < length :
p = self . read ( )
data + = p
if len ( data ) < length and len ( p ) < self . FLASH_SECTOR_SIZE :
raise FatalError ( ' Corrupt data, expected 0x %x bytes but received 0x %x bytes ' % ( self . FLASH_SECTOR_SIZE , len ( p ) ) )
self . write ( struct . pack ( ' <I ' , len ( data ) ) )
if progress_fn and ( len ( data ) % 1024 == 0 or len ( data ) == length ) :
progress_fn ( len ( data ) , length )
if progress_fn :
progress_fn ( len ( data ) , length )
if len ( data ) > length :
raise FatalError ( ' Read more than expected ' )
digest_frame = self . read ( )
if len ( digest_frame ) != 16 :
raise FatalError ( ' Expected digest, got: %s ' % hexify ( digest_frame ) )
expected_digest = hexify ( digest_frame ) . upper ( )
digest = hashlib . md5 ( data ) . hexdigest ( ) . upper ( )
if digest != expected_digest :
raise FatalError ( ' Digest mismatch: expected %s , got %s ' % ( expected_digest , digest ) )
return data
def flash_spi_attach ( self , hspi_arg ) :
""" Send SPI attach command to enable the SPI flash pins
ESP8266 ROM does this when you send flash_begin , ESP32 ROM
has it as a SPI command .
"""
# last 3 bytes in ESP_SPI_ATTACH argument are reserved values
arg = struct . pack ( ' <I ' , hspi_arg )
if not self . IS_STUB :
# ESP32 ROM loader takes additional 'is legacy' arg, which is not
# currently supported in the stub loader or esptool.py (as it's not usually needed.)
is_legacy = 0
arg + = struct . pack ( ' BBBB ' , is_legacy , 0 , 0 , 0 )
self . check_command ( " configure SPI flash pins " , ESP32ROM . ESP_SPI_ATTACH , arg )
def flash_set_parameters ( self , size ) :
""" Tell the ESP bootloader the parameters of the chip
Corresponds to the " flashchip " data structure that the ROM
has in RAM .
' size ' is in bytes .
All other flash parameters are currently hardcoded ( on ESP8266
these are mostly ignored by ROM code , on ESP32 I ' m not sure.)
"""
fl_id = 0
total_size = size
block_size = 64 * 1024
sector_size = 4 * 1024
page_size = 256
status_mask = 0xffff
self . check_command ( " set SPI params " , ESP32ROM . ESP_SPI_SET_PARAMS ,
struct . pack ( ' <IIIIII ' , fl_id , total_size , block_size , sector_size , page_size , status_mask ) )
2024-02-10 17:13:25 +01:00
def run_spiflash_command ( self , spiflash_command , data = b " " , read_bits = 0 , addr = None , addr_len = 0 , dummy_len = 0 ) :
2022-01-10 02:07:12 +01:00
""" Run an arbitrary SPI flash command.
This function uses the " USR_COMMAND " functionality in the ESP
SPI hardware , rather than the precanned commands supported by
hardware . So the value of spiflash_command is an actual command
byte , sent over the wire .
After writing command byte , writes ' data ' to MOSI and then
reads back ' read_bits ' of reply on MISO . Result is a number .
"""
# SPI_USR register flags
SPI_USR_COMMAND = ( 1 << 31 )
2024-02-10 17:13:25 +01:00
SPI_USR_ADDR = ( 1 << 30 )
SPI_USR_DUMMY = ( 1 << 29 )
2022-01-10 02:07:12 +01:00
SPI_USR_MISO = ( 1 << 28 )
SPI_USR_MOSI = ( 1 << 27 )
# SPI registers, base address differs ESP32* vs 8266
base = self . SPI_REG_BASE
SPI_CMD_REG = base + 0x00
2024-02-10 17:13:25 +01:00
SPI_ADDR_REG = base + 0x04
2022-01-10 02:07:12 +01:00
SPI_USR_REG = base + self . SPI_USR_OFFS
SPI_USR1_REG = base + self . SPI_USR1_OFFS
SPI_USR2_REG = base + self . SPI_USR2_OFFS
SPI_W0_REG = base + self . SPI_W0_OFFS
2024-02-10 17:13:25 +01:00
# following two registers are ESP32 and later chips only
2022-01-10 02:07:12 +01:00
if self . SPI_MOSI_DLEN_OFFS is not None :
2024-02-10 17:13:25 +01:00
# ESP32 and later chips have a more sophisticated way to set up "user" commands
2022-01-10 02:07:12 +01:00
def set_data_lengths ( mosi_bits , miso_bits ) :
SPI_MOSI_DLEN_REG = base + self . SPI_MOSI_DLEN_OFFS
SPI_MISO_DLEN_REG = base + self . SPI_MISO_DLEN_OFFS
if mosi_bits > 0 :
self . write_reg ( SPI_MOSI_DLEN_REG , mosi_bits - 1 )
if miso_bits > 0 :
self . write_reg ( SPI_MISO_DLEN_REG , miso_bits - 1 )
2024-02-10 17:13:25 +01:00
flags = 0
if dummy_len > 0 :
flags | = ( dummy_len - 1 )
if addr_len > 0 :
flags | = ( addr_len - 1 ) << SPI_USR_ADDR_LEN_SHIFT
if flags :
self . write_reg ( SPI_USR1_REG , flags )
2022-01-10 02:07:12 +01:00
else :
def set_data_lengths ( mosi_bits , miso_bits ) :
SPI_DATA_LEN_REG = SPI_USR1_REG
SPI_MOSI_BITLEN_S = 17
SPI_MISO_BITLEN_S = 8
mosi_mask = 0 if ( mosi_bits == 0 ) else ( mosi_bits - 1 )
miso_mask = 0 if ( miso_bits == 0 ) else ( miso_bits - 1 )
2024-02-10 17:13:25 +01:00
flags = ( miso_mask << SPI_MISO_BITLEN_S ) | ( mosi_mask << SPI_MOSI_BITLEN_S )
if dummy_len > 0 :
flags | = ( dummy_len - 1 )
if addr_len > 0 :
flags | = ( addr_len - 1 ) << SPI_USR_ADDR_LEN_SHIFT
self . write_reg ( SPI_DATA_LEN_REG , flags )
2022-01-10 02:07:12 +01:00
# SPI peripheral "command" bitmasks for SPI_CMD_REG
SPI_CMD_USR = ( 1 << 18 )
# shift values
SPI_USR2_COMMAND_LEN_SHIFT = 28
2024-02-10 17:13:25 +01:00
SPI_USR_ADDR_LEN_SHIFT = 26
2022-01-10 02:07:12 +01:00
if read_bits > 32 :
raise FatalError ( " Reading more than 32 bits back from a SPI flash operation is unsupported " )
if len ( data ) > 64 :
raise FatalError ( " Writing more than 64 bytes of data with one SPI command is unsupported " )
data_bits = len ( data ) * 8
old_spi_usr = self . read_reg ( SPI_USR_REG )
old_spi_usr2 = self . read_reg ( SPI_USR2_REG )
flags = SPI_USR_COMMAND
if read_bits > 0 :
flags | = SPI_USR_MISO
if data_bits > 0 :
flags | = SPI_USR_MOSI
2024-02-10 17:13:25 +01:00
if addr_len > 0 :
flags | = SPI_USR_ADDR
if dummy_len > 0 :
flags | = SPI_USR_DUMMY
2022-01-10 02:07:12 +01:00
set_data_lengths ( data_bits , read_bits )
self . write_reg ( SPI_USR_REG , flags )
self . write_reg ( SPI_USR2_REG ,
( 7 << SPI_USR2_COMMAND_LEN_SHIFT ) | spiflash_command )
2024-02-10 17:13:25 +01:00
if addr and addr_len > 0 :
self . write_reg ( SPI_ADDR_REG , addr )
2022-01-10 02:07:12 +01:00
if data_bits == 0 :
self . write_reg ( SPI_W0_REG , 0 ) # clear data register before we read it
else :
data = pad_to ( data , 4 , b ' \00 ' ) # pad to 32-bit multiple
words = struct . unpack ( " I " * ( len ( data ) / / 4 ) , data )
next_reg = SPI_W0_REG
for word in words :
self . write_reg ( next_reg , word )
next_reg + = 4
self . write_reg ( SPI_CMD_REG , SPI_CMD_USR )
def wait_done ( ) :
for _ in range ( 10 ) :
if ( self . read_reg ( SPI_CMD_REG ) & SPI_CMD_USR ) == 0 :
return
raise FatalError ( " SPI command did not complete in time " )
wait_done ( )
status = self . read_reg ( SPI_W0_REG )
# restore some SPI controller registers
self . write_reg ( SPI_USR_REG , old_spi_usr )
self . write_reg ( SPI_USR2_REG , old_spi_usr2 )
return status
2024-02-10 17:13:25 +01:00
def read_spiflash_sfdp ( self , addr , read_bits ) :
CMD_RDSFDP = 0x5A
return self . run_spiflash_command ( CMD_RDSFDP , read_bits = read_bits , addr = addr , addr_len = 24 , dummy_len = 8 )
2022-01-10 02:07:12 +01:00
def read_status ( self , num_bytes = 2 ) :
""" Read up to 24 bits (num_bytes) of SPI flash status register contents
via RDSR , RDSR2 , RDSR3 commands
Not all SPI flash supports all three commands . The upper 1 or 2
bytes may be 0xFF .
"""
SPIFLASH_RDSR = 0x05
SPIFLASH_RDSR2 = 0x35
SPIFLASH_RDSR3 = 0x15
status = 0
shift = 0
for cmd in [ SPIFLASH_RDSR , SPIFLASH_RDSR2 , SPIFLASH_RDSR3 ] [ 0 : num_bytes ] :
status + = self . run_spiflash_command ( cmd , read_bits = 8 ) << shift
shift + = 8
return status
def write_status ( self , new_status , num_bytes = 2 , set_non_volatile = False ) :
""" Write up to 24 bits (num_bytes) of new status register
num_bytes can be 1 , 2 or 3.
Not all flash supports the additional commands to write the
second and third byte of the status register . When writing 2
bytes , esptool also sends a 16 - byte WRSR command ( as some
flash types use this instead of WRSR2 . )
If the set_non_volatile flag is set , non - volatile bits will
be set as well as volatile ones ( WREN used instead of WEVSR ) .
"""
SPIFLASH_WRSR = 0x01
SPIFLASH_WRSR2 = 0x31
SPIFLASH_WRSR3 = 0x11
SPIFLASH_WEVSR = 0x50
SPIFLASH_WREN = 0x06
SPIFLASH_WRDI = 0x04
enable_cmd = SPIFLASH_WREN if set_non_volatile else SPIFLASH_WEVSR
# try using a 16-bit WRSR (not supported by all chips)
# this may be redundant, but shouldn't hurt
if num_bytes == 2 :
self . run_spiflash_command ( enable_cmd )
self . run_spiflash_command ( SPIFLASH_WRSR , struct . pack ( " <H " , new_status ) )
# also try using individual commands (also not supported by all chips for num_bytes 2 & 3)
for cmd in [ SPIFLASH_WRSR , SPIFLASH_WRSR2 , SPIFLASH_WRSR3 ] [ 0 : num_bytes ] :
self . run_spiflash_command ( enable_cmd )
self . run_spiflash_command ( cmd , struct . pack ( " B " , new_status & 0xFF ) )
new_status >> = 8
self . run_spiflash_command ( SPIFLASH_WRDI )
def get_crystal_freq ( self ) :
# Figure out the crystal frequency from the UART clock divider
# Returns a normalized value in integer MHz (40 or 26 are the only supported values)
#
# The logic here is:
# - We know that our baud rate and the ESP UART baud rate are roughly the same, or we couldn't communicate
# - We can read the UART clock divider register to know how the ESP derives this from the APB bus frequency
# - Multiplying these two together gives us the bus frequency which is either the crystal frequency (ESP32)
# or double the crystal frequency (ESP8266). See the self.XTAL_CLK_DIVIDER parameter for this factor.
uart_div = self . read_reg ( self . UART_CLKDIV_REG ) & self . UART_CLKDIV_MASK
est_xtal = ( self . _port . baudrate * uart_div ) / 1e6 / self . XTAL_CLK_DIVIDER
norm_xtal = 40 if est_xtal > 33 else 26
if abs ( norm_xtal - est_xtal ) > 1 :
print ( " WARNING: Detected crystal freq %.2f MHz is quite different to normalized freq %d MHz. Unsupported crystal in use? " % ( est_xtal , norm_xtal ) )
return norm_xtal
def hard_reset ( self ) :
print ( ' Hard resetting via RTS pin... ' )
self . _setRTS ( True ) # EN->LOW
time . sleep ( 0.1 )
self . _setRTS ( False )
def soft_reset ( self , stay_in_bootloader ) :
if not self . IS_STUB :
if stay_in_bootloader :
return # ROM bootloader is already in bootloader!
else :
# 'run user code' is as close to a soft reset as we can do
self . flash_begin ( 0 , 0 )
self . flash_finish ( False )
else :
if stay_in_bootloader :
# soft resetting from the stub loader
# will re-load the ROM bootloader
self . flash_begin ( 0 , 0 )
self . flash_finish ( True )
elif self . CHIP_NAME != " ESP8266 " :
raise FatalError ( " Soft resetting is currently only supported on ESP8266 " )
else :
# running user code from stub loader requires some hacks
# in the stub loader
self . command ( self . ESP_RUN_USER_CODE , wait_response = False )
2024-02-10 17:13:25 +01:00
def check_chip_id ( self ) :
try :
chip_id = self . get_chip_id ( )
if chip_id != self . IMAGE_CHIP_ID :
print ( " WARNING: Chip ID {} ( {} ) doesn ' t match expected Chip ID {} . esptool may not work correctly. "
. format ( chip_id , self . UNSUPPORTED_CHIPS . get ( chip_id , ' Unknown ' ) , self . IMAGE_CHIP_ID ) )
# Try to flash anyways by disabling stub
self . stub_is_disabled = True
except NotImplementedInROMError :
pass
2022-01-10 02:07:12 +01:00
class ESP8266ROM ( ESPLoader ) :
""" Access class for ESP8266 ROM bootloader
"""
CHIP_NAME = " ESP8266 "
IS_STUB = False
CHIP_DETECT_MAGIC_VALUE = [ 0xfff0c101 ]
# OTP ROM addresses
ESP_OTP_MAC0 = 0x3ff00050
ESP_OTP_MAC1 = 0x3ff00054
ESP_OTP_MAC3 = 0x3ff0005c
SPI_REG_BASE = 0x60000200
SPI_USR_OFFS = 0x1c
SPI_USR1_OFFS = 0x20
SPI_USR2_OFFS = 0x24
SPI_MOSI_DLEN_OFFS = None
SPI_MISO_DLEN_OFFS = None
SPI_W0_OFFS = 0x40
UART_CLKDIV_REG = 0x60000014
XTAL_CLK_DIVIDER = 2
FLASH_SIZES = {
' 512KB ' : 0x00 ,
' 256KB ' : 0x10 ,
' 1MB ' : 0x20 ,
' 2MB ' : 0x30 ,
' 4MB ' : 0x40 ,
' 2MB-c1 ' : 0x50 ,
' 4MB-c1 ' : 0x60 ,
' 8MB ' : 0x80 ,
' 16MB ' : 0x90 ,
}
2024-02-10 17:13:25 +01:00
FLASH_FREQUENCY = {
' 80m ' : 0xf ,
' 40m ' : 0x0 ,
' 26m ' : 0x1 ,
' 20m ' : 0x2 ,
}
2022-01-10 02:07:12 +01:00
BOOTLOADER_FLASH_OFFSET = 0
MEMORY_MAP = [ [ 0x3FF00000 , 0x3FF00010 , " DPORT " ] ,
[ 0x3FFE8000 , 0x40000000 , " DRAM " ] ,
[ 0x40100000 , 0x40108000 , " IRAM " ] ,
[ 0x40201010 , 0x402E1010 , " IROM " ] ]
def get_efuses ( self ) :
# Return the 128 bits of ESP8266 efuse as a single Python integer
result = self . read_reg ( 0x3ff0005c ) << 96
result | = self . read_reg ( 0x3ff00058 ) << 64
result | = self . read_reg ( 0x3ff00054 ) << 32
result | = self . read_reg ( 0x3ff00050 )
return result
def _get_flash_size ( self , efuses ) :
# rX_Y = EFUSE_DATA_OUTX[Y]
r0_4 = ( efuses & ( 1 << 4 ) ) != 0
r3_25 = ( efuses & ( 1 << 121 ) ) != 0
r3_26 = ( efuses & ( 1 << 122 ) ) != 0
r3_27 = ( efuses & ( 1 << 123 ) ) != 0
if r0_4 and not r3_25 :
if not r3_27 and not r3_26 :
return 1
elif not r3_27 and r3_26 :
return 2
if not r0_4 and r3_25 :
if not r3_27 and not r3_26 :
return 2
elif not r3_27 and r3_26 :
return 4
return - 1
def get_chip_description ( self ) :
efuses = self . get_efuses ( )
is_8285 = ( efuses & ( ( 1 << 4 ) | 1 << 80 ) ) != 0 # One or the other efuse bit is set for ESP8285
if is_8285 :
flash_size = self . _get_flash_size ( efuses )
max_temp = ( efuses & ( 1 << 5 ) ) != 0 # This efuse bit identifies the max flash temperature
chip_name = {
1 : " ESP8285H08 " if max_temp else " ESP8285N08 " ,
2 : " ESP8285H16 " if max_temp else " ESP8285N16 "
} . get ( flash_size , " ESP8285 " )
return chip_name
return " ESP8266EX "
def get_chip_features ( self ) :
features = [ " WiFi " ]
if " ESP8285 " in self . get_chip_description ( ) :
features + = [ " Embedded Flash " ]
return features
def flash_spi_attach ( self , hspi_arg ) :
if self . IS_STUB :
super ( ESP8266ROM , self ) . flash_spi_attach ( hspi_arg )
else :
# ESP8266 ROM has no flash_spi_attach command in serial protocol,
# but flash_begin will do it
self . flash_begin ( 0 , 0 )
def flash_set_parameters ( self , size ) :
# not implemented in ROM, but OK to silently skip for ROM
if self . IS_STUB :
super ( ESP8266ROM , self ) . flash_set_parameters ( size )
def chip_id ( self ) :
""" Read Chip ID from efuse - the equivalent of the SDK system_get_chip_id() function """
id0 = self . read_reg ( self . ESP_OTP_MAC0 )
id1 = self . read_reg ( self . ESP_OTP_MAC1 )
return ( id0 >> 24 ) | ( ( id1 & MAX_UINT24 ) << 8 )
def read_mac ( self ) :
""" Read MAC from OTP ROM """
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 FatalError ( " Unknown OUI " )
return oui + ( ( mac1 >> 8 ) & 0xff , mac1 & 0xff , ( mac0 >> 24 ) & 0xff )
def get_erase_size ( self , offset , size ) :
""" Calculate an erase size given a specific size in bytes.
Provides a workaround for the bootloader erase bug . """
sectors_per_block = 16
sector_size = self . FLASH_SECTOR_SIZE
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 :
return ( num_sectors + 1 ) / / 2 * sector_size
else :
return ( num_sectors - head_sectors ) * sector_size
def override_vddsdio ( self , new_voltage ) :
raise NotImplementedInROMError ( " Overriding VDDSDIO setting only applies to ESP32 " )
class ESP8266StubLoader ( ESP8266ROM ) :
""" Access class for ESP8266 stub loader, runs on top of ROM.
"""
FLASH_WRITE_SIZE = 0x4000 # matches MAX_WRITE_BLOCK in stub_loader.c
IS_STUB = True
def __init__ ( self , rom_loader ) :
self . secure_download_mode = rom_loader . secure_download_mode
self . _port = rom_loader . _port
self . _trace_enabled = rom_loader . _trace_enabled
self . flush_input ( ) # resets _slip_reader
def get_erase_size ( self , offset , size ) :
return size # stub doesn't have same size bug as ROM loader
ESP8266ROM . STUB_CLASS = ESP8266StubLoader
class ESP32ROM ( ESPLoader ) :
""" Access class for ESP32 ROM bootloader
"""
CHIP_NAME = " ESP32 "
IMAGE_CHIP_ID = 0
IS_STUB = False
2024-02-10 17:13:25 +01:00
FPGA_SLOW_BOOT = True
2022-01-10 02:07:12 +01:00
CHIP_DETECT_MAGIC_VALUE = [ 0x00f01d83 ]
IROM_MAP_START = 0x400d0000
IROM_MAP_END = 0x40400000
DROM_MAP_START = 0x3F400000
DROM_MAP_END = 0x3F800000
# ESP32 uses a 4 byte status reply
STATUS_BYTES_LENGTH = 4
SPI_REG_BASE = 0x3ff42000
SPI_USR_OFFS = 0x1c
SPI_USR1_OFFS = 0x20
SPI_USR2_OFFS = 0x24
SPI_MOSI_DLEN_OFFS = 0x28
SPI_MISO_DLEN_OFFS = 0x2c
EFUSE_RD_REG_BASE = 0x3ff5a000
EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT_REG = EFUSE_RD_REG_BASE + 0x18
EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT = ( 1 << 7 ) # EFUSE_RD_DISABLE_DL_ENCRYPT
DR_REG_SYSCON_BASE = 0x3ff66000
2024-02-10 17:13:25 +01:00
APB_CTL_DATE_ADDR = DR_REG_SYSCON_BASE + 0x7C
APB_CTL_DATE_V = 0x1
APB_CTL_DATE_S = 31
2022-01-10 02:07:12 +01:00
SPI_W0_OFFS = 0x80
UART_CLKDIV_REG = 0x3ff40014
XTAL_CLK_DIVIDER = 1
FLASH_SIZES = {
' 1MB ' : 0x00 ,
' 2MB ' : 0x10 ,
' 4MB ' : 0x20 ,
' 8MB ' : 0x30 ,
2024-02-10 17:13:25 +01:00
' 16MB ' : 0x40 ,
' 32MB ' : 0x50 ,
' 64MB ' : 0x60 ,
' 128MB ' : 0x70
}
FLASH_FREQUENCY = {
' 80m ' : 0xf ,
' 40m ' : 0x0 ,
' 26m ' : 0x1 ,
' 20m ' : 0x2 ,
2022-01-10 02:07:12 +01:00
}
BOOTLOADER_FLASH_OFFSET = 0x1000
OVERRIDE_VDDSDIO_CHOICES = [ " 1.8V " , " 1.9V " , " OFF " ]
MEMORY_MAP = [ [ 0x00000000 , 0x00010000 , " PADDING " ] ,
[ 0x3F400000 , 0x3F800000 , " DROM " ] ,
[ 0x3F800000 , 0x3FC00000 , " EXTRAM_DATA " ] ,
[ 0x3FF80000 , 0x3FF82000 , " RTC_DRAM " ] ,
[ 0x3FF90000 , 0x40000000 , " BYTE_ACCESSIBLE " ] ,
[ 0x3FFAE000 , 0x40000000 , " DRAM " ] ,
[ 0x3FFE0000 , 0x3FFFFFFC , " DIRAM_DRAM " ] ,
[ 0x40000000 , 0x40070000 , " IROM " ] ,
[ 0x40070000 , 0x40078000 , " CACHE_PRO " ] ,
[ 0x40078000 , 0x40080000 , " CACHE_APP " ] ,
[ 0x40080000 , 0x400A0000 , " IRAM " ] ,
[ 0x400A0000 , 0x400BFFFC , " DIRAM_IRAM " ] ,
[ 0x400C0000 , 0x400C2000 , " RTC_IRAM " ] ,
[ 0x400D0000 , 0x40400000 , " IROM " ] ,
[ 0x50000000 , 0x50002000 , " RTC_DATA " ] ]
FLASH_ENCRYPTED_WRITE_ALIGN = 32
""" Try to read the BLOCK1 (encryption key) and check if it is valid """
def is_flash_encryption_key_valid ( self ) :
""" Bit 0 of efuse_rd_disable[3:0] is mapped to BLOCK1
this bit is at position 16 in EFUSE_BLK0_RDATA0_REG """
word0 = self . read_efuse ( 0 )
rd_disable = ( word0 >> 16 ) & 0x1
# reading of BLOCK1 is NOT ALLOWED so we assume valid key is programmed
if rd_disable :
return True
else :
# reading of BLOCK1 is ALLOWED so we will read and verify for non-zero.
# When ESP32 has not generated AES/encryption key in BLOCK1, the contents will be readable and 0.
# If the flash encryption is enabled it is expected to have a valid non-zero key. We break out on
# first occurance of non-zero value
key_word = [ 0 ] * 7
for i in range ( len ( key_word ) ) :
key_word [ i ] = self . read_efuse ( 14 + i )
# key is non-zero so break & return
if key_word [ i ] != 0 :
return True
return False
def get_flash_crypt_config ( self ) :
""" For flash encryption related commands we need to make sure
user has programmed all the relevant efuse correctly so before
writing encrypted write_flash_encrypt esptool will verify the values
of flash_crypt_config to be non zero if they are not read
protected . If the values are zero a warning will be printed
bit 3 in efuse_rd_disable [ 3 : 0 ] is mapped to flash_crypt_config
this bit is at position 19 in EFUSE_BLK0_RDATA0_REG """
word0 = self . read_efuse ( 0 )
rd_disable = ( word0 >> 19 ) & 0x1
if rd_disable == 0 :
""" we can read the flash_crypt_config efuse value
so go & read it ( EFUSE_BLK0_RDATA5_REG [ 31 : 28 ] ) """
word5 = self . read_efuse ( 5 )
word5 = ( word5 >> 28 ) & 0xF
return word5
else :
# if read of the efuse is disabled we assume it is set correctly
return 0xF
def get_encrypted_download_disabled ( self ) :
if self . read_reg ( self . EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT_REG ) & self . EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT :
return True
else :
return False
def get_pkg_version ( self ) :
word3 = self . read_efuse ( 3 )
pkg_version = ( word3 >> 9 ) & 0x07
pkg_version + = ( ( word3 >> 2 ) & 0x1 ) << 3
return pkg_version
2024-02-10 17:13:25 +01:00
# Returns new version format based on major and minor versions
def get_chip_full_revision ( self ) :
return self . get_major_chip_version ( ) * 100 + self . get_minor_chip_version ( )
# Returns old version format (ECO number). Use the new format get_chip_full_revision().
2022-01-10 02:07:12 +01:00
def get_chip_revision ( self ) :
2024-02-10 17:13:25 +01:00
return self . get_major_chip_version ( )
def get_minor_chip_version ( self ) :
return ( self . read_efuse ( 5 ) >> 24 ) & 0x3
def get_major_chip_version ( self ) :
rev_bit0 = ( self . read_efuse ( 3 ) >> 15 ) & 0x1
rev_bit1 = ( self . read_efuse ( 5 ) >> 20 ) & 0x1
apb_ctl_date = self . read_reg ( self . APB_CTL_DATE_ADDR )
rev_bit2 = ( apb_ctl_date >> self . APB_CTL_DATE_S ) & self . APB_CTL_DATE_V
combine_value = ( rev_bit2 << 2 ) | ( rev_bit1 << 1 ) | rev_bit0
revision = {
0 : 0 ,
1 : 1 ,
3 : 2 ,
7 : 3 ,
} . get ( combine_value , 0 )
return revision
2022-01-10 02:07:12 +01:00
def get_chip_description ( self ) :
pkg_version = self . get_pkg_version ( )
2024-02-10 17:13:25 +01:00
major_rev = self . get_major_chip_version ( )
minor_rev = self . get_minor_chip_version ( )
rev3 = major_rev == 3
2022-01-10 02:07:12 +01:00
single_core = self . read_efuse ( 3 ) & ( 1 << 0 ) # CHIP_VER DIS_APP_CPU
chip_name = {
0 : " ESP32-S0WDQ6 " if single_core else " ESP32-D0WDQ6 " ,
1 : " ESP32-S0WD " if single_core else " ESP32-D0WD " ,
2 : " ESP32-D2WD " ,
4 : " ESP32-U4WDH " ,
5 : " ESP32-PICO-V3 " if rev3 else " ESP32-PICO-D4 " ,
6 : " ESP32-PICO-V3-02 " ,
2024-02-10 17:13:25 +01:00
7 : " ESP32-D0WDR2-V3 " ,
2022-01-10 02:07:12 +01:00
} . get ( pkg_version , " unknown ESP32 " )
# ESP32-D0WD-V3, ESP32-D0WDQ6-V3
if chip_name . startswith ( " ESP32-D0WD " ) and rev3 :
chip_name + = " -V3 "
2024-02-10 17:13:25 +01:00
return " %s (revision v %d . %d ) " % ( chip_name , major_rev , minor_rev )
2022-01-10 02:07:12 +01:00
def get_chip_features ( self ) :
features = [ " WiFi " ]
word3 = self . read_efuse ( 3 )
# names of variables in this section are lowercase
# versions of EFUSE names as documented in TRM and
# ESP-IDF efuse_reg.h
chip_ver_dis_bt = word3 & ( 1 << 1 )
if chip_ver_dis_bt == 0 :
features + = [ " BT " ]
chip_ver_dis_app_cpu = word3 & ( 1 << 0 )
if chip_ver_dis_app_cpu :
features + = [ " Single Core " ]
else :
features + = [ " Dual Core " ]
chip_cpu_freq_rated = word3 & ( 1 << 13 )
if chip_cpu_freq_rated :
chip_cpu_freq_low = word3 & ( 1 << 12 )
if chip_cpu_freq_low :
features + = [ " 160MHz " ]
else :
features + = [ " 240MHz " ]
pkg_version = self . get_pkg_version ( )
if pkg_version in [ 2 , 4 , 5 , 6 ] :
features + = [ " Embedded Flash " ]
if pkg_version == 6 :
features + = [ " Embedded PSRAM " ]
word4 = self . read_efuse ( 4 )
adc_vref = ( word4 >> 8 ) & 0x1F
if adc_vref :
features + = [ " VRef calibration in efuse " ]
blk3_part_res = word3 >> 14 & 0x1
if blk3_part_res :
features + = [ " BLK3 partially reserved " ]
word6 = self . read_efuse ( 6 )
coding_scheme = word6 & 0x3
features + = [ " Coding Scheme %s " % {
0 : " None " ,
1 : " 3/4 " ,
2 : " Repeat (UNSUPPORTED) " ,
3 : " Invalid " } [ coding_scheme ] ]
return features
def read_efuse ( self , n ) :
""" Read the nth word of the ESP3x EFUSE region. """
return self . read_reg ( self . EFUSE_RD_REG_BASE + ( 4 * n ) )
def chip_id ( self ) :
raise NotSupportedError ( self , " chip_id " )
def read_mac ( self ) :
""" Read MAC from EFUSE region """
words = [ self . read_efuse ( 2 ) , self . read_efuse ( 1 ) ]
bitstring = struct . pack ( " >II " , * words )
bitstring = bitstring [ 2 : 8 ] # trim the 2 byte CRC
try :
return tuple ( ord ( b ) for b in bitstring )
except TypeError : # Python 3, bitstring elements are already bytes
return tuple ( bitstring )
def get_erase_size ( self , offset , size ) :
return size
def override_vddsdio ( self , new_voltage ) :
new_voltage = new_voltage . upper ( )
if new_voltage not in self . OVERRIDE_VDDSDIO_CHOICES :
raise FatalError ( " The only accepted VDDSDIO overrides are ' 1.8V ' , ' 1.9V ' and ' OFF ' " )
RTC_CNTL_SDIO_CONF_REG = 0x3ff48074
RTC_CNTL_XPD_SDIO_REG = ( 1 << 31 )
RTC_CNTL_DREFH_SDIO_M = ( 3 << 29 )
RTC_CNTL_DREFM_SDIO_M = ( 3 << 27 )
RTC_CNTL_DREFL_SDIO_M = ( 3 << 25 )
# RTC_CNTL_SDIO_TIEH = (1 << 23) # not used here, setting TIEH=1 would set 3.3V output, not safe for esptool.py to do
RTC_CNTL_SDIO_FORCE = ( 1 << 22 )
RTC_CNTL_SDIO_PD_EN = ( 1 << 21 )
reg_val = RTC_CNTL_SDIO_FORCE # override efuse setting
reg_val | = RTC_CNTL_SDIO_PD_EN
if new_voltage != " OFF " :
reg_val | = RTC_CNTL_XPD_SDIO_REG # enable internal LDO
if new_voltage == " 1.9V " :
reg_val | = ( RTC_CNTL_DREFH_SDIO_M | RTC_CNTL_DREFM_SDIO_M | RTC_CNTL_DREFL_SDIO_M ) # boost voltage
self . write_reg ( RTC_CNTL_SDIO_CONF_REG , reg_val )
print ( " VDDSDIO regulator set to %s " % new_voltage )
def read_flash_slow ( self , offset , length , progress_fn ) :
BLOCK_LEN = 64 # ROM read limit per command (this limit is why it's so slow)
data = b ' '
while len ( data ) < length :
block_len = min ( BLOCK_LEN , length - len ( data ) )
r = self . check_command ( " read flash block " , self . ESP_READ_FLASH_SLOW ,
struct . pack ( ' <II ' , offset + len ( data ) , block_len ) )
if len ( r ) < block_len :
raise FatalError ( " Expected %d byte block, got %d bytes. Serial errors? " % ( block_len , len ( r ) ) )
data + = r [ : block_len ] # command always returns 64 byte buffer, regardless of how many bytes were actually read from flash
if progress_fn and ( len ( data ) % 1024 == 0 or len ( data ) == length ) :
progress_fn ( len ( data ) , length )
return data
class ESP32S2ROM ( ESP32ROM ) :
CHIP_NAME = " ESP32-S2 "
IMAGE_CHIP_ID = 2
2024-02-10 17:13:25 +01:00
FPGA_SLOW_BOOT = False
2022-01-10 02:07:12 +01:00
IROM_MAP_START = 0x40080000
IROM_MAP_END = 0x40b80000
DROM_MAP_START = 0x3F000000
DROM_MAP_END = 0x3F3F0000
CHIP_DETECT_MAGIC_VALUE = [ 0x000007c6 ]
SPI_REG_BASE = 0x3f402000
SPI_USR_OFFS = 0x18
SPI_USR1_OFFS = 0x1c
SPI_USR2_OFFS = 0x20
SPI_MOSI_DLEN_OFFS = 0x24
SPI_MISO_DLEN_OFFS = 0x28
SPI_W0_OFFS = 0x58
MAC_EFUSE_REG = 0x3f41A044 # ESP32-S2 has special block for MAC efuses
UART_CLKDIV_REG = 0x3f400014
FLASH_ENCRYPTED_WRITE_ALIGN = 16
# todo: use espefuse APIs to get this info
EFUSE_BASE = 0x3f41A000
EFUSE_RD_REG_BASE = EFUSE_BASE + 0x030 # BLOCK0 read base address
2024-02-10 17:13:25 +01:00
EFUSE_BLOCK1_ADDR = EFUSE_BASE + 0x044
EFUSE_BLOCK2_ADDR = EFUSE_BASE + 0x05C
2022-01-10 02:07:12 +01:00
EFUSE_PURPOSE_KEY0_REG = EFUSE_BASE + 0x34
EFUSE_PURPOSE_KEY0_SHIFT = 24
EFUSE_PURPOSE_KEY1_REG = EFUSE_BASE + 0x34
EFUSE_PURPOSE_KEY1_SHIFT = 28
EFUSE_PURPOSE_KEY2_REG = EFUSE_BASE + 0x38
EFUSE_PURPOSE_KEY2_SHIFT = 0
EFUSE_PURPOSE_KEY3_REG = EFUSE_BASE + 0x38
EFUSE_PURPOSE_KEY3_SHIFT = 4
EFUSE_PURPOSE_KEY4_REG = EFUSE_BASE + 0x38
EFUSE_PURPOSE_KEY4_SHIFT = 8
EFUSE_PURPOSE_KEY5_REG = EFUSE_BASE + 0x38
EFUSE_PURPOSE_KEY5_SHIFT = 12
EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT_REG = EFUSE_RD_REG_BASE
EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT = 1 << 19
PURPOSE_VAL_XTS_AES256_KEY_1 = 2
PURPOSE_VAL_XTS_AES256_KEY_2 = 3
PURPOSE_VAL_XTS_AES128_KEY = 4
UARTDEV_BUF_NO = 0x3ffffd14 # Variable in ROM .bss which indicates the port in use
UARTDEV_BUF_NO_USB = 2 # Value of the above variable indicating that USB is in use
USB_RAM_BLOCK = 0x800 # Max block size USB CDC is used
GPIO_STRAP_REG = 0x3f404038
GPIO_STRAP_SPI_BOOT_MASK = 0x8 # Not download mode
RTC_CNTL_OPTION1_REG = 0x3f408128
RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK = 0x1 # Is download mode forced over USB?
MEMORY_MAP = [ [ 0x00000000 , 0x00010000 , " PADDING " ] ,
[ 0x3F000000 , 0x3FF80000 , " DROM " ] ,
[ 0x3F500000 , 0x3FF80000 , " EXTRAM_DATA " ] ,
[ 0x3FF9E000 , 0x3FFA0000 , " RTC_DRAM " ] ,
[ 0x3FF9E000 , 0x40000000 , " BYTE_ACCESSIBLE " ] ,
[ 0x3FF9E000 , 0x40072000 , " MEM_INTERNAL " ] ,
[ 0x3FFB0000 , 0x40000000 , " DRAM " ] ,
[ 0x40000000 , 0x4001A100 , " IROM_MASK " ] ,
[ 0x40020000 , 0x40070000 , " IRAM " ] ,
[ 0x40070000 , 0x40072000 , " RTC_IRAM " ] ,
[ 0x40080000 , 0x40800000 , " IROM " ] ,
[ 0x50000000 , 0x50002000 , " RTC_DATA " ] ]
2024-02-10 17:13:25 +01:00
# Returns old version format (ECO number). Use the new format get_chip_full_revision().
def get_chip_revision ( self ) :
return self . get_major_chip_version ( )
2022-01-10 02:07:12 +01:00
def get_pkg_version ( self ) :
2024-02-10 17:13:25 +01:00
num_word = 4
return ( self . read_reg ( self . EFUSE_BLOCK1_ADDR + ( 4 * num_word ) ) >> 0 ) & 0x0F
def get_minor_chip_version ( self ) :
hi_num_word = 3
hi = ( self . read_reg ( self . EFUSE_BLOCK1_ADDR + ( 4 * hi_num_word ) ) >> 20 ) & 0x01
low_num_word = 4
low = ( self . read_reg ( self . EFUSE_BLOCK1_ADDR + ( 4 * low_num_word ) ) >> 4 ) & 0x07
return ( hi << 3 ) + low
def get_major_chip_version ( self ) :
2022-01-10 02:07:12 +01:00
num_word = 3
2024-02-10 17:13:25 +01:00
return ( self . read_reg ( self . EFUSE_BLOCK1_ADDR + ( 4 * num_word ) ) >> 18 ) & 0x03
def get_flash_version ( self ) :
num_word = 3
return ( self . read_reg ( self . EFUSE_BLOCK1_ADDR + ( 4 * num_word ) ) >> 21 ) & 0x0F
def get_psram_version ( self ) :
num_word = 3
return ( self . read_reg ( self . EFUSE_BLOCK1_ADDR + ( 4 * num_word ) ) >> 28 ) & 0x0F
def get_block2_version ( self ) :
# BLK_VERSION_MINOR
num_word = 4
return ( self . read_reg ( self . EFUSE_BLOCK2_ADDR + ( 4 * num_word ) ) >> 4 ) & 0x07
2022-01-10 02:07:12 +01:00
def get_chip_description ( self ) :
chip_name = {
0 : " ESP32-S2 " ,
2024-02-10 17:13:25 +01:00
1 : " ESP32-S2FH2 " ,
2 : " ESP32-S2FH4 " ,
102 : " ESP32-S2FNR2 " ,
100 : " ESP32-S2R2 " ,
} . get ( self . get_flash_version ( ) + self . get_psram_version ( ) * 100 , " unknown ESP32-S2 " )
2022-01-10 02:07:12 +01:00
2024-02-10 17:13:25 +01:00
major_rev = self . get_major_chip_version ( )
minor_rev = self . get_minor_chip_version ( )
return " %s (revision v %d . %d ) " % ( chip_name , major_rev , minor_rev )
2022-01-10 02:07:12 +01:00
def get_chip_features ( self ) :
features = [ " WiFi " ]
if self . secure_download_mode :
features + = [ " Secure Download Mode Enabled " ]
2024-02-10 17:13:25 +01:00
flash_version = {
0 : " No Embedded Flash " ,
1 : " Embedded Flash 2MB " ,
2 : " Embedded Flash 4MB " ,
} . get ( self . get_flash_version ( ) , " Unknown Embedded Flash " )
features + = [ flash_version ]
psram_version = {
0 : " No Embedded PSRAM " ,
1 : " Embedded PSRAM 2MB " ,
2 : " Embedded PSRAM 4MB " ,
} . get ( self . get_psram_version ( ) , " Unknown Embedded PSRAM " )
features + = [ psram_version ]
block2_version = {
0 : " No calibration in BLK2 of efuse " ,
1 : " ADC and temperature sensor calibration in BLK2 of efuse V1 " ,
2 : " ADC and temperature sensor calibration in BLK2 of efuse V2 " ,
} . get ( self . get_block2_version ( ) , " Unknown Calibration in BLK2 " )
features + = [ block2_version ]
2022-01-10 02:07:12 +01:00
return features
def get_crystal_freq ( self ) :
# ESP32-S2 XTAL is fixed to 40MHz
return 40
def override_vddsdio ( self , new_voltage ) :
raise NotImplementedInROMError ( " VDD_SDIO overrides are not supported for ESP32-S2 " )
def read_mac ( self ) :
mac0 = self . read_reg ( self . MAC_EFUSE_REG )
mac1 = self . read_reg ( self . MAC_EFUSE_REG + 4 ) # only bottom 16 bits are MAC
bitstring = struct . pack ( " >II " , mac1 , mac0 ) [ 2 : ]
try :
return tuple ( ord ( b ) for b in bitstring )
except TypeError : # Python 3, bitstring elements are already bytes
return tuple ( bitstring )
def get_flash_crypt_config ( self ) :
return None # doesn't exist on ESP32-S2
def get_key_block_purpose ( self , key_block ) :
if key_block < 0 or key_block > 5 :
raise FatalError ( " Valid key block numbers must be in range 0-5 " )
reg , shift = [ ( self . EFUSE_PURPOSE_KEY0_REG , self . EFUSE_PURPOSE_KEY0_SHIFT ) ,
( self . EFUSE_PURPOSE_KEY1_REG , self . EFUSE_PURPOSE_KEY1_SHIFT ) ,
( self . EFUSE_PURPOSE_KEY2_REG , self . EFUSE_PURPOSE_KEY2_SHIFT ) ,
( self . EFUSE_PURPOSE_KEY3_REG , self . EFUSE_PURPOSE_KEY3_SHIFT ) ,
( self . EFUSE_PURPOSE_KEY4_REG , self . EFUSE_PURPOSE_KEY4_SHIFT ) ,
( self . EFUSE_PURPOSE_KEY5_REG , self . EFUSE_PURPOSE_KEY5_SHIFT ) ] [ key_block ]
return ( self . read_reg ( reg ) >> shift ) & 0xF
def is_flash_encryption_key_valid ( self ) :
# Need to see either an AES-128 key or two AES-256 keys
purposes = [ self . get_key_block_purpose ( b ) for b in range ( 6 ) ]
if any ( p == self . PURPOSE_VAL_XTS_AES128_KEY for p in purposes ) :
return True
return any ( p == self . PURPOSE_VAL_XTS_AES256_KEY_1 for p in purposes ) \
and any ( p == self . PURPOSE_VAL_XTS_AES256_KEY_2 for p in purposes )
def uses_usb ( self , _cache = [ ] ) :
if self . secure_download_mode :
return False # can't detect native USB in secure download mode
if not _cache :
buf_no = self . read_reg ( self . UARTDEV_BUF_NO ) & 0xff
_cache . append ( buf_no == self . UARTDEV_BUF_NO_USB )
return _cache [ 0 ]
def _post_connect ( self ) :
if self . uses_usb ( ) :
self . ESP_RAM_BLOCK = self . USB_RAM_BLOCK
def _check_if_can_reset ( self ) :
"""
Check the strapping register to see if we can reset out of download mode .
"""
if os . getenv ( " ESPTOOL_TESTING " ) is not None :
print ( " ESPTOOL_TESTING is set, ignoring strapping mode check " )
# Esptool tests over USB CDC run with GPIO0 strapped low, don't complain in this case.
return
strap_reg = self . read_reg ( self . GPIO_STRAP_REG )
force_dl_reg = self . read_reg ( self . RTC_CNTL_OPTION1_REG )
if strap_reg & self . GPIO_STRAP_SPI_BOOT_MASK == 0 and force_dl_reg & self . RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK == 0 :
2024-02-10 17:13:25 +01:00
print ( " WARNING: {} chip was placed into download mode using GPIO0. \n "
2022-01-10 02:07:12 +01:00
" esptool.py can not exit the download mode over USB. "
" To run the app, reset the chip manually. \n "
2024-02-10 17:13:25 +01:00
" To suppress this note, set --after option to ' no_reset ' . " . format ( self . get_chip_description ( ) ) )
2022-01-10 02:07:12 +01:00
raise SystemExit ( 1 )
def hard_reset ( self ) :
if self . uses_usb ( ) :
self . _check_if_can_reset ( )
2024-02-10 17:13:25 +01:00
print ( ' Hard resetting via RTS pin... ' )
2022-01-10 02:07:12 +01:00
self . _setRTS ( True ) # EN->LOW
if self . uses_usb ( ) :
# Give the chip some time to come out of reset, to be able to handle further DTR/RTS transitions
time . sleep ( 0.2 )
self . _setRTS ( False )
time . sleep ( 0.2 )
else :
2024-02-10 17:13:25 +01:00
time . sleep ( 0.1 )
2022-01-10 02:07:12 +01:00
self . _setRTS ( False )
class ESP32S3ROM ( ESP32ROM ) :
CHIP_NAME = " ESP32-S3 "
2024-02-10 17:13:25 +01:00
IMAGE_CHIP_ID = 9
CHIP_DETECT_MAGIC_VALUE = [ 0x9 ]
BOOTLOADER_FLASH_OFFSET = 0x0
FPGA_SLOW_BOOT = False
2022-01-10 02:07:12 +01:00
IROM_MAP_START = 0x42000000
IROM_MAP_END = 0x44000000
DROM_MAP_START = 0x3c000000
DROM_MAP_END = 0x3e000000
UART_DATE_REG_ADDR = 0x60000080
SPI_REG_BASE = 0x60002000
SPI_USR_OFFS = 0x18
SPI_USR1_OFFS = 0x1c
SPI_USR2_OFFS = 0x20
SPI_MOSI_DLEN_OFFS = 0x24
SPI_MISO_DLEN_OFFS = 0x28
SPI_W0_OFFS = 0x58
FLASH_ENCRYPTED_WRITE_ALIGN = 16
# todo: use espefuse APIs to get this info
2024-02-10 17:13:25 +01:00
EFUSE_BASE = 0x60007000 # BLOCK0 read base address
2022-01-10 02:07:12 +01:00
MAC_EFUSE_REG = EFUSE_BASE + 0x044
2024-02-10 17:13:25 +01:00
EFUSE_BLOCK1_ADDR = EFUSE_BASE + 0x44
EFUSE_BLOCK2_ADDR = EFUSE_BASE + 0x5C
2022-01-10 02:07:12 +01:00
EFUSE_RD_REG_BASE = EFUSE_BASE + 0x030 # BLOCK0 read base address
EFUSE_PURPOSE_KEY0_REG = EFUSE_BASE + 0x34
EFUSE_PURPOSE_KEY0_SHIFT = 24
EFUSE_PURPOSE_KEY1_REG = EFUSE_BASE + 0x34
EFUSE_PURPOSE_KEY1_SHIFT = 28
EFUSE_PURPOSE_KEY2_REG = EFUSE_BASE + 0x38
EFUSE_PURPOSE_KEY2_SHIFT = 0
EFUSE_PURPOSE_KEY3_REG = EFUSE_BASE + 0x38
EFUSE_PURPOSE_KEY3_SHIFT = 4
EFUSE_PURPOSE_KEY4_REG = EFUSE_BASE + 0x38
EFUSE_PURPOSE_KEY4_SHIFT = 8
EFUSE_PURPOSE_KEY5_REG = EFUSE_BASE + 0x38
EFUSE_PURPOSE_KEY5_SHIFT = 12
EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT_REG = EFUSE_RD_REG_BASE
EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT = 1 << 20
PURPOSE_VAL_XTS_AES256_KEY_1 = 2
PURPOSE_VAL_XTS_AES256_KEY_2 = 3
PURPOSE_VAL_XTS_AES128_KEY = 4
2024-02-10 17:13:25 +01:00
UARTDEV_BUF_NO = 0x3fcef14c # Variable in ROM .bss which indicates the port in use
UARTDEV_BUF_NO_USB = 3 # Value of the above variable indicating that USB is in use
USB_RAM_BLOCK = 0x800 # Max block size USB CDC is used
2022-01-10 02:07:12 +01:00
GPIO_STRAP_REG = 0x60004038
2024-02-10 17:13:25 +01:00
GPIO_STRAP_SPI_BOOT_MASK = 0x8 # Not download mode
RTC_CNTL_OPTION1_REG = 0x6000812C
RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK = 0x1 # Is download mode forced over USB?
UART_CLKDIV_REG = 0x60000014
2022-01-10 02:07:12 +01:00
MEMORY_MAP = [ [ 0x00000000 , 0x00010000 , " PADDING " ] ,
[ 0x3C000000 , 0x3D000000 , " DROM " ] ,
[ 0x3D000000 , 0x3E000000 , " EXTRAM_DATA " ] ,
[ 0x600FE000 , 0x60100000 , " RTC_DRAM " ] ,
[ 0x3FC88000 , 0x3FD00000 , " BYTE_ACCESSIBLE " ] ,
[ 0x3FC88000 , 0x403E2000 , " MEM_INTERNAL " ] ,
[ 0x3FC88000 , 0x3FD00000 , " DRAM " ] ,
[ 0x40000000 , 0x4001A100 , " IROM_MASK " ] ,
[ 0x40370000 , 0x403E0000 , " IRAM " ] ,
[ 0x600FE000 , 0x60100000 , " RTC_IRAM " ] ,
[ 0x42000000 , 0x42800000 , " IROM " ] ,
[ 0x50000000 , 0x50002000 , " RTC_DATA " ] ]
2024-02-10 17:13:25 +01:00
# Returns old version format (ECO number). Use the new format get_chip_full_revision().
def get_chip_revision ( self ) :
return self . get_minor_chip_version ( )
def get_pkg_version ( self ) :
num_word = 3
return ( self . read_reg ( self . EFUSE_BLOCK1_ADDR + ( 4 * num_word ) ) >> 21 ) & 0x07
def is_eco0 ( self , minor_raw ) :
# Workaround: The major version field was allocated to other purposes
# when block version is v1.1.
# Luckily only chip v0.0 have this kind of block version and efuse usage.
return (
( minor_raw & 0x7 ) == 0 and self . get_blk_version_major ( ) == 1 and self . get_blk_version_minor ( ) == 1
)
def get_minor_chip_version ( self ) :
minor_raw = self . get_raw_minor_chip_version ( )
if self . is_eco0 ( minor_raw ) :
return 0
return minor_raw
def get_raw_minor_chip_version ( self ) :
hi_num_word = 5
hi = ( self . read_reg ( self . EFUSE_BLOCK1_ADDR + ( 4 * hi_num_word ) ) >> 23 ) & 0x01
low_num_word = 3
low = ( self . read_reg ( self . EFUSE_BLOCK1_ADDR + ( 4 * low_num_word ) ) >> 18 ) & 0x07
return ( hi << 3 ) + low
def get_blk_version_major ( self ) :
num_word = 4
return ( self . read_reg ( self . EFUSE_BLOCK2_ADDR + ( 4 * num_word ) ) >> 0 ) & 0x03
def get_blk_version_minor ( self ) :
num_word = 3
return ( self . read_reg ( self . EFUSE_BLOCK1_ADDR + ( 4 * num_word ) ) >> 24 ) & 0x07
def get_major_chip_version ( self ) :
minor_raw = self . get_raw_minor_chip_version ( )
if self . is_eco0 ( minor_raw ) :
return 0
return self . get_raw_major_chip_version ( )
def get_raw_major_chip_version ( self ) :
num_word = 5
return ( self . read_reg ( self . EFUSE_BLOCK1_ADDR + ( 4 * num_word ) ) >> 24 ) & 0x03
2022-01-10 02:07:12 +01:00
def get_chip_description ( self ) :
2024-02-10 17:13:25 +01:00
major_rev = self . get_major_chip_version ( )
minor_rev = self . get_minor_chip_version ( )
return " %s (revision v %d . %d ) " % ( self . CHIP_NAME , major_rev , minor_rev )
2022-01-10 02:07:12 +01:00
def get_chip_features ( self ) :
return [ " WiFi " , " BLE " ]
def get_crystal_freq ( self ) :
# ESP32S3 XTAL is fixed to 40MHz
return 40
def get_flash_crypt_config ( self ) :
return None # doesn't exist on ESP32-S3
def get_key_block_purpose ( self , key_block ) :
if key_block < 0 or key_block > 5 :
raise FatalError ( " Valid key block numbers must be in range 0-5 " )
reg , shift = [ ( self . EFUSE_PURPOSE_KEY0_REG , self . EFUSE_PURPOSE_KEY0_SHIFT ) ,
( self . EFUSE_PURPOSE_KEY1_REG , self . EFUSE_PURPOSE_KEY1_SHIFT ) ,
( self . EFUSE_PURPOSE_KEY2_REG , self . EFUSE_PURPOSE_KEY2_SHIFT ) ,
( self . EFUSE_PURPOSE_KEY3_REG , self . EFUSE_PURPOSE_KEY3_SHIFT ) ,
( self . EFUSE_PURPOSE_KEY4_REG , self . EFUSE_PURPOSE_KEY4_SHIFT ) ,
( self . EFUSE_PURPOSE_KEY5_REG , self . EFUSE_PURPOSE_KEY5_SHIFT ) ] [ key_block ]
return ( self . read_reg ( reg ) >> shift ) & 0xF
def is_flash_encryption_key_valid ( self ) :
# Need to see either an AES-128 key or two AES-256 keys
purposes = [ self . get_key_block_purpose ( b ) for b in range ( 6 ) ]
if any ( p == self . PURPOSE_VAL_XTS_AES128_KEY for p in purposes ) :
return True
return any ( p == self . PURPOSE_VAL_XTS_AES256_KEY_1 for p in purposes ) \
and any ( p == self . PURPOSE_VAL_XTS_AES256_KEY_2 for p in purposes )
def override_vddsdio ( self , new_voltage ) :
raise NotImplementedInROMError ( " VDD_SDIO overrides are not supported for ESP32-S3 " )
def read_mac ( self ) :
mac0 = self . read_reg ( self . MAC_EFUSE_REG )
mac1 = self . read_reg ( self . MAC_EFUSE_REG + 4 ) # only bottom 16 bits are MAC
bitstring = struct . pack ( " >II " , mac1 , mac0 ) [ 2 : ]
try :
return tuple ( ord ( b ) for b in bitstring )
except TypeError : # Python 3, bitstring elements are already bytes
return tuple ( bitstring )
2024-02-10 17:13:25 +01:00
def uses_usb ( self , _cache = [ ] ) :
if self . secure_download_mode :
return False # can't detect native USB in secure download mode
if not _cache :
buf_no = self . read_reg ( self . UARTDEV_BUF_NO ) & 0xff
_cache . append ( buf_no == self . UARTDEV_BUF_NO_USB )
return _cache [ 0 ]
def _post_connect ( self ) :
if self . uses_usb ( ) :
self . ESP_RAM_BLOCK = self . USB_RAM_BLOCK
def _check_if_can_reset ( self ) :
"""
Check the strapping register to see if we can reset out of download mode .
"""
if os . getenv ( " ESPTOOL_TESTING " ) is not None :
print ( " ESPTOOL_TESTING is set, ignoring strapping mode check " )
# Esptool tests over USB CDC run with GPIO0 strapped low, don't complain in this case.
return
strap_reg = self . read_reg ( self . GPIO_STRAP_REG )
force_dl_reg = self . read_reg ( self . RTC_CNTL_OPTION1_REG )
if strap_reg & self . GPIO_STRAP_SPI_BOOT_MASK == 0 and force_dl_reg & self . RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK == 0 :
print ( " WARNING: {} chip was placed into download mode using GPIO0. \n "
" esptool.py can not exit the download mode over USB. "
" To run the app, reset the chip manually. \n "
" To suppress this note, set --after option to ' no_reset ' . " . format ( self . get_chip_description ( ) ) )
raise SystemExit ( 1 )
def hard_reset ( self ) :
if self . uses_usb ( ) :
self . _check_if_can_reset ( )
print ( ' Hard resetting via RTS pin... ' )
self . _setRTS ( True ) # EN->LOW
if self . uses_usb ( ) :
# Give the chip some time to come out of reset, to be able to handle further DTR/RTS transitions
time . sleep ( 0.2 )
self . _setRTS ( False )
time . sleep ( 0.2 )
else :
time . sleep ( 0.1 )
self . _setRTS ( False )
2022-01-10 02:07:12 +01:00
class ESP32S3BETA2ROM ( ESP32S3ROM ) :
CHIP_NAME = " ESP32-S3(beta2) "
IMAGE_CHIP_ID = 4
CHIP_DETECT_MAGIC_VALUE = [ 0xeb004136 ]
2024-02-10 17:13:25 +01:00
EFUSE_BASE = 0x6001A000 # BLOCK0 read base address
2022-01-10 02:07:12 +01:00
def get_chip_description ( self ) :
2024-02-10 17:13:25 +01:00
major_rev = self . get_major_chip_version ( )
minor_rev = self . get_minor_chip_version ( )
return " %s (revision v %d . %d ) " % ( self . CHIP_NAME , major_rev , minor_rev )
2022-01-10 02:07:12 +01:00
class ESP32C3ROM ( ESP32ROM ) :
CHIP_NAME = " ESP32-C3 "
IMAGE_CHIP_ID = 5
2024-02-10 17:13:25 +01:00
FPGA_SLOW_BOOT = False
2022-01-10 02:07:12 +01:00
IROM_MAP_START = 0x42000000
IROM_MAP_END = 0x42800000
DROM_MAP_START = 0x3c000000
DROM_MAP_END = 0x3c800000
SPI_REG_BASE = 0x60002000
SPI_USR_OFFS = 0x18
SPI_USR1_OFFS = 0x1C
SPI_USR2_OFFS = 0x20
SPI_MOSI_DLEN_OFFS = 0x24
SPI_MISO_DLEN_OFFS = 0x28
SPI_W0_OFFS = 0x58
BOOTLOADER_FLASH_OFFSET = 0x0
# Magic value for ESP32C3 eco 1+2 and ESP32C3 eco3 respectivly
CHIP_DETECT_MAGIC_VALUE = [ 0x6921506f , 0x1b31506f ]
UART_DATE_REG_ADDR = 0x60000000 + 0x7c
EFUSE_BASE = 0x60008800
2024-02-10 17:13:25 +01:00
EFUSE_BLOCK1_ADDR = EFUSE_BASE + 0x044
2022-01-10 02:07:12 +01:00
MAC_EFUSE_REG = EFUSE_BASE + 0x044
EFUSE_RD_REG_BASE = EFUSE_BASE + 0x030 # BLOCK0 read base address
EFUSE_PURPOSE_KEY0_REG = EFUSE_BASE + 0x34
EFUSE_PURPOSE_KEY0_SHIFT = 24
EFUSE_PURPOSE_KEY1_REG = EFUSE_BASE + 0x34
EFUSE_PURPOSE_KEY1_SHIFT = 28
EFUSE_PURPOSE_KEY2_REG = EFUSE_BASE + 0x38
EFUSE_PURPOSE_KEY2_SHIFT = 0
EFUSE_PURPOSE_KEY3_REG = EFUSE_BASE + 0x38
EFUSE_PURPOSE_KEY3_SHIFT = 4
EFUSE_PURPOSE_KEY4_REG = EFUSE_BASE + 0x38
EFUSE_PURPOSE_KEY4_SHIFT = 8
EFUSE_PURPOSE_KEY5_REG = EFUSE_BASE + 0x38
EFUSE_PURPOSE_KEY5_SHIFT = 12
EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT_REG = EFUSE_RD_REG_BASE
EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT = 1 << 20
PURPOSE_VAL_XTS_AES128_KEY = 4
GPIO_STRAP_REG = 0x3f404038
FLASH_ENCRYPTED_WRITE_ALIGN = 16
MEMORY_MAP = [ [ 0x00000000 , 0x00010000 , " PADDING " ] ,
[ 0x3C000000 , 0x3C800000 , " DROM " ] ,
[ 0x3FC80000 , 0x3FCE0000 , " DRAM " ] ,
[ 0x3FC88000 , 0x3FD00000 , " BYTE_ACCESSIBLE " ] ,
[ 0x3FF00000 , 0x3FF20000 , " DROM_MASK " ] ,
[ 0x40000000 , 0x40060000 , " IROM_MASK " ] ,
[ 0x42000000 , 0x42800000 , " IROM " ] ,
[ 0x4037C000 , 0x403E0000 , " IRAM " ] ,
[ 0x50000000 , 0x50002000 , " RTC_IRAM " ] ,
[ 0x50000000 , 0x50002000 , " RTC_DRAM " ] ,
[ 0x600FE000 , 0x60100000 , " MEM_INTERNAL2 " ] ]
2024-02-10 17:13:25 +01:00
# Returns old version format (ECO number). Use the new format get_chip_full_revision().
def get_chip_revision ( self ) :
return self . get_minor_chip_version ( )
2022-01-10 02:07:12 +01:00
def get_pkg_version ( self ) :
num_word = 3
2024-02-10 17:13:25 +01:00
return ( self . read_reg ( self . EFUSE_BLOCK1_ADDR + ( 4 * num_word ) ) >> 21 ) & 0x07
2022-01-10 02:07:12 +01:00
2024-02-10 17:13:25 +01:00
def get_minor_chip_version ( self ) :
hi_num_word = 5
hi = ( self . read_reg ( self . EFUSE_BLOCK1_ADDR + ( 4 * hi_num_word ) ) >> 23 ) & 0x01
low_num_word = 3
low = ( self . read_reg ( self . EFUSE_BLOCK1_ADDR + ( 4 * low_num_word ) ) >> 18 ) & 0x07
return ( hi << 3 ) + low
def get_major_chip_version ( self ) :
num_word = 5
return ( self . read_reg ( self . EFUSE_BLOCK1_ADDR + ( 4 * num_word ) ) >> 24 ) & 0x03
2022-01-10 02:07:12 +01:00
def get_chip_description ( self ) :
chip_name = {
0 : " ESP32-C3 " ,
} . get ( self . get_pkg_version ( ) , " unknown ESP32-C3 " )
2024-02-10 17:13:25 +01:00
major_rev = self . get_major_chip_version ( )
minor_rev = self . get_minor_chip_version ( )
return " %s (revision v %d . %d ) " % ( chip_name , major_rev , minor_rev )
2022-01-10 02:07:12 +01:00
def get_chip_features ( self ) :
return [ " Wi-Fi " ]
def get_crystal_freq ( self ) :
# ESP32C3 XTAL is fixed to 40MHz
return 40
def override_vddsdio ( self , new_voltage ) :
raise NotImplementedInROMError ( " VDD_SDIO overrides are not supported for ESP32-C3 " )
def read_mac ( self ) :
mac0 = self . read_reg ( self . MAC_EFUSE_REG )
mac1 = self . read_reg ( self . MAC_EFUSE_REG + 4 ) # only bottom 16 bits are MAC
bitstring = struct . pack ( " >II " , mac1 , mac0 ) [ 2 : ]
try :
return tuple ( ord ( b ) for b in bitstring )
except TypeError : # Python 3, bitstring elements are already bytes
return tuple ( bitstring )
def get_flash_crypt_config ( self ) :
return None # doesn't exist on ESP32-C3
def get_key_block_purpose ( self , key_block ) :
if key_block < 0 or key_block > 5 :
raise FatalError ( " Valid key block numbers must be in range 0-5 " )
reg , shift = [ ( self . EFUSE_PURPOSE_KEY0_REG , self . EFUSE_PURPOSE_KEY0_SHIFT ) ,
( self . EFUSE_PURPOSE_KEY1_REG , self . EFUSE_PURPOSE_KEY1_SHIFT ) ,
( self . EFUSE_PURPOSE_KEY2_REG , self . EFUSE_PURPOSE_KEY2_SHIFT ) ,
( self . EFUSE_PURPOSE_KEY3_REG , self . EFUSE_PURPOSE_KEY3_SHIFT ) ,
( self . EFUSE_PURPOSE_KEY4_REG , self . EFUSE_PURPOSE_KEY4_SHIFT ) ,
( self . EFUSE_PURPOSE_KEY5_REG , self . EFUSE_PURPOSE_KEY5_SHIFT ) ] [ key_block ]
return ( self . read_reg ( reg ) >> shift ) & 0xF
def is_flash_encryption_key_valid ( self ) :
# Need to see an AES-128 key
purposes = [ self . get_key_block_purpose ( b ) for b in range ( 6 ) ]
return any ( p == self . PURPOSE_VAL_XTS_AES128_KEY for p in purposes )
2024-02-10 17:13:25 +01:00
class ESP32H2BETA1ROM ( ESP32ROM ) :
CHIP_NAME = " ESP32-H2(beta1) "
IMAGE_CHIP_ID = 10
IROM_MAP_START = 0x42000000
IROM_MAP_END = 0x42800000
DROM_MAP_START = 0x3c000000
DROM_MAP_END = 0x3c800000
SPI_REG_BASE = 0x60002000
SPI_USR_OFFS = 0x18
SPI_USR1_OFFS = 0x1C
SPI_USR2_OFFS = 0x20
SPI_MOSI_DLEN_OFFS = 0x24
SPI_MISO_DLEN_OFFS = 0x28
SPI_W0_OFFS = 0x58
BOOTLOADER_FLASH_OFFSET = 0x0
CHIP_DETECT_MAGIC_VALUE = [ 0xca26cc22 ]
UART_DATE_REG_ADDR = 0x60000000 + 0x7c
EFUSE_BASE = 0x6001A000
EFUSE_BLOCK1_ADDR = EFUSE_BASE + 0x044
MAC_EFUSE_REG = EFUSE_BASE + 0x044
EFUSE_RD_REG_BASE = EFUSE_BASE + 0x030 # BLOCK0 read base address
EFUSE_PURPOSE_KEY0_REG = EFUSE_BASE + 0x34
EFUSE_PURPOSE_KEY0_SHIFT = 24
EFUSE_PURPOSE_KEY1_REG = EFUSE_BASE + 0x34
EFUSE_PURPOSE_KEY1_SHIFT = 28
EFUSE_PURPOSE_KEY2_REG = EFUSE_BASE + 0x38
EFUSE_PURPOSE_KEY2_SHIFT = 0
EFUSE_PURPOSE_KEY3_REG = EFUSE_BASE + 0x38
EFUSE_PURPOSE_KEY3_SHIFT = 4
EFUSE_PURPOSE_KEY4_REG = EFUSE_BASE + 0x38
EFUSE_PURPOSE_KEY4_SHIFT = 8
EFUSE_PURPOSE_KEY5_REG = EFUSE_BASE + 0x38
EFUSE_PURPOSE_KEY5_SHIFT = 12
EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT_REG = EFUSE_RD_REG_BASE
EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT = 1 << 20
PURPOSE_VAL_XTS_AES128_KEY = 4
GPIO_STRAP_REG = 0x3f404038
FLASH_ENCRYPTED_WRITE_ALIGN = 16
MEMORY_MAP = [ ]
FLASH_FREQUENCY = {
' 48m ' : 0xf ,
' 24m ' : 0x0 ,
' 16m ' : 0x1 ,
' 12m ' : 0x2 ,
}
# Returns old version format (ECO number). Use the new format get_chip_full_revision().
def get_chip_revision ( self ) :
return 0
def get_pkg_version ( self ) :
num_word = 4
return ( self . read_reg ( self . EFUSE_BLOCK1_ADDR + ( 4 * num_word ) ) >> 0 ) & 0x07
def get_minor_chip_version ( self ) :
num_word = 3
return ( self . read_reg ( self . EFUSE_BLOCK1_ADDR + ( 4 * num_word ) ) >> 18 ) & 0x07
def get_major_chip_version ( self ) :
num_word = 3
return ( self . read_reg ( self . EFUSE_BLOCK1_ADDR + ( 4 * num_word ) ) >> 21 ) & 0x03
def get_chip_description ( self ) :
chip_name = {
0 : " ESP32-H2 " ,
} . get ( self . get_pkg_version ( ) , " unknown ESP32-H2 " )
major_rev = self . get_major_chip_version ( )
minor_rev = self . get_minor_chip_version ( )
return " %s (revision v %d . %d ) " % ( chip_name , major_rev , minor_rev )
def get_chip_features ( self ) :
return [ " BLE/802.15.4 " ]
def get_crystal_freq ( self ) :
return 32
def override_vddsdio ( self , new_voltage ) :
raise NotImplementedInROMError ( " VDD_SDIO overrides are not supported for ESP32-H2 " )
def read_mac ( self ) :
mac0 = self . read_reg ( self . MAC_EFUSE_REG )
mac1 = self . read_reg ( self . MAC_EFUSE_REG + 4 ) # only bottom 16 bits are MAC
bitstring = struct . pack ( " >II " , mac1 , mac0 ) [ 2 : ]
try :
return tuple ( ord ( b ) for b in bitstring )
except TypeError : # Python 3, bitstring elements are already bytes
return tuple ( bitstring )
def get_flash_crypt_config ( self ) :
return None # doesn't exist on ESP32-H2
def get_key_block_purpose ( self , key_block ) :
if key_block < 0 or key_block > 5 :
raise FatalError ( " Valid key block numbers must be in range 0-5 " )
reg , shift = [ ( self . EFUSE_PURPOSE_KEY0_REG , self . EFUSE_PURPOSE_KEY0_SHIFT ) ,
( self . EFUSE_PURPOSE_KEY1_REG , self . EFUSE_PURPOSE_KEY1_SHIFT ) ,
( self . EFUSE_PURPOSE_KEY2_REG , self . EFUSE_PURPOSE_KEY2_SHIFT ) ,
( self . EFUSE_PURPOSE_KEY3_REG , self . EFUSE_PURPOSE_KEY3_SHIFT ) ,
( self . EFUSE_PURPOSE_KEY4_REG , self . EFUSE_PURPOSE_KEY4_SHIFT ) ,
( self . EFUSE_PURPOSE_KEY5_REG , self . EFUSE_PURPOSE_KEY5_SHIFT ) ] [ key_block ]
return ( self . read_reg ( reg ) >> shift ) & 0xF
def is_flash_encryption_key_valid ( self ) :
# Need to see an AES-128 key
purposes = [ self . get_key_block_purpose ( b ) for b in range ( 6 ) ]
return any ( p == self . PURPOSE_VAL_XTS_AES128_KEY for p in purposes )
class ESP32H2BETA2ROM ( ESP32H2BETA1ROM ) :
CHIP_NAME = " ESP32-H2(beta2) "
IMAGE_CHIP_ID = 14
def get_chip_description ( self ) :
major_rev = self . get_major_chip_version ( )
minor_rev = self . get_minor_chip_version ( )
return " %s (revision v %d . %d ) " % ( self . CHIP_NAME , major_rev , minor_rev )
class ESP32C2ROM ( ESP32C3ROM ) :
CHIP_NAME = " ESP32-C2 "
IMAGE_CHIP_ID = 12
IROM_MAP_START = 0x42000000
IROM_MAP_END = 0x42400000
DROM_MAP_START = 0x3c000000
DROM_MAP_END = 0x3c400000
# Magic value for ESP32C2 ECO0 and ECO1 respectively
CHIP_DETECT_MAGIC_VALUE = [ 0x6F51306F , 0x7c41a06f ]
EFUSE_BASE = 0x60008800
EFUSE_BLOCK2_ADDR = EFUSE_BASE + 0x040
MAC_EFUSE_REG = EFUSE_BASE + 0x040
FLASH_FREQUENCY = {
' 60m ' : 0xf ,
' 30m ' : 0x0 ,
' 20m ' : 0x1 ,
' 15m ' : 0x2 ,
}
# Returns old version format (ECO number). Use the new format get_chip_full_revision().
def get_chip_revision ( self ) :
return self . get_major_chip_version ( )
def get_pkg_version ( self ) :
num_word = 1
return ( self . read_reg ( self . EFUSE_BLOCK2_ADDR + ( 4 * num_word ) ) >> 22 ) & 0x07
def get_chip_description ( self ) :
chip_name = {
0 : " ESP32-C2 " ,
1 : " ESP32-C2 " ,
} . get ( self . get_pkg_version ( ) , " unknown ESP32-C2 " )
major_rev = self . get_major_chip_version ( )
minor_rev = self . get_minor_chip_version ( )
return " %s (revision v %d . %d ) " % ( chip_name , major_rev , minor_rev )
def get_minor_chip_version ( self ) :
num_word = 1
return ( self . read_reg ( self . EFUSE_BLOCK2_ADDR + ( 4 * num_word ) ) >> 16 ) & 0xF
def get_major_chip_version ( self ) :
num_word = 1
return ( self . read_reg ( self . EFUSE_BLOCK2_ADDR + ( 4 * num_word ) ) >> 20 ) & 0x3
def _post_connect ( self ) :
# ESP32C2 ECO0 is no longer supported by the flasher stub
if self . get_chip_revision ( ) == 0 :
self . stub_is_disabled = True
self . IS_STUB = False
2022-01-10 02:07:12 +01:00
class ESP32C6BETAROM ( ESP32C3ROM ) :
2024-02-10 17:13:25 +01:00
CHIP_NAME = " ESP32-C6(beta) "
2022-01-10 02:07:12 +01:00
IMAGE_CHIP_ID = 7
CHIP_DETECT_MAGIC_VALUE = [ 0x0da1806f ]
UART_DATE_REG_ADDR = 0x00000500
2024-02-10 17:13:25 +01:00
# Returns old version format (ECO number). Use the new format get_chip_full_revision().
def get_chip_revision ( self ) :
return 0
def get_pkg_version ( self ) :
num_word = 3
return ( self . read_reg ( self . EFUSE_BLOCK1_ADDR + ( 4 * num_word ) ) >> 29 ) & 0x07
def get_minor_chip_version ( self ) :
num_word = 3
return ( self . read_reg ( self . EFUSE_BLOCK1_ADDR + ( 4 * num_word ) ) >> 18 ) & 0x0F
def get_major_chip_version ( self ) :
num_word = 3
return ( self . read_reg ( self . EFUSE_BLOCK1_ADDR + ( 4 * num_word ) ) >> 22 ) & 0x03
2022-01-10 02:07:12 +01:00
def get_chip_description ( self ) :
chip_name = {
0 : " ESP32-C6 " ,
} . get ( self . get_pkg_version ( ) , " unknown ESP32-C6 " )
2024-02-10 17:13:25 +01:00
major_rev = self . get_major_chip_version ( )
minor_rev = self . get_minor_chip_version ( )
return " %s (revision v %d . %d ) " % ( chip_name , major_rev , minor_rev )
2022-01-10 02:07:12 +01:00
class ESP32StubLoader ( ESP32ROM ) :
""" Access class for ESP32 stub loader, runs on top of ROM.
"""
FLASH_WRITE_SIZE = 0x4000 # matches MAX_WRITE_BLOCK in stub_loader.c
STATUS_BYTES_LENGTH = 2 # same as ESP8266, different to ESP32 ROM
IS_STUB = True
def __init__ ( self , rom_loader ) :
self . secure_download_mode = rom_loader . secure_download_mode
self . _port = rom_loader . _port
self . _trace_enabled = rom_loader . _trace_enabled
self . flush_input ( ) # resets _slip_reader
ESP32ROM . STUB_CLASS = ESP32StubLoader
class ESP32S2StubLoader ( ESP32S2ROM ) :
""" Access class for ESP32-S2 stub loader, runs on top of ROM.
( Basically the same as ESP32StubLoader , but different base class .
Can possibly be made into a mixin . )
"""
FLASH_WRITE_SIZE = 0x4000 # matches MAX_WRITE_BLOCK in stub_loader.c
STATUS_BYTES_LENGTH = 2 # same as ESP8266, different to ESP32 ROM
IS_STUB = True
def __init__ ( self , rom_loader ) :
self . secure_download_mode = rom_loader . secure_download_mode
self . _port = rom_loader . _port
self . _trace_enabled = rom_loader . _trace_enabled
self . flush_input ( ) # resets _slip_reader
if rom_loader . uses_usb ( ) :
self . ESP_RAM_BLOCK = self . USB_RAM_BLOCK
self . FLASH_WRITE_SIZE = self . USB_RAM_BLOCK
ESP32S2ROM . STUB_CLASS = ESP32S2StubLoader
class ESP32S3BETA2StubLoader ( ESP32S3BETA2ROM ) :
""" Access class for ESP32S3 stub loader, runs on top of ROM.
( Basically the same as ESP32StubLoader , but different base class .
Can possibly be made into a mixin . )
"""
FLASH_WRITE_SIZE = 0x4000 # matches MAX_WRITE_BLOCK in stub_loader.c
STATUS_BYTES_LENGTH = 2 # same as ESP8266, different to ESP32 ROM
IS_STUB = True
def __init__ ( self , rom_loader ) :
self . secure_download_mode = rom_loader . secure_download_mode
self . _port = rom_loader . _port
self . _trace_enabled = rom_loader . _trace_enabled
self . flush_input ( ) # resets _slip_reader
ESP32S3BETA2ROM . STUB_CLASS = ESP32S3BETA2StubLoader
2024-02-10 17:13:25 +01:00
class ESP32S3StubLoader ( ESP32S3ROM ) :
2022-01-10 02:07:12 +01:00
""" Access class for ESP32S3 stub loader, runs on top of ROM.
( Basically the same as ESP32StubLoader , but different base class .
Can possibly be made into a mixin . )
"""
FLASH_WRITE_SIZE = 0x4000 # matches MAX_WRITE_BLOCK in stub_loader.c
STATUS_BYTES_LENGTH = 2 # same as ESP8266, different to ESP32 ROM
IS_STUB = True
def __init__ ( self , rom_loader ) :
self . secure_download_mode = rom_loader . secure_download_mode
self . _port = rom_loader . _port
self . _trace_enabled = rom_loader . _trace_enabled
self . flush_input ( ) # resets _slip_reader
2024-02-10 17:13:25 +01:00
if rom_loader . uses_usb ( ) :
self . ESP_RAM_BLOCK = self . USB_RAM_BLOCK
self . FLASH_WRITE_SIZE = self . USB_RAM_BLOCK
2022-01-10 02:07:12 +01:00
2024-02-10 17:13:25 +01:00
ESP32S3ROM . STUB_CLASS = ESP32S3StubLoader
2022-01-10 02:07:12 +01:00
class ESP32C3StubLoader ( ESP32C3ROM ) :
""" Access class for ESP32C3 stub loader, runs on top of ROM.
( Basically the same as ESP32StubLoader , but different base class .
Can possibly be made into a mixin . )
"""
FLASH_WRITE_SIZE = 0x4000 # matches MAX_WRITE_BLOCK in stub_loader.c
STATUS_BYTES_LENGTH = 2 # same as ESP8266, different to ESP32 ROM
IS_STUB = True
def __init__ ( self , rom_loader ) :
self . secure_download_mode = rom_loader . secure_download_mode
self . _port = rom_loader . _port
self . _trace_enabled = rom_loader . _trace_enabled
self . flush_input ( ) # resets _slip_reader
ESP32C3ROM . STUB_CLASS = ESP32C3StubLoader
2024-02-10 17:13:25 +01:00
class ESP32H2BETA1StubLoader ( ESP32H2BETA1ROM ) :
""" Access class for ESP32H2BETA1 stub loader, runs on top of ROM.
( Basically the same as ESP32StubLoader , but different base class .
Can possibly be made into a mixin . )
"""
FLASH_WRITE_SIZE = 0x4000 # matches MAX_WRITE_BLOCK in stub_loader.c
STATUS_BYTES_LENGTH = 2 # same as ESP8266, different to ESP32 ROM
IS_STUB = True
def __init__ ( self , rom_loader ) :
self . secure_download_mode = rom_loader . secure_download_mode
self . _port = rom_loader . _port
self . _trace_enabled = rom_loader . _trace_enabled
self . flush_input ( ) # resets _slip_reader
ESP32H2BETA1ROM . STUB_CLASS = ESP32H2BETA1StubLoader
class ESP32H2BETA2StubLoader ( ESP32H2BETA2ROM ) :
""" Access class for ESP32H2BETA2 stub loader, runs on top of ROM.
( Basically the same as ESP32StubLoader , but different base class .
Can possibly be made into a mixin . )
"""
FLASH_WRITE_SIZE = 0x4000 # matches MAX_WRITE_BLOCK in stub_loader.c
STATUS_BYTES_LENGTH = 2 # same as ESP8266, different to ESP32 ROM
IS_STUB = True
def __init__ ( self , rom_loader ) :
self . secure_download_mode = rom_loader . secure_download_mode
self . _port = rom_loader . _port
self . _trace_enabled = rom_loader . _trace_enabled
self . flush_input ( ) # resets _slip_reader
ESP32H2BETA2ROM . STUB_CLASS = ESP32H2BETA2StubLoader
class ESP32C2StubLoader ( ESP32C2ROM ) :
""" Access class for ESP32C2 stub loader, runs on top of ROM.
( Basically the same as ESP32StubLoader , but different base class .
Can possibly be made into a mixin . )
"""
FLASH_WRITE_SIZE = 0x4000 # matches MAX_WRITE_BLOCK in stub_loader.c
STATUS_BYTES_LENGTH = 2 # same as ESP8266, different to ESP32 ROM
IS_STUB = True
def __init__ ( self , rom_loader ) :
self . secure_download_mode = rom_loader . secure_download_mode
self . _port = rom_loader . _port
self . _trace_enabled = rom_loader . _trace_enabled
self . flush_input ( ) # resets _slip_reader
ESP32C2ROM . STUB_CLASS = ESP32C2StubLoader
2022-01-10 02:07:12 +01:00
class ESPBOOTLOADER ( object ) :
""" These are constants related to software ESP8266 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 ( chip , filename ) :
""" Load a firmware image. Can be for any supported SoC.
ESP8266 images will be examined to determine if they are original ROM firmware images ( ESP8266ROMFirmwareImage )
or " v2 " OTA bootloader images .
Returns a BaseFirmwareImage subclass , either ESP8266ROMFirmwareImage ( v1 ) or ESP8266V2FirmwareImage ( v2 ) .
"""
2024-02-10 17:13:25 +01:00
chip = re . sub ( r " [-()] " , " " , chip . lower ( ) )
2022-01-10 02:07:12 +01:00
with open ( filename , ' rb ' ) as f :
if chip == ' esp32 ' :
return ESP32FirmwareImage ( f )
elif chip == " esp32s2 " :
return ESP32S2FirmwareImage ( f )
elif chip == " esp32s3beta2 " :
return ESP32S3BETA2FirmwareImage ( f )
2024-02-10 17:13:25 +01:00
elif chip == " esp32s3 " :
return ESP32S3FirmwareImage ( f )
2022-01-10 02:07:12 +01:00
elif chip == ' esp32c3 ' :
return ESP32C3FirmwareImage ( f )
elif chip == ' esp32c6beta ' :
return ESP32C6BETAFirmwareImage ( f )
2024-02-10 17:13:25 +01:00
elif chip == ' esp32h2beta1 ' :
return ESP32H2BETA1FirmwareImage ( f )
elif chip == ' esp32h2beta2 ' :
return ESP32H2BETA2FirmwareImage ( f )
elif chip == ' esp32c2 ' :
return ESP32C2FirmwareImage ( f )
2022-01-10 02:07:12 +01:00
else : # Otherwise, ESP8266 so look at magic to determine the image type
magic = ord ( f . read ( 1 ) )
f . seek ( 0 )
if magic == ESPLoader . ESP_IMAGE_MAGIC :
return ESP8266ROMFirmwareImage ( f )
elif magic == ESPBOOTLOADER . IMAGE_V2_MAGIC :
return ESP8266V2FirmwareImage ( f )
else :
raise FatalError ( " Invalid image magic number: %d " % magic )
class ImageSegment ( object ) :
""" Wrapper class for a segment in an ESP image
( very similar to a section in an ELFImage also ) """
def __init__ ( self , addr , data , file_offs = None ) :
self . addr = addr
self . data = data
self . file_offs = file_offs
self . include_in_checksum = True
if self . addr != 0 :
self . pad_to_alignment ( 4 ) # pad all "real" ImageSegments 4 byte aligned length
def copy_with_new_addr ( self , new_addr ) :
""" Return a new ImageSegment with same data, but mapped at
a new address . """
return ImageSegment ( new_addr , self . data , 0 )
def split_image ( self , split_len ) :
""" Return a new ImageSegment which splits " split_len " bytes
from the beginning of the data . Remaining bytes are kept in
this segment object ( and the start address is adjusted to match . ) """
result = copy . copy ( self )
result . data = self . data [ : split_len ]
self . data = self . data [ split_len : ]
self . addr + = split_len
self . file_offs = None
result . file_offs = None
return result
def __repr__ ( self ) :
r = " len 0x %05x load 0x %08x " % ( len ( self . data ) , self . addr )
if self . file_offs is not None :
r + = " file_offs 0x %08x " % ( self . file_offs )
return r
def get_memory_type ( self , image ) :
"""
Return a list describing the memory type ( s ) that is covered by this
segment ' s start address.
"""
return [ map_range [ 2 ] for map_range in image . ROM_LOADER . MEMORY_MAP if map_range [ 0 ] < = self . addr < map_range [ 1 ] ]
def pad_to_alignment ( self , alignment ) :
self . data = pad_to ( self . data , alignment , b ' \x00 ' )
class ELFSection ( ImageSegment ) :
""" Wrapper class for a section in an ELF image, has a section
name as well as the common properties of an ImageSegment . """
def __init__ ( self , name , addr , data ) :
super ( ELFSection , self ) . __init__ ( addr , data )
self . name = name . decode ( " utf-8 " )
def __repr__ ( self ) :
return " %s %s " % ( self . name , super ( ELFSection , self ) . __repr__ ( ) )
class BaseFirmwareImage ( object ) :
SEG_HEADER_LEN = 8
SHA256_DIGEST_LEN = 32
""" Base class with common firmware image functions """
def __init__ ( self ) :
self . segments = [ ]
self . entrypoint = 0
self . elf_sha256 = None
self . elf_sha256_offset = 0
2024-02-10 17:13:25 +01:00
self . pad_to_size = 0
2022-01-10 02:07:12 +01:00
def load_common_header ( self , load_file , expected_magic ) :
( magic , segments , self . flash_mode , self . flash_size_freq , self . entrypoint ) = struct . unpack ( ' <BBBBI ' , load_file . read ( 8 ) )
if magic != expected_magic :
raise FatalError ( ' Invalid firmware image magic=0x %x ' % ( magic ) )
return segments
def verify ( self ) :
if len ( self . segments ) > 16 :
raise FatalError ( ' Invalid segment count %d (max 16). Usually this indicates a linker script problem. ' % len ( self . segments ) )
def load_segment ( self , f , is_irom_segment = False ) :
""" Load the next segment from the image file """
file_offs = f . tell ( )
( offset , size ) = struct . unpack ( ' <II ' , f . read ( 8 ) )
self . warn_if_unusual_segment ( offset , size , is_irom_segment )
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 = ImageSegment ( offset , segment_data , file_offs )
self . segments . append ( segment )
return segment
def warn_if_unusual_segment ( self , offset , size , is_irom_segment ) :
if not is_irom_segment :
if offset > 0x40200000 or offset < 0x3ffe0000 or size > 65536 :
print ( ' WARNING: Suspicious segment 0x %x , length %d ' % ( offset , size ) )
def maybe_patch_segment_data ( self , f , segment_data ) :
""" If SHA256 digest of the ELF file needs to be inserted into this segment, do so. Returns segment data. """
segment_len = len ( segment_data )
file_pos = f . tell ( ) # file_pos is position in the .bin file
if self . elf_sha256_offset > = file_pos and self . elf_sha256_offset < file_pos + segment_len :
# SHA256 digest needs to be patched into this binary segment,
# calculate offset of the digest inside the binary segment.
patch_offset = self . elf_sha256_offset - file_pos
# Sanity checks
if patch_offset < self . SEG_HEADER_LEN or patch_offset + self . SHA256_DIGEST_LEN > segment_len :
raise FatalError ( ' Cannot place SHA256 digest on segment boundary '
' (elf_sha256_offset= %d , file_pos= %d , segment_size= %d ) ' %
( self . elf_sha256_offset , file_pos , segment_len ) )
# offset relative to the data part
patch_offset - = self . SEG_HEADER_LEN
if segment_data [ patch_offset : patch_offset + self . SHA256_DIGEST_LEN ] != b ' \x00 ' * self . SHA256_DIGEST_LEN :
raise FatalError ( ' Contents of segment at SHA256 digest offset 0x %x are not all zero. Refusing to overwrite. ' %
self . elf_sha256_offset )
2024-02-10 17:13:25 +01:00
assert len ( self . elf_sha256 ) == self . SHA256_DIGEST_LEN
2022-01-10 02:07:12 +01:00
segment_data = segment_data [ 0 : patch_offset ] + self . elf_sha256 + \
segment_data [ patch_offset + self . SHA256_DIGEST_LEN : ]
return segment_data
def save_segment ( self , f , segment , checksum = None ) :
""" Save the next segment to the image file, return next checksum value if provided """
segment_data = self . maybe_patch_segment_data ( f , segment . data )
f . write ( struct . pack ( ' <II ' , segment . addr , len ( segment_data ) ) )
f . write ( segment_data )
if checksum is not None :
return ESPLoader . checksum ( segment_data , checksum )
def read_checksum ( self , f ) :
""" Return ESPLoader 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 calculate_checksum ( self ) :
""" Calculate checksum of loaded image, based on segments in
segment array .
"""
checksum = ESPLoader . ESP_CHECKSUM_MAGIC
for seg in self . segments :
if seg . include_in_checksum :
checksum = ESPLoader . checksum ( seg . data , checksum )
return checksum
def append_checksum ( self , f , checksum ) :
""" Append ESPLoader checksum to the just-written image """
align_file_position ( f , 16 )
f . write ( struct . pack ( b ' B ' , checksum ) )
def write_common_header ( self , f , segments ) :
f . write ( struct . pack ( ' <BBBBI ' , ESPLoader . ESP_IMAGE_MAGIC , len ( segments ) ,
self . flash_mode , self . flash_size_freq , self . entrypoint ) )
def is_irom_addr ( self , addr ) :
""" Returns True if an address starts in the irom region.
Valid for ESP8266 only .
"""
return ESP8266ROM . IROM_MAP_START < = addr < ESP8266ROM . IROM_MAP_END
def get_irom_segment ( self ) :
irom_segments = [ s for s in self . segments if self . is_irom_addr ( s . addr ) ]
if len ( irom_segments ) > 0 :
if len ( irom_segments ) != 1 :
raise FatalError ( ' Found %d segments that could be irom0. Bad ELF file? ' % len ( irom_segments ) )
return irom_segments [ 0 ]
return None
def get_non_irom_segments ( self ) :
irom_segment = self . get_irom_segment ( )
return [ s for s in self . segments if s != irom_segment ]
def merge_adjacent_segments ( self ) :
if not self . segments :
return # nothing to merge
segments = [ ]
# The easiest way to merge the sections is the browse them backward.
for i in range ( len ( self . segments ) - 1 , 0 , - 1 ) :
# elem is the previous section, the one `next_elem` may need to be
# merged in
elem = self . segments [ i - 1 ]
next_elem = self . segments [ i ]
if all ( ( elem . get_memory_type ( self ) == next_elem . get_memory_type ( self ) ,
elem . include_in_checksum == next_elem . include_in_checksum ,
next_elem . addr == elem . addr + len ( elem . data ) ) ) :
# Merge any segment that ends where the next one starts, without spanning memory types
#
# (don't 'pad' any gaps here as they may be excluded from the image due to 'noinit'
# or other reasons.)
elem . data + = next_elem . data
else :
# The section next_elem cannot be merged into the previous one,
# which means it needs to be part of the final segments.
# As we are browsing the list backward, the elements need to be
# inserted at the beginning of the final list.
segments . insert ( 0 , next_elem )
# The first segment will always be here as it cannot be merged into any
# "previous" section.
segments . insert ( 0 , self . segments [ 0 ] )
# note: we could sort segments here as well, but the ordering of segments is sometimes
# important for other reasons (like embedded ELF SHA-256), so we assume that the linker
# script will have produced any adjacent sections in linear order in the ELF, anyhow.
self . segments = segments
2024-02-10 17:13:25 +01:00
def set_mmu_page_size ( self , size ) :
""" If supported, this should be overridden by the chip-specific class. Gets called in elf2image. """
print ( ' WARNING: Changing MMU page size is not supported on {} ! Defaulting to 64KB. ' . format ( self . ROM_LOADER . CHIP_NAME ) )
2022-01-10 02:07:12 +01:00
class ESP8266ROMFirmwareImage ( BaseFirmwareImage ) :
""" ' Version 1 ' firmware image, segments loaded directly by the ROM bootloader. """
ROM_LOADER = ESP8266ROM
def __init__ ( self , load_file = None ) :
super ( ESP8266ROMFirmwareImage , self ) . __init__ ( )
self . flash_mode = 0
self . flash_size_freq = 0
self . version = 1
if load_file is not None :
segments = self . load_common_header ( load_file , ESPLoader . ESP_IMAGE_MAGIC )
for _ in range ( segments ) :
self . load_segment ( load_file )
self . checksum = self . read_checksum ( load_file )
self . verify ( )
def default_output_name ( self , input_file ) :
""" Derive a default output name from the ELF name. """
return input_file + ' - '
def save ( self , basename ) :
""" Save a set of V1 images for flashing. Parameter is a base filename. """
# IROM data goes in its own plain binary file
irom_segment = self . get_irom_segment ( )
if irom_segment is not None :
with open ( " %s 0x %05x .bin " % ( basename , irom_segment . addr - ESP8266ROM . IROM_MAP_START ) , " wb " ) as f :
f . write ( irom_segment . data )
# everything but IROM goes at 0x00000 in an image file
normal_segments = self . get_non_irom_segments ( )
with open ( " %s 0x00000.bin " % basename , ' wb ' ) as f :
self . write_common_header ( f , normal_segments )
checksum = ESPLoader . ESP_CHECKSUM_MAGIC
for segment in normal_segments :
checksum = self . save_segment ( f , segment , checksum )
self . append_checksum ( f , checksum )
ESP8266ROM . BOOTLOADER_IMAGE = ESP8266ROMFirmwareImage
class ESP8266V2FirmwareImage ( BaseFirmwareImage ) :
""" ' Version 2 ' firmware image, segments loaded by software bootloader stub
( ie Espressif bootloader or rboot )
"""
ROM_LOADER = ESP8266ROM
def __init__ ( self , load_file = None ) :
super ( ESP8266V2FirmwareImage , self ) . __init__ ( )
self . version = 2
if load_file is not None :
segments = self . load_common_header ( load_file , ESPBOOTLOADER . IMAGE_V2_MAGIC )
if segments != ESPBOOTLOADER . IMAGE_V2_SEGMENT :
# 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
#
# the file is saved in the image with a zero load address
# in the header, so we need to calculate a load address
irom_segment = self . load_segment ( load_file , True )
irom_segment . addr = 0 # for actual mapped addr, add ESP8266ROM.IROM_MAP_START + flashing_addr + 8
irom_segment . include_in_checksum = False
first_flash_mode = self . flash_mode
first_flash_size_freq = self . flash_size_freq
first_entrypoint = self . entrypoint
# load the second header
segments = self . load_common_header ( load_file , ESPLoader . ESP_IMAGE_MAGIC )
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: Entrypoint address in first header (0x %08x ) disagrees with second header (0x %08x ). Using second value. '
% ( first_entrypoint , self . entrypoint ) )
# load all the usual segments
for _ in range ( segments ) :
self . load_segment ( load_file )
self . checksum = self . read_checksum ( load_file )
self . verify ( )
def default_output_name ( self , input_file ) :
""" Derive a default output name from the ELF name. """
irom_segment = self . get_irom_segment ( )
if irom_segment is not None :
irom_offs = irom_segment . addr - ESP8266ROM . IROM_MAP_START
else :
irom_offs = 0
return " %s -0x %05x .bin " % ( os . path . splitext ( input_file ) [ 0 ] ,
irom_offs & ~ ( ESPLoader . FLASH_SECTOR_SIZE - 1 ) )
def save ( self , filename ) :
with open ( filename , ' wb ' ) as f :
# Save first header for irom0 segment
f . write ( struct . pack ( b ' <BBBBI ' , ESPBOOTLOADER . IMAGE_V2_MAGIC , ESPBOOTLOADER . IMAGE_V2_SEGMENT ,
self . flash_mode , self . flash_size_freq , self . entrypoint ) )
irom_segment = self . get_irom_segment ( )
if irom_segment is not None :
# save irom0 segment, make sure it has load addr 0 in the file
irom_segment = irom_segment . copy_with_new_addr ( 0 )
irom_segment . pad_to_alignment ( 16 ) # irom_segment must end on a 16 byte boundary
self . save_segment ( f , irom_segment )
# second header, matches V1 header and contains loadable segments
normal_segments = self . get_non_irom_segments ( )
self . write_common_header ( f , normal_segments )
checksum = ESPLoader . ESP_CHECKSUM_MAGIC
for segment in normal_segments :
checksum = self . save_segment ( f , segment , checksum )
self . append_checksum ( f , checksum )
# calculate a crc32 of entire file and append
# (algorithm used by recent 8266 SDK bootloaders)
with open ( filename , ' rb ' ) as f :
crc = esp8266_crc32 ( f . read ( ) )
with open ( filename , ' ab ' ) as f :
f . write ( struct . pack ( b ' <I ' , crc ) )
def esp8266_crc32 ( data ) :
"""
CRC32 algorithm used by 8266 SDK bootloader ( and gen_appbin . py ) .
"""
crc = binascii . crc32 ( data , 0 ) & 0xFFFFFFFF
if crc & 0x80000000 :
return crc ^ 0xFFFFFFFF
else :
return crc + 1
class ESP32FirmwareImage ( BaseFirmwareImage ) :
""" ESP32 firmware image is very similar to V1 ESP8266 image,
except with an additional 16 byte reserved header at top of image ,
and because of new flash mapping capabilities the flash - mapped regions
can be placed in the normal image ( just @ 64 kB padded offsets ) .
"""
ROM_LOADER = ESP32ROM
# ROM bootloader will read the wp_pin field if SPI flash
# pins are remapped via flash. IDF actually enables QIO only
# from software bootloader, so this can be ignored. But needs
# to be set to this value so ROM bootloader will skip it.
WP_PIN_DISABLED = 0xEE
2024-02-10 17:13:25 +01:00
EXTENDED_HEADER_STRUCT_FMT = " <BBBBHBHH " + ( " B " * 4 ) + " B "
2022-01-10 02:07:12 +01:00
IROM_ALIGN = 65536
def __init__ ( self , load_file = None ) :
super ( ESP32FirmwareImage , self ) . __init__ ( )
self . secure_pad = None
self . flash_mode = 0
self . flash_size_freq = 0
self . version = 1
self . wp_pin = self . WP_PIN_DISABLED
# SPI pin drive levels
self . clk_drv = 0
self . q_drv = 0
self . d_drv = 0
self . cs_drv = 0
self . hd_drv = 0
self . wp_drv = 0
self . min_rev = 0
2024-02-10 17:13:25 +01:00
self . min_rev_full = 0
self . max_rev_full = 0
2022-01-10 02:07:12 +01:00
self . append_digest = True
if load_file is not None :
start = load_file . tell ( )
segments = self . load_common_header ( load_file , ESPLoader . ESP_IMAGE_MAGIC )
self . load_extended_header ( load_file )
for _ in range ( segments ) :
self . load_segment ( load_file )
self . checksum = self . read_checksum ( load_file )
if self . append_digest :
end = load_file . tell ( )
self . stored_digest = load_file . read ( 32 )
load_file . seek ( start )
calc_digest = hashlib . sha256 ( )
calc_digest . update ( load_file . read ( end - start ) )
self . calc_digest = calc_digest . digest ( ) # TODO: decide what to do here?
self . verify ( )
def is_flash_addr ( self , addr ) :
return ( self . ROM_LOADER . IROM_MAP_START < = addr < self . ROM_LOADER . IROM_MAP_END ) \
or ( self . ROM_LOADER . DROM_MAP_START < = addr < self . ROM_LOADER . DROM_MAP_END )
def default_output_name ( self , input_file ) :
""" Derive a default output name from the ELF name. """
return " %s .bin " % ( os . path . splitext ( input_file ) [ 0 ] )
def warn_if_unusual_segment ( self , offset , size , is_irom_segment ) :
pass # TODO: add warnings for ESP32 segment offset/size combinations that are wrong
def save ( self , filename ) :
total_segments = 0
with io . BytesIO ( ) as f : # write file to memory first
self . write_common_header ( f , self . segments )
# first 4 bytes of header are read by ROM bootloader for SPI
# config, but currently unused
self . save_extended_header ( f )
checksum = ESPLoader . ESP_CHECKSUM_MAGIC
# split segments into flash-mapped vs ram-loaded, and take copies so we can mutate them
flash_segments = [ copy . deepcopy ( s ) for s in sorted ( self . segments , key = lambda s : s . addr ) if self . is_flash_addr ( s . addr ) ]
ram_segments = [ copy . deepcopy ( s ) for s in sorted ( self . segments , key = lambda s : s . addr ) if not self . is_flash_addr ( s . addr ) ]
# check for multiple ELF sections that are mapped in the same flash mapping region.
# this is usually a sign of a broken linker script, but if you have a legitimate
# use case then let us know
if len ( flash_segments ) > 0 :
last_addr = flash_segments [ 0 ] . addr
for segment in flash_segments [ 1 : ] :
if segment . addr / / self . IROM_ALIGN == last_addr / / self . IROM_ALIGN :
raise FatalError ( ( " Segment loaded at 0x %08x lands in same 64KB flash mapping as segment loaded at 0x %08x . "
" Can ' t generate binary. Suggest changing linker script or ELF to merge sections. " ) %
( segment . addr , last_addr ) )
last_addr = segment . addr
def get_alignment_data_needed ( segment ) :
# Actual alignment (in data bytes) required for a segment header: positioned so that
# after we write the next 8 byte header, file_offs % IROM_ALIGN == segment.addr % IROM_ALIGN
#
# (this is because the segment's vaddr may not be IROM_ALIGNed, more likely is aligned
# IROM_ALIGN+0x18 to account for the binary file header
align_past = ( segment . addr % self . IROM_ALIGN ) - self . SEG_HEADER_LEN
pad_len = ( self . IROM_ALIGN - ( f . tell ( ) % self . IROM_ALIGN ) ) + align_past
if pad_len == 0 or pad_len == self . IROM_ALIGN :
return 0 # already aligned
# subtract SEG_HEADER_LEN a second time, as the padding block has a header as well
pad_len - = self . SEG_HEADER_LEN
if pad_len < 0 :
pad_len + = self . IROM_ALIGN
return pad_len
# try to fit each flash segment on a 64kB aligned boundary
# by padding with parts of the non-flash segments...
while len ( flash_segments ) > 0 :
segment = flash_segments [ 0 ]
pad_len = get_alignment_data_needed ( segment )
if pad_len > 0 : # need to pad
if len ( ram_segments ) > 0 and pad_len > self . SEG_HEADER_LEN :
pad_segment = ram_segments [ 0 ] . split_image ( pad_len )
if len ( ram_segments [ 0 ] . data ) == 0 :
ram_segments . pop ( 0 )
else :
pad_segment = ImageSegment ( 0 , b ' \x00 ' * pad_len , f . tell ( ) )
checksum = self . save_segment ( f , pad_segment , checksum )
total_segments + = 1
else :
# write the flash segment
assert ( f . tell ( ) + 8 ) % self . IROM_ALIGN == segment . addr % self . IROM_ALIGN
checksum = self . save_flash_segment ( f , segment , checksum )
flash_segments . pop ( 0 )
total_segments + = 1
# flash segments all written, so write any remaining RAM segments
for segment in ram_segments :
checksum = self . save_segment ( f , segment , checksum )
total_segments + = 1
if self . secure_pad :
# pad the image so that after signing it will end on a a 64KB boundary.
# This ensures all mapped flash content will be verified.
if not self . append_digest :
raise FatalError ( " secure_pad only applies if a SHA-256 digest is also appended to the image " )
align_past = ( f . tell ( ) + self . SEG_HEADER_LEN ) % self . IROM_ALIGN
# 16 byte aligned checksum (force the alignment to simplify calculations)
checksum_space = 16
if self . secure_pad == ' 1 ' :
# after checksum: SHA-256 digest + (to be added by signing process) version, signature + 12 trailing bytes due to alignment
space_after_checksum = 32 + 4 + 64 + 12
elif self . secure_pad == ' 2 ' : # Secure Boot V2
# after checksum: SHA-256 digest + signature sector, but we place signature sector after the 64KB boundary
space_after_checksum = 32
pad_len = ( self . IROM_ALIGN - align_past - checksum_space - space_after_checksum ) % self . IROM_ALIGN
pad_segment = ImageSegment ( 0 , b ' \x00 ' * pad_len , f . tell ( ) )
checksum = self . save_segment ( f , pad_segment , checksum )
total_segments + = 1
# done writing segments
self . append_checksum ( f , checksum )
image_length = f . tell ( )
if self . secure_pad :
assert ( ( image_length + space_after_checksum ) % self . IROM_ALIGN ) == 0
# kinda hacky: go back to the initial header and write the new segment count
# that includes padding segments. This header is not checksummed
f . seek ( 1 )
try :
f . write ( chr ( total_segments ) )
except TypeError : # Python 3
f . write ( bytes ( [ total_segments ] ) )
if self . append_digest :
# calculate the SHA256 of the whole file and append it
f . seek ( 0 )
digest = hashlib . sha256 ( )
digest . update ( f . read ( image_length ) )
f . write ( digest . digest ( ) )
2024-02-10 17:13:25 +01:00
if self . pad_to_size :
image_length = f . tell ( )
if image_length % self . pad_to_size != 0 :
pad_by = self . pad_to_size - ( image_length % self . pad_to_size )
f . write ( b " \xff " * pad_by )
2022-01-10 02:07:12 +01:00
with open ( filename , ' wb ' ) as real_file :
real_file . write ( f . getvalue ( ) )
def save_flash_segment ( self , f , segment , checksum = None ) :
""" Save the next segment to the image file, return next checksum value if provided """
segment_end_pos = f . tell ( ) + len ( segment . data ) + self . SEG_HEADER_LEN
segment_len_remainder = segment_end_pos % self . IROM_ALIGN
if segment_len_remainder < 0x24 :
# Work around a bug in ESP-IDF 2nd stage bootloader, that it didn't map the
# last MMU page, if an IROM/DROM segment was < 0x24 bytes over the page boundary.
segment . data + = b ' \x00 ' * ( 0x24 - segment_len_remainder )
return self . save_segment ( f , segment , checksum )
def load_extended_header ( self , load_file ) :
def split_byte ( n ) :
return ( n & 0x0F , ( n >> 4 ) & 0x0F )
fields = list ( struct . unpack ( self . EXTENDED_HEADER_STRUCT_FMT , load_file . read ( 16 ) ) )
self . wp_pin = fields [ 0 ]
# SPI pin drive stengths are two per byte
self . clk_drv , self . q_drv = split_byte ( fields [ 1 ] )
self . d_drv , self . cs_drv = split_byte ( fields [ 2 ] )
self . hd_drv , self . wp_drv = split_byte ( fields [ 3 ] )
chip_id = fields [ 4 ]
if chip_id != self . ROM_LOADER . IMAGE_CHIP_ID :
print ( ( " Unexpected chip id in image. Expected %d but value was %d . "
" Is this image for a different chip model? " ) % ( self . ROM_LOADER . IMAGE_CHIP_ID , chip_id ) )
2024-02-10 17:13:25 +01:00
self . min_rev = fields [ 5 ]
self . min_rev_full = fields [ 6 ]
self . max_rev_full = fields [ 7 ]
2022-01-10 02:07:12 +01:00
# reserved fields in the middle should all be zero
2024-02-10 17:13:25 +01:00
if any ( f for f in fields [ 8 : - 1 ] if f != 0 ) :
2022-01-10 02:07:12 +01:00
print ( " Warning: some reserved header fields have non-zero values. This image may be from a newer esptool.py? " )
append_digest = fields [ - 1 ] # last byte is append_digest
if append_digest in [ 0 , 1 ] :
self . append_digest = ( append_digest == 1 )
else :
raise RuntimeError ( " Invalid value for append_digest field (0x %02x ). Should be 0 or 1. " , append_digest )
def save_extended_header ( self , save_file ) :
def join_byte ( ln , hn ) :
return ( ln & 0x0F ) + ( ( hn & 0x0F ) << 4 )
append_digest = 1 if self . append_digest else 0
fields = [ self . wp_pin ,
join_byte ( self . clk_drv , self . q_drv ) ,
join_byte ( self . d_drv , self . cs_drv ) ,
join_byte ( self . hd_drv , self . wp_drv ) ,
self . ROM_LOADER . IMAGE_CHIP_ID ,
2024-02-10 17:13:25 +01:00
self . min_rev ,
self . min_rev_full ,
self . max_rev_full ]
fields + = [ 0 ] * 4 # padding
2022-01-10 02:07:12 +01:00
fields + = [ append_digest ]
packed = struct . pack ( self . EXTENDED_HEADER_STRUCT_FMT , * fields )
save_file . write ( packed )
2024-02-10 17:13:25 +01:00
class ESP8266V3FirmwareImage ( ESP32FirmwareImage ) :
""" ESP8266 V3 firmware image is very similar to ESP32 image
"""
EXTENDED_HEADER_STRUCT_FMT = " B " * 16
def is_flash_addr ( self , addr ) :
return ( addr > ESP8266ROM . IROM_MAP_START )
def save ( self , filename ) :
total_segments = 0
with io . BytesIO ( ) as f : # write file to memory first
self . write_common_header ( f , self . segments )
checksum = ESPLoader . ESP_CHECKSUM_MAGIC
# split segments into flash-mapped vs ram-loaded, and take copies so we can mutate them
flash_segments = [ copy . deepcopy ( s ) for s in sorted ( self . segments , key = lambda s : s . addr ) if self . is_flash_addr ( s . addr ) and len ( s . data ) ]
ram_segments = [ copy . deepcopy ( s ) for s in sorted ( self . segments , key = lambda s : s . addr ) if not self . is_flash_addr ( s . addr ) and len ( s . data ) ]
# check for multiple ELF sections that are mapped in the same flash mapping region.
# this is usually a sign of a broken linker script, but if you have a legitimate
# use case then let us know
if len ( flash_segments ) > 0 :
last_addr = flash_segments [ 0 ] . addr
for segment in flash_segments [ 1 : ] :
if segment . addr / / self . IROM_ALIGN == last_addr / / self . IROM_ALIGN :
raise FatalError ( ( " Segment loaded at 0x %08x lands in same 64KB flash mapping as segment loaded at 0x %08x . "
" Can ' t generate binary. Suggest changing linker script or ELF to merge sections. " ) %
( segment . addr , last_addr ) )
last_addr = segment . addr
# try to fit each flash segment on a 64kB aligned boundary
# by padding with parts of the non-flash segments...
while len ( flash_segments ) > 0 :
segment = flash_segments [ 0 ]
# remove 8 bytes empty data for insert segment header
if segment . name == ' .flash.rodata ' :
segment . data = segment . data [ 8 : ]
# write the flash segment
checksum = self . save_segment ( f , segment , checksum )
flash_segments . pop ( 0 )
total_segments + = 1
# flash segments all written, so write any remaining RAM segments
for segment in ram_segments :
checksum = self . save_segment ( f , segment , checksum )
total_segments + = 1
# done writing segments
self . append_checksum ( f , checksum )
image_length = f . tell ( )
# kinda hacky: go back to the initial header and write the new segment count
# that includes padding segments. This header is not checksummed
f . seek ( 1 )
try :
f . write ( chr ( total_segments ) )
except TypeError : # Python 3
f . write ( bytes ( [ total_segments ] ) )
if self . append_digest :
# calculate the SHA256 of the whole file and append it
f . seek ( 0 )
digest = hashlib . sha256 ( )
digest . update ( f . read ( image_length ) )
f . write ( digest . digest ( ) )
with open ( filename , ' wb ' ) as real_file :
real_file . write ( f . getvalue ( ) )
def load_extended_header ( self , load_file ) :
def split_byte ( n ) :
return ( n & 0x0F , ( n >> 4 ) & 0x0F )
fields = list ( struct . unpack ( self . EXTENDED_HEADER_STRUCT_FMT , load_file . read ( 16 ) ) )
self . wp_pin = fields [ 0 ]
# SPI pin drive stengths are two per byte
self . clk_drv , self . q_drv = split_byte ( fields [ 1 ] )
self . d_drv , self . cs_drv = split_byte ( fields [ 2 ] )
self . hd_drv , self . wp_drv = split_byte ( fields [ 3 ] )
if fields [ 15 ] in [ 0 , 1 ] :
self . append_digest = ( fields [ 15 ] == 1 )
else :
raise RuntimeError ( " Invalid value for append_digest field (0x %02x ). Should be 0 or 1. " , fields [ 15 ] )
# remaining fields in the middle should all be zero
if any ( f for f in fields [ 4 : 15 ] if f != 0 ) :
print ( " Warning: some reserved header fields have non-zero values. This image may be from a newer esptool.py? " )
2022-01-10 02:07:12 +01:00
ESP32ROM . BOOTLOADER_IMAGE = ESP32FirmwareImage
class ESP32S2FirmwareImage ( ESP32FirmwareImage ) :
""" ESP32S2 Firmware Image almost exactly the same as ESP32FirmwareImage """
ROM_LOADER = ESP32S2ROM
ESP32S2ROM . BOOTLOADER_IMAGE = ESP32S2FirmwareImage
class ESP32S3BETA2FirmwareImage ( ESP32FirmwareImage ) :
""" ESP32S3 Firmware Image almost exactly the same as ESP32FirmwareImage """
ROM_LOADER = ESP32S3BETA2ROM
ESP32S3BETA2ROM . BOOTLOADER_IMAGE = ESP32S3BETA2FirmwareImage
2024-02-10 17:13:25 +01:00
class ESP32S3FirmwareImage ( ESP32FirmwareImage ) :
2022-01-10 02:07:12 +01:00
""" ESP32S3 Firmware Image almost exactly the same as ESP32FirmwareImage """
2024-02-10 17:13:25 +01:00
ROM_LOADER = ESP32S3ROM
2022-01-10 02:07:12 +01:00
2024-02-10 17:13:25 +01:00
ESP32S3ROM . BOOTLOADER_IMAGE = ESP32S3FirmwareImage
2022-01-10 02:07:12 +01:00
class ESP32C3FirmwareImage ( ESP32FirmwareImage ) :
""" ESP32C3 Firmware Image almost exactly the same as ESP32FirmwareImage """
ROM_LOADER = ESP32C3ROM
ESP32C3ROM . BOOTLOADER_IMAGE = ESP32C3FirmwareImage
class ESP32C6BETAFirmwareImage ( ESP32FirmwareImage ) :
""" ESP32C6 Firmware Image almost exactly the same as ESP32FirmwareImage """
ROM_LOADER = ESP32C6BETAROM
ESP32C6BETAROM . BOOTLOADER_IMAGE = ESP32C6BETAFirmwareImage
2024-02-10 17:13:25 +01:00
class ESP32H2BETA1FirmwareImage ( ESP32FirmwareImage ) :
""" ESP32H2 Firmware Image almost exactly the same as ESP32FirmwareImage """
ROM_LOADER = ESP32H2BETA1ROM
ESP32H2BETA1ROM . BOOTLOADER_IMAGE = ESP32H2BETA1FirmwareImage
class ESP32H2BETA2FirmwareImage ( ESP32FirmwareImage ) :
""" ESP32H2 Firmware Image almost exactly the same as ESP32FirmwareImage """
ROM_LOADER = ESP32H2BETA2ROM
ESP32H2BETA2ROM . BOOTLOADER_IMAGE = ESP32H2BETA2FirmwareImage
class ESP32C2FirmwareImage ( ESP32FirmwareImage ) :
""" ESP32C2 Firmware Image almost exactly the same as ESP32FirmwareImage """
ROM_LOADER = ESP32C2ROM
def set_mmu_page_size ( self , size ) :
if size not in [ 16384 , 32768 , 65536 ] :
raise FatalError ( " {} is not a valid page size. " . format ( size ) )
self . IROM_ALIGN = size
ESP32C2ROM . BOOTLOADER_IMAGE = ESP32C2FirmwareImage
2022-01-10 02:07:12 +01:00
class ELFFile ( object ) :
SEC_TYPE_PROGBITS = 0x01
SEC_TYPE_STRTAB = 0x03
2024-02-10 17:13:25 +01:00
SEC_TYPE_INITARRAY = 0x0e
SEC_TYPE_FINIARRAY = 0x0f
PROG_SEC_TYPES = ( SEC_TYPE_PROGBITS , SEC_TYPE_INITARRAY , SEC_TYPE_FINIARRAY )
2022-01-10 02:07:12 +01:00
LEN_SEC_HEADER = 0x28
SEG_TYPE_LOAD = 0x01
LEN_SEG_HEADER = 0x20
def __init__ ( self , name ) :
# Load sections from the ELF file
self . name = name
with open ( self . name , ' rb ' ) as f :
self . _read_elf_file ( f )
def get_section ( self , section_name ) :
for s in self . sections :
if s . name == section_name :
return s
raise ValueError ( " No section %s in ELF file " % section_name )
def _read_elf_file ( self , f ) :
# read the ELF file header
LEN_FILE_HEADER = 0x34
try :
( ident , _type , machine , _version ,
self . entrypoint , _phoff , shoff , _flags ,
_ehsize , _phentsize , _phnum , shentsize ,
shnum , shstrndx ) = struct . unpack ( " <16sHHLLLLLHHHHHH " , f . read ( LEN_FILE_HEADER ) )
except struct . error as e :
raise FatalError ( " Failed to read a valid ELF header from %s : %s " % ( self . name , e ) )
if byte ( ident , 0 ) != 0x7f or ident [ 1 : 4 ] != b ' ELF ' :
raise FatalError ( " %s has invalid ELF magic header " % self . name )
if machine not in [ 0x5e , 0xf3 ] :
raise FatalError ( " %s does not appear to be an Xtensa or an RISCV ELF file. e_machine= %04x " % ( self . name , machine ) )
if shentsize != self . LEN_SEC_HEADER :
raise FatalError ( " %s has unexpected section header entry size 0x %x (not 0x %x ) " % ( self . name , shentsize , self . LEN_SEC_HEADER ) )
if shnum == 0 :
raise FatalError ( " %s has 0 section headers " % ( self . name ) )
self . _read_sections ( f , shoff , shnum , shstrndx )
self . _read_segments ( f , _phoff , _phnum , shstrndx )
def _read_sections ( self , f , section_header_offs , section_header_count , shstrndx ) :
f . seek ( section_header_offs )
len_bytes = section_header_count * self . LEN_SEC_HEADER
section_header = f . read ( len_bytes )
if len ( section_header ) == 0 :
raise FatalError ( " No section header found at offset %04x in ELF file. " % section_header_offs )
if len ( section_header ) != ( len_bytes ) :
raise FatalError ( " Only read 0x %x bytes from section header (expected 0x %x .) Truncated ELF file? " % ( len ( section_header ) , len_bytes ) )
# walk through the section header and extract all sections
section_header_offsets = range ( 0 , len ( section_header ) , self . LEN_SEC_HEADER )
def read_section_header ( offs ) :
name_offs , sec_type , _flags , lma , sec_offs , size = struct . unpack_from ( " <LLLLLL " , section_header [ offs : ] )
return ( name_offs , sec_type , lma , size , sec_offs )
all_sections = [ read_section_header ( offs ) for offs in section_header_offsets ]
2024-02-10 17:13:25 +01:00
prog_sections = [ s for s in all_sections if s [ 1 ] in ELFFile . PROG_SEC_TYPES ]
2022-01-10 02:07:12 +01:00
# search for the string table section
if not ( shstrndx * self . LEN_SEC_HEADER ) in section_header_offsets :
raise FatalError ( " ELF file has no STRTAB section at shstrndx %d " % shstrndx )
_ , sec_type , _ , sec_size , sec_offs = read_section_header ( shstrndx * self . LEN_SEC_HEADER )
if sec_type != ELFFile . SEC_TYPE_STRTAB :
print ( ' WARNING: ELF file has incorrect STRTAB section type 0x %02x ' % sec_type )
f . seek ( sec_offs )
string_table = f . read ( sec_size )
# build the real list of ELFSections by reading the actual section names from the
# string table section, and actual data for each section from the ELF file itself
def lookup_string ( offs ) :
raw = string_table [ offs : ]
return raw [ : raw . index ( b ' \x00 ' ) ]
def read_data ( offs , size ) :
f . seek ( offs )
return f . read ( size )
prog_sections = [ ELFSection ( lookup_string ( n_offs ) , lma , read_data ( offs , size ) ) for ( n_offs , _type , lma , size , offs ) in prog_sections
if lma != 0 and size > 0 ]
self . sections = prog_sections
def _read_segments ( self , f , segment_header_offs , segment_header_count , shstrndx ) :
f . seek ( segment_header_offs )
len_bytes = segment_header_count * self . LEN_SEG_HEADER
segment_header = f . read ( len_bytes )
if len ( segment_header ) == 0 :
raise FatalError ( " No segment header found at offset %04x in ELF file. " % segment_header_offs )
if len ( segment_header ) != ( len_bytes ) :
raise FatalError ( " Only read 0x %x bytes from segment header (expected 0x %x .) Truncated ELF file? " % ( len ( segment_header ) , len_bytes ) )
# walk through the segment header and extract all segments
segment_header_offsets = range ( 0 , len ( segment_header ) , self . LEN_SEG_HEADER )
def read_segment_header ( offs ) :
seg_type , seg_offs , _vaddr , lma , size , _memsize , _flags , _align = struct . unpack_from ( " <LLLLLLLL " , segment_header [ offs : ] )
return ( seg_type , lma , size , seg_offs )
all_segments = [ read_segment_header ( offs ) for offs in segment_header_offsets ]
prog_segments = [ s for s in all_segments if s [ 0 ] == ELFFile . SEG_TYPE_LOAD ]
def read_data ( offs , size ) :
f . seek ( offs )
return f . read ( size )
prog_segments = [ ELFSection ( b ' PHDR ' , lma , read_data ( offs , size ) ) for ( _type , lma , size , offs ) in prog_segments
if lma != 0 and size > 0 ]
self . segments = prog_segments
def sha256 ( self ) :
# return SHA256 hash of the input ELF file
sha256 = hashlib . sha256 ( )
with open ( self . name , ' rb ' ) as f :
sha256 . update ( f . read ( ) )
return sha256 . digest ( )
def slip_reader ( port , trace_function ) :
""" 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
2024-02-10 17:13:25 +01:00
successful_slip = False
2022-01-10 02:07:12 +01:00
while True :
waiting = port . inWaiting ( )
read_bytes = port . read ( 1 if waiting == 0 else waiting )
if read_bytes == b ' ' :
2024-02-10 17:13:25 +01:00
if partial_packet is None : # fail due to no data
msg = " Serial data stream stopped: Possible serial noise or corruption. " if successful_slip else " No serial data received. "
else : # fail during packet transfer
msg = " Packet content transfer stopped (received {} bytes) " . format ( len ( partial_packet ) )
trace_function ( msg )
raise FatalError ( msg )
2022-01-10 02:07:12 +01:00
trace_function ( " Read %d bytes: %s " , len ( read_bytes ) , HexFormatter ( read_bytes ) )
for b in read_bytes :
if type ( b ) is int :
b = bytes ( [ b ] ) # python 2/3 compat
if partial_packet is None : # waiting for packet header
if b == b ' \xc0 ' :
partial_packet = b " "
else :
trace_function ( " Read invalid data: %s " , HexFormatter ( read_bytes ) )
trace_function ( " Remaining data in serial buffer: %s " , HexFormatter ( port . read ( port . inWaiting ( ) ) ) )
2024-02-10 17:13:25 +01:00
raise FatalError ( ' Invalid head of packet (0x %s ): Possible serial noise or corruption. ' % hexify ( b ) )
2022-01-10 02:07:12 +01:00
elif in_escape : # part-way through escape sequence
in_escape = False
if b == b ' \xdc ' :
partial_packet + = b ' \xc0 '
elif b == b ' \xdd ' :
partial_packet + = b ' \xdb '
else :
trace_function ( " Read invalid data: %s " , HexFormatter ( read_bytes ) )
trace_function ( " Remaining data in serial buffer: %s " , HexFormatter ( port . read ( port . inWaiting ( ) ) ) )
raise FatalError ( ' Invalid SLIP escape (0xdb, 0x %s ) ' % ( hexify ( b ) ) )
elif b == b ' \xdb ' : # start of escape sequence
in_escape = True
elif b == b ' \xc0 ' : # end of packet
trace_function ( " Received full packet: %s " , HexFormatter ( partial_packet ) )
yield partial_packet
partial_packet = None
2024-02-10 17:13:25 +01:00
successful_slip = True
2022-01-10 02:07:12 +01:00
else : # normal byte in packet
partial_packet + = b
def arg_auto_int ( x ) :
return int ( x , 0 )
2024-02-10 17:13:25 +01:00
def format_chip_name ( c ) :
""" Normalize chip name from user input """
c = c . lower ( ) . replace ( ' - ' , ' ' )
if c == ' esp8684 ' : # TODO: Delete alias, ESPTOOL-389
print ( ' WARNING: Chip name ESP8684 is deprecated in favor of ESP32-C2 and will be removed in a future release. Using ESP32-C2 instead. ' )
return ' esp32c2 '
return c
2022-01-10 02:07:12 +01:00
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 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 flash_size_bytes ( size ) :
""" Given a flash size of the type passed in args.flash_size
( ie 512 KB or 1 MB ) then return the size in bytes .
"""
if " MB " in size :
return int ( size [ : size . index ( " MB " ) ] ) * 1024 * 1024
elif " KB " in size :
return int ( size [ : size . index ( " KB " ) ] ) * 1024
else :
raise FatalError ( " Unknown size %s " % size )
def hexify ( s , uppercase = True ) :
format_str = ' %02X ' if uppercase else ' %02x '
if not PYTHON2 :
return ' ' . join ( format_str % c for c in s )
else :
return ' ' . join ( format_str % ord ( c ) for c in s )
class HexFormatter ( object ) :
"""
Wrapper class which takes binary data in its constructor
and returns a hex string as it ' s __str__ method.
This is intended for " lazy formatting " of trace ( ) output
in hex format . Avoids overhead ( significant on slow computers )
of generating long hex strings even if tracing is disabled .
Note that this doesn ' t save any overhead if passed as an
argument to " % " , only when passed to trace ( )
If auto_split is set ( default ) , any long line ( > 16 bytes ) will be
printed as separately indented lines , with ASCII decoding at the end
of each line .
"""
def __init__ ( self , binary_string , auto_split = True ) :
self . _s = binary_string
self . _auto_split = auto_split
def __str__ ( self ) :
if self . _auto_split and len ( self . _s ) > 16 :
result = " "
s = self . _s
while len ( s ) > 0 :
line = s [ : 16 ]
ascii_line = " " . join ( c if ( c == ' ' or ( c in string . printable and c not in string . whitespace ) )
else ' . ' for c in line . decode ( ' ascii ' , ' replace ' ) )
s = s [ 16 : ]
result + = " \n %-16s %-16s | %s " % ( hexify ( line [ : 8 ] , False ) , hexify ( line [ 8 : ] , False ) , ascii_line )
return result
else :
return hexify ( self . _s , False )
def pad_to ( data , alignment , pad_character = b ' \xFF ' ) :
""" Pad to the next alignment boundary """
pad_mod = len ( data ) % alignment
if pad_mod != 0 :
data + = pad_character * ( alignment - pad_mod )
return data
class FatalError ( RuntimeError ) :
"""
Wrapper class for runtime errors that aren ' t caused by internal bugs, but by
2024-02-10 17:13:25 +01:00
ESP ROM responses or input content .
2022-01-10 02:07:12 +01:00
"""
def __init__ ( self , message ) :
RuntimeError . __init__ ( self , message )
@staticmethod
def WithResult ( message , result ) :
"""
Return a fatal error object that appends the hex values of
2024-02-10 17:13:25 +01:00
' result ' and its meaning as a string formatted argument .
2022-01-10 02:07:12 +01:00
"""
2024-02-10 17:13:25 +01:00
err_defs = {
0x101 : ' Out of memory ' ,
0x102 : ' Invalid argument ' ,
0x103 : ' Invalid state ' ,
0x104 : ' Invalid size ' ,
0x105 : ' Requested resource not found ' ,
0x106 : ' Operation or feature not supported ' ,
0x107 : ' Operation timed out ' ,
0x108 : ' Received response was invalid ' ,
0x109 : ' CRC or checksum was invalid ' ,
0x10A : ' Version was invalid ' ,
0x10B : ' MAC address was invalid ' ,
# Flasher stub error codes
0xC000 : ' Bad data length ' ,
0xC100 : ' Bad data checksum ' ,
0xC200 : ' Bad blocksize ' ,
0xC300 : ' Invalid command ' ,
0xC400 : ' Failed SPI operation ' ,
0xC500 : ' Failed SPI unlock ' ,
0xC600 : ' Not in flash mode ' ,
0xC700 : ' Inflate error ' ,
0xC800 : ' Not enough data ' ,
0xC900 : ' Too much data ' ,
0xFF00 : ' Command not implemented ' ,
}
err_code = struct . unpack ( " >H " , result [ : 2 ] )
message + = " (result was {} : {} ) " . format ( hexify ( result ) , err_defs . get ( err_code [ 0 ] , ' Unknown result ' ) )
2022-01-10 02:07:12 +01:00
return FatalError ( message )
class NotImplementedInROMError ( FatalError ) :
"""
Wrapper class for the error thrown when a particular ESP bootloader function
is not implemented in the ROM bootloader .
"""
def __init__ ( self , bootloader , func ) :
FatalError . __init__ ( self , " %s ROM does not support function %s . " % ( bootloader . CHIP_NAME , func . __name__ ) )
class NotSupportedError ( FatalError ) :
def __init__ ( self , esp , function_name ) :
FatalError . __init__ ( self , " Function %s is not supported for %s . " % ( function_name , esp . CHIP_NAME ) )
# "Operation" commands, executable at command line. One function each
#
# Each function takes either two args (<ESPLoader instance>, <args>) or a single <args>
# argument.
class UnsupportedCommandError ( RuntimeError ) :
"""
Wrapper class for when ROM loader returns an invalid command response .
Usually this indicates the loader is running in Secure Download Mode .
"""
def __init__ ( self , esp , op ) :
if esp . secure_download_mode :
msg = " This command (0x %x ) is not supported in Secure Download Mode " % op
else :
msg = " Invalid (unsupported) command 0x %x " % op
RuntimeError . __init__ ( self , msg )
def load_ram ( esp , args ) :
image = LoadFirmwareImage ( esp . CHIP_NAME , args . filename )
print ( ' RAM boot... ' )
for seg in image . segments :
size = len ( seg . data )
print ( ' Downloading %d bytes at %08x ... ' % ( size , seg . addr ) , end = ' ' )
sys . stdout . flush ( )
esp . mem_begin ( size , div_roundup ( size , esp . ESP_RAM_BLOCK ) , esp . ESP_RAM_BLOCK , seg . addr )
seq = 0
while len ( seg . data ) > 0 :
esp . mem_block ( seg . data [ 0 : esp . ESP_RAM_BLOCK ] , seq )
seg . data = seg . 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 ) :
with open ( args . filename , ' wb ' ) as f :
for i in range ( args . size / / 4 ) :
d = esp . read_reg ( args . address + ( i * 4 ) )
f . write ( struct . pack ( b ' <I ' , d ) )
if f . tell ( ) % 1024 == 0 :
print_overwrite ( ' %d bytes read... ( %d %% ) ' % ( f . tell ( ) ,
f . tell ( ) * 100 / / args . size ) )
sys . stdout . flush ( )
print_overwrite ( " Read %d bytes " % f . tell ( ) , last_line = True )
print ( ' Done! ' )
def detect_flash_size ( esp , args ) :
if args . flash_size == ' detect ' :
if esp . secure_download_mode :
raise FatalError ( " Detecting flash size is not supported in secure download mode. Need to manually specify flash size. " )
flash_id = esp . flash_id ( )
size_id = flash_id >> 16
args . flash_size = DETECTED_FLASH_SIZES . 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 4MB ' % ( flash_id , size_id ) )
args . flash_size = ' 4MB '
else :
print ( ' Auto-detected Flash size: ' , args . flash_size )
def _update_image_flash_params ( esp , address , args , image ) :
""" Modify the flash mode & size bytes if this looks like an executable bootloader image """
if len ( image ) < 8 :
return image # not long enough to be a bootloader image
# unpack the (potential) image header
magic , _ , flash_mode , flash_size_freq = struct . unpack ( " BBBB " , image [ : 4 ] )
if address != esp . BOOTLOADER_FLASH_OFFSET :
return image # not flashing bootloader offset, so don't modify this
if ( args . flash_mode , args . flash_freq , args . flash_size ) == ( ' keep ' , ) * 3 :
return image # all settings are 'keep', not modifying anything
# easy check if this is an image: does it start with a magic byte?
if magic != esp . ESP_IMAGE_MAGIC :
print ( " Warning: Image file at 0x %x doesn ' t look like an image file, so not changing any flash settings. " % address )
return image
# make sure this really is an image, and not just data that
# starts with esp.ESP_IMAGE_MAGIC (mostly a problem for encrypted
# images that happen to start with a magic byte
try :
test_image = esp . BOOTLOADER_IMAGE ( io . BytesIO ( image ) )
test_image . verify ( )
except Exception :
print ( " Warning: Image file at 0x %x is not a valid %s image, so not changing any flash settings. " % ( address , esp . CHIP_NAME ) )
return image
if args . flash_mode != ' keep ' :
flash_mode = { ' qio ' : 0 , ' qout ' : 1 , ' dio ' : 2 , ' dout ' : 3 } [ args . flash_mode ]
flash_freq = flash_size_freq & 0x0F
if args . flash_freq != ' keep ' :
2024-02-10 17:13:25 +01:00
flash_freq = esp . parse_flash_freq_arg ( args . flash_freq )
2022-01-10 02:07:12 +01:00
flash_size = flash_size_freq & 0xF0
if args . flash_size != ' keep ' :
flash_size = esp . parse_flash_size_arg ( args . flash_size )
flash_params = struct . pack ( b ' BB ' , flash_mode , flash_size + flash_freq )
if flash_params != image [ 2 : 4 ] :
print ( ' Flash params set to 0x %04x ' % struct . unpack ( " >H " , flash_params ) )
image = image [ 0 : 2 ] + flash_params + image [ 4 : ]
return image
def write_flash ( esp , args ) :
# set args.compress based on default behaviour:
# -> if either --compress or --no-compress is set, honour that
# -> otherwise, set --compress unless --no-stub is set
if args . compress is None and not args . no_compress :
args . compress = not args . no_stub
# In case we have encrypted files to write, we first do few sanity checks before actual flash
if args . encrypt or args . encrypt_files is not None :
do_write = True
if not esp . secure_download_mode :
if esp . get_encrypted_download_disabled ( ) :
raise FatalError ( " This chip has encrypt functionality in UART download mode disabled. "
" This is the Flash Encryption configuration for Production mode instead of Development mode. " )
crypt_cfg_efuse = esp . get_flash_crypt_config ( )
if crypt_cfg_efuse is not None and crypt_cfg_efuse != 0xF :
print ( ' Unexpected FLASH_CRYPT_CONFIG value: 0x %x ' % ( crypt_cfg_efuse ) )
do_write = False
enc_key_valid = esp . is_flash_encryption_key_valid ( )
if not enc_key_valid :
print ( ' Flash encryption key is not programmed ' )
do_write = False
# Determine which files list contain the ones to encrypt
files_to_encrypt = args . addr_filename if args . encrypt else args . encrypt_files
for address , argfile in files_to_encrypt :
if address % esp . FLASH_ENCRYPTED_WRITE_ALIGN :
print ( " File %s address 0x %x is not %d byte aligned, can ' t flash encrypted " %
( argfile . name , address , esp . FLASH_ENCRYPTED_WRITE_ALIGN ) )
do_write = False
if not do_write and not args . ignore_flash_encryption_efuse_setting :
raise FatalError ( " Can ' t perform encrypted flash write, consult Flash Encryption documentation for more information " )
# verify file sizes fit in flash
if args . flash_size != ' keep ' : # TODO: check this even with 'keep'
flash_end = flash_size_bytes ( args . flash_size )
for address , argfile in args . addr_filename :
argfile . seek ( 0 , os . SEEK_END )
if address + argfile . tell ( ) > flash_end :
raise FatalError ( ( " File %s (length %d ) at offset %d will not fit in %d bytes of flash. "
2024-02-10 17:13:25 +01:00
" Use --flash_size argument, or change flashing address. " )
2022-01-10 02:07:12 +01:00
% ( argfile . name , argfile . tell ( ) , address , flash_end ) )
argfile . seek ( 0 )
if args . erase_all :
erase_flash ( esp , args )
else :
for address , argfile in args . addr_filename :
argfile . seek ( 0 , os . SEEK_END )
write_end = address + argfile . tell ( )
argfile . seek ( 0 )
bytes_over = address % esp . FLASH_SECTOR_SIZE
if bytes_over != 0 :
print ( " WARNING: Flash address {:#010x} is not aligned to a {:#x} byte flash sector. "
" {:#x} bytes before this address will be erased. "
. format ( address , esp . FLASH_SECTOR_SIZE , bytes_over ) )
# Print the address range of to-be-erased flash memory region
print ( " Flash will be erased from {:#010x} to {:#010x} ... "
. format ( address - bytes_over , div_roundup ( write_end , esp . FLASH_SECTOR_SIZE ) * esp . FLASH_SECTOR_SIZE - 1 ) )
""" Create a list describing all the files we have to flash. Each entry holds an " encrypt " flag
marking whether the file needs encryption or not . This list needs to be sorted .
First , append to each entry of our addr_filename list the flag args . encrypt
For example , if addr_filename is [ ( 0x1000 , " partition.bin " ) , ( 0x8000 , " bootloader " ) ] ,
all_files will be [ ( 0x1000 , " partition.bin " , args . encrypt ) , ( 0x8000 , " bootloader " , args . encrypt ) ] ,
where , of course , args . encrypt is either True or False
"""
all_files = [ ( offs , filename , args . encrypt ) for ( offs , filename ) in args . addr_filename ]
""" Now do the same with encrypt_files list, if defined.
In this case , the flag is True
"""
if args . encrypt_files is not None :
encrypted_files_flag = [ ( offs , filename , True ) for ( offs , filename ) in args . encrypt_files ]
# Concatenate both lists and sort them.
# As both list are already sorted, we could simply do a merge instead,
# but for the sake of simplicity and because the lists are very small,
# let's use sorted.
all_files = sorted ( all_files + encrypted_files_flag , key = lambda x : x [ 0 ] )
for address , argfile , encrypted in all_files :
compress = args . compress
# Check whether we can compress the current file before flashing
if compress and encrypted :
print ( ' \n WARNING: - compress and encrypt options are mutually exclusive ' )
print ( ' Will flash %s uncompressed ' % argfile . name )
compress = False
if args . no_stub :
print ( ' Erasing flash... ' )
image = pad_to ( argfile . read ( ) , esp . FLASH_ENCRYPTED_WRITE_ALIGN if encrypted else 4 )
if len ( image ) == 0 :
print ( ' WARNING: File %s is empty ' % argfile . name )
continue
image = _update_image_flash_params ( esp , address , args , image )
calcmd5 = hashlib . md5 ( image ) . hexdigest ( )
uncsize = len ( image )
if compress :
uncimage = image
image = zlib . compress ( uncimage , 9 )
# Decompress the compressed binary a block at a time, to dynamically calculate the
# timeout based on the real write size
decompress = zlib . decompressobj ( )
blocks = esp . flash_defl_begin ( uncsize , len ( image ) , address )
else :
blocks = esp . flash_begin ( uncsize , address , begin_rom_encrypted = encrypted )
argfile . seek ( 0 ) # in case we need it again
seq = 0
bytes_sent = 0 # bytes sent on wire
bytes_written = 0 # bytes written to flash
t = time . time ( )
timeout = DEFAULT_TIMEOUT
while len ( image ) > 0 :
print_overwrite ( ' Writing at 0x %08x ... ( %d %% ) ' % ( address + bytes_written , 100 * ( seq + 1 ) / / blocks ) )
sys . stdout . flush ( )
block = image [ 0 : esp . FLASH_WRITE_SIZE ]
if compress :
# feeding each compressed block into the decompressor lets us see block-by-block how much will be written
block_uncompressed = len ( decompress . decompress ( block ) )
bytes_written + = block_uncompressed
block_timeout = max ( DEFAULT_TIMEOUT , timeout_per_mb ( ERASE_WRITE_TIMEOUT_PER_MB , block_uncompressed ) )
if not esp . IS_STUB :
timeout = block_timeout # ROM code writes block to flash before ACKing
esp . flash_defl_block ( block , seq , timeout = timeout )
if esp . IS_STUB :
timeout = block_timeout # Stub ACKs when block is received, then writes to flash while receiving the block after it
else :
# Pad the last block
block = block + b ' \xff ' * ( esp . FLASH_WRITE_SIZE - len ( block ) )
if encrypted :
esp . flash_encrypt_block ( block , seq )
else :
esp . flash_block ( block , seq )
bytes_written + = len ( block )
bytes_sent + = len ( block )
image = image [ esp . FLASH_WRITE_SIZE : ]
seq + = 1
if esp . IS_STUB :
# Stub only writes each block to flash after 'ack'ing the receive, so do a final dummy operation which will
# not be 'ack'ed until the last block has actually been written out to flash
esp . read_reg ( ESPLoader . CHIP_DETECT_MAGIC_REG_ADDR , timeout = timeout )
t = time . time ( ) - t
speed_msg = " "
if compress :
if t > 0.0 :
speed_msg = " (effective %.1f kbit/s) " % ( uncsize / t * 8 / 1000 )
print_overwrite ( ' Wrote %d bytes ( %d compressed) at 0x %08x in %.1f seconds %s ... ' % ( uncsize ,
bytes_sent ,
address , t , speed_msg ) , last_line = True )
else :
if t > 0.0 :
speed_msg = " ( %.1f kbit/s) " % ( bytes_written / t * 8 / 1000 )
print_overwrite ( ' Wrote %d bytes at 0x %08x in %.1f seconds %s ... ' % ( bytes_written , address , t , speed_msg ) , last_line = True )
if not encrypted and not esp . secure_download_mode :
try :
res = esp . flash_md5sum ( address , uncsize )
if res != calcmd5 :
print ( ' File md5: %s ' % calcmd5 )
print ( ' Flash md5: %s ' % res )
print ( ' MD5 of 0xFF is %s ' % ( hashlib . md5 ( b ' \xFF ' * uncsize ) . hexdigest ( ) ) )
raise FatalError ( " MD5 of file does not match data in flash! " )
else :
print ( ' Hash of data verified. ' )
except NotImplementedInROMError :
pass
print ( ' \n Leaving... ' )
if esp . IS_STUB :
# skip sending flash_finish to ROM loader here,
# as it causes the loader to exit and run user code
esp . flash_begin ( 0 , 0 )
# Get the "encrypted" flag for the last file flashed
# Note: all_files list contains triplets like:
# (address: Integer, filename: String, encrypted: Boolean)
last_file_encrypted = all_files [ - 1 ] [ 2 ]
# Check whether the last file flashed was compressed or not
if args . compress and not last_file_encrypted :
esp . flash_defl_finish ( False )
else :
esp . flash_finish ( False )
if args . verify :
print ( ' Verifying just-written flash... ' )
print ( ' (This option is deprecated, flash contents are now always read back after flashing.) ' )
# If some encrypted files have been flashed print a warning saying that we won't check them
if args . encrypt or args . encrypt_files is not None :
print ( ' WARNING: - cannot verify encrypted files, they will be ignored ' )
# Call verify_flash function only if there at least one non-encrypted file flashed
if not args . encrypt :
verify_flash ( esp , args )
def image_info ( args ) :
2024-02-10 17:13:25 +01:00
if args . chip == " auto " :
print ( " WARNING: --chip not specified, defaulting to ESP8266. " )
2022-01-10 02:07:12 +01:00
image = LoadFirmwareImage ( args . chip , args . filename )
print ( ' Image version: %d ' % image . version )
2024-02-10 17:13:25 +01:00
if args . chip != ' auto ' and args . chip != ' esp8266 ' :
print (
" Minimal chip revision: " ,
" v {} . {} , " . format ( image . min_rev_full / / 100 , image . min_rev_full % 100 ) ,
" (legacy min_rev = {} ) " . format ( image . min_rev )
)
print (
" Maximal chip revision: " ,
" v {} . {} " . format ( image . max_rev_full / / 100 , image . max_rev_full % 100 ) ,
)
2022-01-10 02:07:12 +01:00
print ( ' Entry point: %08x ' % image . entrypoint if image . entrypoint != 0 else ' Entry point not set ' )
print ( ' %d segments ' % len ( image . segments ) )
print ( )
idx = 0
for seg in image . segments :
idx + = 1
segs = seg . get_memory_type ( image )
seg_name = " , " . join ( segs )
print ( ' Segment %d : %r [ %s ] ' % ( idx , seg , seg_name ) )
calc_checksum = image . calculate_checksum ( )
print ( ' Checksum: %02x ( %s ) ' % ( image . checksum ,
' valid ' if image . checksum == calc_checksum else ' invalid - calculated %02x ' % calc_checksum ) )
try :
digest_msg = ' Not appended '
if image . append_digest :
is_valid = image . stored_digest == image . calc_digest
digest_msg = " %s ( %s ) " % ( hexify ( image . calc_digest ) . lower ( ) ,
" valid " if is_valid else " invalid " )
print ( ' Validation Hash: %s ' % digest_msg )
except AttributeError :
pass # ESP8266 image has no append_digest field
def make_image ( args ) :
image = ESP8266ROMFirmwareImage ( )
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 ) :
with open ( seg , ' rb ' ) as f :
data = f . read ( )
image . segments . append ( ImageSegment ( addr , data ) )
image . entrypoint = args . entrypoint
image . save ( args . output )
def elf2image ( args ) :
e = ELFFile ( args . input )
if args . chip == ' auto ' : # Default to ESP8266 for backwards compatibility
args . chip = ' esp8266 '
2024-02-10 17:13:25 +01:00
print ( " Creating {} image... " . format ( args . chip ) )
2022-01-10 02:07:12 +01:00
if args . chip == ' esp32 ' :
image = ESP32FirmwareImage ( )
if args . secure_pad :
image . secure_pad = ' 1 '
elif args . secure_pad_v2 :
image . secure_pad = ' 2 '
elif args . chip == ' esp32s2 ' :
image = ESP32S2FirmwareImage ( )
if args . secure_pad_v2 :
image . secure_pad = ' 2 '
elif args . chip == ' esp32s3beta2 ' :
image = ESP32S3BETA2FirmwareImage ( )
if args . secure_pad_v2 :
image . secure_pad = ' 2 '
2024-02-10 17:13:25 +01:00
elif args . chip == ' esp32s3 ' :
image = ESP32S3FirmwareImage ( )
2022-01-10 02:07:12 +01:00
if args . secure_pad_v2 :
image . secure_pad = ' 2 '
elif args . chip == ' esp32c3 ' :
image = ESP32C3FirmwareImage ( )
if args . secure_pad_v2 :
image . secure_pad = ' 2 '
elif args . chip == ' esp32c6beta ' :
image = ESP32C6BETAFirmwareImage ( )
if args . secure_pad_v2 :
image . secure_pad = ' 2 '
2024-02-10 17:13:25 +01:00
elif args . chip == ' esp32h2beta1 ' :
image = ESP32H2BETA1FirmwareImage ( )
if args . secure_pad_v2 :
image . secure_pad = ' 2 '
elif args . chip == ' esp32h2beta2 ' :
image = ESP32H2BETA2FirmwareImage ( )
if args . secure_pad_v2 :
image . secure_pad = ' 2 '
elif args . chip == ' esp32c2 ' :
image = ESP32C2FirmwareImage ( )
if args . secure_pad_v2 :
image . secure_pad = ' 2 '
2022-01-10 02:07:12 +01:00
elif args . version == ' 1 ' : # ESP8266
image = ESP8266ROMFirmwareImage ( )
2024-02-10 17:13:25 +01:00
elif args . version == ' 2 ' :
2022-01-10 02:07:12 +01:00
image = ESP8266V2FirmwareImage ( )
2024-02-10 17:13:25 +01:00
else :
image = ESP8266V3FirmwareImage ( )
2022-01-10 02:07:12 +01:00
image . entrypoint = e . entrypoint
image . flash_mode = { ' qio ' : 0 , ' qout ' : 1 , ' dio ' : 2 , ' dout ' : 3 } [ args . flash_mode ]
if args . chip != ' esp8266 ' :
2024-02-10 17:13:25 +01:00
image . min_rev = args . min_rev
image . min_rev_full = args . min_rev_full
image . max_rev_full = args . max_rev_full
if args . flash_mmu_page_size :
image . set_mmu_page_size ( flash_size_bytes ( args . flash_mmu_page_size ) )
2022-01-10 02:07:12 +01:00
# ELFSection is a subclass of ImageSegment, so can use interchangeably
image . segments = e . segments if args . use_segments else e . sections
2024-02-10 17:13:25 +01:00
if args . pad_to_size :
image . pad_to_size = flash_size_bytes ( args . pad_to_size )
image . flash_size_freq = image . ROM_LOADER . parse_flash_size_arg ( args . flash_size )
image . flash_size_freq + = image . ROM_LOADER . parse_flash_freq_arg ( args . flash_freq )
2022-01-10 02:07:12 +01:00
if args . elf_sha256_offset :
image . elf_sha256 = e . sha256 ( )
image . elf_sha256_offset = args . elf_sha256_offset
before = len ( image . segments )
image . merge_adjacent_segments ( )
if len ( image . segments ) != before :
delta = before - len ( image . segments )
print ( " Merged %d ELF section %s " % ( delta , " s " if delta > 1 else " " ) )
image . verify ( )
if args . output is None :
args . output = image . default_output_name ( args . input )
image . save ( args . output )
2024-02-10 17:13:25 +01:00
print ( " Successfully created {} image. " . format ( args . chip ) )
2022-01-10 02:07:12 +01:00
def read_mac ( esp , args ) :
mac = esp . read_mac ( )
def print_mac ( label , mac ) :
print ( ' %s : %s ' % ( label , ' : ' . join ( map ( lambda x : ' %02x ' % x , mac ) ) ) )
print_mac ( " MAC " , mac )
def chip_id ( esp , args ) :
try :
chipid = esp . chip_id ( )
print ( ' Chip ID: 0x %08x ' % chipid )
except NotSupportedError :
print ( ' Warning: %s has no Chip ID. Reading MAC instead. ' % esp . CHIP_NAME )
read_mac ( esp , args )
def erase_flash ( esp , args ) :
print ( ' Erasing flash (this may take a while)... ' )
t = time . time ( )
esp . erase_flash ( )
print ( ' Chip erase completed successfully in %.1f s ' % ( time . time ( ) - t ) )
def erase_region ( esp , args ) :
print ( ' Erasing region (may be slow depending on size)... ' )
t = time . time ( )
esp . erase_region ( args . address , args . size )
print ( ' Erase completed successfully in %.1f seconds. ' % ( time . time ( ) - t ) )
def run ( esp , args ) :
esp . run ( )
def flash_id ( esp , args ) :
flash_id = esp . flash_id ( )
print ( ' Manufacturer: %02x ' % ( flash_id & 0xff ) )
flid_lowbyte = ( flash_id >> 16 ) & 0xFF
print ( ' Device: %02x %02x ' % ( ( flash_id >> 8 ) & 0xff , flid_lowbyte ) )
print ( ' Detected flash size: %s ' % ( DETECTED_FLASH_SIZES . get ( flid_lowbyte , " Unknown " ) ) )
def read_flash ( esp , args ) :
if args . no_progress :
flash_progress = None
else :
def flash_progress ( progress , length ) :
msg = ' %d ( %d %% ) ' % ( progress , progress * 100.0 / length )
padding = ' \b ' * len ( msg )
if progress == length :
padding = ' \n '
sys . stdout . write ( msg + padding )
sys . stdout . flush ( )
t = time . time ( )
data = esp . read_flash ( args . address , args . size , flash_progress )
t = time . time ( ) - t
print_overwrite ( ' Read %d bytes at 0x %x in %.1f seconds ( %.1f kbit/s)... '
% ( len ( data ) , args . address , t , len ( data ) / t * 8 / 1000 ) , last_line = True )
with open ( args . filename , ' wb ' ) as f :
f . write ( data )
def verify_flash ( esp , args ) :
differences = False
for address , argfile in args . addr_filename :
image = pad_to ( argfile . read ( ) , 4 )
argfile . seek ( 0 ) # rewind in case we need it again
image = _update_image_flash_params ( esp , address , args , image )
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 = esp . flash_md5sum ( address , image_size )
expected_digest = hashlib . md5 ( image ) . hexdigest ( )
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 = esp . read_flash ( 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 read_flash_status ( esp , args ) :
print ( ' Status value: 0x %04x ' % esp . read_status ( args . bytes ) )
def write_flash_status ( esp , args ) :
fmt = " 0x %% 0 %d x " % ( args . bytes * 2 )
args . value = args . value & ( ( 1 << ( args . bytes * 8 ) ) - 1 )
print ( ( ' Initial flash status: ' + fmt ) % esp . read_status ( args . bytes ) )
print ( ( ' Setting flash status: ' + fmt ) % args . value )
esp . write_status ( args . value , args . bytes , args . non_volatile )
print ( ( ' After flash status: ' + fmt ) % esp . read_status ( args . bytes ) )
def get_security_info ( esp , args ) :
2024-02-10 17:13:25 +01:00
si = esp . get_security_info ( )
# TODO: better display and tests
print ( ' Flags: {:#010x} ( {} ) ' . format ( si [ " flags " ] , bin ( si [ " flags " ] ) ) )
print ( ' Flash_Crypt_Cnt: {:#x} ' . format ( si [ " flash_crypt_cnt " ] ) )
print ( ' Key_Purposes: {} ' . format ( si [ " key_purposes " ] ) )
if si [ " chip_id " ] is not None and si [ " api_version " ] is not None :
print ( ' Chip_ID: {} ' . format ( si [ " chip_id " ] ) )
print ( ' Api_Version: {} ' . format ( si [ " api_version " ] ) )
2022-01-10 02:07:12 +01:00
def merge_bin ( args ) :
2024-02-10 17:13:25 +01:00
try :
chip_class = _chip_to_rom_loader ( args . chip )
except KeyError :
msg = " Please specify the chip argument " if args . chip == " auto " else " Invalid chip choice: ' {} ' " . format ( args . chip )
msg = msg + " (choose from {} ) " . format ( ' , ' . join ( SUPPORTED_CHIPS ) )
raise FatalError ( msg )
2022-01-10 02:07:12 +01:00
# sort the files by offset. The AddrFilenamePairAction has already checked for overlap
input_files = sorted ( args . addr_filename , key = lambda x : x [ 0 ] )
if not input_files :
raise FatalError ( " No input files specified " )
first_addr = input_files [ 0 ] [ 0 ]
if first_addr < args . target_offset :
raise FatalError ( " Output file target offset is 0x %x . Input file offset 0x %x is before this. " % ( args . target_offset , first_addr ) )
if args . format != ' raw ' :
raise FatalError ( " This version of esptool only supports the ' raw ' output format " )
with open ( args . output , ' wb ' ) as of :
def pad_to ( flash_offs ) :
# account for output file offset if there is any
of . write ( b ' \xFF ' * ( flash_offs - args . target_offset - of . tell ( ) ) )
for addr , argfile in input_files :
pad_to ( addr )
image = argfile . read ( )
image = _update_image_flash_params ( chip_class , addr , args , image )
of . write ( image )
if args . fill_flash_size :
pad_to ( flash_size_bytes ( args . fill_flash_size ) )
print ( " Wrote 0x %x bytes to file %s , ready to flash to offset 0x %x " % ( of . tell ( ) , args . output , args . target_offset ) )
def version ( args ) :
print ( __version__ )
#
# End of operations functions
#
def main ( argv = None , esp = None ) :
"""
Main function for esptool
argv - Optional override for default arguments parsing ( that uses sys . argv ) , can be a list of custom arguments
as strings . Arguments and their values need to be added as individual items to the list e . g . " -b 115200 " thus
becomes [ ' -b ' , ' 115200 ' ] .
esp - Optional override of the connected device previously returned by get_default_connected_device ( )
"""
external_esp = esp is not None
2024-02-10 17:13:25 +01:00
parser = argparse . ArgumentParser ( description = ' esptool.py v %s - Espressif chips ROM Bootloader Utility ' % __version__ , prog = ' esptool ' )
2022-01-10 02:07:12 +01:00
parser . add_argument ( ' --chip ' , ' -c ' ,
help = ' Target chip type ' ,
2024-02-10 17:13:25 +01:00
type = format_chip_name , # support ESP32-S2, etc.
choices = [ ' auto ' ] + SUPPORTED_CHIPS ,
2022-01-10 02:07:12 +01:00
default = os . environ . get ( ' ESPTOOL_CHIP ' , ' auto ' ) )
parser . add_argument (
' --port ' , ' -p ' ,
help = ' Serial port device ' ,
default = os . environ . get ( ' ESPTOOL_PORT ' , None ) )
parser . add_argument (
' --baud ' , ' -b ' ,
help = ' Serial port baud rate used when flashing/reading ' ,
type = arg_auto_int ,
default = os . environ . get ( ' ESPTOOL_BAUD ' , ESPLoader . ESP_ROM_BAUD ) )
parser . add_argument (
' --before ' ,
help = ' What to do before connecting to the chip ' ,
choices = [ ' default_reset ' , ' usb_reset ' , ' no_reset ' , ' no_reset_no_sync ' ] ,
default = os . environ . get ( ' ESPTOOL_BEFORE ' , ' default_reset ' ) )
parser . add_argument (
' --after ' , ' -a ' ,
help = ' What to do after esptool.py is finished ' ,
choices = [ ' hard_reset ' , ' soft_reset ' , ' no_reset ' , ' no_reset_stub ' ] ,
default = os . environ . get ( ' ESPTOOL_AFTER ' , ' hard_reset ' ) )
parser . add_argument (
' --no-stub ' ,
help = " Disable launching the flasher stub, only talk to ROM bootloader. Some features will not be available. " ,
action = ' store_true ' )
parser . add_argument (
' --trace ' , ' -t ' ,
help = " Enable trace-level output of esptool.py interactions. " ,
action = ' store_true ' )
parser . add_argument (
' --override-vddsdio ' ,
help = " Override ESP32 VDDSDIO internal voltage regulator (use with care) " ,
choices = ESP32ROM . OVERRIDE_VDDSDIO_CHOICES ,
nargs = ' ? ' )
parser . add_argument (
' --connect-attempts ' ,
help = ( ' Number of attempts to connect, negative or 0 for infinite. '
' Default: %d . ' % DEFAULT_CONNECT_ATTEMPTS ) ,
type = int ,
default = os . environ . get ( ' ESPTOOL_CONNECT_ATTEMPTS ' , DEFAULT_CONNECT_ATTEMPTS ) )
subparsers = parser . add_subparsers (
dest = ' operation ' ,
help = ' Run esptool {command} -h for additional help ' )
def add_spi_connection_arg ( parent ) :
parent . add_argument ( ' --spi-connection ' , ' -sc ' , help = ' ESP32-only argument. Override default SPI Flash connection. '
' Value can be SPI, HSPI or a comma-separated list of 5 I/O numbers to use for SPI flash (CLK,Q,D,HD,CS). ' ,
action = SpiConnectionAction )
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 , nargs = ' ? ' , default = ' 0xFFFFFFFF ' )
def add_spi_flash_subparsers ( parent , allow_keep , auto_detect ) :
""" Add common parser arguments for SPI flash properties """
extra_keep_args = [ ' keep ' ] if allow_keep else [ ]
if auto_detect and allow_keep :
extra_fs_message = " , detect, or keep "
elif auto_detect :
extra_fs_message = " , or detect "
elif allow_keep :
extra_fs_message = " , or keep "
else :
extra_fs_message = " "
parent . add_argument ( ' --flash_freq ' , ' -ff ' , help = ' SPI Flash frequency ' ,
2024-02-10 17:13:25 +01:00
choices = extra_keep_args + [ ' 80m ' , ' 60m ' , ' 48m ' , ' 40m ' , ' 30m ' , ' 26m ' , ' 24m ' , ' 20m ' , ' 16m ' , ' 15m ' , ' 12m ' ] ,
2022-01-10 02:07:12 +01:00
default = os . environ . get ( ' ESPTOOL_FF ' , ' keep ' if allow_keep else ' 40m ' ) )
parent . add_argument ( ' --flash_mode ' , ' -fm ' , help = ' SPI Flash mode ' ,
choices = extra_keep_args + [ ' qio ' , ' qout ' , ' dio ' , ' dout ' ] ,
default = os . environ . get ( ' ESPTOOL_FM ' , ' keep ' if allow_keep else ' qio ' ) )
2024-02-10 17:13:25 +01:00
parent . add_argument ( ' --flash_size ' , ' -fs ' , help = ' SPI Flash size in MegaBytes (1MB, 2MB, 4MB, 8MB, 16MB, 32MB, 64MB, 128MB) '
2022-01-10 02:07:12 +01:00
' plus ESP8266-only (256KB, 512KB, 2MB-c1, 4MB-c1) ' + extra_fs_message ,
action = FlashSizeAction , auto_detect = auto_detect ,
default = os . environ . get ( ' ESPTOOL_FS ' , ' keep ' if allow_keep else ' 1MB ' ) )
add_spi_connection_arg ( parent )
parser_write_flash = subparsers . add_parser (
' 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 )
parser_write_flash . add_argument ( ' --erase-all ' , ' -e ' ,
help = ' Erase all regions of flash (not just write areas) before programming ' ,
action = " store_true " )
add_spi_flash_subparsers ( parser_write_flash , allow_keep = True , 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 on flash '
' (mostly superfluous, data is read back during flashing) ' , action = ' store_true ' )
parser_write_flash . add_argument ( ' --encrypt ' , help = ' Apply flash encryption when writing data (required correct efuse settings) ' ,
action = ' store_true ' )
# In order to not break backward compatibility, our list of encrypted files to flash is a new parameter
parser_write_flash . add_argument ( ' --encrypt-files ' , metavar = ' <address> <filename> ' ,
help = ' Files to be encrypted on the flash. Address followed by binary filename, separated by space. ' ,
action = AddrFilenamePairAction )
parser_write_flash . add_argument ( ' --ignore-flash-encryption-efuse-setting ' , help = ' Ignore flash encryption efuse settings ' ,
action = ' store_true ' )
compress_args = parser_write_flash . add_mutually_exclusive_group ( required = False )
compress_args . add_argument ( ' --compress ' , ' -z ' , help = ' Compress data in transfer (default unless --no-stub is specified) ' ,
action = " store_true " , default = None )
compress_args . add_argument ( ' --no-compress ' , ' -u ' , help = ' Disable data compression during transfer (default if --no-stub is specified) ' ,
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 )
2024-02-10 17:13:25 +01:00
parser_elf2image . add_argument ( ' --version ' , ' -e ' , help = ' Output image version ' , choices = [ ' 1 ' , ' 2 ' , ' 3 ' ] , default = ' 1 ' )
parser_elf2image . add_argument (
# kept for compatibility
# Minimum chip revision (deprecated, consider using --min-rev-full)
" --min-rev " ,
" -r " ,
# In v3 we do not do help=argparse.SUPPRESS because
# it should remain visible.
help = " Minimal chip revision (ECO version format) " ,
type = int ,
choices = range ( 256 ) ,
metavar = " { 0, ... 255} " ,
default = 0 ,
)
parser_elf2image . add_argument (
" --min-rev-full " ,
help = " Minimal chip revision (in format: major * 100 + minor) " ,
type = int ,
choices = range ( 65536 ) ,
metavar = " { 0, ... 65535} " ,
default = 0 ,
)
parser_elf2image . add_argument (
" --max-rev-full " ,
help = " Maximal chip revision (in format: major * 100 + minor) " ,
type = int ,
choices = range ( 65536 ) ,
metavar = " { 0, ... 65535} " ,
default = 65535 ,
)
2022-01-10 02:07:12 +01:00
parser_elf2image . add_argument ( ' --secure-pad ' , action = ' store_true ' ,
help = ' Pad image so once signed it will end on a 64KB boundary. For Secure Boot v1 images only. ' )
parser_elf2image . add_argument ( ' --secure-pad-v2 ' , action = ' store_true ' ,
help = ' Pad image to 64KB, so once signed its signature sector will start at the next 64K block. '
' For Secure Boot v2 images only. ' )
parser_elf2image . add_argument ( ' --elf-sha256-offset ' , help = ' If set, insert SHA256 hash (32 bytes) of the input ELF file at specified offset in the binary. ' ,
type = arg_auto_int , default = None )
parser_elf2image . add_argument ( ' --use_segments ' , help = ' If set, ELF segments will be used instead of ELF sections to genereate the image. ' ,
action = ' store_true ' )
2024-02-10 17:13:25 +01:00
parser_elf2image . add_argument ( ' --flash-mmu-page-size ' , help = " Change flash MMU page size. " , choices = [ ' 64KB ' , ' 32KB ' , ' 16KB ' ] )
parser_elf2image . add_argument (
" --pad-to-size " ,
help = " The block size with which the final binary image after padding must be aligned to. Value 0xFF is used for padding, similar to erase_flash " ,
default = None ,
)
2022-01-10 02:07:12 +01:00
add_spi_flash_subparsers ( parser_elf2image , allow_keep = False , auto_detect = False )
subparsers . add_parser (
' read_mac ' ,
help = ' Read MAC address from OTP ROM ' )
subparsers . add_parser (
' chip_id ' ,
help = ' Read Chip ID from OTP ROM ' )
parser_flash_id = subparsers . add_parser (
' flash_id ' ,
help = ' Read SPI flash manufacturer and device ID ' )
add_spi_connection_arg ( parser_flash_id )
parser_read_status = subparsers . add_parser (
' read_flash_status ' ,
help = ' Read SPI flash status register ' )
add_spi_connection_arg ( parser_read_status )
parser_read_status . add_argument ( ' --bytes ' , help = ' Number of bytes to read (1-3) ' , type = int , choices = [ 1 , 2 , 3 ] , default = 2 )
parser_write_status = subparsers . add_parser (
' write_flash_status ' ,
help = ' Write SPI flash status register ' )
add_spi_connection_arg ( parser_write_status )
parser_write_status . add_argument ( ' --non-volatile ' , help = ' Write non-volatile bits (use with caution) ' , action = ' store_true ' )
parser_write_status . add_argument ( ' --bytes ' , help = ' Number of status bytes to write (1-3) ' , type = int , choices = [ 1 , 2 , 3 ] , default = 2 )
parser_write_status . add_argument ( ' value ' , help = ' New value ' , type = arg_auto_int )
parser_read_flash = subparsers . add_parser (
' read_flash ' ,
help = ' Read SPI flash content ' )
add_spi_connection_arg ( parser_read_flash )
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 ' )
add_spi_flash_subparsers ( parser_verify_flash , allow_keep = True , auto_detect = True )
parser_erase_flash = subparsers . add_parser (
' erase_flash ' ,
help = ' Perform Chip Erase on SPI flash ' )
add_spi_connection_arg ( parser_erase_flash )
parser_erase_region = subparsers . add_parser (
' erase_region ' ,
help = ' Erase a region of the flash ' )
add_spi_connection_arg ( parser_erase_region )
parser_erase_region . add_argument ( ' address ' , help = ' Start address (must be multiple of 4096) ' , type = arg_auto_int )
parser_erase_region . add_argument ( ' size ' , help = ' Size of region to erase (must be multiple of 4096) ' , type = arg_auto_int )
parser_merge_bin = subparsers . add_parser (
' merge_bin ' ,
help = ' Merge multiple raw binary files into a single file for later flashing ' )
parser_merge_bin . add_argument ( ' --output ' , ' -o ' , help = ' Output filename ' , type = str , required = True )
parser_merge_bin . add_argument ( ' --format ' , ' -f ' , help = ' Format of the output file ' , choices = ' raw ' , default = ' raw ' ) # for future expansion
add_spi_flash_subparsers ( parser_merge_bin , allow_keep = True , auto_detect = False )
parser_merge_bin . add_argument ( ' --target-offset ' , ' -t ' , help = ' Target offset where the output file will be flashed ' ,
type = arg_auto_int , default = 0 )
parser_merge_bin . add_argument ( ' --fill-flash-size ' , help = ' If set, the final binary file will be padded with FF '
' bytes up to this flash size. ' , action = FlashSizeAction )
parser_merge_bin . add_argument ( ' addr_filename ' , metavar = ' <address> <filename> ' ,
help = ' Address followed by binary filename, separated by space ' ,
action = AddrFilenamePairAction )
subparsers . add_parser ( ' get_security_info ' , help = ' Get some security-related data ' )
2024-02-10 17:13:25 +01:00
subparsers . add_parser ( ' version ' , help = ' Print esptool version ' )
2022-01-10 02:07:12 +01:00
# 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
argv = expand_file_arguments ( argv or sys . argv [ 1 : ] )
args = parser . parse_args ( argv )
print ( ' esptool.py v %s ' % __version__ )
# operation function can take 1 arg (args), 2 args (esp, arg)
# or be a member function of the ESPLoader class.
if args . operation is None :
parser . print_help ( )
sys . exit ( 1 )
# Forbid the usage of both --encrypt, which means encrypt all the given files,
# and --encrypt-files, which represents the list of files to encrypt.
# The reason is that allowing both at the same time increases the chances of
# having contradictory lists (e.g. one file not available in one of list).
if args . operation == " write_flash " and args . encrypt and args . encrypt_files is not None :
raise FatalError ( " Options --encrypt and --encrypt-files must not be specified at the same time. " )
operation_func = globals ( ) [ args . operation ]
if PYTHON2 :
# This function is depreciated in Python3
operation_args = inspect . getargspec ( operation_func ) . args
else :
operation_args = inspect . getfullargspec ( operation_func ) . args
if operation_args [ 0 ] == ' esp ' : # operation function takes an ESPLoader connection object
if args . before != " no_reset_no_sync " :
initial_baud = min ( ESPLoader . ESP_ROM_BAUD , args . baud ) # don't sync faster than the default baud rate
else :
initial_baud = args . baud
if args . port is None :
ser_list = get_port_list ( )
print ( " Found %d serial ports " % len ( ser_list ) )
else :
ser_list = [ args . port ]
esp = esp or get_default_connected_device ( ser_list , port = args . port , connect_attempts = args . connect_attempts ,
initial_baud = initial_baud , chip = args . chip , trace = args . trace ,
before = args . before )
2024-02-10 17:13:25 +01:00
2022-01-10 02:07:12 +01:00
if esp is None :
raise FatalError ( " Could not connect to an Espressif device on any of the %d available serial ports. " % len ( ser_list ) )
if esp . secure_download_mode :
print ( " Chip is %s in Secure Download Mode " % esp . CHIP_NAME )
else :
print ( " Chip is %s " % ( esp . get_chip_description ( ) ) )
print ( " Features: %s " % " , " . join ( esp . get_chip_features ( ) ) )
print ( " Crystal is %d MHz " % esp . get_crystal_freq ( ) )
read_mac ( esp , args )
if not args . no_stub :
if esp . secure_download_mode :
print ( " WARNING: Stub loader is not supported in Secure Download Mode, setting --no-stub " )
args . no_stub = True
2024-02-10 17:13:25 +01:00
elif not esp . IS_STUB and esp . stub_is_disabled :
print ( " WARNING: Stub loader has been disabled for compatibility, setting --no-stub " )
args . no_stub = True
2022-01-10 02:07:12 +01:00
else :
esp = esp . run_stub ( )
if args . override_vddsdio :
esp . override_vddsdio ( args . override_vddsdio )
if args . baud > initial_baud :
try :
esp . change_baud ( args . baud )
except NotImplementedInROMError :
print ( " WARNING: ROM doesn ' t support changing baud rate. Keeping initial baud rate %d " % initial_baud )
# override common SPI flash parameter stuff if configured to do so
if hasattr ( args , " spi_connection " ) and args . spi_connection is not None :
if esp . CHIP_NAME != " ESP32 " :
raise FatalError ( " Chip %s does not support --spi-connection option. " % esp . CHIP_NAME )
print ( " Configuring SPI flash mode... " )
esp . flash_spi_attach ( args . spi_connection )
elif args . no_stub :
print ( " Enabling default SPI flash mode... " )
# ROM loader doesn't enable flash unless we explicitly do it
esp . flash_spi_attach ( 0 )
2024-02-10 17:13:25 +01:00
# XMC chip startup sequence
XMC_VENDOR_ID = 0x20
def is_xmc_chip_strict ( ) :
id = esp . flash_id ( )
rdid = ( ( id & 0xff ) << 16 ) | ( ( id >> 16 ) & 0xff ) | ( id & 0xff00 )
vendor_id = ( ( rdid >> 16 ) & 0xFF )
mfid = ( ( rdid >> 8 ) & 0xFF )
cpid = ( rdid & 0xFF )
if vendor_id != XMC_VENDOR_ID :
return False
matched = False
if mfid == 0x40 :
if cpid > = 0x13 and cpid < = 0x20 :
matched = True
elif mfid == 0x41 :
if cpid > = 0x17 and cpid < = 0x20 :
matched = True
elif mfid == 0x50 :
if cpid > = 0x15 and cpid < = 0x16 :
matched = True
return matched
def flash_xmc_startup ( ) :
# If the RDID value is a valid XMC one, may skip the flow
fast_check = True
if fast_check and is_xmc_chip_strict ( ) :
return # Successful XMC flash chip boot-up detected by RDID, skipping.
sfdp_mfid_addr = 0x10
mf_id = esp . read_spiflash_sfdp ( sfdp_mfid_addr , 8 )
if mf_id != XMC_VENDOR_ID : # Non-XMC chip detected by SFDP Read, skipping.
return
print ( " WARNING: XMC flash chip boot-up failure detected! Running XMC25QHxxC startup flow " )
esp . run_spiflash_command ( 0xB9 ) # Enter DPD
esp . run_spiflash_command ( 0x79 ) # Enter UDPD
esp . run_spiflash_command ( 0xFF ) # Exit UDPD
time . sleep ( 0.002 ) # Delay tXUDPD
esp . run_spiflash_command ( 0xAB ) # Release Power-Down
time . sleep ( 0.00002 )
# Check for success
if not is_xmc_chip_strict ( ) :
print ( " WARNING: XMC flash boot-up fix failed. " )
print ( " XMC flash chip boot-up fix successful! " )
# Check flash chip connection
if not esp . secure_download_mode :
try :
flash_id = esp . flash_id ( )
if flash_id in ( 0xffffff , 0x000000 ) :
print ( ' WARNING: Failed to communicate with the flash chip, read/write operations will fail. '
' Try checking the chip connections or removing any other hardware connected to IOs. ' )
except Exception as e :
esp . trace ( ' Unable to verify flash chip connection ( {} ). ' . format ( e ) )
# Check if XMC SPI flash chip booted-up successfully, fix if not
if not esp . secure_download_mode :
try :
flash_xmc_startup ( )
except Exception as e :
esp . trace ( ' Unable to perform XMC flash chip startup sequence ( {} ). ' . format ( e ) )
2022-01-10 02:07:12 +01:00
if hasattr ( args , " flash_size " ) :
print ( " Configuring flash size... " )
detect_flash_size ( esp , args )
if args . flash_size != ' keep ' : # TODO: should set this even with 'keep'
esp . flash_set_parameters ( flash_size_bytes ( args . flash_size ) )
2024-02-10 17:13:25 +01:00
# Check if stub supports chosen flash size
if esp . IS_STUB and args . flash_size in ( ' 32MB ' , ' 64MB ' , ' 128MB ' ) :
print ( " WARNING: Flasher stub doesn ' t fully support flash size larger than 16MB, in case of failure use --no-stub. " )
if esp . IS_STUB and hasattr ( args , " address " ) and hasattr ( args , " size " ) :
if args . address + args . size > 0x1000000 :
print ( " WARNING: Flasher stub doesn ' t fully support flash size larger than 16MB, in case of failure use --no-stub. " )
2022-01-10 02:07:12 +01:00
try :
operation_func ( esp , args )
finally :
try : # Clean up AddrFilenamePairAction files
for address , argfile in args . addr_filename :
argfile . close ( )
except AttributeError :
pass
# Handle post-operation behaviour (reset or other)
if operation_func == load_ram :
# the ESP is now running the loaded image, so let it run
print ( ' Exiting immediately. ' )
elif args . after == ' hard_reset ' :
esp . hard_reset ( )
elif args . after == ' soft_reset ' :
print ( ' Soft resetting... ' )
# flash_finish will trigger a soft reset
esp . soft_reset ( False )
elif args . after == ' no_reset_stub ' :
print ( ' Staying in flasher stub. ' )
else : # args.after == 'no_reset'
print ( ' Staying in bootloader. ' )
if esp . IS_STUB :
esp . soft_reset ( True ) # exit stub back to ROM loader
if not external_esp :
esp . _port . close ( )
else :
operation_func ( args )
def get_port_list ( ) :
if list_ports is None :
raise FatalError ( " Listing all serial ports is currently not available. Please try to specify the port when "
" running esptool.py or update the pyserial package to the latest version " )
return sorted ( ports . device for ports in list_ports . comports ( ) )
def expand_file_arguments ( argv ) :
""" Any argument starting with " @ " gets replaced with all values read from a text file.
Text file arguments can be split by newline or by space .
Values are added " as-is " , as if they were specified in this order on the command line .
"""
new_args = [ ]
expanded = False
for arg in argv :
if arg . startswith ( " @ " ) :
expanded = True
with open ( arg [ 1 : ] , " r " ) as f :
for line in f . readlines ( ) :
new_args + = shlex . split ( line )
else :
new_args . append ( arg )
if expanded :
print ( " esptool.py %s " % ( " " . join ( new_args [ 1 : ] ) ) )
return new_args
return argv
class FlashSizeAction ( argparse . Action ) :
""" Custom flash size parser class to support backwards compatibility with megabit size arguments.
( At next major relase , remove deprecated sizes and this can become a ' normal ' choices = argument again . )
"""
def __init__ ( self , option_strings , dest , nargs = 1 , auto_detect = False , * * kwargs ) :
super ( FlashSizeAction , self ) . __init__ ( option_strings , dest , nargs , * * kwargs )
self . _auto_detect = auto_detect
def __call__ ( self , parser , namespace , values , option_string = None ) :
try :
value = {
' 2m ' : ' 256KB ' ,
' 4m ' : ' 512KB ' ,
' 8m ' : ' 1MB ' ,
' 16m ' : ' 2MB ' ,
' 32m ' : ' 4MB ' ,
' 16m-c1 ' : ' 2MB-c1 ' ,
' 32m-c1 ' : ' 4MB-c1 ' ,
} [ values [ 0 ] ]
print ( " WARNING: Flash size arguments in megabits like ' %s ' are deprecated. " % ( values [ 0 ] ) )
print ( " Please use the equivalent size ' %s ' . " % ( value ) )
print ( " Megabit arguments may be removed in a future release. " )
except KeyError :
value = values [ 0 ]
known_sizes = dict ( ESP8266ROM . FLASH_SIZES )
known_sizes . update ( ESP32ROM . FLASH_SIZES )
if self . _auto_detect :
known_sizes [ ' detect ' ] = ' detect '
known_sizes [ ' keep ' ] = ' keep '
if value not in known_sizes :
raise argparse . ArgumentError ( self , ' %s is not a known flash size. Known sizes: %s ' % ( value , " , " . join ( known_sizes . keys ( ) ) ) )
setattr ( namespace , self . dest , value )
class SpiConnectionAction ( argparse . Action ) :
""" Custom action to parse ' spi connection ' override. Values are SPI, HSPI, or a sequence of 5 pin numbers separated by commas.
"""
def __call__ ( self , parser , namespace , value , option_string = None ) :
if value . upper ( ) == " SPI " :
value = 0
elif value . upper ( ) == " HSPI " :
value = 1
elif " , " in value :
values = value . split ( " , " )
if len ( values ) != 5 :
raise argparse . ArgumentError ( self , ' %s is not a valid list of comma-separate pin numbers. Must be 5 numbers - CLK,Q,D,HD,CS. ' % value )
try :
values = tuple ( int ( v , 0 ) for v in values )
except ValueError :
raise argparse . ArgumentError ( self , ' %s is not a valid argument. All pins must be numeric values ' % values )
if any ( [ v for v in values if v > 33 or v < 0 ] ) :
raise argparse . ArgumentError ( self , ' Pin numbers must be in the range 0-33. ' )
# encode the pin numbers as a 32-bit integer with packed 6-bit values, the same way ESP32 ROM takes them
# TODO: make this less ESP32 ROM specific somehow...
clk , q , d , hd , cs = values
value = ( hd << 24 ) | ( cs << 18 ) | ( d << 12 ) | ( q << 6 ) | clk
else :
raise argparse . ArgumentError ( self , ' %s is not a valid spi-connection value. '
' Values are SPI, HSPI, or a sequence of 5 pin numbers CLK,Q,D,HD,CS). ' % value )
setattr ( namespace , self . dest , value )
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 :
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 ) )
# Sort the addresses and check for overlapping
end = 0
for address , argfile in sorted ( pairs , key = lambda x : x [ 0 ] ) :
argfile . seek ( 0 , 2 ) # seek to end
size = argfile . tell ( )
argfile . seek ( 0 )
sector_start = address & ~ ( ESPLoader . FLASH_SECTOR_SIZE - 1 )
sector_end = ( ( address + size + ESPLoader . FLASH_SECTOR_SIZE - 1 ) & ~ ( ESPLoader . FLASH_SECTOR_SIZE - 1 ) ) - 1
if sector_start < end :
message = ' Detected overlap at address: 0x %x for file: %s ' % ( address , argfile . name )
raise argparse . ArgumentError ( self , message )
end = sector_end
setattr ( namespace , self . dest , pairs )
# Binary stub code (see flasher_stub dir for source & details)
ESP8266ROM . STUB_CODE = eval ( zlib . decompress ( base64 . b64decode ( b """
2024-02-10 17:13:25 +01:00
eNq9Pftj1DbS / 4 rthCQbkiLZXq / Mo2w2yQItXCEcKddL28gvelxpwzZXcj34 / vbP85Jl7yaB67U / LFl5ZWk0M5q3xH8265 / OF / / evB1oNUlNmmTjeCfYrOy5bZ8VmycXypxcGH1y0dT328aYP2n7Ue0nbj9J + 5 lw \
O + FPQe0iP7mo2t + 0 mp5c1I3X0FXbMNworGv80PZzfer2cY6Nc / ft5KJUruH3Ni0sleVG03gNfKEYvNB9e9n + Wg6etf9WDb8OC6kVNu64b6sGouUtdWjX1w5Va2y0S6pjfmxbTNUJNtr56xS / tf / W40unWPWtXVmd \
DZ597c2ew / IrwVLj4d1mbrK2Ufj4K1fiuC7dXPojofv0b5GD43sPoqL2YFWa + U15fOi3k0E7HbTHg / ak1z7vtRb9vnowt879dug3ej33 / Ibtj2EGY5bD9en + ms2gjd / jQTsZtNNBOxu0zaBd9tt6AI / u9Q / 8 Rq / n \
1 G + cDtb1R370Ne34E3noOp66jseG7eya9uSatrmyfX5F66crWk52X9our2wvrto7134 + dd9mn4Sj809Y9xDy5hopMIBcDyDRAyzq3nhrfuOm3 + gNe8dv7PuN536jR5BfBpJmAKcdtMtBu05W7BL9J + 7 iP1oK / F4p \
8 XulyO + VMr9XCl3X / sSP5r2hY28HTnDnZbjjxrzTUpYcCe406F0z9pvVOm + JMr2VbrZW63l9cc5WqzWisNhaAIOwaeZ9CYCmERq3zX0GVRpG0cftm9RvT51Se7jHL + SpGwr + zWlKQAMo80YFAcwdWyJxOSZ7WEEH \
C7C1q88zQEXyrG2l8DoMncEXLU / aQQCBRn3xAsxaVLo / wDuzdiqk3FRRX13sA5DwlfvNXsC / tzP3IEIJEsmbYNAVNAm818qvPL4fkr2HINCXFqgaF3c77oPwTHqcbMJSaO0mW80m / OKIyMitv8Ew824PeY / T3iuJ \
JoO + BSJybCQd4iPhPN3hX6NAb0To9cR7bnwmUM7d + erh3iPiJFvyrzZ1ja0WBLL0H7cLFyu1k72nwnPL + + NaGcXPTNnnQeeN + J9ukumyTUlOW + o12vk3vRHTVeAyyL2V99zAovdLb6eY0WB3Nf4ACTe08howkhst \
L3k / NPdhhEq2o3GPiWBDfdpp + tNOzT9lqSINJ5TUXQ / MQnnzF6nXqKBhsXHHe6HpSY3ShwyGqj0R4hsV2v8Re8rqrlOoAKH2BHOj + 4 yV + / TAhpXllELPKSHRNWzXeIlUnB7M8c / OY / xz8dDx1Bf8rUgf8bey / Iy / \
VQbdn + lDcpiVOJw1Lmn6eEPm5ndDggmgz0H0sWORs9ooVWTXItyhrE5tK6XK2LYCrootCJ / YglyLLeOtZklb + i5YEbPMKhLGVMkKI / OxDSDFX0YT6G1IKBeAZs0QwAZU5f52SHrLsoLAfjCYDt / z5Po3ntCiWNre \
ceKo / QIYikN6vwMGn2r / 6 RENXy2tSJOr3jQRYQyBoOGBSkmwHvTlI8If8HDJcDh + Hn / s87eyEVsRn9esBOiLli8FQyYOAZuJZbWCOjkygCZMrTnIDb2ms1 / kHUZgxb8MBH3ePdVxtAc8FqFcR + d1HZ + MZ8 / 2 Yxtt \
ILe1ckGXCQQZ4oBVU88 / oLeTWCwSg8pRqyhoQL / qrV039xb0iGzU5yhdRtGzfWIQYhZhJLZ2QOZpGx224BT08 + oZ46BF0wSlyoS4WSc8VMlGWp5549c1rLV9OGZgxsQxSs8puNVJCw9FwMUPaB8iItsXcgpmbKb7 \
QFF4WoLBwFKsyRZBw71N9lYezgCRwJvU / xiet / OWGOQA1MnoBp2i5qgb2fLIQIwSbYUNiOT9mywf4 + bqegLYuQ82 / GhweU1 / Lc61yTr + 0 + W6e3X + nteKGhGQ1B / 2 c / mZpDhExJbm5SkA9MJ5fA2RrJ3hS5kVVG03 \
8 UvGUIGvz4iGlX4AnPkDzogmdOm9YvyeCnt + xSh0Jof8HPegvIVGEf + UTNyI7ReAeNx + sQj6zoy3WI0WXCKYfI29brKxKJIZ2M34PK7UoTyhHZDzd + O + u93A4MD207iJCtAp9JoBttHHzLeVgwlliarnvHfEkCzJ \
9 zbZ97wY3APIRknHZ7njhVP2QfQ / lsVfY2nAvIzoNdOz3oIdiOHpZvjiMVBjDZQSKcEGbYQd6lKWtwC6f4hrFaCBfkxQg9Vf65s0IAokhBMtcATyuxVzVSC9atX94uZqJp + TLeoNRTB / vTTOEcUgVsB8LB7J / BsA \
mjFpmM37tkrDTAzsVFWeegN6xZ3EMtnF6tcOGC + ZZ5DLi0pvSD / ocYYcuAu6U1OABWar444n3YwxcHDh6I + RHvMtyG8z / hXF4t3O1V05ncmQ9DFOF9KCLp9u4nY8mptb91gtlQFFUExMs5djCMfA5ga5DtQnW + AR \
DFUlN8GcWQNdnjL7Zt94erNJvHfqmecoCSAZBdHBF6WVv + QO5mhrN6boToPbuBRpgQoN5stBw8Ae0 + YJvF6waml8uh91frcuDp62bu1fYYjJDTBKSY2Nj / AP62LYRMCJFC9D7fZ0P5jGAf086dgFdZ8CbRtswJiy \
JggtA9w57vL9QbAdjSWHk / gKnDhuQLkOqsV1MZx7cKx2l4Nf8czhDx7f7sS5iTvIidTAct0IE6d7wBsZN / ud0kCrezzHFaggXb1hEB4nw6kvCA4waiDhU7E8jN8TXyCJUBNsfR6LjaiWTLmHn21BXq1A8z1YB + M7 \
DoHvkaq1s2 + OyG7fV08PKCfTNzAzcsBAVgLOy5qs + GZyl7CJshOdFFr4nOQHSgdQWQhX429uCpmgnw + DZ4NkCUo3R + aHbHYB3seOrWGr1tMbsHPUd / Dv04jAWmKF + Mu3lJtE + VsxlilS1fXOJ + Zz9BrTPmkAeton \
5 CFU4w4cpb7ZRtvwyxcY2 / jyQAzUp6QE270ypjl1hgm3R57hBbNnrxg3JulYph53ETNdehyTLTuPqyQUm9PIit + z34TNcvwdNA9 / Rp37GB0w30BsyFzWJ5uwh8dizKHdAeRIxUwEKJGa6Ubc9Wlkd2b9PJku1yad \
jYKjAfbFLqhinPWQLafE7YW5CMG5jBeStENCmnvw6q7nfTDX9L2PWB52Xgc8xuBGsEYsU4Mwwoyyuhcc0p5TGsQ6jl / S + MJCpedCUFaXTZ8iG3ZgLsRFvPz24RNzL2KAy / HrLjFXC78rEqpJ1LkIKE2DJHCM7 + T2 \
4 SNed + YJRjf1kXQ7suuXuzoRcU07qt1mhkPb5BwV4TsY9C15HzB6EUe4QxmnRnivhXeTRs7jcBt + sKiSef0tPjZzj6Ut7CrqEYPfYsY5IzAOs7dvYPCjcL0It8 + + ZKvKHrx + QXajSY / sDZxhhz0Mp / 1 Aj6UtyAUy \
6 fwx / QbbEDagBVJqdQT / jrfeAKh2CwHZOLKju89BlH2A / bRLMgBCBK09uumZPOB3QTTyBCAk63iLJrcuF6BnvugScUCBC1BK8NckI9D / I + aeFgI2fbFcAKByxi + lvBahOC06OQODOX0PWvx0H8Z8g + GF4lvAzyJc \
7 zhKkgsmhqctZW6EbFKAKwBRvQr3pBENf5ETTVFtsUy2Mc + 9 pl4D / t6T2Y6wpKewV3oAzCmMppIWkqN2wi2Y8Gtyl6riqMvZAXpN / DfaoEbdEicItvw30czqI4i + En + A8oFaCzP + dyerdDIL9VGYEtccs8wgpyMI \
XgCsx + SNUBAvSIXXTTDmeKSvY8WW1bH9rNtLupnyZrL668xLVzQYb1ckSZDYsEeQQxPVBOBVzLsNpVZI7qolS8hyt97ZZwSyfkCfhx8UYKBAdQzSpRaGAEowNwjkp2tCD7RXEnqFYok8GMw693ygiq22ljtoPxUV \
l88UZOOrYjfqJ7p0ORXhpwl3JVq + dcCw1imA0Lx1mwjUz / oIuCAI99q / JRfO1Mke0aRE5 + + Yq2VIfa2fbN7dmne51zyWRUMA8ap1Pw + pdqWp8uIDyDz0CdNhuu1qjFjEiBWM6P8WI7wSipFig0OdU8YB8lDEXpgJ \
2 MlV5KyJagcTKWcuM9kwDf9x7MCv1 + xKwboZBX3q1 + pj1yprNGwdggI0WNwUCk09 + gIvgDghfVhBLNCZ3IgJs7dqcUx52fWO + CQCU1kikLb8hbVhurREIGdItVkAlKNr1c5ZwdatYskSNTb6IhLABBGaRUSTfdWZ \
QBRrC0nBNNV7zuDFJIw0xBXwSxU06hRyLmlr7uWN2H5UFyeyAAHdwRVwnoXQGbD4aD6O0ncT6l / 04 oCftPvz / 373 C / 3 fEMDI3OpclK2jco8nEAiXCLy7z7hlve5QE / 7 TKWROp7Y0Bf1b / AxfD4VeL2i3ICNNjhkR \
hkVh6YUVEy / cGV + FoND2Zra + hryeIDslC6C8n8a5dP9VoKEhJAazV5BuLdVTkJrqFQceNA78itiZWs0Tgggs2Kc3glZ7FnYsChRtK9mObmMV4djpz9dA7tcvTn / GkBDweT6nlCd6lYiGDTYW0A1JVy4E02NJ + LMn \
e9EZmAxkL + apNpydH3QSJXoFEn + H4zAGJ5 / Bs1ik4ZjWtgDbqVseZkx6y1uECa2N1gnW / IRDkrmEQ5SZ7UPQpICdWiQQJqO0izoTJoHwKhr41Ocd9SFWytMFAP0S6PVCwpcXc3 / D / Na + lVt0L1Ci7BImdIFhWxCE \
OnwNr + rXXfzT9HJXMyeKInajmskcw6bhK3qnlVjdLgVdo6v5chGT9RSovVKBss2b / XUgRkM0fWnCIrzxapW1cC / u05rlBcyJrlYdeGEbEh8T1I4hzFbt / dXN4PUTK5pqrzoBNP2K6QjEQZNcS5jYMULSU7QPMaC4 \
iwpoXUE + WkUlG59OxN / 3 RDxGAsMHyIes8ygz5RmoSv8w5D7ifeRzt8nm / bwu2 + aOJuYamtQi2VEyLtymy1F6oE0vBcnFO / xp85N0N1pu6CKAUtTxCt5qR1fr2wehGqj2Fpc9TQ52HaEzJBhb9Rqhb3 + fUpS6TCDk \
COoc2WbCCkOJUkTOf + xFCSiOqQ98yYYyQOn9lVsfI1KwgBLXe8pVPuBHxHtkULdwb + EKhabsImCsJ7Yoau8MB0eigRkDIQliCqwWunwoBVQfDgIhkLKhwcBz2EZWjIgVjSVWbL3T3ch + 9 oTZqJVkTqqxHE4YM6p5 \
2 XGwJuc0joSxCdk6Dj + b3sKoVEzlO5S8HcFvI5ajlZdnBe8UKg9hkBxraw6 + oDAPZniaL / a2RxyS4HlG5Dzn4OJbDvnVYyknKqGAx / BebrLtmyEbX + WMJHilpg / YB4XpzEsIJhiW2kW5vY50uYngnWMsdGbziKJ9 \
+ x9j6 + wknTL25OJNnPAnIS9Yh2hDGHK4NPj5Vl1wyrrggH7uaUfTZcIZH5E1s9CcBRgdt7d4HzTTKDh7u3f8fRcmgNnMZHLn7IIxrd6hQnwHzbMzPQvVAt / H2MlbjjOxJaMNFwJBEYjVgK / 0 jCDPuXgF8lxaLyja \
4 FJPTizMwlvwdjR72aXF2tc3SZxgAjgLaFflAW0irH6ztJks788CSt5tTpBZHGMqcCyYYUAsl69imKj8QBEpCmrVwWiPDEqEsWTnJ3UxJCQNz67SbiuXLfj5e6IHPVMMWsqVO4Z + sGolMO8IGPWLi69hXCucsGFG \
gmAyCzl1UZjWg6WpAQmlCZC + wTsA5T0KgDA / + 039 togE / u13YMiAtCmhW44Ozdcw3Az4DrgpR4v7YGZvLsLPSJSjycTR0oJTbS1cuxtbXpoz58yy8dQkRuhdhgo6dl7UDdhhI45rZ5FouRscKxXc19g93fsAze01 \
OKKRK / ZlyDeLMXwYaA4N4vRqBCIgizHVBkZIa9itd / l + HfftP5gM4UwOKIgCRGnK92Sse5r7FH4G76lF85ba3kB7ysGUi6wqfiFZgrjIbpZo770iuxdUzFJQ6yMsIS0lKNmv / fCqs4g8K6iOxIHUxb3r9a3QCtUf \
4 sKLj1e2S59QfI7j9KxwXcyObCEsrXLTV7Fki1ZAsRa4wEQsoYbwXz2626 + I7qeI49ine9KjuyW65xrGNOl8UHHfauAfWVABvVPeproQ3yFF + kIJmcYS2zQ42VQo42102ifwE0JSDkzrCNG37JzdF4GrSemMk02I \
8 I / D2wDHAqORLe2 / I6ikJGkxk + xd2tPxWw1FSSFeDZ7 + hKx + 5836 QT3PB6IkBghV4uYMg3kZlPfW + jmhtgu7C5kiu8GWOw9cfyqrQmlVNv4IPl3t3S / Cjesc / HccxWpxs6H52FhOPnjZZfFYIzX0fo4mWtG49AWQ \
Czw / dBd9n9D0qLndhKxLjCPs9i0xrhrKxqhSElfwsftoZ5W3U + eCoFM89IUfUf + hI / yathzZVkuVzCVHNLFkvDVXIP3bohH + xK8rccVjrn1AN2LCZVuTvsbtscrzOVUMSmCg9QZh / BDYHEVYs8LeHQ8X0BqFYmdT \
MYTWBGF8 + z6f0Oq / 4 DvGmkIaTbHF50GKaMTE0uT6NsUF2apVxokDKpfSBWgUJ8ik8sqiKVbvsaFSPE3l + RoqhSeSGROB52krk33GfM + db / BhOgy1FRhJBhnV00nFCp1E2QDWSUeeToKJ17tSseFJnhXqiVVS7lkk \
f6R6 + phgru5FOquPCir9j9QTOsXFUD0t1Rua4u6lKuomD / sxummPA + NXkh7tbaiiUkx9quQKRlpKncggLH14kciok4qOsLpY61GXNAsbnTjUhE3kJkGBwaePc47XY3AXiQsNqMZz4rgf8A + ctupy9g2b08Sk0852 \
6 vvwUixEnnSP6uE / EFVejLShwCi717p4MaeDH31zEVgqpwB4Z0dI7nktuIxdmJAdtcR17aiFroVatiCJZEojjarMK6YpiRq6WL / MTFC5ug1puwkzhrHJNGFbBQt / 0 FyI2ThNOAxQefrmElq8A9S + 51 IorLiYAGR2 \
toSZAwR / Pbh8I / lWmMfOZ8vYIYae + djB4aeBnj4S7MQedjAlKq5ttiSFRq0UwpMHxSpL6g2nxdlubyqJnHu4FsRMPcQojv5igB2RGc + HB + Cg1Fnpm5Cql7LuBkAzaUOpkV7tCOILw2vbECbCeA2iBhhVv4646npD \
iqOL1kR5XbB / wGVDWAEwBsNEU5h98WlxNV1KqdnfO0HqCVEvtlbxJQErMyOtmbZxfX7EUqSzwL5 + SDPz3 / GDmaeDYGbWN5K6tHW9pEjZMkCfDyRCz + kryenbS1aLVmBe1Vzh8ilfvc566tWIei2HXt9qzw8SHcWf \
4 vZ9ClMgkNnNS7XrgDH + V9pV / bna1XLdQscCp30WuML7M33vr69dDZY5F3 + 264 fWcyG7R28nBqNMdkNz / TNlgje211i4bKEY + YlT / hMstJh / SpWF5mQJ1r9Nurjc9QZZdZksyT9KluQT9sZY9PjixHavFZQi7STK \
NnkaP / UQyOWOiMPCbr3lg1aW / OCzf5H0aAHbkVCuRceMxQrpdnU7WtjRPRIGcrYKTaE9PlIEpoFFzOFBF8wSOyERklRo7M5z8JjL7A2mg6GYAwqWG / vhZIGudAnLzX68LtQrxX / WmUOErFGLkkW4c7qNDENJzsae \
EXLWPDoAdSwe3yEJb0es / VTx8zHzU + UXg / JOpPcL5gnWr / Ic6xyVU + fxnQgfxHdAq2ZcLKhFaLl + ikaBnKkaC4ya9O3rF37VikRj31OA2jivZeGd2ZKgYi1RP9gIqDomJJjcCaGJuI5YxRJ4acWPk6Y7bHXx0ZVu \
e1xdkTAQr / kfW33zHlbjKm9aPpsPyrUgn + bXLLRstP6ObjLCiD / XPaD3jlFzgIS + PMGzfjb18tXu7JZkLsEZb3341hkPqMpQu5ADMMPOZXU / n0CEPOmXkfyvk4dS + IFO03kfW67sgy8F + Oiw1g5WE8A + s3kxHwLP \
MS2sLIy7ysIW / r1TznqgJt2SDJ0UvY7oAZXymLkkef2xw29QEq3lRVfdZ1y8T3pheG + D9qsefwkHGcqFfx7nEvYxzD5myD6gOCH6asXmBT6iLxAnuhBmkvoHSlJyYGssR + 4 kqUixOdYPwGeQMKgxlBGOmCwxn1SO \
f4AvmF0E5nY56yIcc2E3EBJ7gfeAX8Zcvz0hLYCpmjHXYcs + xvOBNQaCLQ1SGaypPqSFyQlrLIFOn0gGjT / jD4z + 8 gIBwSz6jWF1mG + Gi1yloBZW3cZdiTg8AyRAfhL2GcJUeyIvod / kg669pVOvrk96xW / jK37L \
rvht0v8NYKu5bYroNqziQQ6ona5BXA1YuWCU5 + q054jFvgaDV7vBtnOSpyp + AHnZRv8FUIAnKWatqbCCqegAlYKyCcAVnZ7YlVMtv1L + ULuk9PQdHwBr + W8PIt6W + EcOXUDsYrLLZa1YSyQl8tny5QmYyQaJqgzP \
bimmAVSt4o7JqvJYxDIzU83puJpr0DA9mMxXXNgigpbdBSWpR1TuWAduj8LtrbUCaqgrOksFX17wF + hY8bH2xo7WzMniLaGnfRFq6kv7 / G8nizPeF + 60 dEn7p1F4gqWw60FwfixG9YxCO0bR + RYu / 4 y928LGQjgY \
b7ztjp4vEPfbBBPWwOYsWwwcU4Nxilh23S0t5wlgJegaMjkwe1qwZ9V4Z0HLyehb72yUUscgQ2sYG5VnPH / GVRBy1KqLpMvDHIbAcv3W7NjtCgJU9phFH25NPhOAMtwuH1zX9fJzkz3gh0YAwqjAocDkHdZA7A / e \
L / 0 LrsCtbaycsqHCjvdzPoBiLx + kd2onWXbjSpB / DQDYqJ0AnCfwikpGQsnV342dQxl6gZstkVMjZpx453u72fjQnEFHZIQR + w8 / vKETJ1u7HYsrjrE0eR / m2q / 6 iGnPlLGERm6wK + dhvxkvD + ImyTpni8x62L / 5 \
w2 / N7iM + fkOnhXLvOJjliwbQ1M9loxg + JOaO7jFceIJCQQwQlU1 + r5tdl / iT69iF + QGeXpZVQoOlAFDRHolIBpRcuYjEjsP9 / h0WZRziLRUh3lIR4i0V4X0S31r7988MLyzpqlBV74K8U / + + mNOQs1q9a4VI9nkX \
QjA / xyfn8CznI5IoNRszuFihZHMDZS4W5ZYkIyrN2gdqPvsXLbm7iHp3IbRu9QJvOCr9O6zQFpRwFBfJ0sshx6IbvqenA55ZoDQhX1SpvR268jYfpx3kmgiHt9jHaLyEXpjbQ6p / IEyp9eN9BpTuctoTluzfPyFH \
ebvHj3qXVOD9HMfnSwhzBVVULqKipSWZpYtgpt0dPU4zpv6aPa5Aypf6DME / 11 uPhQ9I5KOZYQ67NWmJ3xjoyrly + LsAZxmzjOXWjoo6T6QFnZVSQi5zBRxE93 / BkTnhNjzniIis5MqQul5GZB / / x79w12qyzGtB \
sH48Qzt + R8vphSlVnPHhLJ11pRoiwSq4ZS9PzOMAqZM / Njvba6Md5MpzySliXQ2eugeLAg07qFys + gBHlcixvPc8LnoXV3krYd5XHuWQ7X0uX8Xdj + XuHfkR5TBszVIfMGviyUUDHm2 + d3L + Fgj9rBPgGFdJWEZz \
cFGzPIb14ewlFZhANBspWe6tOqF0yAmCBljESpgm3dpBFqm47IePtwpT5TF7XBrjds297qa3jg3cDQsgQkwZi2GQjk5O8NWHd1lhN5AJKaFkSZePIORp3gIa8QLhZ + xUNt3NX8FKQdH4pdDPRoRHb / ecy4mydIZB \
FvV479ENJwCg73g0xpx5 + kW0vRbs7I0ORUFFtVww8fcVCtFoUVzjYI206VVCjW58nA6Ac2eRufhby8UaVeEp9GZ4LhUJO19xRaC7HaSQQ7lyE1522ThckNtpgt4ar5PUS4KKRdO5lgzZbVCkPes86gwxcZzlohGR \
YSArcpFcYFSgHMYv8dXbDD4vfWV71r + pDWp2sUQQaVHitS7T1HuGbmcu9195JFjat7BJcc + 6 ndrdfSW7Nuo8iFKSlGJiDa8s0Z0zblwwRRwXTKo0fK9TRYdCFrAD9W63DRu1chs25Uw6e / Ao8SIGcFRyCxF / p8jx \
MR / grEI6w97gdSM8FNYkGxlzsuKiqFp3JytUInfFjAUTu5112RdUyKUlEUJuJWEr7JMERsen3eVL53y3QmH / C9b / 0 WerH / zGud + 48 Bvv + 6 xoBpcI5sO2f8mbKe + s0B / In0xD0h6Vb4U3zKnAnOdvmWN9Jm3J4Gwf \
lO47nPmwCRo / ex1PVT697YF32Yo6qmQ3v6GKutW8aCVUIWf1sP6ugSsns1d + UesjOkyz8E7eL10Ll4gBgvyzvdfBPP98cOME3emn + XacppsU32GbFC2Jje7GDoIGYqFxYIX37rHxahu4yRIPpdfobPMFbdasuO + t \
MnzfJ073kuYCv7iQ5Fs6e8O2XOVdnLEUzdgjAuDdNWLr2QO + UxP3khYg693uwiY8kdkcCkUQaF5IpXYZ8tyuoJbYRDVfptSu + 6 VkXxIBgFL5aK7BmoAzsfyxebojPEaZuu5mHUHnXBYiJ7bU7MmPIV / G0AzESrHq \
sia6FsXKK9HJAvclGGmo + SA64E6lc / oD7 + zJ / iJlbNphEqBJo / 0 uQVTzvRyEV3e960QOTGz8ZRT + h3eHuhf0AP6 / FTJlzCV3TfjrkjQ8lMLgQ5GgO11o03J4tR8PkXuP4NhA0 + DlB7a / oyAwZb6 + 66 WfxcrNPK / N \
yxJ + uniFiFHsLiBCo / 4 BZ0AAe6UXeFCZd623HE5RUKZVxzSYnBqcbER8WctSWkRuRiuAw8c4FR4ZiUa8sXoRGWcs4T1oY / QOEBrsjTdC9UW7DI / nfWNZSdF7rTPR3KvYe6gkimwNa7PgwpgmwzsFEjgCU4xHTw + 8 \
i + zS7hCdIGaCxzAVM5AuInfyRGkw6IuDYw6n1JNMYmmRD4Ch4hd8Msm8EEq8yoyjz8Gj4y6Myb3aRWwB / LEPP8ROLlsCBWEo8tgCO2M4m2Ous2v8DjJLWT6 / DpCl66mxzCDD4c + DRP7nBcoULr0T + 9 jtfpa7pr / / \
5 dwuzr3 / OyU1 / H + n + L8kk1ilxnz4f3giyVw = \
2022-01-10 02:07:12 +01:00
""" )))
ESP32ROM . STUB_CODE = eval ( zlib . decompress ( base64 . b64decode ( b """
2024-02-10 17:13:25 +01:00
eNqVWm1z2zYS / iuyEtmRL + kAFEUCvutEdh3ZTtKp3TaKk1PvSoJkk7uMx3Z0Y8VN / vth3wiQUtO7D7JJEFzsLnaffQF / 31 vV69XewaDcW66V8T + 1 XDfp0 + Vau + gGLtqbIl2u69LfVDAtPMkO4XLHXxf + 1 yzXTg1g \
BKgm / lljO8OP / J90MFgt19YvVSf + NvO / aVhNKXhrSm8Z7f9nHQqeFaDt2TGGuC9gTHmStQriqHLYAAt + NPdTgUYKdIBT3SFoaZqu / KiKpDYDFr0xsaiec3i / 6 jHlmfEcwEyjHi5O6SnOLP6Xmf3V4afVoN2JQW9P \
8 GeEoxrU5US8kkgqR9oIC7OkyFUZKdj2OLTJO7oII6jqxadNUTzFz340AWmGajCgrdkmjlIz4rcWZv08vy + 2 CKzUVaQ412fL9gTqcrV9TdJ0f0xpetsptmggID + ckA565o3cmCPg + QFZbQFaN0EQV5AlG5gF2gfF \
4 i5M / KC3woLvjRn65Vl / 6 Et + UFtgFf5MlFrVwXBwmQnrCN / UpPCmmTEJDfT9q5pVJ2p0QHfCY6zOAq6b / kba5RXtQKX / T607sGYUwLCN2cm39IpXBAvp8NERCnBg7DDsllXRLigz4yvT2ziTxvezmVyd0jC + Y9OW \
lDhGqWWLBowRoM0yZ0n8ptWyaRHS2Oi6BRfLopvYL8pkHGECb5LsbGemBYhhbLL8A0TS3hGdJcYdj7UvueSS3 / Bc2jLGwOSs76LRAuhKReBGFhOt4nUOezvnyWmQvJ5GhsLmjevHRuAQxMroVaSHdjwnmFHqMxGA \
J9oTqPUcbSXa1p4BBr3a5aol0x2 / 6 r61IqariFFEJTTLSDmsyC5InD8BT2ZY8n9K0E1zmZ5PHLk0bKma + Hd1 + van8 + XykEIJvV1zRES / PPYKy3gHMDY9ZJ + fkrOCVuvJJupBMNOw5xXhRFmR2KZ18m5ca + 3 RuIMh \
3 bp0 / PMjoHIwHMO / RykQcMrGQGy6EQQd6JoicFM8PX2IioC5Q1JJESJLJchREXCaCJwDa9 / CziAOJAQ2tWyFJuMskmD9AnwYkjRpqdaR9yXBBsWtegG0wKtBPJ68F6 / cYWVFWIdcq20KfQCYuyUBgFAg4UAJogAZ \
I96SEBsk4SlMAPncoYTQJE4BcESPDXKYggUM9yfqb4ccI5LxpT2Ng8oThGRQaCEbPe1z + ZiYaEMrKIHnKt6whF0TptW0C / C8Klkl5RaVyBzH5j7p0sZ3haZhOvlX6FQ8J92csxm1SZIDSQ2T8AwtiO91OWSoQgGY \
myb9o1RKri / jGw9QFeL / DBDkG / YBQLF2GDJikNffZDs7xAPESM22IFE6lsnr7Cpe / yIktOje + vw7xx6fRrukw7StHt + 4 / fBWyc64wc8GZjwfziZgD4sJpa3oFLzrZfQ2QHRRdPOwDh9RPMA6oQzv4EbkZLVkLhmx \
F5SANuz + eMODuTgWxZV / Zi4f4s18F99cxzer + GYd34BSf2M8rFTrTLDeO3arnSJkyHG2rIvmjOTUCHRl0CT6c / p4efUGCB01PCXKKoJIF6EeQZklMS5eQcSavvZ7ZNjqM9mIitbF + dvMr93AG3 / RsgtM3l8gS / Pd \
aCZu6ewj6V0zaEttRWZ2vRZ7zbv26vRXIxTw53momqgimf6IEfD + BoW4jbx7KvZ / DSnaIAQcjJ1oDKPgIsKOxjRv8CvE3wFxVW5o + X54f5wT7LqaE12kc77aLrfJQdIJ4V8dFwYOfLtI5h + YjIr1C08eBd1WG / Fm \
wYlZTclTIVkm7u8HIlOWNUUktM + cc1QwjOzZcvWW8tcieSFA + Irr20nw6aLhNXLKNJxpngELP + 7 CEqAJqLiT16QSyEHAuy3q + FdQ4IAUi3jAwtgNTKgDUODr2TZwqEPeg1EeDfyxlBul + 7 qPY0rsnv5wenhGfLY9 \
CFA2pAyqnFEGhSTgRoUmBtYT6dNebdcrBHG3XLfe0GrWqVz7aStyRUjc3niF7UUU0qh7IomHsNCRRJmISBnqoc / veOmaMepSss / Zh32MVSbhkKU92tCVc3T1kv5BKjplMoAtlsRZU6xTZNs + tl222PeS4jxgH / k3 \
lr9V3brw1bBAWOPcR2Ck7m8kvIXgoymc4uttWDiCmKpeDPOEbXxK79IiL8glnB54j69ydn16LjVHvSMXPn5UGPb2H / S7OmVb0u6wl9RsIPh7exawTFAqSHBIKNM0P5KBA + dxH8tb8fkwZKBjTv31l + 2 YLDVKR0Uu \
2 + iPJbRVZUGu5NIZ1MsQQF1CySHGUehQQFcOkYJdLq7g4 + VBaSXLV2nuObloEMHIkHqgJih0DHdxZOpwT + 57 yOBd708gCqZ / kZS2FeuUsgKdrTrSXnKy4LgGCnqcHB2CzEfcG9Q4aQ8H9e3FcjW + 2 KWCH0OAy + + I \
gq7ZxpBBeXtyyxfYpToGGtfHgwYvTkcdJtOzi3k3c9Hu4dHFkjsHVRIiGUR8MlLCSrBgh1v89XVvyHF8mLsGdDghlNZJNwFCapMQHWAcfKyIxnEObuA8CrLqtqM0dEKb0w85ySg + KHVnF81vklxQJoDGkt0NGp7s \
OM + g8RPQbIMol8x / Yx1lGPCaExz0yyDcNK8DXYdIOH / YWhT2lGKWYJUiJ5ZAmc1dGHdEjyoO4l96EBnFioDMjZCchyYPrFd21psLDHL6ZmWje1yp7JxY2m + YHLL0i0xfru5sABPIC2tOjbrqPo7YbY2K1u0uV7cW \
dxIP / 95 WFejj0uzByirpK3IWtBhNUzRF53 + PBrUMvifmVNqNcu3ESV + i11FVDxPS / oQFdw9QH7di8HMybV + 2 nvWeOWEwDWaDqacaX0igwYwwGXALL5WmXgIhgm5rDp3hgrMhhqzYW1V5XEumuODStJK8uP3BNuUL \
CK5GbyuJMM2dsAFX / fRil1oFqFekxH1j3MSjEcoPfyfXgGWD0Ksr0wUZbVOPyDM8WhxThoI9toIoSfe5Tbuh9WD05UbWeQDS3XIoheKo4MTFo / peqCbaVLztEy6opdXU82AfBf8abJtebCy2T0W50X / lukbd8Q7r \
A07vaXkwU4jgLuox9EQ53SqK3RRlwXqCrc + uGQbJtGQbGPuMi42THThnqCt439CgAF4zF9FSmh + 1 tDLpOI1IhAIvkprMgVOetum5jUysewsPfPwY8QFEnQxEiuSELIJe + cLLmlcQtkfBviAkFEpa17dinevIni14 \
Rfr49BlFeS1xveOPuFwhy6EA62lY8HpFWSIAeQ1B19acCODBWEXd4lqPqyNMCD49JLqAGGbyBTWS / TtqnRt6S / bEe + oq3sMMm6ZXYPnrN0B8dERxpYHE1nL / PI6fVt3DSYRkMAJVkO6aflupoHoIoALrFkNteRzH \
ExB1sk + dFazTICBNJcZzVx3maj2Di4E8yiIbo + jPpp22U3JODLCTo44HrTAMeoBWFk8fsBT9ibNSTIQjdWHqnm4qbEWIak0q42ZBTVc8FUowMq1 / Bl5fxhkPzYkdROddgHbZKXuRgZ03 / 4 H09hUs8AysZMpmaWRv \
6 y5wetJXrYHOj2K03T2WFZ5KrTTqtcXVJkEM5E1UrhWgj16MWbCD1zPwgENK4ez0 + + XVZ + poo2XUkWXgcZalQhO8D / YD0oSK2 + BYw / VYsVyYQt / G6fWYe / wWCwBuXBfcuHDJzvJ2lwxB1VEvu06eYBvrM1wDfmqE \
uiLudxcW2ie6PCYDcBz0yzylSkG1xwzNcYAsa7b5u5o / 6 GxCwCdH6RbUQoyv1rxaj / hgELSIJUz9Ce6 + BEyTTwmgssC6pQ6WBGmyKlsXpt6CSmacRmC78hYurmkZ3c8lXLZ4E87tsAqp5REp2m / HybfPZzSm09gG \
MHp6VuW8omlPmu4pfbXm / Ys7XPmKWYOYBQw4mFK4k5xudTamxBtPdkvqtmE5n92HQkHnDUUgnR9yXKjXDB34uHXOcZQsFIdEtqmvCXfMZNzyTcq4lu8eEmwQ1VTr4 + biCYgjw6Q5Uzm3GDHkQtoDdlfatsqS + NRg \
OTU7QYcn45l / wgS7Col5ju62G0VDPCiq6Vqc0ZvB3h2XajjtI4wDZYRalD96rrIDLBY8MSnWBdOqBLn6QW5TvP0456qs7iWviEHgptgKuxwFRzbS6qp7xb4R / mFSxclEVBuKb89WkBkIpFNlp2YnmMAd8zEJKuqc \
W44MXDbZRAuMWRM + KG3Z3QQ37LRGMlaYaAAfeJHGOYgZjQjpMOUuXXizlPg1OYpLU + g5cvwqywFNw + 47 M4gHT6b + V2jiYOJc / 0 mhC3l1FXs5HhqpYz5chdQPcmFQquZGJdiBlUO0 + PzHoo1jAfyznL7ttEdae3yU \
kol3I + juEdCCFwFQF9OXW85f6WUQ3aUi + pSSAAMO0d8MnT3vnbSBH9vXfKiZP + dGsiWhjMlnp2AtZ5xiWjGyZKvy5IuOazzdPIqYSbi9ICCNKcYgsnj9GijfvAGtX + I3RvbmHFPvT8V6V2Dljk8UsDtwQIxiKcje \
UWCYq78Px9NO35wTz4Uc1 + ofWDEV8V7yqVQjFZahUz / Mq2XMSW3S0egvgGel2YW1bkj6SocoWrBxOjz935VzXx6ggFrA4bvhJjlko5i5lZEXFtxRL20bgdXqOlB27p5gBI82uWKWl7R8JiJnL / kWrU3fslUy3yTH \
J / k + AcJ / U60tgUxhPE + 20 echd62yMZ8PYJjNCACLKZ8R6ZR9CS0uP3s1j76NAROxbRXzgTQMXMOA5bYgNEcCBXN2OX8fcMFyryhMsGdv5o1MOB0tUmI1HP6kW7BMDwRAv9l8arTuD87 / 0 a4AVp5xHyEs8vjPAHN3 \
S26polZG9pq10Tk3bL / kk4XawuFR + FoL8xv8iCoJHzYY1qqkSgo / AJp8pq5pyB93abrKd4e0FaZX426cp6vxA1kXfXUohBtoWNGaw / Zcfi98mEWs4ewnfHi17cy + ks / 0 RLSs8 + owsNLV1d7jAX4u + s + Pq + IWPhrV \
Kk + niVdi6p / UV6vbT + 2 gnurMD1bFqoi + LuWzjT1 + EhOaZGo6TdMv / wXshTKs \
2022-01-10 02:07:12 +01:00
""" )))
ESP32S2ROM . STUB_CODE = eval ( zlib . decompress ( base64 . b64decode ( b """
2024-02-10 17:13:25 +01:00
eNqVW3t33TQS / yo3TvNsu0i2ry13YfMALild6AvSwuacxZbtcvb05CRpIEkp + 9 lX87LG9i2wf9zGlqXRSDP6zUv9bee6u73eebRods5ujQs / A7 + fzm6tVy / 0 wC + 1 v39223cHoU9sLo7gz0b4UIdff3brzQJagGQa \
vvXVqHk3 / JMvwmOVh1 + YqktDSxF + Sz0bDFzSQGfD32JEJLAC5AMF54j7GtrMdSBn1HKapAcuQmsZugKNHOgAs3ZEsKJutg2tAw / fLQ4PvjOHB7TCwC2MaSeMBAbCrA6ezL3TE / qKPeu / 0 nM8I / wehFnhL / 9 RPyeM \
dLAzXlbSECXjaeFxPl4UMtOovawmjFXpz / QQW3BXT + / mKwgUP4TWFBaRGJAjSGG + CvgdEr + dMBv6BRFUdWSla9V + + Slb1WRBY67Wz0kbPG0zlkcb1l8gID / skC9EzhvCijsGhjdJO2vYchdX4WvSWAe9YOthV1EE \
WWgM2lbzu3NJmJs3D49NaLQV8An / ZMZcd1FZcJqMNwhHWtrtvj9kEhboh6GW90320APdjNt4L2t47qdSrM7Oaftb + 39 uuQcNxgU4VrAq + 4 yGhI3gRXr8dIwLeOSqJIqqMkoEOAhe2gM5FUqIuX4 / PJSnEyKPYwBA \
mJocjMaKlBYMB7ChXcaLCXLrRG4KVCr1POBIxat3 + lw06Z6CApaTCHfUswI0YY2r + AfgY8NB9BWtwnPbMMinr3lE4LJqNNylj6dHVE2AR6mO3Mhksqv4XIJ4V9w5jyvvlkpXWMNxfq0HHrGrUUORHqryimDGmA9E \
AL7YQKCzK1QXJdaJDsZ9rc6uBzLj9vPxqGtiulWMIiqhZqrN4Y0cg8RPnwAUHIq2oA50RCiajQSmtE4OagJioX6EsAtSfJc9pBFTHEL9w / MHep2nOGVYSk5GJ2gzCEiaUQesQkuN7gpqUjvGHyIAirGpFlNOFwMd \
6 If7WrCpVezq70gjX0PDMa4prY / fV1Pb / QDxBbAv5TFmThOssbHqxCvY0v1ke3sjvkczPDlvlUA34rYhMWbUO9qtmj2FRtlmp0zjyHFgW + pGfsHhfTVbFx0UkEddqy1MmXq6y42eNEpLNkxxPrbH61ZNu7OrJ96K \
MrDF1yQYPMeVzLoXJYuLt2iN + OTIEef54aBXCYOL4xG4rYOZIlvmMgJHB + DoPR07VB6wTHWdDOeC1tu7LmqsK + LhHilfS + 1 O2an5NgznNSUWvB5ZIhReMS / hS9uwGRWTKu1 + RZs1chbUICaFyowEqgmBdis2jgVE \
HD4STzYdHy95t00y + EI8V5 + vJxVn9YIIxXzi2slJqHN5MgO0dHw6cM1Be2rGaJDwaPuy + 6 Ah4mFPXHE5 + 6 gP4de0pFEfwxtot2ncOTnj4qj / dVwJzwVZkrZdgbIaA3qbbUdHyzMzDW + WSavIXdfNKVZOfXciiAr1 \
BxnsaUaDHWZBR / r52E + aYr / 3 l + qkHuEEr / NnmSeQgDWaLJgmm / / 48 tnZ2RGfDMOA4lvx / L4ggdghyrnHXuWS4AU0wi / nTjUswua8MRlLKx3D0kgC4u44 / yhhhcv3vtsFKo + SPfizmwMBb6pBVS8oaOtr9OJUnHIo \
zlvDKljzU1FH09agWBTuWXTcFsBJtmBW6ymr36ACeNItsBONWH5LJ0lsmHa1Eb5tOLU + U55eGv0deZ7EaIhzYOgnnkcjaNpGvV0HW7W5R0A48w1AgfvpUQcyhq1QJZqMyzqBDrAofyRhWqqjS2yxew59JYin62Q / \
M58e8QlJ915XJwMMPxSzbGsR9XJ9GBWj + 9 f65e0LFrLzVyxg56XN + JfkxWxskDAguLDmY / JEzT / X0z6PBpUw5tnnnhU5H1sK6bZWkXu / H0cJWM34mR2Fr5NDwBNzmlGMj94wy6RRowEcwNbr6HXEhzKxmEhp4hiM \
RQyJgjB14gOB8xlW4ddJ5a2WxM / 65 UK / XOuXW / 3 CLhSi00Y9SQk0chShwRyjih6zJvKqnWviNqA48h9g3ccImXDIkMhzFcCCo847bevvwSVfvuJThKbSKS + g4P5mvUmhY3YZhUIsvX / + A0esxfY0Fjp8R9bTMlKI \
BSKluLgV7SrH2uXLP4RJ8MeBDauyLssXCMPvL9l8Dgm0LYlUgoyacsETASK1RQz4W55RuGFAhIRbwYDYzDzj98n7L0o69b7jvUM6z67XL9uVsNCMTI6gFnmBdbp6yySyiMkS09aDG6tSBpGR09BYU4AErnXtle0v \
WGubhkmF1VYla2PJ0TgAVPElHJUfYZdg6BNxmL4nzRCNol39QFoAXcHu + WUPo92LbVg07Am4L + kr2hwwo3Aoa1SssKcN7CnqcM0RMgPETOhgY / toLCof4aA2YzjAvgIHSxKJqAceDhHgzCK0B09Pjh6DJzhENY5N \
Qnp48ulgSMmghDYVpbifyNpjpgS / xNStOzw8mOS4JgkxRvqdIcqAaadJR8fhixuZh8NRmm9OHvMzJlcvNld5ZIkHhbO4dlkajmlUuuiMQBtna40KpWsdV7ccbr8WHxjk1OXCZX7Bzcb8IgbM0RElu2Yei + / sloMX \
/ XfpOuwdRPDgHnAHovr2Uoh7eXL5Hj7d / ip9zY2Mz38deKFd / HmgXnAEBhGt4CEc1KQ + O1eg7FTyJurthgCxpTBt8OlxFKwvfZKUKTkF6I8NUPCEY0a7y19QoTvpcnQDotjAf624n / v3Bi8JaW / Qmeg7jJw78kjJ \
S92Hs7zBkUZHa0TPhHuhTYAKw + AxdPOovPKfklGN8fMckgiJmkpBNP8qNt2 + / + NMCfSp + jkLSHcZfW3iOayOU4q + / kj2hI0LgN + Upv4 + Nz4ATv2sgpI9S6J7ujd4E8qhQixuBsOC3vOPbEzadZOgnF4Q1EGDnu3B \
2 fmaCYEWuNjTBeEcbo1i1na2kJS0FLx5cJd8Bi6QNZJ1kzQcBAZouzgSBQ3AadbIHjRf4qHWbvOUqrGxG8x0yss1Wp9qv4Z1GhucGwvI3u1 / Ac7Q8v6Ks3HTGoTlmKMbFpsKMfLRinfs1 + r0HPVMToTI2zXKAnuu \
PLeqX + 8 vzdsvCez6ntO0qkxXMl1WPgfCtWZUHCuZbPWR6WbiBoGlC6X1k1G6HBhTmnDsUb7liME8xn7cegetyRFoTcLqj0mr3TuYEj + 0 kN55TgGX9eUNjAfl8l / h4yZGW1syOrviByptQaHlIln0WAPa3Rzr7B7r \
vsr1IBtuqgqg0WnCPACk1nnEJBAj4S3pPn5Po49BbFGOijjb6EVA5FJhLQgk0CygEidpfhhsU51CCkpUdeNsVtdtSeNWnBecrbYVyzzaWfAvbzhDltJaPa1zp / weAgiTrrgMBoewuFr0qutQ + imOMc2SYH8sD6z2 \
MbVzzBRqNNeARIbzae1SIsZtZQg0M2DgGsfMgEvZX8UPbcGpI9zIdMUJc1MsuF1T8crWNNMpxPLCMxRyBocIVYp60moCG4akajURU7wfmLS8q / 14 V58zU80w5I2a1OpJz3ZGpEmj6FOkuXqucv + De3jFKc10NPc / \
I3O59DCjPVjowXY0eFNqAbnuk0VGoIJipT0fse5V2r1MxFln6Y + r448n3UAHAV8wVQb645EYRNjlHTvS / obQCOKkTqqDAE5w5Jxy2ys7fx5yi258XE1zQ36 / w1N5yulQjGVnJehEsjoQLblPyLp5yUjAcca6EnoD \
CJr7a8Kspv4bY4pGDggyZQ140qq4mTaFYe4h2ekOE59baxDc8cfic830NgePCn1BIA0qYQLyTq / g3 + wCztYi1hub / JQ9AAQZNJ0XCX3DmmHNYZ2LMQCG2JDmwgTyzMycPoK9veIgF3IeuAGG694lH4AiEqPC5 + ku \
cLGK0UjNv345zlVPpmpUogjUBo6BN6qc1Q0s3DiOMYo4y5A9V6u6XjtRNV9T2Lya / URf / DI2J0rvRTwMTpXVvR6SDTUKelGgKBXIGRCsDHq5HfMyTiFJg + XFTOqM4NBXjarpuJmCb + sET5QIFm073kykJQgzjPVc \
j2gqNZzNa + NewtHY3KKy / xBal5LpRBI7UMxY / f5HPHlGYGGCOUMx / xlDvvgVXYAs8rPYJR4RMBC0Lp8yCAwlxDco4ft7GGnzOaox2s5W57I7X6tRmAKh0rlwUtzQOSL5 / AfZ + AHcrk0CKDBboJYNFyf8pFxWwXxV \
FvPL3awq955rd + yoAp5agDg8tLC2poQV9Kk4SkvlmuR8RMgjAbgdPhURY4JLdas / lQwZmFa / 4 Jy7W7cEG7dmWIorTsUBxYtZw16xZUHd / P3smqvoFSUhKr4uoE6SuyHEIRk / lSsE6Ibvvl59M9hbKigwHyLKaHxg \
9 NdyCeAdeNngNIGmePc56EyGOsNRxiy6 / J1LHOcX + yQKP4ZjmXL7RCY7lABycxwVSYQ9Cp4LujKkEzu1QLtClRuG53T1SGWA8iPChCoLXH4gfAwbfSUMVNHCgOxaDr89l9qqds4QhAzQj4zP7h4Xryq5bFJzSI75 \
QyxKb + O6r1RZBjM3fnEfHRUMHMGy17pyI / ewUJdBL1ARDOdDfc4KgzF9f8I + hMYYtTereyM5aFTZwDjxNwY49 / J2U24PbLyHxruYKxBsgwAFPTP7q7pfUsqqxgdbtM1xjrjh0wEHoWXkW0jla4QaDnACTlffSXuF \
ESAUtMp / fEu0MKGZay2o6i8fDkW3frids / / u9s2T5A6DZeClX / wXBuxTbruBzBLURSxXLwHzSQR3ePsRIHRISOKaeua6vsSUzy7nwItE458bUtUYjF5iPlBIy2HEBDsWtAfGTRkvgv2LE / HhdQuft / 6 NjtsLcFiu \
YqgjoGP4lpMtD8DE22X / 1 ciL799E41vikrZLFUhwYoTDjBgl7nAKHAkhyN + ksbweZDzq8Bl71ZJNGFm2Fjvb8qm85 / T + jh01hJjbDOChXn6Bi90n5Kkts + fst + sSOAfUWMnD + AqoG4J7dPaWDNZ8O3FUgaXo9IKO \
iks5cEXLar9Zk95IKc + D98js8bwDhmGt3gvYhk0yR15senRbaO2 / xHCiruLgGGUnOviX4HoBDws + hw2dQ / TQID3itlAHObuK8XP3x7kEWNGn0xVd0np7Lxkv3Dw0jAnfGsAApOA9LenOjkslNX80vkolKBwckh25 \
TMC4jMpiuCSC9g7NoegboG / Xbi3WXCqgkbAfPpX9yOh8O / twLiS6l2TEeOfVK2R9PwPoLV25wHzKHuWUdtbbKleKnK7WpUIm / kYiHF1xDlVdtDUEzqrW7jiFXfN9rI4qXFf1GV4gx0JwCkYwd9tCp6SCE + VC1pi7 \
Ds2dbB8ZN44gwbHr + FpVrdrkckbFl1YoJrxHUYcEh626a2DtGjOKud0GGAUJVjZKvJbThjcpoYNEMY3cmsebAO4o4g8Us5ohvV4ZSoeCN9JUo5v2irb378mFbrnM16phlu / wGDtc8Ei56leFB / C5l08mmoqbh9NB \
GQa8KIR1EF + vAAbmbotdiY4zuiQF6R60vIPxKPe + n + fmnH9MpBt0Ul + JtimL4 + FKgy + + m36qiN4LNFebYD / AX + iWWawVYi2w5RpNyiowLWl383YDORiNbRRuWA6FqnRLXa2KQMkl2AkxfJaCBlRYx4Q / dvOskmL4 \
ED / sxmvtqKt42zxVTknBVtgMF09gxAdaO37tOP7EJEK5nfCaij + 5 kmf2NmVePImJEO7BTaM5xfh5RDLWUmINe / M9XT3XMEcr / 5 lBllaMhiaRlfFe7TxY4P + f + fe76 / oK / heNNWVWmWVR5OFLd359dTc0lstlGhrb \
+ rqe / Hebvj3Y4S8jQkWaGpP / / j8geRE3 \
2022-01-10 02:07:12 +01:00
""" )))
ESP32S3BETA2ROM . STUB_CODE = eval ( zlib . decompress ( base64 . b64decode ( b """
2024-02-10 17:13:25 +01:00
eNqNW3t31Eay / yozY4wfmF21ZkbqJrnBDslgks1dQ4gxrM9ZWi0pJIf1MWayY3thP / tVvdSlh7n5w / aM1I + q6nr8qqr9n511db3eeTQpds6vzeL82ibn18n8bfMrUV98eHB + HYrHzdc4JjviUeb8ui6an7r5Hp41 \
A5MJvXGu + WubBxlNhGftDEsz2sHe0mB8mdNLD8 + TdfOQX1YJ / U2SaTMi6y2Bo9LzhpUKxjYLmbIZksSdk3Y + / DScmaC + xFHwJn4hTqeRTd4Rt2ve1b7zeBe4nTQfXcOzazao0uYJyGCp9ziKHADP1XJMIIvIeyuM \
SjNRzGpk + rpqZGZhvQWsA8SazoKOhnUl8hJfvX2p + WxohpnlULhrOnub3Ds9prc40v + ZkcOTOJi0Yp8MDgA4FHJQMkG4ajWNdavdlRl0Sh1Rl1yPPJe + ow / xCUr49GZUnT41T1NgZZbAmcKJjGpVckj0VkJsM645 \
DufVEZdKaqFPlusx1KWqv6fjv2SVluQd / GMRvLK2hf5 + eCifjmEvnuMW7Woi9aI10gnrnW / Owcybt80b17BWeVJplHKmWMt61uuYG6uFXqR7StvmPJ3F3hnpQG1tZBt + QMtNc8rBMRf8rJ0U0jOe0VDpCm1X6bP + \
+ asN8Jx8pEY2E6ni5xyOa8WDF5FzsWFvyAXhc0 / MR8rQPAo1Fddr9MHAmqDDSfKJFoA3xoMvW8EkrXBa8S + 0 XN35ul2m + / yiO2tNRJeKUFT5AG + UcFiQXQ08YJEtD9ftJ3JNZolfMhIPjg4 / ksECKzpcRE / WLJfk \
5 CXNnbNsf9blyyfRE + DQZn4JRC9pHbA4kIR1R / ALFNdNHb1Dh2FgY3YntoR4cMA7e5ia / eZ / e87nXPd37wzsak4c9Gb + ZnbyHSlolVGQLXKmeuCPvyS0aj4kAMNXxj + hE8EGovFd0RDHriOkZoh3z0FIKK4pCQlG \
l31hhcAkWn7bJY1YBuYj26G8i + 1 mlQBbg3tlNwpOJ53wwZcjnPcWKDjWtgvw1Ko / dQZmYEQobgaugrYgaU2IaTt / SDN6B6TXzv / k2uHPra1 + 0 HhPyC + G6jEZqauRveNj / EPug5 + 9 PO5wjn6IVdGH39soeBSJBoEX \
KaOM + V0MsXYvUMPW3Sc4YzkyQ6 + 46 L9fqYHLGJ1jeASa8PRBynP + wWUnHFFT5amr4f4AghLTRZtDu4zCrxMBt0X7Cca5Ssl0GiM2rifYihBnC14LhYfGHV00SttBZCcc8HuHmEYk4b1SPX5u091oP6B4EStVMarC \
bONl9x / u8qZCyqRHSsVywpV + oMN1KsJZieQ + ZYkYNGaOL1XeIrbGOiyEQzfjEGx5RjBiM3zYjg4elQAgBLgbCE6wha8PQRqz1prI8p3djlwN4XbfqYypDkhPQJrJ9pWa2jEU1jqDlCgNYh0px / T0 / IpJbt6UBR8P \
q3z7PKxIpppWqybxUshPpYhpFyirL1P4SPKPNG4Ak + W7KWYtauW96sVdzPqW2e0I9fRQb8WK / II + vWz91GH7iXApLAR65hnzgC50JDh / aGgUWlvvUMXToOaAPytJ9 + 7 yZa3lVsy / UUDffsGLjUTeim2qLJGuC318 \
wOH8Plv6nAMfaviD1H / ZeTkbWSD7QqyJeoRU1mzKOKCfAPv0STeZGITZsWBjeFvJJuo6 / uCAheRnjnFEA + / etZ9IMVrHN9viJAkElIITAJ0tyuiKCs8O0DJEAYsQYACnjkCgAiPY4pyOD8kwWPdMtk32wP4nE85O \
CsFo8JkNorBb7IfwJShewQAwB1R8wN4DlL54AEGAAyMq1t / Eax7yG86MOsCsuAOYpSvio5U0Lqdn2vHwYJqZSqMQpTNrAV / zs0qlG6afvSLeR3RWkMr / 6 VQTEwCkQxCLE8TSnNmEziLgHk / gy9b + 7 t5WVLZKSh5i \
2 I2ClEje2eJkzijdgXiLM0CGb34 + OT8 / 4 sCNLG8LcnjXwEif0Yxk8e7er + yO0206pG6WfQ2rNZ8C2n7yChSk + VVk / + IjTRjLqCNIhknFyZq1DN3DyW / Aa7NxMWeplWTVBfsp9Fecu6Dpg6WEDIoFRTq5bAbkkxeN \
BBgqOUTUj37Z5qS1Pjk7UkhduTjQ + uBA3OI1HKNx1G8 + fHRekAqXjHvgs + TQGDvhdI35HQh5S4JoxILuDggF / wRKKhY6TLsSUp8BxlKe + C6ZoPotRjBiW9gLj2YcTxZ7L3fhAB / N9uDP7gIWCknKS3dd2SWpWO0f \
M65W9SOscDSaVZIOXrefSBsrsu26VsUhg2Y9AarmknQMIONPaJNsf6DkhWTMhhUmjQcn7oqyTFJbn6siSRptVz73KmgIfoC3XtJeCMQqY4gay059co / QUT8K4DHXYymNFSVVTtalkFwgX + FIymeprv3hE7Nn2zzM \
z / bnydei1unemTtuIcpDdEuoX3Lyy / FMKFZgz / QXmPRv4PyQvCHkxQgoAj8 + 3 OCfbDqlIwHfY5K7TpWVuXXTz3Ux6wIN9El4xFCVUx8CGi2axDgCCq + 8 UuhlIS1yN1 + whjrs9zcZsjCEDT / MDtGKT7cIxXmFugs1 \
GyzaS + hxI3SomhQGsELmbJOo4fQIcQ24w7MMYwf5Xh / eO / 3 lUn + 51 l / W3QOHzMZJdWDqY15lfP1aVT + WhTB7AhY3ecFoCbEBqvYHCUVPuQyD0Pixymy5BFMm4 / iRbPVDtDh0k / aW7CF2PB5i7PjISYGBzLu + XHcU \
KVeINUdvshaVAgfvM5XKaOQu + NiA0 / yCNrnwRmWbKYbKz / TN6eqjofnCDPAAWZpvs7jGQK5k / 4 pCRsMMCHDC5JiKcjgBX0GsgPlk / wqtpIz9azEA6Lez219yOqjAZWUKmlf3iTLD / pbw + uUfDELynBwK2HEVeg4M \
EePqPQOmJLp7J6mmZK8oz4EbPW1meQqAgPvkUNDVZ + + JpqKoovMHwA7lBqiU19nTaLfBaoURLm4pPrdFaVNsVuIlPpGZupwATkhAaxNL4shFgTckMihXeFbBuoZAD5KGzdHkmQg3wKoVxTGJRW4Z / YZPtN + oIqZG \
e3KxEWPgpV + OdyqCf / z346NnAOkoHfwOSMkhFcTAHd5yqEvzwxLSQnjVomH7Foif8jiUZQIDTOxPvpRnsQL61h4yJOgkMr2sZ / BsgevuqBo3VWN7rSnLBRfbCVaHwwXj5iMvsfEibVH8Yhaq / 1 gnXeJFelE2OKfQ \
faDa / oRO0lI5cLdNsKEM2Kbe7x / xM8xjDX65pGwSyouG34KxFPTsgNMh + uRk + uL8nD9iV6RQdUle9rrNBIhV / vKO9AwfA2CoqdwEy0GAbrfn5wcvOdxX4R8UBHgD0IsQG4IXM5gcvo2mr7PkTu84ZIwAQ67iNc4C \
Q0x / nOXgwmxF / LbAGEtcBjFkHt2dt1xdLcH9lFP5YATj799rURwuD5V1OFssq6eQ6lYtNjJUTcimK07bwCpLrlpwvbHNZjgOwMl2MbrysIX6bCSD55p0LB24CCbDGJg0X8EWL1TybR4PEwPcizVXiMUl / R2FYnyf \
DxfqTB6ECvDExaDpPj + ZRbS8F5GKsSp6WRfTRuxgpib6LiluQZJVqOeqwhJUxsP0FW6MPiNq832XSHgFQK0ap3YdBelCbKQGP4nkSdfV1yMbUy78gvNXM9icCuwjm1tOHgfJnqEYMjAilw6OICWgCBEFVC3MARia \
RDoh0hqBJMtzcT9hpInbjFTawEotH3bJpRfcVT0v1OeqTd3bmrOh8DhgwOJSz / 8 LPO9 / B + Bx + WDF1Yakj93 / 0 amipLIMgdXsIzk4Lz2nsh05O + 61 o8q + ZX2OQMyZcdg5fP7hViCC7VxIgfqZ4UqOxcQ56dwB4azc \
FuMb2cFB66K5Hcka9K0Xll3KfjzQfoq6Rcyg + ekNPJ0d3ZuxBeAJ794AF7OjbYAFzwlMmZBvYCYoVHi6oUWMgD1TywLzK / 5 AtzbAmC9nk / pbkMTuVldb91hVVDpjwmyGO1ZRSNgrLcT731NeBBwx7UrAnjaevhH5 \
AzjkaiMIuJhAWUjuEMBkY3RJncKJXXS9UQUpX / twSdAR18 + kCHilpYcFjx0CjliExNJQsklf179KysNNYLyusdmqZaRn1QfkelEjFOLhVhpI2YNOrfspz8QMOKlf8ddK / GR2XxdA8bBa0ijsoittqIMDqjfxOfal \
E74f0Sy6JQRMesX2yGcd9ym6IiiW3H0ED0fn / K0mxGZ7RMVfOUgOyLRVJJO9UNpdoWZ6inbWf2KFwUT96q / sanmlaa5FgBrYcsmk7O3sW9IWMiLpjpjq2ab77p40Zhd60FzRAmmjkRcLPTvJrlVbM5 / Fbg0oDVQh \
1 I2vZ71hoPCwEAZo1Bpsg0HunN + waYaNOCC / XMt1pHSyjZ + 3 r + NvhYpsa6oQWjZgVX / AkFPuC5Wr73sCTcwppDdLtlmse0vTOaQUbCSRM2NN5 / L84vRWqja57M + TUswHtz9GqOlrXe0 / PZ63o3iPsfYVwEYzX70S \
2 u9zEqmcKgg9oNCvt3axlJlP4uWlYnHK930q8jQO4 + 3 pLYe2jCJxp3MOo0uoYoejDWWAhgoXF2MNjXwY37C8NmdZQyW2ThpvVWJVQHU + MdMaKWfglR7OwG0W + 7 NtGiXhJOOkAspKdGvr9DXVw7QJtTeekCcYttyQ \
8 zKVsBVUr8DrdpMhV9rnsH + / 1 UlFpcJaKG + ay4MNz1D3GLDbUQ0Zd4rxUt0 / QDFW5zsQ + pf7YI + xB4mI2Q1PLbvz1LAyUfZvaeC5jJ1U0k + uRgivfFSpQPbCTqNMVpsRjxFabU6JXTfXox4ykshImKj7WI2AYlH2 \
W8eS + ZUsZEu5cLglCJjbihhr6xhK483gsbUwCNp4wgXnZNSlXFxJ1ikMpaoyLNNZSQr7M / iWrW40xyNRV8M8ufB5I / q8 / hJlIfskHoKbr47zl7r6 / 8 gK5L9355GqyS6sVEk34ANXjUp9deVXVIwHe4gQ2AI9YCo7 \
X11GGblMTSSvuNbEZBvScTqr90jJaygMbZE3R83ktqvjq576toWDLUFTpN5X1e3dx1u + O8RIHWKMgVhgca0Ew / QOBHHBTksF4fDG50aQ22aiXmXx0kCDLK / 1 q5xdLTZoLrl7Y8doN1EmLQ8hOxPDgoykbIXE1z9Q \
9 z6fY7rxmu5ZOr6wqezIbugaDencCymBYhKyewZi / ruqWgXuUDEtcpQxKMP + 3 ASv7UeIBwAvQVMCtO8LvKUDx4XpQdn3Bp + 5 Z3ZxuU / HEbLve8kVbnn / WDb7VvL / rW5qaEdcZEKwJlblvMRC5Vc2XPFIV1 + r8t3i \
iCC9mzckfqKOQshiX8zj1SIJyQ4 / YOHFs1KY2yE9DiN02obq3T0qZMB5hPaWFF + iwZo4jgNbxuPSTfoyPcDs6wGCOcyiAfn4Sb9oE1ivQUfA7xupiRd8OdRhVK2PlW9N1SX6KKnVvc6RaAeDcp6escezP19vyQW2 \
6 St4eMMcBJVfsN8N5meaJkpW2hahtwYuimcZPpVsLKAyBfpBO59IV7XjPSz4C / MTbCHPHcIW6JTm3 / wvOQ8s6iy0Tjj / / cO2oRu9xv7H619 / nN1g2QBoqSf / hQn71OgooCoICAQv5Fm6 / kOncIO3prO1QifIE7fo \
jf / wDdC4yw2RbKb9oJVSBa6Vf3Bc7nPKLLHZQneihO4kp7v5219zR4bv6X8lqeJjRr2AF3ylkkRxRJJemfybDVC5oHwOThBzF0kYMQEi0KkSF7gSgScrKVknuVn9wVnJxTClWn0TwaEv1eFRZmPyF51sw + T / ZtzM \
kiGjv8KITuG8mjKvdIuZsgfwkaixZm + 8 rgV0ISpTn2O7XW5Xti03xNBL9uWWPEGn209J / iWZjk05 / 8 egO0aC54tGlVWpxchdC5dHtkvkF5tsHO0jsiFI8XtMwNw8Toy1ipmukEiJAu6rFBM2y4LMErMYqL3Y6jgm \
L1g2q75cb2F + bJ + fD4zgg5QEUXoYOGd4NYird5YTDSg72FR6LnJLKO3flmQ / XS0fyZ3UKXtu + 5 SvEDmSGtUfEg5yYMNVIZcTqu7 / 7 HgaajAHFMnMiav27tAA / P7A5UG61OJeISP7c / DMeZONYQVqj6twSToW1C7l \
zGayHzQ2THI1VmvqAZUrLpeL / 3 GrTrVkyv9HUEoDuODbX + kN15zxwmLijux9WUE6jYhZXBsMt + V / dijoyUWzkrvgRqJbQuqd8MFiLldKVn1AHXXJlEt9pSUMMy2HCUwBtHm + bm1lEy / GJi1PGOBgJMgnJDGceXsU \
L416T9PI / 4 IxQ6sM4w6jKNKu2FlX + 4 TwOzlXvKdLZZd2PiIlL3eKWgjhl3lHcClm3HCjLeFJ6OVBrWvlXmC7MtvlnjQeIiSF2Y2OI / neLyv9TxXPeEHErYdKrSTqhPAEbPp / OuFz7xd49pUM3906Jchgq23ua3Oz \
2 HPiaCUdKocellLRvoovtRuXBARsD10lZIzGqCKocpF4j6O3XpWodDr7S3 / tu64yO3HrbUbBPRL6By + KjiGk3fJKkqvEFEHd / BN1HeJFovs8fHl / xm3dTFU69N3uBG5O0n6weZjJgjXHWNpOomHAEOriTTQc / lBd \
HlzccZG8lH9kFN6yzhKzSFNXWDsHE / wn4X9 + XPsr + Fdhk + T5PHdJnjZvqov11Y08tGZp4GHp157 / p1j1qnf4jV4oSXOXZfbz / wH6hAMl \
2022-01-10 02:07:12 +01:00
""" )))
2024-02-10 17:13:25 +01:00
ESP32S3ROM . STUB_CODE = eval ( zlib . decompress ( base64 . b64decode ( b """
eNqNW3t31MaS / yrjMTa2gb1qzYzUTbLBDsngJHv3AiGGsD5nabWkkBzWx5jJjs2F + 9 lX9eouPczmD8OM1I / qev6qquefdzfN9ebuw1l19 / zaLM + vbXZ + nS3edP9k6osP986vQ / Wo + 5 rGFCc8ypxft1X313bfww / d \
wGxGb5zr / rfdg4ImwrM4w9KMONhbGowvS3rp4Xm26R7yyyaj / 7 NspxtRDJbAUfl5d5QGxnYLmbobkqWdszgf / rqTmaC + pFHwJn2hk + 6 kY / KOuF33rvW9xwdw2ln30XVndt0GTd49AR6s9B4n6QRw5mY1xZBlOntk \
RqMPUc1bPPR10 / HMwnpLWAeINb0FHQ3rc + QFvnrzQp + zoxlm1mPmbkj2NrtzdkpvcaT / KyPHkrg / i2yfjQQAJxRykDNBThU1jXUr7soHdEodUZfcgDyXv6UP6Qly + OxmUp0 + dU9zOMo8A5mCRCa1KjsmehshthvX \
icN5JeJacS0MyXKDA / WpGu7p + H + ySkv8Dv6RMF5Z21J / Pz6WT6ewF89xy7iacL2KRjpjvfOdHMyie9u9cd3RGk8qjVwu1NGKgfU6Po3VTK / yQ6VtC57ObO + NdKC2Nh0b / kDLTSfl4PgU / CxOCvkrntFR6SptV / kP \
Q / mrDVBOPlEjmwlX8XMJ4lrz4GU6udiwN + SC8LmnwyfK0DwqNRXX6 / TBwJqgw1n2iRaAN8aDL1vDJK1wWvEvNF / d + SYu039 + 0 Z + 1 IaJrRSiqfIA3ijnMyKEGNlbc0DGxJX6p0puCmIRzwk9ktnAgHTSSP7vf / VOS \
rzS3zrLDWZcvHid / gEO7 + TWQvqJ1wO6AH9adwD + gvm7H0Tt0GwY2Zqdia4gK93lnD1OL3 / 3 vz1ja7XD33sC + / qRBrxev50 + / IzVtCgq1VclUj7zyl5jWLMYEYBAr + C / 04 tiINb7PGjqx6zGpG + LdM2ASsmuHmASj \
6 yGzQmASLb / tk0ZHhsOnY4f6tmPD + wPlScHv5DOWej1x7P7sc1Yemc4Tm + HEOdiBEX64OfgK2oAYNaPz2sUDmjGQjV67 / Itrh7 + 2 tvpj660kSjTg2O0 + G5hl74df3FIeg6 / ix73Yyerowx8xHp4k6m0AZ8x4Y3Hb \
yVjDl6hlm / 4 TnLGamKFXXA7fr9XAVYrTKVACTagEwO4F / + GyM46tufLZzXh / gEOZ6ePOsW0mKbSZwNyKPx3sK27upKiNKwm + ItQZAWylMNG0m0smaQeoDKN6MZZgngCF90oB + bnND / hhIPVLkKlJwRVmGy8E / Hib \
OxVqTgekNMwkXOlHkqxTgc5KQPc5M8WAI5Ew05QRuHU2YiEqujlHYsszghHLYUk7kjpqACAJ8DcQo2AL3x4DN + bRpiiIOruXTjVG3UPHMqU3wD3BaqY4Ujpqp8BYdAk5URrENHIO7fn5FZPcvakrFg / re3we1sRT \
TatVk3gpPE + jiIkL1M2XKXwoaUieNoDJ8t1U8wheea92edthfTzsXkJ8eqi3Ykx + SZ + OyZiiu6ozti7PiAdUoMe4xQNDu6CdDWQp3gUVBnxYTSp3m / + KNtvwsY2C + fYLnmsi4jZsSnWNdF1oqYHaLfbZxhcc8FCx \
7 + X + yw7L2XQEMitEmqg + SGXLFowDhumvzx / 3 U4lhdLFTkcbwtpJLtG36wwHLmJ0FcvyUQWyUa4geb77LGRLwJ4cxoKlVnRxQ5dnzWUYmYAeCB0DoYPPga2y + ywkdy8gwUvdMtc0OwepnM05NKoFmK0Kg8Lmyu + x9 \
8 CWEzopxXwmQ + D77DFD16h74fY6FqFf / Ib7ymN9wWtTDY9UteCxf0zkio3E5PdNOxwXTzVQKhRCdjxbwNT9rVK5hhqkrgn0EZRVp / F / OM0nIGEfYszpBK53MZiSLgHs8hi + 7 RweHu0nXOoh3oay6U48aiXu1fLpg \
aO6AudUrGPv656fn5yccqfHAewIV3nbY0Rc0I1u + vfMbu + B8j0TUT7CvYbXuU0DDz16CenT / VMX / sEAzBi9KANk4k3i6YR1D3 / D0dzhpt3G1YJ7VZNIVOyl0VpywoN2DmYQC6gRVPrvsBpSz5x0HGBs5hNEPf9nj \
fLV9 + upEwXPl30DngwNmi8twDMFRu1n06LkA8NUMdOCzpM8YL0G2xvwBhLwhRnRsQV8HhIJzAhUV + xznWhkpzwhUKTd8G09Q + ZYToDDW9MLDOceQ5eGLAxDgw / khJgFLWChkOS / d92OXVFBr / SNG1Kp0dBxh8wV / \
7 WKK1seGbLttVWXIoFnPgK6FpBsjlPh3XIbtrwnkyCz7a1SZPIlO3BUll6S4vlQVkjzZrnwelM8Q8sDpBhl7JcCqThFqKin12R3CRMMggIJup9IZK2qqnKzLAQbiucKJ1M5yXfjDJ + bQIt + h4OnnR4vsa1Hs / PCV \
O43A5AG6JdQwkf1qOgtK5ddX + gtkeVSzPZ6xYN / GJ5v4qdjZIYmA8zHZbUJlbY5e + pkuZF2ghT4ODxmfcrJDMCNCSAwjoPHKLYVB3hHhuvmCObThaLjJ + Ahj0PDj / BjN + GyXoJtXULtSs8GkvUQeN0GHqkchEyuZ \
s0dxB4RHeGt0OhRlmJLjOy27t / rLpf5yrb9serV3lYTs + JRMGd / + qgoeq0pO + hSsbfacgRLiAlTr9xKInnDlBcHwI5XIctWlzqahI9np + 2 Rt6CTtR7KF1Op4gJHjA6cBBhLt9nLT06JSgdUSPclG9Ancuy9U8qKx \
ukBjAy7zC6rkwmuVYuYYKD / TN6fLjobmy2HgDJCX + Zi3ddZxJfs3FDC6wwADZ0yOaShrE + AVxAT4nOxboYdUsG + tRtj84 / zjLyUJKnA9mULm1T5RZtjXElS / / JMBSFmSMwEjbsLAeSFaXL9jsJQlV + 8 kuZR8Ffk5 \
cqFn3SxP4Q8wnwgF3XzxjmiqqiY5fsDqUF2AEnlbPElGG6xWGDnFR4rOsRptqu1aXMQnslFXErwJGWhtZokdpSjwlljmGhKZQ6uGMA + chs3R3pkIN8KpDcUwiUNulZyGz7TTaBKeRntyqQNj4KVfTbcogn / 0 j9OT \
HwDQUQL4HZBSQqEKw3Z4w2EuL49rKGrBq4iE7RsgfofHIS8zGGBSY / KFPEtFzzf2mN1GL4cZJDyjZ0tc925sTUkBdtCTslxisb1AdTxeMG0 + 8 RI7LtIPxS9mqfxdm / WJF + 4 l3uCcSjeAkCAqfl6r0mDFFcN6P8ZH \
IaoRiPQ0QqUsFZ8gBqB1HN8hrqBkDmTuMg7cMKeWx5hJSX7YqlTRxg28VRvYJQ95F4c6ySoPVDOhTpMKynSwEhdSH / BiDtAufJsMX6fHvZZxKBj7hVKFapwFZpj / NC / BgdmG8tAIirGkZRA9lsnZecul1BqcT70j \
H4zg + 6 M7Eb / h8lBKB8liHT2HJLeJqMhQGaHYWXPCBjZZc7mCS4wxk + Eo4NohPlf + tVKfDYP6jCvRqWbgEowMUzDSfAVbPFdpt3k0TgpwL9ZbIRaX9LdUhfF9OV6oN3kUKMAPV6Ne + + LpPOHkwwRSjFWxy7qUMmLj \
MjfJc0kxCxKsSj1XpZWgsh2mr3JT9BlRm + / 7 RMIrwGjNNLWbxEgXUv80 + FkiT5qtvp3YmPLg55y7mtHmVE2f2Nxy4jhK9AxFkJERuXwkgpwwIsQTULWwAExoMul / SEME0ivPlfyMQSZuM1FiAyu1LOyaiy64q3pe \
qc9NTNtjjdlQcBwdwOJSz / 4 FZz76DqDj6t6aKw3ZELb / V69 + kssyhFOLD + TJvXSa6jhyfjpoQtVDy / qcYJgz06Bz / Pz9RwEItncPBSpnhms4FpPmrHf1gzNyW01vZEeC1kVyO5Ew6MsuzLucciyUbNmjbplyZ356 \
A0 / nJ3fmbAEo4YMbOMX8ZA9AwTOCUiaUW5gJChWebGkRI1DPtLLA4oo / 0 GUNMObL + az9FjhxsNvX1kNWFZXJmDCf445NYhI2Ryvx / neUFwFHTLsSrKeNd14L / wEacp0RGFzNoCQkVwdgsjG6hE7hxC773qiBbC8 + \
XBFwxPULKf9dae51D / Hykyu5 / IhloWyb / 9 r + JgkPd33xlsZ2t5WRnlUfcOtFiyGfh1tpGBX3ekXuJzwTk9 + sfclfG / GTxb4ufaKwImkUdtGVdtSBgNpteo6N6IyvRXSL7goBs0GVPZ2zTftUfRZUK241EiYCOX + r \
CbHFIVHxNw6SIzJtk8hkL5T3V2iZnirO + mcqLpikX8OVXSuvNM2tMFDDWi6W1IOdfSRtKSOy / ogdPdv0392RLuxSD1ooWiBpNPJiqWdnxbXqZJbz1KYBpWnK3kWvHwbDQOFhIQzQqDXY9oLMubxh0wxbcUB + tZFb \
SPlsDz / vXad / FSqy0VQhtGzBqv6EIWfcEKrX3w8YmpkzQMkrtlmseEuHOeQUbCSNM1Md5vr84uyjFGxK2Z8n5ZgN7n1IUNO3us5 / drqIo3iPqb4VwEazWL8U2vc5hVROFZgekOnXuwdYxCxn6c5StTzjaz4NeRqH \
8 fbsI4e2giJxr00Oo2uoYIeTLeV / hsoWF1OtjHIc37CytmBe1xAass5b1VgTUJ1Oyh4meihFyr9tkfqxMYmScFJQ6ooVJbqsdfYrlcK0CcWLTngmGLbakvMyjRwrqD6B140mQ650eMLhtVYn9ZQGq6C8aSkPtjxD \
XVrATkczPrhTB6 / VlQNkY3N + F0L / 6 gjsMTUfETG7sdSKW6WGdYl6eCUD5TIlqWyYXE0Q3vikUoHshZ1Gna23Ex4jRG3O6bhuoUc9YCRREDNR97EWAaWi4veeJfMrWcjWcs9wVxAwNxQx1rYplKYLwVNrYRC0ScIV \
52 TUn1xeSdYpB8pVUVims5JU9mfwLbv9aI4iUXfBPLnwRcf6sv0SZaH4JB6C266O85e2 + f / ICuS / DxaJqtkBrNRIH + A914xqfVvlN1SMe4eIENgCPWAqu1hfJh65Qk0kr7jRxBRb0nGS1Tuk5FcoC + 2 SN0fN5Iar \
4 xue + naFgy1BU6Ta17TxyuNHvijESB1ijIFYYHGtDMP0XQjigp1WCsLhRc + tILftTL0q0m2BDlle61clu1pszVxy38ZO0W4ST + IZQvFKDAsykjoyia97oO59Psd0o2OSAf / N9zSVHdktXZshnXsuBVBMQg5eAZv / \
oWpWgXtTTIuIMgVl2J / b3639APEA4CVoSoDGfYW3ckBcmB7UQ2 / wmbtlF5dHJI5QfD9IrnDL / VPZ7FvJ / 3 f7qaGdcJEZwZpUk / MSC5Vf2XLFI19 / rYp3yxOC9G7RkfiJ + gmhSB0xj1eJJCQ7 / ICFF89KYT6O6XEY \
ofMYqg8OqZAB8gjxVhRfmsGKOI4DW0Zx6fZ8nd / H7OsegjnMogH5 + NmwaBNYr0FHwO8bqYhXfBvUYVRtT5VvzdXd + cSp9Z2eSLSDQT7vvGKPZ3 + + 3 pU7azsv4eENnyCo / IL9bjA / 0 zRRstpGhB4NXBTPMnyq2VhA \
ZSr0g3Yxk35qz3tY8Bfm77CFPHcIW6BHWn7zn + Q8sKiz1Drh / PcPYis3eY2jD9e / / TS / wbIB0NLO / gUTjqjNUUFVEBAIXsCzdO + HpHCDl6WLjUIneCZuzxv / / hug8YDbIcVc + 0 ErpQpcq3zvuNznlFliq4UuQwnd \
WUlX8ve + 5 n4MX8 / / SlLFR4x6AS / 4 RiWJ4ogkvTLlN1ugckn5HEgQcxdJGDEBItCpEhe4DoGSlZSsl9ys / + Ss5GKcUq2 / SeDQ10p4lNmY8nkv2zDl / zJuZs6Q0V9hRKdw3uzwWenaMmUP4CNRY83hdF0L6EJUpj6n \
RrvcpowNN8TQK / blljxBr89PSf4lmY7NOf / HoDtFgucrRo1VqcXEPQtXpmPXeF5ssXG0T8iGIMUfKQFzizQx1SrmukIiJQq4q1LN2CwrMkvMYqD2YpvTlLxg2az5cr2Fz2OH53nPCD5ISRC5h4FzjteCuHpnOdGA \
soPNpeMiN4Ty4e1I9tPN6qHcQd1hz22f8PUhR1yj + kPGQQ5suKnkWkLT / 6 mOp6EGc0DhzIJOFe8NjcDvj1wepAst7iUe5GgBnrnssjGsQB1yFS7Lp4LapchsLvtBY8NkV1O1pgFQueJyufgft + 5 VS3b4hwO1tH8r \
vveV33DNGW8qZu7E7ssK0mdEzOJiMNyTn + pQ0JMrZjX3wI1EN / mFCAsWc7lasur71E + XTLnWl1nCONNymMBgN8vzDWsrm3gxNml4wgAHI4E / IUvhzNuTdFvUe5pG / heM2QeOO4yiSLtSX13tE8If5FzxXi6VXeJ8 \
REpebhNFCOFXZY9xOWbccJst40no5UGtW + VeYLu6OOCONAoRksLiRseR8vCXtTIL + BEmLoi49ViplUSdEB6DTf97L3we / gLPvpLhB7tnBBlss8ddbW4Ve04craRD9djDUio6VPGVduOSgIDtoauEjNEYVQRVLhJv \
cQzWa9QvJWzxb8O1b7u67MStx4yCeyT0uy6KjiHk / fJKVqrEFEHd4hN1HdIdon0evtqfc1O3UJUOfZc7gzuTtB9sHuayYMsxlraTaBgwhLp0Bw2HP1AXB5e3XByv5feLcrait8Q80dRn1t37M / xt8H9 / 2 Pgr + IWw \
ycpyUbqszLs3zcXm6kYeWrMy8LD2G88 / JVad6rv8Ri + U5aUrCvv5 / wDWE / uZ \
2022-01-10 02:07:12 +01:00
""" )))
ESP32C3ROM . STUB_CODE = eval ( zlib . decompress ( base64 . b64decode ( b """
2024-02-10 17:13:25 +01:00
eNqVWmt7EzcW / itpEkjhaXcley4aaINNbZwLsNCHkg1rtsxoZlJomy3BLGG3 + e + r91xGYwcb9kNiW9LoHJ3re47mv3uL5nKxd2er2hvPL63dnl / 65 Gb4N5hfmix8hr8qz8OP5BXmZ / PL2oXhKk9krMon4be7N5 + P \
5 peFnV + 6 ij + b8FQ1GG2H6WIwCbsbTIZ9nQnfXVjthvPLkvYOf2HQDt / PL9s0 / Gh5ZREm6xQ0 + Glr5Tv + zKsjEA7fsE0RnqBRMF8 + Buc / 4 ixyjAYH8 / I0kQust / VpfsTMX3rzEAwswmhyb34R2A8MtYNRNdkJu7td \
ZqAaHAaOBpP7h9vhQZOW8UROTrQ3P197mqswGog3gWFbhC9tmPGB86rNw3Ng6s / jsK5mIeLZts3zNRNKesxyUW3VdU7H5P3AfCYCG3SSg / zDdlDkEJ8 / XAkv7oR0BuankY4Jn65Y0uAq0eLelZBmCqMeD0op / rb + \
B0ifdw / 0 rm9NdkiWVQTlFEPeN6gzKKjwx0pZLLUMD / v2ZW5ZnHbISubl1h0EInWwXGcnwQTqjG2TxJBMMAftMZUqYRUZ8wh003tgdtIXnOX9C9CnsbBlHo7RZLyALCwTKw9 / RcG2pOO8icriCasAJPFZ5Tc7kd0n \
pYxn4k14LJ1hKY5TsnNYzzYOIq6RfdwGv7PD3lE8ZDkO / 20 wqiblUSeu60CswP72K3gALMmwLWBZW5zsY1HPpdOZZydU6briAUaCeGsZaesTVjykAXnbXLa0kbfKwP + 8 lc2GbGrgr22w6UydpmcY4RGcIxOPM2Jz \
Ni7RE7c2Uoc + aqJGjBGpAYec1l / fAoz9zoSrfEq2KX6AFS6dwtzYMJ / e84dyMoqgs54l41ReRTTsOajEshqr8aMXQe5jyZjXW + GcZGcwk2FmsAOFy9Yug4HsMJFSj9 / fvLBnkR0in0QikGWQdathA3tSmFQdKf8l \
dlFrNCuHUZOnk6 + XAf2WQGISkO7cScIXH0kssNYfOCyT + opngk2yEo3YAZS4Gl5ky7q / ZSMM12SZK7GtEDbhV4hX5Bzi3MQlQqVLNUGMgs5sCkml / MWYTZHTpzkPBEO7mPbZtSJRpnsm8h4iFHwRhyGI1A1ELsnT \
p / 3 da / GgT0bfuPVyvIBuyhgqaDJVh63tvTjkaShu3hRsDss + Tg5XRd / 2 No9biGOmnUquY5PbgeMQ8SqJ91Uq8RjbFAIq1kMQ6E2hB3lqgpjf1D1OUz35xz4IISzxyeRE4ANfZ4o7CF887glT8hIiEdKIUCgbOUpB \
sgvM3ZrIDDBGcZNjvB2ecvAj8fsD / 1 PYxujPif8ZTgCfhUXepZR8MLop7tWwma63lnYFtKjV8AlHFD / n9CCwXsmWNyeG5xNWA85Q5KMJpyXCTwKYBDwtYaYqvduwGSq6WwJrtrgrcM03937r29yEWd / kmnUjOQw + \
2 JDtz / goFNXyrX1SysXdPiyZadAafYIa0oJrJekSuJ2uZ4EiVQWh5JI4FU9K2uwJYqzIViMkJXo28R1O7BqherFzwewhkPl + zqOMenbFXgAxegkimIF1uPoOGx5MC5 + V5bTvsLja4Qn2 + jE8oeA0yMJ4wml0NW68 \
fwtLt1fvsOY1FJjeFNINK6NGZhX8VZdyvvxqwTGliHl2wcSdYhzxGphWW + 6 zZ7NXbLMo1Xk + rYstTijLm5v0TaRAhlvOxux + jLTOesiB / sBoITGBWVnDYiEsLj / eo9ZA6tufcUbrZYW3sLKU8gsCVuqjwKpydBux \
rZzMz4Nvtdlp + wICfnHY2Racv / DvsEgj3esjDghQhEsff4YRxLNrUaGpl / bYeBg2fzLGkqMYB4rd8et + pAVr2U + IBvbt1bvnYeshzmL / CWgfHMnXgtfabYS1m3CvI + Sxx / CBH + GeqGCqN / Ch884PVFgZm7ddUQph \
fSc5tK0 / KJjZF99TT2id2Fz5F67BqoF82rsrGHMQPaarVZGxzP4XSAmQMD3zSI0fOHS5tEYNAzbVBJ0WsXmvEljyEizqKuKjTXS3oNSr / yiJ9QTojP4OhxCyYX99ceDigi2dVsmjzh7uS / WaroLOPFLm5WQbMwkt \
w28Re0GpzQLyNpUCeCS54U8QWimgs7PCoJHxUHJeSYlwowBGqNcpl5XyyeReM / qqhNmqQKHUDoR1mTH53bGkmoQy + geBgTSyxMTzz2ihKpb87BGj0f9LYDvfsCZgrKHWRaR / RJBxoePtZhMkMyyvBEQMpwl / a0qR \
bL3vR4KKBozemgErlioz8rZVM8r7ZiSDTP9HLlHOuUAhplNmVNFiB + hh3QroqQmFQdRCDVhM1NOTSHs5cFdZz4h7AfyLArc + / GWBu22vyPuntPCweAm92mI1GHCQaFYL1MH + 57 b3KzmgLZ + 8 eYaM + uzoFGI + vf0C \
tvxifv4PTB6 / eYjJh0ePMPnoVh995SfjQ / DxNqoL2AjCCLlmXyBjxgZeSvouJX0jmFMrsJGUjvmk970kCM81SMOWAjSCkgnPwqhwuqbZVMSMmDBDI3cv / 7 BSQ6rgNK6Tq5aet28owN8oIKjpL7ANgpipKKMrapcL \
0 q / O9skwtwQpaiGbu1HXLeQnYHIVrSu193Mk / JUQA60SUQmT59wd9LHqsIOuVeZfTQFAHYM6l06n0w1ygeCIsRSdR2MfaBtC65bNT7exq + uP8 + jVWpE31CgwAnerzzCyLqKglmkbUSA3tlCkaENH / ZokD4teqZwR \
x6afiVdUO5 / lxyJ5dJdsvly5clptx61KLPbyCFQVFCLDIReImb / mD1XNG + oLao3h5JXmRrbvrqsjgAf2gyOiuqLxJI6H + oTbEefSjIACkN + ogBuyXLgdOV9gKQ7VyDwSX3 + 9 IQd8RE24VCuGp / RzqD9v4WfR9Fp0 \
RCSJ / SGf70rdKd0VNAqo6rH0bBqfLTywp / 9 hH3TtPlQxPWBh + / Q73S7pE5K9CwsQZ / KXvHXnzOimwNyQCxqBFf0mw9tkucNw2vtNsL0ZI5CnCaB8rh1LZdcKySpd4zgN82zT6WaDrpt4BzGfz1Z6BQP1H7FDQyVy \
LkVoDTNVVbLqSi6Bl / WdauvU59 / 4 cwASHA / RmiaoBM3gljTh5hc0LO0 / 6 / xvMpPMFzSDKEvNZXsY0x61DrqqH3Tb7xP9CQjQ3kCtBWVSFs7iY8vcfk9mhoSWT / yvCJyZWgx4avJn4Jx + Urvt + fxCkngh3XgEEdoz \
/ ZTBWAlsnJrEOFOBSLWUp0ibyN + 1 ZTkXlk5Ozb5dmO4DIZlygEXU9gATJp16DsQtK4xNu3YHO7sa3He2OacYI7mwTFWuYXckHC8SJ0REc8iA + dfwC6XcCuWs31QZeA6IRLxRRw7f6wnFJzqsnU6h3BQAvB5EX5Mr \
JW8HqkIo2AD9ldLiK4sep0hNq3IA4iJdoRenXNT + IRoKQA + 4 cSjMAeIbQKWbbMAnzqApVyCEc9eDenTfssfVrBFt4kJkaLeJoYbTRYXBqCWNq1QrlmrB0zBG3wlWGiFk0OlEesiUvumSA6KyZ + QUcAJv0KDCaDiM \
dK2cQLPCJggcnkAzdYsTqfQMOwWO5Tsv7FjppFrxYOM765ro1USOQB + FWNnvgM8VsWZbdInRHRB2QbeQWYwqcoN00ZlXD / FmZLsXy8qFAEtSLl1 + 6 DVDOpXeaVn3OZCdyUzv6 + Co2zOXPZG6hjPOmKJQqktK8pxx \
7 F2XyaGEdNBq + mAtiVcyzs7iEUiFJLxanmg0lTzrR4aJ5vDDfirhuVqMc3BtZiF9 + 5 XkRuWEY1E3SeTQCocAGdRdRi + QwEv7h8AKasooSIGNAAT0MwapsIyYpyYsimeblo9L0MCJ13KArjt6v8dWJ9ZSE28JAv0 + \
THodRs1tQ1atEzGTxVfDMw6XqoSCUBkKkZYy / OAWossY7buivuISpa2vtGiqR1K1ODkyRZhcuofrOCzNGw7ObX3OBVYjTFbDDzc4nlNtl49fR0rIeY4a3Vcf4 + hHROWN9LZ + kWBaHDGx7gJhE4 + NaeP1H2i1hixg \
dyydjJy8SG6XDDtSk6ykU7bnp2F5Jb4PG0YI1kAQrzNbTQP + wIslSIbXuISnHOuryuWKrO2Q2gMeoC5JKnWqlV5xLXfa6qJsCr7tAVO5QsDSZrgjTlbFO0hx3O / 12 oM691PWIwyzkLzZK88l2BDnHWr / tLC9mg7c \
/ oOEJcNskSUOX856iT + JdkyctPVL0F3wLnV9MNnkH0 / oauTvsN1kdAV / l2tqvdApjIQ6LwUwngc0c60CVCfBJJ0vxqxrvT8jQWimdcMbR3LhR1dQ4vF0j9IuGcsrjit04WjlvRGK6FQbNoqyCSd8UAArL0yY4de6 \
VuBrCKDiIy0gbaXWovdhTRGDbyNlA / VSM8CamgHOrnQ7paaqqhWbs + axgmjITCXyQNo1rXhINArJwDXF793IspU7rq75Qm4T6J8L0KJPIIjuLieVdlsUIPqWfszSW3JCpyE7lTZhf7LQjEOThxG1wka8oB + TTBWc \
SqWEDtD6SmnJRu31yomKi1r6yPQ6zeGqJzJLF / GNET / 0E1 xcWH / C77BoQfsHyYpV1RRRpYW2a3sNLD7tKXPEjwvEqMQeuo54zPGFvrklp0yu43Ek3OQl45 / 1 fncc38ziQ8IrEv + OXqXaFsF3lzKV6QceQ8HbylYs \
qgUhiefxwlNFyGnCPyFTPY9pFS7nzXv2Mto71b1dFpslqrZ1JzFmRt2B5YtQUUFb9Hk5lDBWyssXkPvyCpUFN + j3xlT + 39 rEwGiKzl2Ao02vzkzf9oSrCjJrw + BWvvTeHJLo8eobSVJEkWBOpP9O2TB / z2Zcydtn \
atbSGd9jTggPYIhuSRVLrxWpi4dxZn / 9 So + 9 Efhg158qRFTZ9OkkdGlGNf / iXIzUz2 / t / FtvWNV2Mm5Nb + gHyttV7b7cdumTVBLglb6w4Tn3Ovn6IyR9vahJIgWTHsidf8sI7OLgPjqfd1iD / XNU + fZscCBvGOn9 \
wZ9SmQpM1laT1b + hhF5TrZPmK / wfnNDh9wRYwG68pgzkF8J4tZwXQaUanCA1EKjg2k474dKGML1XIHzMVBTx5AUCekuIokvJWanJ5CZCjw38VSfSsSnUIwUFSzZZcPOYsLqmIR + JSxrp + + VLuXfwkxjc6LFSKf + N \
HPKab61xI4htPcy9 / ZTJEdamyOYk6zgl91dpmnze9LduiAtJjVZRIfykRas / e / YArf7s9CZa / dkLWASa / dlxi3I9e / gAISN7NF903f69b7boTd + f3y3KC7zva02eJ9a6xISZ5nxx8bEbHCbOhcG6XJT0YjDEOiLX \
2 JPh / i7GZklhkqv / AdnBtYo = \
2022-01-10 02:07:12 +01:00
""" )))
ESP32C6BETAROM . STUB_CODE = eval ( zlib . decompress ( base64 . b64decode ( b """
2024-02-10 17:13:25 +01:00
eNrNWlt7G7cR / SuyJEuJm68FyL3BiSXSpkTJtzqpY9Uu3XoX2FWdC79YpmK5Df97cebCXVIi5cc + UCKxu8Bg5syZC / a / + 7 P6arZ / f6van1wZO7my8VPl8Ts + 5 t3jyZUv4rf + 5 Kp0k6uCRvfiYPk8 / sl + iH + SOJTF \
/ / V2 / OPl6YSenlw14XVOcxzGP + ZpnL8 / i6PJ4eRiclWb + LU3qEY7cfZilwWoeqeTq9AbPTzdjg + atIyr9uIn3lsUg / inP9mfTDE9JruMM6TxR8N3uXweR + PidRTYuviliVd8lLxq8sk + CfXHk3hfiPdX / GzT5Pma \
C7r0kPVC24yfEHLaJs8H4TNRWG + hufiJenPx4 / v4 / 2 gushRn2OMAwh + 165 j4v3BDVsHNi7rDuSzNKww6MuhK7W / rH0H7PHtc7 / rUMFeSj6CeaBzX53mjOaOBnH + iK8PY8aEyPuybt7llddo + G5lvt8VJXCSM49x2 \
FCEQ4pO1qiEZ4Rqsx6tUCZvImGdYNz2EsKOu4izP77A + jcUp87iNOuMbCGEZYwK / nWMs6ThPorp4wSbAkvhf5XsLlT0kowyj3M7yFCYd41ZspwScIBtjHIsUtcxTkE63485cb7SkWlGNbsVDl8P410ZQ1SmPFpbV \
U2Axh / ntHXgAkGQYC7itcWcHuKmdOkrn2QlVu4U7xkhUb5CRJpyx4aEN6NvmMqVtZasM / M9bmazPUIN8TY1Jx + o0HWDER7CPTDzOCOZse4vuuLHt6rBHoNVIMFoq7iZgKX99Cgj2Ky9c5UeETfED3FGkR4AbA / P7 \
Q38qO8tw97iDZOzKq4r6HQcVLgu4Gz86DPIQtwz5fiuSk + 4 MrmS40tuBwWXqIgNAdniRUrffndzZ81YcWj5pF4Euo64bpQ3MSTSpNlL5S8yiaDQrm1HI087X64B + C5GYBEsv3Enoi7ckCAz6A5vlpe7wlYhJNqIR \
HMCIq / QiU4bulLUIHASZHGsoZNzIQRRj8HWs4YXCyHPeQ5d + ADiwhYCyBIDjpcoRZqPGvh7JFYQSt8eubPuvGeMUR / yJ / zFOY / TnyP8Le4VpEEceEPOeDPZEizW7pRPtgg5As + TThomk9ivhCRv2qW5yQJ4yoWcn \
A9ZXnG5CMk / AmH3ehssHIyYgipQSGiVMdqPjRVqz32gU7wTly28lJvv68BcNnXh8xIKv2whtphKisikzRNUb8y4IuvnWAZuktt92o89YsTm4YT14f9EIt8Lre0frhNiKX8BEMbQxOWrOINTYUcFQsxf1AiJzJosd \
Jm9FYcc / ZiwbwOq7vEaseT5nDkBeg + Dh5QqgUYT7jDrgCv8ry9Re4OZqhy8wLw4RSBxTHWviBVPljZG5DfrLsQSQMhJGejqRkvk6G2Is9Nu7Pd3drmuFVfi58w6N9VbjBZF30caJjUvadknmf2i3ytvUQxIL3uo9 \
cVrx6yqVsA / pXCJhf13ENcikjNxFASE5Qlgjqrn8AOKw84 + 49 B4rp3tizLiFUPNGg2QtoRSJ8jmAUXJmIMQ0Y2UUmhmIsBC8KQ9YUCaZbQanctHNinq3tzqzSX9qpycGKMdDpjJOTs47wZY + kNKJmlmONfI5kW / 5 \
8 c5qsGe6vZnYSrMjd3h7hC9wMnwzqW + 1 VZWDe7BTOZpMI0k12evmDbT75nThqiBS5z / iJrXa + 8 dMrrBCkT6 / TRBF2ArD1mFpmu3biRpBGJHGOSXd3eF7Ji / GL6TLfmRuVURrpnOj8xC27IcP84 + vQARvwEb / BKQq \
oRiKjdugwT345mPsBYGtQTkVUCT0f2KuqAWipZhVVbwkR + / PXLNUUG2VMQGBIHzvlq0XUM + 5 x + gnnrRIg0Kq0DpuM + S3b7eUzef / WT8nIdLfZ1IlGPrrN8eFL / gRol7NhuzpgdRs6Wqqla94EiUSVtLBgECEdZok \
Zpum8h / Yy4hJIHKZ5hrngdV0CP9K8 + cMrvJLwKVRD7M4lMvO + d / fM14q5VWH2qDpidyLK38ast0LkrH / SUpjGlmS4dXtAPfLfvKMc7AvVRjCmGc7NICLPUXki5OUuMfpleZ2OareXHIqKCVN + Edd8pO1O / B / bUGN \
UFP3HkgRvl5WGiQk / MDZ + JRzcZI1Ba9qMbLIXFHAaeZq7ZgHoVHkGU3yf0CzhJ7ix7Tl2stvPzxY9foNGRMVwcuU / OLeS8Dw5WT6GuKf / gTWKR8 / foKLT + 49 xcWnk + kz8PXbZ52mS5WfDU + RvXxobYDMD9uN3H8g \
bpIJUUksLSWWgllDIbE2k1ibdL6XlB7Qs7in7nGyhdoEzwIe2B6I0Gebna30mvwVh + CzpUpIsyjNaDBWlbISZrHJXZjEHv2bU8SCtZ0yehbV2XJldef8gGC3JdmwVmRpMdA2Gr51hQDpGnusBa1adNEls + kRTSeB \
cU3qEH90unSTyfg6OiqaUnVAW8wpgZyw9GvR05Qb6XwqWuYeCgol2okYW3Q8XbTopK8A6eB1pDfA0tJgyl + 4 K3K0mUBK6pSc509kW2hy2Hw5SUZ + XKTNsFFDtC0lSlSiqDMEZfwtws / 5 UzXrOjWXEgdcpZGKAbro \
LOSy74L3h / BN40k7HssnLomnUhDDPOAl / MfNPteW2GSGW7GjWq6DZ7v3G / KgZ9QIqqSiyb6nn05 / fo2fru60iWiRpE3ufb4rTiMVfnBSkll6Nm2fdR7JnH90wJ5rUUyU9ugEyn6gdK5zF925nW22MftbnnrhBWAn \
lH6NSviFxcyAGxLLlcwmEu2mSpRAbyha0CtwYKLUcr + yyrUfp4qwFAq4OvFODcWGuWTALVszpeYcErr8G4ChyRihlrt24LQ88xVfiEi5oGHxElv4X + RKMpnRFYiZf0V5Txt3qGvRaIxP + EcVDnTE8UiZHrx2EgGN \
PHkNfg9aWI38ryC1TCEBvq / zlxB + LFLW + avJhQRQJz2eRkGQ3oQIK5zHwUPQl0pGEiRXSe9 + J50GImTaOTpHmMSlx7JeyjU7yMojipv0yDOZNExzDNxQnOzsKp2ieqnp2yWvXaaq15DvIRg0onFE10DXUBhC56XV \
lRtZOes2c3pe0JBzSWF66i4jpZ51mIaG6I46JHegAABNPYzOM + zBXOwMwxs7kqgOs3YawLvIbFXKnkgJs5EBQboqXfAI + MVTyHgC452A1UZQ9GhzLl94lOoOxM3tGKncrQTKYFuNwuE81 + EC4wgLurzoDno2iOq8 \
Cq21udfsF2qXKUkN6ajbPqUmO7Rlz / 3 PGEOBb9A2w2jckvTSCuEak37Hae1MG6CJVFCG3Rk786zYqiPKQrciX + 0X2B u12Ju2qrxMUE4U2gnfog76YneADB2BKeD6i + OLC6Hz0OlXGSaPi2X7Qnsl2ddUbRISnUHs \
UqZdCWRmJGDouvLgYDFnInMiZvXHHCeFwTR1iU41bOveMjkVLmd66ORYSVslF3bcboHsV0sUpSdqjSEvu4wx0sh92o0hfC0IPnvXrsykfFiJarXwDlRdJ62EViV00nNKRwBF3fwmqQQ1N9R7AQ9IvZqYxP8zZqxA \
6 SM36XriqfDZi8X0v7Yd18ZIM3Fpus / 9 pNPpFK8iXQfhTSPorvrn7Pmqc0dpmb3LDXEf0IxswhBNLxfm3IFswlzrmDDgobqQPVZUKkkXc52ExqBVAJcOU4ZMLUJW / U93mdapvMqH79uV / FSqRTP / 3 I5 + Bj / fsl5h \
ZiK5e8zrNdpIXP / Y4KNEOlmrMWTw3aF0GXJyGjnJsOw3dbISWBm + 6 L9V4uc + MOmq07dHZ40GBH / SDSN1y0Hzgo1V5VJuNot07JgHqDuRSmPAcn5Aew0dd2Qc + KaTfcohBmi27u + IQ1XtYZc46QM9eKGDgyM2IofY \
sYwsymMhFooGi7z8BjX / xq8F8GHfJ + EfwzI1lJO8HXcif9IimMRowlssesmzhHAyWrfSO1Qt1o / + DswmgzncWo5C9TTJ4ZzOSTrTyNNg86LRBLQQzkgnsyHbOOgpJ3RQyo + if / cxde2l / ywlDJ3gNEsgeScFQRCL \
4 d0EKoWoeV1rFk2ZwidNI + VQ3vS / 0 nulIow8qXil + lGBos3w2rUcS8jV89UMiU3gFGdXzh2M7KdagZs1zzWV5aDKGjmWc5ZGPKPFgxE8EFR3W5GtHLAt + h / kLnH9qaRa9B + JwuJMIEh / q1XglhyDpNJ / 6 / pfoQfo \
N110pvOkPe3WP9Z7DYXJkaanUuVXxaZiaAmk9npxRBVM4DF + a + N01Q85Nb5oX0zwfT / aAkn4M35VQgvW30hdbK3atVZFy48ooNNG4t2 + Zon4cUkmtAT0Ot5Gc3nLAPPQLpPrGTlCa / KWs9z1IemsfQGINwnHSPxH \
emNnWxTfpzJjxlGypR1DpG2Fy1lVM8oZXrUHrqpCjhD + BaF1Kjwq5Y0vLxlBNHeqcxdZ20lRs62hkL9R6b98BCv6b1xXkFMhsVIO + KH05TtUEdwD3R9Sef / 1 ptUR07 + XtypiOVd3 + jXph45 + 1 UZmtCkEP1p6SQtR \
9 Mnq6y9STJF6zqT3bQnPlwzmSl51UnBXXJ7vC69QQtCXs9q8bTjeDJFH7X4Kc7BJCYfMgJW / ufZQk4No0R9A / goE63jZsJIKq2 + J / K6nvQqijF + X2NC2k7d5mgM5J9InqQRAhR8nnHJjkg8eIsHrqUjSrmDSE3n5 \
oOEs7OLkIdqU91lEFvkvMMT2uHcir7MwnIr0DylShcO1p2T10xcORiRfp00PvRc99s99yS + oSNPwQXnagGM97RcJVNU7Q5ig81Gq3 / TgJZXDa9N5F8O3UYuoT15joLdSiGZKjlB1JmcAain8Dz3poTh1Tck5KLIQ \
XVD / Vc8S8vb0x + SLkLLsoxSye1LeK9Hpk2Z9soJPdt3z1iN1LeC3ss8MAErDa0IIxaTiFgGMSb7ITwba4E + kFUUl8ovmJfD68jjGgSp7vfcGYe0N4PMPXH7SoJzPnh4 / w + Vnk1mniU + UYPa / 2 aLXPf / 1 cVZe4KVP \
a / I8sbZITLxST2cXnxeD / X6viIOhnJX6dihAFZ1pX4a7sxibJc4k8 / 8 BksMRng == \
""" )))
ESP32H2BETA1ROM . STUB_CODE = eval ( zlib . decompress ( base64 . b64decode ( b """
eNrFWmtbG8cV / isYMCRunnZG2ts4MUi2QFyM66SOKa7cend2lzoXnoBFjNvov3fec9GsAMn + 1 g8CaXZ35sw573nPZfa / 29 PmZrr9eK3antwYO7mx4VPl4Ts + 5 t3R5MYX4Vt / clO6yU1Bo1thsHwR / mQ / hD9JGMrC \
/ 2 Y9 / PHydEJPT27a + iynOXbDH / M8zN + fhtFkd3I1uWlM + NobVKONMHuxyQJUvcPJTd0bPT1cDw + atAyr9sIn3FsUg / CnP9meXGB6THYdZkjDj5bvcvksjIbFmyCwdeFLG674IHnV5pNtEuqP43BfHe6v + Nm2zfMl \
F3TpIeuFthk + dZ3TNnk + CJ + JwnpzzYVP0JsLH9 / H / 2 czkaU4xR4HEH4vrmPC / 8 INWQX3L + p2Z7I0rzDoyKArxd / WP4P2efaw3t2pYa4kH0E9wTiuz / MGcwYDOX + sK8PY4aEyPOzbt7llddo + G5lvt8VBWKQeh7nt \
KECgDk82qoZkhGuwHq9SJWwiY06wbroLYUddxVme32F9GgtT5mEbTcY3EMIyxgR + O8dY0nGeRHXxkk2AJfG / yrfmKntKRhkGuZ3lKUw6xq3YTgk4QTbGOBYpGpmnIJ2uh5253mhBtaIa3YqHLofhrw2galIeLSyr \
p8BiDvPbB / AAIMkwFnBb6053cFOcOkjn2QlVu4Xbx0hQby0jbX3Khoc2oG + by5Q2ylYZ + J + 3 MlmfoQb52gaTjtVpOsAIj2AfmXicEczZeIvuuLVxddijptVIMFoq7KbGUv7uFBDsV164yvcIm + IHuKNI9wA3Bub3 \
u / 5 Qdpbh7nEHydiVVxX1Ow4qXFbjbvzoMMhT3DLk + 61 ITrozuJLhSm8DBpepiwwA2eBFSt1 + d3Jnz6M4tHwSF4Eug65bpQ3MSTSpNlL5S8yiaDS3NqOQp50v1wH9FiIxCZaeu5PQF29JEFjrD2yWl3rAVwIm2YhG \
cAAj3qYXmbLuTtmIwLUgk2MNhYx7OYhiDL6ONbxQGHnBe + jSDwAHthBQlgBwuFQ5wmzQ2NcjuYJQ4rbYlW3 / jDFOccQf + B / DNEZ / jvy / sFeYBnHkCTHvwWBLtNiwWzrRLugANEs + bZhIGn8rPGHDPtVNDshTJvTs \
ZMD6CtNNSOYJGLPP23D5YMQERJFSQqOEyW50vEob9huN4p2gfP2txGTf7P6ioVN8R2Lksr3QfirhKpsySVS9MW + E0Juv7bBVGvttNwCNFZ6D + 5 cEBxStMCx8v7e3TI618AV8FAIcU6RmDkKQHUUMNYdRXyBKZ8rY \
YApXLHa8ZMriAbK + y27EneczZgJkNwghXq4AIEX9mLEHdOF / ZZngC9xcbfAFZschwoljwmNlvGTCvDc + x9C / GFEALCPBpBe1ypS + zIwYq / vxbk93x3WtcAs / d94hs97tqEEUXsRosXJJG5fkKADtVnlMQCS94K0 + \
EtcV765SCf6QziUS / JfFXYN8yshdjLM9BDcinOtL0IedfcCl91g53RJjhi3UDW + 0 ltylLkWifAZglJwfCD1NWRmF5gciLARvyx0WlKlmncGpjLQM2tu3pzbpT3F + IoJyPGRG4xzlvBNz6QMxneiZBVkioBMBFx / v \
rAaDpuur + O3dplz2dg9f4GL4ZlIfdVWVg0ewUjmaXASiarOz9g10 + + Zw7qggU + c / 4 Ca12fsjJljYoEhfrGbZco6vWyzb1AvTrH9 + GkRDRBvnlHg3h + + ZvRi9kC77kfl1TmOS7dzrOoQse3k5 + / AaNPAGXPRPAKoS \
gqH4uA4S3IJnHmEvCG4tSqoa8vR / YqZoBKCl2FRVvCBH789ct1RQbZUx / YAefO8zWweHpeceox950iKtFU + F1nKrAb8aL6CL2X + WT0hY9I + ZTwmA / u7NYdUrfoRYV9Mhe7gjRVt6O9fKb / kQZRJW8sF6hImQOiQh \
3 TSVv2T / IhKBXso010APoKZDeFaav2BklV + CLC8xD7M41MvO + d / fM1gqpVSH4qDtidzzK38astELkrH / UWpjGlmQ4fXnUxG / 6 CQnnIR9qcIQwbwE7QKh7RBBL0xS4h6nV9rPy1H1ZpJUQSlpwj + akp9s3I7 / a0Q0 \
okzTeyJV + HJZaZCQ8AOn4xecjJOsKRhVq5F56ooKTlNXa8c8CI0ixWiT / zvBDibTH9PIsdffXj657e2rFU018CIbv3z0CiB8Nbk4g / CHP4FwyqOjY1w8fvQcF59PLk5A1W9POj2XKj8dHoImLqMFkPVhs4H2d8RJ \
MuEoCaKlBFGQal1IkM0kyCad7yXlBfQs7ml6nGWhNMGzAAd2CA702epco / Sa9RW7oLKFQkjTJ01lMFaVshJmsclDGMTu / Ztzw4IVnjJ25sXZYmH14HyHQLcmmbAWZGkx0C4avnWFQOZk7L7Ws2rUeZPMpns0ncTE \
pelw62OTbjIZ3wVIRVOqDmiLOWWOE5Z + FYDacgWZm0r0zE0UVEq0FzG3aPli3qOTxgLkg9eR3ACmpcGUv3BbZG + 1 WCW1Ss7zY9kYuhw2X8yPkRoXaTts1RSxp0RZShB1ioiMv0X9c / 5 cDbtsu6XEAVdppGKIzlsL \
uey74P0hdtN4EsdD8cQ18YVUxDAQeAn / cbPPtSc2meJW7KiR6 + DZ7v2GfOiEOkGVFDPZ9 / TT6c + v8dM1nT4RLZLEvN7nm + I2UuLXTqoxS8 + m8Vnnkcn5ZzvsuxZ1RGn3DqDsJ0rnOnfRndvZdh2zv + Wp534AfkLV \
16 qEX1jHDLgjsVjErKpcu3kSMWOzOlw7cFFquWFZ5dqQU0VYCgVcmHinhmLDXDPgFq2ZUncO2Vz + DcDQZoxQy207sFqe + YovBKRc0bB4iS38L3IlmUzpCsTMv6K8J8Ydalu0GuMT / lHVOzrieKRMd86cREAjT96B \
35 MIq5H / FbSWKSTA + E3 + CsKPRcomfz25kgDqpMnTKgjS + xBhhfU4fAj6UslIaslV0offSZ + BKJl2jtYRJnHpvqyXcrkOrvKI4ibd80wmLRMdA7cuDjY2lVA31jmmGHPNa5ep6rXOtxAOWtE4QmxN11ATQuel1ZVb \
WTnrdnN6XtCQc2PP9NRdRko9yzANDdEdTZ08gAIANPUwOtCwOzOxMwxv7EjiOsza6QBvIrNVKXsiJcxGBgTpqnS1R8gvnkPGAxjvAKw2gqJHqwuGwqNKdyBu7sRI0W4lVNY2ahQO57kEFxgHWNDleXvQs0FU51Ud \
rc3NZj9Xu0xJakhH3f4pddmhLXvuf8YYanuDvhlGw5akmVYI15j0O05rp9oBTaR8MuzO2JlnxVYdUea6FfkaP8feKGLvIqryOkE5UWgrfI1a6PPdATIUbhVw / fn5xZXQed1pVRkmj6tF + 0 J7JdkXgVnTkOAMYpcy \
7 UogMyMFQ9uVBwfzOROZEzGrP + Y4KQymyUtwqmEsesvkULic6aGTZSWxRC7sOG6B7NdIFKUnGo0hr7qMMdLIfdiNIXytFnz27lyZSvlwK6o1wjtQdZNECa1K6KTdlI4Aiqb9TVIJ6myo91rDPePFxGRKT4COKHvk \
5 lxP3BQOezWf + 9 fYbG2NNBEX5vrUTzodTnEpUnQtpGkE2lX / nN1eFe4oJ7MPuR3uazQh23qIZperZ1zpt / VMi5h6wENNIRusqE6S7uUyCY1BkwD + XF8wXhoRsup / fMicTrVVPnwfV / IXUiqa2ac4 + gnk / Jn1CjMV \
yd0Rr9dqA3H5Y4MPEuZkrdaQtTeH0mLIyWPkHMOy0zTJrajK2EXbrRIn9zUzrnp8PDhrNRr4g24MaSIBzQo2VpVLrdnOc7F9HqDWRCpdAcvJAe217vgi48C3ndRTjjDAsU1 / Q7ypikdd4qFP9NiFjg322IgcX8cy \
Mq + NhVUoFMyT8nvU / Bu / FMBHfR + FfDouYvtvx52wn0QEkxht / RaLXvMsdX0wWrbSOxQt1o / + Dswmgxl8Wg5C9SzJ4ZTOSS7TytOg8qLV7LMQwkgn0yHbuNYzTuiglB9F / + ERdeul7yz1C53ftAsgeSfVQC0Ww5sJ \
VAdR07rRFJrShI + aQ8qRvOl / pfdKQRhIUvFK5aMCRZvgjYsES8jV09UMWU3N + c2mnDcY2U91C27WvNA8liMqa2Rfzlda8YyIByN4IKhuRpGtiw0uan6Qu4T1LyTPov / IEuZnAbU0t6IC1 + T4I5XmW9f / Cj0 + v + + i \
M50n7WG3 + LHeaxxM9jQ3lSK / KlZVQgsgtXcrIypfah7jdzYOb / sh58VX8bUE3 / ejNZCEP + UXJbRa / Y3UxdZqXLQq + n1EAZ0eEu / 2 jCXixyWT0PrP63gM5fKOAeahXSZ303HE1eQtp7jLQ9JpfP2HNwnHSPwHel9n \
XRTfpxpjyhlUpB1DpG2Fy1lVU0oYXsfjVlUhRwj / ktB6ITwqtY0vrxlBNHeqcxdZbKSo2ZZQyN + o7l88gBX9t64ryKGQWCnH + 1 D64h2qCG6Abg + ptv961eqI6d / LOxWhlms67Zr0sqNftZEZrQrBzxZe0UIUPb79 \
8 otUUqSeU2l8W8LzNYO5khedFNwV1 + bbwiuUEPTljDaPLcf7IfIs7qcwO6uUsMsMWPn7Cw81OYgWzQEkr0CwjpctK6mw + o7I73rKqyDK + GWJFV07eZen3ZETIn2S8n + U92HCC + 5 L8qlDIHg9D0niCiY9kFcPWs7C \
rg6eokv5mEVkkf8CQ6yPewfyMgvDqUj / kApVOFwbSlY / feFgRPJl2vTQe9Fj / 9 yW / IIqNA0flKcNONbTfpFAVb1ThAk6F6XiTU9dUjm0Np03MXyMWkR98hIDvZNCNFNyhGoyOQBQS + F / 3 ZMGilPXlJyDIgvRBbVf \
9 SAhj0c / Jp + HlEUfpZDdk9peiU6fNMuTFXyyu563HKlLAb + WfWIAUBreEEIoJhWfEcCY5Iv8ZKAt / kT6UFQfv2xfAa + v9kMcqLKzrTcIa28An3 / g8nGLWj57vn + CyyeTaaeHT5Rgtr9Zo5c9 / / VhWl7hlU9r8jyx \
tkhMuNJcTK8 + zQf7 / V4RButyWuq7oQBVcKZtGe7OYmyWOJPM / gf0hhEn \
""" )))
ESP32H2BETA2ROM . STUB_CODE = eval ( zlib . decompress ( base64 . b64decode ( b """
eNrNWmtb20YW / isECLTZ24yt26QbsFMbY0iyaTcpSx / TVhpJbLotz0JMQ3br / 77 znoskG + zk434w2KPRmTNn3vOei / Tf / Xl1N99 / ulXsz + 6 Mnd3Z8CnS8B0f89PJ7M5n4Vt / dpe72V1Go3thMH8V / iTfhj9RGErC \
/ 2 o7 / PFyd0R3z + 7 q8jwlGYfhj3kR5PfnYTQ6nN3M7ioTvvYGxWgnSM92WYGiN53dlb3R8 + l2uNHEeVi1Fz5hbpYNwp / + bH92BfEQdhskxOFHzbNcugijYfEqKGxd + FKHKz5oXtTpbJ + U + v00zCvD / ILvres0XXNB \
lx6yXWib4VOWKW2T5UH5RAzWaywXPsFuLnx8H / + / Xogu2Rn2OIDy43YdE / 5 nbsgmeHhRd7iQpXmFQUcHXan9bf3XsD5LD + vdF43jitIRzBMOx / VZbjjOcEDOn + rKOOxwUx5u9vVFatmcts + HzNNtdhwWKSdBth0F \
CJThzkrNEI1wDafHqxQRH5ExL7FufAhlR13DWZbvsD6NBZFp2EaV8ARCWMKYwG / nGEs6zkLUFq / 5 CLAk / hfpXmOy53Qow6C3syzCxBNMxXZywAm6McaxSFaJnIxsuh125nqjJdOKaXQrHrYchr82gKqKeTSzbJ4M \
iznIt4 / gAUCSYSxgWu3ODjCpFR208 + yEat3MHWEkmLeUkbo844OHNWBvm4pI2 + pWGPiftyKsz1CDfnUFoRN1mg4wwi3YRyIeZwRztp2iO65tuzrOo6TVSDFaKuymxFL + vggo9isvXKRjwqb4AWZk8RhwY2B + c + in \
srMEsycdJGNXXk3U7ziocFmJ2fjRYZDnmDLk + VY0J9sZXElwpbeDAxfRWQKA7PAiuW6 / K9zZy1YdWj5qF4Etg61rpQ3IJJrUM1L9c0hRNJqVzSjkaefrbUC / hUhMhKUbdxL64i0JAkv9gc3yUo / 4 SsAkH6IRHOAQ \
V + lFRJZdkZUoXAoyOdZQyHiQgyjG4OtEwwuFkVe8hy79AHBgCwFlDgCHS4UjzAaLfTmSKwglbo9d2fbPGeMUR / yxfxvEGP058j9irzgaxJFnxLzHgz2xYsVu6cS6oAPQLPk0mKXyK7EJu / Wx7nBAbjKjG2cDNlaQ \
NSOFZ6DLPu / BpYMRsw + FSYmLEiO7ofEmrthpNIR3IvLtVxKQfXX4i8ZNcRwJkA9vhMeqQojKxswQRW / CGyHoplsHfCSV / aobfSaKzcHDS4IAslroFY7fG6 / XgwAEtIcAxxSpmYMQZMcWQ81h1BeI0pkydpjCFYsd \
L5mzhoCs77IbceflgpkA2Q1CiJcrAEhWPmXsAV34X1gm + AyTix2 + wOw4RDhxTHhsj9dMmA / G5zb0L0cUYMtIMOm1hmVKX2dBeELZme1pdruuFW7h + y5FCCxyL2oQhWdttNi4ZOfkOQrAukXaJiCSXvBWn4jrincX \
sQR / aOciCf7r4q5BPmVkFkNtjOBGhHN7Dfqwi / e49A4rx3tymGELZcXWKSV3KXPRKF0AGDnnB0JPczZGpvmBKAvF6 / yAFWWq2WZwKiOtoYudVckm / rkVT1SQT4ZMaJyiXHZCLn2gpRMzsx5r9HOi3 / LtndVwnvH2 \
JnqDA27JDG / H + AInwzcT + 9 ZaRT54gnPKR7OrwFZ1cl5 / D + t + P21cFXTq / HtM0lN7d8IUi1PI4lebFNlScK7wbFUuyfjEZmBR5GYINogLTL27w3fMXwxeqJa8ZYZtiEySnQc9BzmEt9fXi / ffgQW + BxX9ADwVwi8U \
HrdBg8EIZf8Ee0Fsq1FRlagT + j8zUVSCz1zOVO27pEfvz1y2FLBrkTD7gB187xNbB2LiS4 / RDyw0i0vFU6al3Ga8fxIvyJoX / 1 kvk + DonzKjEgb9 / clh4Ru + hXhXEyI7PZCyLV7NttIVN6JcwkpGWI4gCMlDFBJO \
U / hrdjGiEaicx6mGegA1HpKp0lcMrvxzwEWBz7AUh4rZOf / bO8ZLoaTqUB7UPdG7ufKHIZ97Rjr2P0h1TCNLOnz3iWTELzvJS87BPtdaCGBewnaGyDZFzAtCcsxxeqX + NASK3kJyKjhHHPGPKuc7K3fg / 9 YiGkGm \
6 j2TIny9rjRIMPiWs / ErzsVJ1xiMqsVIk7migNPM1doJD8KclQMY / g8Ilku9t3HLsrdfXT9bdfkNvGj698j49ZM3wOCb2dU51J / + DMrJT05OcfH0yQtcfDG7egmmvnjZaboU6dlwen7dHgDSPuw1UP6BOIiQbY4Q \
Gkny32NOLQX + XkKslxBL13LWHTCnFLLHyS4KExAXsOHNFU / YlGbkhSZ82SFobKkG0sxJsxiMFcB0JV0KGz3GYdjxPzktzNjOMeOmqcuWa6pHlwcEuC3Jg7UWi7OBNtDwrasEkiZjj7SU1bNs + mM2HpM4CYZrHbpu \
d / XTbDa5j4uCRKoNaIspEdGMtV + Lmzrd7MLEV1TdUv8ERRLtJefzFCtfNe056SlAP3gcWQ6QtDQY8xfuiIw3bDinFslleiq7QnfDpst5MWg2i + threfQ9pIoNwl6zhGK8Tcr / 5 W + 0 FPdEBxzqfVcoSGK8dt0FVLZ \
d8b7Q9ym8agdD6UTl8NXUgwTX1o + KEoTUm2HzeaYSmWWXM / 7 y / PxKZOX1AQqpI5JvqGfTn9 + iZ + u6rSIaJGo7U / 4 dFcKYqnuSye1mKV74 / Ze55HC + a8P2Gltid3Z8Rew9zOlcpWddWU7W29D + gWLbvwAPQfUfLVq \
+ NklDEm9V8I8PHuwnCNR2lxtJlyHciW23Kss0p0W8bwdCgNck3inB8UHc8uYWz7NWBtzPn0CMNQJg9Ryx64iw / mCLwSk3LTz0WXzv8iVaDanK5kaFxFYYw51LGqN7xH / KMoDHXE8kscH506in5E778HvWQur5 / 5 X \
kEGkkADdV + m3UJ5 + EkLezG4keDrp79QKgvghRFhhPY4dgr5YspFS8pT48V + ly0CUfCSLUyuJsHkkS8Y8CLL0COImHnvuJhAdV4LdMjve2VVO3dnmmEJlpJTseazHEfJxBIVa7I74Wh6JSUo0jXOri9eyeNLt6PSw \
finrJ + qsGBspB21ANkxFk6oyegRLAHHqavRQwx4s5MCBAGNHEtpxvowpL0VFXqqiPVEU50cniRhUS1e79Ij62QsE7WOc4jHobQSsjTYEicyjTHegb27FSNVuKWC + VJMxsHypMB4eCU2t4Btn0egrLZia2p6CLiM5 \
B51hF / xNuzlmp / V8lHI4JpoqPCXOo5LK + hOOAl5Mn3IbtqFzJy0HajekuzuMJVO0veM8IvM / 5 u1zj3uLeuNo + 5 QUaT1zeAOkfvNggublomLSXrJNb8UVfBVIzenQjO + gKh4 / UR + a3oqfkAyRTSB4roODRqoXqdmK \
EZI2OQkeM2wLWtln4 / 6 dLKpj / MxO2k0QOtH8xLFX4occI950GWGkwXnajRF8rRTY9e5dmUtpsBK1KH0XmFVRq6FVDR3Dw8SjsbSKBWLUw2sTjd / 7 kUJ6Kt1UKhTF3JRrUnP2khdUczjKiCwXKze9PyFBGaLH5MoF \
N / zqcqHFQzngoSoTf88p3EjT8GHN8PkIOJVXDMxKctOi / + ExkykVNOnwXbuMv5L6zCw + tqMfQYmfWMyYDxLd3AmvV2vTbtNt3txqUKQT2B0yp8OQRNE4MAqfzNcjCUgCbhIp7PMWuU / cubWdHbWznT / 2 Engktnp / \
LOxVo + 7 I9FlDc3fOd1dVc7fyUyWnqyKkA59JilLajCvhuT4ziaTjIs / zilSqyLrJtI7Eu6xk + 6 k0hytpSpZK5fGYn65zzJvICCks8YSqZOyrSZQfPribZ / rQ7oNQhtTSaG1BAXaQi0knJEctvkmdurzAPb + xTmV9 \
PFq34uAHelbyD0A6GizgjvJ4Up / wOCNM5JXEa06asloTw0x8PRTrQ3bcUp88WunX0NPj / uMT6qFLN5hLC6T09VJY + Em6cla6GHhbgAiXWF1LPmbeD10DSZe + JMb6Qu + Rio1YrtATptpCfKrOBp5bB1VHmtEnnxly \
jpKzj115FmDE6YsVxFjzShNNzqnYLkcsnuL8MjjEppTd9ndbla08 + mo6E8SEYf0ryYLoPwK4Zh94GlMUS4ZEG6HpiXXDLkv75oErzug9dtotSqynPJeC8nglKOPB5voKZQmg9n7F4oT7TaqvUUwfiOXB + 27 aNwV8 \
34 + 2 gBB / xu8uaCH5b7ISH1LlOtDw4rxJ29fhrZ6zRny75ABal3kdb0OwPPaHHNpldD9NRjyMLjiMrwlT5 + 3 rOLzDISVa76k22Bar9ynxn3NHuI3mhgjdSlBhO80pyn / XPv5U + 3 H08K8JoVeSGkvB4f0tZ + 4 kO1bZ \
WdJ2N / TM1rFVZd6Q / y4 / FhX7166ry1TCby5P3GH05RlqC25K7g + p5v5ykwJbf + c0pQgFVtXpocTXHfvqAZnRprg3WnplCl5 / uvoyipQ3ZJ4zoTVLYL5lJBfy4pEiu + CCeV + 4 BLSPIXpsmrbtv4cUys243U9mDtYi \
aRigVPiH0389bOppmJxf29HBvOaGXWb1VY3f9GGrYidh197QQZNXauoDbgw2qKOgilI7CLxqHiHe0Gnpc4moXcHEx / IGQM052c3xc2j3VHqJpPJf4Pbbk96xvFPCEAoonXHioAmsdnesfvrCtygA1gRBPALrndHW \
9 yXnAGT8QA4RIZyyvlJ2S / 3 h3hniAeUBVEDpg49YnhybzusQopSTQllfJqAXQzR2IShVibTh9Zzwv4ykleHUH0vONCiEEEdQVNR2fsrdI11cYseyV9JLHD0psZXa9E6zPkUZZPd9bQ02o03EQY2YXFyvInBQ / Mk3 \
ro758Wd5BnYp7TYXfRQkJ6 / 30 EJPUMkkaKIn0xpN9OTkCO6enO6hnE5e4DLa6L2LqtNGJxow + 3 / cohcuf3w / z2 / w2qU1aRpZm0UmXKmu5jcfm8F + v5eFwTKf5 / p + JjAVPGlfhrtSjE0iZ6LF / wCAbuTq \
""" )))
ESP32C2ROM . STUB_CODE = eval ( zlib . decompress ( base64 . b64decode ( b """
eNq1Wmt7E8cV / iuObXBC8 / SZ0V4HgpFARrbBFFKCCxVNdmd2XUhwYyMH00b / vfOei3YlWyb90A + WpdmZM2fO5T2X2f / szJrL2c7djXpnemns9NLGv7qI3 / FnfjqcXvoyfkuml5WbXpY0ejsOVs / iR / 59 / EjjUB7 / \
N5vxw8vqlFZPL9vwugCN6kH8ME8j / WQWR9MH0 / PpZWPi18GwHm9F6uU2M1APDqaXYTB + eLAZF5qsirsO4l + cW5bD + JFMd6anIA9iF5FCFn + 0 PMsV8zgaN28iw9bFL2184iPndVtMd4ip35 / EeSHOr3lt2xbFmge6 \
9 YjlQseMfyEUdEymB + ZzEdhgIbn4F + Xm4p9P8P / RXHgpj3HGIZjf6 / Yx8X / pRiyC6zd1D + ayNe8w7PGgO3W / rX8E6TP1uN9V0lBXWozjZx2V4xKmG9UZFeT8E90Zyo6LqrjYt28Ly + K0CSuZp9tyP24SJpG2HUcT \
CHFlo2JIx3gG7fEudcoqMuYI + 2 YPwOy4LzjL9B32p7FIsojHaHKeQBaWs03gt3NsSzrORFQWz1kF2BL / 6 + L2QmQPSSmjyLezTMJkE0zFcSqYE3hjG8cmZSN0SpLpZjyZG4yXRCui0aN4yHIUP200qibj0dKyeEps \
5 kDffgUPgCUZtgVMa93xLiZ1pCN3np1QpVu6xxiJ4g0y0oZjVjykAXnbQkjajrfawP + 8 FWIJmxr4axsQnajT9AwjLsE5cvE4IzZnuyl64tZ2u0MfgXYjxmireJqArfxVEmDsA29cF3tkm + IHmFFmezA3NswXD / yB \
nCzH7EnPknEqryJKeg4qWBYwGz96CPIQU0Y83wrnJDuDJzmeDLagcCFd5jCQLd6k0uP3iTt70rFD26fdJpBllHWrsAGaBJOqI + W / AhW1RrNyGDV5Ovl6GdBvARKTYuuFOwl88ZHEAoP + wGF5q6 / 4 SbRJVqIRO4AS \
V + FFSIY + yUYYDmKZHGsoZFyLQRRj8HWi4YVC0TM + Qx9 + YHBACzHKCgYcH9WObDZK7JuxPEEocbfZlW3ymm2c4ojf9z9EMkZ / jv2POCtUgzhyn5B3f3hbpNiwWzqRLuAAMEs + bRhImrASnnBgn + khh + QpU1o7HbK8 \
Irkp8TwFYiZ8DFcMxwxAFCklNEqY7EfH86xhv9Eo3gvKF / ckJvvmwS8aOrF8zIyvO4iRgxBQ2YwRoh5M + BRkusXGLquksff60Weitjm8Zj94f9kKtsLrB3vrmSDrqSCRQvBR0wZBx54URprAqCMQnjNebDF + qyH2 \
XGTG7MFefR / aCDhP5gwDSG0QP7w8gXWU4S4bHkwL / 2 vL6F5icr3FDxgaR4gljtGOhfGc0fLa4NzF / eVwAtc1EkkGSkjxfJ0E4QahN9vT7G5fK8DC606ECCRyJWQQfpddqLhxS9NtySEA0q2LLvuQ3IKPekf8Vly7 \
ziTygzuXSuRfF3QNkikjsygmpHsFG29dXJwBO + z8Ix69w87ZbVFmPEJoWDpBEpdQCUfFHIZRcXIg2DRjYZSaHAizYLytdplRxplNNk6FI1esMW2Em2XiJnvf7UA4UE1GDGicopz0Qi79zS96rGRim0RAWFqevsJ / \
JoviWAVxZrtszNeyDA5KI4fzdg9f4Gf4ZjLfCayuhnegqmo8PY1Q1eav2zcQ8JuDhbcCTp3 / iEmquHeHDLFQRJk9uxlnq4Vdr + BsE5bIbCqZjVUaG79zmEFEYMTdHr1j5GLRgKn8BwZWFZamOde6DdTp7dnZ / OMr \
QMAbBJl / wJhqARcKjJvAwHj8kBzCyxDVWtRSARVC8p5RohHjJHuqOsku8TH4MxcsNSRa5ww9gAY / uClAUfZ54jH0iSmWWYC9L9KV9IuWvl6s2GE2 / / d6amSP / i5bat1wFrw6OW55zks6e4Z + D3alTstW06tixW8o \
ebCSAgYEH + zTpjHDNLU / Y58i6IApVVmhsR2WmY3IMYpnbErVF0xp + CsvdqiMnfO / vWPrqBU / HcqAdiDsLp78acRaLom15JNUwTSytPWrLycdftkZjjjd + qNyQrjyLP62RBw7QISLRCrMcfqk / bJT1oO5pE / whizl \
HwjiWNm4Xf + XzoQRUprBfam31 / NKg2QA33PifcppN / GaATy17lgkqajVNEm1dsKDkGjjYAb / TywVDGVAXeMk3jQ / ZB2UXtw7u7 / q3evkvHHrCtw + v / MS1vdyevoaDBy8B7RUh4dP8PDJnad4 + HR6egQsfnvUa6vU \
xfHoAMnJWSd65HZw9Yjru + IUuaCRhMpKQiXgM5QSSnMJpWnve8WZANZiTjPgXArVB9bCKnA2oJ3Pb85FK6 + 5 XfUAuLVU62iSpAkLxmqsaKT8sOktaMLu / ZMzwJJFnbHRLOqv5drpq5NdsrYNyXe15srKoTbK8M3Y \
x8ucWE0xM1Xnog9msz0iJ0Hv + gMjia66Ptx0Olk1jeJJTwB0voKSwymzvsZuOAklIVKmXpY / QhJ7GDskgmdw9orzfZtk / OVGRitELVSpaDvYYjlnRbpaZu2oVcF1TR5KGiL3M + yLzzL8XDxVNazdrRLJivGJzk + 1 \
KUggXGuMYWNb9AEKWVMyMCDe0njajcdihwvYUylf4YWAFvyn8F5oA2s6w1SctpHngMr + fEPecERtm1qKj / wF / XT68xv8dE2vqUObpF1HwRfb4gBSjwcnBZSltVm31nkkXf7RLnnh + T30D / b2WQ0VgML0iZd94s62 \
myD / lmkvrBltAlRqrbL4PxUe7krhcZOH92dTsru2xhgy7vnMcXuxLrR9ppLAYYsP / NQ71RRr5oKtcVmdGfXSYMzFt7CGNme5WW6yAaCK3Nf8IJrKOQ1nQqL0v8iTdDqjJ + C5 + JpSli6mUJOh1TiNfdt3uf5EyGxf \
5 EdW4peRNVcs735nUWP / AdiUqzUAtpviJdieCH9N8Wp6LuHPCRq2qv7sOluwAl0cA8TwMsknglSxhKapdAXoBx0bXR7Qcdlj2TLj4hqI5xGGTbbneX3LmMVmG8r9rW0Fxq1Njg3GXPD2VaZCDcVtwHor4kaQDPQM \
FRwEXlnduZWd837jZeDFFAruwRkJWRYpIoPSDQYNOdGkEL6DzN105jovo / uHW / lcFA3NGzuWGN3CB7uG7TayUuV0IJxCnqRHRHblMHiE7 / Ip + NyHDveBa2MIe3wD1JctimoHEOfGidTYVmJesJ1I4WueK2Yx4mga \
9 HjRyvOsERV6HTp1c2PYL + QuJEkG2bjf66SOOERlT / zPGEMpbtDjwmg8jzS + CHoG2Xeclc60VZlKuWPYk3EszyKte3wspCrMNX5heePO8k47ITbfoQgotWe9Qb3uxdEGkkUszC1ZXDScC5SHXlvJMG6cL2sWoqtI \
s6bukonoCqKUKutzIJSRSKE / yoPDBc1UaCJeJROOnwJei + zDo0miRWqVHgiMMz70cqW0K2lLO + mOQMprJILSikbDx8s + ZIw1oh / 0 wwc / C2KZgytPZpL9r0S0RoAHom7SjkOrHDppDWVjGEXT / iopBjUg1HdbI026 \
nu + WEUhmDFaBckAsC9ldcVC46vmC9oeuMXodrcZ8TtJeN1L8iQQdBDWN2HWdnLDDq8AdJV / 2 Fvet24CGYRtGaEy5MOcErQ1zLSfCkIegazpgTWWOdBrXn / YSgAZKp2wvjTBZJ59uMahTaVSM3nU7 + VOp9Mz8czf6 \
GdD8hf2M + U04d4e8X6vNvvXLhhcS52Sv1pC2t0fSGCjIY + TCwbLTNOlKQGXbRYOsFif3gbFWPb674Wo1Fvj9fgRpOvRBbWhYX3Uh1WK7SMUe8wD1FDKp6y2nBnTc0HNHNgXf9jJP17XUm2RLHKrurqXESe / rFQm1 \
+ PdYjxxgJzKyqG4FWIjz8mbzP + c7fL6Z + yQQZJitljKSt5Ne9E87OyZO2vAW + 14 wlRD2x + s223hLdyh / g + GmwzkcW64t9ebH4U7NSUbTymLgedlq9lkKasTKfsSKDnojCSlU8qNMbh1Se10axXKNSbct7ZKl / CTl \
QBCd4T0CKiKoy9xoDk2ZwifNIeUC3SRf61yp9SJSqtFSJaimol3rxnUoS + ard6E5EpvAKc62XBAYOU + 9 YnDWPNM8lmMqS + Qxk6d0YtkijFgEGet2x7KVy7BFY4N8Ju5 / KqkW / UeeoIk43dMlSwLckPuKTPpmfScs \
9 bL7uofO9Fbag371Y73XYJjuaYYqbw / U5U2l0JKN2qulEZUvgcf4DYuDVU / k7Pi8e4nAJ36MCtn6Y36tQUvZX0lcrK3GdVpFz45AoNcX4tO + Zo54uaQTWgB6He / iubwRADp0yvRqUo7gmr7lLHd9XDruXtbhQ8Ix \
Uv + R3q7ZFMEnVILMOI3qgMcQclvBCxbVjLKGV93lqIqQw4R / TtZ6KmBq2Ot8dcEWRLQzpV3mXU9E1Xb9SX76KzUFlq9LRf6t6zNyIBhWyWU8hL48QwXBTcydERX339y0OwL7C3kDItZyTa / zkp315Ks6MuP1lLx5 \
tPRCFULpk9VXVaSeIvEcS8 / akj1fsDHX8lqSGnfNtfmO4AplBYncqxZd3 / AawY57jUqzu3bakLGv9tdXHKps8IJVyF1huzz + L1ZdafVFjt / 0 NlZtJ + c3Gm7ou8kLN + 2 uXOLoSsr98ZZXJHi6uGM8J1zX64u028Fk \
+ 17 zLM7X9x + iz3iX1afnaChb2JwM9uWlE + 3 c / i7lqaC39pKs / iWCvojia3IcyGzAbrkjiQUk5zVqUI425CBP50XyVA + OER3o / pKqNr0nyeRy2fRel / BdsCLEkzcN6MURQpeKA1OTS + 9 ej43GXUikb + LUIyXZoIBC \
KIFWbaN3AEV3WWOKRSRZdk2K1AOp6hXfdOWNSbrBNeiqw62x0WRt / lF8Zu1T / t2QeVAcKr9YIqR / yD3ApzT4SFmBt2vz5 + 1 L2OvLxxH + 6 / z17TeIZm9gO3 / H4yctKvj86eMjPD6aznpdeEICs / PtBr2R + ePHWXWO \
9 zKtKYrU2jI18UlzOjv / vBhMkkEZB0M1q / QFThhVdKYdGe5TMQOXuSKf / xdxa / YZ \
2022-01-10 02:07:12 +01:00
""" )))
def _main ( ) :
try :
main ( )
except FatalError as e :
print ( ' \n A fatal error occurred: %s ' % e )
sys . exit ( 2 )
if __name__ == ' __main__ ' :
_main ( )