一键导入
wifi-sta-setup
Apply or audit the canonical ESP-IDF WiFi STA configuration for a monorepo project
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Apply or audit the canonical ESP-IDF WiFi STA configuration for a monorepo project
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Debug static, hiss, or noise on embedded I2S audio (ESP32 + MAX98357A / PCM DACs), especially streamed PCM. Measure the signal at each pipeline stage instead of guessing at the amp. Use when audio plays but sounds wrong.
Generate a printable Typst build guide (source + PDF) for an ESP32/MCU project by analyzing its docs, source, and schematic
Register a project in the monorepo — justfile mod, CI matrix, standard file inventory
Standardize credential handling for an ESP32 project (credentials.h.example, .gitignore, pre-commit protection)
Set up ESP-IDF development environment, create new projects, and verify configuration
Generate a CLAUDE.md for a project with build commands, hardware constraints, architecture, and development tasks
| name | wifi-sta-setup |
| description | Apply or audit the canonical ESP-IDF WiFi STA configuration for a monorepo project |
| argument-hint | audit|apply <project-path> |
| user-invocable | true |
| allowed-tools | Read, Write, Edit, Grep, Glob |
Bring an ESP-IDF project's WiFi STA setup into line with the monorepo canonical pattern, or audit it without modifying anything.
Invocation:
audit <project-path> — read-only drift report against canonical.apply <project-path> — migrate source + sdkconfig to canonical.<project-path> is relative to repo root, typically
packages/<domain>/<project-name>.
The canonical source of truth is
packages/robocar/unified/main/wifi_manager.c (identical to
packages/robocar/camera/main/wifi_manager.c). That project
runs reliably on XIAO ESP32-S3 Sense in the same environment as every other
WiFi project in this monorepo, so its configuration is the reference.
Drop into main/wifi_manager.{c,h} for new projects, or inline into main.c
for small projects where a separate module isn't justified. The function
boundary doesn't matter — these settings do:
// ---- event handler (reason-code logging is the key bit) ----
static void event_handler(void *arg, esp_event_base_t event_base, int32_t event_id,
void *event_data)
{
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
esp_wifi_connect();
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
wifi_event_sta_disconnected_t *disconnected = (wifi_event_sta_disconnected_t *)event_data;
ESP_LOGW(TAG, "WiFi disconnected. Reason: %d (%s)", disconnected->reason,
disconnected->reason == WIFI_REASON_NO_AP_FOUND ? "AP not found"
: disconnected->reason == WIFI_REASON_AUTH_FAIL ? "Auth failed"
: disconnected->reason == WIFI_REASON_ASSOC_FAIL ? "Assoc failed"
: disconnected->reason == WIFI_REASON_HANDSHAKE_TIMEOUT ? "Handshake timeout"
: "Other");
if (s_retry_num < WIFI_MAXIMUM_RETRY) {
esp_wifi_connect();
s_retry_num++;
ESP_LOGI(TAG, "Retry %d/%d to connect to the AP", s_retry_num, WIFI_MAXIMUM_RETRY);
} else {
xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
ESP_LOGE(TAG, "Failed to connect to AP after %d retries", WIFI_MAXIMUM_RETRY);
}
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
ESP_LOGI(TAG, "Got IP:" IPSTR, IP2STR(&event->ip_info.ip));
s_retry_num = 0;
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
}
}
// ---- init ----
esp_netif_init();
esp_event_loop_create_default();
esp_netif_create_default_wifi_sta();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
esp_wifi_init(&cfg);
// Regulatory: allow channels 1–13 (default US policy clips to 1–11)
wifi_country_t country = {
.cc = "FI", .schan = 1, .nchan = 13, .policy = WIFI_COUNTRY_POLICY_AUTO};
esp_wifi_set_country(&country);
esp_wifi_set_ps(WIFI_PS_NONE);
esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL, &any_id);
esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL, &got_ip);
// ---- connect ----
wifi_config_t wifi_config = {
.sta = {
.threshold.authmode = WIFI_AUTH_WPA_WPA2_PSK, // mixed-mode tolerant
.pmf_cfg = {.capable = true, .required = false}, // 4-way handshake tolerance
.scan_method = WIFI_FAST_SCAN,
.sort_method = WIFI_CONNECT_AP_BY_SIGNAL,
},
};
strncpy((char *)wifi_config.sta.ssid, ssid, sizeof(wifi_config.sta.ssid) - 1);
strncpy((char *)wifi_config.sta.password, password, sizeof(wifi_config.sta.password) - 1);
esp_wifi_set_mode(WIFI_MODE_STA);
esp_wifi_set_config(WIFI_IF_STA, &wifi_config);
esp_wifi_start();
Retry cap: #define WIFI_MAXIMUM_RETRY 5. Higher values just lengthen the
failure path — they don't recover from a wrong SSID or password.
sdkconfig.defaults WiFi blockCONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM=10
CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM=32
CONFIG_ESP_WIFI_TX_BUFFER_TYPE=1
CONFIG_ESP_WIFI_DYNAMIC_TX_BUFFER_NUM=32
CONFIG_ESP_WIFI_AMPDU_TX_ENABLED=y
CONFIG_ESP_WIFI_AMPDU_RX_ENABLED=y
CONFIG_ESP_WIFI_AMSDU_TX_ENABLED=y
CONFIG_ESP_WIFI_NVS_ENABLED=y
Anti-pattern to remove: CONFIG_ESP_PHY_MAX_WIFI_TX_POWER=<N> (below
default). Reducing TX power only loses link margin. It's sometimes justified
as a brownout mitigation, but on a board with CONFIG_ESP_BROWNOUT_DET=n the
brownout justification is moot — if brownout is disabled, you aren't going to
get a brownout-triggered reset regardless of TX power. Only keep this setting
if CONFIG_ESP_BROWNOUT_DET=y AND you've measured USB supply dips correlated
with TX bursts.
CMakeLists.txt REQUIRESREQUIRES esp_wifi nvs_flash esp_netif esp_event
credentials.h must define:
#define WIFI_SSID "..."
#define WIFI_PASSWORD "..."
See sibling skill credential-setup for standardized credential handling
(.example template, gitignore, pre-commit protection).
When the canonical event handler is in place and connection still fails, the
log line WiFi disconnected. Reason: <N> (<name>) tells you what to fix:
| Reason | Likely cause |
|---|---|
WIFI_REASON_NO_AP_FOUND | SSID wrong, out of range, 5 GHz-only AP (ESP32/S3 is 2.4 GHz), country code blocks channel (fixed by canonical FI/1-13) |
WIFI_REASON_AUTH_FAIL | Password wrong, or threshold.authmode stricter than AP advertises (canonical WPA_WPA2_PSK accepts both) |
WIFI_REASON_ASSOC_FAIL | AP rejected (MAC filter, client cap) |
WIFI_REASON_HANDSHAKE_TIMEOUT / WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT | PMF mismatch, TX power too low, brownout during handshake |
WIFI_REASON_BEACON_TIMEOUT | Link lost after connect (roaming, interference) |
For hidden SSIDs, use .scan_method = WIFI_ALL_CHANNEL_SCAN and
.bssid_set = 0 (known override; document in the project).
audit <project-path>)main/main.c and main/wifi_manager.c if present.sdkconfig.defaults.main/credentials.h.example (for contract check).file:line
references.Drift check (compare against canonical values):
| Check | Canonical |
|---|---|
threshold.authmode | WIFI_AUTH_WPA_WPA2_PSK |
pmf_cfg | {.capable = true, .required = false} |
scan_method | WIFI_FAST_SCAN (unless hidden SSID) |
sort_method | WIFI_CONNECT_AP_BY_SIGNAL |
esp_wifi_set_country() | Called with FI/1/13/POLICY_AUTO |
esp_wifi_set_ps(WIFI_PS_NONE) | Called |
| Disconnect reason logged | Yes, with name mapping |
| Retry cap | WIFI_MAXIMUM_RETRY = 5 (higher is drift) |
CONFIG_ESP_PHY_MAX_WIFI_TX_POWER | Not set (unless justified by brownout+measurement) |
CONFIG_ESP_WIFI_AMPDU_* / AMSDU_TX_ENABLED | y (ESP-IDF default; materially improves STA throughput) |
| SoftAP-on-S3 exception | If the project runs WIFI_MODE_AP on ESP32-S3, keep AMPDU off — see espressif/esp-idf#13508 (clients can't see the AP with AMPDU on) |
CMakeLists.txt REQUIRES | Includes esp_wifi nvs_flash esp_netif esp_event |
apply <project-path>)wifi_manager.{c,h} or inline in main.c?
main.c (one handler + one
init function), keep it inline and edit in place.main.c OR the project is large enough
that extracting clarifies it, create main/wifi_manager.{c,h} with the
canonical shape (see robocar-unified/main/wifi_manager.{c,h} for exact
file contents). Update main/CMakeLists.txt SRCS accordingly and
replace inline WiFi code in main.c with wifi_init() +
wifi_connect(WIFI_SSID, WIFI_PASSWORD) calls.threshold.authmode → WIFI_AUTH_WPA_WPA2_PSKesp_wifi_set_country() call right after esp_wifi_init() (and
before the config path — country affects scan).scan_method → WIFI_FAST_SCAN (unless hidden SSID — verify with user).STA_DISCONNECTED branch.esp_wifi_set_ps(WIFI_PS_NONE) is present.sdkconfig.defaults:
CONFIG_ESP_PHY_MAX_WIFI_TX_POWER=N unless brownout is enabled
and the user confirms a measurement.main/CMakeLists.txt REQUIRES includes the four components.sdkconfig (ESP-IDF preserves existing values in
sdkconfig and silently ignores new defaults — see
.claude/rules/esp-idf-sdkconfig.md)..claude/skills/sdkconfig-audit — further sdkconfig drift review.claude/skills/credential-setup — credentials.h contract.claude/rules/mdns-hostname.md — every STA project should also
advertise an mDNS hostnameBullet list of changes per file (use file:line form), with the delta
between before/after for each. End with a "Next steps" block:
- Build: just <project>::clean && just <project>::build
- Flash + monitor: just <project>::flash-monitor
- On failure: the log will now show `WiFi disconnected. Reason: <N> (<name>)` —
cross-reference the diagnosis table in this skill.