0. 前言
這兩天恰好了解了一下微信小程序的藍牙功能。主要用於配網功能。發現微信的小程序藍牙API已經封裝的很好了。編程起來很方便。什麼藍牙知識都不懂的狀況下,不到兩天就晚上數據的收發了,剩下的就是數據幀格式的定義,固然這部分就不是本次博客的重點。
1. 準備硬件
這裏我準備了CH341SER這個做爲USB轉串口。用sscom5.13.1 串口工具。因爲我不太懂硬件開發。硬件部分都是由公司其餘人開發的。我只是負責把環境搭建起來。而後負責個人微信小程序開發。html
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 },
定義搜索設備任務api
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 },
設置能夠進行鏈接的設備ide
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的規則)ui
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
參考資料:
http://www.javashuo.com/article/p-rxdikxyf-cm.html
本文地址: