<template> <view> <!-- pages/comm/comm.wxml --> <text id="v5" class="devices_summary">{{ deviceadd }}</text> <!-- 横向均分 --> <view class="layout_horizontal" id="v1"> <button class="button_sp" hover-class="button_sp2" style="flex: 1" @tap="goclear">清屏</button> <button class="button_sp" style="flex: 1">{{ connectState }}</button> <button class="button_sp" hover-class="button_sp2" style="flex: 1" @tap="gotoback">返回</button> <button class="button_sp" hover-class="button_sp2" style="flex: 1" @tap="gotohome">返回首页</button> </view> <view class="layout_horizontal" id="v2"> <checkbox-group style="flex: 1.45; display: flex" @change="hexsend"> <checkbox class="flex-wrp2" value="send">十六进制发送</checkbox> </checkbox-group> <checkbox-group style="flex: 1.45; display: flex" @change="hexrec"> <checkbox class="flex-wrp2" value="rec">十六进制接收</checkbox> </checkbox-group> <button class="button_sp" hover-class="button_sp2" style="flex: 1" @tap="godisconnect">{{ reconnect }}</button> </view> <view class="layout_horizontal" id="v3"> <view class="vcon" hover-class="vcon2" @tap="settime" style="flex: 1; display: flex; flex-direction: column"> <view class="flex-view-item">自动发送周期</view> <view class="flex-view-item">{{ autoSendInv }}ms</view> </view> <view class="vcon" style="flex: 1; display: flex; flex-direction: column"> <view class="flex-view-item">RX:{{ rxRate }}B/S</view> <view class="flex-view-item">TX:{{ txRate }}B/S</view> </view> <view class="vcon" style="flex: 1; display: flex; flex-direction: column"> <view class="flex-view-item">RX:{{ rxCount }}</view> <view class="flex-view-item">TX:{{ txCount }}</view> </view> </view> <scroll-view class="container" :style="'height: ' + scrollViewHeight + 'px'" :scroll-y="true"> <view style="display: flex"> <view class="note_itemtext">{{ recdata }}</view> </view> </scroll-view> <view class="layout_horizontal" id="v4"> <input class="inputView" @input="voteTitle" :adjust-position="true" style="flex: 1; font-size: 16px; margin-left: 5px" placeholder="写入数据" :value="sendText" /> <button class="button_sp" hover-class="button_sp2" @tap="goautosend">{{ autosendText }}</button> <button class="button_sp" hover-class="button_sp2" @tap="gosend">发送</button> </view> <!-- 弹窗设置时间 --> <view class="modal-mask" @tap="hideModal" @touchmove.stop.prevent="preventTouchMove" v-if="showModal"></view> <view class="modal-dialog" v-if="showModal"> <view class="modal-title">自动发送周期[ms]</view> <view class="modal-content"> <view class="modal-input"> <input placeholder-class="input-holder" type="number" maxlength="5" @input="timeinputChange" class="input" placeholder="时间ms" :value="autoSendInv" /> </view> </view> <view class="modal-footer"> <view class="btn-cancel" @tap="onCancel" data-status="cancel">取消</view> <view class="btn-confirm" @tap="onConfirm" data-status="confirm">确定</view> </view> </view> <!-- 弹出定时发送设置 --> <!-- 弹出提示框 start --> <view class="dialog_screen" @tap="hideModalTips" v-if="showModalStatus"></view> <view :animation="animationData" class="dialog_attr_box" v-if="showModalStatus"> <view style="background: white; position: relative; overflow: hidden"> <view class="dialog_title">{{ showTips }}</view> </view> </view> <!-- 弹出提示框 end --> </view> </template> <script> // pages/comm/comm.js var app = getApp(); var timer; var autoTimer; // 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('').toUpperCase(); } function ab2Str(arrayBuffer) { let unit8Arr = new Uint8Array(arrayBuffer); let encodedString = String.fromCharCode.apply(null, unit8Arr); //var decodedString = decodeURIComponent(mencode.encodeURIComponent((encodedString)));//没有这一步中文会乱码 //var decodedString = mencode.encodeURIComponent((encodedString)); //console.log(decodedString); //return decodedString return encodedString; } function stringToBytes(str) { var ch; var st; var re = []; for (var i = 0; i < str.length; i++) { ch = str.charCodeAt(i); // get char st = []; // set up "stack" do { st.push(ch & 255); // push byte to stack ch = ch >> 8; // shift value down by 1 byte } while (ch); // add stack contents to result // done because chars have "wrong" endianness re = re.concat(st.reverse()); } // return an array of bytes return re; } export default { data() { return { device: null, connected: false, readyRec: false, hexSend: false, hexRec: false, chs: [], deviceadd: ' ', windowHeight: 0, // 页面总高度将会放在这里 navbarHeight: 0, // navbar的高度 headerHeight: 0, // header的高度 scrollViewHeight: 300, // scroll-view的高度 recdata: '', rxCount: 0, txCount: 0, rxRate: 0, txRate: 0, connectState: '正在连接', reconnect: '连接中...', timRX: 0, timTX: 0, sendText: 'POWER', autoSendInv: 10, autosendEn: false, autosendText: '自动发送', showModal: false, showModalStatus: false, showTips: '', name: '', deviceId: '', canWrite: false, animationData: '' }; } /** * 生命周期函数--监听页面隐藏 */, onHide: function () { console.warn('onHide goto disconnect'); if (this.connected) { uni.closeBLEConnection({ deviceId: this.deviceId }); this.setData({ connected: false, reconnect: '重新连接', connectState: '已断开' }); uni.setNavigationBarTitle({ title: '已断开 ' + this.device.name }); console.warn('DisConnect ', this.deviceId); } }, /** * 生命周期函数--监听页面卸载 */ onUnload: function () { app.globalData.saveSetting(this.autoSendInv, this.sendText); if (this.connected) { uni.closeBLEConnection({ deviceId: this.deviceId }); console.warn('DisConnect ', this.deviceId); this.connected = false; } }, /** * 生命周期函数--监听页面加载 */ onLoad: function (options) { console.log('common页面接收全局options', options) //app.readSetting() // this.device = app.globalData.ble_device; this.device = { deviceId: options.deviceId, name: options.name } // console.log('onload -> createBLEConnection', this.device) this.readyRec = false; // this.setData({ // autoSendInv: app.globalData.mautoSendInv, // sendText: app.globalData.msendText // }); // console.log("start ", this.data.autoSendInv, this.data.sendText) // this.Countdown(); //this.showModalTips("开始连接设备....") // if (this.device == null) { // this.calScrolHigh(); // return; // } const deviceId = this.device.deviceId; this.setData({ deviceadd: 'MAC ' + deviceId }); // this.calScrolHigh(); const name = this.device.name; console.log('device = ', this.device); this.serviceu = '0000FFE0-0000-1000-8000-00805F9B34FB'.toUpperCase(); this.txdu = '0000FFE1-0000-1000-8000-00805F9B34FB'.toUpperCase(); this.rxdu = '0000FFE1-0000-1000-8000-00805F9B34FB'.toUpperCase(); console.log('target uuids = ', this.serviceu, this.txdu, this.rxdu); uni.setNavigationBarTitle({ title: '正在连接 ' + this.device.name }); setTimeout(()=>{ uni.createBLEConnection({ deviceId, success: (res) => { console.log('onload -> createBLEConnection', deviceId,res) this.setData({ connected: true, name, deviceId, connectState: '读取服务', reconnect: '断开连接' }); uni.setNavigationBarTitle({ title: '已连接 ' + this.device.name }); //this.showModalTips("读取BLE所有服务") this.getBLEDeviceServices(deviceId); }, fail(res) { console.log('createBLEConnection', res) } }); }, 2000) }, methods: { setData(obj){ for(var key in obj){ this[key] = obj[key] } }, gotohome(){ uni.navigateTo({ url: '/pages/tabBar/component/component111' }) }, goclear() { this.setData({ recdata: '', rxCount: 0, txCount: 0 }); }, Countdown() { var that = this; timer = setTimeout(function () { //console.log("----Countdown----"); that.setData({ rxRate: that.timRX * 2, txRate: that.timTX * 2 }); that.setData({ timRX: 0, timTX: 0 }); that.Countdown(); }, 500); }, autoSend() { //定时发送 var that = this; if (this.connected) { this.autosendEn = true; autoTimer = setTimeout(function () { that.autoSend(); that.gosend(); }, this.autoSendInv); } else { //已经断开了连接 禁止自动 发送 this.autosendEn = false; clearTimeout(autoTimer); this.setData({ autosendText: '自动发送' }); } }, preventTouchMove: function () {}, goautosend() { if (!this.connected) { this.showModalTips('请先连接BLE设备...'); return; } if (!this.autosendEn) { this.autoSend(); this.setData({ autosendText: '停止发送' }); } else { this.autosendEn = false; clearTimeout(autoTimer); this.setData({ autosendText: '自动发送' }); } }, voteTitle: function (e) { this.sendText = e.detail.value; }, calScrolHigh() { var that = this; // 先取出页面高度 windowHeight uni.getSystemInfo({ success: function (res) { that.setData({ windowHeight: res.windowHeight }); } }); // 然后取出navbar和header的高度 // 根据文档,先创建一个SelectorQuery对象实例 let query = uni.createSelectorQuery().in(this); // 然后逐个取出navbar和header的节点信息 // 选择器的语法与jQuery语法相同 query.select('#v1').boundingClientRect(); query.select('#v2').boundingClientRect(); query.select('#v3').boundingClientRect(); query.select('#v4').boundingClientRect(); query.select('#v5').boundingClientRect(); // 执行上面所指定的请求,结果会按照顺序存放于一个数组中,在callback的第一个参数中返回 query.exec((res) => { // 分别取出navbar和header的高度 let navbarHeight = res[0].height + res[4].height; let headerHeight = res[1].height + res[2].height + res[3].height + 15; // 然后就是做个减法 let scrollViewHeight = this.windowHeight - navbarHeight - headerHeight; // 算出来之后存到data对象里面 this.setData({ scrollViewHeight: scrollViewHeight }); }); }, getBLEDeviceServices(deviceId) { console.log('***-getBLEDeviceServices--deviceId----****', deviceId) var that = this; this.readyRec = false; setTimeout(()=>{ uni.getBLEDeviceServices({ deviceId, success: (res) => { var isService = false; console.log('***-getBLEDeviceServices--services----****', res.services) console.log('service size = ', res.services.length); for (let i = 0; i < res.services.length; i++) { // if (res.services[i].isPrimary) { if (this.serviceu == res.services[i].uuid) { //this.showModalTips(this.serviceu+"\r找到服务UUID,正在读取所有特征值") isService = true; this.getBLEDeviceCharacteristics(deviceId, res.services[i].uuid); this.setData({ connectState: '获取特征值' }); } } if (!isService) { //没有找到服务 this.setData({ connectState: 'UUID错误' }); this.showModalTips(this.serviceu + '\r找不到目标服务UUID 请确认UUID是否设置正确或重新连接'); } } }); }, 2000) }, getBLEDeviceCharacteristics(deviceId, serviceId) { const that = this; uni.getBLEDeviceCharacteristics({ deviceId, serviceId, success: (res) => { var ismy_service = false; console.log('compute ', serviceId, this.serviceu); if (serviceId == this.serviceu) { ismy_service = true; console.warn('this is my service '); } console.log('getBLEDeviceCharacteristics success', res.characteristics); for (let i = 0; i < res.characteristics.length; i++) { let item = res.characteristics[i]; if (ismy_service) { console.log('-----------------------'); } console.log('this properties = ', item.properties); if (item.properties.read) { console.log('[Read]', item.uuid); uni.readBLECharacteristicValue({ deviceId, serviceId, characteristicId: item.uuid }); } if (item.properties.write) { this.setData({ canWrite: true }); console.log('[Write]', item.uuid); this._deviceId = deviceId; if (ismy_service && this.txdu == item.uuid) { console.warn('find write uuid ready to ', item.uuid); this._characteristicId = item.uuid; this._serviceId = serviceId; // this.showModalTips(this.txdu+ "\r找到发送特征值") } //this.writeBLECharacteristicValue() } if (item.properties.notify || item.properties.indicate) { console.log('[Notify]', item.uuid); if (ismy_service && this.rxdu == item.uuid) { console.warn('find notity uuid try enablec....', item.uuid); // this.showModalTips(this.rxdu + "\r正在开启通知...") uni.notifyBLECharacteristicValueChange({ //开启通知 deviceId, serviceId, characteristicId: item.uuid, state: true, success(res) { console.warn('notifyBLECharacteristicValueChange success', res.errMsg); that.setData({ connectState: '连接成功' }); // that.showModalTips(that.rxdu + "\r开启通知成功") that.readyRec = true; } }); } } } }, fail(res) { console.error('getBLEDeviceCharacteristics', res); } }); // 操作之前先监听,保证第一时间获取数据 uni.onBLECharacteristicValueChange((characteristic) => { var buf = new Uint8Array(characteristic.value); var nowrecHEX = ab2hex(characteristic.value); console.warn('rec: ', nowrecHEX, characteristic.characteristicId); var recStr = ab2Str(characteristic.value); console.warn('recstr: ', recStr, characteristic.characteristicId); if (this.rxdu != characteristic.characteristicId) { console.error('no same : ', this.rxdu, characteristic.characteristicId); return; } if (!this.readyRec) { return; } var mrecstr; if (this.hexRec) { mrecstr = nowrecHEX; } else { mrecstr = recStr; } if (this.recdata.length > 3000) { this.recdata = this.recdata.substring(mrecstr.length, this.recdata.length); } console.warn('RXlen: ', buf.length); this.setData({ recdata: this.recdata + mrecstr, rxCount: this.rxCount + buf.length, timRX: this.timRX + buf.length }); }); }, writeBLECharacteristicValue() { // 向蓝牙设备发送一个0x00的16进制数据 let buffer = new ArrayBuffer(1); let dataView = new DataView(buffer); dataView.setUint8(0, (Math.random() * 255) | 0); uni.writeBLECharacteristicValue({ deviceId: this._deviceId, serviceId: this._deviceId, characteristicId: this._characteristicId, value: buffer }); }, gotoback: function () { if (this.device == null) { uni.navigateTo({ url: '/pages/index/index' }); return; } clearTimeout(timer); uni.closeBLEConnection({ deviceId: this.deviceId }); this.setData({ connected: false, chs: [] }); uni.navigateBack({ delta: 1 }); }, gosend() { if (!this.connected) { this.showModalTips('请先连接BLE设备...'); return; } var that = this; var hex = this.sendText; //要发送的数据 var buffer1; if (this.hexSend) { //十六进制发送 var typedArray = new Uint8Array( hex.match(/[\da-f]{2}/gi).map(function (h) { return parseInt(h, 16); }) ); console.log('hextobyte ', typedArray); buffer1 = typedArray.buffer; } else { //string发送 var strbuf = new Uint8Array(stringToBytes(hex)); console.log('strtobyte ', strbuf); buffer1 = strbuf.buffer; } console.log('Txbuf = ', buffer1); if (buffer1 == null) { return; } const txlen = buffer1.byteLength; uni.writeBLECharacteristicValue({ deviceId: that._deviceId, serviceId: that._serviceId, characteristicId: that._characteristicId, value: buffer1, success: function (res) { // success that.setData({ txCount: that.txCount + txlen, timTX: that.timTX + txlen }); console.log('success 指令发送成功'); console.log(res); }, fail: function (res) { // fail console.log(res); }, complete: function (res) { // complete } }); }, hexsend: function (e) { console.log('checking ', e.detail.value); var selected; //= e.target.dataset.checks ? false : true; if (e.detail.value.length == 0) { selected = false; } else { selected = true; } this.setData({ hexSend: selected }); console.log('hexsend ', this.hexSend); }, hexrec: function (e) { console.log('checking '); var selected; //= e.target.dataset.checks ? false : true; if (e.detail.value.length == 0) { selected = false; } else { selected = true; } this.setData({ hexRec: selected }); console.log('hexRec = ', this.hexRec); }, godisconnect() { console.log('godisconnect****closeBLEConnection***') if (this.connected) { uni.closeBLEConnection({ deviceId: this.deviceId }); this.setData({ connected: false, reconnect: '重新连接', connectState: '已断开' }); uni.setNavigationBarTitle({ title: '已断开 ' + this.device.name }); this.showModalTips(this.device.name + '已断开连接...'); } else { uni.setNavigationBarTitle({ title: '正在连接 ' + this.device.name }); this.setData({ connectState: '正在连接', reconnect: '连接中...' }); uni.createBLEConnection({ deviceId: this.deviceId, success: (res) => { this.setData({ connected: true, connectState: '读取服务', reconnect: '断开连接', recdata: '', rxCount: 0, txCount: 0 }); uni.setNavigationBarTitle({ title: '已连接 ' + this.device.name }); this.getBLEDeviceServices(this.deviceId); } }); } }, settime() { console.log('Click Time set'); this.autoC = false; this.inputinv = '' + this.autoSendInv; if (this.autosendEn) { //正在自动发送,停止它 this.autosendEn = false; clearTimeout(autoTimer); this.setData({ autosendText: '自动发送' }); } this.setData({ showModal: true }); }, timeinputChange: function (e) { this.autoC = true; this.inputinv = e.detail.value; console.log('minputC', this.inputinv); }, hideModal: function () { this.setData({ showModal: false }); }, onCancel: function () { this.hideModal(); }, onConfirm: function () { this.hideModal(); if (this.autoC) { //this.data.autoSendInv = new Number(this.inputinv) this.setData({ autoSendInv: parseInt(this.inputinv) }); console.log('time change'); } console.log('minputOK', this.inputinv, this.autoSendInv); }, showModalTips: function (str) { var that = this; this.setData({ showTips: str }); // 显示遮罩层 var animation = uni.createAnimation({ duration: 200, timingFunction: 'linear', delay: 0 }); this.animation = animation; animation.translateY(300).step(); this.setData({ animationData: animation.export(), showModalStatus: true }); setTimeout( function () { animation.translateY(0).step(); this.setData({ animationData: animation.export() }); }.bind(this), 200 ); setTimeout(function () { that.hideModalTips(); }, 2000); }, hideModalTips: function () { // 隐藏遮罩层 var animation = uni.createAnimation({ duration: 200, timingFunction: 'linear', delay: 0 }); this.animation = animation; animation.translateY(300).step(); this.setData({ animationData: animation.export() }); setTimeout( function () { animation.translateY(0).step(); this.setData({ animationData: animation.export(), showModalStatus: false }); }.bind(this), 200 ); } } }; </script> <style> /* pages/comm/comm.wxss */ page { color: #333; } .button_sp { margin-left: 5px; margin-right: 5px; margin-top: 5px; background-color: #10b7ff; color: #fff; font-size: 15px; } .button_sp2 { margin-left: 5px; margin-right: 5px; margin-top: 5px; background-color: #757575; color: #fff; } .flex-wrp { height: auto; display: flex; background-color: #ffffff; } .flex-wrp2 { margin-left: 5px; margin-right: 5px; margin-top: 8px; font-size: 12px; } /*横向布局 */ .layout_horizontal { display: flex; /*row 横向 column 列表 */ flex-direction: row; } /*纵向布局 */ .layout_vertical { height: 100rpx; display: flex; /*row 横向 column 列表 */ flex-direction: column; } /*划线 */ .line { background-color: blue; height: 5px; width: 100%; } .div { background-color: white; height: 10px; width: 10px; } .checkbox-hidden { position: absolute; left: -9999px; } .checkbox-icon-group { position: absolute; left: 3px; margin-top: 14px; vertical-align: middle; display: inline-block; border: 1px solid #d1d1d1; background-color: #fff; border-radius: 3px; width: 13px; height: 13px; } .checkbox-icon { position: absolute; top: 1px; left: 1px; } .vcon { margin-left: 5px; margin-right: 5px; margin-top: 5px; margin-bottom: 5px; background-color: #35373a; border-radius: 3px; } .vcon2 { margin-left: 5px; margin-right: 5px; margin-top: 5px; margin-bottom: 5px; background-color: #666666; border-radius: 3px; } .flex-view-item { color: #eee; margin-left: 5px; margin-top: 1px; margin-bottom: 1px; font-size: 13px; display: flex; align-items: center; justify-content: center; } .container { background-color: #fff; background-size: cover; width: 98%; margin-left: 0.7%; justify-content: center; border-radius: 3px; border: 1px solid #111; white-space: normal; } .inputView { border: 1px solid #ddd; border-radius: 5px; margin: 5px; margin-top: auto; } .txt-light { color: #0b0d0d; font-size: 13px; margin-left: 3px; white-space: pre-line; display: block; width: 100%; } .note_itemtext { display: -webkit-box; font-size: 26rpx; color: #000000; line-height: 40rpx; word-break: break-all; -webkit-box-orient: vertical; overflow: hidden; text-overflow: ellipsis; } .blank { height: 150rpx; } .devices_summary { margin-top: 5px; font-size: 16px; color: #222; align-items: center; justify-content: center; display: flex; } .show-btn { margin-top: 100rpx; color: #22cc22; } .modal-mask { width: 100%; height: 100%; position: fixed; top: 0; left: 0; background: #000; opacity: 0.5; overflow: hidden; z-index: 9000; color: #fff; } .modal-dialog { width: 540rpx; overflow: hidden; position: fixed; top: 50%; left: 0; z-index: 9999; background: #f9f9f9; margin: -180rpx 105rpx; border-radius: 36rpx; } .modal-title { padding-top: 50rpx; font-size: 36rpx; color: #030303; text-align: center; } .modal-content { padding: 50rpx 32rpx; } .modal-input { display: flex; background: #fff; border: 2rpx solid #ddd; border-radius: 4rpx; font-size: 28rpx; } .input { width: 100%; height: 82rpx; font-size: 28rpx; line-height: 28rpx; padding: 0 20rpx; box-sizing: border-box; color: #333; } input-holder { color: #666; font-size: 28rpx; } .modal-footer { display: flex; flex-direction: row; height: 86rpx; border-top: 1px solid #dedede; font-size: 34rpx; line-height: 86rpx; } .btn-cancel { width: 50%; color: #333; text-align: center; border-right: 1px solid #dedede; } .btn-confirm { width: 50%; color: #10b7ff; text-align: center; } .dialog_screen { width: 100%; height: 100%; position: fixed; top: 0; left: 0; background: #000; opacity: 0.2; overflow: hidden; z-index: 1000; color: #fff; } .dialog_attr_box { width: 100%; overflow: hidden; position: fixed; bottom: 0; left: 0; z-index: 2000; background: #fff; padding-top: 1px; } .dialog_title { font-size: 16px; height: 60px; display: flex; align-items: center; padding: 10px; background: #10b7ff; color: white; justify-content: center; display: -webkit-box; line-height: 40rpx; word-break: break-all; -webkit-box-orient: vertical; overflow: hidden; text-overflow: ellipsis; } .dialog_content { position: relative; float: left; padding: 10px 10px; width: 25%; box-sizing: border-box; } </style>