# ESP Wi-Fi Service

[中文]

## Introduction

Wi-Fi Service unifies credential storage, provisioning interaction, automatic connection, network selection, and quality probing in a device’s networking workflow into a single set of ESP Service service interfaces. With it, applications no longer need to separately maintain credential storage, SoftAP/Web provisioning, BluFi provisioning, reconnection, and multi-AP selection logic, making it possible to build stable, field-serviceable networked products more quickly.

## Feature List

- Profile management: maintains multiple sets of Wi-Fi credentials, supporting addition, update, enabling, disabling, deletion, and cleanup; provisioning channels and connection selection share the same profile manager

- Pluggable storage: reuses the NVS, file system, dual-partition raw flash, or custom storage adapter layers from ESP Config Manager, and supports encryption callbacks to protect saved credentials

- Multi-channel provisioning: HTTP SoftAP/Web UI, BluFi, and application-defined custom provisioning flows, all of which write to the same shared profile

- Automatic startup strategy: when the service starts, it automatically enters the connection selection flow or starts the configured provisioning flow, depending on whether an enabled profile exists

- Intelligent selection and switching: selects a more suitable AP based on user priority, signal quality, historical connectivity, and a temporary blacklist, and re-evaluates after disconnection or link degradation

- Network quality probing: supports connectivity, latency, and throughput degradation detection to handle the “connected but the service is unusable” scenario

- Optional MCP tool support: once both `CONFIG_ESP_MCP_ENABLE` and `CONFIG_WIFI_SERVICE_MCP_ENABLE` are enabled, the MCP server from ESP Service can be used to remotely query status, manage profiles, and trigger provisioning/connection

## Technical Deep Dive

### Profile Management and Storage

Each Wi-Fi credential is stored as an `esp_wifi_service_profile_t`(SSID, password, priority, enable flag), which is centrally managed and persisted by `esp_wifi_service_profile_mgr_t`. The storage layer of the profile manager directly reuses the storage adapter interface from ESP Config Manager; an `esp_config_storage_t` handle must be prepared before creation:

    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);

The same `profile_manager` handle must be passed to both the Wi-Fi Service and the configuration of each provisioning channel, so that credentials written by provisioning are immediately visible to the connection selection logic.

### Provisioning Channels

If at least one enabled profile exists when the service starts, it goes directly into the connection selection flow; otherwise, it starts all the provisioning channels configured in `prov_list`. HTTP SoftAP/Web UI and BluFi are provided as built-in channels, and applications can also implement a custom provisioning flow that writes to the same profile manager:

- The HTTP channel starts a SoftAP, DNS captive portal, HTTP server, and a default or custom Web UI; credentials are submitted via `POST /prov/profiles`

- The BluFi channel sends network information via Bluetooth from the phone side; credentials also land in the shared profile manager, and this channel depends on `CONFIG_WIFI_SERVICE_PROV_BLUFI_ENABLE` and the underlying BluFi protocol stack

- Both channels can be enabled in parallel; once provisioning finishes, the selector logic takes over the connection

    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);

### Selection and Switching

In multi-network environments, the selector automatically decides which AP to connect to: during re-evaluation, it first scans the surrounding APs, keeps only the candidates that match a saved and enabled profile, and then ranks them by user priority, signal quality, the most recent record of successful access, and a temporary blacklist, rather than simply choosing the AP with the strongest signal.

            flowchart TD
        Init[Service initialization] --> Check{Enabled profile exists}
        Check -- Yes --> Selector[Start selector]
        Check -- No --> Prov[Start provisioning channel]
        Prov --> Save[Receive and save credentials]
        Save --> Selector
        Selector --> Scan[Scan and rank candidate APs]
        Scan --> Decide{Switch needed}
        Decide -- No --> Keep[Keep current connection]
        Decide -- Yes --> Switch[Switch or fail over]
        Switch --> Probe[Probe quality after connecting]
        Probe --> Decide2{Probe failed or degraded}
        Decide2 -- No --> Keep
        Decide2 -- Yes --> Scan

When no usable candidate network is found, the selector retries scanning according to a built-in backoff table (`1000, 5000, 10000, 20000, 30000` ms), which can be overridden via `selector_policy.retry`; applications can also call `esp_wifi_service_request_connect()` to skip scanning and re-evaluation and connect directly to a saved SSID, which is suitable for command-line tools or remote management scenarios.

### Network Quality Probing

Being connected to Wi-Fi while the service remains unusable is a common field issue: the device has obtained an IP address, but access to cloud endpoints fails, latency is too high, or throughput is insufficient. Quality probing covers connectivity checks (accessing a specified URL to determine external connectivity) and latency/throughput checks (measuring request duration and actual throughput); handling is triggered only after consecutive failures or sustained degradation, to avoid switching networks because of a single transient fluctuation. Once degradation is confirmed, the current BSSID is temporarily added to the blacklist, and the selector re-selects a candidate network.

## Application Examples

- The example under the `examples` directory demonstrates the minimal integration flow for NVS profile storage, HTTP provisioning, and a custom selector policy; refer to the component repository for the complete code.

## FAQ

Q1: Does setting max_connect_retry to 0 cause it to retry forever?

Yes.`0` preserves the original behavior of retrying continuously; when set to a non-zero value, automatic re-evaluation stops once consecutive connection failures reach that count, and the counter only resets after `esp_wifi_service_request_connect()` or `esp_wifi_service_request_reeval()` is called.

## API Reference

### 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.

  Note

  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.

  Parameters:

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

    - out_service – [out] Created service handle

  Returns:

    - 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.

  Parameters:

    service – [in] Service handle

  Returns:

    - 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.

  Parameters:

    - service – [in] Service handle

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

  Returns:

    - 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.

  Note

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

  Parameters:

    - service – [in] Service handle

    - scan_handle_out – [out] Scan handle owned by service

  Returns:

    - 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.

  Parameters:

    service – [in] Service handle

  Returns:

    - 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.

  Parameters:

    service – [in] Service handle

  Returns:

    - 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.

  Parameters:

    - service – [in] Service handle

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

  Returns:

    - 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.

  Note

  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.

  Parameters:

    - 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

  Returns:

    - 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.

  Note

  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.

  Parameters:

    service – [in] Service handle

  Returns:

    - 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.

  Parameters:

    - 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

  Returns:

    - 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.

  Parameters:

    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.

  Parameters:

    - handle – [in] Profile manager handle

    - count_out – [out] Number of profiles

  Returns:

    - 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.

  Note

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

  Parameters:

    - handle – [in] Profile manager handle

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

    - user_ctx – [in] Forwarded to every callback invocation

  Returns:

    - 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.

  Parameters:

    - handle – [in] Profile manager handle

    - ssid – [in] SSID to match

    - profile_out – [out] Profile content

  Returns:

    - 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.

  Parameters:

    - handle – [in] Profile manager handle

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

  Returns:

    - 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.

  Parameters:

    - handle – [in] Profile manager handle

    - ssid – [in] NUL-terminated SSID

    - enabled – [in] True to enable

  Returns:

    - 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.

  Parameters:

    - handle – [in] Profile manager handle

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

  Returns:

    - 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.

  Parameters:

    - 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

  Returns:

    - 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.

  Parameters:

    - handle – [in] Profile manager handle

    - ssid – [in] SSID to remove

  Returns:

    - 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.

  Parameters:

    handle – [in] Profile manager handle

  Returns:

    - 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.

Provide feedback about this document