uniapp蓝牙多设备连接
博客园断更快一年了终于想起来我还有个博客,也主要是最近工作上面也没有什么想记录的,不过最近倒是搞了个有意思的功能项目
请注意:此文章禁止转载,抄袭,这是对我个人知识产权的不尊重
项目简介:原先单片机应用移植做一个无后端参与手机APP
功能介绍:单片机为子机的命令发送装置,开机使用单片机连接上子机的蓝牙设备,然后发送命令给子机执行
需求介绍:需要将原先单片机上的功能全部移植到新APP内,并且要求连接多个蓝牙设备,最好做到无上限设备连接(其实因为各个手机的性能差异,基本上不可能无限连接蓝牙设备,但是要求最少连接四个)
先简单介绍一下连接蓝牙设备的注意事项,整体连接蓝牙的流程为
1. 开启扫描器,扫描蓝牙,期间会获取到设备的 deviceId ,这个ID需要用于手机和蓝牙设备进行连接
2. 使用 deviceId 和蓝牙设备进行初始连接,可以获取到很多服务项,这些服务项都存在一个 serviceId ,后续需要再使用这个 serviceId 去获取特征项
3. 使用 serviceId 获取服务内的特征项,特征项需要区分是否支持读写监听等功能,可看下图
图中不同的uuid支持不同的功能,比如write:true就是支持往蓝牙设备写入数据的,notify:true是支持监听蓝牙设备发送出来的数据,read:true是支持读取数据
此时需要注意如果需要监听和发送信息功能就需要使用两个不同的uuid去实现,
蓝牙notify功能只需要运行一次即可在蓝牙连接期间进行接收设备发出来的数据,发送需要每次均运行一遍
使用这三个ID就可以连接上蓝牙进行对应的操作
完整代码放在附件文件内,记得更换UUID去连接对应的设备
先弄一个需要用户手动开启扫描的按钮,然后获取到扫描的列表,丢进去数组将其展示到页面(在开启之前需要先检测手机的蓝牙是否开启,不然是无法正常连接的)
添加完一些蓝牙设备之后开始调用uniapp的官方api,即可实现连接
注意事项:
1. 连接上时最先要调用notify监听功能,并且可以全局仅调用一次,以防止丢失信息
2. 当你已知服务项的uuid和特征项的uuid时,可以省去获取服务项和获取特征项的功能,直接使用已知的uuid进行连接,可以省下很多多设备连接的时间(具体的请查看完整代码内 nowLinkLis 方法)
3. 目前我所测试的设备中,最好的情况是连接上了7个蓝牙设备,若你在测试中发现只能6,7个设备连上是正常现象的,目前仅测试了安卓和小程序设备,iOS的话由于公司未给我提供设备就没测试
4. 在调用 uni.getBLEDeviceServices 方法中一定要设置一个定时器延迟一点再连接,具体原因不详但是是我踩过坑的
5. 使用固定服务uuid,固定特征uuid进行连接启用notify监听时也同样需要延迟一点再进行连接,不然会报连接失败的错误
完整代码,uniapp中.vue文件,
我已使用固定的uuid,如果需要直接贴上去先获取服务和特征的uuid,更换后再进行测试
<template>
<view class="content">
<button type="default" v-show="!shows" @click="initBle">
初始化蓝牙模块
</button>
<scroll-view scroll-y="true" show-scrollbar="true">
<radio-group>
<view
v-for="(item, index) in bleDevs"
:key="index"
v-show="item.name.length > 0 && !shows"
style="padding: 10rpx 20rpx; border-bottom: 1rpx solid #ececec"
v-if="Math.max(100 + item.RSSI, 0) >= 30"
>
<view style="font-size: 32rpx; color: #333">
<checkbox-group
@change="checkboxChange"
:data-name="item.name"
:data-deviceId="item.deviceId"
>
<label>
<checkbox :value="item.deviceId">
{{ item.name }}
</checkbox>
</label>
</checkbox-group>
</view>
<view style="font-size: 20rpx; padding: 10rpx 0">
deviceId: {{ item.deviceId }} 信号强度: {{ item.RSSI }}dBm ({{
Math.max(100 + item.RSSI, 0)
}}%)
</view>
</view>
<view class="dis">
<view @tap="connectBle" v-if="!shows" class="pl"> 连接 </view>
<view @tap="close" v-if="shows" class="pl"> 断开 </view>
</view>
</radio-group>
</scroll-view>
<view class="barItems" v-if="shows">
<view class="barItem" v-for="(item, index) in testItems" :key="index">
<view class="name">{{ item.name }}</view>
<!-- <sliderBar
class="bar"
:min="item.min"
:max="item.max"
@change="changeBar($event, item)"
></sliderBar> -->
<view class="bar">
<view class="reduce" @click="changNums(1, item)">-</view>
<input type="tel" v-model="item.typeNums" @input="changeBar(item)" />
<view class="add" @click="changNums(2, item)">+</view>
</view>
</view>
</view>
<view class="timers" v-if="shows">
<view class="time">
{{ titleTime }}
</view>
<view class="btns">
<view @click="begin">启动</view>
<view @click="pause">暂停</view>
<view @click="stop">停止</view>
</view>
</view>
<view v-if="shows">
<view class="input3">
<input type="text" v-model="input1" />
<input type="text" v-model="input2" />
</view>
<button type="default" class="send" @click="send(1)">发送</button>
</view>
<view class="appItems">
<viwe
:class="[item.status ? 'item bakBlue' : 'item']"
v-for="(item, index) in totalList"
:key="index"
>
<view class="txt">{{ item.text }}</view>
<view class="name p_hide">{{ item.name }}</view>
</viwe>
</view>
<view class="items" v-if="shows">
<view class="item" v-for="(item, index) in getData" :key="index">
{{ item.name }}:{{ item.txt }}
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
config: {
color: "#333",
backgroundColor: [1, "#fff"],
title: "多设备蓝牙连接",
back: false,
},
title: "Hello",
bleDevs: [],
status: -2, //-2未连接 -1已连接 0连接成功
deviceId: "",
serviceId: "",
characteristicId: "",
sendData: "",
getData: [],
deviceIds: [],
totalList: [], // 全部已连接的设备
timeIndex: 0, // 默认是列表的第一个
timeout: null,
shows: false,
testItems: [
{
index: 1,
typeNums: 1,
min: 0,
max: 150,
name: "设定频率",
value: "F",
},
{
index: 2,
typeNums: 250,
min: 50,
max: 250,
name: "设定脉宽",
value: "W",
},
{ index: 3, typeNums: 3, min: 0, max: 3, name: "设定类型", value: "C" },
{
index: 4,
typeNums: 0,
min: 0,
max: 120,
name: "设定电流",
value: "I",
},
{
index: 5,
typeNums: 0,
min: 1,
max: 100,
name: "设定方案",
value: "M",
},
],
titleTime: "00:00:00",
timer: "",
hour: 0,
minutes: 0,
seconds: 0,
input1: "B",
input2: "",
};
},
destroyed() {
clearInterval(this.timer);
},
onLoad() {},
mounted() {
this.onBLEConnectionStateChange();
},
methods: {
// 开始计时
begin() {
if (this.start) {
return;
}
this.sendData = "BS1\r";
this.start = true;
this.timer = setInterval(this.startTimer, 1000);
this.send();
},
startTimer() {
this.seconds += 1;
if (this.seconds >= 60) {
this.seconds = 0;
this.minute = this.minute + 1;
}
if (this.minute >= 60) {
this.minute = 0;
this.hour = this.hour + 1;
}
this.titleTime =
(this.hour < 10 ? "0" + this.hour : this.hour) +
":" +
(this.minutes < 10 ? "0" + this.minutes : this.minutes) +
":" +
(this.seconds < 10 ? "0" + this.seconds : this.seconds);
},
// 暂停倒计时
pause() {
if (this.timer) {
clearInterval(this.timer);
this.start = false;
this.sendData = "BS2\r";
this.send();
// this.timer = null
}
},
stop() {
if (this.timer) {
clearInterval(this.timer);
// this.timer = null
this.sendData = "BS3\r";
this.send();
this.titleTime = "00:00:00";
this.timer = "";
this.hour = 0;
this.minutes = 0;
this.seconds = 0;
this.start = false;
}
},
changNums(index, item) {
// 1为减少,2为增加
if (index == 1) {
if (item.typeNums <= item.min) {
uni.showToast({
title: "已经不能再减少了",
icon: "none",
});
return;
}
item.typeNums--;
} else if (index == 2) {
if (item.typeNums >= item.max) {
uni.showToast({
title: "已经不能再增加了",
icon: "none",
});
return;
}
item.typeNums++;
}
this.changeBar(item);
},
changeBar(item) {
// 处理防抖
if (this.timeout) {
clearTimeout(this.timeout);
}
this.timeout = setTimeout(() => {
if (item.typeNums < item.min) {
uni.showToast({
title: "低于最小值,已变更为最小值发送",
icon: "none",
});
item.typeNums = item.min;
} else if (item.typeNums > item.max) {
uni.showToast({
title: "超过最大值,已变更为最大值发送",
icon: "none",
});
item.typeNums = item.max;
}
this.sendData = "B" + item.value + item.typeNums + "\r";
for (let i = 0; i < this.deviceIds.length; i++) {
this.getBLEDeviceServices(1, this.deviceIds[i]);
}
}, 500);
},
checkboxChange(e) {
if (e.target.value[0] && e.target.dataset.name) {
let item = {
deviceId: e.target.value[0],
name: e.target.dataset.name,
};
this.deviceIds.push(item);
} else {
for (let index = 0; index < this.deviceIds.length; index++) {
let item = this.deviceIds[index];
if (item.deviceId == e.target.dataset.deviceid) {
this.deviceIds.splice(index, 1);
}
}
}
},
hextoString(hex) {
var arr = hex.split("");
var out = "";
for (var i = 0; i < arr.length / 2; i++) {
var tmp = "0x" + arr[i * 2] + arr[i * 2 + 1];
var charValue = String.fromCharCode(tmp);
out += charValue;
}
return out;
},
send(index) {
let that = this;
if (index == 1) {
that.sendData = that.input1 + that.input2 + "\r";
}
if (!that.sendData) {
return uni.showToast({
title: "发送数据不可为空",
icon: "none",
});
}
uni.showLoading({
title: "发送中,请稍等",
mask: true,
});
for (let i = 0; i < that.deviceIds.length; i++) {
that.getBLEDeviceServices(1, that.deviceIds[i]);
}
},
// ArrayBuffer转16进度字符串示例
ab2hex(buffer) {
const hexArr = Array.prototype.map.call(
new Uint8Array(buffer),
function (bit) {
return ("00" + bit.toString(16)).slice(-2);
}
);
return hexArr.join("");
},
onBLEConnectionStateChange() {
uni.onBLEConnectionStateChange((res) => {
// 该方法回调中可以用于处理连接意外断开等异常情况
if (res.connected == false) {
uni.hideLoading();
for (let i = 0; i < this.deviceIds.length; i++) {
if (res.deviceId == this.deviceIds[i].deviceId) {
uni.showToast({
title: this.deviceIds[i].name + " 蓝牙设备断开连接",
icon: "none",
});
}
}
}
});
},
//初始化蓝牙
initBle() {
// console.log("初始化蓝牙>>>");
this.bleDevs = [];
this.deviceIds = [];
uni.openBluetoothAdapter({
success: (res) => {
//已打开
uni.getBluetoothAdapterState({
//蓝牙的匹配状态
success: (res1) => {
// console.log(res1, "“本机设备的蓝牙已打开”");
// 开始搜索蓝牙设备
this.startBluetoothDeviceDiscovery();
},
fail(error) {
uni.showToast({ icon: "none", title: "查看手机蓝牙是否打开" });
},
});
},
fail: (err) => {
//未打开
uni.showToast({ icon: "none", title: "查看手机蓝牙是否打开" });
},
});
},
// 开始搜索蓝牙设备
startBluetoothDeviceDiscovery() {
uni.startBluetoothDevicesDiscovery({
success: (res) => {
// console.log("启动成功", res);
// 发现外围设备
this.onBluetoothDeviceFound();
},
fail: (err) => {
// console.log(err, "错误信息");
},
});
},
// 发现外围设备
onBluetoothDeviceFound() {
// console.log("执行到这--发现外围设备")
uni.onBluetoothDeviceFound((res) => {
// 吧搜索到的设备存储起来,方便我们在页面上展示
if (this.bleDevs.indexOf(res.devices[0]) == -1) {
this.bleDevs.push(res.devices[0]);
}
// console.log("蓝牙列表", res);
});
},
// 多选然后连接
connectBle() {
if (this.deviceIds.length == 0) {
uni.showToast({ title: "请选择连接的设备", icon: "none" });
return;
}
this.getData = [];
// for (let i = 0; i < this.deviceIds.length; i++) {
// this.createBLEConnection(this.deviceIds[i]);
// // this.nowLinkLis(this.deviceIds[i]);
// }
this.deviceIds.forEach((item) => {
// this.createBLEConnection(item);
this.nowLinkLis(item);
});
},
//选择设备连接吧deviceId传进来
createBLEConnection(item) {
uni.showLoading({
title: "连接中,请稍等",
mask: true,
});
let that = this;
//连接蓝牙
uni.createBLEConnection({
deviceId: item.deviceId,
success(res) {
that.shows = true;
that.stopBluetoothDevicesDiscovery();
that.getBLEDeviceServices(2, item);
},
fail(res) {
console.log("蓝牙连接失败", res);
uni.showToast({
title: items.name + "蓝牙连接失败",
icon: "none",
});
},
});
},
// 停止搜寻蓝牙设备
stopBluetoothDevicesDiscovery() {
uni.stopBluetoothDevicesDiscovery({
success: (e) => {
this.loading = false;
// console.log("停止搜索蓝牙设备:" + e.errMsg);
},
fail: (e) => {
console.log("停止搜索蓝牙设备失败,错误码:" + e.errCode);
},
});
},
//获取蓝牙的所有服务
getBLEDeviceServices(index, items) {
setTimeout(() => {
uni.getBLEDeviceServices({
// 这里的 deviceId 需要已经通过 createBLEConnection 与对应设备建立链接
deviceId: items.deviceId,
success: (res) => {
// console.log("成功",res)
// console.log("device services:", res);
//这里会获取到好多个services uuid 我们只存储我们需要用到的就行,这个uuid一般硬件厂家会给我们提供
console.log("services", res.services);
res.services.forEach((item) => {
if (
item.uuid.indexOf("0000FFE0-0000-1000-8000-00805F9B34FB") != -1
) {
items["serviceId"] = item.uuid;
//进入特征
this.getBLEDeviceCharacteristics(index, items);
}
});
},
});
}, 1000);
},
//获取蓝牙特征
getBLEDeviceCharacteristics(index, items) {
// console.log("进入特征");
setTimeout(() => {
uni.getBLEDeviceCharacteristics({
// 这里的 deviceId 需要已经通过 createBLEConnection 与对应设备建立链接
deviceId: items.deviceId,
// 这里的 serviceId 需要在 getBLEDeviceServices 接口中获取
serviceId: items.serviceId,
success: (res) => {
console.log("characteristics", res);
res.characteristics.forEach((item) => {
if (
// 2 支持监听 1 支持写入
item.uuid.indexOf(
index == 1
? "0000FFE1-0000-1000-8000-00805F9B34FB"
: "0000FFE2-0000-1000-8000-00805F9B34FB"
) != -1
) {
items["characteristicId"] = item.uuid;
if (index == 2) {
this.notifyBLECharacteristicValueChange(items);
}
}
});
if (index == 1) {
this.writeString(this