Wi-Fi STA模式编程
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 热点的标准流程如下:
- 使用
RegisterWifiEvent
接口注册 Wi-Fi 事件监听器。 - 使用
EnableWifi
接口开启 Wi-Fi 设备的 STA 模式。 - 使用
Scan
接口开始扫描 Wi-Fi 热点。 - 在扫描状态变化事件 (
OnWifiScanStateChanged
) 的回调函数中监测扫描是否完成。 - 等待扫描完成。
- 使用
GetScanInfoList
接口获取扫描结果。 - 显示扫描结果。
- 使用
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 热点
连接 Wi-Fi 热点的标准流程
- 使用
RegisterWifiEvent
接口注册 Wi-Fi 事件监听器。 - 使用
EnableWifi
接口开启 Wi-Fi 设备的 STA 模式。 - 使用
AddDeviceConfig
接口向系统添加热点配置,主要是 SSID、PSK 和加密方式等配置项。 - 使用
ConnectTo
接口连接到热点上。 - 在连接状态变化 (
OnWifiConnectionChanged
) 事件的回调函数中监测连接是否成功。 - 等待连接成功。
- 使用海思 SDK 接口的 DHCP 客户端 API,从热点中获取 IP 地址。
断开 Wi-Fi 热点的标准流程
- 使用
netifapi_dhcp_stop
接口停止 DHCP 客户端。 - 使用
Disconnect
接口断开热点。 - 使用
RemoveDevice
接口删除热点配置。 - 使用
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
程序运行结果: