Back to skills
SkillHub ClubShip Full StackFull StackTesting

esp-idf-helper

Help develop, build, flash, and debug ESP32/ESP8266 firmware using Espressif ESP-IDF on Linux/WSL. Use when the user asks about ESP-IDF project setup, configuring targets, menuconfig, building, flashing via esptool/idf.py, serial monitor, partition tables, sdkconfig, troubleshooting build/flash/monitor errors, or automating common idf.py workflows from the command line.

Packaged view

This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.

Stars
3,127
Hot score
99
Updated
March 20, 2026
Overall rating
C4.6
Composite score
4.6
Best-practice grade
B73.6

Install command

npx @skill-hub/cli install openclaw-skills-esp-idf-helper

Repository

openclaw/skills

Skill path: skills/547895019/esp-idf-helper

Help develop, build, flash, and debug ESP32/ESP8266 firmware using Espressif ESP-IDF on Linux/WSL. Use when the user asks about ESP-IDF project setup, configuring targets, menuconfig, building, flashing via esptool/idf.py, serial monitor, partition tables, sdkconfig, troubleshooting build/flash/monitor errors, or automating common idf.py workflows from the command line.

Open repository

Best for

Primary workflow: Ship Full Stack.

Technical facets: Full Stack, Testing.

Target audience: everyone.

License: Unknown.

Original source

Catalog source: SkillHub Club.

Repository owner: openclaw.

This is still a mirrored public skill entry. Review the repository before installing into production workflows.

What it helps with

  • Install esp-idf-helper into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
  • Review https://github.com/openclaw/skills before adding esp-idf-helper to shared team environments
  • Use esp-idf-helper for development workflows

Works across

Claude CodeCodex CLIGemini CLIOpenCode

Favorites: 0.

Sub-skills: 0.

Aggregator: No.

Original source / Raw SKILL.md

---
name: esp-idf-helper
description: Help develop, build, flash, and debug ESP32/ESP8266 firmware using Espressif ESP-IDF on Linux/WSL. Use when the user asks about ESP-IDF project setup, configuring targets, menuconfig, building, flashing via esptool/idf.py, serial monitor, partition tables, sdkconfig, troubleshooting build/flash/monitor errors, or automating common idf.py workflows from the command line.
---

# esp-idf-helper

## Goal
Provide a repeatable, command-line-first workflow for ESP-IDF development on Linux/WSL: configure → build → flash → monitor → debug/troubleshoot.

## Quick start (typical loop)

### Method 1: Activate ESP-IDF first (Recommended)
```bash
# 1) Source the ESP-IDF environment (once per terminal session)
cd /path/to/esp-idf
. ./export.sh

# 1.1) Enable ccache to speed up compilation (recommended)
export IDF_CCACHE_ENABLE=1

# 2) Go to your project and build
cd /path/to/your/project
idf.py set-target <target>    # Set target chip (once per project)
idf.py build                 # Compile
idf.py -p <PORT> -b <BAUD> flash  # Flash to device (optional)
```

### Common commands
- `idf.py set-target <target>` — Set chip target: esp32, esp32s2, esp32s3, esp32c3, esp32p4
- `idf.py menuconfig` — Configure project settings (**must run in a new terminal window**)
- `idf.py build` — Build the project
- `idf.py update-dependencies` — Update project component dependencies
- `idf.py partition-table` — Build partition table and print partition entries
- `idf.py partition-table-flash` — Flash partition table to device
- `idf.py storage-flash` — Flash storage filesystem partition
- `idf.py size` — Show firmware size information
- `idf.py -p <PORT> -b <BAUD> flash` — Flash firmware (default baud: 1152000)
- `idf.py -p <PORT> monitor` — Open serial monitor
- `idf.py -p <PORT> -b <BAUD> monitor` — Open serial monitor with specific baud (e.g. 1152000)
- `idf.py -p <PORT> -b <BAUD> flash monitor` — Flash then monitor
- `bash {baseDir}/scripts/monitor_auto_attach.sh --project <PROJECT_DIR> --idf <IDF_DIR> --port <PORT> --baud <BAUD>` — Auto attach serial (usbipd) and retry monitor on open failure
- `bash {baseDir}/scripts/flash_with_progress.sh --project <PROJECT_DIR> --idf <IDF_DIR> --port <PORT> --mode <MODE>` — Flash with real-time progress output (supports auto usbipd attach retry, error summary, and second retry)

#### flash_with_progress 使用示例

