1:ly.js
// import { TextDecoder } from 'text-encoding-utf-8';
let bluetoothOpen = false; // 手机蓝牙是否打开
let bluetoothConnect = false; // 设备和蓝牙是否连接
let isHaveDevice = false; // 是否查找到设备
let deviceId = null; // 设备id
let serviceId = null; // 服务id
let notify = null; // 监听uuid
let writeId = null; // 写入uuid
let timer = null
/**
* 获取手机蓝牙是否打开
*/
const getBluetoothState = () => {
// 主机模式
return new Promise((resolve, reject) => {
// mode: 'central',
uni.openBluetoothAdapter({
success: (r) => {
console.log("蓝牙初始化成功");
// 获取蓝牙的匹配状态
uni.getBluetoothAdapterState({
success: function(row) {
console.log('蓝牙状态:', row.available);
if (row.available) {
bluetoothOpen = true;
connectTimeout()
resolve();
} else {
// 请开启蓝牙
uni.showToast({
title: '请打开蓝牙',
icon: 'none'
})
bluetoothOpen = false;
bluetoothConnect = false;
reject();
}
},
fail: function(err) {
// 请开启蓝牙
uni.showToast({
title: '请打开蓝牙',
icon: 'none'
})
bluetoothOpen = false;
bluetoothConnect = false;
reject();
}
})
},
fail: (err) => {
// 请开启蓝牙
uni.showToast({
title: '请前往手机设置,打开此APP蓝牙权限',
icon: 'none'
})
bluetoothOpen = false;
bluetoothConnect = false;
reject();
}
});
});
};
/**
* 开始搜索蓝牙设备
*/
const discoveryBluetooth = () => {
return new Promise((resolve) => {
uni.startBluetoothDevicesDiscovery({
success(res) {
console.log('搜索蓝牙外围设备完成', res)
setTimeout(() => {
resolve();
}, 2000);
}
});
})
};
// 关闭蓝牙搜索
const stopDiscoveryBluetooth = () => {
uni.stopBluetoothDevicesDiscovery({
success(r) {
console.log("停止搜索蓝牙设备", r);
}
});
};
/**
* 获取搜索到的设备信息
*/
const getBluetoothDevices = (deviceName) => {
return new Promise((resolve, reject) => {
uni.getBluetoothDevices({
success(res) {
console.log('获取搜索到的设备信息', res.devices);
bluetoothConnect = false;
// 过滤掉name为空或者未知设备的设备
let devices = res.devices.filter(function(obj) {
return obj.name !== "" && obj.name !== "未知设备"
});
console.log('有名称蓝牙列表', devices, deviceName);
devices && devices.forEach(item => {
if (item.name == deviceName || item.localName == deviceName) {
deviceId = item.deviceId;
isHaveDevice = true;
resolve(isHaveDevice);
console.log('设备ID', deviceId, item);
}
});
timer = setInterval(() => {
console.log('---',deviceId);
if (!deviceId) {
discoveryBluetooth().then(
getBluetoothDevices2(deviceName).then(res => {
resolve(res);
})
)
} else {
clearInterval(timer)
timer = null
}
}, 1000)
},
fail: function() {
console.log('搜索蓝牙设备失败');
bluetoothConnect = false;
isHaveDevice = false;
reject(isHaveDevice);
},
complete: function() {
console.log("蓝牙搜索完成");
}
});
});
}
const getBluetoothDevices2 = (deviceName) => {
return new Promise((resolve, reject) => {
uni.getBluetoothDevices({
success(res) {
console.log('获取搜索到的设备信息', res);
bluetoothConnect = false;
// 过滤掉name为空或者未知设备的设备
let devices = res.devices.filter(function(obj) {
return obj.name !== "" && obj.name !== "未知设备"
});
console.log('有名称蓝牙列表', devices, deviceName);
devices && devices.forEach(item => {
if (item.name == deviceName || item.localName == deviceName) {
deviceId = item.deviceId;
isHaveDevice = true;
clearInterval(timer)
timer = null
resolve(isHaveDevice);
console.log('设备ID', deviceId, item);
}
});
},
fail: function() {
console.log('搜索蓝牙设备失败');
bluetoothConnect = false;
isHaveDevice = false;
reject(isHaveDevice);
}
});
});
}
/**
* 连接蓝牙
* deviceId 蓝牙设备id
*/
const connectBluetooth = () => {
return new Promise((resolve, reject) => {
uni.createBLEConnection({
deviceId: deviceId, // 设备id
success() {
bluetoothConnect = true;
console.log('连接蓝牙成功', deviceId);
// 蓝牙连接成功后关闭蓝牙搜索
stopDiscoveryBluetooth();
// 获取服务id
getServiceId();
setTimeout(() => {
resolve();
})
},
fail(err) {
bluetoothConnect = false;
console.log("蓝牙连接失败", err);
reject();
}
});
});
};
// 获取服务id
const getServiceId = () => {
setTimeout(() => {
uni.getBLEDeviceServices({
deviceId: deviceId,
success(res) {
console.log(res, '服务id成功', deviceId);
// 方案一
res.services.forEach(item => {
let firstFive = item.uuid.substring(0, 8);
console.log(item, firstFive);
if (firstFive == '0000FFF0') {
serviceId = item.uuid;
// 调用蓝牙监听和写入功能
getCharacteId();
}
});
// 方案二
// let model = res.services[0];
// serviceId = model.uuid;
// console.log(res, '服务id成功', serviceId);
// // 调用蓝牙监听和写入功能
// getCharacteId();
},
fail(err) {
console.log('获取服务失败', err);
}
})
}, 1000)
};
// 获取蓝牙低功耗设备某个服务中所有特征
const getCharacteId = () => {
uni.getBLEDeviceCharacteristics({
deviceId: deviceId, // 蓝牙设备id
serviceId: serviceId, // 蓝牙服务UUID
success(res) {
console.log('数据监听', res);
res.characteristics.forEach(item => {
// 003
if (item.properties.notify === true) {
// 监听
notify = item.uuid;
startNotice();
}
// 002
if (item.properties.write === true) {
// 写入
let writeId = item.uuid;
uni.setStorageSync("writeId", item.uuid);
}
});
},
fail(err) {
console.log("数据监听失败", err)
}
})
};
// 启用低功耗蓝牙设备特征值变化时的notify功能
const startNotice = () => {
uni.notifyBLECharacteristicValueChange({
characteristicId: notify,
deviceId: deviceId,
serviceId: serviceId,
state: true,
success(res) {
// 监听低功耗蓝牙设备的特征值变化
uni.onBLECharacteristicValueChange(result => {
console.log("监听低功耗蓝牙设备的特征值变化", result);
if (result.value) {
// let decoder = new TextDecoder('utf-8');
// let data = decoder.decode(result.value);
// let data = result.value;
console.log('帽子返回数据', result)
}
})
}
});
};
// 蓝牙发送数据
const writeData = (buffer) => {
return new Promise((resolve, reject) => {
console.log(uni.getStorageSync("writeId"), '下发命令1writeId');
console.log(deviceId, '下发命令2deviceId');
console.log(serviceId, '下发命令3serviceId');
console.log(buffer, '下发命令4buffer');
uni.writeBLECharacteristicValue({
characteristicId: uni.getStorageSync("writeId"),
deviceId: deviceId,
serviceId: serviceId,
value: buffer,
success(res) {
console.log("writeBLECharacteristicValue success", res);
resolve();
},
fail(err) {
console.log("报错了", err);
reject();
}
});
});
};
// 断开蓝牙
const closeBlec = () => {
return new Promise((resolve, reject) => {
uni.closeBLEConnection({
deviceId: deviceId,
success(res) {
resolve();
console.log(res, '断开成功')
},
fail(err) {
reject();
console.log("断开失败", err);
}
})
})
}
const BLECStateChange = () => {
return new Promise((resolve, reject) => {
uni.onBLEConnectionStateChange(function(res) {
// 该方法回调中可以用于处理连接意外断开等异常情况
console.log(
`device ${res.deviceId} state has changed, connected: ${res.connected}`)
return resolve(res)
})
})
}
/*设置蓝牙搜索时间30秒, 如果超时没有搜索到就停止搜索*/
const connectTimeout = () => {
setTimeout(() => {
if (!bluetoothConnect) {
clearInterval(timer)
timer = null
uni.stopBluetoothDevicesDiscovery({
success: (res) => {
console.log("停止搜索成功")
}
})
uni.showToast({
title: '蓝牙连接失败',
icon: 'none'
})
}
}, 30000)
}
// closeBLEConnection deviceId 断开蓝牙
export default {
getBluetoothState,
discoveryBluetooth,
stopDiscoveryBluetooth,
getBluetoothDevices,
connectBluetooth,
getServiceId,
getCharacteId,
startNotice,
writeData,
closeBlec,
BLECStateChange
};
2:inde.vue 页面使用
<template>
<view class="device_container">
<image src="https://beoka-file.oss-cn-beijing.aliyuncs.com/beoka_Iot/img/1000001166582801.png" class="bg">
</image>
<view class="card">
<view class="tsip" @click="clicljydTisp">
</view>
<image class="mb" src="@/static/mb.png" style="width: 100%;height: 100%;"></image>
<view class="info">
<view class="beoka">
<image src="@/static/beoka.png"></image>
</view>
<view class="infos">
<view>
蓝牙名称: {{bluetoothName?bluetoothName: 'XXXX XXXX XXXX'}}
</view>
<view>
设备编号:{{deviceNo?deviceNo:'XXXXXXXXXX'}}
<!-- 产品编号:{{deviceName?'866 9288 98376':' XXXX XXXX XXXX'}} -->
</view>
</view>
<view class="ipt">
<span>写入编码:</span>
<view class="input">
<input type="number" v-model="code" placeholder="请输入设备编码" />
<image @click="scanCodeValue" src="@/static/scan.png" mode=""></image>
</view>
</view>
</view>
</view>
<view>
<view class="btn" v-if="bluetoothName&&bluetoothStatus">
<view class="text" @click="clickBreak">
断开蓝牙
</view>
<view class="text" @click="clickgh">
确定写入编码
</view>
</view>
<view class="sm" v-else @click="snacode">扫码连接设备 {{stateText}}</view>
</view>
<!-- 连接引导弹窗 -->
<uni-popup ref="ljydPopup" type="center">
<view class="ljyd">
<view class="title">
连接引导
<image src="@/static/close.png" @click="clickClose('ljydPopup')"></image>
</view>
<view class="item">
<view class="itemText">
1.打开设备电源
</view>
<view class="itemImg">
<image src="@/static/img1.png"></image>
</view>
</view>
<view class="item">
<view class="itemText">
1.打开手机蓝牙
</view>
<view class="itemImg">
<image src="@/static/img2.png"></image>
</view>
</view>
<view class="item">
<view class="itemText">
1.连接设备
</view>
<view class="itemImg">
<image src="@/static/img3.png"></image>
</view>
</view>
</view>
</uni-popup>
</view>
</template>
<script>
// Hi-BEOKA-POC003AEE
import bluetooth from '@/uilts/ly.js';
export default {
data() {
return {
deviceNo: '',
stateText: '未连接',
bluetoothStatus: false, // 蓝牙连接状态
bluetoothName: '', //蓝牙名字
buffer: '', //下发数据
code: '',
}
},
onLoad() {
},
onShow() {
},
methods: {
// 扫码
snacode() {
let _this = this;
uni.scanCode({
success: function(res) {
let str = res.result
var deviceNo = _this.getParameterByName('no', str)
var bluetoothName = _this.getParameterByName('bluetooth', str)
console.log(str, deviceNo, bluetoothName);
if (deviceNo && bluetoothName) {
_this.deviceNo = deviceNo
_this.bluetoothName = bluetoothName
_this.initBluetooth(() => {
console.log('蓝牙连接成功');
}, bluetoothName);
} else {
uni.showToast({
title: '二维码参数错误',
icon: 'none'
})
}
},
fail: (err => {
uni.showToast({
title: '扫码失败',
icon: 'none'
})
})
});
},
scanCodeValue() {
let _this = this
uni.scanCode({
success: function(res) {
let value = res.result
_this.code = value
console.log(value, 'deviceNo');
}
})
},
clickgh() {
//针头=0xAA55 T=0x01 - 0xFF L=0x01 – 0xFF V=0x00 - N
if (!this.code) {
return uni.showToast({
title: '请输入设备编码',
icon: 'none'
})
}
let str = this.code;
let arr = Array.from(str);
let v = ["AA", "55", "09", '08', ...arr]; //命令数据表示16进制
//转16进制
let list = this.parseInt(v)
// 10进制转换
let buffer = this.arr2ab(list)
this.buffer = buffer
console.log(buffer, list, this.bluetoothName, 'buffer');
let _this = this
if (this.bluetoothStatus) {
_this.writeBlueData(buffer)
} else {
_this.initBluetooth(() => {
_this.writeBlueData(buffer)
}, _this.bluetoothName);
}
},
clickBreak() {
let _this = this
_this.deviceNo = ''
_this.stateText = '未连接'
_this.bluetoothStatus = false // 蓝牙连接状态
_this.bluetoothName = '' //蓝牙名字
_this.buffer = '' //下发数据
_this.code = ''
bluetooth.closeBlec().then(res => {
uni.showToast({
title: '断开成功',
icon: 'none'
})
})
},
// 向设备发送数据
writeBlueData(buffer) {
let _this = this
uni.showLoading({
title: '写入中...',
icon: 'none'
})
bluetooth.writeData(buffer).then(res => {
setTimeout(() => {
_this.deviceNo = ''
_this.stateText = '未连接'
_this.bluetoothStatus = false // 蓝牙连接状态
_this.bluetoothName = '' //蓝牙名字
_this.buffer = '' //下发数据
_this.code = ''
bluetooth.closeBlec().then(res => {
uni.showToast({
title: '写入成功',
icon: 'none'
})
})
}, 1000)
_this.bluetoothStatus = false // 蓝牙连接状态
})
.catch(err=>{
uni.showToast({
title: '写入失败',
icon: 'none'
})
})
},
// 获取蓝牙和设备是否已连接
initBluetooth(callback, bluetoothName) {
let _this = this;
_this.stateText = '搜索蓝牙'
uni.showLoading({
title: _this.stateText
})
// 初始化蓝牙
bluetooth.getBluetoothState().then(() => {
// 搜索外围蓝牙设备
bluetooth.discoveryBluetooth().then(() => {
_this.discoveryLoading = true;
// 获取蓝牙设备
bluetooth.getBluetoothDevices(bluetoothName).then((isHaveDevice) => {
if (isHaveDevice) {
_this.stateText = '连接中...'
// 搜索到指定设备,连接蓝牙
bluetooth.connectBluetooth().then((bluetoothConnect) => {
console.log('连接成功');
setTimeout(() => {
callback()
_this.stateText = '连接成功'
uni.hideLoading();
uni.showToast({
title: '蓝牙连接成功',
icon: 'none'
})
_this.bluetoothStatus = true;
}, 1000)
// 监听蓝牙与设备连接状态
bluetooth.BLECStateChange().then(res => {
_this.bluetoothStatus = res.connected
_this.stateText = '未连接'
console.log(res, '监听设备连接状态');
})
}).catch((err) => {
_this.stateText = '连接失败'
_this.bluetoothStatus = false;
uni.hideLoading();
uni.showToast({
title: '蓝牙连接失败',
icon: 'none'
})
})
} else {
// 未搜到设备
_this.bluetoothStatus = false;
uni.hideLoading();
uni.showToast({
title: '请打开设备',
icon: 'none'
})
}
}, () => {
// 蓝牙搜索失败
uni.hideLoading();
uni.showToast({
title: '蓝牙连接失败',
icon: 'none'
})
_this.bluetoothStatus = false;
});
});
}, () => {
// 未开启蓝牙
_this.bluetoothStatus = false;
});
},
// 16进制
parseInt(arr) {
let list = arr.map((v) => {
return parseInt(v, 16); //16表示v 是16进制格式的数据
});
return list
},
// 高低位
IntToBytesLittleEndian(number, length) {
var bytes = [];
var i = 0;
do {
bytes[i++] = number & (255);
number = number >> 8;
} while (i < length)
return bytes.reverse();
},
// 10进制转换
arr2ab(arr) {
const buffer = new ArrayBuffer(arr.length);
const dataView = new DataView(buffer);
for (var i = 0; i < arr.length; i++) {
dataView.setUint8(i, arr[i]);
}
return buffer;
},
// 获取扫码进来 地址携带的参数
getParameterByName(name, url) {
name = name.replace(/[\[\]]/g, "\\$&");
var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
results = regex.exec(url);
if (!results) return null;
if (!results[2]) return '';
return decodeURIComponent(results[2].replace(/\+/g, " "));
},
// 点击关闭
clickClose(name) {
this.$refs[name].close()
},
// 连接引导弹窗
clicljydTisp() {
console.log('999');
this.$refs.ljydPopup.open()
},
clickPay() {
uni.navigateTo({
url: '/pages/index/orderlist'
})
},
},
}
</script>
<style lang="scss">
page {
height: 100%;
}
.bg {
width: 100vw;
height: 100vh;
}
.ljgh,
.sm {
border-radius: 100px;
background: #3A87EF;
width: calc(100% - 96rpx);
height: 112rpx;
box-shadow: 0px 20px 60px 0px rgba(58, 96, 178, 0.30);
text-align: center;
line-height: 112rpx;
color: #FFF;
font-size: 32rpx;
font-style: normal;
font-weight: 500;
position: fixed;
bottom: 120rpx;
left: 50%;
transform: translateX(-50%);
letter-spacing: 2rpx;
}
.btn {
width: 100%;
position: fixed;
bottom: 120rpx;
display: flex;
justify-content: space-evenly;
.text {
width: 45vw;
height: 112rpx;
border-radius: 100px;
background: #3A87EF;
line-height: 112rpx;
color: #FFF;
font-size: 32rpx;
font-style: normal;
font-weight: 500;
text-align: center;
}
}
.card {
width: calc(100% - 64rpx);
height: 520rpx;
position: fixed;
top: 43%;
left: 50%;
transform: translateX(-50%);
.md {
width: 100%;
height: 100%;
}
.tsip {
width: 32rpx;
height: 32rpx;
border-radius: 100%;
position: absolute;
right: 44rpx;
top: 32rpx;
// background-color: red;
z-index: 1;
}
.info {
position: absolute;
top: 0;
padding: 60rpx 40rpx 22rpx;
box-sizing: border-box;
width: 100%;
height: 100%;
.beoka {
width: 65px;
height: 20px;
image {
width: 65px;
height: 20px;
}
}
}
.infos {
font-size: 32rpx;
font-style: normal;
font-weight: 700;
line-height: normal;
margin-top: 44rpx;
border-bottom: 2rpx dashed #E9ECF3;
padding-bottom: 32rpx;
color: #666;
view {
margin-top: 32rpx;
}
}
.ipt {
margin-top: 44rpx;
display: flex;
align-items: center;
width: 100%;
span {
font-size: 32rpx;
font-style: normal;
font-weight: 700;
line-height: normal;
}
.input {
border: 2rpx solid #ccc;
height: 80rpx;
flex: 1;
border-radius: 8rpx;
display: flex;
align-items: center;
padding: 0 20rpx;
input {
flex: 1;
}
image {
width: 50rpx;
height: 50rpx;
}
}
}
}
.ljyd {
width: 644rpx;
background-color: #fff;
border-radius: 28rpx;
padding-bottom: 16rpx;
// height: 517px;
.title {
color: #333;
text-align: center;
font-size: 32rpx;
font-style: normal;
font-weight: 600;
line-height: 44rpx;
/* 137.5% */
width: 100%;
padding: 32rpx 0 48rpx;
position: relative;
image {
width: 20rpx;
height: 20rpx;
position: absolute;
right: 44rpx;
top: 45rpx;
}
}
.item {
margin: 0 32rpx 32rpx;
.itemText {
color: var(--unnamed, #333);
text-align: left;
font-family: PingFang HK;
font-size: 16px;
font-style: normal;
font-weight: 400;
line-height: normal;
letter-spacing: -0.408px;
margin-bottom: 12rpx;
}
.itemImg {
width: 340rpx;
height: 210rpx;
margin: auto;
image {
width: 100%;
height: 100%;
}
}
}
}
</style>