Skip to content

Latest commit

 

History

History
324 lines (209 loc) · 20.5 KB

wifi_provisioning.rst

File metadata and controls

324 lines (209 loc) · 20.5 KB
orphan:

Wi-Fi 配网

:link_to_translation:`en:[English]`

概述

该组件提供控制 Wi-Fi 配网服务的 API,可以通过 SoftAP 或低功耗蓝牙建立 :doc:`protocomm` 安全会话,接收和配置 Wi-Fi 凭证。通过一组 wifi_prov_mgr_ API,可以快速实现配网服务,该服务具备必要功能、代码量少且足够灵活。

初始化

调用 :cpp:func:`wifi_prov_mgr_init()` 可以配置和初始化配网管理器,因此在调用任何其他 wifi_prov_mgr_ API 之前必须先调用此函数。请注意,该管理器依赖于 ESP-IDF 的其他组件,包括 NVS、TCP/IP、Event Loop 和 Wi-Fi,以及可选的 mDNS,因此在调用之前必须先初始化这些组件。调用 :cpp:func:`wifi_prov_mgr_deinit()` 可以随时反初始化管理器。

wifi_prov_mgr_config_t config = {
    .scheme = wifi_prov_scheme_ble,
    .scheme_event_handler = WIFI_PROV_SCHEME_BLE_EVENT_HANDLER_FREE_BTDM
};

ESP_ERROR_CHECK( wifi_prov_mgr_init(config) );

以下配置结构体 :cpp:type:`wifi_prov_mgr_config_t` 里包含的部分字段可用于指定特定管理器行为:

  • :cpp:member:`wifi_prov_mgr_config_t::scheme` - 用于指定配网方案。每个方案对应一种 protocomm 支持的传输模式,因此支持三个选项:

    • wifi_prov_scheme_ble - 使用低功耗蓝牙传输和 GATT 服务器来处理配网命令。
    • wifi_prov_scheme_softap - 使用 Wi-Fi SoftAP 传输和 HTTP 服务器来处理配网命令。
    • wifi_prov_scheme_console - 使用串口传输和控制台来处理配网命令。
  • :cpp:member:`wifi_prov_mgr_config_t::scheme_event_handler` - 为方案定义的专属事件处理程序。选择适当方案后,其专属事件处理程序支持管理器自动处理特定事项。目前,该选项不适用于 SoftAP 或基于控制台的配网方案,但对于低功耗蓝牙配网方案来说非常方便。因为蓝牙需要相当多内存才能正常工作,所以配网完成后,主应用程序需要使用低功耗蓝牙或经典蓝牙时,可能需要回收配网所占的全部或部分内存。此外,未来每当配网设备重启时,都需要再次回收内存。为了便于使用 wifi_prov_scheme_ble 选项,各方案定义了专属处理程序。设备会根据所选处理程序,在反初始化配网管理器时自动释放低功耗蓝牙、经典蓝牙或蓝牙双模的内存。可用选项包括:

    • WIFI_PROV_SCHEME_BLE_EVENT_HANDLER_FREE_BTDM - 同时释放经典蓝牙和低功耗蓝牙或蓝牙双模的内存,可以在主应用程序不需要蓝牙时使用该选项。
    • WIFI_PROV_SCHEME_BLE_EVENT_HANDLER_FREE_BLE - 只释放低功耗蓝牙的内存,可以在主应用程序需要经典蓝牙时使用该选项。
    • WIFI_PROV_SCHEME_BLE_EVENT_HANDLER_FREE_BT - 仅释放经典蓝牙的内存,可以在主应用程序需要低功耗蓝牙时使用该选项,内存会在初始化管理器时立即释放。
    • WIFI_PROV_EVENT_HANDLER_NONE - 不使用任何特定方案的专属处理程序。以下情况可使用该选项:不使用低功耗蓝牙配网方案,即使用 SoftAP 或控制台方案;主应用程序需要自行回收内存;主应用程序需要同时使用低功耗蓝牙和经典蓝牙。
  • :cpp:member:`wifi_prov_mgr_config_t::app_event_handler` (不推荐)- 目前建议使用默认的事件循环处理程序捕获生成的 WIFI_PROV_EVENT。关于配网服务生成事件的列表,请参阅 wifi_prov_cb_event_t 的定义。以下是配网事件示例摘录:

