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 })
4. 运行时截图
工具下载地址:
https://files.cnblogs.com/files/wunaozai/sscom5.13.1.zip
https://files.cnblogs.com/files/wunaozai/CH341SER_64bit.zip
参考资料:
https://www.cnblogs.com/guhonghao/p/9947144.html
本文地址: