Wi-Fi STA模式编程

未尽的清平乐
发布于 2024-11-27 23:37
浏览
0收藏

Wi-Fi STA模式编程

个人简介:`深圳技术大学大二学生,学习研究鸿蒙南向开发知识,发帖子记录一下学习过程中的笔记。
博客主页:https://ost.51cto.com/person/posts/16275438

通过本文可以学到:

  • Wi-Fi STA 模式编程相关的 API;
  • 通过案例程序学习相关 API 的具体使用方法。

一、相关 API 介绍

1. HAL API

硬件抽象层(Hardware Abstraction Layer, HAL)是一种软件层,它位于操作系统内核或运行时环境与硬件设备驱动之间,目的是隐藏具体的硬件细节,为上层软件提供一致的接口,使得上层软件不必关心底层硬件的实现细节,从而提高代码的可移植性和复用性。

Wi-Fi 接口的声明在 foundation\communication\wifi_lite\interfaces\wifiservice\*.h 文件中,如下图所示。

communication
├── bluetooth
├── ipc_lite
├── softbus_lite
├── wifi_aware
└── wifi_lite
├── interfaces
└── wifiservice
├── station_info.h
├── wifi_device_config.h
├── wifi_device.h
├── wifi_error_code.h
├── wifi_event.h
├── wifi_hotspot_config.h
├── wifi_hotspot.h
├── wifi_linked_info.h
└── wifi_scan_info.h

Wi-Fi接口的定义在 device\hisilicon\hispark_pegasus\hi3861_adapter\hals\communication\wifi_lite\wifiservice\source\*.c 文件中。可以在源码中查看接口的详细信息。

STA模式编程接口的声明在 foundation\communication\wifi-lite\interfaces\wifiservice wifi_device.h 文件中。可见下表。

API名称 说明
WifiErrorCode EnableWifi(void) 开启STA
WifiErrorCode DisableWifi(void) 关闭STA
int IsWifiActive(void) 查询STA是否已开启
WifiErrorCode Scan(void) 触发扫描
WifiErrorCode GetScanInfoList(WifiScanInfo* result, unsigned int* size) 获取扫描结果
WifiErrorCode AddDeviceConfig(const WifiDeviceConfig* config, int* result) 添加热点配置,若成功,则会通过result参数传出 netId
WifiErrorCode GetDeviceConfigs(WifiDeviceConfig* result, unsigned int* size) 获取本机的所有热点配置
WifiErrorCode RemoveDevice(int networkId) 删除热点配置
WifiErrorCode ConnectTo(int networkId) 连接到热点
WifiErrorCode Disconnect(void) 断开热点连接
WifiErrorCode GetLinkedInfo(WifiLinkedInfo* result) 获取当前连接的热点信息
WifiErrorCode RegisterWifiEvent(WifiEvent* event) 注册事件监听
WifiErrorCode UnRegisterWifiEvent(const WifiEvent* event) 解除事件监听
WifiErrorCode GetDeviceMacAddress(unsigned char* result) 获取MAC地址
WifiErrorCode AdvanceScan(WifiScanParams* params) 高级扫描

2. 海思SDK API

海思SDK中集成了第三方组件LwIP来完成查找网络接口、启动/停止DHCP客户端等操作。

接口的声明在 device\hisilicon\hispark_pegasus\sdk_liteos\third_party\lwip_sack\include\lwip\*.h 文件中,如下图所示。

third_party
└── lwip_sack
├── include
│   ├── arch
│   ├── compat
│   └── lwip
│       ├── priv
│       ├── prot
│       ├── api.h
│       ├── api_shell.h
│       ├── arch.h
│       ├── autoip.h
│       ├── debug.h
│       ├── def.h
│       ├── dhcp.h
│       ├── dhcp6.h
│       ├── dhcps.h

接口的实现在 device\hisilicon\hispark_pegasus\sdk_liteos\build\libs\liblwip.a 文件中。这是一个预先编译好的静态库文件,我们无法查看具体源码。我们需要使用的API见下表。

API名称 描述
struct netif* netifapi_netif_find(const char* name) 按名称查找网络接口,STA模式的网络接口名为“wlan0”
err_t netifapi_dhcp_start(struct netif* netif) 在指定的网络接口上启动 DHCP 客户端
err_t netifapi_dhcp_stop(struct netif* netif) 在指定的网络接口上停止 DHCP 客户端

二、扫描 Wi-Fi 热点

扫描 Wi-Fi 热点的标准流程如下:

  1. 使用 RegisterWifiEvent 接口注册 Wi-Fi 事件监听器。
  2. 使用 EnableWifi 接口开启 Wi-Fi 设备的 STA 模式。
  3. 使用 Scan 接口开始扫描 Wi-Fi 热点。
  4. 在扫描状态变化事件 (OnWifiScanStateChanged) 的回调函数中监测扫描是否完成。
  5. 等待扫描完成。
  6. 使用 GetScanInfoList 接口获取扫描结果。
  7. 显示扫描结果。
  8. 使用 DisableWifi 接口关闭 Wi-Fi 设备的 STA 模式。

三、案例:扫描 Wi-Fi 热点

1. 新建目录

新建 applications\sample\wifi-iot\app\wifi_demo 目录。

2. 编写源码与编译脚本

新建 applications\sample\wifi-iot\app\wifi_demo\wifi_scan_demo.c 文件:

#include "cmsis_os2.h"
#include "ohos_init.h"
#include "wifi_device.h"   // 用于开启和关闭 Wi-Fi 设备的 STA 模式,连接或断开 STA,查询 STA 状态,事件监听
#include <stdio.h>
#include <string.h>
#include <unistd.h>   // POSIX 头文件

static int g_scanDone = 0;   // 全局变量,用于标识扫描是否完成

// 返回字符串形式的 Wi-Fi security types,用于日志输出
static char* SecurityTypeName(WifiSecurityType type)
{
    switch (type)
    {
        case WIFI_SEC_TYPE_OPEN:
            return "OPEN";
        case WIFI_SEC_TYPE_WEP:
            return "WEP";
        case WIFI_SEC_TYPE_PSK:
            return "PSK";
        case WIFI_SEC_TYPE_SAE:
            return "SAE";
        default:
            break;
    }
    return "UNKNOWN";
}

// 连接状态变化回调函数
// 该回调函数有两个参数 state 和 info
// state 表示连接状态,WIFI_STATE_AVALIABLE 表示连接成功,WIFI_STATE_NOT_AVALIABLE 表示连接失败。
// 注意,"AVALIABLE"是鸿蒙源码的错误,而非本程序出错,该拼写错误于 3.2 Beta2 版之后被修复。
// info 类型为 WifiLinkedInfo*,有多个成员,包括 ssid, bssid, rssi, connState, disconnectedReason 等
void OnWifiConnectionChanged(int state, WifiLinkedInfo* info)
{
    (void)state;
    (void)info;

    // 简单输出日志信息,表明函数被执行了
    printf("%s %d\r\n", __FUNCTION__, __LINE__);
}

// 显示扫描结果
void PrintScanResult(void)
{
    // 创建一个 WifiScanInfo 数组,用于存放扫描结果
    WifiScanInfo scanResult[WIFI_SCAN_HOTSPOT_LIMIT] = { 0 };
    uint32_t resultSize = WIFI_SCAN_HOTSPOT_LIMIT;

    // 初始化数组
    memset(&scanResult, 0, sizeof(scanResult));

    // 获取扫描结果
    // GetScanInfoList 函数有两个参数:
    // result 指向用于存放结果的数组,需要大于等于 WIFI_SCAN_HOTSPOT_LIMIT
    // size 类型为指针,是为了内部能够修改它的值,返回后 size 指向的值是实际搜索到的热点个数
    WifiErrorCode errCode = GetScanInfoList(scanResult, &resultSize);

    // 检查接口调用结果
    if (errCode != WIFI_SUCCESS)
    {
        printf("GetScanInfoList failed: %d\r\n", errCode);
        return;
    }

    // 打印扫描结果
    for (uint32_t i = 0; i < resultSize; i++)
    {
        // 存储 MAC 地址
        static char macAddress[32] = { 0 };

        // 获取 MAC 地址
        WifiScanInfo info = scanResult[i];
        // 函数体,打印每个扫描到的 Wi-Fi 热点的信息
        unsigned char* mac = info.bssid;

        // 把 MAC 地址转换为字符串
        snprintf(macAddress, sizeof(macAddress), "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);

        // 输出日志
        printf("AP[%d]:%s,%4s,%d,%d,%d,%s\r\n",       // 格式
               i,                                     // 热点序号
               macAddress,                            // MAC 地址
               SecurityTypeName(info.securityType),   // 安全类型
               info.rssi,                             // 信号强度 (Received Signal Strength Indicator)
               info.band,                             // 频带类型
               info.frequency,                        // 频段
               info.ssid);                            // SSID
    }
}

// 扫描状态变化回调函数
// 该回调函数有两个参数 state 和 size
// state 表示扫描状态, WIFI_STATE_AVALIABLE 表示扫描动作完成, WIFI_STATE_NOT_AVALIABLE 表示扫描动作未完成
// size 表示扫描到的热点个数
void OnWifiScanStateChanged(int state, int size)
{
    printf("%s %d, state=%X, size=%d\r\n", __FUNCTION__, __LINE__, state, size);

    // 扫描完成,并且找到了热点
    if (state == WIFI_STATE_AVALIABLE && size > 0)
    {
        g_scanDone = 1;
    }
}

// 主线程函数
static void WifiScanTask(void* arg)
{
    (void)arg;

    // 用于接收接口返回值
    WifiErrorCode errCode;

    // 创建 Wi-Fi 事件监听器
    // 开启 Wi-Fi 设备的 STA 模式之前,需要使用 RegisterWifiEvent 接口,向系统注册状态监听函数,用于接收状态通知
    // STA 模式需要绑定以下两个回调函数:
    // OnWifiScanStateChanged 用于绑定扫描状态监听函数
    // OnWifiConnectionChanged 用于绑定连接状态监听函数
    WifiEvent eventListener = { // 在连接状态发生变化时,调用 OnWifiConnectionChanged 回调函数
                                .OnWifiConnectionChanged = OnWifiConnectionChanged,
                                // 在扫描状态发生变化时,调用 OnWifiScanStateChanged 回调函数
                                .OnWifiScanStateChanged = OnWifiScanStateChanged
    };

    osDelay(10);

    // 使用 RegisterWifiEvent 接口,注册 Wi-Fi 事件监听器
    errCode = RegisterWifiEvent(&eventListener);

    // 接收并判断返回值是否为 WIFI_SUCCESS,用于确认是否调用成功
    printf("RegisterWifiEvent:%d\r\n", errCode);

    // 工作循环
    while (1)
    {
        // 进行 Wi-Fi 的 STA 模式开发前,必须调用 EnableWifi 函数
        errCode = EnableWifi();
        printf("EnableWifi:%d\r\n", errCode);
        osDelay(100);
        // 开始扫描 Wi-Fi 热点。只是触发扫描动作,并不会等到扫描完成才返回
        // 不返回扫描结果,只是通过 OnWifiScanStateChanged 事件通知扫描结果
        // 在事件处理函数中,可以通过调用 GetScanInfoList 函数获取扫描结果
        g_scanDone = 0;
        errCode = Scan();
        printf("Scan: %d\r\n", errCode);

        // 等待扫描完成
        while (!g_scanDone)
        {
            osDelay(5);
        }

        // 打印扫描结果
        // 扫描完成后要及时调用 GetScanInfoList 函数获取扫描结果
        // 如果间隔时间太长(例如 5 秒以上),可能会无法获得上次的扫描结果
        PrintScanResult();

        // 关闭 Wi-Fi 设备的 STA 模式
        errCode = DisableWifi();
        printf("DisableWifi: %d\r\n", errCode);
        osDelay(500);
    }
}

// 入口函数
static void WifiScanDemo(void)
{
    osThreadAttr_t attr;
    attr.name = "WifiScanTask";
    attr.attr_bits = 0U;
    attr.cb_mem = NULL;
    attr.cb_size = 0U;
    attr.stack_mem = NULL;
    attr.stack_size = 10240;
    attr.priority = osPriorityNormal;

    if (osThreadNew(WifiScanTask, NULL, &attr) == NULL)
    {
        printf("[WifiScanDemo] Failed to create WifiScanTask!\n");
    }
}

// 运行入口函数
APP_FEATURE_INIT(WifiScanDemo);

新建 applications\sample\wifi-iot\app\wifi_demo\BUILD.gn 文件:

static_library("wifi_demo") {
  sources = [
    "wifi_scan_demo.c"
  ]
  include_dirs = [
    "//kernel/liteos_m/kal",
    "//utils/native/lite/include",
    "//kernel/liteos_m/kal/cmsis",
    "//base/iot_hardware/peripheral/interfaces/kits",
    # HAL接口中的Wi-Fi接口
    "//foundation/communication/wifi_lite/interfaces/wifiservice",
    # 海思SDK接口中的lwIP TCP/IP协议栈
    "//device/hisilicon/hispark_pegasus/sdk_liteos/third_party/lwip_sack/include"    
  ]
}

修改 applications\sample\wifi-iot\app\BUILD.gn 文件:

import("//build/lite/config/component/lite_component.gni")

lite_component("app") {
    features = [
    "wifi_demo",
    ]
}

3. 编译、烧录、运行

运行结果如下图:

Wi-Fi STA模式编程-鸿蒙开发者社区

四、连接 Wi-Fi 热点

连接 Wi-Fi 热点的标准流程

  1. 使用 RegisterWifiEvent 接口注册 Wi-Fi 事件监听器。
  2. 使用 EnableWifi 接口开启 Wi-Fi 设备的 STA 模式。
  3. 使用 AddDeviceConfig 接口向系统添加热点配置,主要是 SSID、PSK 和加密方式等配置项。
  4. 使用 ConnectTo 接口连接到热点上。
  5. 在连接状态变化 (OnWifiConnectionChanged) 事件的回调函数中监测连接是否成功。
  6. 等待连接成功。
  7. 使用海思 SDK 接口的 DHCP 客户端 API,从热点中获取 IP 地址。

断开 Wi-Fi 热点的标准流程

  1. 使用 netifapi_dhcp_stop 接口停止 DHCP 客户端。
  2. 使用 Disconnect 接口断开热点。
  3. 使用 RemoveDevice 接口删除热点配置。
  4. 使用 DisableWifi 接口关闭 Wi-Fi 设备的 STA 模式。

五、案例:连接 Wi-Fi 热点

1. 新建目录

沿用上一个案例的 applications\sample\wifi-iot\app\wifi_demo 目录。

2. 编写源码与编译脚本

新建 applications\sample\wifi-iot\app\wifi_demo\wifi_connect_demo.c 文件,源码如下:

#include "cmsis_os2.h"
#include "lwip/api_shell.h"   // lwIP TCP/IP 协议栈: SHELL 命令 API
#include "lwip/netifapi.h"    // lwIP TCP/IP 协议栈: 网络 API
#include "ohos_init.h"
#include "wifi_device.h"   // Wi-Fi 设备接口: STA 模式
#include <stdio.h>
#include <string.h>
#include <unistd.h>

static int g_connected = 0;   // 全局变量, 用于标识连接是否成功

// 输出连接信息
static void PrintLinkedInfo(WifiLinkedInfo* info)
{
    if (!info)
        return;

    static char macAddress[32] = { 0 };   // 存储 MAC 地址
    unsigned char* mac = info->bssid;     // 获取 MAC 地址

    // 把 MAC 地址转换为字符串
    snprintf(macAddress, sizeof(macAddress), "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);

    // 输出日志
    printf("bssid:%s, rssi:%d, connState:%d, reason:%d, ssid:%s\r\n",
           macAddress,                 // MAC 地址
           info->rssi,                 // 信号强度
           info->connState,            // 连接状态
           info->disconnectedReason,   // 断开原因
           info->ssid);                // SSID
}

// 连接状态变化回调函数
// 该回调函数有两个参数 state 和 info
// state 表示连接状态, WIFI_STATE_AVAILABLE 表示连接成功, WIFI_STATE_NOT_AVAILABLE 表示连接失败
// info 类型为 WifiLinkedInfo*, 有多个成员,包括 ssid, bssid, rssi, connState, disconnectedReason 等
static void OnWifiConnectionChanged(int state, WifiLinkedInfo* info)
{
    if (!info)
        return;

    // 输出日志
    printf("%s %d, state=%d, info=\r\n", __FUNCTION__, __LINE__, state);

    PrintLinkedInfo(info);   // 输出连接信息

    if (state == WIFI_STATE_AVALIABLE)
    {
        g_connected = 1;
    }
    else
    {
        g_connected = 0;
    }
}

// 扫描状态变化回调函数
// 该回调函数有两个参数 state 和 size
// state 表示扫描状态, WIFI_STATE_AVAILABLE 表示扫描动作完成, WIFI_STATE_NOT_AVAILABLE 表示扫描动作未完成
// size 表示扫描到的热点个数
static void OnWifiScanStateChanged(int state, int size)
{
    // 简单输出日志信息,表明函数被执行了
    printf("%s %d, state=%X, size=%d\r\n", __FUNCTION__, __LINE__, state, size);
}

// 主线程函数
static void WifiConnectTask(void* arg)
{
    (void)arg;

    WifiErrorCode errCode;   // 用于接收接口返回值

    // 创建 Wi-Fi 事件监听器
    // 在开启 Wi-Fi 设备的 STA 模式之前,需要使用 RegisterWifiEvent 接口向系统注册状态监听函数,用于接收状态通知
    // STA 模式需要绑定以下两个回调函数:
    // OnWifiScanStateChanged 用于绑定扫描状态监听函数
    // OnWifiConnectionChanged 用于绑定连接状态监听函数
    WifiEvent eventListener = { // 在连接状态发生变化时,调用 OnWifiConnectionChanged 回调函数
                                .OnWifiConnectionChanged = OnWifiConnectionChanged,
                                // 在扫描状态发生变化时,调用 OnWifiScanStateChanged 回调函数
                                .OnWifiScanStateChanged = OnWifiScanStateChanged
    };

    WifiDeviceConfig apConfig = {};   // 定义热点配置

    int netId = -1;   // 用于保存 netId

    osDelay(10);

    errCode = RegisterWifiEvent(&eventListener);   // 向系统注册状态监听函数
    printf("RegisterWifiEvent:%d\r\n", errCode);   // 打印接口调用结果
    strcpy(apConfig.ssid, "WARDEN");               // 设置热点配置中的 SSID
    strcpy(apConfig.preSharedKey, "88888888");     // 设置热点配置中的密码
    apConfig.securityType = WIFI_SEC_TYPE_PSK;     // 设置热点配置中的加密方式

    while (1)
    {
        // 开启 Wi-Fi 设备的 STA 模式。使其可以扫描,并且连接到 AP 上
        errCode = EnableWifi();
        printf("EnableWifi:%d\r\n", errCode);
        osDelay(10);

        // 通过 AddDeviceConfig 接口向系统添加热点配置,它有两个参数:
        // 第一个参数 config,类型为 const WifiDeviceConfig*,用于指定热点配置
        // 第二个参数 result,类型为 int*,用于操作成功时返回 netId
        errCode = AddDeviceConfig(&apConfig, &netId);

        printf("AddDeviceConfig:%d\r\n", errCode);   // 打印接口调用结果

        // 使用 ConnectTo 接口连接热点,它有一个参数:
        // netId, 类型为 int, 应该使用 AddDeviceConfig 接口调用成功之后通过 result 参数传出的值进行填充
        // ConnectTo 接口是同步的,连接成功/失败会通过返回值体现
        g_connected = 0;
        errCode = ConnectTo(netId);

        printf("ConnectTo(%d):%d\r\n", netId, errCode);   // 打印接口调用结果

        // 等待连接成功
        while (!g_connected)
        {
            osDelay(10);
        }

        // 输出日志
        printf("g_connected:%d\r\n", g_connected);
        osDelay(50);

        // 连接成功后,需要调用 DHCP 客户端接口从热点中获取 IP 地址
        // 使用 netifapi_netif_find("wlan0") 获取 STA 模式的网络接口
        struct netif* iface = netifapi_netif_find("wlan0");

        // 获取网络接口成功
        if (iface)
        {
            // 使用 netifapi_dhcp_start 接口启动 DHCP 客户端
            err_t ret = netifapi_dhcp_start(iface);

            // 打印接口调用结果
            printf("netifapi_dhcp_start:%d\r\n", ret);

            // 等待 DHCP 服务端分配 IP 地址
            osDelay(200);
        }

        // 模拟一段时间的联网业务
        int timeout = 60;
        printf("after %d seconds, I'll disconnect WiFi!\n", timeout);
        while (timeout--)
        {
            osDelay(100);
            printf("after %d seconds, I'll disconnect WiFi!\n", timeout);
        }

        // 使用 Disconnect 接口断开热点,无须参数,断开之前需要停止 DHCP 客户端
        // 使用 netifapi_dhcp_stop 接口停止 DHCP 客户端
        err_t ret = netifapi_dhcp_stop(iface);

        // 打印接口调用结果
        printf("netifapi_dhcp_stop:%d\r\n", ret);

        // 输出日志
        printf("disconnect!\r\n");

        // 断开热点连接
        Disconnect();

        // 使用 RemoveDevice 接口删除热点配置
        RemoveDevice(netId);

        // 关闭 Wi-Fi 设备的 STA 模式
        errCode = DisableWifi();

        // 打印接口调用结果
        printf("DisableWifi:%d\r\n", errCode);

        // 等待 2 秒
        osDelay(200);
    }   // 工作循环结束
}

// 入口函数
static void WifiConnectDemo(void)
{
    osThreadAttr_t attr;
    attr.name = "WifiConnectTask";
    attr.attr_bits = 0U;
    attr.cb_mem = NULL;
    attr.cb_size = 0U;
    attr.stack_mem = NULL;
    attr.stack_size = 10240;
    attr.priority = osPriorityNormal;

    if (osThreadNew(WifiConnectTask, NULL, &attr) == NULL)
    {
        printf("[WifiConnectDemo] Failed to create WifiConnectTask!\n");
    }
}

// 运行入口函数
APP_FEATURE_INIT(WifiConnectDemo);

修改 applications\sample\wifi-iot\app\wifi_demo\BUILD.gn 文件,源码如下:

static_library("wifi_demo") {
  sources = [
    # "wifi_scan_demo.c",
    "wifi_connect_demo.c"
  ]
  include_dirs = [
    "//kernel/liteos_m/kal",
    "//utils/native/lite/include",
    "//kernel/liteos_m/kal/cmsis",
    "//base/iot_hardware/peripheral/interfaces/kits",
    # HAL接口中的Wi-Fi接口
    "//foundation/communication/wifi_lite/interfaces/wifiservice",
    # 海思SDK接口中的lwIP TCP/IP协议栈
    "//device/hisilicon/hispark_pegasus/sdk_liteos/third_party/lwip_sack/include"
    
  ]
}

3. 编译、烧录、运行

使用手机开启 Wi-Fi 热点。

本人配置:
SSID:WARDEN
PASSWORD:88888888

程序运行结果:
Wi-Fi STA模式编程-鸿蒙开发者社区
Wi-Fi STA模式编程-鸿蒙开发者社区

收藏
回复
举报
回复
    相关推荐