mirror of
https://github.com/espressif/esp-idf
synced 2025-03-26 01:10:12 -04:00
1. example test uses auto detect flash size. we need to call `detect_flash_size` before write flash 2. fix incorrect baudrate used: when using WROVER-Kits, it's likely that download with baudrate 921600 will fail. If we don't reset serial setting in decorator, 921600 will become the default baudrate. This causes all the subsequent communication fails 3. do hw reset after used esptool function
254 lines
8.6 KiB
Python
254 lines
8.6 KiB
Python
# Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http:#www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
""" DUT for IDF applications """
|
|
import os
|
|
import os.path
|
|
import sys
|
|
import re
|
|
import functools
|
|
import tempfile
|
|
|
|
from serial.tools import list_ports
|
|
|
|
import DUT
|
|
|
|
try:
|
|
import esptool
|
|
except ImportError: # cheat and use IDF's copy of esptool if available
|
|
idf_path = os.getenv("IDF_PATH")
|
|
if not idf_path or not os.path.exists(idf_path):
|
|
raise
|
|
sys.path.insert(0, os.path.join(idf_path, "components", "esptool_py", "esptool"))
|
|
import esptool
|
|
|
|
|
|
class IDFToolError(OSError):
|
|
pass
|
|
|
|
|
|
def _uses_esptool(func):
|
|
""" Suspend listener thread, connect with esptool,
|
|
call target function with esptool instance,
|
|
then resume listening for output
|
|
"""
|
|
@functools.wraps(func)
|
|
def handler(self, *args, **kwargs):
|
|
self.stop_receive()
|
|
|
|
settings = self.port_inst.get_settings()
|
|
|
|
try:
|
|
rom = esptool.ESP32ROM(self.port_inst)
|
|
rom.connect('hard_reset')
|
|
esp = rom.run_stub()
|
|
|
|
ret = func(self, esp, *args, **kwargs)
|
|
# do hard reset after use esptool
|
|
esp.hard_reset()
|
|
finally:
|
|
# always need to restore port settings
|
|
self.port_inst.apply_settings(settings)
|
|
|
|
self.start_receive()
|
|
|
|
return ret
|
|
return handler
|
|
|
|
|
|
class IDFDUT(DUT.SerialDUT):
|
|
""" IDF DUT, extends serial with esptool methods
|
|
|
|
(Becomes aware of IDFApp instance which holds app-specific data)
|
|
"""
|
|
|
|
# /dev/ttyAMA0 port is listed in Raspberry Pi
|
|
# /dev/tty.Bluetooth-Incoming-Port port is listed in Mac
|
|
INVALID_PORT_PATTERN = re.compile(r"AMA|Bluetooth")
|
|
# if need to erase NVS partition in start app
|
|
ERASE_NVS = True
|
|
|
|
def __init__(self, name, port, log_file, app, **kwargs):
|
|
super(IDFDUT, self).__init__(name, port, log_file, app, **kwargs)
|
|
|
|
@classmethod
|
|
def get_mac(cls, app, port):
|
|
"""
|
|
get MAC address via esptool
|
|
|
|
:param app: application instance (to get tool)
|
|
:param port: serial port as string
|
|
:return: MAC address or None
|
|
"""
|
|
try:
|
|
esp = esptool.ESP32ROM(port)
|
|
esp.connect()
|
|
return esp.read_mac()
|
|
except RuntimeError:
|
|
return None
|
|
finally:
|
|
# do hard reset after use esptool
|
|
esp.hard_reset()
|
|
esp._port.close()
|
|
|
|
@classmethod
|
|
def confirm_dut(cls, port, app, **kwargs):
|
|
return cls.get_mac(app, port) is not None
|
|
|
|
@_uses_esptool
|
|
def _try_flash(self, esp, erase_nvs, baud_rate):
|
|
"""
|
|
Called by start_app() to try flashing at a particular baud rate.
|
|
|
|
Structured this way so @_uses_esptool will reconnect each time
|
|
"""
|
|
try:
|
|
# note: opening here prevents us from having to seek back to 0 each time
|
|
flash_files = [(offs, open(path, "rb")) for (offs, path) in self.app.flash_files]
|
|
|
|
if erase_nvs:
|
|
address = self.app.partition_table["nvs"]["offset"]
|
|
size = self.app.partition_table["nvs"]["size"]
|
|
nvs_file = tempfile.TemporaryFile()
|
|
nvs_file.write(b'\xff' * size)
|
|
nvs_file.seek(0)
|
|
flash_files.append((int(address, 0), nvs_file))
|
|
|
|
# fake flasher args object, this is a hack until
|
|
# esptool Python API is improved
|
|
class FlashArgs(object):
|
|
def __init__(self, attributes):
|
|
for key, value in attributes.items():
|
|
self.__setattr__(key, value)
|
|
|
|
flash_args = FlashArgs({
|
|
'flash_size': self.app.flash_settings["flash_size"],
|
|
'flash_mode': self.app.flash_settings["flash_mode"],
|
|
'flash_freq': self.app.flash_settings["flash_freq"],
|
|
'addr_filename': flash_files,
|
|
'no_stub': False,
|
|
'compress': True,
|
|
'verify': False,
|
|
'encrypt': False,
|
|
})
|
|
|
|
esp.change_baud(baud_rate)
|
|
esptool.detect_flash_size(esp, flash_args)
|
|
esptool.write_flash(esp, flash_args)
|
|
finally:
|
|
for (_, f) in flash_files:
|
|
f.close()
|
|
|
|
def start_app(self, erase_nvs=ERASE_NVS):
|
|
"""
|
|
download and start app.
|
|
|
|
:param: erase_nvs: whether erase NVS partition during flash
|
|
:return: None
|
|
"""
|
|
for baud_rate in [921600, 115200]:
|
|
try:
|
|
self._try_flash(erase_nvs, baud_rate)
|
|
break
|
|
except RuntimeError:
|
|
continue
|
|
else:
|
|
raise IDFToolError()
|
|
|
|
@_uses_esptool
|
|
def reset(self, esp):
|
|
"""
|
|
hard reset DUT
|
|
|
|
:return: None
|
|
"""
|
|
# decorator `_use_esptool` will do reset
|
|
# so we don't need to do anything in this method
|
|
pass
|
|
|
|
@_uses_esptool
|
|
def erase_partition(self, esp, partition):
|
|
"""
|
|
:param partition: partition name to erase
|
|
:return: None
|
|
"""
|
|
raise NotImplementedError() # TODO: implement this
|
|
# address = self.app.partition_table[partition]["offset"]
|
|
size = self.app.partition_table[partition]["size"]
|
|
# TODO can use esp.erase_region() instead of this, I think
|
|
with open(".erase_partition.tmp", "wb") as f:
|
|
f.write(chr(0xFF) * size)
|
|
|
|
@_uses_esptool
|
|
def dump_flush(self, esp, output_file, **kwargs):
|
|
"""
|
|
dump flush
|
|
|
|
:param output_file: output file name, if relative path, will use sdk path as base path.
|
|
:keyword partition: partition name, dump the partition.
|
|
``partition`` is preferred than using ``address`` and ``size``.
|
|
:keyword address: dump from address (need to be used with size)
|
|
:keyword size: dump size (need to be used with address)
|
|
:return: None
|
|
"""
|
|
if os.path.isabs(output_file) is False:
|
|
output_file = os.path.relpath(output_file, self.app.get_log_folder())
|
|
if "partition" in kwargs:
|
|
partition = self.app.partition_table[kwargs["partition"]]
|
|
_address = partition["offset"]
|
|
_size = partition["size"]
|
|
elif "address" in kwargs and "size" in kwargs:
|
|
_address = kwargs["address"]
|
|
_size = kwargs["size"]
|
|
else:
|
|
raise IDFToolError("You must specify 'partition' or ('address' and 'size') to dump flash")
|
|
|
|
content = esp.read_flash(_address, _size)
|
|
with open(output_file, "wb") as f:
|
|
f.write(content)
|
|
|
|
@classmethod
|
|
def list_available_ports(cls):
|
|
ports = [x.device for x in list_ports.comports()]
|
|
espport = os.getenv('ESPPORT')
|
|
if not espport:
|
|
# It's a little hard filter out invalid port with `serial.tools.list_ports.grep()`:
|
|
# The check condition in `grep` is: `if r.search(port) or r.search(desc) or r.search(hwid)`.
|
|
# This means we need to make all 3 conditions fail, to filter out the port.
|
|
# So some part of the filters will not be straight forward to users.
|
|
# And negative regular expression (`^((?!aa|bb|cc).)*$`) is not easy to understand.
|
|
# Filter out invalid port by our own will be much simpler.
|
|
return [x for x in ports if not cls.INVALID_PORT_PATTERN.search(x)]
|
|
|
|
# On MacOs with python3.6: type of espport is already utf8
|
|
if isinstance(espport, type(u'')):
|
|
port_hint = espport
|
|
else:
|
|
port_hint = espport.decode('utf8')
|
|
|
|
# If $ESPPORT is a valid port, make it appear first in the list
|
|
if port_hint in ports:
|
|
ports.remove(port_hint)
|
|
return [port_hint] + ports
|
|
|
|
# On macOS, user may set ESPPORT to /dev/tty.xxx while
|
|
# pySerial lists only the corresponding /dev/cu.xxx port
|
|
if sys.platform == 'darwin' and 'tty.' in port_hint:
|
|
port_hint = port_hint.replace('tty.', 'cu.')
|
|
if port_hint in ports:
|
|
ports.remove(port_hint)
|
|
return [port_hint] + ports
|
|
|
|
return ports
|