共计 12195 个字符,预计需要花费 31 分钟才能阅读完成。
 以前用小程序开发过蓝牙,项目遗憾没上线,打算把代码公布出来,帮到一些新手,此代码兼容 ios 和安卓 
const tokenUtil = require('../../../utils/token.js')const ips = require('../../../utils/ips.js')const req = require('../../../utils/req.js')function inArray(arr, key, val) {for (let i = 0; i < arr.length; i++) {if (arr[i][key] === val) {return i;}}return -1;}// ArrayBuffer 转 16 进度字符串示例 function ab2hex(buffer) {var hexArr = Array.prototype.map.call(new Uint8Array(buffer),function(bit) {return ('00' + bit.toString(16)).slice(-2)})return hexArr.join('');}function base64_encode(str) { // 编码,配合 encodeURIComponent 使用 var c1, c2, c3;var base64EncodeChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";var i = 0, len = str.length, strin = '';while (i < len) {c1 = str.charCodeAt(i++) & 0xff;if (i == len) {strin += base64EncodeChars.charAt(c1 >> 2);strin += base64EncodeChars.charAt((c1 & 0x3) << 4);strin += "==";break;}c2 = str.charCodeAt(i++);if (i == len) {strin += base64EncodeChars.charAt(c1 >> 2);strin += base64EncodeChars.charAt(((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4));strin += base64EncodeChars.charAt((c2 & 0xF) << 2);strin += "=";break;}c3 = str.charCodeAt(i++);strin += base64EncodeChars.charAt(c1 >> 2);strin += base64EncodeChars.charAt(((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4));strin += base64EncodeChars.charAt(((c2 & 0xF) << 2) | ((c3 & 0xC0) >> 6));strin += base64EncodeChars.charAt(c3 & 0x3F)}return strin}Page({data: {imageURL: "/assets/images/searchgateway.png",houseId: '',dev_sn: '',msg: ' 正在识别,请稍后 ',// 上行数据(发送给服务器的),// 下行数据(发送给蓝牙设备的)upData: '',downData: '',// 蓝牙模块处理数据 devices: [],deviceId: "",connected: false,chs: [],// 蓝牙可读可写可 noticefycanWrite: false,canNotify: false,setInter0: "",// 蓝牙消息定时器 timestamp: 0,temStr: '',setInter: '',},/** * 生命周期函数 -- 监听页面加载  */onLoad: function(options) {var _this = this;var houseId = options.houseId;var wifissid = options.wifissid;var wifiPassword = options.wifiPassword;console.info("bluetoothgatewaysearch houseId :>>>" + houseId)console.info("bluetoothgatewaysearch wifissid :>>>" + wifissid)console.info("bluetoothgatewaysearch wifiPassword :>>>" + wifiPassword)_this.setData({houseId: houseId,wifissid: wifissid,wifiPassword: wifiPassword})// 调取接口获取下行数据 // 先关闭一下蓝牙设备 wx.closeBluetoothAdapter();// 重新开始获取蓝牙设备 console.log(" ================start get bleDeves================")this.openBluetoothAdapter();// 循环函数,开始不停得遍历蓝牙设备列表,查询指定设备 ID,如 GN 开头,并将设备信息存入设备列表 console.log(" ================start for bleDeves================")},getDownData: function() {var _this = this;var data = {deviceSn: _this.data.deviceSn,wifi_ssid: _this.data.wifissid,wifi_password: _this.data.wifiPassword,canWrite: false,canNotify: false};// 查询房间详情 req.requestToken(ips.GetGatewayNet, data).then((res) => {if (res.code == 0) {_this.setData({downData: res.data.ble_data,})// 链接蓝牙设备 _this.createBLEConnection();// 下发数据给蓝牙 _this.data.setInter0 = setInterval(function() {// 判断是否已经连接成功了蓝牙 console.log("=============================== 每隔 500ms 就判断是否已经设置好蓝牙 ================================")if (_this.data.canWrite && _this.data.canNotify) {clearInterval(_this.data.setInter0)console.log("===============================clearInterval setInter0 ================================")setTimeout(function() {_this.sendDataToBle()}, 500)}}, 200)} else {wx.showModal({title: ' 系统提示 ',content: res.msg,})return false;}}).catch((errMsg) => {// 错误提示信息 return false;});},// 开启手机蓝牙适配 openBluetoothAdapter() {var _this = this;wx.openBluetoothAdapter({success: (res) => {_this.setData({message: ' 蓝牙初始化成功, 正在连接...',})_this.startBluetoothDevicesDiscovery()},fail: (res) => {//console.log('res', JSON.stringify(res))if (res.errCode === 10001) {wx.showModal({title: ' 系统提示 ',content: ' 初始蓝牙失败, 请开启蓝牙和定位功能!',})_this.setData({message: ' 初始蓝牙失败, 请开启蓝牙和定位功能 ',})// 监听蓝牙适配器状态变化事件 wx.onBluetoothAdapterStateChange(function(res) {console.log('onBluetoothAdapterStateChange', res)if (res.available) {_this.startBluetoothDevicesDiscovery()}})}},complete: (res) => {console.log(" 初始化蓝牙适配器完成 ")}})},// 获取本机蓝牙适配状态 getBluetoothAdapterState() {wx.getBluetoothAdapterState({success: (res) => {console.log('getBluetoothAdapterState', res)if (res.discovering) {this.onBluetoothDeviceFound()} else if (res.available) {this.startBluetoothDevicesDiscovery()}}})},// 开始搜索 startBluetoothDevicesDiscovery() {if (this._discoveryStarted) {return}this._discoveryStarted = truewx.startBluetoothDevicesDiscovery({allowDuplicatesKey: true,interval: 0,success: (res) => {console.log(' 寻找新设备启动 success, 开始执行回调函数 ', res)// 寻找到新设备的事件的回调函数 this.onBluetoothDeviceFound()// console.info("当前 devices:" + this.data.devices)},fail: (res) => {console.log(' 寻找新设备启动 fail, 开始执行回调函数 ', res)},complete: (res) => {console.log(' 寻找新设备启动无论失败还是成功,都将执行此方法 ', res)},})},// 停止蓝牙搜索,搜索连接到设备后停止搜索 stopBluetoothDevicesDiscovery() {wx.stopBluetoothDevicesDiscovery()console.log('>>> 停止蓝牙搜索...');},// 寻找到新设备的事件的回调函数,循环函数 onBluetoothDeviceFound() {var _this = this;wx.onBluetoothDeviceFound((res) => {res.devices.forEach(device => {if (!device.name && !device.localName) {return}// 开始正则匹配蓝牙名字,搜索特定设备 var re = device.name.substring(0, 2);// console.info("[re] ...." + JSON.stringify(re))if (re == 'CW') {const foundDevices = _this.data.devices// console.info("[foundDevices] ...." + JSON.stringify(foundDevices))const idx = inArray(foundDevices, 'deviceId', device.deviceId)const data = {}// 去重 if (idx === -1) {data[`devices[${foundDevices.length}]`] = device} else {data[`devices[${idx}]`] = device}_this.setData(data)}})})// console.log("devices >>>" + JSON.stringify(_this.data.devices))},// 开始连接低功耗蓝牙设备。// 若小程序在之前已有搜索过某个蓝牙设备,并成功建立连接,// 可直接传入之前搜索获取的 deviceId 直接尝试连接该设备,无需进行搜索操作。createBLEConnection(e) {var _this = this;// const mac = "19:07:02:17:24:4C"// const mac = "2755F278-6644-C1D2-0378-892B7B169A41"const deviceId = _this.data.deviceId;console.log("===>>> 开始连接低功耗蓝牙,deviceId " + deviceId)// const name = ds.namewx.createBLEConnection({deviceId,success: (res) => {this.setData({message: ' 连接蓝牙成功,正在数据交互...',connected: true,deviceId: deviceId})// 获取蓝牙设备所有服务 (service)。this.getBLEDeviceServices(deviceId)// 该方法回调中可以用于处理连接意外断开等异常情况 wx.onBLEConnectionStateChange(function(res) {console.log(`=========device ${res.deviceId} state has changed, connected: ${res.connected}=========`)if (res.connected == false) {_this.setData({message: ' 操作超时,硬件设备已断开蓝牙...',connected: false,})}})}})// 关闭搜索蓝牙设备, 因为太耗电 this.stopBluetoothDevicesDiscovery()},// 获取蓝牙设备所有服务 (service)getBLEDeviceServices(deviceId) {var _this = this;// console.log("===>>> 开始获取蓝牙设备所有服务 (service)")wx.getBLEDeviceServices({deviceId,success: (res) => {// console.log(">>> 当前蓝牙设备服务个数" + res.services.length)console.log("【当前蓝牙设备服务数据】" + JSON.stringify(res))for (let i = 0; i < res.services.length; i++) {if (res.services[i].isPrimary && res.services[i].uuid == "0000FFF0-0000-1000-8000-00805F9B34FB") {console.log("【当前的 UUID 是:>>>>>>>>>>>>>>>>>>>】" + JSON.stringify(res))_this.getBLEDeviceCharacteristics(deviceId, res.services[i].uuid);return}}}})},// 获取蓝牙设备某个服务中所有特征值 (characteristic)。getBLEDeviceCharacteristics(deviceId, serviceId) {var _this = this;wx.getBLEDeviceCharacteristics({deviceId,serviceId,success: (res) => {console.log('serviceId >>>' + serviceId + ' 获取特征值成功,返回数据 res >>>' + JSON.stringify(res))for (let i = 0; i < res.characteristics.length; i++) {let item = res.characteristics[i]console.info("===>>> 开始遍历特征值,当前 item :" + JSON.stringify(item))// 设备的特征值支持 读的特征值,就读取特征值的二进制数据值 if (item.properties.read) {console.log("serviceId[" + serviceId + "], 第(" + i + ")特征值可 Read, 执行 success ")wx.readBLECharacteristicValue({deviceId,serviceId,characteristicId: item.uuid,})}// 设备的特征值支持 写的特征值,就向低功耗蓝牙设备特征值中写入二进制数据 if (item.properties.write) {console.log("serviceId[" + serviceId + "], 第(" + i + ")特征值可 Write, 执行 success ")_this.setData({canWrite: true})this._deviceId = deviceIdthis._serviceId = serviceIdthis._characteristicId = item.uuid}// 设备的特征值支持 notify 或者 indicate 才可以成功调用。if (item.properties.notify || item.properties.indicate) {console.log("serviceId[" + serviceId + "], 第(" + i + ")特征值可 Notify, 执行 success ")_this.setData({canNotify: true})wx.notifyBLECharacteristicValueChange({deviceId,serviceId,characteristicId: item.uuid,state: true,success: function(res) {console.log(">>>>>>>>>> 启用 Notify 功能成功,res " + JSON.stringify(res));},fail: function(res) {console.log(">>>>>>>>>> 启用 Notify 功能失败,res " + JSON.stringify(res));}})}}// console.log('_this.data.canWrite123 >>>' + _this.data.canWrite)// console.log('_this.data.canNotify123 >>>' + _this.data.canNotify)},fail(res) {console.error('getBLEDeviceCharacteristics', JSON.stringify(res))}})// 监听低功耗蓝牙设备的特征值变化事件。// 必须先启用 notifyBLECharacteristicValueChange 接口才能接收到设备推送的 notification。wx.onBLECharacteristicValueChange((characteristic) => {if (!_this.data.canWrite || !_this.data.canNotify) {return;}console.info("================================ ble characteristic value change ================================================================");var nowTimetamps = new Date().getTime();console.info("nowTimetamps > > > > > > > > > > > > > > > > > > > > > >" + nowTimetamps);console.info("_this.data.timestamp > > > > > > > > > > > > > > > > > > > > > >" + _this.data.timestamp);var oldTimetamps = 0;// 首次 if (_this.data.timestamp == 0) {oldTimetamps = nowTimetamps;} else {oldTimetamps = _this.data.timestamp;}var diff = nowTimetamps - oldTimetamps;var tem = ab2hex(characteristic.value);console.info("diff tims > > > > > > > > > > > > > > > > > > > > > >" + diff + "ms");// 2 包数据之间间隔大于 500ms,则认为是 2 条不同得数据得 if (diff > 500) {_this.setData({upData: tem,temStr: tem,timestamp: nowTimetamps})_this.startSetTimeout();} else {// 2 包之间数据间隔小于 500ms,则认为是同一条数据,分包接收了,先关掉定时发送器,然后进行数据累加 _this.endSetTimeout();var temStr = _this.data.temStr;var newTem = temStr + tem;_this.setData({upData: newTem,temStr: newTem,timestamp: nowTimetamps})// 开启定时发送器 _this.startSetTimeout();}})},// 监听蓝牙设备发送的消息定时器 startSetTimeout: function() {var _this = this;// 将计时器赋值给 setInter, 过 250ms 将发送数据给服务器 _this.data.setInter = setTimeout(function() {console.log('【当前小程序收到网关需要发送至服务器的数据字节长度】> > > > > > > > > > > > > > > > > > > > > > > > > > > > > >' + _this.data.temStr.length / 2);console.log('【当前小程序收到网关需要发送至服务器的数据 temStr】> > > > > > > > > > > > > > > > > > > > > > > > > > > > > >' + _this.data.temStr);var fdStart = _this.data.temStr.indexOf("08");if (fdStart != 0) {console.log('【当前小程序收到网关数据为冗杂数据,程序中断,继续等待网关数据】> > > > > > > > > > > > > > > > > > > > > > > > > > > > > >');return false;}// 上传数据给服务器 _this.uploadData();// 清空缓存和重置时间标识 _this.setData({temStr: "",timestamp: 0})}, 500);},// 清除发送消息给服务器的定时器 endSetTimeout: function() {var _this = this;clearTimeout(_this.data.setInter)console.log('clear setInter > > > > > > > > > > > > > > > > > > > > > > > > > > > > > >');},// 将获取到的下行数据(页面初始化时获取到的数据包)// 发送给蓝牙网关,由蓝牙网关自行处理逻辑 sendDataToBle() {var _this = this;var getData = _this.data.downData;// 按协议组装数据,并分包发送 var base64ToArrayBuffer = wx.base64ToArrayBuffer(getData)let dataView = new DataView(base64ToArrayBuffer)console.log(" send data to ble > -> -> -> -> -> -> -> -> -> -> -> -> > -> -> -> -> -> --> -> -> -> -> -> -> -> -> -> ->" + getData)console.info("deviceId > -> -> -> -> -> -> -> -> -> -> -> -> > -> -> -> -> -> --> -> -> -> -> -> -> -> -> -> ->" + _this._deviceId)console.info("serviceId > -> -> -> -> -> -> -> -> -> -> -> -> > -> -> -> -> -> --> -> -> -> -> -> -> -> -> -> ->" + _this._serviceId)console.info("characteristicId > -> -> -> -> -> -> -> -> -> -> -> -> > -> -> -> -> -> --> -> -> -> -> -> -> -> -> -> ->" + _this._characteristicId)for (var i = 0; i < base64ToArrayBuffer.length; i++) {dataView.setUint8(i, base64ToArrayBuffer.charAt(i).charCodeAt())}let pos = 0;let bytes = base64ToArrayBuffer.byteLength;while (bytes > 0) {let tmpBuffer;if (bytes > 20) {tmpBuffer = base64ToArrayBuffer.slice(pos, pos + 20);pos += 20;bytes -= 20;} else {tmpBuffer = base64ToArrayBuffer.slice(pos, pos + bytes);pos += bytes;bytes -= bytes;}setTimeout(function () {wx.writeBLECharacteristicValue({deviceId: _this._deviceId,serviceId: _this._serviceId,characteristicId: _this._characteristicId,value: tmpBuffer,})}, 100)}},// 上行数据,将网关数据通过小程序,传输给服务器 uploadData: function () {var _this = this;var deviceSn = _this.data.deviceSn;var ble_up_data = _this.data.upData;var houseId = _this.data.houseId;console.info("up deviceSn > > > > > > > > > > > > > > > > > > > > > > > > > > > > > >" + deviceSn)console.info("up ble_up_data > > > > > > > > > > > > > > > > > > > > > > > > > > > > > >" + ble_up_data)var data = {deviceSn: deviceSn,ble_data: ble_up_data}req.requestToken(ips.PostGatewayNet, data).then((res) => {console.log("upload ble data res > > > > > > > > > > > > > > > > > > > > > > > > > > > > > >" + JSON.stringify(res))if (res.code == 0) {_this.onUnload();wx.navigateTo({url: '../../../pages/device/bluetoothpost/bluetoothgatewayadd?deviceSn='+deviceSn +"&houseId="+houseId,})} else {// wx.showModal({// title: '系统信息',// content: res.msg,// })return false;}}).catch((errMsg) => { });},// 关闭低功耗蓝牙连接 closeBLEConnection() {wx.closeBLEConnection({deviceId: this.data.deviceId})this.setData({connected: false,chs: [],canWrite: false,})console.info("close BLEConnection > > > > > > > > > > > > > > > > > > > > > > > > > > > > > >")},/** * 生命周期函数 -- 监听页面卸载  */onUnload: function() {clearInterval(this.data.setInter0)this.endSetTimeout();this.closeBLEConnection();this.stopBluetoothDevicesDiscovery();},/** * 用户点击链接蓝牙  */linkBle: function(e) {var _this = this;var bleName = e.target.dataset.devicesn.name;var deviceSn = bleName.substring(2);var deviceId = e.target.dataset.devicesn.deviceId;_this.setData({deviceSn: deviceSn,deviceId: deviceId})// 获取下行数据, 并下发 _this.getDownData();}}) 
正文完