# ESP Wi-Fi Service

[English]

## 简介

Wi-Fi Service 把设备联网流程中的凭据存储、配网交互、自动连接、网络选择和质量探测统一到一套 ESP Service 服务接口中。使用它之后，应用不需要分别维护凭据存储、SoftAP/Web 配网、BluFi 配网、断线重连和多 AP 选择逻辑，可以更快构建稳定、便于现场运维的联网产品。

## 功能清单

- Profile 管理：维护多组 Wi-Fi 凭据，支持新增、更新、启用、禁用、删除和清理，配网通道与连接选择共享同一个 profile manager

- 可插拔存储：复用 ESP Config Manager 的 NVS、文件系统、双分区 raw flash 或自定义存储适配层，并支持加密回调保护已保存凭据

- 多通道配网：HTTP SoftAP/Web UI、BluFi，以及应用自定义配网流程，所有通道写入同一份共享 profile

- 自动启动策略：服务启动时根据是否存在已启用配置，自动进入连接选择流程或启动已配置的配网流程

- 智能选择与切换：按用户优先级、信号质量、历史连通性和临时黑名单选择更合适的 AP，并在断线或链路退化后重新评估

- 网络质量探测：支持连通性、延迟和吞吐退化判断，处理“已连接但业务不可用”的场景

- 可选 MCP 工具支持：同时启用 `CONFIG_ESP_MCP_ENABLE` 和 `CONFIG_WIFI_SERVICE_MCP_ENABLE` 后，可通过 ESP Service 的 MCP 服务器远程查询状态、管理 profile 和触发配网/连接

## 技术拆解

### Profile 管理与存储

每个 Wi-Fi 凭据保存为一个 `esp_wifi_service_profile_t`（SSID、密码、优先级、启用标志），由 `esp_wifi_service_profile_mgr_t` 统一管理并持久化。profile manager 的存储层直接复用 ESP Config Manager 的存储适配接口，创建时需要先准备好一个 `esp_config_storage_t` 句柄：

    esp_config_storage_nvs_t nvs_cfg = {
        .nvs_namespace = "wifi_store",
        .key_primary   = "profile_p",
        .key_backup    = "profile_b",
    };
    esp_config_storage_t profile_store = NULL;
    esp_config_storage_init_nvs(&nvs_cfg, &profile_store);

    esp_wifi_service_profile_mgr_cfg_t profile_cfg = {
        .max_profiles = 8,
        .storage      = profile_store,
    };
    esp_wifi_service_profile_mgr_t profile_manager = NULL;
    esp_wifi_service_profile_mgr_init(&profile_cfg, &profile_manager);

同一个 `profile_manager` 句柄要同时传给 Wi-Fi Service 和每个配网通道的配置，这样配网写入的凭据才能被连接选择逻辑立即看到。

### 配网通道

服务启动时如果存在至少一个已启用的 profile，就直接进入连接选择流程；否则启动 `prov_list` 中配置的所有配网通道。内置提供 HTTP SoftAP/Web UI 和 BluFi 两种通道，也可以由应用自行实现自定义配网流程写入同一个 profile manager：

- HTTP 通道启动 SoftAP、DNS captive portal、HTTP server 和默认或自定义 Web UI，凭据通过 `POST /prov/profiles` 提交

- BluFi 通道通过手机侧蓝牙发送网络信息，凭据同样落到共享 profile manager，依赖 `CONFIG_WIFI_SERVICE_PROV_BLUFI_ENABLE` 及底层 BLUFI 协议栈

- 两种通道都可以并行启用，配网结束后统一由 selector 逻辑接管连接

    esp_wifi_service_prov_t *http_agent = NULL;
    esp_wifi_service_prov_http_config_t http_cfg = {
        .name             = "http",
        .port             = 80,
        .profile_manager  = profile_manager,
        .default_priority = 10,
    };
    esp_wifi_service_prov_http_create(&http_cfg, &http_agent);

    esp_wifi_service_config_t cfg = {
        .name            = "wifi_service",
        .profile_manager = profile_manager,
        .prov_list       = &http_agent,
        .prov_num        = 1,
    };
    esp_wifi_service_t *svc = NULL;
    esp_wifi_service_create(&cfg, &svc);
    esp_service_start((esp_service_t *)svc);

### 选择与切换

selector 在多网络环境下自动决策连接哪个 AP：重新评估时先扫描周围 AP，只保留匹配已保存且已启用 profile 的候选项，再按用户优先级、信号质量、最近一次可正常访问的记录，以及临时黑名单排序，而不是简单选择信号最强的 AP。

            flowchart TD
        Init[服务初始化] --> Check{存在已启用配置}
        Check -- 是 --> Selector[启动 selector]
        Check -- 否 --> Prov[启动配网通道]
        Prov --> Save[接收凭据并保存]
        Save --> Selector
        Selector --> Scan[扫描候选 AP 并排序]
        Scan --> Decide{需要切换}
        Decide -- 否 --> Keep[保持当前连接]
        Decide -- 是 --> Switch[切换或故障转移]
        Switch --> Probe[连接后进行质量探测]
        Probe --> Decide2{探测失败或退化}
        Decide2 -- 否 --> Keep
        Decide2 -- 是 --> Scan

没有找到可用候选网络时，selector 按内置退避表（`1000, 5000, 10000, 20000, 30000` ms）重试扫描，可通过 `selector_policy.retry` 覆盖；应用也可以调用 `esp_wifi_service_request_connect()` 跳过扫描和重新评估，直接连接一个已保存的 SSID，适合命令行工具或远程管理场景。

### 网络质量探测

已连接 Wi-Fi 但业务不可用是常见的现场问题：设备已经拿到 IP，但云端接口访问失败、延迟过高或吞吐不足。质量探测覆盖连通性检查（访问指定 URL 判断外部连通性）和延迟/吞吐检查（统计请求耗时和实际吞吐），在连续失败或持续退化后才触发处理，避免因为一次偶发抖动就切换网络；确认退化后，当前 BSSID 会被临时加入黑名单，交由 selector 重新选择候选网络。

## 应用示例

- `examples` 目录下的示例演示 NVS profile 存储、HTTP 配网、自定义 selector 策略的最小接入流程，完整代码可参考组件仓库。

## FAQ

Q1：max_connect_retry 设为 0 会一直重试吗？

会。`0` 表示保持原有行为、持续重试；设为非 0 值后，连续连接失败达到该次数会停止自动重新评估，直到调用 `esp_wifi_service_request_connect()` 或 `esp_wifi_service_request_reeval()` 才会重新计数。

## API 参考

### Header File

- components/esp_wifi_service/include/esp_wifi_service.h

### Functions

esp_err_t esp_wifi_service_create(const esp_wifi_service_config_t *cfg, esp_wifi_service_t **out_service)

  Create Wi-Fi service instance.

  备注

  The caller is responsible for creating the profile manager before calling this function and destroying it after `esp_wifi_service_destroy` returns. The same handle should be passed to each provisioning config so that all components share one profile store.

  参数:

    - cfg – [in] Required configuration; `profile_manager` must be a valid, initialised handle

    - out_service – [out] Created service handle

  返回:

    - ESP_OK On success

    - ESP_ERR_INVALID_ARG Invalid argument

    - ESP_ERR_NO_MEM Out of memory

esp_err_t esp_wifi_service_destroy(esp_wifi_service_t *service)

  Destroy the service.

  参数:

    service – [in] Service handle

  返回:

    - ESP_OK On success

    - ESP_ERR_INVALID_ARG Invalid argument

esp_err_t esp_wifi_service_get_profile_manager(esp_wifi_service_t *service, esp_wifi_service_profile_mgr_t *manager_out)

  Get profile manager handle owned by service.

  参数:

    - service – [in] Service handle

    - manager_out – [out] Profile manager handle owned by service

  返回:

    - ESP_OK On success

    - ESP_ERR_INVALID_ARG NULL argument

esp_err_t esp_wifi_service_get_scan_handle(esp_wifi_service_t *service, esp_wifi_service_scan_handle_t *scan_handle_out)

  Get the shared scan handle owned by service.

  备注

  Selector, provisioning transports, and application code should share this agent so scan requests can be coalesced and receive the same driver scan records.

  参数:

    - service – [in] Service handle

    - scan_handle_out – [out] Scan handle owned by service

  返回:

    - ESP_OK On success

    - ESP_ERR_INVALID_ARG NULL argument

esp_err_t esp_wifi_service_start_provisioning(esp_wifi_service_t *service)

  Start all provisioning instances.

  参数:

    service – [in] Service handle

  返回:

    - ESP_OK On success

    - ESP_ERR_INVALID_ARG Invalid argument

    - Others Provisioning-specific error

