Skip to content

File espos_prov.h

File List > espos_prov > include > espos_prov.h

Go to the documentation of this file

/*
 * SPDX-FileCopyrightText: 2026 Dirk Wahrheit
 * SPDX-License-Identifier: Apache-2.0
 *
 * BLE provisioning: hand a device its WiFi credentials from a phone, with no
 * access point and no captive portal.
 *
 * WHAT THIS DOES NOT DO, and why it matters.
 *
 * Espressif's network_provisioning manager normally drives the station itself
 * -- esp_wifi_set_config(), esp_wifi_connect(), esp_wifi_start() and its own
 * retry logic. espOS already has a WiFi state machine that owns exactly those
 * calls and is host-tested against them, and two owners of one radio is a
 * fault that only shows up in the field.
 *
 * So this component uses the manager as a BLE TRANSPORT ONLY. Credentials
 * arriving over BLE are written into the `wifi` config namespace -- the same
 * keys the web UI writes -- and the existing state machine picks them up
 * through its config-change callback and connects exactly as it always does.
 * Nothing here calls esp_wifi_*.
 *
 * The consequence is deliberate: because the manager never attempts the
 * connection, it cannot report "wrong password" from its own connect path.
 * espos_prov answers the phone from espos_wifi_get_status() instead, which is
 * the same truth by a different route.
 *
 * THE SCANNER STOPS WHILE THIS RUNS. Bluedroid keeps exactly one GAP callback
 * (esp_ble_gap_register_callback is a setter, not a subscribe), and
 * protocomm's simple_ble registers its own. A BLE gateway scanning at the
 * same time would keep running and silently receive nothing. espos_prov
 * therefore suspends the scanner for the duration and resumes it --
 * reclaiming the GAP callback first -- when provisioning ends. A device being
 * provisioned has no network to publish to yet, so nothing is lost.
 */
#pragma once

#include <stdbool.h>

#include "esp_err.h"

#ifdef __cplusplus
extern "C" {
#endif

typedef struct {
    /* BLE device name the phone sees. Empty picks "ESPOS_<id>", where <id> is
     * the same short device id used for the hostname and the portal SSID, so
     * one device is one recognisable name everywhere. */
    const char *service_name;
    /* Proof of possession, at most 23 characters; longer is rejected rather
     * than truncated. Empty makes the device generate a random one on first
     * use and keep it (config key `prov.pop`), printed on the log and served
     * by GET /api/v1/prov so it can be shown to whoever is holding the phone.
     *
     * It is deliberately NOT derived from the MAC or the device id: the
     * advertised name already carries the id, so a derived PoP would travel
     * over the air beside the thing it protects. A PoP printed on the
     * enclosure at manufacture is better still, and is what this field is
     * for. */
    const char *pop;
    /* Stop advertising after this many seconds. 0 takes
     * CONFIG_ESPOS_PROV_TIMEOUT_S. A device left advertising is a device
     * anyone in the marina can try to provision, so the window closes on its
     * own -- and closes early, a few seconds after credentials arrive, since
     * the job is then done. */
    uint32_t timeout_s;
} espos_prov_cfg_t;

esp_err_t espos_prov_start(const espos_prov_cfg_t *cfg);

esp_err_t espos_prov_stop(void);

bool espos_prov_is_active(void);

bool espos_prov_got_credentials(void);

esp_err_t espos_prov_register_api(void);

#ifdef __cplusplus
}
#endif