0. 前言
这两天刚好了解了一下微信小程序的蓝牙功能。主要用于配网功能。发现微信的小程序蓝牙API已经封装的很好了。编程起来很方便。什么蓝牙知识都不懂的情况下,不到两天就晚上数据的收发了,剩下的就是数据帧格式的定义,当然这部分就不是本次博客的重点。
1. 准备硬件
这里我准备了CH341SER这个作为USB转串口。用sscom5.13.1 串口工具。由于我不太懂硬件开发。硬件部分都是由公司其他人开发的。我只是负责把环境搭建起来。然后负责我的微信小程序开发。
2. 开发小程序简单讲解
onLoad 这个一方面是用来获取当前连接的WiFi名称,减少用户输入,另一方面也是用来判断当前是否开启GPS功能。对于Android用户,是需要打开GPS蓝牙功能才能搜索到周围的蓝牙设备。
1 onLoad: function(options) {
2 var that = this;
3 wx.startWifi({
4 success(res) {
5 console.log(res.errMsg)
6 wx.getConnectedWifi({
7 success: function(res) {
8 console.log(res);
9 that.setData({
10 ssid: res.wifi.SSID
11 })
12 },
13 fail: function(res) {
14 if(res.errCode == 12006){
15 wx.showModal({
16 title: '请打开GPS定位',
17 content: 'Android手机不打开GPS定位,无法搜索到蓝牙设备.',
18 showCancel: false
19 })
20 }
21 console.log(res);
22 }
23 })
24 }
25 })
26 },
搜索蓝牙设备相关代码
1 searchBleEvent: function(ret){
2 var ssid = this.data.ssid;
3 var pass = this.data.pass;
4 console.log(ssid, pass);
5 if (util.isEmpty(ssid) || util.isEmpty(pass)) {
6 util.toastError('请输入WiFi名称及密码');
7 return;
8 }
9 this.initBLE();
10 },
初始化蓝牙适配器
1 initBLE: function() {
2 this.printLog("启动蓝牙适配器, 蓝牙初始化")
3 var that = this;
4 wx.openBluetoothAdapter({
5 success: function(res) {
6 console.log(res);
7 that.findBLE();
8 },
9 fail: function(res) {
10 util.toastError('请先打开蓝牙');
11 }
12 })
13 },
定义搜索设备任务
1 findBLE: function() {
2 this.printLog("打开蓝牙成功.")
3 var that = this
4 wx.startBluetoothDevicesDiscovery({
5 allowDuplicatesKey: false,
6 interval: 0,
7 success: function(res) {
8 wx.showLoading({
9 title: '正在搜索设备',
10 })
11 console.log(res);
12 delayTimer = setInterval(function(){
13 that.discoveryBLE() //3.0 //这里的discovery需要多次调用
14 }, 1000);
15 setTimeout(function () {
16 if (isFound) {
17 return;
18 } else {
19 wx.hideLoading();
20 console.log("搜索设备超时");
21 wx.stopBluetoothDevicesDiscovery({
22 success: function (res) {
23 console.log('连接蓝牙成功之后关闭蓝牙搜索');
24 }
25 })
26 clearInterval(delayTimer)
27 wx.showModal({
28 title: '搜索设备超时',
29 content: '请检查蓝牙设备是否正常工作,Android手机请打开GPS定位.',
30 showCancel: false
31 })
32 util.toastError("搜索设备超时,请打开GPS定位,再搜索")
33 return
34 }
35 }, 15000);
36 },
37 fail: function(res) {
38 that.printLog("蓝牙设备服务发现失败: " + res.errMsg);
39 }
40 })
41 },
搜索设备回调
1 discoveryBLE: function() {
2 var that = this
3 wx.getBluetoothDevices({
4 success: function(res) {
5 var list = res.devices;
6 console.log(list);
7 if(list.length <= 0){
8 return ;
9 }
10 var devices = [];
11 for (var i = 0; i < list.length; i++) {
12 //that.data.inputValue:表示的是需要连接的蓝牙设备ID,
13 //简单点来说就是我想要连接这个蓝牙设备,
14 //所以我去遍历我搜索到的蓝牙设备中是否有这个ID
15 var name = list[i].name || list[i].localName;
16 if(util.isEmpty(name)){
17 continue;
18 }
19 if(name.indexOf('JL') >= 0 && list[i].RSSI != 0){
20 console.log(list[i]);
21 devices.push(list[i]);
22 }
23 }
24 console.log('总共有' + devices.length + "个设备需要设置")
25 if (devices.length <= 0) {
26 return;
27 }
28 that.connectBLE(devices);
29 },
30 fail: function() {
31 util.toastError('搜索蓝牙设备失败');
32 }
33 })
34 },
设置可以进行连接的设备
1 connectBLE: function(devices){
2 this.printLog('总共有' + devices.length + "个设备需要设置")
3 var that = this;
4 wx.hideLoading();
5 isFound = true;
6 clearInterval(delayTimer);
7 wx.stopBluetoothDevicesDiscovery({
8 success: function (res) {
9 that.printLog('连接蓝牙成功之后关闭蓝牙搜索');
10 }
11 })
12 //两个的时候需要选择
13 var list = [];
14 for (var i = 0; i < devices.length; i++) {
15 var name = devices[i].name || devices[i].localName;
16 list.push(name + "[" + devices[i].deviceId + "]")
17 }
18 this.setData({
19 deviceArray: list
20 })
21 //默认选择
22 this.setData({
23 currDeviceID: list[0]
24 })
25 },
选择设备,然后点击对应的配网按钮,创建BLE连接
1 createBLE: function(deviceId){
2 this.printLog("连接: [" + deviceId+"]");
3 var that = this;
4 this.closeBLE(deviceId, function(res){
5 console.log("预先关闭,再打开");
6 setTimeout(function(){
7 wx.createBLEConnection({
8 deviceId: deviceId,
9 success: function (res) {
10 that.printLog("设备连接成功");
11 that.getBLEServiceId(deviceId);
12 },
13 fail: function (res) {
14 that.printLog("设备连接失败" + res.errMsg);
15 }
16 })
17 }, 2000)
18 });
19 },
获取蓝牙设备提供的服务UUID(本项目由于只会提供一个服务,就默认选择,实际项目,会自定义这个UUID的前缀或者后缀规则,定义多个不同的服务)
1 //获取服务UUID
2 getBLEServiceId: function(deviceId){
3 this.printLog("获取设备[" + deviceId + "]服务列表")
4 var that = this;
5 wx.getBLEDeviceServices({
6 deviceId: deviceId,
7 success: function(res) {
8 console.log(res);
9 var services = res.services;
10 if (services.length <= 0){
11 that.printLog("未找到主服务列表")
12 return;
13 }
14 that.printLog('找到设备服务列表个数: ' + services.length);
15 if (services.length == 1){
16 var service = services[0];
17 that.printLog("服务UUID:["+service.uuid+"] Primary:" + service.isPrimary);
18 that.getBLECharactedId(deviceId, service.uuid);
19 }else{ //多个主服务
20 //TODO
21 }
22 },
23 fail: function(res){
24 that.printLog("获取设备服务列表失败" + res.errMsg);
25 }
26 })
27 },
获取服务下的特征值(由于这个例子,是包含两个特征值,一个用于读,一个用于写,实际项目,跟上面的服务一样,要定义好特征量UUID的规则)
1 getBLECharactedId: function(deviceId, serviceId){
2 this.printLog("获取设备特征值")
3 var that = this;
4 wx.getBLEDeviceCharacteristics({
5 deviceId: deviceId,
6 serviceId: serviceId,
7 success: function(res) {
8 console.log(res);
9 //这里会获取到两个特征值,一个用来写,一个用来读
10 var chars = res.characteristics;
11 if(chars.length <= 0){
12 that.printLog("未找到设备特征值")
13 return ;
14 }
15 that.printLog("找到设备特征值个数:" + chars.length);
16 if(chars.length == 2){
17 for(var i=0; i<chars.length; i++){
18 var char = chars[i];
19 that.printLog("特征值[" + char.uuid + "]")
20 var prop = char.properties;
21 if(prop.notify == true){
22 that.printLog("该特征值属性: Notify");
23 that.recvBLECharacterNotice(deviceId, serviceId, char.uuid);
24 }else if(prop.write == true){
25 that.printLog("该特征值属性: Write");
26 that.sendBLECharacterNotice(deviceId, serviceId, char.uuid);
27 }else{
28 that.printLog("该特征值属性: 其他");
29 }
30 }
31 }else{
32 //TODO
33 }
34 },
35 fail: function(res){
36 that.printLog("获取设备特征值失败")
37 }
38 })
39 },
recv 接收设备发送过来数据
1 recvBLECharacterNotice: function(deviceId, serviceId, charId){
2 //接收设置是否成功
3 this.printLog("注册Notice 回调函数");
4 var that = this;
5 wx.notifyBLECharacteristicValueChange({
6 deviceId: deviceId,
7 serviceId: serviceId,
8 characteristicId: charId,
9 state: true, //启用Notify功能
10 success: function(res) {
11 wx.onBLECharacteristicValueChange(function(res){
12 console.log(res);
13 that.printLog("收到Notify数据: " + that.ab2hex(res.value));
14 //关闭蓝牙
15 wx.showModal({
16 title: '配网成功',
17 content: that.ab2hex(res.value),
18 showCancel: false
19 })
20 });
21 },
22 fail: function(res){
23 console.log(res);
24 that.printLog("特征值Notice 接收数据失败: " + res.errMsg);
25 }
26 })
27 },
send 小程序发送数据到设备
1 sendBLECharacterNotice: function (deviceId, serviceId, charId){
2 //发送ssid/pass
3 this.printLog("延时1秒后,发送SSID/PASS");
4 var that = this;
5 var cell = {
6 "ssid": this.data.ssid,
7 "pass": this.data.pass
8 }
9 var buffer = this.string2buffer(JSON.stringify(cell));
10 setTimeout(function(){
11 wx.writeBLECharacteristicValue({
12 deviceId: deviceId,
13 serviceId: serviceId,
14 characteristicId: charId,
15 value: buffer,
16 success: function(res) {
17 that.printLog("发送SSID/PASS 成功");
18 },
19 fail: function(res){
20 console.log(res);
21 that.printLog("发送失败." + res.errMsg);
22 },
23 complete: function(){
24
25 }
26 })
27
28 }, 1000);
29 },
手机端可以同时连接多个蓝牙设备,但是同一个蓝牙设备不能被多次连接,所以需要在每次连接前关闭BLE连接
1 closeBLE: function(deviceId, callback){
2 var that = this;
3 wx.closeBLEConnection({
4 deviceId: deviceId,
5 success: function(res) {
6 that.printLog("断开设备[" + deviceId + "]成功.");
7 console.log(res)
8 },
9 fail: function(res){
10 that.printLog("断开设备成功.");
11 },
12 complete: callback
13 })
14 },
说明:接收数据和发送数据时,注意BLE限制了发送数据包的大小,现在20byte。具体参考微信小程序官方文档: https://developers.weixin.qq.com/miniprogram/dev/api/device/bluetooth-ble/wx.writeBLECharacteristicValue.html
3. 蓝牙相关的所有JS代码
1 // pages/bluetoothconfig/bluetoothconfig.js
2 const util = require('../../utils/util.js')
3
4 var delayTimer; //用来控制是否持续服务发现
5 var isFound = false;
6
7 Page({
8 /**
9 * 页面的初始数据
10 */
11 data: {
12 ssid: '',
13 pass: '',
14 logs: [],
15 deviceArray: [],
16 currDeviceID: '请选择...'
17 },
18 onLoad: function(options) {
19 var that = this;
20 wx.startWifi({
21 success(res) {
22 console.log(res.errMsg)
23 wx.getConnectedWifi({
24 success: function(res) {
25 console.log(res);
26 that.setData({
27 ssid: res.wifi.SSID
28 })
29 },
30 fail: function(res) {
31 if(res.errCode == 12006){
32 wx.showModal({
33 title: '请打开GPS定位',
34 content: 'Android手机不打开GPS定位,无法搜索到蓝牙设备.',
35 showCancel: false
36 })
37 }
38 console.log(res);
39 }
40 })
41 }
42 })
43 },
44 bindPickerChange: function(ret){
45 var array = this.data.deviceArray;
46 console.log(array[ret.detail.value]);
47 this.setData({
48 currDeviceID: array[ret.detail.value]
49 })
50 },
51 searchBleEvent: function(ret){
52 var ssid = this.data.ssid;
53 var pass = this.data.pass;
54 console.log(ssid, pass);
55 if (util.isEmpty(ssid) || util.isEmpty(pass)) {
56 util.toastError('请输入WiFi名称及密码');
57 return;
58 }
59 this.initBLE();
60 },
61 bleConfigEvent: function (ret) {
62 var deviceID = this.data.currDeviceID;
63 console.log("选中:" + deviceID);
64 if (util.isEmpty(deviceID) || deviceID == "请选择..."){
65 util.toastError("请先搜索设备");
66 return ;
67 }
68 var device = deviceID.split('[');
69 if(device.length <= 1){
70 util.toastError("请先搜索设备");
71 return ;
72 }
73 var id = device[device.length - 1].replace("]", "");
74 console.log(id);
75 util.toastError("连接" + id);
76 this.createBLE(id);
77 },
78
79
80 initBLE: function() {
81 this.printLog("启动蓝牙适配器, 蓝牙初始化")
82 var that = this;
83 wx.openBluetoothAdapter({
84 success: function(res) {
85 console.log(res);
86 that.findBLE();
87 },
88 fail: function(res) {
89 util.toastError('请先打开蓝牙');
90 }
91 })
92 },
93 findBLE: function() {
94 this.printLog("打开蓝牙成功.")
95 var that = this
96 wx.startBluetoothDevicesDiscovery({
97 allowDuplicatesKey: false,
98 interval: 0,
99 success: function(res) {
100 wx.showLoading({
101 title: '正在搜索设备',
102 })
103 console.log(res);
104 delayTimer = setInterval(function(){
105 that.discoveryBLE() //3.0 //这里的discovery需要多次调用
106 }, 1000);
107 setTimeout(function () {
108 if (isFound) {
109 return;
110 } else {
111 wx.hideLoading();
112 console.log("搜索设备超时");
113 wx.stopBluetoothDevicesDiscovery({
114 success: function (res) {
115 console.log('连接蓝牙成功之后关闭蓝牙搜索');
116 }
117 })
118 clearInterval(delayTimer)
119 wx.showModal({
120 title: '搜索设备超时',
121 content: '请检查蓝牙设备是否正常工作,Android手机请打开GPS定位.',
122 showCancel: false
123 })
124 util.toastError("搜索设备超时,请打开GPS定位,再搜索")
125 return
126 }
127 }, 15000);
128 },
129 fail: function(res) {
130 that.printLog("蓝牙设备服务发现失败: " + res.errMsg);
131 }
132 })
133 },
134 discoveryBLE: function() {
135 var that = this
136 wx.getBluetoothDevices({
137 success: function(res) {
138 var list = res.devices;
139 console.log(list);
140 if(list.length <= 0){
141 return ;
142 }
143 var devices = [];
144 for (var i = 0; i < list.length; i++) {
145 //that.data.inputValue:表示的是需要连接的蓝牙设备ID,
146 //简单点来说就是我想要连接这个蓝牙设备,
147 //所以我去遍历我搜索到的蓝牙设备中是否有这个ID
148 var name = list[i].name || list[i].localName;
149 if(util.isEmpty(name)){
150 continue;
151 }
152 if(name.indexOf('JL') >= 0 && list[i].RSSI != 0){
153 console.log(list[i]);
154 devices.push(list[i]);
155 }
156 }
157 console.log('总共有' + devices.length + "个设备需要设置")
158 if (devices.length <= 0) {
159 return;
160 }
161 that.connectBLE(devices);
162 },
163 fail: function() {
164 util.toastError('搜索蓝牙设备失败');
165 }
166 })
167 },
168 connectBLE: function(devices){
169 this.printLog('总共有' + devices.length + "个设备需要设置")
170 var that = this;
171 wx.hideLoading();
172 isFound = true;
173 clearInterval(delayTimer);
174 wx.stopBluetoothDevicesDiscovery({
175 success: function (res) {
176 that.printLog('连接蓝牙成功之后关闭蓝牙搜索');
177 }
178 })
179 //两个的时候需要选择
180 var list = [];
181 for (var i = 0; i < devices.length; i++) {
182 var name = devices[i].name || devices[i].localName;
183 list.push(name + "[" + devices[i].deviceId + "]")
184 }
185 this.setData({
186 deviceArray: list
187 })
188 //默认选择
189 this.setData({
190 currDeviceID: list[0]
191 })
192 },
193
194
195 createBLE: function(deviceId){
196 this.printLog("连接: [" + deviceId+"]");
197 var that = this;
198 this.closeBLE(deviceId, function(res){
199 console.log("预先关闭,再打开");
200 setTimeout(function(){
201 wx.createBLEConnection({
202 deviceId: deviceId,
203 success: function (res) {
204 that.printLog("设备连接成功");
205 that.getBLEServiceId(deviceId);
206 },
207 fail: function (res) {
208 that.printLog("设备连接失败" + res.errMsg);
209 }
210 })
211 }, 2000)
212 });
213 },
214 //获取服务UUID
215 getBLEServiceId: function(deviceId){
216 this.printLog("获取设备[" + deviceId + "]服务列表")
217 var that = this;
218 wx.getBLEDeviceServices({
219 deviceId: deviceId,
220 success: function(res) {
221 console.log(res);
222 var services = res.services;
223 if (services.length <= 0){
224 that.printLog("未找到主服务列表")
225 return;
226 }
227 that.printLog('找到设备服务列表个数: ' + services.length);
228 if (services.length == 1){
229 var service = services[0];
230 that.printLog("服务UUID:["+service.uuid+"] Primary:" + service.isPrimary);
231 that.getBLECharactedId(deviceId, service.uuid);
232 }else{ //多个主服务
233 //TODO
234 }
235 },
236 fail: function(res){
237 that.printLog("获取设备服务列表失败" + res.errMsg);
238 }
239 })
240 },
241 getBLECharactedId: function(deviceId, serviceId){
242 this.printLog("获取设备特征值")
243 var that = this;
244 wx.getBLEDeviceCharacteristics({
245 deviceId: deviceId,
246 serviceId: serviceId,
247 success: function(res) {
248 console.log(res);
249 //这里会获取到两个特征值,一个用来写,一个用来读
250 var chars = res.characteristics;
251 if(chars.length <= 0){
252 that.printLog("未找到设备特征值")
253 return ;
254 }
255 that.printLog("找到设备特征值个数:" + chars.length);
256 if(chars.length == 2){
257 for(var i=0; i<chars.length; i++){
258 var char = chars[i];
259 that.printLog("特征值[" + char.uuid + "]")
260 var prop = char.properties;
261 if(prop.notify == true){
262 that.printLog("该特征值属性: Notify");
263 that.recvBLECharacterNotice(deviceId, serviceId, char.uuid);
264 }else if(prop.write == true){
265 that.printLog("该特征值属性: Write");
266 that.sendBLECharacterNotice(deviceId, serviceId, char.uuid);
267 }else{
268 that.printLog("该特征值属性: 其他");
269 }
270 }
271 }else{
272 //TODO
273 }
274 },
275 fail: function(res){
276 that.printLog("获取设备特征值失败")
277 }
278 })
279 },
280 recvBLECharacterNotice: function(deviceId, serviceId, charId){
281 //接收设置是否成功
282 this.printLog("注册Notice 回调函数");
283 var that = this;
284 wx.notifyBLECharacteristicValueChange({
285 deviceId: deviceId,
286 serviceId: serviceId,
287 characteristicId: charId,
288 state: true, //启用Notify功能
289 success: function(res) {
290 wx.onBLECharacteristicValueChange(function(res){
291 console.log(res);
292 that.printLog("收到Notify数据: " + that.ab2hex(res.value));
293 //关闭蓝牙
294 wx.showModal({
295 title: '配网成功',
296 content: that.ab2hex(res.value),
297 showCancel: false
298 })
299 });
300 },
301 fail: function(res){
302 console.log(res);
303 that.printLog("特征值Notice 接收数据失败: " + res.errMsg);
304 }
305 })
306 },
307 sendBLECharacterNotice: function (deviceId, serviceId, charId){
308 //发送ssid/pass
309 this.printLog("延时1秒后,发送SSID/PASS");
310 var that = this;
311 var cell = {
312 "ssid": this.data.ssid,
313 "pass": this.data.pass
314 }
315 var buffer = this.string2buffer(JSON.stringify(cell));
316 setTimeout(function(){
317 wx.writeBLECharacteristicValue({
318 deviceId: deviceId,
319 serviceId: serviceId,
320 characteristicId: charId,
321 value: buffer,
322 success: function(res) {
323 that.printLog("发送SSID/PASS 成功");
324 },
325 fail: function(res){
326 console.log(res);
327 that.printLog("发送失败." + res.errMsg);
328 },
329 complete: function(){
330
331 }
332 })
333
334 }, 1000);
335 },
336
337 closeBLE: function(deviceId, callback){
338 var that = this;
339 wx.closeBLEConnection({
340 deviceId: deviceId,
341 success: function(res) {
342 that.printLog("断开设备[" + deviceId + "]成功.");
343 console.log(res)
344 },
345 fail: function(res){
346 that.printLog("断开设备成功.");
347 },
348 complete: callback
349 })
350 },
351
352
353
354
355 printLog: function(msg){
356 var logs = this.data.logs;
357 logs.push(msg);
358 this.setData({ logs: logs })
359 },
360 /**
361 * 将字符串转换成ArrayBufer
362 */
363 string2buffer(str) {
364 if (!str) return;
365 var val = "";
366 for (var i = 0; i < str.length; i++) {
367 val += str.charCodeAt(i).toString(16);
368 }
369 console.log(val);
370 str = val;
371 val = "";
372 let length = str.length;
373 let index = 0;
374 let array = []
375 while (index < length) {
376 array.push(str.substring(index, index + 2));
377 index = index + 2;
378 }
379 val = array.join(",");
380 // 将16进制转化为ArrayBuffer
381 return new Uint8Array(val.match(/[\da-f]{2}/gi).map(function (h) {
382 return parseInt(h, 16)
383 })).buffer
384 },
385 /**
386 * 将ArrayBuffer转换成字符串
387 */
388 ab2hex(buffer) {
389 var hexArr = Array.prototype.map.call(
390 new Uint8Array(buffer),
391 function (bit) {
392 return ('00' + bit.toString(16)).slice(-2)
393 }
394 )
395 return hexArr.join('');
396 },
397 inputSSID: function(res) {
398 var ssid = res.detail.value;
399 this.setData({
400 ssid: ssid
401 })
402 },
403 inputPASS: function(res) {
404 var pass = res.detail.value;
405 this.setData({
406 pass: pass
407 })
408 }
409
410 })
View Code
4. 运行时截图