```bash
# 普通烧录整个固件(带进度)
bash {baseDir}/scripts/flash_with_progress.sh \
  --project <PROJECT_DIR> \
  --idf <IDF_DIR> \
  --port <PORT> \
  --mode flash

# 加密烧录 app(带进度)
bash {baseDir}/scripts/flash_with_progress.sh \
  --project <PROJECT_DIR> \
  --idf <IDF_DIR> \
  --port <PORT> \
  --mode encrypted-app-flash

# 仅烧录分区表(带进度)
bash {baseDir}/scripts/flash_with_progress.sh \
  --project <PROJECT_DIR> \
  --idf <IDF_DIR> \
  --port <PORT> \
  --mode partition-table-flash

# 仅烧录文件系统分区(storage,带进度)
bash {baseDir}/scripts/flash_with_progress.sh \
  --project <PROJECT_DIR> \
  --idf <IDF_DIR> \
  --port <PORT> \
  --mode storage-flash

# 指定波特率并关闭自动串口映射重试
bash {baseDir}/scripts/flash_with_progress.sh \
  --project <PROJECT_DIR> \
  --idf <IDF_DIR> \
  --port <PORT> \
  --baud 460800 \
  --mode flash \
  --no-auto-attach

# 串口异常时自动二次重试(默认 retries=2,也可手动指定)
bash {baseDir}/scripts/flash_with_progress.sh \
  --project <PROJECT_DIR> \
  --idf <IDF_DIR> \
  --port <PORT> \
  --mode encrypted-app-flash \
  --retries 2
```
- `idf.py fullclean` — Clean build directory

### 在新窗口打开 monitor(Windows + WSL2)

推荐用脚本方式,避免 PowerShell/cmd 引号和 UNC 路径问题。

### 在新窗口打开 menuconfig(必须)

`menuconfig` 是 TUI 交互界面,必须在新窗口打开(不要在非交互后台中运行)。

1) 在 WSL 直接运行(你已在独立终端时):

```bash
bash {baseDir}/scripts/run_menuconfig.sh \
  --project <PROJECT_DIR> \
  --idf <IDF_DIR>
```

2) 从 Windows PowerShell 拉起新窗口运行:

```powershell
Start-Process powershell -ArgumentList '-NoExit','-Command','wsl.exe -d <DISTRO> --cd / -- bash {baseDir}/scripts/run_menuconfig.sh --project <PROJECT_DIR> --idf <IDF_DIR>'
```

### 打开串口失败时自动映射并重试

当 `idf.py monitor` 因串口打开失败(端口不存在/被占用)报错时,可自动执行 usbipd 串口映射脚本并重试一次:

```bash
bash {baseDir}/scripts/monitor_auto_attach.sh \
  --project <PROJECT_DIR> \
  --idf <IDF_DIR> \
  --port <PORT> \
  --baud <BAUD> \
  --keyword "ESP32"
```


1) 在 WSL 创建启动脚本:

```bash
cat >/tmp/run_monitor.sh <<'EOF'
#!/usr/bin/env bash
set -e
cd /path/to/your/project
source /path/to/esp-idf/export.sh
exec idf.py -p <PORT> -b 1152000 monitor
EOF
chmod +x /tmp/run_monitor.sh
```

2) 在 Windows PowerShell 新开窗口执行:

```powershell
Start-Process cmd.exe -WorkingDirectory C:\ -ArgumentList '/k','wsl.exe -d <DISTRO> -- bash /tmp/run_monitor.sh'
```

说明:
- 使用 `cmd.exe -WorkingDirectory C:\` 可以避免 `\\wsl.localhost\...` UNC 路径导致工作目录掉到 `C:\Windows`。
- 退出 monitor:`Ctrl+]`。

## Workflow decision tree
- If the user has **no project yet** → create from example/template; confirm target chip and IDF version.
- If **build fails** → collect the *first* error lines; identify missing deps/toolchain/cmake/python packages; confirm IDF env.
- If **flash fails** → confirm PORT permissions/WSL USB passthrough, baud rate, boot mode, correct chip target.
- If **monitor is gibberish** → wrong baud (monitor uses app baud), wrong serial adapter settings, or wrong console encoding.
- If **boot loop / panic** → request panic backtrace; decode with `addr2line` (or `idf.py monitor` built-in) and check partition/sdkconfig.
- If **`[Errno 11] Could not exclusively lock port <PORT>`** → serial port is occupied by another process; force release it with:
  ```bash
  sudo fuser -k <PORT>
  ```

## What to ask the user for (minimal)
1) Chip target: e.g. `esp32`, `esp32s2`, `esp32s3`, `esp32c3`, `esp32p4`.
2) ESP-IDF version + how it’s installed/activated (IDF Tools installer vs git clone; `IDF_PATH` / `ESPIDF_ROOT`).
3) Project path + whether it’s an ESP-IDF project (has `CMakeLists.txt`, `main/`, `sdkconfig`).
4) Serial port path: use `<PORT>` (e.g. `/dev/ttyUSB0`, `/dev/ttyACM*`, or WSL mapped `/dev/ttyS*`).
5) Exact failing command + the *first* error block in output.

## 串口设备获取(Windows + WSL2)

先在 Windows PowerShell 找到串口号,再在 WSL2 里映射 USB 设备。

### 安装 usbipd-win(Windows,推荐)

在 **管理员模式** PowerShell 中执行:

```powershell
winget install dorssel.usbipd-win
```

然后查看并映射设备:

```powershell
# Windows PowerShell:查看 USB 设备与 BUSID
usbipd list

# 将设备挂载到 WSL(每次重新插拔 USB 后都要重新执行)
usbipd attach --wsl --busid=<BUSID>
```

### 串口自动映射(推荐)

可直接使用脚本自动选择并挂载串口设备(优先选择 Connected 区域中的 serial 设备,且优先 STATE=Shared):

```bash
bash {baseDir}/scripts/usbipd_attach_serial.sh
```

常用参数:

```bash
# 指定 BUSID
bash {baseDir}/scripts/usbipd_attach_serial.sh --busid <BUSID>

# 指定发行版
bash {baseDir}/scripts/usbipd_attach_serial.sh --distro <DISTRO>

# 按关键词筛选设备(如 ESP32 / COMxx / CP210x)
bash {baseDir}/scripts/usbipd_attach_serial.sh --keyword "ESP32"

# 仅预览,不执行
bash {baseDir}/scripts/usbipd_attach_serial.sh --dry-run
```

说明:
- 每次 USB 设备重新插拔后,`BUSID` 可能变化,需要重新 `usbipd list` 或重新运行自动映射脚本。
- 重新插拔 USB 后,通常都要再执行一次 `usbipd attach --wsl --busid=<BUSID>`(脚本会自动执行)。
- 在 WSL2 内可用 `ls /dev/ttyUSB* /dev/ttyACM* 2>/dev/null` 确认串口节点。

### 首次需要手动加载内核模块(WSL 2.3.11+)

在较新的 WSL 版本中,内核采用模块化设计,`vhci_hcd`(虚拟主机控制器接口)驱动可能不会自动加载。

在 WSL 终端执行:

```bash
sudo modprobe vhci_hcd
```

## New Feature: Create ESP-IDF Projects
- **Description**: Create a new ESP-IDF project or create a project from an example in the ESP Component Registry.
- **Usage**:
  ```bash
  idf.py create-project <project_name> <project_path>
  idf.py create-project-from-example <example_name> <project_path>
  ```

## Flash Encryption Support (加密烧录)

直接使用 `idf.py` 执行加密/非加密应用烧录:

```bash
# 加密烧录整个固件 (bootloader + partition table + app)
idf.py -p <PORT> encrypted-flash

# 加密烧录 app 分区
idf.py -p <PORT> encrypted-app-flash

# 非加密烧录 app 分区
idf.py -p <PORT> app-flash

# 非加密烧录整个固件 (bootloader + partition table + app)
idf.py -p <PORT> flash
```


## Bundled resources
### references/
- `references/esp-idf-cli.md` — concise command patterns + what to paste back when reporting errors.
- `references/idf-py-help.txt` — captured `idf.py --help` output for quick lookup/search.

To refresh the help text for your installed ESP-IDF version, run:
- `scripts/capture_idf_help.sh`

### assets/
Not used by default.

---

## Referenced Files

> The following files are referenced in this skill and included for context.

### references/esp-idf-cli.md

```markdown
# ESP-IDF CLI patterns (Linux/WSL)

Keep this reference short; paste outputs when troubleshooting.

## Recommended workflow

```bash
# 1. Activate ESP-IDF environment (do this first)
cd /path/to/esp-idf
. ./export.sh

# 2. Build your project
cd /path/to/your-project
idf.py set-target esp32s2
idf.py build
idf.py -p /dev/ttyS33 flash monitor
```

## Environment sanity
- Confirm idf.py exists:
  - `command -v idf.py`
  - `idf.py --version`
- If `idf.py` is missing:
  - `source $IDF_PATH/export.sh` (recommended)
  - or run with wrapper: `./scripts/espidf.sh --idf-path /path/to/esp-idf build`
