Page 1 of 1

【iOS】BLE-WiFi 双模配网最佳实践

Posted: 2024年 Nov 26日 17:49
by yuguo

什么是双模配网?

通常我们将支持BLE、WiFi双模组的设备称为双模设备或者蓝牙双模设备。
设备重置后对外发送蓝牙广播包,App在捕获到设备的蓝牙广播包后可以显示出对应的设备,然后选择指定设备并输入WiFi配网信息,此时App会和设备建立蓝牙连接,通过蓝牙通道完成数据传输。以上这个过程我们称之为双模配网。

双模配网的优势?

WiFi设备一般的配网方式有三种:EZ配网、AP配网、双模配网

EZ配网对于用户来说只需要输入路由器的SSID和Password即可一键完成配网,但由于路由器的兼容性问题、iOS本地网络权限是否打开、设备是否成功被重置等等因素都影响着EZ配网,虽然操作简单,但如果配网失败了对于用户来说就大概率不知道怎么办了。

AP配网需要用户先重置设备到AP模式,然后在手机上切换到系统设置页连接设备发出的AP热点,再回到App进行配网。因为通过AP建立了稳定的连接,数据的传输得到了可靠的保障,所以只要进行正确的引导,配网成功率还是颇为可观的。但是AP配网的操作过程过于复杂,对于新手用户的理解成本过高。

双模配网能通过蓝牙扫描到具体的设备,明确的知道设备是否被成功重置,并且可以选择指定的待配网设备并且在配网过程能建立稳定的数据传输通道。

概念澄清

配网成功:
设备成功在云端激活成功,并且连接MQTT服务成功(成功连云上线),才认定为配网成功。

轮询:
App在向设备发送配网信息后会调用云端接口开启设备轮询,轮询到设备且设备的在线状态为true时才会触发配网成功回调。

代码示例

几个关键类

ThingSmartBLEManager

蓝牙管理
用于触发蓝牙扫描、连接等功能 (双模配网仅需关注蓝牙扫描)

Code: Select all

@interface ThingSmartBLEManager (Biz)
// 开启蓝牙扫描
//
// @param clearCache 是否清理缓存
- (void)startListening:(BOOL)clearCache;

// 关闭蓝牙扫描
//
// @param clearCache 是否清理缓存
- (void)stopListening:(BOOL)clearCache;

/// 查询设备产品信息
///
/// @param uuid         设备uuid
/// @param productId    产品id
/// @param success      成功回调
/// @param failure     失败回调
- (void)queryDeviceInfoWithUUID:(NSString *)uuid
                      productId:(NSString *)productId
                        success:(nullable ThingSuccessDict)success
                        failure:(nullable ThingFailureError)failure;
@end

ThingSmartBLEManagerDelegate

蓝牙扫描回调

Code: Select all

- (void)didDiscoveryDeviceWithDeviceInfo:(ThingBLEAdvModel *)deviceInfo;

ThingSmartBLEWifiActivator

双模配网

Code: Select all

/// 开始激活双模设备
///
/// @param UUID         设备uuid
/// @param homeId       家庭id
/// @param productId    产品id 
/// @param ssid         待配网WiFi名称
/// @param password     WiFi密码
/// @param timeout      超时时间
/// @param success      设备收到配网信息回调
/// @param failure      失败回调
- (void)startConfigBLEWifiDeviceWithUUID:(NSString *)UUID
                                  homeId:(long long)homeId
                               productId:(NSString *)productId
                                    ssid:(NSString *)ssid
                                password:(NSString *)password
                               bleActive:(BOOL)bleActive
                                 timeout:(NSTimeInterval)timeout
                                 success:(ThingSuccessHandler)success
                                 failure:(ThingFailureHandler)failure;

ThingSmartBLEWifiActivatorDelegate

双模配网回调

Code: Select all

/// 双模配网回调
///
/// @param deviceModel      配网成功的设备模型
/// @param error            	    配网失败原因
- (void)bleWifiActivator:(ThingSmartBLEWifiActivator *)activator didReceiveBLEWifiConfigDevice:(nullable ThingSmartDeviceModel *)deviceModel error:(nullable NSError *)error;

完整示例

Code: Select all

