【iOS】BLE-WiFi 双模设备先扫描支持的WiFi列表后配网
背景
设备无法联网的一个主要原因是它们无法连接到路由器。而设备连不上路由器的最常见原因,是设备不兼容当前路由器发出的WiFi。这种情况可能是因为路由器的待配网WiFi是2.4G的,也可能是因为路由器使用了较新的WiFi6或WiFi7标准,而设备无法适应这些新的WiFi技术。
如果能通过设备扫描当前环境下可以进行配网的WiFi,并且返回WiFi的信号,则可以规避一大部分以上问题。
优势
用户体验提升:通过扫描WiFi列表,设备可以展示周围可用的网络,用户无需手动输入SSID。这使得连接过程更加直观和简便,尤其对于不熟悉技术的用户来说。
减少错误连接:扫描WiFi可以确保用户选择的网络确实是当前环境中的有效网络,从而减少了手动输入SSID时可能出现的拼写错误或连接错误的可能性。
提高兼容性识别:设备在扫描后可以预先识别出哪些网络是不兼容的(例如,WiFi 5设备无法连接到仅支持WiFi 6的网络),从而避免用户尝试连接不支持的网络。
信号强度判断:通过扫描,设备可以提供每个网络的信号强度信息,帮助用户选择信号最强、最稳定的网络连接,以提高连接质量和稳定性。
支持网络选择:如果用户环境中有多个WiFi网络,扫描结果可以帮助用户在多个选项中选择最佳网络,比如一个信号更强或使用更少的网络。
安全性增强:通过显示加密类型,用户可以选择配备较强加密措施的安全WiFi,避免连接到不安全的网络。
代码示例
本功能仅适用于Tuya OS 3.8.0版本以上的设备,需要设备支持扫描WiFi列表才可使用。
查询WiFi列表API & 开始配网API
Code: Select all
@interface ThingSmartBLEWifiActivator : NSObject
/// 查询WiFi列表
/// @param UUID 设备uuid
/// @param success 连接成功回调
/// @param failure 连接失败回调
- (void)connectAndQueryWifiListWithUUID:(NSString *)UUID
success:(ThingSuccessHandler)success
failure:(ThingFailureError)failure;
/// 开始配网 (查询完WiFi列表后会保持蓝牙连接,通过此API进行配网)
/// @param UUID 设备uuid
/// @param token 配网token
/// @param ssid 待配网ssid
/// @param pwd 密码
/// @param timeout 配网超时时间
- (void)pairDeviceWithUUID:(NSString *)UUID token:(NSString *)token ssid:(NSString *)ssid pwd:(NSString *)pwd timeout:(long)timeout;
@end
查询WiFi列表回调
Code: Select all
@protocol ThingSmartBLEWifiActivatorDelegate <NSObject>
- (void)bleWifiActivator:(ThingSmartBLEWifiActivator *)activator didReceiveBLEWifiConfigDevice:(nullable ThingSmartDeviceModel *)deviceModel error:(nullable NSError *)error;
@optional
/// 设备配网状态回调 (如果不是待配网状态时触发)
- (void)bleWifiActivator:(ThingSmartBLEWifiActivator *)activator notConfigStateWithError:(NSError *)error;
/// WiFi列表扫描回调
- (void)bleWifiActivator:(ThingSmartBLEWifiActivator *)activator didScanWifiList:(NSArray *)wifiList uuid:(NSString *)uuid error:(NSError *)error;
@end
完整示例
Code: Select all
class OptDualModeViewController: UIViewController {
var startScanBtn = UIButton(type: .system)
var stopScanBtn = UIButton(type: .system)
var tableView: UITableView!
var bleMgr = ThingSmartBLEManager.sharedInstance()
var stashUuids = [String]()
var deviceList = [(String, ThingBLEAdvModel)]()
var req = ThingSmartActivatorDiscoveryRequest()
var bleWifiMultier = ThingSmartBLEWifiMultier()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
startScanBtn.frame = CGRect(x: 100, y: 150, width: 100, height: 20)
startScanBtn.setTitle("Start Scan", for: .normal)
startScanBtn.addTarget(self, action: #selector(startScan), for: .touchUpInside)
view.addSubview(startScanBtn)
stopScanBtn.frame = CGRect(x: 200, y: 150, width: 100, height: 20)
stopScanBtn.setTitle("Stop Scan", for: .normal)
stopScanBtn.addTarget(self, action: #selector(stopScan), for: .touchUpInside)
view.addSubview(stopScanBtn)
tableView = UITableView(frame: CGRect(x: 0, y: 220, width: view.frame.width, height: view.frame.height - 250), style: .plain)
tableView.delegate = self
tableView.dataSource = self
tableView.register(UITableViewCell.classForCoder(), forCellReuseIdentifier: "cell")
view.addSubview(tableView)
}
@objc func startScan() {
bleMgr.delegate = self
bleMgr.startListening(false)
}
@objc func stopScan() {
bleMgr.stopListening(false)
}
func stopConfigWifi() {
bleWifiMultier.stopConfigWifi()
}
}
extension OptDualModeViewController: ThingSmartBLEManagerDelegate {
func didDiscoveryDevice(withDeviceInfo deviceInfo: ThingBLEAdvModel) {
let bleType = deviceInfo.bleType
if bleType == ThingSmartBLETypeUnknow ||
bleType == ThingSmartBLETypeBLE ||
bleType == ThingSmartBLETypeBLESecurity ||
bleType == ThingSmartBLETypeBLEPlus ||
bleType == ThingSmartBLETypeBLEZigbee ||
bleType == ThingSmartBLETypeBLEBeacon {
print("Please use BLE to pair: %@", deviceInfo.uuid ?? "")
return
}
if (!deviceInfo.isSupportQueryWifiList) {
print("Device does not support wifi list query: %@", deviceInfo.uuid ?? "")
return;
}
if (!stashUuids.contains(deviceInfo.uuid)) {
stashUuids.append(deviceInfo.uuid)
bleMgr.queryDeviceInfo(withUUID: deviceInfo.uuid, productId: deviceInfo.productId) { result in
if let r = result, let name = r["name"] as? String {
self.deviceList.append((name, deviceInfo))
self.tableView.reloadData()
}
}
}
}
}
extension OptDualModeViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell")!
let (name, _) = deviceList[indexPath.row]
cell.textLabel?.text = name
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return deviceList.count
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let (name, deviceInfo) = deviceList[indexPath.row]
let vc = ChooseWifiViewController(uuid: deviceInfo.uuid)
vc.title = name
navigationController?.pushViewController(vc, animated: true)
}
}
struct WifiModel: Codable {
let ssid: String
let rssi: Int
let sec: Int
}
class ChooseWifiViewController: UIViewController, ThingSmartBLEWifiActivatorDelegate {
var uuid: String?
var tableView: UITableView!
var wifiList = Array<WifiModel>()
let wifiActer = ThingSmartBLEWifiActivator.sharedInstance()
init(uuid: String? = nil) {
self.uuid = uuid
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
view.backgroundColor = .white
tableView = UITableView(frame: CGRect(x: 0, y: 120, width: view.frame.width, height: view.frame.height - 140), style: .plain)
tableView.delegate = self
tableView.dataSource = self
tableView.register(UITableViewCell.classForCoder(), forCellReuseIdentifier: "cell")
view.addSubview(tableView)
if let uuid = uuid {
SVProgressHUD.show()
wifiActer.bleWifiDelegate = self
wifiActer.connectAndQueryWifiList(withUUID: uuid) {
} failure: { _ in
}
} else {
SVProgressHUD.showError(withStatus: "uuid is invalid")
}
}
func startActiveDevice(ssid: String, pwd: String) {
guard let uuid = uuid else {return}
ThingSmartActivator.sharedInstance()?.getTokenWithHomeId(Home.current!.homeId) { token in
guard let t = token else {return}
self.wifiActer.pairDevice(withUUID: uuid, token: t, ssid: ssid, pwd: pwd, timeout: 120)
SVProgressHUD.show()
} failure: { _ in
}
}
func bleWifiActivator(_ activator: ThingSmartBLEWifiActivator, didReceiveBLEWifiConfigDevice deviceModel: ThingSmartDeviceModel?, error: Error?) {
SVProgressHUD.dismiss()
if error == nil && deviceModel != nil {
SVProgressHUD.showSuccess(withStatus: "Device active success!!!")
self.navigationController?.popToRootViewController(animated: true)
}
}
func bleWifiActivator(_ activator: ThingSmartBLEWifiActivator, didScanWifiList wifiList: [Any], uuid: String, error: Error) {
SVProgressHUD.dismiss()
if (error != nil) { print("\(error)") }
if let wifis = wifiList as? [[String:Any]] {
for w in wifis {
do {
let jsonData = try JSONSerialization.data(withJSONObject: w, options: [])
let decoder = JSONDecoder()
let wifiModel = try decoder.decode(WifiModel.self, from: jsonData)
self.wifiList.append(wifiModel)
self.tableView.reloadData()
} catch {
print("Error decoding JSON: \(error)")
}
}
self.tableView.reloadData()
}
}
}
extension ChooseWifiViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return wifiList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell")!
let wifiModel = self.wifiList[indexPath.row]
cell.textLabel?.text = wifiModel.ssid
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let wifiModel = self.wifiList[indexPath.row]
let alertController = UIAlertController(title: wifiModel.ssid, message: nil, preferredStyle: .alert)
alertController.addTextField { textField in
textField.placeholder = "Enter your Password"
}
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
alertController.addAction(cancelAction)
let okAction = UIAlertAction(title: "OK", style: .default) { [weak self] _ in
if let text = alertController.textFields?.first?.text {
print("Entered Password: \(text)")
self?.startActiveDevice(ssid: wifiModel.ssid, pwd: text)
}
}
alertController.addAction(okAction)
present(alertController, animated: true, completion: nil)
}
}
小结
通过设备扫描的WiFi列表进行配网,可以更精准地识别当前WiFi是否适用于配网设备。这不仅提升了配网体验,还显著提高了配网的成功率,从而减少用户的困扰和烦恼。