- In an ESP-IDF project dir, these should exist:
  - `CMakeLists.txt`
  - `main/`

## Common commands
- Select chip target (once per project):
  - `idf.py set-target esp32`
  - `idf.py set-target esp32s2`
  - `idf.py set-target esp32s3`
  - `idf.py set-target esp32c3`
- Configure:
  - `idf.py menuconfig`
- Build:
  - `idf.py build`
- Flash:
  - `idf.py -p /dev/ttyS33 -b 1152000 flash`
- Monitor:
  - `idf.py -p /dev/ttyS33 monitor`
- Flash + monitor:
  - `idf.py -p /dev/ttyS33 -b 1152000 flash monitor`
- Clean:
  - `idf.py fullclean`
- Erase flash (careful):
  - `idf.py -p /dev/ttyUSB0 erase-flash`

## What to paste back when something fails
1) Your chip target (`esp32`, `esp32s3`, etc.)
2) `idf.py --version`
3) The exact command you ran
4) The *first* error lines (usually near the top)
5) Your serial port path and whether it exists: `ls -l <PORT>`

## Serial notes
- If monitor output is unreadable, baud is likely wrong.
- Flash baud (`-b`) and app UART baud (menuconfig) can differ.
- If flash is flaky, drop to `-b 115200`.

## WSL notes (generic)
- Your ports may look like `/dev/ttyS33` (WSL mapped serial). Use whatever exists under `/dev/ttyS*`.
- Ensure the USB-serial device is visible in WSL.
- Ensure your user has permission to open the tty device (group membership).

```

### references/idf-py-help.txt

```text
Usage: idf.py [OPTIONS] COMMAND1 [ARGS]... [COMMAND2 [ARGS]...]...

  ESP-IDF CLI build management tool. For commands that are not known to idf.py
  an attempt to execute it as a build system target will be made. Selected
  target: None

Options:
  --version                       Show IDF version and exit.
  --list-targets                  Print list of supported targets and exit.
  -C, --project-dir PATH          Project directory.
  -B, --build-dir PATH            Build directory.
  -w, --cmake-warn-uninitialized / -n, --no-warnings
                                  Enable CMake uninitialized variable warnings
                                  for CMake files inside the project
                                  directory. (--no-warnings is now the
                                  default, and doesn't need to be specified.)
                                  The default value can be set with the
                                  IDF_CMAKE_WARN_UNINITIALIZED environment
                                  variable.
  -v, --verbose                   Verbose build output.
  --preview                       Enable IDF features that are still in
                                  preview.
  --ccache / --no-ccache          Use ccache in build. Disabled by default.
                                  The default value can be set with the
                                  IDF_CCACHE_ENABLE environment variable.
  -G, --generator [Ninja|Unix Makefiles]
                                  CMake generator.
  --no-hints                      Disable hints on how to resolve errors and
                                  logging.
  -D, --define-cache-entry TEXT   Create a cmake cache entry. This option can
                                  be used at most once either globally, or for
                                  one subcommand.
  -p, --port PATH                 Serial port. The default value can be set
                                  with the ESPPORT environment variable. This
                                  option can be used at most once either
                                  globally, or for one subcommand.
  -b, --baud INTEGER              Global baud rate for all idf.py subcommands
                                  if they don't overwrite it locally.It can
                                  imply monitor baud rate as well if it hasn't
                                  been defined locally. The default value can
                                  be set with the ESPBAUD environment
                                  variable. This option can be used at most
                                  once either globally, or for one subcommand.
  --help                          Show this message and exit.

Commands:
  add-dependency                  Add dependency to the manifest file.
  all                             Aliases: build. Build the project.
  app                             Build only the app.
  app-flash                       Flash the app only.
  bootloader                      Build only bootloader.
  bootloader-flash                Flash bootloader only.
  build-system-targets            Print list of build system targets.
  clang-check                     run clang-tidy check under current folder,
                                  write the output into "warnings.txt"
  clang-html-report               generate html report to "html_report" folder
                                  by reading "warnings.txt" (may take a few
                                  minutes). This feature requires extra
                                  dependency "codereport". Please install this
                                  by running "pip install codereport"
  clean                           Delete build output files from the build
                                  directory.
  confserver                      Run JSON configuration server.
  coredump-debug                  Create core dump ELF file and run GDB debug
                                  session with this file.
  coredump-info                   Print crashed task’s registers, callstack,
                                  list of available tasks in the system,
                                  memory regions and contents of memory stored
                                  in core dump (TCBs and stacks)
  create-component                Create a new component.
  create-manifest                 Create manifest for specified component.
  create-project                  Create a new project.
  create-project-from-example     Create a project from an example...
  diag                            Create diagnostic report.
  docs                            Open web browser with documentation for ESP-
                                  IDF
  efuse-burn                      Burn the eFuse with the specified name.
  efuse-burn-key                  Burn a 256-bit key to EFUSE: BLOCK1,
                                  flash_encryption, BLOCK2, secure_boot_v1,
                                  secure_boot_v2, BLOCK3.
  efuse-common-table              Generate C-source for IDF's eFuse fields.
  efuse-custom-table              Generate C-source for user's eFuse fields.
  efuse-dump                      Dump raw hex values of all eFuses.
  efuse-read-protect              Disable writing to the eFuse with the
                                  specified name.
  efuse-summary                   Get the summary of the eFuses.
  efuse-write-protect             Disable writing to the eFuse with the
                                  specified name.
  encrypted-app-flash             Flash the encrypted app only.
  encrypted-flash                 Flash the encrypted project.
  erase-flash                     Erase entire flash chip.
  erase-otadata                   Erase otadata partition.
  flash                           Flash the project.
  fullclean                       Delete the entire build directory contents.
  gdb                             Run the GDB.
  gdbgui                          GDB UI in default browser.
  gdbtui                          GDB TUI mode.
  menuconfig                      Run "menuconfig" project configuration tool.
  merge-bin
  monitor                         Display serial output.
  openocd                         Run openocd from current path
  partition-table                 Build only partition table.
  partition-table-flash           Flash partition table only.
  post-debug                      Utility target to read the output of async
                                  debug action and stop them.
  python-clean                    Delete generated Python byte code from the
                                  IDF directory
  qemu                            Run QEMU.
  read-otadata                    Read otadata partition.
  reconfigure                     Re-run CMake.
  save-defconfig                  Generate a sdkconfig.defaults with options
                                  different from the default ones
  secure-decrypt-flash-data
  secure-digest-secure-bootloader
                                  Take a bootloader binary image and a secure
                                  boot key, and output a combineddigest+binary
                                  suitable for flashing along with the
                                  precalculated secure boot key.
  secure-encrypt-flash-data       Encrypt some data suitable for encrypted
                                  flash (using known key).
  secure-encrypt-nvs-partition    Encrypt the NVS partition.
  secure-generate-flash-encryption-key
  secure-generate-key-digest      Generate a digest of a puiblic key file for
                                  use with secure boot.
  secure-generate-nvs-partition-key
                                  Generate a key for NVS partition encryption.
  secure-generate-signing-key     Generate a private key for signing secure
                                  boot images as per the secure boot version.
                                  Key file is generated in PEM format, Secure
                                  Boot V1 - ECDSA NIST256p private key. Secure
                                  Boot V2 - RSA 3072, ECDSA NIST256p, ECDSA
                                  NIST192p private key.
  secure-sign-data                Sign a data file for use with secure boot.
                                  Signing algorithm is deterministic ECDSA w/
                                  SHA-512 (V1) or either RSA-PSS or ECDSA w/
                                  SHA-256 (V2).
  secure-verify-signature         Verify a previously signed binary image,
                                  using the ECDSA (V1) or either RSA or ECDSA
                                  (V2) public key.
  set-target                      Set the chip target to build.
  show-efuse-table                Print eFuse table.
  size                            Print basic size information about the app.
  size-components                 Print per-component size information.
  size-files                      Print per-source-file size information.
  uf2                             Generate the UF2 binary with all the
                                  binaries included
  uf2-app                         Generate an UF2 binary for the application
                                  only
  update-dependencies             Update dependencies of the project

```

### scripts/capture_idf_help.sh

```bash
#!/usr/bin/env bash
set -euo pipefail

# Capture idf.py help text into references/ so it can be searched quickly.
# Uses ESPIDF_ROOT to activate the environment if available.

OUT_REL="references/idf-py-help.txt"

ESPIDF_ROOT="${ESPIDF_ROOT:-}"

cd "$(dirname "$0")/.."  # skill root

if [[ -n "${ESPIDF_ROOT:-}" && -f "$ESPIDF_ROOT/export.sh" ]]; then
  bash -lc "set -euo pipefail; source \"$ESPIDF_ROOT/export.sh\" >/dev/null; idf.py --help" > "$OUT_REL"
elif command -v idf.py >/dev/null 2>&1; then
  idf.py --help > "$OUT_REL"
else
  echo "ERROR: idf.py not found. Either activate ESP-IDF in this shell, or set ESPIDF_ROOT to an ESP-IDF checkout (with export.sh)." >&2
  exit 1
fi

echo "Wrote: $(pwd)/$OUT_REL"

```



---

## Skill Companion Files

> Additional files collected from the skill directory layout.

### _meta.json

```json
{
  "owner": "547895019",
  "slug": "esp-idf-helper",
  "displayName": "ESP-IDF Helper",
  "latest": {
    "version": "1.0.0",
    "publishedAt": 1771152649659,
    "commit": "https://github.com/openclaw/skills/commit/0fb6d615d35749fd493151118ebb5411c18bcecf"
  },
  "history": []
}

```

### scripts/flash_with_progress.sh

```bash
#!/usr/bin/env bash
set -euo pipefail

# Real-time flash with progress + optional usbipd auto-attach retries
# Supports automatic second retry on serial disconnect/open failures.

PROJECT_DIR=""
IDF_DIR=""
PORT="/dev/ttyACM0"
BAUD="1152000"
MODE="flash" # flash | app-flash | encrypted-app-flash | encrypted-flash | partition-table-flash | storage-flash
KEYWORD="ESP32"
AUTO_ATTACH=1
RETRIES=2

usage(){
  cat <<'EOF'
Usage:
  flash_with_progress.sh --project <PROJECT_DIR> --idf <IDF_DIR> [--port <PORT>] [--baud <BAUD>] [--mode <MODE>] [--keyword <TEXT>] [--retries <N>] [--no-auto-attach]

Modes:
  flash | app-flash | encrypted-app-flash | encrypted-flash | partition-table-flash | storage-flash

Notes:
  - Retries are full re-run retries (cannot resume partial esptool writes).
  - Progress is printed in real time (Writing at ... xx%).
EOF
}

while [[ $# -gt 0 ]]; do
  case "$1" in
    --project) PROJECT_DIR="$2"; shift 2;;
    --idf) IDF_DIR="$2"; shift 2;;
    --port) PORT="$2"; shift 2;;
    --baud) BAUD="$2"; shift 2;;
    --mode) MODE="$2"; shift 2;;
    --keyword) KEYWORD="$2"; shift 2;;
    --retries) RETRIES="$2"; shift 2;;
    --no-auto-attach) AUTO_ATTACH=0; shift;;
    -h|--help) usage; exit 0;;
    *) echo "Unknown arg: $1"; usage; exit 2;;
  esac