class DualViewController: UIViewController {
    
let scanBtn = UIButton(type: .system) let stopScanBtn = UIButton(type: .system) let tableview = UITableView() let bleMgr = ThingSmartBLEManager.sharedInstance() var stashUuids = [String]() var deviceList = [(String, ThingBLEAdvModel)]() override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .yellow scanBtn.setTitle("开始扫描", for: .normal) scanBtn.frame = CGRect(x: 100, y: 150, width: 100, height: 20) view.addSubview(scanBtn) scanBtn.addTarget(self, action: #selector(startScan), for: .touchUpInside) stopScanBtn.setTitle("停止扫描", for: .normal) stopScanBtn.frame = CGRect(x: 200, y: 150, width: 100, height: 20) view.addSubview(stopScanBtn) tableview.frame = CGRect(x: 0, y: 200, width: view.frame.size.width, height: view.frame.size.height - 200) tableview.register(UITableViewCell.self, forCellReuseIdentifier: "cell") tableview.dataSource = self tableview.delegate = self view.addSubview(tableview) } @objc func startScan() { bleMgr.delegate = self bleMgr.startListening(true) } @objc func stopScan() { bleMgr.stopListening(false) } func startActiveDeive(ssid: String, pwd: String, deviceInfo: ThingBLEAdvModel) { guard let homeID = Home.current?.homeId else { SVProgressHUD.showError(withStatus: NSLocalizedString("No Home Selected", comment: "")) return } SVProgressHUD.show(withStatus: NSLocalizedString("Sending Data to the Device", comment: "Sending Data to the BLE Device")) ThingSmartBLEWifiActivator.sharedInstance().bleWifiDelegate = self ThingSmartBLEWifiActivator.sharedInstance().startConfigBLEWifiDevice(withUUID: deviceInfo.uuid, homeId: homeID, productId: deviceInfo.productId, ssid: ssid, password: pwd, timeout: 100) { SVProgressHUD.show(withStatus: NSLocalizedString("Configuring", comment: "")) } failure: { SVProgressHUD.showError(withStatus: NSLocalizedString("Failed to Send Data to the Device", comment: "")) } } } extension DualViewController: UITableViewDataSource, UITableViewDelegate { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return deviceList.count }
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, didSelectRowAt indexPath: IndexPath) { let (_, deviceInfo) = deviceList[indexPath.row] let alert = UIAlertController(title: "请输入SSID和密码", message: nil, preferredStyle: .alert) alert.addTextField { textField in textField.placeholder = "在此输入SSID" textField.keyboardType = .default } alert.addTextField { textField in textField.placeholder = "在此输入密码" textField.keyboardType = .default } let cancelAction = UIAlertAction(title: "取消", style: .cancel, handler: nil) alert.addAction(cancelAction) let confirmAction = UIAlertAction(title: "开始配网", style: .default) { _ in if let sf = alert.textFields?.first, let ssid = sf.text, let pf = alert.textFields?.last, let password = pf.text { self.startActiveDeive(ssid: ssid, pwd: password, deviceInfo: deviceInfo) } } alert.addAction(confirmAction) present(alert, animated: true, completion: nil) } } extension DualViewController: ThingSmartBLEWifiActivatorDelegate { func bleWifiActivator(_ activator: ThingSmartBLEWifiActivator, didReceiveBLEWifiConfigDevice deviceModel: ThingSmartDeviceModel?, error: (any Error)?) { guard error == nil, let deviceModel = deviceModel else { return }
let name = deviceModel.name ?? NSLocalizedString("Unknown Name", comment: "Unknown name device.") SVProgressHUD.showSuccess(withStatus: NSLocalizedString("Successfully Added \(name)", comment: "Successfully added one device.")) } }
extension DualViewController: 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 (!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() } } } } }

常见错误码

错误码:2

错误原因:设备找不到路由器
解决方案:

  1. 确认输入的SSID是否为2.4G的WiFi,如果路由器是WiFi6也需要确认一下设备是否支持WiFi6的网络
  2. 设备可能不在路由器信号的覆盖范围内
  3. SSID输入错误
  4. 重新配网试试看

错误码:3

错误原因:路由器密码输入错误
解决方案:

  1. 输入正确的密码

错误码:4

错误原因:设备连不上路由器
解决方案:

  1. 设备与路由器距离太远(通常iot设备的WiFi芯片没有手机的好,手机如果与设备处于同一位置,手机有网设备也不一定能连上路由器),调整设备与路由器之间的距离
  2. 密码错误(部分设备在路由器密码错误时上报的也是连不上路由器,也有路由器兼容性问题)
  3. 重启路由器
  4. 检查路由器配置
  5. 重新配网试试看

错误码:5

错误原因:DHCP错误
解决方案:

  1. 重新配网试试看
  2. 检查路由器DHCP配置
  3. 重启路由器

错误码:6

错误原因:连接涂鸦云mqtt服务器失败
解决方案:
能出现该错误,则证明配网信息已经发送到设备了,但是设备在连云激活阶段出现了错误

  1. 重新配网试试看
  2. 如果多次重试依然失败,请联系FAE同学帮忙协助看看

错误码:7

错误原因:获取涂鸦云服务URL失败
解决方案:
能出现该错误,则证明配网信息已经发送到设备了,但是设备在连云激活阶段出现了错误

  1. 重新配网试试看
  2. 如果多次重试依然失败,请联系FAE同学帮忙协助看看

错误码:8

错误原因:调用atop激活接口返回失败
解决方案:
能出现该错误,则证明配网信息已经发送到设备了,但是设备在连云激活阶段出现了错误

  1. 重新配网试试看
  2. 如果多次重试依然失败,请联系FAE同学帮忙协助看看

错误码:148

错误原因:联网阶段超时
解决方案:
能出现该错误,则证明配网信息已经发送到设备了,但是设备在连接路由器阶段或者连云激活阶段出现了错误
参考错误码 2 3 4 5 6 7 8 的解决方案

错误码 108

错误原因:token失效
解决方案:

  1. 重新配网试试看

错误码:113

错误原因:该设备是强绑定设备,归属权不属于当前用户
解决方案:

  1. 找到绑定设备的用户然后解绑设备

错误码:101 和 400

错误原因:蓝牙连接失败
解决方案:

  1. 确认设备是否处于待配网状态然后重新配网试试看
  2. 重启手机蓝牙

错误码:135

错误原因:查询设备信息失败
解决方案:

  1. 重启手机蓝牙
  2. 重新配网试试看

错误码:136

错误原因:设备配对失败
解决方案:

  1. 重启手机蓝牙
  2. 重新配网试试看

错误码:142

错误原因:发送配网信息失败
解决方案:
此错误码多数情况是配网信息过长且设备的MTU大小设置的过小造成的数据包丢失,请检查设备MTU大小和蓝牙驱动是否正常。

了解更多

【iOS】BLE-WiFi 双模设备先扫描支持的WiFi列表后配网

结语

以上内容基本上涵盖了双模配网的流程和常见错误,如果有任何开发使用上的问题可以下方留言或者联系我们。
若您还对多设备批量配网、先扫描WiFi列表再进行配网等功能感兴趣,请持续关注!