OpenHarmony3.1-WIFI子系统之STA模式源码解析 原创 精华
作者:李威
简介
Wi-Fi是WLAN具体采用的技术,也是目前WLAN的主流技术。Wi-Fi采用的技术是IEEE80211系列协议,IEEE (Institute of Electrical and Electronics Engineers)是美国电气和电子工程师协会的简称。
STA模式:Station,类似于无线终端,sta本身并不接受无线的接入,它可以连接到AP(AccessPoint),一般无线网卡即工作在该模式,这是Wifi最基本的工作模式。
Wifi子系统架构
<center>Wifi子系统架构图</center>
Wifi架构解析
Wi-Fi App:
主要是开发者自行开发Wi-Fi相关功能的应用。通过调用Wifi SDK对外提供的API实现对设备Wi-Fi的控制及其功能实现。这一层平台将会提供相关的API调用示例,以供参考。
Wi-Fi Native JS:
JS层使用NAPI机制开发,连接APP层与Framework层,将wifi功能封装成JS接口提供给应用调用,并同时支持Promise和Callback异步回调。
Wi-Fi Framework:
Wi-Fi核心功能实现。直接为上层应用提供服务。根据其工作模式的不同分为四大业务服务模块,分别是STA服务、AP服务、P2P服务、Aware服务,同时DHCP功能。
Wi-Fi Hal:
为FrameWork层操作Wi-Fi硬件提供统一的接口服务,实现应用框架与硬件操作的分离。主要包括Hal适配器及扩展Hal模块及Wi-Fi硬件厂家提供的二进制库模块。
WPA Supplicant:
包含wpa_supplicant和hosapd两个子模块,wpa_supplicant和hostapd实现了定义好的驱动API,对外提供控制接口,框架就能通过其控制接口来实现Wifi的各种操作。wpa_supplicant支持STA及P2P模式,hostapd则支持AP模式。
HDF:
HDF 驱动框架主要由驱动基础框架、驱动程序、驱动配置文件和驱动接口这四个部分组成,实现WIfi驱动功能,加载驱动,驱动接口部署等。
Wi-Fi Kernel:
包含Wi-Fi 驱动,包括了对设备的一些基本的读写操作由Wi-Fi驱动移植人员编译进内核。
代码结构
关键模块实现
IPC通信
Native JS和Wifi框架通过IPC(Inter-Process Communication)进行通信,实现接口调用及事件传递。IPC通信采用客户端-服务器(Client-Server)模型,服务请求方(Client)可获取提供服务提供方(Server)的代理 (Proxy),并通过此代理读写数据来实现进程间的数据通信。
在OpenHarmony中,首先服务端注册系统能力(System Ability)到系统能力管理者(System Ability Manager,缩写SAMgr),SAMgr负责管理这些SA并向客户端提供相关的接口。客户端要和某个具体的SA通信,必须先从SAMgr中获取该SA的代理,然后使用代理和服务端通信,Proxy表示服务请求方,Stub表示服务提供方。
Wifi系统对不同模式各实现了一套Proxy-Stub类,STA模式分别是WifiDeviceProxy和WifiDeviceStub,WifiScanProxy和WifiScanStub,对扫描和其他STA流程进行了分离。
<center>IPC通信图</center>
由图所示,首先WifiDeviceImpl是Native JS层WifiDevice的实现类,是服务请求方,作为IPC服务客户端,WifiDeviceProxy作为代理类,通过它来向服务方发起请求;WifiDeviceStub作为服务方接收请求并处理;WifiDeviceServiceImpl继承WifiDeviceStub类和SystemAbility类,是IPC通信服务方的具体实现。
以WifiDeviceProxy和WifiDeviceStub为例,分别从代理方和服务方说明实现过程。
WiFi Native JS作为服务请求方发起流程时,WifiDeviceImpl初始化通过Init函数构造代理WifiDeviceProxy,步骤如下:
首先,获取SAMgr。
然后,通过SAMgr及相应的ability id获取到对应SA的代理IRemoteObject。
最后,使用IRemoteObject构造WifiDeviceProxy。
目录:foundation/communication/wifi/interfaces/innerkits/native_cpp/wifi_standard/src/wifi_device_impl.cpp
我们以打开WiFi为例看看其调用过程,客户端发起请求后,接口层提供WifiDeviceProxy作为代理发送打开WiFi请求:
目录:foundation/communication/wifi/interfaces/innerkits/native_cpp/wifi_standard/src/wifi_device_proxy.cpp
WifiDeviceProxy继承自IRemoteProxy,封装WiFi Station模式相关业务函数,调用SendRequest将请求发到服务端Stub。
WiFi框架提供服务方WifiDeviceStub,继承IRemoteStub,实现了IWifiDevice接口类中未实现的方法,并重写了OnRemoteRequest方法。Proxy请求方发来的请求就在OnRemoteRequest中处理。
目录:foundation/communication/wifi/services/wifi_standard/wifi_framework/wifi_manage/wifi_device_stub.cpp
WifiDeviceStub对proxy请求事件和相应处理函数进行了映射。
根据映射关系调用其OnEnableWifi方法:
这里调用EnableWifi(),其具体实现在子类WifiDeviceServiceImpl。
WifiDeviceServiceImpl作为IPC通信服务方的具体实现,如以下代码所示,WifiDeviceServiceImpl通过MakeAndRegisterAbility将WifiDeviceServiceImpl实例注册到SAMgr。接下来,服务请求方就可以通过从SAMgr获取代理来和服务提供方通信。
状态机管理
Wifi框架维护了四个状态机,分别是sta statemachine、scan statemachine、p2p statemachine和ap statemachine。Wifi各个模式工作流程中会涉及到各个不同的阶段,需要对不同阶段的状态进行管理。对于Native JS通过代理发送到Wifi框架的请求以及HAL回送的WPA Supplicant的响应,需要在相应模式的相应状态下做合适的处理。
本章仅介绍Wifi基本模式STA和Scan的状态机。
<center>STA状态机树状图</center>
STA状态机维护了wifi打开、关闭、连接、获取IP及漫游的状态及切换。Wifi打开时,会启动staService,构造sta statemachine并初始化。如STA状态机树状图所示,sta statemachine在初始化时,会创建状态树,创建子状态必须保证相应的父状态被创建。当迁移到子状态,子状态激活,也就是执行GoInState后,其父节点会同时处于激活状态,不会调用GoOutState,子节点共同需要处理的事件或者不关心的事件由父状态处理,子状态只负责处理自己感兴趣的消息。
<center>STA状态迁移图</center>
比如wifi打开时,状态机从InitState迁移到目标状态SeparatedState,这时处于激活状态的有WpaStartedState及LinkState。
当wifi关闭时,WpaStartedState处理WIFI_SVR_CMD_STA_DISABLE_WIFI事件,关闭wifi,回到InitState状态。
当用户连接网络时,LinkState处理CMD_START_CONNECT_SELECTED_NETWORK事件进行连接网络的动作,收到WIFI_SVR_CMD_STA_NETWORK_CONNECTION_EVENT后,状态迁移到GetIpState状态,同时ApLinkedState状态处于激活。在GetIpState状态,如果IP地址分配成功,则进入LinkedState。如果分配失败,则回到SeparatedState。
不管是在GetIpState还是在LinkedState,只要收到断开网络请求WIFI_SVR_CMD_STA_DISCONNECT,都由ApLinkedState处理,进入SeparatedState。
<center>Scan状态机树状图</center>
Scan状态机维护了wifi普通扫描,pno扫描(硬件扫描和软件扫描)的状态及切换过程。
这里对PNO扫描稍加说明,PNO扫描即Preferred Network Offload,用于系统在休眠的时候连接WiFi,当手机休眠时,存在已经保存的网络并且没有连接时,进行PNO扫描,只扫描已保存的网络。PNO模式能让设备在熄屏时通过搜索最近连接过的Wifi网络,从而优先连接至Wifi网络,达到延长续航时间并且减少手机数据流量消耗的目的。
Wifi打开后,启动scanService同时构造scan statemachine并初始化,与sta statemachine相同,scan statemachine按照Scan状态机树状图所示创建状态机各个状态。
<center>Scan状态机迁移图
scan statemachine初始化时设置状态为InitState,随后发送CMD_SCAN_PREPARE给InitState,进入HardwareReady状态。
处于HardwareReady状态时Native JS调用scan接口进行扫描,HardwareReady会收到CMD_START_COMMON_SCAN消息进入CommonScanning状态,扫描成功、失败或者超时后进入CommonScanUnworked状态,如果这时候再次发起扫描则会回到CommonScanning状态。
处于HardwareReady状态时发起PNO扫描时,首先判断系统是否支持硬件PNO扫描,如果支持,则进入PnoScanHardware状态,向底层下发PNO扫描命令。
在PnoScanHardware状态,如果收到PNO扫描结果通知,并且NeedCommonScanAfterPno为true,则进入CommonScanAfterPno状态,等收到CMD_START_COMMON_SCAN进入CommonScanning,或者收到普通扫描命令,进入HardwareReady状态准备进行普通扫描,否则一直处于PNO硬件扫描状态。
当发起PNO扫描后,如果系统不支持硬件PNO扫描,则进入PnoScanSoftware状态,启动一次软件PNO扫描,进入PnoSwScanning状态,扫描成功或者失败或者超时后进入PnoSwScanFree状态。
在PnoSwScanFree状态,收到CMD_START_COMMON_SCAN命令,则回到HardwareReady状态准备进行一次普通扫描;如果收到PNO扫描命令则回到PnoSwScanning状态。
STA业务代码流程
前面讲过,Native Js发起调用,经过IPC通信,调用到WifiDeviceServiceImpl作为服务端的实现。
Wifi打开流程
<center>Wifi打开流程时序图
目录:foundation/communication/wifi/services/wifi_standard/wifi_framework/wifi_manage/wifi_device_service_impl.cpp
这里主要做了四件事情:
- 加载STA服务,在WifiServiceManager中根据服务名称WIFI_SERVICE_STA,调用LoadStaService方法加载STA服务;
- 构建StaService,获取接口IStaService实例;
- 注册回调函数;
- 调用StaService打开Wifi。
StaService是Wifi框架层Station服务的主要实现,通过创建StaStateMachine和StaMonitor对Wifi Station命令和事件进行处理。
目录:foundation/communication/wifi/services/wifi_standard/wifi_framework/wifi_manage/wifi_sta/sta_service.cpp
StaService首先在调用之前,首先会初始化:
初始化主要会做四件事:
1.构造StaStateMachine状态机
2.注册回调函数
3.构造StaMonitor
4.构造自动连接服务
这些准备工作做完后,就是真正执行EnableWifi的流程。
目录:foundation/communication/wifi/services/wifi_standard/wifi_framework/wifi_manage/wifi_sta/sta_state_machine.cpp
StaStateMachine在初始化时设置初始状态为InitState ,”SetFirstState(pInitState)“,故接收到WIFI_SVR_CMD_STA_ENABLE_WIFI在InitState状态中处理:
WifiStaHalInterface是框架调用Wifi Hal的接口。
框架与Wifi Hal的通信主要是借助IDL Client组件,采用RPC服务实现其交互。
无论是框架调用Wifi Hal还是Wifi Hal回调消息到框架,都要经过IDL Clinet,从架构图来看,它属于Wifi框架的一部分,我们先来看看它。
IDL客户端初始化:
目录:foundation/communication/wifi/services/wifi_standard/wifi_framework/wifi_manage/idl_client/wifi_base_hal_interface.cpp
IDL客户端初始化主要做了两件事:
1.构造WifiIdlClient实例
2.初始化RPC通信客户端
有了WifiIdlClient实例,调用其StartWifi()。
目录:foundation/communication/wifi/services/wifi_standard/wifi_framework/wifi_manage/idl_client/wifi_sta_hal_interface.cpp
通过wifi_idl_client向wifi hal发起RPC调用“Start”。
目录:foundation/communication/wifi/services/wifi_standard/wifi_framework/wifi_manage/idl_client/wifi_idl_client.cpp
目录:foundation/communication/wifi/services/wifi_standard/wifi_framework/wifi_manage/idl_client/idl_interface/i_wifi.c
这里主要操作是获取RPC调用客户端,然后通过RemoteCall(client)方法发起远程调用。
Wifi Hal作为RPC服务端,启动后调用InitRpcFunc初始化RPC函数,然后CreateRpcServer创建RPC服务,最后调用RunRpcLoop循环读取远程调用信息,处理客户端请求。
目录:foundation/communication/wifi/services/wifi_standard/wifi_hal/main.c
InitRpcFunc中Map了“Start”消息的处理函数,PushRpcFunc(“Start”, RpcStart)。
目录:foundation/communication/wifi/services/wifi_standard/wifi_hal/wifi_hal_crpc_server.c
根据配对关系调用到RpcStart,
目录:foundation/communication/wifi/services/wifi_standard/wifi_hal/wifi_hal_crpc_sta.c
调用Start()实际操作实现在wifi_hal_sta_interface的Start函数,调用完成。
目录:foundation/communication/wifi/services/wifi_standard/wifi_hal/wifi_hal_sta_interface.c
主要做了三步操作:
- start supplicant
命令:wpa_supplicant -iglan0 -g/data/misc/wifi/sockets
- Add a new interface wlan0
命令:interface_add wlan0 /data/misc/wifi/wpa_supplicant/wpa_supplicant.conf
-
构造并初始化WifiWpaStaInterface,封装了wpa_supplicant关于STA的操作命令。
以上三步成功后,RPC调用返回WIFI_HAL_SUCCESS,日志会打印"Start wifi successfully"。
StaStateMachine在EnableWifi成功后,设置wifistate为ENABLED并执行OnStaOpenRes回调:
回调在WifiManager中处理,广播wifi状态改变消息,构造ScanService并初始化,初始化过程做了这几件事:
- 构造ScanStateMachine并初始化,调用EnrollScanStatusListener绑定Scan状态上报事件的处理函数。
- 构造ScanMonitor并初始化,在初始化函数中调用RegisterSupplicantEventCallback,注册supplicant事件回调。
目录:foundation/communication/wifi/services/wifi_standard/wifi_framework/wifi_manage/wifi_scan/scan_service.cpp
ScanStateMachine初始化状态为InitState,然后接收到CMD_SCAN_PREPARE消息,调用LoadDriver();
LoadDriver()做了两件事情:
- ScanStateMAchine切换状态为为HardwareReadyState
- 上报SCAN_STARTED_STATUS状态
然后就可以发起扫描的流程了。
扫描流程
<center>Wifi扫描流程时序图
应用调用Native JS的Scan接口,会通过获取WifiScan实例,调用C++接口,发起IPC通信,通过WifiScanProxy向服务框架发送WIFI_SVR_CMD_FULL_SCAN请求。WifiScanStub作为服务端接收到请求,WifiScanServiceImpl作为服务端的主要实现。
目录:foundation/communication/wifi/services/wifi_standard/wifi_framework/wifi_manage/wifi_scan_service_impl.cpp
构造ScanService,通过IScanService接口调用其Scan方法:
这里首先构造scanConfig,然后调用SingleScan向ScanStateMachine发送CMD_START_COMMON_SCAN命令并携带scanConfig,
ScanService初始化完成后,ScanStateMachine处于HardwareReady是可以发起扫描的激活状态,
目录:foundation/communication/wifi/services/wifi_standard/wifi_framework/wifi_manage/wifi_scan/scan_state_machine.cpp
接收CMD_START_COMMON_SCAN并处理,进行获取扫描参数的操作,并校验Scan类型是否合法,之后转换扫描参数,通过RPC调用HAL的scan操作,
HAL得到scan配置参数后,通过wpaCliCmdScan向supplicant发送SCAN命令,
目录:foundation/communication/wifi/services/wifi_standard/wifi_hal/wifi_hal_sta_interface.c
扫描成功,HAL返回WIFI_HAL_SUCCESS。
调用成功StaStateMachine切换状态为CommonScanningState。
扫描结果获取
<center>扫描结果获取流程时序图
Supplicant执行扫描成功后,调用WifiHalCbNotifyScanEnd(WPA_CB_SCAN_OVER_OK)通知扫描成功。ScanMonitor执行回调,向ScanStateMachine发送SCAN_RESULT_EVENT事件:
ScanStateMachine此时状态为CommonScanning,接收消息并处理,发起RPC远程调用wifi hal获取扫描结果。
WifiHal返回扫描结果给ScanStateMachine后,ScanStateMachine构造ScanStatusReport,包含scanInfoList和status为COMMON_SCAN_SUCCESS,通过scanStatusReportHandler上报,在ScanService::HandleScanStatusReport中处理。
ScanService拿到扫描结果,主要做的事是调用WifiSettings的SaveScanInfoList(filterScanInfo),将扫描结果保存。
之后,应用调用native js的GetScanInfos接口,通过WifiScanServiceImpl调用WifiSettings的GetScanInfoList获取到保存的扫描结果。
扫描结果中包含的信息如下,一般常用到的信息有:
Bssid - 扫描到的AP的mac地址
Ssid - 扫描到的AP的标识名称
Band - 支持频段为2.4G还是5G
securityType - 安全类型: OPEN/WEP/PSK/SAE
连接流程
<center>Wifi连接流程时序图
应用获取到扫描结果后,选择一个Wifi网络进行连接,调用Native Js的connectToDevice接口调用到WifiDevice,然后通过IPC通信,WifiDeviceProxy作为客户端代理发送WIFI_SVR_CMD_CONNECT2_TO消息,WifiDeviceStub作为服务端接收到消息后调用IPC服务的实现类WifiDeviceServiceImpl进行发起连接流程。
目录:foundation/communication/wifi/services/wifi_standard/wifi_framework/wifi_manage/wifi_device_service_impl.cpp
获取StaService的接口实例,调用其ConnectToDevice方法。
目录:foundation/communication/wifi/services/wifi_standard/wifi_framework/wifi_manage/wifi_sta/sta_service.cpp
StaService首先调用AddDeviceConfig,在这个函数中主要做了两件事:
1).调用GetNextNetworkId,通过HAL向supplicant发送ADD_NETWORK命令,得到netwrok id,保存在WifiDeviceConfig。
2)调用ConvertDeviceCfg,在StaStateMachine中将网络配置参数转换为idl参数,然后调用HAL的SetDeviceConfig函数,向supplicant发送SET_NETWORK命令。
StaService在调用AddDeviceConfig得到networkid并且设置配置参数到supplicant成功后,向StaStateMachine发送“WIFI_SVR_CMD_STA_CONNECT_NETWORK”消息,
目录:foundation/communication/wifi/services/wifi_standard/wifi_framework/wifi_manage/wifi_sta/sta_state_machine.cpp
StaStateMachine接收WIFI_SVR_CMD_STA_CONNECT_NETWORK消息调用DealConnectToUserSelectedNetwork
StartConnectToNetwork:
这里主要做了三个操作:发起RPC调用EnableNetwork,Connect,SaveDeviceConfig。
目录:foundation/communication/wifi/services/wifi_standard/wifi_hal/wifi_hal_sta_interface.c
EnableNetwork:
Connect:
SaveDeviceConfig:
这里按调用先后向supplicant发送EnableNetwork、SELECT_NETWORK以及SAVE_CONFIG命令,supplicant根据收到的命令完成AP的连接管理。
Supplicant连接成功后,回送WPA_EVENT_CONNECTED事件,调用WifiHalCbNotifyConnectChanged(WPA_CB_CONNECTED,id,pMacPos)通知连接成功,经过Hal再由StaMonitor的处理回调,发送消息WIFI_SVR_CMD_STA_NETWORK_CONNECTION_EVENT给StaStateMachine,调用DealConnectionEvent方法来处理连接事件,状态机进入getIpState状态,获取ip地址,静态ip或者dhcp动态获取成功后,继续调用StaNetworkCheck检查网络连接状态。
Wifi-STA接口说明及使用
接口说明
WLAN基础功能由@ohos.wifi类提供,其接口(JS接口)说明如下。
表 1 WLAN功能接口(JS接口)
接口名 | 描述 |
---|---|
function enableWifi(): boolean | 打开WLAN。 |
function disableWifi(): boolean | 关闭WLAN。 |
function isWifiActive(): boolean | 查询WLAN是否处于打开状态。 |
function scan(): boolean | 发起WLAN扫描。 |
function getScanInfos(): Promise<Array<WifiScanInfo>>;<br/>function getScanInfos(callback: AsyncCallback<Array<WifiScanInfo>>): void; | 获取WLAN扫描结果,接口可采用promise或callback方式调用。 |
function addDeviceConfig(config: WifiDeviceConfig): Promise<number>;<br/>function addDeviceConfig(config: WifiDeviceConfig, callback: AsyncCallback<number>): void; | 添加WLAN热点的配置信息,接口可采用promise或callback方式调用。 |
function connectToNetwork(networkId: number): boolean | 连接到WLAN网络。(一般为隐藏热点的连接) |
function connectToDevice(config: WifiDeviceConfig): boolean | 连接到WLAN网络。 |
function disconnect(): boolean | 断开WLAN连接。 |
function getSignalLevel(rssi: number, band: number): number | 获取WLAN信号强度。 |
function getLinkedInfo(): Promise<WifiLinkedInfo>;<br/>function getLinkedInfo(callback: AsyncCallback<WifiLinkedInfo>): void; | 获取WLAN连接信息接口可采用promise或callback方式调用。 |
function isConnected(): boolean; | 查询是否是连接状态。 |
function getSupportedFeatures(): number; | 获取设备支持的feature。 |
function isFeatureSupported(featureId: number): boolean; | 查询设备是否支持指定feature。 |
function getDeviceMacAddress(): string[]; | 获取MAC地址。 |
function getIpInfo(): IpInfo; | 获取IP。 |
function getCountryCode(): string; | 获取国家码。 |
function reassociate(): boolean; | 重新连接当前网络。 |
function reconnect(): boolean; | 重新连接当前Wifi。 |
function getDeviceConfigs(): Array<WifiDeviceConfig>; | 获取现存的Wifi列表。 |
function updateNetwork(config: WifiDeviceConfig): number; | 更新指定的Wifi配置。 |
function disableNetwork(netId: number): boolean; | 关闭指定的网络。 |
function removeAllNetwork(): boolean; | 移除所有的网络。 |
function removeDevice(id: number): boolean; | 删除指定ID的网络。 |
表 1 WLAN事件接口(JS接口)
接口名 | 描述 |
---|---|
function on(type: “wifiStateChange”, callback: Callback<number>): void;<br/>function off(type: “wifiStateChange”, callback?: Callback<number>): void; | Wifi状态监听事件 |
function on(type: “wifiConnectionChange”, callback: Callback<number>): void;<br/>function off(type: “wifiConnectionChange”, callback?: Callback<number>): void; | Wifi连接状态监听 |
function on(type: “wifiScanStateChange”, callback: Callback<number>): void;<br/>function off(type: “wifiScanStateChange”, callback?: Callback<number>): void; | Wifi扫描状态监听 |
function on(type: “wifiRssiChange”, callback: Callback<number>): void;<br/>function off(type: “wifiRssiChange”, callback?: Callback<number>): void; | Wifi信号监听 |
更多原创内容请关注:深开鸿技术团队
入门到精通、技巧到案例,系统化分享HarmonyOS开发技术,欢迎投稿和订阅,让我们一起携手前行共建HarmonyOS生态。
源码大佬,学习一下。