static void event_handler(void* arg, esp_event_base_t event_base,
                          int event_id, void* event_data)
{
    if (event_base == WIFI_PROV_EVENT) {
        switch (event_id) {
            case WIFI_PROV_START:
                ESP_LOGI(TAG, "Provisioning started");
                break;
            case WIFI_PROV_CRED_RECV: {
                wifi_sta_config_t *wifi_sta_cfg = (wifi_sta_config_t *)event_data;
                ESP_LOGI(TAG, "Received Wi-Fi credentials"
                         "\n\tSSID     : %s\n\tPassword : %s",
                         (const char *) wifi_sta_cfg->ssid,
                         (const char *) wifi_sta_cfg->password);
                break;
            }
            case WIFI_PROV_CRED_FAIL: {
                wifi_prov_sta_fail_reason_t *reason = (wifi_prov_sta_fail_reason_t *)event_data;
                ESP_LOGE(TAG, "Provisioning failed!\n\tReason : %s"
                         "\n\tPlease reset to factory and retry provisioning",
                         (*reason == WIFI_PROV_STA_AUTH_ERROR) ?
                         "Wi-Fi station authentication failed" : "Wi-Fi access-point not found");
                break;
            }
            case WIFI_PROV_CRED_SUCCESS:
                ESP_LOGI(TAG, "Provisioning successful");
                break;
            case WIFI_PROV_END:
                /*配网完成后,反初始化管理器。*/
                wifi_prov_mgr_deinit();
                break;
            default:
                break;
        }
    }
}

调用 :cpp:func:`wifi_prov_mgr_deinit()` 可以随时反初始化管理器。

检查配网状态

在运行时,可以调用 :cpp:func:`wifi_prov_mgr_is_provisioned()` 检查设备是否配网完成,该函数会在内部检查 Wi-Fi 凭据是否存储在 NVS 中。

请注意,目前管理器并没有自己的 NVS 命名空间来存储 Wi-Fi 凭据,而是依赖 esp_wifi_ API 来设置和获取存储在默认位置的 NVS 中的凭据。

可以采用以下任一方法重置配网状态:

  • 手动擦除 NVS 分区的配网相关部分。
  • 主应用程序必须实现某种逻辑,以在运行时调用 esp_wifi_ API 来擦除凭据。
  • 主应用程序必须实现某种逻辑,以在不考虑配网状态的情况下,强制启动配网。
bool provisioned = false;
ESP_ERROR_CHECK( wifi_prov_mgr_is_provisioned(&provisioned) );

启动配网服务

在启动配网服务时,需要指定服务名称和相应密钥,即:

  • 使用 wifi_prov_scheme_softap 方案时,服务名称对应 Wi-Fi SoftAP 的 SSID,密钥对应密码。
  • 使用 wifi_prov_scheme_ble 方案时,服务名称对应低功耗蓝牙设备名称,无需指定密钥。

此外,由于管理器内部使用了 protocomm,可以选择其提供的任一安全功能:

  • Security 1 是安全通信,该安全通信需要先握手,其中涉及 X25519 密钥交换和使用所有权证明 pop 完成身份验证,随后使用 AES-CTR 加密或解密后续消息。
  • Security 0 是纯文本通信,会直接忽略 pop

关于安全功能的更多详情,请参阅 :doc:`provisioning`

const char *service_name = "my_device";
const char *service_key  = "password";

wifi_prov_security_t security = WIFI_PROV_SECURITY_1;
const char *pop = "abcd1234";

ESP_ERROR_CHECK( wifi_prov_mgr_start_provisioning(security, pop, service_name, service_key) );

如果收到有效的 Wi-Fi AP 凭据,且设备成功连接到该 AP 并获取了 IP,配网服务会自动结束。此外,调用 :cpp:func:`wifi_prov_mgr_stop_provisioning()` 可以随时停止配网服务。

Note

如果设备使用提供的凭据无法连接,则它不再接受新的凭据,但在设备重新启动前,配网服务仍然会继续运行,并向客户端传递连接失败的信息。设备重新启动后配网状态将变为已配网,因为在 NVS 中找到了凭据,但除非出现与凭据匹配的可用 AP,否则设备仍然无法使用原凭据进行连接。可以通过重置 NVS 中的凭据或强制启动配网服务来解决这个问题,详情请参阅上文 :ref:`wifi-prov-check-state`

等待配网完成

主应用程序通常会等待配网服务完成,然后反初始化管理器以释放资源,最后开始执行自己的逻辑。

有两种方法可以实现这一点,其中调用阻塞 :cpp:func:`wifi_prov_mgr_wait()` 更为简单。

// 启动配网服务
ESP_ERROR_CHECK( wifi_prov_mgr_start_provisioning(security, pop, service_name, service_key) );

// 等待服务完成
wifi_prov_mgr_wait();

// 最后反初始化管理器
wifi_prov_mgr_deinit();

另一种方法是使用默认的事件循环处理程序捕获 WIFI_PROV_EVENT 并在事件 ID 为 WIFI_PROV_END 时调用 :cpp:func:`wifi_prov_mgr_deinit()`

static void event_handler(void* arg, esp_event_base_t event_base,
                          int event_id, void* event_data)
{
    if (event_base == WIFI_PROV_EVENT && event_id == WIFI_PROV_END) {
        /* 配网完成后反初始化管理器 */
        wifi_prov_mgr_deinit();
    }
}

用户端实现

启动服务时,通过广播服务名称识别即将配网的设备。根据选择的传输方式,该服务名称为低功耗蓝牙设备的名称或 SoftAP SSID。

使用 SoftAP 传输方式时,为便于服务发现,必须在启动配网之前初始化 mDNS。在这种情况下,应使用主应用程序设置的主机名,并且在内部将服务类型设置为 _esp_wifi_prov

使用低功耗蓝牙传输方式时,应使用 :cpp:func:`wifi_prov_scheme_ble_set_service_uuid()` 设置一个自定义的 128 位 UUID。该 UUID 将包含在低功耗蓝牙广播中,并对应于提供配网端点作为 GATT 特征的主要服务。每个 GATT 特征都基于主要服务 UUID 形成,其中从第 0 个字节开始计数,第 12 和第 13 个字节为自动分配的不同字节。由于端点特征 UUID 自动分配,因此不应将其用于识别端点。客户端应用程序应通过读取每个特征的用户特征描述符 (0x2901) 来识别端点,该描述符包含特征的端点名称。例如,如果将服务 UUID 设置为 55cc035e-fb27-4f80-be02-3c60828b7451,每个端点特征将分配到一个类似于 55cc____-fb27-4f80-be02-3c60828b7451 的 UUID,其中第 12 和第 13 个字节具有唯一值。

连接设备后,可以通过以下方式识别与配网相关的 protocomm 端点:

配网服务提供的端点
端点名称
即低功耗蓝牙 + GATT 服务器
URI 即 SoftAP
+ HTTP 服务器 + mDNS
描述
prov-session http://<mdns-hostname>.local/prov-session 用于建立会话的安全端点
prov-scan http://wifi-prov.local/prov-scan 用于启动 Wi-Fi 扫描和接收扫描结果的端点
prov-ctrl http://wifi-prov.local/prov-ctrl 用于控制 Wi-Fi 配网状态的端点
prov-config http://<mdns-hostname>.local/prov-config 用于在设备上配置 Wi-Fi 凭据的端点
proto-ver http://<mdns-hostname>.local/proto-ver 用于获取版本信息的端点