esp_err_t esp_wifi_service_stop_provisioning(esp_wifi_service_t *service)

  Stop all started provisioning instances.

  参数:

    service – [in] Service handle

  返回:

    - ESP_OK On success

    - ESP_ERR_INVALID_ARG Invalid argument

    - Others Provisioning-specific error

esp_err_t esp_wifi_service_is_provisioning_running(esp_wifi_service_t *service, bool *running_out)

  Query provisioning running state.

  参数:

    - service – [in] Service handle

    - running_out – [out] True if any provisioning instance is running

  返回:

    - ESP_OK On success

    - ESP_ERR_INVALID_ARG Invalid argument

esp_err_t esp_wifi_service_request_connect(esp_wifi_service_t *service, char *ssid, char *password, uint8_t prio, uint32_t wait_sec)

  Save a Wi-Fi profile and request connection to the specified SSID.

  备注

  This API stores or updates the profile as enabled, stops provisioning, starts the selector if needed, and asks the selector to connect to the specified saved profile directly without running a scan/re-evaluation cycle.

  参数:

    - service – [in] Service handle

    - ssid – [in] NUL-terminated SSID

    - password – [in] NUL-terminated password; NULL is treated as an empty password

    - prio – [in] Profile priority, 0 to ::ESP_WIFI_SERVICE_PROFILE_PRIORITY_MAX

    - wait_sec – [in] Seconds to wait for STA got IP; 0 returns after the request is accepted

  返回:

    - ESP_OK On success

    - ESP_ERR_INVALID_ARG Invalid argument

    - ESP_ERR_TIMEOUT `wait_sec` is non-zero and STA did not get IP in time

    - Others From profile manager or selector

esp_err_t esp_wifi_service_request_reeval(esp_wifi_service_t *service)

  Request one Wi-Fi selector re-evaluation cycle.

  备注

  This API is asynchronous. It returns after the request is accepted, not after scan results are evaluated or a connection decision is completed. If the selector is already running and no re-evaluation is in flight, this API attempts to start a Wi-Fi scan before returning. If a scan/re-evaluation is already in flight, the request is marked pending and runs after the current cycle finishes. If the selector is not running but saved profiles exist, the service starts the selector and schedules evaluation.

  参数:

    service – [in] Service handle

  返回:

    - ESP_OK On success or when the request is throttled

    - ESP_ERR_INVALID_ARG service is NULL

    - ESP_ERR_NOT_FOUND No Wi-Fi profile is available to evaluate

    - Others Selector-specific error

### Structures

struct esp_wifi_service_config_t

  Wi-Fi service configuration.

  Public Members

  const char *name

    Service instance name for service manager

  esp_wifi_service_profile_mgr_t profile_manager

    Required profile manager handle; created and owned by the caller

  esp_wifi_service_prov_t *prov_list

    Provisioning handle array owned by application

  size_t prov_num

    Number of entries in prov_list

  const esp_wifi_service_selector_cfg_t *selector_policy

    Selector policy; NULL uses selector built-in defaults

### Type Definitions

typedef struct esp_wifi_service esp_wifi_service_t

  Opaque Wi-Fi service handle.

### Enumerations

enum esp_wifi_service_event_t

  Wi-Fi service event identifiers.

  Values:

  enumerator ESP_WIFI_SERVICE_EVENT_CONNECTED

    Wi-Fi service reports connected

  enumerator ESP_WIFI_SERVICE_EVENT_DISCONNECTED

    Wi-Fi service reports disconnected

  enumerator ESP_WIFI_SERVICE_EVENT_PROV_STARTED

    Provisioning transport started

  enumerator ESP_WIFI_SERVICE_EVENT_PROV_STOPPED

    Provisioning transport stopped

  enumerator ESP_WIFI_SERVICE_EVENT_PROV_PEER_CONNECTED

    Provisioning peer connected

  enumerator ESP_WIFI_SERVICE_EVENT_PROV_PEER_DISCONNECTED

    Provisioning peer disconnected

  enumerator ESP_WIFI_SERVICE_EVENT_PROV_CREDENTIAL_RECEIVED

    Provisioning credential received

  enumerator ESP_WIFI_SERVICE_EVENT_PROV_CUSTOM_DATA_RECEIVED

    Provisioning custom data received

  enumerator ESP_WIFI_SERVICE_EVENT_PROV_ERROR

    Provisioning runtime error

  enumerator ESP_WIFI_SERVICE_EVENT_STA_CONFIG

    STA config can be adjusted before connect

  enumerator ESP_WIFI_SERVICE_EVENT_STA_GOT_IP

    Wi-Fi service reports station got IP

  enumerator ESP_WIFI_SERVICE_EVENT_STA_LOST_IP

    Wi-Fi service reports station lost IP

  enumerator ESP_WIFI_SERVICE_EVENT_SELECTOR_CANDIDATE

    Selector candidate chosen from scan result

  enumerator ESP_WIFI_SERVICE_EVENT_SELECTOR_SWITCHING

    Selector decided to switch/connect

  enumerator ESP_WIFI_SERVICE_EVENT_SELECTOR_SWITCH_FAILED

    Selector switch/connect failed

  enumerator ESP_WIFI_SERVICE_EVENT_SELECTOR_BLACKLISTED

    Selector blacklisted one BSSID

  enumerator ESP_WIFI_SERVICE_EVENT_SELECTOR_ACCESS_FAILED

    Selector probe access check failed

  enumerator ESP_WIFI_SERVICE_EVENT_SELECTOR_LATENCY_DEGRADED

    Selector latency check degraded

  enumerator ESP_WIFI_SERVICE_EVENT_SELECTOR_THROUGHPUT_DEGRADED

    Selector throughput check degraded

  enumerator ESP_WIFI_SERVICE_EVENT_SELECTOR_RSSI_LOW

    Selector RSSI check is below threshold

### Header File

- components/esp_wifi_service/include/esp_wifi_service_profile_mgr.h

### Functions

esp_err_t esp_wifi_service_profile_mgr_init(const esp_wifi_service_profile_mgr_cfg_t *cfg, esp_wifi_service_profile_mgr_t *out_handle)

  Create profile manager and esp_config_manager handle.

  参数:

    - cfg – [in] Configuration; `cfg->storage` from ::esp_config_storage_init_nvs (or related) and must outlive the profile manager

    - out_handle – [out] Profile manager handle

  返回:

    - ESP_OK On success

    - ESP_ERR_INVALID_ARG Invalid configuration; `max_profiles` must be greater than 0

    - ESP_ERR_NO_MEM Allocation failure

    - Others From esp_config_manager

void esp_wifi_service_profile_mgr_deinit(esp_wifi_service_profile_mgr_t handle)

  Destroy wifi_profile instance.

  参数:

    handle – [in] Profile manager handle

esp_err_t esp_wifi_service_profile_mgr_count(esp_wifi_service_profile_mgr_t handle, uint8_t *count_out)

  Get number of profiles.

  参数:

    - handle – [in] Profile manager handle

    - count_out – [out] Number of profiles

  返回:

    - ESP_OK On success

    - ESP_ERR_INVALID_ARG NULL argument

esp_err_t esp_wifi_service_profile_mgr_foreach(esp_wifi_service_profile_mgr_t handle, bool (*callback)(const esp_wifi_service_profile_t *profile, void *user_ctx), void *user_ctx)

  Iterate all stored profiles in insertion order.

  备注

  The callback is invoked under the internal lock; it must not call any profile manager API that acquires the same lock.

  参数:

    - handle – [in] Profile manager handle

    - callback – [in] Called for each profile; return `false` to stop early

    - user_ctx – [in] Forwarded to every callback invocation

  返回:

    - ESP_OK On success (including early stop by callback)

    - ESP_ERR_INVALID_ARG NULL argument

esp_err_t esp_wifi_service_profile_mgr_get(esp_wifi_service_profile_mgr_t handle, const char *ssid, esp_wifi_service_profile_t *profile_out)

  Get profile by SSID.

  参数:

    - handle – [in] Profile manager handle

    - ssid – [in] SSID to match

    - profile_out – [out] Profile content

  返回:

    - ESP_OK On success

    - ESP_ERR_INVALID_ARG Invalid SSID or NULL handle

    - ESP_ERR_NOT_FOUND SSID not in store

    - Others From load/save