done

[[ -n "$PROJECT_DIR" && -n "$IDF_DIR" ]] || { usage; exit 2; }

BASE_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
ATTACH="$BASE_DIR/scripts/usbipd_attach_serial.sh"

run_flash(){
  cd "$PROJECT_DIR"
  # shellcheck disable=SC1090
  source "$IDF_DIR/export.sh"
  stdbuf -oL -eL idf.py -p "$PORT" -b "$BAUD" "$MODE"
}

is_retryable_error(){
  local log="$1"
  grep -Eiq "Could not open .*${PORT}|Could not exclusively lock port|Resource temporarily unavailable|No such file or directory: '${PORT}'|Serial port ${PORT}|failed to connect|connection closed|No /dev/ttyACM|No /dev/ttyUSB" "$log"
}

print_error_summary(){
  local log="$1"
  echo "[error] 失败关键信息:"
  grep -Ei "fatal|error|failed|Could not open|No such file|Resource temporarily unavailable|CMake Error|ninja failed" "$log" | tail -n 20 || true
}

attempt=0
max_attempts=$((RETRIES + 1))

while (( attempt < max_attempts )); do
  attempt=$((attempt + 1))
  echo "[flash] attempt ${attempt}/${max_attempts}: mode=${MODE}, port=${PORT}, baud=${BAUD}"

  log_file=$(mktemp /tmp/flash_with_progress.XXXXXX.log)
  set +e
  run_flash 2>&1 | tee "$log_file"
  rc=${PIPESTATUS[0]}
  set -e

  if [[ $rc -eq 0 ]]; then
    echo "[flash] success on attempt ${attempt}/${max_attempts}"
    rm -f "$log_file"
    exit 0
  fi

  print_error_summary "$log_file"

  if [[ $AUTO_ATTACH -eq 1 ]] && is_retryable_error "$log_file" && (( attempt < max_attempts )); then
    echo "[auto] 检测到串口/连接异常,执行 usbipd 映射后重试..."
    bash "$ATTACH" --keyword "$KEYWORD" || true
    sleep 2
    rm -f "$log_file"
    continue
  fi

  echo "[flash] 失败且不再重试(rc=$rc)"
  rm -f "$log_file"
  exit $rc