连接后,客户端应用程序可以立即从 proto-ver 端点获取版本或属性信息。所有与此端点的通信均未加密,因此在建立安全会话前,可以检索相关必要信息,确保会话兼容。响应结果以 JSON 格式返回,格式类似于 prov: { ver: v1.1, cap: [no_pop] }, my_app: { ver: 1.345, cap: [cloud, local_ctrl] },....。其中 prov 标签提供了配网服务的版本 ver 和属性 cap。目前仅支持 no_pop 属性,表示该服务不需要验证所有权证明。任何与应用程序相关的版本或属性将由其他标签给出,如本示例中的 my_app。使用 :cpp:func:`wifi_prov_mgr_set_app_info()` 可以设置这些附加字段。

用户端应用程序需要根据所配置的安全方案实现签名握手,以建立和认证 protocomm 安全会话。当管理器配置为使用 protocomm security 0 时,则不需要实现签名握手。

关于安全握手和加密的详情,请参阅 :doc:`provisioning`。应用程序必须使用 :component:`protocomm/proto` 中的 .proto 文件。.proto 文件定义了 prov-session 端点支持的 protobuf 消息结构。

建立会话后,以下 wifi_config 命令集可用于配置 Wi-Fi 凭据,这些命令会被序列化为 protobuf 消息,对应的 .proto 文件存放在 :component:`wifi_provisioning/proto` 中。

  • get_status - 用于查询 Wi-Fi 连接状态。设备响应状态为连接中、已连接或已断开。如果状态为已断开,则还会包含断开原因。
  • set_config - 用于设置 Wi-Fi 连接凭据。
  • apply_config - 用于应用先前保存的凭据,即由 set_config 设置的凭据,并启动 Wi-Fi 站点。

建立会话后,客户端还可以从设备请求 Wi-Fi 扫描结果。返回结果为 AP SSID 的列表,按信号强度降序排序。由此,客户端应用程序可以在设备配网时显示附近的 AP,并且用户可以选择其中一个 SSID 并提供密码,然后使用上述 wifi_config 命令发送密码。wifi_scan 端点支持以下 protobuf 命令:

  • scan_start - 启动 Wi-Fi 扫描有多个选项,具体如下:

    • blocking (输入) - 如果参数为 true,则命令只会在扫描完成后返回。
    • passive (输入) - 如果参数为 true,则以被动模式启动扫描,扫描速度可能更慢。
    • group_channels (输入) - 该参数用于指定是否分组扫描。如果参数为 0,表示一次性扫描所有信道;如果参数为非零值,则表示分组扫描信道且参数值为每组中的信道数,每个连续组之间有 120 毫秒的延迟。分组扫描非常适用于使用 SoftAP 的传输模式,因为一次性扫描所有信道可能会导致 Wi-Fi 驱动没有足够时间发送信标,进而导致与部分站点断连。分组扫描时,管理器每扫描完一组信道,至少会等待 120 毫秒,确保驱动程序有足够时间发送信标。例如,假设共有 14 个 Wi-Fi 信道,将 group_channels 设置为 3 则将创建 5 个分组,每个分组包含 3 个信道,最后一个分组则为 14 除以 3 余下的 2 个信道。因此,扫描开始时,首先会扫描前 3 个信道,然后等待 120 毫秒,再继续扫描后 3 个信道,以此类推,直到扫描完 14 个信道。可以根据实际情况调整此参数,因为分组中信道数量过少可能会增加整体扫描时间,而信道数量过多则可能会导致连接再次断开。大多数情况下,将参数值设置为 4 即可。请注意,对于低功耗蓝牙等其他传输模式,可以放心将该参数设置为 0,从而在最短时间内完成扫描。
    • period_ms (输入) - 该扫描参数用于设置在每个信道上的等待时间。
  • scan_status - 可以返回扫描过程的状态:

    • scan_finished (输出) - 扫描完成时,该参数返回为 true。
    • result_count (输出) - 该参数返回到目前为止获取的结果总数。如果扫描仍在进行,该数字会不断更新。
  • scan_result - 用于获取扫描结果。即使扫描仍在进行,也可以调用此函数。

    • start_index (输入) - 从结果列表中获取条目的起始索引位置。
    • count (输入) - 从起始索引位置获取的条目数目。
    • entries (输出) - 返回条目的列表。每个条目包含 ssidchannelrssi 信息。

