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

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

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.