概述:Uniapp实现连接电子秤蓝牙,实现监听数据同步回显,实现数据实时监听,并在弹框中打印日志功能,本文将具体阐述。
蓝牙电子秤连接步骤:
初始化蓝牙---搜索蓝牙---连接蓝牙(蓝牙设备值)---获取服务值(蓝牙设备值)---获取特征值(蓝牙设备值+蓝牙服务值)---监听---处理蓝牙ArrayBufer数据---页面回显
疑难杂症场景+解决办法:
1. 连接上了,但是获取特征值,无回调无任何反应?
答:是因为服务值和设备id对应不上,请检查该设备id对应的服务值是否正确
2. 监听成功,但是在页面显示电子秤数据的时候,出现闪屏或数据为空
答:是因为notifyBLECharacteristicValueChange方法,返回的ArrayBufer格式不正确,比如我这边就是13.0 13.0,返回了两次,正确的应该是:13.0,只有一次,此种问题请联系电子秤商家的技术人员,更改蓝牙电子秤的传输数据格式
3. notifyBLECharacteristicValueChange返回的数据格式可以修改吗?比如我截取一个重复的.
答:不能!因为ArrayBuffer二进制数据,我们只能解析和读取,无法对它原数据进行修改,所以如果想通过处理数据方式去重,显然不可以
4. 监听启动成功,但是后续无任何反应和回调?
答:设备服务值和特征值不匹配,因为每个服务值都会有多个对应的特征值,如果是自动连接循环获取,他们并不是一一对应,则会出现此问题,所以请排查特征值和服务值是否一一对应。
5.可以让后端通过chrome://bluetooth-internals/#devices/c8:b2:1e:97:9e:a3 页面,获取服务值和特征值,然后通过接口传给前端,从而实现自动连接蓝牙,同时监听电子秤数据吗?
答:不能!因为虽然电子秤设备id是固定的,但是它的服务值会有多个,每个服务值对应的特征值也不一样,而特征值分为:可读、可写、可监听等几个类型,只有选择对应类型为true的特征值,才可以实现监听功能,通过后端接口拿,如果拿到了不具备监听功能的特征值,或者服务值和特征值不匹配,就会监听失败。
<template>
<view class="main-container">
<u-navbar :title="title"></u-navbar>
<view class="tit-nav mt-50">
<view class="border-left"></view>
<view class="user-type">{{ pageType == 'manger' ? '管理员回收' : '用户回收' }}</view>
</view>
<view class="common-box operator-info">
<view class="flex">
<image class="user-img" mode="aspectFill" src="@/static/images/login.png"></image>
<view class="user-name ellipsis">{{ userData.name }}</view>
</view>
<image class="check-img" mode="aspectFill" src="@/static/images/check-type.png" @click="getCheckType"></image>
</view>
<view class="tit-nav">
<view class="border-left"></view>
<view class="user-type">选择回收类别</view>
</view>
<view class="common-box large-category">
<view class="flex">
<view class="name add-weight-tit">选择废品类型</view>
<view class="desc-type">选择您要回收的废品类型,再进行称重</view>
</view>
<view class="category-box-list">
<view class="category-list" v-for="(item, index) in largeCategory" :key="index" :class="currentActive == index ? 'active-btn' : ''" @click="getSelectType(item, index)">
<view class="category-name">{{ item.name }}</view>
</view>
</view>
<view class="common-box accounting-box">
<view class="flex" @click="getInput">
<view class="flex">
<input class="input-number" :class="weightNumber.length == 6 ? 'customer-input-number' : ''" placeholder="请输入重量" v-model="weightNumber" placeholder-style="color:#7D7D7D; font-size: 22rpx;" maxlength="6" type="text" data-datatag='weightNumber' @blur="blurNumberInput($event)" @focus="onfocus" placeholder-class="weight-placeholder" :disabled="weightInputFlag" @input="updateData" />
<span class="unit number-unit">{{ weightInputFlag ? copyPopData.unit : selectWasteData.unit }}</span>
</view>
<view class="flex money-box">
<view class="money" :class="weightNumber.length == 6 ? 'customer-input-money' : ''">{{ totalAmount }}</view>
<span class="unit">元</span>
</view>
</view>
<view class="flex-img" v-if="bluetoothLinkStatus">
<image class="link-image" mode="aspectFill" src="@/static/images/success-link.png"></image>
<view class="bluetooth-desc">{{ bluetoothCustomerName }}电子秤已连接</view>
</view>
<image v-else class="link-image" mode="aspectFill" src="@/static/images/no-link.png"></image>
</view>
<view class="flex">
<view v-if="!bluetoothLinkStatus" class="common-btn link-btn mr-16" @click="getLink">连接蓝牙</view>
<view class="common-btn link-btn" @click="getLinkStatus">连接状态检测</view>
</view>
<view class="accounting-list" :class="accountingList.length > 4 ? 'scroll-box' : ''">
<view class="accounting-item" v-for="(item, index) in accountingList" :key="index">
<view class="item-list ellipsis">{{ item.time }}</view>
<view class="item-list">{{ item.name }}</view>
<view class="item-list">{{ item.weight }}{{ item.unit }}</view>
<view class="item-list">{{ item.total }}元</view>
<image v-if="item.deleteFlag" class="delete-image" mode="aspectFill" src="@/static/images/delete-img.png" @click="getDelete(index)"></image>
<!-- 占位符 -->
<view v-else class="delete-image"></view>
</view>
</view>
<view v-if="totalEndAmount > 0 || (bluetoothLinkStatus && totalAmount > 0)" class="bottom-nav">
<view class="flex">
<view class="summarize">总结:</view>
<view class="total-number">{{ totalEndAmount }}</view>
<view class="summarize mount-unit">元</view>
</view>
<view class="flex">
<view v-if="bluetoothLinkStatus" class="common-btn" :class="continueBtnFlag && !finshRecycleFlag ? 'disabled-btn' : ''" @click="getContinue">完成称重</view>
<view class="common-btn over-btn" @click="getOrderSure" :class="finshRecycleFlag ? 'disabled-btn' : ''">确认订单</view>
</view>
</view>
</view>
<lf-popup v-model="showPopup" class="customer-popup">
<view class="tit-nav">
<view class="border-left"></view>
<view class="user-type">{{ selectlargeTypeData.name }}</view>
</view>
<view class="content-box">
<view class="list-item" v-for="(item, index) in wasteList" :key="index" :class="selectActive == index ? 'select-btn' : ''" @click="getCurrent(item, index)">
<view class="">
<view class="item-name">{{ item.name }}</view>
<view class="price">¥{{ (item.recyclingPrice)/100 }}/{{ item.unit }}</view>
</view>
</view>
<view class="bottom-nav popup-bottom">
<view class="flex">
<view class="common-btn border-btn" @click="getCancel">取消</view>
<view class="common-btn over-btn" @click="getSure" :class="selectActive == -1 ? 'disabled-btn' : ''">确认</view>
</view>
</view>
</view>
</lf-popup>
<lf-popup v-model="showLogPopup" class="customer-popup">
<view class="titile-box">
<image class="arr-img" mode="aspectFill" src="@/static/images/arr-down.png" @click="onClose"></image>
<view class="popup-tit" @click="onClose">蓝牙连接状态</view>
</view>
<scroll-view v-if=" logStatusList.length > 0" scroll-y="true" class="scroll-box">
<view class="" v-for="(item, index) in logStatusList" :key="index">
<view class="item-status">{{ item.name }}</view>
</view>
</scroll-view>
<view v-esle class="empty-box-center" >
<u-empty text="暂未连接" mode="favor"></u-empty>
</view>
</lf-popup>
<Bluetooth ref="BluetoothRef" @getInitBluetooth="getInitBluetooth" @getStatusLog="getStatusLog"></Bluetooth>
</view>
</template>
<script>
import Bluetooth from '../Bluetooth.vue'
export default {
components: { Bluetooth },
data() {
return {
logStatusList: [],
bluetoothName: '',
copyPopData: {},
focusFlag: false,
title: '开始回收',
optionsData: {},
userData: {},
pageType: '',
previousSelect: {},
viewDetail: false,
totalAmount: 0,
showPopup: false,
showLogPopup: false,
weightType: 0,
bluetoothCustomerName: '',
finshRecycleFlag: true,
continueBtnFlag: false,
bluetoothLinkStatus: false,
largeCategory: [],
weightNumber: '',
currentActive: -1,
selectActive: -1,
selectCurrentData: {},
weightInputFlag: true,
selectWasteData: {},
selectlargeTypeData: {},
accountingList: [],
wasteList: [],
totalEndAmount: 0,
historySelectIndex: -1
};
},
watch: {
showPopup(status) {
// 弹框关闭,如果未选择废品单价。则回显上次选中
if (!status) {
if (this.historySelectIndex !== -1) {
this.currentActive = this.historySelectIndex
}
}
},
},
onLoad(options) {
this.optionsData = options
this.pageType = options.pageType
this.getInit()
this.getUserInfo()
},
methods: {
getInit() {
this.weightType = 0
this.viewDetail = false
this.focusFlag = false
this.weightInputFlag = true
this.continueBtnFlag = false
this.finshRecycleFlag = true
this.bluetoothLinkStatus = uni.getStorageSync('bluetoothLinkStatus')
if (this.optionsData.qrCodeUid) {
this.getUserInfo('qrCode')
}
this.$H.get('/app-api/recycling/app/category/list').then(res => {
if(res.code == 0) {
this.largeCategory = res.data
} else {
uni.showToast({
title: res.msg,
icon: "none",
duration: 2000
})
}
})
},
getInitBluetooth(status, value, type) {
if (JSON.stringify(uni.getStorageSync('bluetoothLanyaData')) !== '{}') {
this.bluetoothName = uni.getStorageSync('bluetoothLanyaData').name
}
// 蓝牙每次断开或第一次重新连接,重置数据
if (type == 'init') {
this.getInitData()
this.bluetoothLinkStatus = true
}
this.weightNumber = value
this.weightType = status ? 1 : 0
const moneyTotal = (this.selectWasteData.recyclingPrice * this.weightNumber)/100
if (moneyTotal > 0) {
const numStr = moneyTotal.toString();
const decimalIndex = numStr.indexOf('.');
if (decimalIndex !== -1) {
// 截取小数点后一位
this.totalAmount = numStr.slice(0, decimalIndex + 3);
// 如果是整数,直接返回
} else {
this.totalAmount = numStr
}
} else {
this.totalAmount = 0
}
},
getStatusLog(data) {
data.forEach((item, index) => {
this.$set(this.logStatusList, index, item)
})
this.bluetoothCustomerName = this.bluetoothName
},
getLinkStatus() {
this.showLogPopup = true
},
getUserInfo(qrCode) {
if (qrCode) {
this.$H.get('/app-api/recycling/app/recycler-manager/getUserInfo', {
uid: this.optionsData.qrCodeUid
}).then(res => {
if(res.code == 0) {
this.userData = res.data
} else {
uni.showToast({
title: res.msg,
icon: "none",
duration: 2000
})
}
})
} else {
if (this.pageType == 'userRecyle') {
this.userData.name = this.optionsData.userName
} else {
this.userData = uni.getStorageSync('managerInfo')
}
}
},
getCheckType() {
uni.navigateTo({
url: '/pages/recycleType/recycleType'
})
},
onfocus() {
this.weightNumber = ''
},
getInput() {
if (!this.bluetoothLinkStatus) {
this.getValite()
}
},
getLink() {
this.$refs.BluetoothRef.getInitConcat()
},
blurNumberInput(e) {
if(!e.target.value) {
this.weightNumber = ''
} else {
if (this.totalAmount > 0) {
this.getAddRecycleList()
}
}
},
getContinue() {
this.getValite()
},
getValite() {
if (JSON.stringify(this.selectlargeTypeData) == '{}') {
uni.showToast({
title: '请选择废品类别!',
icon: "none",
duration: 1500
})
return
} else if (JSON.stringify(this.selectWasteData) == '{}') {
uni.showToast({
title: '请选择废品单价!',
icon: "none",
duration: 1500
})
return
}
if (this.totalAmount > 0) {
this.getAddRecycleList()
}
},
getAddRecycleList() {
this.finshRecycleFlag = false
this.sumTotalData()
this.accountingList.forEach((item) => {
item.deleteFlag = true
})
},
updateData(e) {
this.$nextTick(() => {
this.weightNumber = e.detail.value.replace(/[^\d.]/g, '')
})
if(e.detail.value=='.'){
this.$nextTick(() => {
this.weightNumber = '';
})
return;
} else if((e.detail.value.split('.').length - 1)>1){//保留小数点后2位
this.$nextTick(() => {
this.weightNumber = this.weightNumber.substring(0,this.weightNumber.length-1);
})
return;
} else if(e.detail.value.split('.').length>1){//小数点只能输入一个
if((e.detail.value.split('.')[1].length>2)){
this.$nextTick(() => {
this.weightNumber = this.weightNumber.substring(0,this.weightNumber.length-1);
})
return;
}
}
let resultMoney = 0
let moneyData = (this.selectWasteData.recyclingPrice * this.weightNumber)/100
const numStr = moneyData.toString();
const decimalIndex = numStr.indexOf('.');
if (decimalIndex !== -1) {
// 截取小数点后一位
resultMoney = numStr.slice(0, decimalIndex + 3);
// 如果是整数,直接返回
} else {
resultMoney = numStr
}
this.totalAmount = resultMoney
if (this.accountingList.length > 0) {
this.accountingList.forEach((item) => {
item.deleteFlag = false
})
}
},
getSelectType(data, index) {
this.selectActive = -1
if (!this.viewDetail) {
this.currentActive = index
this.showPopup = true
this.selectlargeTypeData = data
this.wasteList = data.recyclingPrices
// 点击相同父级分类,选中 且未添加
if (this.previousSelect.categoryId == data.categoryId) {
this.wasteList.forEach((item, index) => {
if (item.categoryId == this.selectWasteData.categoryId) {
this.selectActive = index
// this.currentActive = index
}
})
}
}
},
getCurrent(data, index) {
this.selectActive = index
this.selectCurrentData = data
},
getSure() {
// 蓝牙已连接
if (this.bluetoothLinkStatus) {
if (this.selectActive == -1) {
uni.showToast({
title: '请选择废品单价!',
icon: "none",
duration: 1500
})
} else {
this.selectSureData()
this.showPopup = false
this.continueBtnFlag = false
this.totalAmount = (this.selectWasteData.recyclingPrice * this.weightNumber)/100
}
this.sumAmount()
this.finshRecycleFlag = true
} else {
if (this.selectActive == -1) {
uni.showToast({
title: '请选择废品单价!',
icon: "none",
duration: 1500
})
} else {
this.selectSureData()
this.weightNumber = ''
this.totalAmount = 0
this.weightInputFlag = false
this.showPopup = false
}
}
},
selectSureData() {
this.focusFlag = false
// 上一次点击的父级数据
this.historySelectIndex = this.currentActive
// 上一次点击的子级数据
this.previousSelect = this.selectlargeTypeData
this.selectWasteData = this.selectCurrentData
this.copyPopData = JSON.parse(JSON.stringify(this.selectCurrentData))
},
getCancel() {
this.showPopup = false
},
getDelete(index) {
this.accountingList.splice(index, 1)
this.sumAmount()
},
sumAmount() {
let sum = 0
let result = 0
this.accountingList.forEach((item) => {
sum += Number(item.total)
})
result = (Math.round(Number(sum) * 100))/100
this.totalEndAmount = result
},
sumTotalData() {
let currentData = new Date()
let hour = currentData.getHours()
let minutes = currentData.getMinutes()
let seconds = currentData.getSeconds()
if (hour >= 0 && hour <= 9) {
hour = '0' + hour
}
if (minutes >= 0 && minutes <= 9) {
minutes = '0' + minutes
}
if (seconds >= 0 && seconds <= 9) {
seconds = '0' + seconds
}
let currentTime = hour + ':' + minutes + ':' + seconds
this.focusFlag = true
this.accountingList.push({
time: currentTime, name: this.selectWasteData.name, weight: this.weightNumber, total: this.totalAmount, unit: this.selectWasteData.unit, categoryName: this.selectWasteData.name, categoryId: this.selectWasteData.categoryId
})
// if(e.target.value && e.target.value.indexOf('.') <= 0) {
// this.weightNumber = e.target.value + '.00'
// }
this.historySelectIndex = -1
this.currentActive = -1
this.weightInputFlag = true
this.selectlargeTypeData = {}
this.selectWasteData = {}
this.previousSelect = {}
// 蓝牙为已连接状态
if (this.bluetoothLinkStatus) {
// 添加完成标记为禁状态
this.continueBtnFlag = true
}
this.sumAmount()
},
getInitData() {
this.focusFlag = false
this.historySelectIndex = -1
this.selectActive = -1
this.viewDetail = false
this.totalEndAmount = 0
this.totalAmount = 0
this.accountingList = []
this.currentActive = -1
this.selectActive = -1
this.weightInputFlag = true
this.selectlargeTypeData = {}
this.selectCurrentData = {}
this.selectWasteData = {}
this.previousSelect = {}
},
getOrderSure() {
let sumbitData = []
this.accountingList.forEach((item) => {
sumbitData.push({
weight: item.weight,
categoryId: item.categoryId,
categoryName: item.categoryName
})
})
let onlineParams = {}
// 线上
if (this.optionsData.qrCodeUid) {
onlineParams = {
reqVoList: sumbitData,
uid: this.optionsData.qrCodeUid,
weightType: this.weightType
}
} else {
onlineParams = {
reqVoList: sumbitData,
weightType: this.weightType,
orderNo: this.optionsData.orderNo
}
}
// 线下
let OfflineParams = {
reqVoList: sumbitData,
money: this.totalEndAmount,
weightType: this.weightType,
settleType: 0
}
if (!this.finshRecycleFlag) {
uni.setStorageSync('OnlineData', onlineParams)
uni.setStorageSync('OfflineData', OfflineParams)
uni.setStorageSync('recycleList', this.accountingList)
uni.navigateTo({
url: `/pages/confirmOrder/confirmOrder?pageType=${this.pageType}&operateType=${this.optionsData.operateType}`
})
}
},
getUserApi(api, params) {
this.$H.post(api, params).then(res => {
if (res.code == 0) {
this.commonModel()
} else {
uni.showToast({
title: res.msg,
icon: "none",
duration: 2000
})
}
})
},
getMangerApi(api, params) {
this.$H.post(api, params).then(res => {
if (res.code == 0) {
this.commonModel()
} else {
uni.showToast({
title: res.msg,
icon: "none",
duration: 2000
})
}
})
},
commonModel() {
uni.showToast({
title: '回收成功!',
icon: "none",
duration: 1500
})
this.viewDetail = true
this.weightInputFlag = true
},
onClose() {
this.showLogPopup = false
}
}
};
</script>
<style lang="scss" scoped>
/deep/.weight-placeholder {
font-weight: 500!important;
line-height: 90rpx!important;
font-size: 32rpx!important;
}
.bottom-nav {
.flex {
height: 100rpx;
.summarize {
color: #7D7D7D;
line-height: 52rpx;
height: 50rpx;
}
.mount-unit {
margin-left: 5rpx;
}
.total-number {
font-weight: 600;
font-size: 40rpx;
}
}
}
.over-btn {
margin-left: 16rpx;
}
.main-container {
height: 100%;
.flex {
display: flex;
align-items: center;
}
.operator-info {
.check-img {
width: 45rpx;
height: 45rpx;
}
}
.common-box {
padding: 24rpx;
border-radius: 24rpx;
margin: 16rpx 0 32rpx 0;
background-color: #FFFFFF;
}
.large-category {
.category-box-list {
display: flex;
flex-wrap: wrap;
margin-top: 16rpx;
.category-list {
margin:0 24rpx 24rpx 0;
.category-name {
color: #000000;
height: 64rpx;
width: max-content;
font-size: 28rpx;
padding: 12rpx 32rpx;
display: flex;
align-items: center;
border-radius: 88rpx;
justify-content: center;
background-color: #F4F4F4;
}
}
.active-btn {
.category-name {
color: #FFFFFF;
background-color: #32C25D;
}
}
}
}
.accounting-box {
padding: 20rpx 24rpx 5rpx 24rpx;
margin: 16rpx 0 8rpx 0;
background-color: #E9FFF0;
.unit {
width: 63rpx;
line-height: 75rpx;
height: 55rpx;
font-size: 32rpx;
}
.flex {
position: relative;
.input-number {
width: 180rpx;
font-size: 68rpx;
font-weight: 800;
height: 68rpx;
min-height: 68rpx;
}
.number-unit::after {
top: -5rpx;
right: -25rpx;
width: 2rpx;
content: '';
height: 80rpx;
display: inline-block;
position: absolute;
background-color: #EEEEEE;
}
.customer-input-number {
padding-top: 5rpx;
width: 200rpx;
font-size: 50rpx;
}
}
.money-box {
margin-left: 55rpx;
height: 68rpx;
line-height: 85rpx;
.money {
width: 240rpx;
font-size: 68rpx;
font-weight: 600;
height: 68rpx;
line-height: 75rpx;
}
.customer-input-money {
font-size: 50rpx;
}
}
}
.link-btn {
width: fit-content;
padding: 0 24rpx;
font-size: 22rpx;
line-height: 44rpx;
height: 44rpx;
margin-top: 25rpx;
background: #0A84FF;
}
.link-image {
width: 180rpx;
height: 34rpx;
margin-top: 20rpx;
}
.customer-popup {
.content-box {
display: flex;
flex-wrap: wrap;
margin-top: 16rpx;
.list-item {
width: 219rpx;
display: flex;
padding: 12rpx 0;
border-radius: 24rpx;
margin-right: 16rpx;
align-items: center;
justify-content: center;
margin-bottom: 16rpx;
background-color: #F4F4F4;
.item-name {
text-align: center;
font-size: 28rpx;
}
.price {
font-size: 23rpx;
}
}
.list-item:nth-child(3n) {
margin-right: 0;
}
.select-btn {
color: #FFFFFF;
background-color: #32C25D;
}
}
.popup-bottom {
justify-content: flex-end;
}
}
.flex-img {
display: flex;
align-items: center;
margin: 10rpx 0;
.link-image {
width: 28rpx;
height: 28rpx;
margin-top: 0;
margin-right: 4rpx;
}
.bluetooth-desc {
color: #7D7D7D;
font-size: 24rpx;
}
}
}
.scroll-box {
height: 450rpx;
}
.customer-popup {
.tiit {
text-align: center;
font-size: 33rpx;
font-weight: 600;
margin-bottom: 20rpx;
}
.titile-box {
display: flex;
align-items: center;
padding: 0 0 10rpx 0;
justify-content: flex-start;
.arr-img {
width: 27rpx;
height: 16rpx;
padding-right: 0;
margin-right: 230rpx;
}
.popup-tit {
color: #333333;
font-size: 30rpx;
font-weight: 600;
}
.common-btn {
height: 50rpx;
width: fit-content;
padding: 0 20rpx;
font-size: 28rpx;
}
}
.scroll-box {
height: 1120rpx;
.item-status {
font-size: 30rpx;
padding: 10rpx 0;
border-bottom: 2rpx solid #F6F6F6;
}
}
}
.empty-box-center {
margin-top: 400rpx;
}
</style>
Bluetooth.vue
<template>
<lf-popup v-model="showLinkPopup" class="customer-popup">
<view class="lanya-container">
<view class="tit-flex">
<view class="tit">当前可用蓝牙设备</view>
<view class="button"></view>
<button type="default" @click="getAdapter">搜索蓝牙</button>
</view>
<view v-if="bluetoothList.length == 0" class="empty-box-center" >
<u-empty text="暂无设备" mode="favor"></u-empty>
</view>
<view v-else class="lanya-list" v-for="(item, index) in bluetoothList" :key="index" @click="getOperate(item.deviceId)">
<view class="bluetooth-tittle">蓝牙名称:{{ item.name }}</view>
<!-- <button type="default">{{ lanya.deviceId !== item.deviceId ? '连接' : '已连接' }} </button> -->
<button type="default" v-if="lanya.deviceId !== item.deviceId">连接</button>
<button class="warn-btn" type="warn" v-else>断开</button>
</view>
</view>
</lf-popup>
</template>
<script>
export default {
data() {
return {
lanya: {},
deviceId: '',
lastValue: 0,
bluetoothList: [],
showLinkPopup: false,
bluetoothErrorLog: [],
linkCharacteristicId: ''
};
},
methods: {
getClear(type) {
this.lanya = {}
if (type !== 'parentClear') {
this.bluetoothList = []
}
},
getBluetoothStatus() {
console.log(uni.getStorageSync('bluetoothLinkStatus'), 'aaaa')
if (uni.getStorageSync('bluetoothLinkStatus')) {
this.monitor()
}
},
getLink() {
this.showLinkPopup = true
// 未连接
// if (!uni.getStorageSync('bluetoothLinkStatus')) {
// this.getAdapter()
// }
},
getAdapter() {
this.bluetoothList = []
uni.openBluetoothAdapter({
// 蓝牙初始化成功执行
success: (res) => {
this.bluetoothErrorLog.push({
name: '蓝牙初始化成功...'
})
this.$emit('getStatusLog', this.bluetoothErrorLog)
this.searchBlue()
},
// 蓝牙初始化失败执行
fail: (err) => {
if (err.errCode == 10001) {
// 这种情况是未打开蓝牙 就需要提示用户检测蓝牙并打开
uni.showToast({
title: "请检查蓝牙是否开启!",
icon: "none",
duration: 2000,
});
this.bluetoothErrorLog.push({
name: '蓝牙初始化失败... 请检查蓝牙是否开启',
})
this.$emit('getStatusLog', this.bluetoothErrorLog)
}
this.bluetoothErrorLog.push({
name: '蓝牙初始化失败...' + JSON.stringify(err)
})
this.$emit('getStatusLog', this.bluetoothErrorLog)
}
})
},
//搜索蓝牙
searchBlue() {
this.bluetoothList = []
uni.showLoading({
title: '蓝牙搜索中...'
});
this.$emit('getStatusLog', this.bluetoothErrorLog)
uni.startBluetoothDevicesDiscovery({
success: () => {
setTimeout(() => {
uni.getBluetoothDevices({
success: (res) => {
// 过滤掉未知设备
var arr = []
res.devices.forEach((element) => {
if (element.name !== "未知设备") {
arr.push(element);
}
})
if (arr.length == 0) {
uni.showToast({
title: "未查询到可用设备,请重新扫描",
duration: 1000,
icon: "none",
});
this.bluetoothErrorLog.push({
name: '未查询到可用设备,请重新扫描'
})
this.$emit('getStatusLog', this.bluetoothErrorLog)
} else {
// 最终具有名称的设备列表
let arr1 = []
arr.map((i) => {
if (i.name) {
arr1.push(i);
}
})
this.bluetoothList = arr1
uni.hideLoading()
}
}
})
}, 2000)
}
})
},
//连接蓝牙
getOperate(e) {
this.deviceId = e
// 已连接,断开连接
if (this.lanya.deviceId == e) {
this.closelanya(e)
// 未连接,手动连接
} else {
uni.setStorageSync('linkDeviceId', e)
uni.showLoading({
title: '蓝牙连接中...'
});
this.bluetoothErrorLog.push({
name: '蓝牙连接中...'
})
this.$emit('getStatusLog', this.bluetoothErrorLog)
setTimeout(() => {
uni.hideLoading()
this.bluetoothErrorLog.push({
name: '蓝牙连接失败,请稍后重试...'
})
this.$emit('getStatusLog', this.bluetoothErrorLog)
}, 4000)
// 手环这一类的设备 可能会对程序造成干扰 会一直显示设备已经连接
uni.getConnectedBluetoothDevices({
success: (res) => {
if (res.devices.length !== 0) {
uni.hideLoading()
// 这里就需要提示用户蓝牙已连接
uni.showModal({
title: "提示!",
content: "当前蓝牙已于名称为" + res.devices[0].name + "的设备连接,是否断开连接?",
success: (row) => {
if (row.confirm) {
// 断开当前蓝牙连接
this.closelanya(res.devices[0].deviceId)
} else {
// 用户取消之后不需要做任何操作
console.log('用户点击了取消')
}
}
});
} else {
uni.createBLEConnection({
deviceId: this.deviceId,
timeout: 4000,
success: (res) => {
// 连接成功之后可以再次进行查询一下当前连接的蓝牙信息,保存下载,后面方便使用
uni.getConnectedBluetoothDevices({
success: (devicess) => {
// devicess为当前已经连接的蓝牙列表
// 在这判断一下有没有蓝牙,如果有就证明确实已经连接成功
if (devicess.devices[0]) {
// 到这里就可以提示用户成功,并且吧蓝牙信息保存下来方便后面使用
this.lanya = devicess.devices[0]
uni.setStorageSync('bluetoothLanyaData', devicess.devices[0])
uni.setStorageSync('bluetoothLinkStatus', true)
this.$emit('getInitBluetooth', true, 0, 'init')
this.bluetoothErrorLog.push({
name: '当前设备:' + JSON.stringify(devicess.devices[0])
})
this.$emit('getStatusLog', this.bluetoothErrorLog)
setTimeout(() => {
this.lianjielanya()
}, 1000)
this.stopBluetoothDevicesDiscovery();
} else {
uni.setStorageSync('bluetoothLanyaData', {})
uni.setStorageSync('bluetoothLinkStatus', false)
}
}
})
},
fail: (err) => {
// 在这里查看连接失败的信息,判断为什么连接失败
this.bluetoothErrorLog.push({
name: '蓝牙连接失败' + JSON.stringify(err)
})
this.$emit('getStatusLog', this.bluetoothErrorLog)
// uni.showToast({
// title: "蓝牙连接失败,请稍后重新连接!",
// icon: "none",
// duration: 2000,
// });
}
})
}
},fail: (err) => {
// 在这里查看连接失败的信息,判断为什么连接失败
this.bluetoothErrorLog.push({
name: '蓝牙连接失败' + JSON.stringify(err)
})
this.$emit('getStatusLog', this.bluetoothErrorLog)
uni.hideLoading()
}
})
}
},
lianjielanya() {
let notifyStatus = false
this.bluetoothErrorLog.push({
name: '正在自动连接中,连接成功后,自动启动监听设备deviceId...' + this.deviceId
})
this.$emit('getStatusLog', this.bluetoothErrorLog)
var that = this;
uni.getBLEDeviceServices({
deviceId: that.deviceId,
success(res) {
let serviceId = "";
that.bluetoothErrorLog.push({
name: '我是服务值列表:' + JSON.stringify(res.services)
})
that.$emit('getStatusLog', that.bluetoothErrorLog)
for (var s = 0; s < res.services.length; s++) {
// 服务值uuid列表
let serviceId = res.services[s].uuid
uni.getBLEDeviceCharacteristics({
deviceId: that.deviceId,
serviceId: serviceId,
success(ress) {
that.bluetoothErrorLog.push({
name: '我是特征值列表:' + JSON.stringify(ress.characteristics)
})
that.$emit('getStatusLog', that.bluetoothErrorLog)
var re = JSON.parse(JSON.stringify(ress));
for (var c = 0; c < re.characteristics.length; c++) {
if (re.characteristics[c].properties.notify == true) {
var uuid = re.characteristics[c].uuid
for (var index in that.bluetoothList) {
if (that.bluetoothList[index].deviceId == that.deviceId) {
uni.setStorageSync('linkServiceId', serviceId)
uni.setStorageSync('linkCharacteristicId', re.characteristics[c].uuid)
}
}
setTimeout(() => {wx.hideLoading()}, 2000)
uni.showToast({title: '连接成功',icon:'none'})
break
}
}
that.monitor()
}
})
}
},
fail(res) {
uni.showToast({title: '连接失败,请重试',icon:'none'})
},
})
},
//监听
monitor() {
let linkDeviceId = uni.getStorageSync('linkDeviceId')
let linkServiceId = uni.getStorageSync('linkServiceId')
let linkCharacteristicId = uni.getStorageSync('linkCharacteristicId')
this.bluetoothErrorLog.push({
name: '连接成功,正在启动实时监听...ServiceId:' + linkServiceId + ',' + 'linkCharacteristicId:' + linkCharacteristicId + 'deviceId:' + linkDeviceId
})
let that = this
uni.notifyBLECharacteristicValueChange({
deviceId: linkDeviceId,
serviceId: linkServiceId,
characteristicId: linkCharacteristicId,
state: true,
success: () => {
this.stringArr = []
this.bluetoothErrorLog.push({
name: '蓝牙实时监听启动成功...当前连接设备deviceId:' + linkDeviceId + 'serviceId:' + linkServiceId + 'characteristicId:' + linkCharacteristicId
})
this.$emit('getStatusLog', this.bluetoothErrorLog)
this.rxd()
},
fail: (err) => {
this.bluetoothErrorLog.push({
name: '蓝牙监听启动失败...ServiceId:' + linkServiceId + ',' + 'linkCharacteristicId:' + linkCharacteristicId + 'deviceId:' + linkDeviceId
})
this.bluetoothErrorLog.push({
name: '蓝牙监听启动失败:' + JSON.stringify(err)
})
this.$emit('getStatusLog', this.bluetoothErrorLog)
}
});
},
//数据接受
rxd() {
let that = this
let str1 = '';
uni.onBLECharacteristicValueChange((res) => {
// 这就是蓝牙发送的数据 但是现在的数据,还不能直观的看出来是什么,还需要进行一些列转换才能直观查看
// ArrayBufer转16进制
let ArrayStrBufer = that.buf2hex(res.value);
//16进制转字符串
if (that.hexToString(that.buf2hex(res.value))) {
that.stringArr.push(that.hexToString(that.buf2hex(res.value)))
}
//16进制转字符串处理中文乱码
if (that.utf8to16(that.hexToString(that.buf2hex(res.value)))) {
that.stringArr.push(that.utf8to16(that.hexToString(that.buf2hex(res.value))))
}
/* this.bluetoothErrorLog.push({
name: '原始值' + JSON.stringify(res.value)
})
this.$emit('getStatusLog', this.bluetoothErrorLog) */
});
},
// arraybuffer类型转16进制字符串
buf2hex(buffer) {
const hexArr = Array.prototype.map.call(
new Uint8Array(buffer),
function(bit) {
return ('00' + bit.toString(16)).slice(-2)
}
)
return hexArr.join('')
},
// 16进制转字符串
hexToString(hexCharCodeStr) {
var trimedStr = hexCharCodeStr.trim();
var rawStr = trimedStr.substr(0, 2).toLowerCase() === "0x" ? trimedStr.substr(2) : trimedStr;
var len = rawStr.length;
if (len % 2 !== 0) {
return "";
}
var curCharCode;
var resultStr = [];
for (var i = 0; i < len; i = i + 2) {
curCharCode = parseInt(rawStr.substr(i, 2), 16); // ASCII Code Value
resultStr.push(String.fromCharCode(curCharCode));
}
return resultStr.join("");
},
//处理中文乱码问题
utf8to16(str) {
var out, i, len, c;
var char2, char3;
out = "";
len = str.length;
i = 0;
while (i < len) {
c = str.charCodeAt(i++);
switch (c >> 4) {
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
out += str.charAt(i - 1);
break;
case 12:
case 13:
char2 = str.charCodeAt(i++);
out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F));
break;
case 14:
char2 = str.charCodeAt(i++);
char3 = str.charCodeAt(i++);
out += String.fromCharCode(((c & 0x0F) << 12) |
((char2 & 0x3F) << 6) |
((char3 & 0x3F) << 0));
break;
}
}
const numStr = out.toString();
const decimalIndex = numStr.indexOf('.');
// if (decimalIndex !== -1) {
// // 截取小数点后一位
// this.lastValue = numStr.slice(0, decimalIndex + 2);
// // 如果是整数,直接返回
// } else {
// this.lastValue = numStr
// }
this.lastValue = out;
this.getBluetoothAdapterState()
return out;
},
getBluetoothAdapterState() {
uni.getBluetoothAdapterState ({
success: (res) => {
if (!res.available) {
this.lastValue = 0
uni.showToast({
title: "当前蓝牙已断开连接!",
icon: "none",
duration: 2000,
});
this.$emit('getInitBluetooth', false, 0, 'init')
this.bluetoothErrorLog.push({
name: '当前蓝牙状态已断开,监听数据失败。。。。。。'
})
this.$emit('getStatusLog', this.bluetoothErrorLog)
} else {
this.$emit('getInitBluetooth', true, this.lastValue)
}
},
fail: function (res) {
this.bluetoothErrorLog.push({
name: '监听获取蓝牙数据失败:' + JSON.stringify(res)
})
this.$emit('getStatusLog', this.bluetoothErrorLog)
}
})
},
//断开蓝牙
closelanya(currentDeviceId) {
let bluetoothLanyaData = uni.getStorageSync('bluetoothLanyaData')
uni.showModal({
title: "提示!",
content: "当前蓝牙已于名称为" + bluetoothLanyaData.name + "的设备连接,是否断开连接?",
success: (row) => {
if (row.confirm) {
uni.showLoading({
title: '断开连接中...'
});
this.$emit('getDataClear', 'parentClear')
uni.closeBLEConnection({
// 一定要传入当前连接蓝牙的deviceId
deviceId: currentDeviceId,
success: () => {},
fail: (err) => {}
})
} else {
// 用户取消之后不需要做任何操作
console.log('用户点击了取消')
}
}
});
},
// 关闭蓝牙搜索
stopBluetoothDevicesDiscovery() {
uni.stopBluetoothDevicesDiscovery({
success: e => {
// console.log('停止搜索蓝牙设备:' + e);
},
fail: e => {
// console.log('停止搜索蓝牙设备失败,错误码:' + e);
}
});
},
}
};
</script>
<style lang="scss" scoped>
.lanya-container {
button {
margin: 0;
border-radius: 10rpx;
height: 52rpx;
line-height: 52rpx;
text-align: center;
color: #FFFFFF;
font-size: 23rpx;
background: #0A84FF;
border-radius: 88rpx 88rpx 88rpx 88rpx;
}
.tit-flex {
display: flex;
margin-bottom: 40rpx;
justify-content: space-between;
.tit {
font-size: 34rpx;
font-weight: 600;
}
}
.no-data {
font-size: 28rpx;
color: #898A8D;
text-align: center;
margin-top: 500rpx;
}
.lanya-list {
display: flex;
color: #898A8D;
font-size: 28rpx;
margin-bottom: 40rpx;
justify-content: space-between;
.warn-btn {
background: #FD3643!important;
}
}
.lanya-list:last-child {
margin-bottom: 0;
}
}
</style>
蓝牙连接原模板,复制即用
<template>
<view class="content">
<button type="default" @click="initialize">初始化</button>
<button type="default" @click="searchBlue">搜索</button>
<view class="list">
<!-- 注意事项: 不能使用 index 作为 key 的唯一标识 -->
<view v-for="(item, index) in dataList" :key="item.deviceId">
<view>{{item.name}}</view>---
<view>{{item.deviceId}}</view>---
<view>{{item.RSSI}}</view>
<button type="default" @click="lianjie(item.deviceId)" v-if="lanya.deviceId!==item.deviceId">连接</button>
<button type="warn" @click="closelanya(item.deviceId)" v-if="lanya.deviceId==item.deviceId">断开</button>
</view>
</view>
<button type="default" @click="communication">通讯</button>
<h3>服务值</h3>
<view class="list1">
<!-- 注意事项: 不能使用 index 作为 key 的唯一标识 -->
<view v-for="(item1, index1) in service" :key="item1.uuid">
<view>{{item1.uuid}}</view>
<view>是否主要{{item1.isPrimary}}</view>
<button type="default" @click="choose(item1.uuid)" v-if="serviceuuid!==item1.uuid">选择</button>
<button type="warn" @click="choose()" v-if="serviceuuid==item1.uuid">清空</button>
</view>
</view>
<h3>特征值</h3>
<view class="list1">
<!-- 注意事项: 不能使用 index 作为 key 的唯一标识 -->
<view v-for="(item1, index1) in characteristic" :key="item1.uuid">
<view>{{item1.uuid}}</view>
<view>可监听{{item1.properties.notify}}</view>
<view>可写{{item1.properties.write}}</view>
<view>可读{{item1.properties.read}}</view>
<button type="default" @click="monitor(item1.uuid)"
v-if="characteristicId!==item1.uuid&&item1.properties.notify==true">监听</button>
<button type="default" v-if="item1.properties.read==true" @click="readData(item1.uuid)">读取数据</button>
<button type="default" v-if="item1.properties.write==true" @click="open(item1.uuid)">写入数据</button>
</view>
</view>
<view v-for="(item, index) in stringArr" :key="index"
style="display:flex;padding: 10rpx;box-sizing: border-box;width: 100%;border-bottom: 1rpx solid #000;justify-content: center;">
<text
style="width:90%;display:inline-block;white-space: pre-wrap; word-wrap: break-word;height: auto;">{{item}}</text>
</view>
<u-popup :show="show" @close="close" mode="center">
<view style="padding: 20rpx;">
<view>{{characteristicId1}}</view>
<input placeholder="请输入数据" border="surround" v-model="writeDataValue"></input>
<button @click="writeData()">确定</button>
</view>
</u-popup>
</view>
</template>
<script>
export default {
data() {
return {
dataList: [],
lanya: {},
service: [],
characteristic: [],
stringArr: [],
serviceuuid: undefined,
characteristicId: undefined,
characteristicId1: undefined,
show: false,
writeDataValue: "BB9AA90CEE",
dataObject: {}
}
},
onLoad() {
this.initialize();
},
methods: {
open(e) {
this.show = true;
this.characteristicId1 = e;
// console.log('open');
},
close() {
this.show = false;
this.characteristicId1 = undefined;
// this.writeDataValue = undefined
// console.log('close');
},
//初始化
initialize() {
uni.openBluetoothAdapter({
// 蓝牙初始化成功执行
success: (res) => {
// 这里成功之后就不用管了,直接执行就行
uni.showToast({
title: "初始化成功!",
icon: "none",
duration: 2000,
});
},
// 蓝牙初始化失败执行
fail: (err) => {
// 初始化失败之后有需求的话可以进行一些处理
// 没有需求的也不用管
// 一般情况下,还是需要分析一下原因的,这用用户和自己就知道为什么失败了
if (err.errCode == 10001) {
// 这种情况是未打开蓝牙 就需要提示用户检测蓝牙并打开
uni.showToast({
title: "请检查蓝牙是否开启!",
icon: "none",
duration: 2000,
});
}
// 我这里只演示这一个,其他的状态可进入官网进行查看
// uni-app https://uniapp.dcloud.io/api/system/bluetooth.html
// 微信原生 https://developers.weixin.qq.com/miniprogram/dev/api/device/bluetooth/wx.openBluetoothAdapter.html
}
})
},
//搜索蓝牙
searchBlue() {
uni.startBluetoothDevicesDiscovery({
success: () => {
// 调用成功之后就开始查询附近蓝牙了
// 成功之后可调用这个函数,每次查询到新的蓝牙设备就会调用
// 这个函数使用不使用都可以,不影响查询的结果
uni.onBluetoothDeviceFound((devices) => {
console.log('蓝牙', devices) // 蓝牙设备信息
// 返回的数据是ArrayBuffer格式的需要进行转换,不然咱也看不懂都是些啥
// ArrayBuffer 的转换后面会详细写出来
})
setTimeout(() => {
uni.getBluetoothDevices({
success: (res) => {
// res.devices 为 查询到的蓝牙设备列表
// 拿到结果之后,进行处理
// 首先进行过滤一下查询的列表,会出现很多未知设备这类的,这里就直接把它们给排除掉了,只留下有完整名称的设备
var arr = []
res.devices.forEach((element) => {
if (element.name !== "位置设备") {
arr.push(element);
}
})
// 筛选之后在进行判断是否有正常的蓝牙设备
// 当然你也可以查看一下被筛选掉的设备是否有你所使用的设备,如果有你可以去掉筛选 或者自己定义筛选条件
if (arr.length == 0) {
uni.showToast({
title: "未查询到可用设备,请重新扫描",
duration: 1000,
icon: "none",
});
}
// 最后这个arr就是所查到的列表
let arr1 = []
arr.map((i) => {
if (i.name) {
arr1.push(i);
}
})
this.dataList = arr1
// 不管查没查到一定要使用 stopBluetoothDevicesDiscovery 停止查询
// 不管查没查到一定要使用 stopBluetoothDevicesDiscovery 停止查询
// 不管查没查到一定要使用 stopBluetoothDevicesDiscovery 停止查询
}
})
}, 2000)
}
})
},
// 关闭蓝牙搜索
stopBluetoothDevicesDiscovery() {
uni.stopBluetoothDevicesDiscovery({
success: e => {
console.log('停止搜索蓝牙设备:' + e);
},
fail: e => {
console.log('停止搜索蓝牙设备失败,错误码:' + e);
}
});
},
//连接蓝牙
lianjie(e) {
console.log('连接', e);
// 为了能否顺利的连接蓝牙 可以先查询一下是否有设备已经连接蓝牙了
// 手环这一类的设备 可能会对程序造成干扰 会一直显示设备已经连接
uni.getConnectedBluetoothDevices({
success: (res) => {
// 因为我为了蓝牙的连接的稳定性,就做了这一步
// 大家使用的过程中可以省略这一步直接进行蓝牙设备
// 但是不确定是否可以正常进行蓝牙连接, 大家可以尝试一下
// 如果返回的列表不等于空,就代表已经有设备连接
if (res.devices.length !== 0) {
// 这里就需要提示用户蓝牙已连接
uni.showModal({
title: "提示!",
content: "当前蓝牙已于id为" + res.devices[0].deviceId + "的设备连接,是否断开连接",
success: (row) => {
// 用户点击确定执行
if (row.confirm) {
// 用户点击确定之后把当前连接的蓝牙断开
uni.closeBLEConnection({
// 一定要传入当前连接蓝牙的deviceId
deviceId: res.devices[0].deviceId,
success: () => {
// 到这里就已经断开成功了,再次点击就可以进行连接了
uni.showToast({
title: "连接已断开!",
icon: "none",
duration: 2000,
});
this.lanya = {}
this.service = []
this.characteristic = []
this.serviceuuid = undefined;
this.characteristicId = undefined;
this.stringArr = []
},
fail: (err) => {
// 走到这就是断开失败了,可以进行操作提示用户或者自己查看
uni.showToast({
title: err.errMsg,
icon: "none",
duration: 2000,
});
}
})
} else {
// 用户取消之后不需要做任何操作
console.log('用户点击了取消')
}
}
});
} else {
// 当前未处于已连接状态就开始进行连接,没有连接的情况下就可以直接连接蓝牙
uni.createBLEConnection({
// 连接的时候一定传入要连接蓝牙的deviceId
deviceId: e,
// 这里可以选择设置一个延迟,如果延迟时间过了没有连接成功就直接提示报错
timeout: 5000,
success: (res) => {
// 连接成功之后可以再次进行查询一下当前连接的蓝牙信息,保存下载,后面方便使用
uni.getConnectedBluetoothDevices({
success: (devicess) => {
// devicess就是当前已经连接的蓝牙列表
// 在这判断一下有没有蓝牙,如果有就证明确实已经连接成功
if (devicess.devices[0]) {
// 到这里就可以提示用户成功,并且吧蓝牙信息保存下来方便后面使用
console.log('连接成功', devicess.devices[
0]) // 蓝牙信息
uni.showToast({
title: "连接成功!",
icon: "none",
duration: 2000,
});
this.lanya = devicess.devices[
0]
this.stopBluetoothDevicesDiscovery();
} else {
// 如果走这里面就是没有蓝牙设备,就要查看一下,看看是否真的连接成功了
}
}
})
},
fail: (err) => {
// 在这里查看连接失败的信息,判断为什么连接失败
console.log(err)
}
})
}
}
})
},
//断开蓝牙
closelanya() {
uni.closeBLEConnection({
// 一定要传入当前连接蓝牙的deviceId
deviceId: this.lanya.deviceId,
success: () => {
// 到这里就已经断开成功了,再次点击就可以进行连接了
uni.showToast({
title: "连接已断开!",
icon: "none",
duration: 2000,
});
this.lanya = {}
this.service = []
this.characteristic = []
this.serviceuuid = undefined;
this.characteristicId = undefined;
this.stringArr = []
},
fail: (err) => {
// 走到这就是断开失败了,可以进行操作提示用户或者自己查看
uni.showToast({
title: err.errMsg,
icon: "none",
duration: 2000,
});
}
})
},
//服务值获取
communication() {
// 蓝牙接收数据主要使用的api是开启监听(uni.notifyBLECharacteristicValueChange)
// 但是开启监听是需要几个特殊的值才能开启
// 所以开启之前我们需要获取这个值
// deviceId 蓝牙deviceId,蓝牙信息中包含的有
// serviceId 蓝牙服务值,根据蓝牙deviceId获取
// characteristicId 蓝牙特征值 根据serviceId 获取
// 首先根据deviceId 获取到服务值 serviceId
uni.getBLEDeviceServices({
deviceId: this.lanya.deviceId, // 获取服务值,需要传入deviceId
success: (res) => {
// res.services 返回一个数组,里面包含着支持不同的通讯方式的serviceId 一般为三个左右,也有可能更多
console.log('服务值', res.services)
this.service = res.services
// 拿到之后根据自己所需要的去保存serviceId,在后面使用
// 这里建议多试试,说不定那个可以用,又或者某个不能用
if (res.services.length <= 0) {
this.communication();
} else {
// this.choose(res.services[res.services.length - 2].uuid);
// this.choose(res.services[0].uuid);
}
},
fial: (err) => {
// 一般来说只要 deviceId 对,就不会报错
}
})
},
//选择服务值
choose(e) {
this.characteristic = []
this.stringArr = []
this.characteristicId = undefined
this.serviceuuid = e;
this.characteristicget();
},
//特征值获取
characteristicget() {
//选第4个
// 获取到之后就可以去拿着获取到的serviceId和deviceId去获取特征值
uni.getBLEDeviceCharacteristics({
deviceId: this.lanya.deviceId, // 传入获取到的deviceId
serviceId: this.serviceuuid, // 传入获取到的serviceuuid
success: (ress) => {
// ress里面就是获取到的蓝牙特征值
// 注意:根据传入serviceuuid的不同获取到的特征值也是不一样的,
// 特征值分为,可读、可写、可通知等几个类型的,根据自己想要的操作选择特征值
console.log('特征值', ress)
this.characteristic = ress.characteristics
// for (var i = 0; i < ress.characteristics.length; i++) {
// var model = ress.characteristics[i];
// if (model.properties.notify == true) {
// this.monitor(model.uuid);
// }
// }
},
fial: (err) => {
// 一般来说只要参数对,就不会报错
}
})
},
//监听
monitor(e) {
//选第1个
// 这里所声明介绍一下所用到的东西
// deviceId 就是上面蓝牙设备的deviceId
// serviceuuid 就是上面根据蓝牙设备获取到的serviceuuid
// characteristics 就是上面根据 deviceId 和 serviceuuid 获取到的
// characteristics 是一个数组里面包含着多个特征值, 根据使用去拿响应的特征值
// 我这里没有直接使用,而是进行一个循环判断,判断这么多的特征值中那个是符合要求的
// var characteristicId;
// var i = 0
// then.characteristiclist.forEach((element) => {
// if (element.properties.notify == true) {
// if (i == 0) {
// characteristicId = element.uuid;
// i++;
// }
// }
// });
// 为什么循环
// 因为开启uni.notifyBLECharacteristicValueChange需要特征值是需要固定的,我就直接判断写入了
let that = this
uni.notifyBLECharacteristicValueChange({
deviceId: that.lanya.deviceId,
serviceId: that.serviceuuid,
characteristicId: e,
state: true,
success: () => {
that.characteristicId = e
console.log('监听启动成功');
this.stringArr = []
// 启用成功之后就可以在uni.onBLECharacteristicValueChange 中获取到蓝牙设备发送的数据
that.rxd()
},
});
},
//数据接受
rxd() {
console.log('监听返回');
let that = this
let str1 = '';
uni.onBLECharacteristicValueChange((res) => {
console.log('接收数据', res.value)
// 这就是蓝牙发送的数据 但是现在的数据,还不能直观的看出来是什么,还需要进行一些列转换才能直观查看
// ArrayBufer转16进制
console.log('ArrayBufer转16进制', that.buf2hex(res.value))
let str = that.buf2hex(res.value);
console.log(str);
//16进制转字符串
// console.log('ArrayBufer转字符串',that.hexToString(that.buf2hex(res.value)))
// if (that.hexToString(that.buf2hex(res.value))) {
// that.stringArr.push(that.hexToString(that.buf2hex(res.value)))
// }
//16进制转字符串处理中文乱码
// console.log('ArrayBufer转字符串处理乱码',that.utf8to16(that.hexToString(that.buf2hex(res.value))))
// if (that.utf8to16(that.hexToString(that.buf2hex(res.value)))) {
// that.stringArr.push(that.utf8to16(that.hexToString(that.buf2hex(res.value))))
// }
//ArrayBufer直接转字符串
// console.log('ArrayBufer直接转字符串',that.utf8to16(that.buf2str(res.value)));
// if (that.utf8to16(that.buf2str(res.value))) {
// that.stringArr.push(that.utf8to16(that.buf2str(res.value)))
// }
// 经过 this.buf2str 转换之后就可以直观查看 蓝牙返回的信息
});
},
// 读取数据
readData(e) {
//选第3个
let that = this
uni.readBLECharacteristicValue({
deviceId: that.lanya.deviceId,
serviceId: that.serviceuuid,
characteristicId: e,
success(res) {
console.log('读取数据:', res.errCode)
}
})
},
//写入数据
writeData() {
//选第2个
// 所用到的就是上面获取到的
// 但是特征值跟上面不一样,需要支持可写
// var characteristicId;
// var i = 0;
// then.characteristiclist.forEach((element) => {
// if (element.properties.write == true) {
// if (i == 0) {
// characteristicId = element.uuid;
// i++;
// }
// }
// });
let that = this
// console.log(that.writeDataValue);
// console.log(that.hexToString(that.writeDataValue));
// console.log(that.strToHexCharCode(that.writeDataValue));
// console.log(that.hex2buf(that.strToHexCharCode(that.writeDataValue)));
console.log('12进制转arraybuffer', that.hex2buf(that.writeDataValue));
uni.writeBLECharacteristicValue({
deviceId: that.lanya.deviceId,
serviceId: that.serviceuuid,
characteristicId: that.characteristicId1,
value: that.hex2buf(that.writeDataValue),
// value: that.hex2buf(that.strToHexCharCode(that.writeDataValue)),
// writeType: "write",
success: (res) => {
console.log('指令写入成功');
console.log(res);
that.show = false
that.characteristicId1 = undefined;
// that.writeDataValue = undefined
uni.showToast({
title: "指令写入成功",
icon: "none",
duration: 2000,
});
// 写入成功之后在onBLECharacteristicValueChange里面应该是有反馈的
that.rxd()
},
fail(err) {
console.log(err);
uni.showToast({
title: err.message,
icon: "none",
duration: 2000,
});
}
})
// this.string2buffer 把16进制字符串转换成buffer之后进行写入,我也忘了直接传入字符串可不可以使用,应该是不行,可以尝试一下,不行就按着我的来也行
},
//蓝牙状态变化处理
statesChange() {
uni.onBLEConnectionStateChange(res => {
// 该方法回调中可以用于处理连接意外断开等异常情况
console.log(`device ${res.deviceId} state has changed, connected: ${res.connected}`);
});
},
// arraybuffer类型转16进制字符串
buf2hex(buffer) {
const hexArr = Array.prototype.map.call(
new Uint8Array(buffer),
function(bit) {
return ('00' + bit.toString(16)).slice(-2)
}
)
console.log('arraybuffer类型转16进制', hexArr.join(''));
return hexArr.join('')
},
// 16进制转arraybuffer类型
hex2buf(e) {
var hex = e
var typedArray = new Uint8Array(hex.match(/[\da-f]{2}/gi).map(function(h) {
return parseInt(h, 16)
}))
var buffer = typedArray.buffer
return buffer;
},
// 16进制转字符串
hexToString(hexCharCodeStr) {
var trimedStr = hexCharCodeStr.trim();
var rawStr =
trimedStr.substr(0, 2).toLowerCase() === "0x" ?
trimedStr.substr(2) :
trimedStr;
var len = rawStr.length;
if (len % 2 !== 0) {
console.log("非法格式ASCII码!");
return "";
}
var curCharCode;
var resultStr = [];
for (var i = 0; i < len; i = i + 2) {
curCharCode = parseInt(rawStr.substr(i, 2), 16); // ASCII Code Value
resultStr.push(String.fromCharCode(curCharCode));
}
return resultStr.join("");
},
//字符串转16进制
strToHexCharCode(str) {
if (str === "")
return "";
var hexCharCode = [];
hexCharCode.push("0x");
for (var i = 0; i < str.length; i++) {
hexCharCode.push((str.charCodeAt(i)).toString(16));
}
return hexCharCode.join("");
},
//处理中文乱码问题
utf8to16(str) {
// console.log(str);
var out, i, len, c;
var char2, char3;
out = "";
len = str.length;
i = 0;
while (i < len) {
c = str.charCodeAt(i++);
switch (c >> 4) {
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
out += str.charAt(i - 1);
break;
case 12:
case 13:
char2 = str.charCodeAt(i++);
out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F));
break;
case 14:
char2 = str.charCodeAt(i++);
char3 = str.charCodeAt(i++);
out += String.fromCharCode(((c & 0x0F) << 12) |
((char2 & 0x3F) << 6) |
((char3 & 0x3F) << 0));
break;
}
}
console.log(out, 'out')
return out;
},
},
}
</script>
<style scoped lang="scss">
.content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.list {
padding: 20rpx;
view {
display: flex;
justify-content: space-around;
}
}
</style>