Using esptool in extrascript

Hi,

I need to read the MAC address from the ESP8266 after uploading the firmware in order to log it to a file. I’m trying to do this like so:

import esptool
Import("env")

def post_build(source, target, env):
    esptool.main('--chip esp8266 read_mac')

But as result I get:

usage: esptool [-h]
               [--chip {auto,esp8266,esp32,esp32s2,esp32s3beta2,esp32s3,esp32c3,esp32c6beta,esp32h2beta1,esp32h2beta2,esp32c2,esp32c6,esp32c61,esp32c5,esp32c5beta3,esp32h2,esp32p4}]
               [--port PORT] [--baud BAUD] [--port-filter PORT_FILTER]
               [--before {default_reset,usb_reset,no_reset,no_reset_no_sync}]
               [--after {hard_reset,soft_reset,no_reset,no_reset_stub}]
               [--no-stub] [--trace] [--override-vddsdio [{1.8V,1.9V,OFF}]]
               [--connect-attempts CONNECT_ATTEMPTS]
               {load_ram,dump_mem,read_mem,write_mem,write_flash,run,image_info,make_image,elf2image,read_mac,chip_id,flash_id,read_flash_status,write_flash_status,read_flash,verify_flash,erase_flash,erase_region,read_flash_sfdp,merge_bin,get_security_info,version}
               ...
esptool: error: argument operation: invalid choice: '-' (choose from 'load_ram', 'dump_mem', 'read_mem', 'write_mem', 'write_flash', 'run', 'image_info', 'make_image', 'elf2image', 'read_mac', 'chip_id', 'flash_id', 'read_flash_status', 'write_flash_status', 'read_flash', 'verify_flash', 'erase_flash', 'erase_region', 'read_flash_sfdp', 'merge_bin', 'get_security_info', 'version')

When I run the same command in the PIO terminal it works as expected:

esptool --chip esp8266 read_mac
esptool.py v4.8.1
Serial port COM14
Connecting…
Chip is ESP8266EX
Features: WiFi
Crystal is 26MHz
MAC: c4:d8:d5:30:5e:31
Uploading stub…
Running stub…
Stub running…
MAC: c4:d8:d5:30:5e:31
Hard resetting via RTS pin…

Any ideas?
Additional question: Is there a way in the script to read the configured comport which has been selected via the Platformio IDE?
grafik

Kind regards
Stefan

This must be a list of strings.

This is stored in the UPLOAD_PORT variable in the env object. If this is not set yet, you can also trigger the autodetection (as a real upload without a preset upload_port would do).

E.g.

Import("env")
import sys

platform = env.PioPlatform()
board = env.BoardConfig()
mcu = board.get("build.mcu", "esp32") # works for ESP8266 and ESP32
# make sure "import esptool" can be found by including it in the PATH 
esptoolpy = platform.get_package_dir("tool-esptoolpy")
sys.path.append(esptoolpy)

import esptool

def post_build(source, target, env):
    upload_port = env.get("UPLOAD_PORT", "none")
    upload_baud = env.get("UPLOAD_SPEED", "115200")
    # Get set upload port, or do autodetection NOW if not yet known.
    if upload_port == "none":
        env.AutodetectUploadPort()
        upload_port = env.get("UPLOAD_PORT", "none")
    esptool.main([
        "--chip", mcu, 
        "--port", str(upload_port),
        "--baud",  str(upload_baud),
        "--before", "default_reset",
        "--after", "hard_reset",
        "read_mac"
    ])

env.AddPostAction("buildprog", post_build)

Thank you Max!
Calling esptool works now, and getting the selected upload_port also works!
Now I wonder if I can read the result of the call to esptool.main in a variable in order to use it…
Kind regards
Stefan

Well sadly calling the main function of the esptool package will just print it to console. If you don’t want to do fancy “redirect the own processes’s stdout to a buffer”, you can switch to invoking $PYTHONEXE on the actual esptool.py script, which ois capturable.

Sadly I could not get the “live output” to work at all. It will only print something after esptool.py is done executing, which might take a bit.

Import("env")
from os.path import join
from os import environ
import subprocess
import sys

platform = env.PioPlatform()
board = env.BoardConfig()
mcu = board.get("build.mcu", "esp32") # works for ESP8266 and ESP32

def run_esptool_cmd(arguments: list[str]):
    esptoolpy = join(platform.get_package_dir("tool-esptoolpy") or "", "esptool.py")
    upload_port = env.get("UPLOAD_PORT", "none")
    download_speed = str(board.get("download.speed", "115200"))
    if upload_port == "none":
        env.AutodetectUploadPort()
        upload_port = env.get("UPLOAD_PORT", "none")
    esptoolpy_flags = [
            "--chip", mcu,
            "--port", str(upload_port),
            "--baud",  str(download_speed),
            "--before", "default_reset",
            "--after", "hard_reset",
    ] + arguments
    esptoolpy_cmd = [env["PYTHONEXE"], esptoolpy] + esptoolpy_flags
    print("Running: " + str(esptoolpy_cmd))
    try:
        proc_env = environ.copy()
        proc_env["PYTHONUNBUFFERED"] = "1"
        environ["PYTHONUNBUFFERED"] = "1"
        process = subprocess.Popen(esptoolpy_cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True, env=proc_env, bufsize=0, universal_newlines=True)
        output, err = process.communicate()
        return output
    except subprocess.CalledProcessError as exc:
        print("Executing esptool failed with" + str(exc))
        return ""

def post_build(source, target, env):
    ret = run_esptool_cmd(["read_mac"])
    print("Got output: ")
    print(ret)

env.AddPostAction("buildprog", post_build)

After digging deeper in the sourcecode of esptool I succeeded with his:

import esptool 
Import("env")

platform = env.PioPlatform()
board = env.BoardConfig()
mcu = board.get("build.mcu", "esp32") # works for ESP8266 and ESP32

def post_build(source, target, env):
    upload_port = env.get("UPLOAD_PORT", None)
    if upload_port == None:
        env.AutodetectUploadPort()
        upload_port = env.get("UPLOAD_PORT", "none")
    with esptool.cmds.detect_chip(port=upload_port) as esp:
        mac = esp.read_mac()
        macstr = ":".join(map(lambda x: "%02X" % x, mac))
        print("MAC is ", macstr)

env.AddPostAction("buildprog", post_build)
1 Like