客户端还可以使用 wifi_ctrl 端点来控制设备的配网状态。wifi_ctrl 端点支持的 protobuf 命令如下:

  • ctrl_reset - 仅在配网失败时,重置设备的内部状态机并清除已配置的凭据。
  • ctrl_reprov - 仅在设备已成功配网的前提下,设备需要重新配网获取新的凭据时,重置设备的内部状态机并清除已配置的凭据。

附加端点

如果用户想要根据自己的需求定制一些附加 protocomm 端点,可以通过两步完成。第一步是创建一个具有特定名称的端点,第二步是为该端点注册一个处理程序。关于端点处理程序的函数签名,请参阅 :doc:`protocomm`。自定义端点必须在初始化后、配网服务启动之前创建,但只能在配网服务启动后为该端点注册 protocomm 处理程序。

wifi_prov_mgr_init(config);
wifi_prov_mgr_endpoint_create("custom-endpoint");
wifi_prov_mgr_start_provisioning(security, pop, service_name, service_key);
wifi_prov_mgr_endpoint_register("custom-endpoint", custom_ep_handler, custom_ep_data);

配网服务停止时,端点会自动取消注册。

在运行时,可以调用 :cpp:func:`wifi_prov_mgr_endpoint_unregister()` 来手动停用某个端点。该函数也可以用于停用配网服务使用的内部端点。

何时以及如何停止配网服务?

当设备使用 apply_config 命令设置的 Wi-Fi 凭据成功连接,配网服务将默认停止,并在响应下一个 get_status 命令后自动关闭低功耗蓝牙或 softAP。如果设备没有收到 get_status 命令,配网服务将在超时 30 秒后停止。

如果设备因 SSID 或密码不正确等原因无法使用 Wi-Fi 凭据成功连接,配网服务将继续运行,并通过 get_status 命令持续响应为断连状态,并提供断连原因。此时设备不会再接受任何新的 Wi-Fi 凭据。除非强制启动配网服务或擦除 NVS 存储,这些凭据将保留。

可以调用 :cpp:func:`wifi_prov_mgr_disable_auto_stop()` 来禁用默认设置。禁用后,只有在显式调用 :cpp:func:`wifi_prov_mgr_stop_provisioning()` 之后,配网服务才会停止,且该函数会安排一个任务来停止配网服务,之后立即返回。配网服务将在一定延迟后停止,并触发 WIFI_PROV_END 事件。该延迟时间可以由 :cpp:func:`wifi_prov_mgr_disable_auto_stop()` 的参数指定。

如果需要在成功建立 Wi-Fi 连接后的某个时间再停止配网服务,应用程序可以采取定制行为。例如,如果应用程序需要设备连接到某个云服务并获取另一组凭证,继而通过自定义 protocomm 端点交换凭证,那么成功完成此操作后,可以在 protocomm 处理程序中调用 :cpp:func:`wifi_prov_mgr_stop_provisioning()` 来停止配网服务。设定适当的延迟时间可以确保 protocomm 处理程序的响应到达客户端应用程序后,才释放传输资源。

应用程序示例

关于完整实现示例,请参阅 :example:`provisioning/wifi_prov_mgr`

配网工具

以下为各平台相应的配网应用程序,并附带源代码:

手机应用程序界面简洁,便于用户使用,而开发者可以使用命令行应用程序,便于调试。

API 参考

.. include-build-file:: inc/manager.inc
.. include-build-file:: inc/scheme_ble.inc
.. include-build-file:: inc/scheme_softap.inc
.. include-build-file:: inc/scheme_console.inc
.. include-build-file:: inc/wifi_config.inc