ESP32 · Agent Skills

Firmware that compiles.
Then it bricks the board.

An agent writes pinMode(6, OUTPUT) and the build is green. GPIO6 is wired to the SPI flash — the module stops booting. Twenty-six skills and four checkers catch that before the flash.

skills/esp32-pin-safety/SKILL.md
---
name: esp32-pin-safety
description: Use when choosing or debugging
  ESP32 GPIO assignments in firmware.
---

## Check before you assign a pin

- GPIO6-11 are the SPI flash.
  Driving one bricks the module.
- GPIO34-39 are input-only.
  There is no output driver to enable.
- GPIO12 high at reset selects
  1.8 V flash: it boots once, then not.
- ADC2 is unusable while Wi-Fi is on.

# then: python tools/pincheck.py --soc esp32
26skills
4checkers
150tests
1board verified
Watch it catch one

The agent picks GPIO6. The checker says no.

Same command, two runs: the first exits 1 on a pin wired to the flash, the second exits 0 after the pin is moved. The text is the real output of those two runs, replayed line by line; the terminal frame around it is drawn, not a screen recording.

Terminal animation: pincheck rejects GPIO6, then accepts GPIO25

python tools/pincheck.py --soc esp32 --assign "12=relay1,34=button,6=status_led" → 1 error, exit 1. Swap 6 for 25 → exit 0.

ESP32 Agent Skills poster: 13 skills and 4 tools
The problem

Compiling is not the same as working

Every failure below passes the compiler. None of them throws an error you can read. They show up as a board that will not boot, a battery that dies in three days, or a device that resets at 2am.

flash pins

The board never boots again

GPIO6–11 are the SPI flash. Driving one is not a warning — it is a dead module until you reflash it over serial.

strapping

It works, until the next power cycle

GPIO12 high at reset selects 1.8 V flash. The board boots on your desk and fails in the enclosure.

power

Battery flat in three days

A 12 ms wake every second looks cheap until you multiply by 60 mA. Nobody does that arithmetic by hand.

What is inside

Twenty-six skills, one per failure domain

Each skill is a directory with a short SKILL.md, a license, and a reference/ folder holding the long tables — the layout the official Anthropic skill repository uses, so an agent loads only what it needs.

esp32-pin-safetyFlash pins, input-only pins, strapping pins, ADC2 with Wi-Fi.
esp32-power-and-bootBrownout, boot strap states, deep-sleep leakage — the "random reset" triage.
esp32-i2c-busMissing pull-ups, address conflicts, a bus that hangs mid-transfer.
esp32-partitions-and-otaMisaligned app slots, overlapping regions, an OTA that silently rolls back.
esp32-freertosWatchdog resets, stack overflow, ISR rules, shared data without a lock.
esp32-modbus-rs485DE-pin timing, the 3.5 character gap, the CRC, the 40001 off-by-one.
esp32-addressable-ledsPower sizing, the 3.3 V data problem, GRB vs RGBW, refresh budget, flicker under Wi-Fi.
esp32-gaming-rgbThe OpenRGB SDK wire format, SignalRGB's real options, the latency Wi-Fi cannot do.
esp32-debuggingReading a panic dump, the exception decoder filter, telling a crash from a watchdog.
esp32-wifi-reliabilityReconnect storms, power save as a latency setting, AP+STA, what RSSI means.
esp32-mqtt-and-cloudThe 256 byte buffer that truncates payloads, keepalive, last will, TLS memory.
esp32-adc-and-sensorsADC1 vs ADC2, attenuation, calibration, source impedance, averaging.
esp32-pwm-and-timingLEDC's frequency/resolution trade-off, duty as a count, the core 3.x API change.
esp32-ble-gattConnection parameters and the timeout inequality, MTU and the 20-byte default, notify vs indicate, and the bond that lives on the phone.
esp32-memory-and-heapInternal SRAM vs PSRAM, MALLOC_CAP_DMA, the largest free block as the number that matters, stack sizing in bytes vs words.
esp32-storage-and-filesystemNVS vs LittleFS vs FAT, commit and rename as the only atomic operations, wear, and mount failure as a decision.
esp32-spi-and-devicesBus then device, one initialisation per host, chip select ownership, and why 40 MHz works on the bench and not in the box.
esp32-can-twaiWhich chips have TWAI at all, the transceiver that is not optional, termination at both ends, and bus-off recovery.
esp32-audio-i2sMaster/slave roles, MCLK, DMA buffer counts, underruns as clicks, and why the pitch is wrong.
esp32-camera-pipelineOV2640 limits, PSRAM framebuffers, init once with no de-init, and the camera plus Wi-Fi brownout.
esp32-http-serverWildcard matching off by default, handler limits, never blocking the server task, and chunked responses.
esp32-time-and-rtcSNTP is asynchronous, TZ strings with DST rules, RTC drift across sleep, and certificates that depend on the clock.
esp32-security-and-provisioningSecure boot, flash encryption and NVS keys in the order that does not brick the board, and what a factory reset really erases.
esp32-testing-and-ciHost tests, on-target Unity, size budgets and a build matrix that covers the chips the README claims.
esp32-hardware-bringupMeasure the rail before the firmware: current, strapping levels, the blink-first order, and reading a boot loop.
esp32-field-gotchasA hundred and sixty failures from Stack Overflow — brownout, dead I2C, garbled UART, OTA rollback, ULP wake, cameras, ISRs, secure boot, JTAG — indexed by symptom, each linked to its thread.
Checkers

Four commands, standard library only

Each one exits non-zero on a real problem, so they drop straight into CI or a pre-flash hook. No dependencies, no virtualenv, no network.

pincheck.py

Is this GPIO allowed to do this job?

Reads a pin map or an --assign list, checks it against the constraints of the chosen SoC.

partition_check.py

Does the table fit the flash?

64 KB app alignment, equal OTA slots, overlap, overflow — the four things that break an OTA.

power_budget.py

How long does this battery last?

Turns a duty cycle into days, then names the dominant term so you know which number to attack.

panic_decode.py

Which addr2line command do I run?

Pulls the reason, the core and the frames out of a Guru Meditation dump, in the order the CPU printed them.

$ python tools/power_budget.py --battery 3000 \
    --wakes-per-hour 12 --wake-ms 2000

  duty cycle      0.67%
  average current 0.677 mA
  battery life    184.7 days (4433.9 hours)

  where the charge goes
    idle      0.467 mA   69.0%
    tx        0.200 mA   29.6%
    sleep     0.010 mA    1.5%

  dominant term: idle (69.0%), fix that one first
$ python tools/partition_check.py \
    --file examples/partitions-broken.csv --flash 4MB --ota

  ERROR app0 is an app partition at 68KB,
        app partitions must be aligned to 0x10000
  ERROR app0 (68KB-1604KB) overlaps app1
  ERROR spiffs ends at 4608KB, past the end of 4MB
  ERROR the ota app slots do not have the same size
Evidence

Verified on a board, not on a datasheet

The pin database was checked against a real module by flashing a sketch that asks the chip what it is. The boot log and the reproduction steps are in docs/hardware.md.

● flashed · booted · serial read back

ESP32-D0WD-V3 · rev 301

2 cores · 240 MHz · 4 MB flash · ESP-IDF v5.5.5 · 331,072 bytes free heap at boot

That output is the check itself: chip tells you which profile in data/esp32-pins.json applies, flash is the value to pass to partition_check.py, and the free heap is the budget a TLS connection has to fit inside.

=== board facts ===
chip        : ESP32-D0WD-V3 rev 301, 2 core(s)
cpu         : 240 MHz
flash       : 4 MB (4194304 bytes)
sdk         : v5.5.5
free heap   : 331072 bytes
=== end ===
Install

Copy the folder, or clone the repo

The skills are plain Markdown. The checkers are single files that run on the Python standard library, so there is nothing to install and nothing to break.

# as an agent skill
git clone https://github.com/Esmail-sarhadi/esp32-agent-skills
cp -r esp32-agent-skills/skills/esp32-pin-safety \
      ~/.claude/skills/
# run the checkers anywhere
cd esp32-agent-skills
python tools/pincheck.py --soc esp32 --file examples/pinmap.txt
python -m unittest discover -s tests   # 32 pass