done

```

### scripts/monitor_auto_attach.sh

```bash
#!/usr/bin/env bash
set -euo pipefail

# Auto monitor helper:
# 1) Try idf.py monitor
# 2) If serial open/lock fails, auto-run usbipd_attach_serial.sh and retry once

PORT="${PORT:-/dev/ttyACM0}"
BAUD="${BAUD:-1152000}"
PROJECT_DIR="${PROJECT_DIR:-/path/to/your/project}"
IDF_DIR="${IDF_DIR:-/path/to/esp-idf}"
DISTRO="${DISTRO:-}"
KEYWORD="${KEYWORD:-ESP32}"

usage() {
  cat <<'EOF'
Usage:
  monitor_auto_attach.sh --project <PROJECT_DIR> --idf <IDF_DIR> [--port <PORT>] [--baud <BAUD>] [--distro <DISTRO>] [--keyword <TEXT>]

Example:
  bash monitor_auto_attach.sh \
    --project /path/to/your/project \
    --idf /path/to/esp-idf \
    --port /dev/ttyACM0 \
    --baud 1152000 \
    --keyword ESP32
EOF
}

while [[ $# -gt 0 ]]; do
  case "$1" in
    --project) PROJECT_DIR="$2"; shift 2;;
    --idf) IDF_DIR="$2"; shift 2;;
    --port) PORT="$2"; shift 2;;
    --baud) BAUD="$2"; shift 2;;
    --distro) DISTRO="$2"; shift 2;;
    --keyword) KEYWORD="$2"; shift 2;;
    -h|--help) usage; exit 0;;
    *) echo "Unknown arg: $1" >&2; usage; exit 2;;
  esac
done

if [[ "$PROJECT_DIR" == "/path/to/your/project" || "$IDF_DIR" == "/path/to/esp-idf" ]]; then
  echo "Error: please set --project and --idf" >&2
  usage
  exit 2
fi

BASE_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")"/.. && pwd)
ATTACH_SCRIPT="$BASE_DIR/scripts/usbipd_attach_serial.sh"

run_monitor() {
  cd "$PROJECT_DIR"
  # shellcheck disable=SC1090
  source "$IDF_DIR/export.sh"
  idf.py -p "$PORT" -b "$BAUD" monitor
}

set +e
OUT=$(run_monitor 2>&1)
RC=$?
set -e

if [[ $RC -eq 0 ]]; then
  echo "$OUT"
  exit 0
fi

if echo "$OUT" | grep -Eq "Could not open .*${PORT}|Could not exclusively lock port|Resource temporarily unavailable|No such file or directory: '${PORT}'"; then
  echo "[auto] monitor open failed, trying usbipd auto-attach..."
  cmd=(bash "$ATTACH_SCRIPT" --keyword "$KEYWORD")
  if [[ -n "$DISTRO" ]]; then
    cmd+=(--distro "$DISTRO")
  fi
  "${cmd[@]}"
  sleep 2
  echo "[auto] retry monitor..."
  exec bash -lc "cd '$PROJECT_DIR' && source '$IDF_DIR/export.sh' && idf.py -p '$PORT' -b '$BAUD' monitor"
fi

echo "$OUT"
exit $RC

```

### scripts/run_menuconfig.sh

```bash
#!/usr/bin/env bash
set -euo pipefail

# Run idf.py menuconfig in a dedicated terminal window (TTY required)
# Usage:
#   bash run_menuconfig.sh --project <PROJECT_DIR> --idf <IDF_DIR>

PROJECT_DIR=""
IDF_DIR=""

usage() {
  cat <<'EOF'
Usage:
  run_menuconfig.sh --project <PROJECT_DIR> --idf <IDF_DIR>

Example:
  bash run_menuconfig.sh \
    --project /path/to/your/project \
    --idf /path/to/esp-idf
EOF
}

while [[ $# -gt 0 ]]; do
  case "$1" in
    --project) PROJECT_DIR="$2"; shift 2;;
    --idf) IDF_DIR="$2"; shift 2;;
    -h|--help) usage; exit 0;;
    *) echo "Unknown arg: $1"; usage; exit 2;;
  esac
done

[[ -n "$PROJECT_DIR" && -n "$IDF_DIR" ]] || { usage; exit 2; }

cd "$PROJECT_DIR"
source "$IDF_DIR/export.sh"
exec idf.py menuconfig

