项目简介
通过实现一个简单的运行在Hi3516DV300开发板上的命令行WiFi连接控制程序,介绍在OpenHarmony中可用的WiFi接口功能,了解如何用其实现一些常见的WiFi的功能,希望能通过本篇文章,为使用wifi的接口功能提供一个良好的切入点。
获取WLAN组件
wifi组件的gitee仓库为: WLAN组件。
从目录可以看到,该接口在OpenHarmony源码中的位置为foundation/communication/wifi。
文件和接口介绍
wifi中主要使用的功能接口都存放在其中的/interface/wifiservice文件夹下的头文件中。
其中,在编程过程中需要用到的几个重点文件是:
1、wifi_msg.h
这是首先需要了解的头文件,该头文件定义wifi运行时所使用的数据结构。其中,对实现连接功能最重要数据结构是这三个:
首先是WifiDeviceConfig,即wifi运行配置信息,其中一些数据对于连接方的设备有默认值几乎从不变动,主要应当关注的是wifi名称(ssid)和密码(preSharedKey)两项,其他的部分几乎都在后台被使用。由于wifi_msg.h已经包含于其他必须头文件中,因此不需要包含在自己的代码内。
然后还有WifiState和ConnState两个枚举,它们分别代表wifi的运行状态和与wifi的连接状态。
2、wifi_device.h
该头文件定义了一个WifiDevice类,这就是在程序中需要调用的接口类。同样本次也不是所有的函数都需要关注,其中的这些函数在本次实现连接wifi时需要的各项功能时会用到:
- GetInstance(int system_ability_id)。
首先是这个排在第一的函数,通过它获得一个实例来完成之后的功能。
- WifiErrorCode EnableWifi(void)。
- WifiErrorCode DisableWifi(void)。
- int IsWifiActive(void);
以上三个函数是为了操作wifi开关而使用。
- WifiErrorCode ConnectToDevice(const WifiDeviceConfig *config)。
- WifiErrorCode Disconnect(void)。
这两个函数自然是用来连接和断开wifi的。
3、wifi_errcode.h
这个头文件里只定义了一个枚举类ErrCode,它被用于所有需要判断是否正常结束的函数的返回值中。对于新手来说,通过返回值返回函数的运行情况,时刻不忘检查程序的运行是否正常,是应牢记并时刻应用的良好习惯。
4、wifi_scan.h
wifi_scan.h头文件也只包含一个WifiScan类,提供在wifi开启之后扫描获取周边wifi信息的接口。需要注意的是,其实在系统中当wifi开启之后已经会自动启动对周边wifi的扫描,因此我们自己编写代码并不用调用scan()函数,只需要使用RegisterCallBackGetScanInfoList和GetScanInfoList这两个函数注册一个callback并获取扫描结果就可以了。此外,和在wifi_device.h中一样,也需要通过GetInstance获得一个实例。
5、i_wifi_device_callback.h
i_wifi_device_callback.h头文件包含了监控wifi状态需要的接口。
6、i_wifi_scan_callback.h
i_wifi_scan_callback.h头文件提供了获取扫描wifi信息的接口。
实现一个WiFi控制程序
应当实现的功能
就和常见的各种设备的wifi功能一样,一个简单的wifi控制程序应该有这样的一些功能:
1、开关wifi
这是最基本的wifi功能。
2、显示周边wifi信息
将扫描到的wifi信息打印输出。
3、连接wifi
接入一个目标wifi。可以实现多种接入wifi的方式,比如手动选择wifi进行连接或者自动接入已经存储过数据的wifi。
4、断开wifi连接
断开wifi连接的功能可能被嵌入在其他功能内部,比如在切换连接的wifi前或者关闭wifi功能前。当然,也可以主动选择断开已经连接的wifi。
5、确认wifi状态
命令行程序并不能很方便的显示wifi各状态,不过简单的打印部分信息并不困难。
设计交互方式和控制指令
由于我们要做的只是一个简单的小型程序用来展示wifi的主要接口功能,因此选择只制作一个简单的控制台程序。
通过输入不同的数字,可以实现不同的程序行为。这样一些行为将可以被选择执行:
- 开启wifi。
- 关闭wifi。
- 显示周边wifi信息。
- 连接一个指定wifi。
- 通过扫描结果选择想要连接的wifi名称。
- 断开连接。
- 确认连接状态。
- 退出程序。作为一个特殊选项,程序中将设置输入0可以执行退出。
设计实现指令功能的函数
使用一个WIfiTest类来尝试实现需要的功能。由于使用的接口均在OHOS命名空间下的WiFi命名空间中,所以不要忘了将新建的类也包含在这两层命名空间中:
namespace OHOS
{
namespace Wifi
{
// 所有类均应在命名空间中创建
} // namespace Wifi
} // namespace OHOS
为了使用想要的接口,需要一个接口类的指针。因此为类加入以下指针成员:
std::unique_ptr<WifiScan> wifiScan。
std::unique_ptr<WifiDevice> wifiDevice。
对应想要的wifi功能接口的调用以及扫描wifi信息接口的调用。
设计wifi工作中应有的功能:
- wifi在后台应完成的基础功能:
程序应当在用户调用各个功能之前先调用之前介绍的两个GetInstance函数获取实例。此外,程序也应当尽早的注册监听信息变化的callback来获取想要的信息。最后,这些行为需要在用户进行各种操作的时候都一样进行。
因此,设计一个InitCommon()函数来提取那些隐藏在各个函数功能之后的行为,从而能将其插入到其他所有功能函数的实现中去。该函数本身不会过于复杂:只需要通过方才添加的两个指针获取实例,再注册好回调就算完成了。
但是此时,在注册回调的时候需要的类型为 const sptr<IWifiScanCallback> 和 const sptr<IWifiDeviceCallBack> 的指针参数还没有定义。因此接下来需要从 i_wifi_device_callback.h 和 i_wifi_scan_callback.h 两个头文件中的接口里获取需要的指针。 - 实现接口函数并获取指针:
分别使用两个新类继承两个接口类,在类中重写继承的函数。
定义一个继承IWifiDeviceCallBack类的WifiDeviceResultCallBack类:
class WifiDeviceResultCallBack : public IWifiDeviceCallBack
{
public:
void OnWifiStateChanged(int state) override;
void OnWifiConnectionChanged(int state, const WifiLinkedInfo &info) override;
void OnWifiRssiChanged(int rssi) override;
void OnWifiWpsStateChanged(int state, const std::string &pinCode) override;
void OnStreamChanged(int direction) override;
sptr<IRemoteObject> AsObject() override;
};
根据需要实现这些虚函数:
OnWifiStateChanged函数,输出wifi状态:
void WifiDeviceResultCallBack::OnWifiStateChanged(int state) override
{
// WifiState { DISABLING = 0, DISABLED = 1, ENABLING = 2, ENABLED = 3, UNKNOWN = 4 }
WifiState wState = static_cast<WifiState>(state); //由于传入数据为int型因此需要类型转换
std::cout << "device state changed,state=";
switch (wState) {
case WifiState::DISABLING:
std::cout << "WifiState::DISABLING";
break;
case WifiState::DISABLED:
std::cout << "WifiState::DISABLED";
break;
case WifiState::ENABLING:
std::cout << "WifiState::ENABLING";
break;
case WifiState::ENABLED:
std::cout << "WifiState::ENABLED";
break;
case WifiState::UNKNOWN:
std::cout << "WifiState::UNKNOWN";
break;
default:
break;
}
std::cout << std::endl;
}
OnWifiConnectionChanged函数,输出wifi的连接的状态:
void WifiDeviceResultCallBack::OnWifiConnectionChanged(int state, const WifiLinkedInfo &info) override
{
ConnState cState = static_cast<ConnState>(state);
std::cout << "device connection state changed,state=";
switch (cState) {
case ConnState::CONNECTING:
std::cout << "ConnState::CONNECTING";
break;
case ConnState::CONNECTED:
std::cout << "ConnState::CONNECT_AP_CONNECTED";
break;
case ConnState::DISCONNECTING:
std::cout << "ConnState::DISCONNECT_DISCONNECTING";
break;
case ConnState::DISCONNECTED:
std::cout << "ConnState::DISCONNECT_DISCONNECTED";
break;
case ConnState::OBTAINING_IPADDR:
std::cout << "ConnState::CONNECT_OBTAINING_IP";
break;
case ConnState::UNKNOWN:
std::cout << "ConnState::UNKNOWN";
break;
default:
break;
}
std::cout << std::endl;
(void)info;
}
剩下的三个函数分别输出的信息在本次的程序中不算重要,其中比较有意义的wifi信号强度即rssi值又是纯粹的数值,因此直接给它们简单的输出数值的实现:
void OnWifiRssiChanged(int rssi) override
{
std::cout << "device rssi changed,rssi=" << rssi << std::endl;
}
void OnWifiWpsStateChanged(int state, const std::string &pinCode) override
{
std::cout << "device wps state changed,state=" << state << std::endl;
(void)pinCode;
}
void OnStreamChanged(int direction) override
{
std::cout << "device stream changed,direction=" << direction << std::endl;
(void)direction;
}
这个时候,看起来继承自 IWifiDeviceCallBack 类里的所有虚函数都已经实现了,可是此时却还有一个AsObject() 函数。这是因为IWifiDeviceCallBack类也是继承于IRemoteBroker类,其中包含了这个未实现的虚函数。这一函数也需要实现:
sptr<IRemoteObject> AsObject() override
{
return nullptr;
}
因为继承的层次可能很多,一定要记住构造子类的时候需要实现的虚函数很可能并不是只定义在该子类的父类中。
用同样的方法实现继承 IWifiScanCallback 类的 WifiScanResultCallback 类:
class WifiScanResultCallback : public IWifiScanCallback
{
public:
void OnWifiScanStateChanged(int state) override
{
ScanHandleNotify scanNotify = static_cast<ScanHandleNotify>(state);
std::cout << "scan state changed,state=";
switch (scanNotify)
{
case ScanHandleNotify::SCAN_OK:
std::cout << "ScanHandleNotify::SCAN_OK";
break;
case ScanHandleNotify::SCAN_FAIL:
std::cout << "ScanHandleNotify::SCAN_FAIL";
break;
default:
break;
}
std::cout << std::endl;
std::unique_ptr<WifiScan> wifiScan = WifiScan::GetInstance(WIFI_SCAN_ABILITY_ID);
std::vector<WifiScanInfo> result;
wifiScan->GetScanInfoList(result);
std::cout << "scan result, size=" << result.size() << std::endl;
}
sptr<IRemoteObject> AsObject() override
{
return nullptr;
}
};
此时也获得了在InitCommon()函数中需要的指针,可以完成这一函数并用来嵌入其他函数中:
void WifiTest::InitCommon()
{
if (wifiScan.get() == nullptr)
{
wifiScan = WifiScan::GetInstance(WIFI_SCAN_ABILITY_ID);
const ::OHOS::sptr<IWifiScanCallback> event = new WifiScanResultCallback;
wifiScan->RegisterCallBack(event);
}
if (wifiDevice.get() == nullptr)
{
wifiDevice = WifiDevice::GetInstance(WIFI_DEVICE_ABILITY_ID);
const ::OHOS::sptr<IWifiDeviceCallBack> event = new WifiDeviceResultCallBack;
wifiDevice->RegisterCallBack(event);
}
}
这样一来程序在后台需要的准备就完成了,可以开始设计提供给用户的功能了。
实现开启wifi功能:
本身只需要调用EnableWifi()函数就可以实现,但是它切换了最重要的wifi开关的状态,应当在输出打印中清楚的体现出来:
void WifiTest::EnableWifi()
{
std::cout << "EnableWifi, please wait\n";
ErrCode code = WIFI_OPT_FAILED;
InitCommon(); //在执行用户操作之前执行后台运行的部分
code = wifiDevice->EnableWifi();
std::cout << "EnableWifi code=" << ParseCode(code) << std::endl;
if (code == WIFI_OPT_SUCCESS)
{
std::cout << "Input a number to choose you what" << std::endl;
std::cout << "2. DisableWifi" << std::endl;
std::cout << "3. ShowWIfiInfo" << std::endl;
std::cout << "4. Connect_Customize" << std::endl;
std::cout << "5. ConnectByNumber" << std::endl;
std::cout << "6. DisConnect" << std::endl;
std::cout << "7. IsConnected" << std::endl;
}
}
实现关闭wifi功能:
和开启一样,调用DisableWifi()函数就可以实现关闭wifi:
void WifiTest::DisableWifi()
{
std::cout << "DisableWifi, please wait\n";
ErrCode code = WIFI_OPT_FAILED;
InitCommon();
code = wifiDevice->DisableWifi();
std::cout << "DisableWifi code=" << ParseCode(code) << std::endl;
if (code == WIFI_OPT_SUCCESS)
{
std::cout << "Wifi has been turned off" << std::endl;
std::cout << "Wificontroler still runnning..." << std::endl;
std::cout << "Input 1 to Turn WiFi on " << std::endl;
std::cout << "0. exit wificontroler" << std::endl;
}
}
显示周边wifi信息:
获取wifi信息的方法在WifiScanResultCallback 类中就有实现。作为一个简单的命令行程序,只需要显示wifi名就足够了:
void WifiTest::ShowWifiInfo()
{
std::cout << "Scaning, please wait\n";
InitCommon();
int i;
std::cout << "List 10 Wifi Info\n"; //出于命令行界面大小的限制,选择只显示前10个wifi。
wifiScan->GetScanInfoList(result);
for (i = 1; i < 11; i++)
{
std::cout << i << ". wifiname = " << result[i].ssid << std::endl;
}
std::cout << std::endl << std::endl;
std::cout << "Input a number to choose you what" << std::endl;
std::cout << "2. DisableWifi" << std::endl;
std::cout << "3. ShowWIfiInfo" << std::endl;
std::cout << "4. Connect_Customize" << std::endl;
std::cout << "5. ConnectByNumber" << std::endl;
std::cout << "6. DisConnect" << std::endl;
std::cout << "7. IsConnected" << std::endl;
std::cout << "0. exit wificontroler" << std::endl;
}
如果想要让用户能知道更多的信息,可以从result包含的成员中挑选其他内容打印,比如可以选择一并显示加密类型securityType和信号强度rssi。
连接一个指定wifi:
通过调用ConnectToDevice接口传入wifi名和密码进行连接。为此使用到了WifiDeviceConfig类的对象作为参数传递给ConnectToDevice接口。
void WifiTest::Connect_Customize()
{
std::cout << "Please input wifi name:";
ErrCode code = WIFI_OPT_FAILED;
InitCommon();
WifiDeviceConfig config;
std::string temp;
std::cin >> temp;
config.ssid = temp;
std::cout << "Please input password:\n";
std::cin >> temp;
config.preSharedKey = temp;
config.keyMgmt = "WPA-PSK"; //因为程序仅演示部分功能,默认接入的wifi选择该加密方式
code = wifiDevice->ConnectToDevice(config);
std::cout << "Connect code=" << ParseCode(code) << std::endl;
}
通过扫描结果选择wifi连接:
实现一个对wifi更简单的“选择”:通过显示周边wifi信息功能打印的wifi名列表,我们可以通过输入其序号来选择一个wifi名进行连接。
void WifiTest::ConnectByNumber()
{
std::cout << "Connect Customize, please wait\n";
ErrCode code = WIFI_OPT_FAILED;
InitCommon();
WifiDeviceConfig config;
std::string temp;
int num;
std::cout << "Please input a number to choose wifi name:";
std::cin >> num;
temp = result[num].ssid;
config.ssid = temp;
std::cout << "Please input password:\n";
std::cin >> temp;
config.preSharedKey = temp;
config.keyMgmt = "WPA-PSK";
code = wifiDevice->ConnectToDevice(config);
std::cout << "Connect code=" << ParseCode(code) << std::endl;
}
断开wifi连接:
同样是一个单一动作的函数,只需要调用Disconnect接口就可以了。
void WifiTest::DisConnect()
{
std::cout << "DisConnect, please wait\n";
ErrCode code = WIFI_OPT_FAILED;
InitCommon();
code = wifiDevice->Disconnect();
std::cout << "DisConnect code=" << ParseCode(code) << std::endl;
}
确认连接状态:
获取、打印连接状态。同样没有额外的内容,只需要调用IsConnected接口。
void WifiTest::IsConnected()
{
std::cout << "IsConnected, please wait\n";
InitCommon();
auto isConnect = wifiDevice->IsConnected();
std::cout << "IsConnected " << isConnect << std::endl;
}
提取一个函数返回状态打印函数:
在以上的函数中都可以看见使用了ParseCode(code)。code变量是一个errcode类的对象,那么自然可以想到ParseCode函数是“翻译”返回值结果的函数。
会显示给用户的打印,如果想让用户了解打印的含义,那么就需要根据返回值输出不同的字符串。这一功能在所有需要打印运行情况的函数中使用,自然将其提取为一个函数是良好的编程习惯。
std::string WifiTest::ParseCode(ErrCode code)
{
static const std::string wifiOptSts[] = {
"WIFI_OPT_SUCCESS", "WIFI_OPT_FAILED", "WIFI_OPT_NOT_SUPPORTED",
"WIFI_OPT_INVALID_PARAM", /* invalid params */
"WIFI_OPT_FORBID_AIRPLANE", /* forbid when current airplane opened */
"WIFI_OPT_FORBID_POWSAVING", /* forbid when current powersaving opened */
"WIFI_OPT_PERMISSION_DENIED", /* permission denied */
"WIFI_OPT_OPEN_FAIL_WHEN_CLOSING", /* open failed when current is closing */
"WIFI_OPT_OPEN_SUCC_WHEN_OPENED", /* open success when current has been opened */
"WIFI_OPT_CLOSE_FAIL_WHEN_OPENING", /* close failed when current is opening */
"WIFI_OPT_CLOSE_SUCC_WHEN_CLOSED", /* close success when current has been closed */
"WIFI_OPT_STA_NOT_OPENED", /* sta service not opened */
"WIFI_OPT_SCAN_NOT_OPENED", /* scan service not opened */
"WIFI_OPT_AP_NOT_OPENED", /* ap service not opened */
"WIFI_OPT_INVALID_CONFIG", /* invalid config */
"WIFI_OPT_P2P_NOT_OPENED", /* p2p service not opened */
"WIFI_OPT_P2P_MAC_NOT_FOUND", "WIFI_OPT_P2P_ERR_MAC_FORMAT", "WIFI_OPT_P2P_ERR_INTENT",
"WIFI_OPT_P2P_ERR_SIZE_NW_NAME", "WIFI_OPT_MOVING_FREEZE_CTRL", /* moving freeze scanning control */
};
return wifiOptSts[code];
}
如此一来就得到了一个WifiTest类的完整结构:
class WifiTest
{
public:
void EnableWifi();
void DisableWifi();
void ShowWifiInfo();
void Connect_Customize();
void ConnectByNumber();
void DisConnect();
private:
void InitCommon();
std::string ParseCode(ErrCode code);
private:
std::unique_ptr<WifiScan> wifiScan;
std::unique_ptr<WifiDevice> wifiDevice;
std::vector<WifiScanInfo> result;
};
在main函数中调用完成的实现
现在只需要在main函数中创建好WifiTest类的对象,就可以通过它调用想要的功能了:
int main()
{
using namespace OHOS::Wifi;
std::cout << "Wificontroler is runnning..." << std::endl;
WifiTest wifiTest;
std::cout << "Input 1 to Turn WiFi on " << std::endl;
int i = 1;
while (1)
{
std::cout << "input:";
std::cin >> i;
std::cout << "inputResult:" << i << std::endl;
switch (i)
{
case 0:
return 0;
case 1:
wifiTest.EnableWifi();
break;
case 2:
wifiTest.DisableWifi();
break;
case 3:
wifiTest.ShowWifiInfo();
break;
case 4:
wifiTest.Connect_Customize();
break;
case 5:
wifiTest.ConnectByNumber();
break;
case 6:
wifiTest.DisConnect();
break;
case 7:
wifiTest.IsConnected();
break;
default:
break;
}
}
return 0;
}
现在,所有的功能都已经在程序中实现,我们只需要再完成编译组织文件和配置文件就可以进行编译和烧录了。因为我们计划在Hi3516DV300上运行该程序,因此只要参照OpenHarmony的入门指引就可以完成设置。
结语
foundation/communication/wifi路径下有很多可用的头文件和接口,本文只选择了其中一部分接口进行使用演示,进行wifi功能的初步指引。同路径下有很多近似的接口,如果希望进一步了解wifi相关的功能,可参考本文使用的接口对比探究其中的区别。
本程序作为一个简单的命令行程序,省略了很多需要进行文件读写等操作的功能(如记录wifi信息以备自动连接)和很多边界检查等安全代码规范,想要进一步研究wifi功能,可以尝试实现这些被省略的功能。