uni-app低功耗蓝牙辅助BluFi配网

2,159 阅读8分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第1天,点击查看活动详情

开发流程

1.获取设备MAC

2.低功耗蓝牙连接

3.BluFi配置讲解

4.一键配网

获取设备MAC

在我的开发流程中需要扫码获取MAC我将详细讲解如何高效快速的扫码,如果不需要可以直接跳过此段落。

在使用官方文档uni.scanCode() 我发现自带扫码功能效率比较慢很难识别,我扫公司设备基本扫不到因为它很小。

我通过uni-app官方文档下提示,我使用支付宝提供扫码插件

地址:https://ext.dcloud.net.cn/plugin?id=2636

虽然插件使用流程上有详细介绍,这里怕有些同学不太容易理解我详细解释下。

1.先要去开通阿里云mPaaS,登录阿里云控制台,在 mPaaS 产品页    (免费开通)

2.开通后您需要创建一个 mPaaS 应用

在这个配置过程中,你只需要完成下载配置文件第一步步骤即可

下载配置后,获取 .config 配置文件。

3.导入config信息配置扫码插件

把支付宝原生扫码插件下载下来,项目>manifest.json>App原生插件配置选择支付宝原生扫码插件配置如下

License   ->                                                           ->      mpaasConfigLicense

Workspaceld ->         对应mPaaS的config文件     ->      workspaceId

AppId ->                                                               ->      appId

 4.uniapp 端调起 mPaaS 扫码

var mpaasScanModule = uni.requireNativePlugin("Mpaas-Scan-Module")

mpaasScanModule.mpaasScan({
                        // 扫码识别类型,参数可多选,qrCode、barCode,不设置,默认识别所有
                        'scanType':  ['qrCode','barCode'],
                        // 是否隐藏相册,默认false不隐藏
                        'hideAlbum': false
                    },
                    (ret) => {
                        uni.showModal({
                            title: "弹窗标题",
                            // 返回值中,resp_code 表示返回结果值,10:用户取消,11:其他错误,1000:成功
                            // 返回值中,resp_message 表示返回结果信息
                            // 返回值中,resp_result 表示扫码结果,只有成功才会有返回
                            content: JSON.stringify(ret),
                            showCancel: false,
                            confirmText: "确定"
                        })
                    })

低功耗蓝牙连接

1.首先我们要做蓝牙连接需要检查网络、定位、蓝牙是否开启

// 检查是否连接wifi
uni.getNetworkType({
				 	success: function (res) {
				 	 if(res.networkType !='wifi') {
				 		 uni.showToast({
				 		 	title: '请连接wifi',
				 			icon: 'error',
				 		 	duration: 2000
				 		 });
				 	 } else{}
				 	}
				 });

// 检查是否定位是否开启
let system = uni.getSystemInfoSync();// 获取系统信息
				  if (system.platform === 'android') { // 判断平台
				    var context = plus.android.importClass("android.content.Context");
				    var locationManager = plus.android.importClass("android.location.LocationManager");
				    var main = plus.android.runtimeMainActivity();
				    var mainSvr = main.getSystemService(context.LOCATION_SERVICE);
				    if (!mainSvr.isProviderEnabled(locationManager.GPS_PROVIDER)) {
						that.location= false
				      uni.showModal({
				        title: '提示',
				        content: '请打开定位服务功能',
				        showCancel: false, // 不显示取消按钮
				        success() {
				          if (!mainSvr.isProviderEnabled(locationManager.GPS_PROVIDER)) {
				            var Intent = plus.android.importClass('android.content.Intent');
				            var Settings = plus.android.importClass('android.provider.Settings');
				            var intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS); 
				            main.startActivity(intent); // 打开系统设置GPS服务页面
				          } else {
				            console.log('GPS功能已开启');
				          }
				        }
				      });
				    } else {
					// 其他平台
					}
				  } 


 uni.openBluetoothAdapter({ // 初始化蓝牙
      success(res) {},
       fail(res){
	that.bluetooth = false
	uni.hideLoading()
	uni.showToast({
		title: '请连接蓝牙',
		icon: 'error',
		duration: 2000
	 });
	} 
       })

