一起养成写作习惯!这是我参与「掘金日新计划 · 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 包含广播、连接、服务发现、协商共享密钥、传输数据、回传连接状态等步骤。
-
ESP32 开启 GATT Server 模式,发送带有特定
advertising data
的广播。你可以自定义该广播,该广播不属于 BluFi Profile。
-
使用手机 APP 搜索到该特定广播,手机作为 GATT Client 连接 ESP32。你可以决定使用哪款手机 APP。
-
GATT 连接建立成功后,手机会向 ESP32 发送数据帧进行密钥协商(详情见 BluFi 中定义的帧格式 )。
-
ESP32 收到密钥协商的数据帧后,会按照使用者自定义的协商方法来解析。
-
手机与 ESP32 进行密钥协商。协商过程可使用 DH/RSA/ECC 等加密算法。
-
协商结束后,手机端向 ESP32 发送控制帧,用于设置安全模式。
-
ESP32 收到控制帧后,使用共享密钥以及安全配置对通信数据进行加密和解密。
-
手机向 ESP32 发送 BluFi 中定义的帧格式 中定义的数据帧,包括 SSID、密码等 Wi-Fi 配置信息。
-
手机向 ESP32 发送 Wi-Fi 连接请求的控制帧。ESP32 收到这个控制帧之后,会认为手机已将必要的信息已经传输完毕,准备连接 Wi-Fi。
-
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,