esp_err_t esp_wifi_service_profile_mgr_add(esp_wifi_service_profile_mgr_t handle, esp_wifi_service_profile_t *profile)

  Append or replace by SSID: add credentials, or update if SSID exists.

  参数:

    - handle – [in] Profile manager handle

    - profile – [in] Profile to add or update; caller retains ownership

  返回:

    - ESP_OK On success

    - ESP_ERR_INVALID_ARG Invalid SSID/password/priority or NULL argument

    - ESP_ERR_NO_MEM Profile table full on add

    - Others From load/save

esp_err_t esp_wifi_service_profile_mgr_set_enabled(esp_wifi_service_profile_mgr_t handle, const char *ssid, bool enabled)

  Set profile enabled flag by SSID.

  参数:

    - handle – [in] Profile manager handle

    - ssid – [in] NUL-terminated SSID

    - enabled – [in] True to enable

  返回:

    - ESP_OK On success

    - ESP_ERR_INVALID_ARG NULL argument

    - ESP_ERR_NOT_FOUND SSID not in store

    - Others From load/save

esp_err_t esp_wifi_service_profile_mgr_set_last_working(esp_wifi_service_profile_mgr_t handle, const char *ssid)

  Record the last successfully connected profile by SSID.

  参数:

    - handle – [in] Profile manager handle

    - ssid – [in] NUL-terminated SSID; pass NULL or empty string to clear

  返回:

    - ESP_OK On success

    - ESP_ERR_INVALID_ARG NULL handle

    - ESP_ERR_NOT_FOUND Non-empty SSID not in store

    - Others From load/save

esp_err_t esp_wifi_service_profile_mgr_get_last_working(esp_wifi_service_profile_mgr_t handle, char *ssid, size_t ssid_len)

  Get the SSID of the last successfully connected profile.

  参数:

    - handle – [in] Profile manager handle

    - ssid – [out] Buffer to receive NUL-terminated SSID

    - ssid_len – [in] Size of ssid; must be at least ::ESP_WIFI_SERVICE_PROFILE_SSID_MAX_LEN + 1

  返回:

    - ESP_OK On success

    - ESP_ERR_INVALID_ARG NULL argument or ssid_len too small

    - ESP_ERR_NOT_FOUND No last-working profile recorded

esp_err_t esp_wifi_service_profile_mgr_delete(esp_wifi_service_profile_mgr_t handle, const char *ssid)

  Delete profile by SSID.

  参数:

    - handle – [in] Profile manager handle

    - ssid – [in] SSID to remove

  返回:

    - ESP_OK On success

    - ESP_ERR_INVALID_ARG NULL argument

    - ESP_ERR_NOT_FOUND SSID not found

    - Others From load/save

esp_err_t esp_wifi_service_profile_mgr_clear_all(esp_wifi_service_profile_mgr_t handle)

  Remove all profiles and reset store to defaults.

  参数:

    handle – [in] Profile manager handle

  返回:

    - ESP_OK On success

    - ESP_ERR_INVALID_ARG NULL handle

    - Others From load/save

### Structures

struct esp_wifi_service_profile_t

  One saved Wi-Fi profile (filled by provisioning or application)

  Public Members

  uint8_t flags

    ::ESP_WIFI_SERVICE_PROFILE_FLAG_*

  uint8_t priority

    Higher value = stronger user preference

  char ssid[ESP_WIFI_SERVICE_PROFILE_SSID_MAX_LEN + 1]

    NUL-terminated SSID

  char password[ESP_WIFI_SERVICE_PROFILE_PASS_MAX_LEN + 1]

    NUL-terminated password

struct esp_wifi_service_profile_mgr_cfg_t

  Initialization parameters for ::esp_wifi_service_profile_mgr_init.

  Public Members

  uint8_t max_profiles

    Maximum number of profiles to keep; must be greater than 0

  esp_config_storage_t storage

    From esp_config_storage_init_*; must outlive profile manager

  const esp_config_crypto_ops_t *crypto

    NULL: plaintext record except private metadata

  size_t crypto_extra_size

    Extra bytes crypto may add to the profile store

### Macros

ESP_WIFI_SERVICE_PROFILE_PRIORITY_MAX

ESP_WIFI_SERVICE_PROFILE_SSID_MAX_LEN

ESP_WIFI_SERVICE_PROFILE_PASS_MAX_LEN

ESP_WIFI_SERVICE_PROFILE_FLAG_ENABLED

### Type Definitions

typedef void *esp_wifi_service_profile_mgr_t

  Opaque handle to wifi_profile instance.

提供有关此文档的反馈