1.在初始化蓝牙  uni.openBluetoothAdapter() 成功后调用**uni.onBluetoothDeviceFound()**监听寻找到新设备的事件。

2.匹配你已知蓝牙mac地址后,调用**uni.stopBluetoothDevicesDiscovery()**关闭停止搜寻附近的蓝牙外围设备。拿到 deviceId。

3. 用deviceId调用**uni.createBLEConnection()**连接低功耗蓝牙设备。

4.用deviceId调用 **uni.getBLEDeviceServices()**获取蓝牙设备所有服务,拿到serviceId。

我发现如果uni.createBLEConnection()成功后直接调用该方法会拿不到,setTimeout(()=>{},3000)定时几秒后可成功

5.用deviceId和serviceId调用**uni.getBLEDeviceCharacteristics()**获取蓝牙设备某个服务中所有特征值拿到指定的characteristicId。

要留意特征值支持操作类型,如果需要写入那么该特征值必须支持write操作。

6.用deviceId、serviceId和characteristicId调用uni.writeBLECharacteristicValue()

写入wifi账号和密码,每次写入时候需要有间隔时间不然会出问题,用定时器定时几秒后再执行下一次写入。

BluFi配置讲解

下面以配置 Station 为例说明配置步骤。BluFi 配网的配置 Station 包含广播、连接、服务发现、协商共享密钥、传输数据、回传连接状态等步骤。

  1. ESP32 开启 GATT Server 模式,发送带有特定

    advertising data

    的广播。你可以自定义该广播,该广播不属于 BluFi Profile。

  2. 使用手机 APP 搜索到该特定广播,手机作为 GATT Client 连接 ESP32。你可以决定使用哪款手机 APP。

  3. GATT 连接建立成功后,手机会向 ESP32 发送数据帧进行密钥协商(详情见 BluFi 中定义的帧格式 )。

  4. ESP32 收到密钥协商的数据帧后,会按照使用者自定义的协商方法来解析。

  5. 手机与 ESP32 进行密钥协商。协商过程可使用 DH/RSA/ECC 等加密算法。

  6. 协商结束后,手机端向 ESP32 发送控制帧,用于设置安全模式。

  7. ESP32 收到控制帧后,使用共享密钥以及安全配置对通信数据进行加密和解密。

  8. 手机向 ESP32 发送 BluFi 中定义的帧格式 中定义的数据帧,包括 SSID、密码等 Wi-Fi 配置信息。

  9. 手机向 ESP32 发送 Wi-Fi 连接请求的控制帧。ESP32 收到这个控制帧之后,会认为手机已将必要的信息已经传输完毕,准备连接 Wi-Fi。

  10. ESP32 连接到 Wi-Fi 后,发送 Wi-Fi 连接状态报告的控制帧到手机,以报告连接状态。至此配网结束。

BluFi中传送数据的帧格式

手机 APP 与 ESP32 之间的 BluFi 通信格式定义如下:

注意:WIFI和密码一般不会很长所以只介绍不分片情况

帧不分片情况下的格式 (8 位):

描述

类型字段(最低有效位)

1

帧控制字段

1

序列号字段

1

数据长度字段

1

数据字段

${Data Length}

校验字段(最高有效位)

2

具体参考文档

docs.espressif.com/projects/es…

我会在下面简单描述需要注意细节

1.类型字段 

类型字段,占 1 字节。分为类型字段和子类型字段两部分,类型字段占低 2 位,子类型字段占高 6 位   注意:该字段是由对应的控制帧和数据帧用位换算得来的  (数据帧 << 2) | 控制帧

2.帧控制字段

帧控制字段,占 1 字节,每个位表示不同含义。(具体参考文档)

3.序列控制

序列控制字段。帧发送时,无论帧的类型是什么,序列都会自动加 1,用来防止重放攻击 (Replay Attack)。每次重新连接后,序列清零。注意:在写入配置步骤过程中,从0开始每写入一次它都需要+1

4.长度

数据字段的长度,不包含校验部分。注意:该长度指你写入数据的长度

5.数据

你所需要传输的数据 (这里我一般指WIFI名和密码)注意:需要转换成每个字符的 Unicode 编码

6.校验

此字段占两个字节,用来校验“序列 + 数据长度 + 明文数据”  注意:校验位是用序列控制位+长度+数据 换算成两位数 详细转换在后面说明

一键配网

首先建立GATT连接,就是连接低功耗蓝牙。

第一步:协商秘钥

第二步:设置ESP32为手机安全模式

第三步:写入SSID  (WIFI名称)

第四步:写入Password (WIFI密码)

完成这四步写入就完成蓝牙配网功能,写入用uni.writeBLECharacteristicValue

其实我只需要第三步和第四步,由于步骤不可以省略因为过程中需要传递**序列控制**

我在第一步和第二步写入时候频格式为[0, 0, 0, 0, 0, 0, 0]和[0, 0, 1, 0, 0, 0, 0]

频格式第三位是序列控制,需要从0开始累加1,最后两位是校验位其实是可以不用写的,因为我在第二位帧控制字段设为0,参考文档0等于不加密,所以不需要校验位。我在第三步和第四步不再写入校验位了作为实例。

第一步:写入协商秘钥

uni.writeBLECharacteristicValue({
        deviceId,
        serviceId,
	characteristicId
        value: new Uint8Array([0, 0, 1, 0, 0, 0, 0]).buffer,
        success(res) {
	   setTimeout(()=>{  //需要写定时,连续写入会失败的可能性。(试过百分百会失败)              
                 //这里面继续写入
           },3000)
        }
})																					

第二步:设置ESP32为手机安全模式

uni.writeBLECharacteristicValue({
        deviceId,
        serviceId,
	characteristicId
        value: new Uint8Array([0, 0, 1, 0, 0, 0, 0]).buffer,
        success(res) {
	   setTimeout(()=>{  //需要写定时,连续写入会失败的可能性。(试过百分百会失败)               
                  //这里面继续写入
           },3000)
        }
})														 
																			
												

第三步:写入SSID

假设我的SSID为:‘ abc’

第一位:类型字段中控制帧中设为 0x01 将 ESP 设备设置为安全模式,数据帧设为 0x02 发送 STA 模式的 SSID

 (0x02 << 2) | 0x01     // 所以类型字段为9

第二位:帧控制字段 不加密设为0

第三位:序列控制字段 每次写都累加1,按下标0开始,这是第三步所以为2

第四位:数据长度字段  取决于你的SSID长度, 所以长度为3

第五位:数据字段  需要用charCodeAt()  返回每个字符的 Unicode 编码,所以是:97,98,99

第六位:校验位 设置不加密可以不填写

uni.writeBLECharacteristicValue({
        deviceId,
        serviceId,
	characteristicId
        value: new Uint8Array([9, 0, 2, 3, 97, 98, 99]).buffer,
        success(res) {
	   setTimeout(()=>{  //需要写定时,连续写入会失败的可能性。(试过百分百会失败)             
                   //这里面继续写入
           },3000)
        }
})													
																				

第四步:写入Password

假设我的password为:‘ 123’

第一位:类型字段中控制帧 0x01 将 ESP 设备设置为安全模式,数据帧 0x03 发送 STA 模式的密码

 (0x03 << 2) | 0x01     // 所以类型字段为13

第二位:帧控制字段 不加密设为0

第三位:序列控制字段 每次写都累加1,按下标0开始,这是第四步所以为3

第四位:数据长度字段 取决于你的password长度, 所以长度为3

第五位:数据字段 需要用charCodeAt() 返回每个字符的 Unicode 编码,所以是:49,50,51

第六位:校验位 设置不加密可以不填写

uni.writeBLECharacteristicValue({
        deviceId,
        serviceId,
	characteristicId
        value: new Uint8Array([13, 0, 3, 3, 49, 50, 51]).buffer,
        success(res) {
            // 配网成功
        }
})														  // 这里的value是ArrayBuffer类型
																				  value: new Uint8Array([0, 0, 1, 0, 0, 0, 0]).buffer,