```

### scripts/usbipd_attach_serial.sh

```bash
#!/usr/bin/env bash
set -euo pipefail

# Auto map a USB serial device from Windows to WSL2 via usbipd
# Usage:
#   usbipd_attach_serial.sh [--busid <BUSID>] [--distro <DISTRO>] [--keyword <TEXT>] [--dry-run]

BUSID=""
DISTRO=""
KEYWORD=""
DRY_RUN=0

usage() {
  cat <<'EOF'
Usage:
  usbipd_attach_serial.sh [--busid <BUSID>] [--distro <DISTRO>] [--keyword <TEXT>] [--dry-run]

Options:
  --busid <BUSID>    Specify bus id directly, e.g. 2-1
  --distro <DISTRO>  WSL distro name for attach command (optional)
  --keyword <TEXT>   Filter device line by keyword (e.g. ESP32, COM37, CP210x)
  --dry-run          Print command only, do not execute
  -h, --help         Show help

Behavior:
  1) Reads `usbipd list` from Windows PowerShell
  2) Auto-selects first "Connected" serial-like device (prefer STATE=Shared)
  3) Executes: usbipd attach --wsl --busid=<BUSID> [--distribution <DISTRO>]
  4) Prints detected /dev/ttyACM* and /dev/ttyUSB* in WSL
EOF
}

while [[ $# -gt 0 ]]; do
  case "$1" in
    --busid) BUSID="${2:-}"; shift 2;;
    --distro) DISTRO="${2:-}"; shift 2;;
    --keyword) KEYWORD="${2:-}"; shift 2;;
    --dry-run) DRY_RUN=1; shift;;
    -h|--help) usage; exit 0;;
    *) echo "Unknown arg: $1" >&2; usage; exit 2;;
  esac
done

PS_LIST=$(powershell.exe -NoProfile -Command "usbipd list" | tr -d '\r')

echo "=== usbipd list ==="
echo "$PS_LIST"

auto_pick_busid() {
  local lines
  lines=$(echo "$PS_LIST" | awk '
    BEGIN{inConnected=0}
    /^Connected:/ {inConnected=1; next}
    /^Persisted:/ {inConnected=0}
    { if (inConnected) print }
  ')

  # Keep likely serial rows only
  local serial_rows
  serial_rows=$(echo "$lines" | grep -E "USB 串行设备|USB JTAG/serial debug unit|CP210|CH340|FTDI|Serial|UART|ESP32" || true)

  if [[ -n "$KEYWORD" ]]; then
    serial_rows=$(echo "$serial_rows" | grep -i -- "$KEYWORD" || true)
  fi

  # Prefer Shared first
  local preferred
  preferred=$(echo "$serial_rows" | grep -E "Shared\s*$" | head -n1 || true)
  if [[ -z "$preferred" ]]; then
    preferred=$(echo "$serial_rows" | head -n1 || true)
  fi

  if [[ -n "$preferred" ]]; then
    echo "$preferred" | awk '{print $1}'
  fi
}

if [[ -z "$BUSID" ]]; then
  BUSID=$(auto_pick_busid || true)
fi

if [[ -z "$BUSID" ]]; then
  echo "ERROR: No candidate BUSID found. Please specify --busid <BUSID>." >&2
  exit 1
fi

CMD="usbipd attach --wsl --busid=$BUSID"
if [[ -n "$DISTRO" ]]; then
  CMD+=" --distribution $DISTRO"
fi

echo "Selected BUSID: $BUSID"
echo "Attach command: $CMD"

if [[ "$DRY_RUN" == "1" ]]; then
  echo "[dry-run] not executing attach"
  exit 0
fi

set +e
ATTACH_OUT=$(powershell.exe -NoProfile -Command "$CMD" 2>&1)
ATTACH_RC=$?
set -e
echo "$ATTACH_OUT"
if [[ $ATTACH_RC -ne 0 ]]; then
  if echo "$ATTACH_OUT" | grep -qi "already attached"; then
    echo "[info] device already attached, continue."
  else
    echo "[error] attach failed" >&2
    exit $ATTACH_RC
  fi
fi

echo "\n=== WSL serial nodes ==="
found=0
for pat in /dev/ttyACM* /dev/ttyUSB*; do
  for dev in $pat; do
    if [[ -e "$dev" ]]; then
      echo "$dev"
      found=1
    fi
  done
done
if [[ "$found" -eq 0 ]]; then
  echo "No /dev/ttyACM* or /dev/ttyUSB* yet."
fi

echo "Done."

```

esp-idf-helper | SkillHub