551 lines
12 KiB
Vue
551 lines
12 KiB
Vue
<template>
|
|
<view class="bluetooth-container">
|
|
<!-- 导航栏 -->
|
|
<u-navbar title="蓝牙测试" :border-bottom="false" :background="bgc" title-color='#000' title-size='36'
|
|
height='45'></u-navbar>
|
|
|
|
<!-- 蓝牙状态栏 -->
|
|
<view class="status-bar">
|
|
<view class="status-text">{{ bluetoothStatus }}</view>
|
|
<view class="switch-btn" @tap="toggleBluetooth">
|
|
<text>{{ isSearching ? '停止搜索' : '开始搜索' }}</text>
|
|
</view>
|
|
<view class="switch-btn" v-if="hasConnectedDevice" @tap="rings">
|
|
<text>响铃</text>
|
|
</view>
|
|
</view>
|
|
<!-- <view class="ring-btn" v-if="hasConnectedDevice" @click="toggleBluetooth">
|
|
<text>响铃</text>
|
|
</view> -->
|
|
<!-- 设备列表 -->
|
|
<scroll-view class="device-list" scroll-y>
|
|
<template v-if="devices.length === 0">
|
|
<view class="empty-tip">
|
|
<text>{{ isSearching ? '正在搜索BBLE设备...' : '暂无BBLE设备' }}</text>
|
|
</view>
|
|
</template>
|
|
<template v-else>
|
|
<view v-for="(device, index) in devices" :key="index" class="device-item"
|
|
@tap="handleDeviceClick(device)">
|
|
<view class="device-info">
|
|
<text class="device-name">{{ device.name || '未知设备' }}</text>
|
|
<text class="device-mac">MAC: {{ device.mac }}</text>
|
|
<text class="device-signal">信号强度: {{ device.RSSI }}dBm</text>
|
|
</view>
|
|
<view class="device-status">
|
|
<text :class="['status-dot', device.connected ? 'connected' : '']"></text>
|
|
{{ device.connected ? '已连接' : '未连接' }}
|
|
</view>
|
|
</view>
|
|
</template>
|
|
</scroll-view>
|
|
|
|
<!-- 连接确认弹窗 -->
|
|
<view class="modal" v-if="showModal">
|
|
<view class="modal-content">
|
|
<view class="modal-title">连接确认</view>
|
|
<view class="modal-body">
|
|
<text>是否连接设备?</text>
|
|
<text class="device-detail" v-if="selectedDevice">设备MAC: {{ selectedDevice.mac }}</text>
|
|
</view>
|
|
<view class="modal-footer">
|
|
<button class="btn cancel" @tap="closeModal">取消</button>
|
|
<button class="btn confirm" @tap="confirmConnect">确认</button>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 加载提示 -->
|
|
<view class="loading-mask" v-if="isConnecting">
|
|
<view class="loading-content">
|
|
<view class="loading-spinner"></view>
|
|
<text>正在连接设备...</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script>
|
|
import xBlufi from '@/utils/blufi/xBlufi.js'
|
|
|
|
export default {
|
|
data() {
|
|
return {
|
|
devices: [],
|
|
isSearching: false,
|
|
isConnecting: false,
|
|
bluetoothStatus: '未初始化',
|
|
showModal: false,
|
|
selectedDevice: null,
|
|
hasConnectedDevice: false,
|
|
bgc: {
|
|
backgroundColor: "#F7FAFE",
|
|
},
|
|
serviceId: '0000FEE0-0000-1000-8000-00805F9B34FB',
|
|
characteristicId: '0000FEE1-0000-1000-8000-00805F9B34FB'
|
|
}
|
|
},
|
|
|
|
onLoad() {
|
|
this.initBluetooth()
|
|
},
|
|
|
|
methods: {
|
|
initBluetooth() {
|
|
xBlufi.initXBlufi(xBlufi.XMQTT_SYSTEM.Alis)
|
|
|
|
xBlufi.listenDeviceMsgEvent(true, (res) => {
|
|
console.log('设备消息:', res)
|
|
switch (res.type) {
|
|
case xBlufi.XBLUFI_TYPE.TYPE_GET_DEVICE_LISTS:
|
|
if (res.result && res.data) {
|
|
console.log("搜索到设备:", res.data)
|
|
const bbleDevices = res.data.filter(device => {
|
|
const name = device.name || device.deviceName || '';
|
|
return name.startsWith('BBLE');
|
|
});
|
|
|
|
this.devices = bbleDevices.map(device => ({
|
|
deviceId: device.deviceId,
|
|
name: device.name || device.deviceName || '未知设备',
|
|
mac: device.deviceId,
|
|
RSSI: device.RSSI || 0,
|
|
connected: false
|
|
}));
|
|
}
|
|
break;
|
|
|
|
case xBlufi.XBLUFI_TYPE.TYPE_STATUS_CONNECTED:
|
|
this.handleConnectionStatus(res);
|
|
break;
|
|
|
|
case xBlufi.XBLUFI_TYPE.TYPE_INIT_ESP32_RESULT:
|
|
this.handleInitResult(res);
|
|
break;
|
|
|
|
case xBlufi.XBLUFI_TYPE.TYPE_GET_DEVICE_LISTS_START:
|
|
console.log("开始搜索设备");
|
|
this.devices = [];
|
|
break;
|
|
|
|
case xBlufi.XBLUFI_TYPE.TYPE_GET_DEVICE_LISTS_STOP:
|
|
console.log("停止搜索设备");
|
|
this.isSearching = false;
|
|
break;
|
|
}
|
|
});
|
|
|
|
this.bluetoothStatus = '已初始化'
|
|
},
|
|
|
|
toggleBluetooth() {
|
|
if (this.isSearching) {
|
|
xBlufi.notifyStartDiscoverBle({
|
|
isStart: false
|
|
});
|
|
this.isSearching = false;
|
|
} else {
|
|
this.devices = [];
|
|
this.isSearching = true;
|
|
xBlufi.notifyStartDiscoverBle({
|
|
isStart: true
|
|
});
|
|
|
|
setTimeout(() => {
|
|
if (this.isSearching) {
|
|
xBlufi.notifyStartDiscoverBle({
|
|
isStart: false
|
|
});
|
|
this.isSearching = false;
|
|
}
|
|
}, 10000);
|
|
}
|
|
},
|
|
|
|
handleDeviceClick(device) {
|
|
if (device.connected) {
|
|
uni.showModal({
|
|
title: '断开连接',
|
|
content: '是否断开与该设备的连接?',
|
|
success: (res) => {
|
|
if (res.confirm) {
|
|
this.disconnectDevice(device);
|
|
}
|
|
}
|
|
});
|
|
} else {
|
|
this.selectedDevice = device;
|
|
this.showModal = true;
|
|
}
|
|
},
|
|
|
|
async confirmConnect() {
|
|
if (!this.selectedDevice) return;
|
|
|
|
this.showModal = false;
|
|
this.isConnecting = true;
|
|
|
|
try {
|
|
xBlufi.notifyConnectBle({
|
|
connect: true,
|
|
deviceId: this.selectedDevice.deviceId
|
|
});
|
|
|
|
xBlufi.notifyInitBleEsp32({
|
|
deviceId: this.selectedDevice.deviceId
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('连接失败:', error);
|
|
uni.showToast({
|
|
title: '连接失败',
|
|
icon: 'none'
|
|
});
|
|
} finally {
|
|
this.isConnecting = false;
|
|
}
|
|
},
|
|
|
|
disconnectDevice(device) {
|
|
xBlufi.notifyConnectBle({
|
|
connect: false,
|
|
deviceId: device.deviceId
|
|
});
|
|
},
|
|
|
|
handleConnectionStatus(res) {
|
|
const index = this.devices.findIndex(d => d.deviceId === res.data.deviceId);
|
|
if (index !== -1) {
|
|
this.$set(this.devices[index], 'connected', res.result);
|
|
if (res.result) {
|
|
this.selectedDevice = this.devices[index];
|
|
this.hasConnectedDevice = true;
|
|
} else {
|
|
this.hasConnectedDevice = false;
|
|
this.selectedDevice = null;
|
|
}
|
|
}
|
|
|
|
if (res.result) {
|
|
uni.showToast({
|
|
title: '连接成功',
|
|
icon: 'success'
|
|
});
|
|
} else {
|
|
uni.showToast({
|
|
title: '连接断开',
|
|
icon: 'none'
|
|
});
|
|
}
|
|
},
|
|
|
|
rings() {
|
|
if(!this.selectedDevice || !this.hasConnectedDevice) {
|
|
uni.showToast({
|
|
title: '设备未连接',
|
|
icon: 'none'
|
|
});
|
|
return;
|
|
}
|
|
|
|
my.getBluetoothAdapterState({
|
|
success: (res) => {
|
|
if(!res.available) {
|
|
uni.showToast({
|
|
title: '蓝牙不可用',
|
|
icon: 'none'
|
|
});
|
|
return;
|
|
}
|
|
this.sendRingCommand();
|
|
}
|
|
});
|
|
},
|
|
|
|
sendRingCommand() {
|
|
my.getBLEDeviceServices({
|
|
deviceId: this.selectedDevice.deviceId,
|
|
success: (res) => {
|
|
my.getBLEDeviceCharacteristics({
|
|
deviceId: this.selectedDevice.deviceId,
|
|
serviceId: this.serviceId,
|
|
success: (res) => {
|
|
if(res.characteristics.some(c => c.properties.write)) {
|
|
my.writeBLECharacteristicValue({
|
|
deviceId: this.selectedDevice.deviceId,
|
|
serviceId: this.serviceId,
|
|
characteristicId: this.characteristicId,
|
|
value: this.string2buffer("11play1@"),
|
|
success: (res) => {
|
|
console.log('发送响铃命令成功:', res);
|
|
uni.showToast({
|
|
title: '发送成功',
|
|
icon: 'success'
|
|
});
|
|
},
|
|
fail: (err) => {
|
|
console.error('发送响铃命令失败:', err);
|
|
uni.showToast({
|
|
title: '发送失败',
|
|
icon: 'none'
|
|
});
|
|
// 失败后1秒后重试一次
|
|
setTimeout(() => {
|
|
this.sendRingCommand();
|
|
}, 1000);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
});
|
|
},
|
|
|
|
string2buffer(str) {
|
|
let val = new ArrayBuffer(str.length);
|
|
let dataView = new DataView(val);
|
|
for (let i = 0; i < str.length; i++) {
|
|
dataView.setUint8(i, str.charCodeAt(i));
|
|
}
|
|
return val;
|
|
},
|
|
|
|
handleInitResult(res) {
|
|
if (res.result) {
|
|
console.log('设备初始化成功');
|
|
} else {
|
|
console.error('设备初始化失败');
|
|
uni.showToast({
|
|
title: '设备初始化失败',
|
|
icon: 'none'
|
|
});
|
|
}
|
|
},
|
|
|
|
closeModal() {
|
|
this.showModal = false;
|
|
this.selectedDevice = null;
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss">
|
|
.bluetooth-container {
|
|
min-height: 100vh;
|
|
background-color: #F7FAFE;
|
|
|
|
.ring-btn {
|
|
position: fixed;
|
|
bottom: 40rpx;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
width: 570rpx;
|
|
height: 96rpx;
|
|
background: #4C97E7;
|
|
border-radius: 48rpx;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
|
|
text {
|
|
color: #fff;
|
|
font-size: 32rpx;
|
|
}
|
|
}
|
|
|
|
.status-bar {
|
|
padding: 20rpx;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
background-color: #fff;
|
|
margin-bottom: 20rpx;
|
|
|
|
.status-text {
|
|
font-size: 28rpx;
|
|
color: #333;
|
|
}
|
|
|
|
.switch-btn {
|
|
padding: 12rpx 30rpx;
|
|
background-color: #4C97E7;
|
|
border-radius: 30rpx;
|
|
|
|
text {
|
|
color: #fff;
|
|
font-size: 26rpx;
|
|
}
|
|
}
|
|
}
|
|
|
|
.device-list {
|
|
height: calc(100vh - 200rpx);
|
|
padding: 0 20rpx;
|
|
|
|
.empty-tip {
|
|
text-align: center;
|
|
padding: 100rpx 0;
|
|
color: #999;
|
|
font-size: 28rpx;
|
|
}
|
|
|
|
.device-item {
|
|
margin-bottom: 20rpx;
|
|
padding: 30rpx;
|
|
background-color: #fff;
|
|
border-radius: 12rpx;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
|
|
.device-info {
|
|
.device-name {
|
|
font-size: 30rpx;
|
|
color: #333;
|
|
margin-bottom: 8rpx;
|
|
display: block;
|
|
}
|
|
|
|
.device-mac {
|
|
font-size: 24rpx;
|
|
color: #666;
|
|
margin-bottom: 4rpx;
|
|
display: block;
|
|
}
|
|
|
|
.device-signal {
|
|
font-size: 24rpx;
|
|
color: #999;
|
|
}
|
|
}
|
|
|
|
.device-status {
|
|
display: flex;
|
|
align-items: center;
|
|
font-size: 26rpx;
|
|
color: #666;
|
|
|
|
.status-dot {
|
|
width: 12rpx;
|
|
height: 12rpx;
|
|
border-radius: 50%;
|
|
background-color: #999;
|
|
margin-right: 8rpx;
|
|
|
|
&.connected {
|
|
background-color: #4C97E7;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
.modal {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background-color: rgba(0, 0, 0, 0.5);
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
|
|
.modal-content {
|
|
width: 600rpx;
|
|
background-color: #fff;
|
|
border-radius: 12rpx;
|
|
padding: 40rpx;
|
|
|
|
.modal-title {
|
|
font-size: 32rpx;
|
|
font-weight: bold;
|
|
text-align: center;
|
|
margin-bottom: 30rpx;
|
|
}
|
|
|
|
.modal-body {
|
|
padding: 20rpx 0;
|
|
|
|
.device-detail {
|
|
display: block;
|
|
font-size: 26rpx;
|
|
color: #666;
|
|
margin-top: 10rpx;
|
|
}
|
|
}
|
|
|
|
.modal-footer {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
margin-top: 40rpx;
|
|
|
|
.btn {
|
|
width: 240rpx;
|
|
height: 80rpx;
|
|
line-height: 80rpx;
|
|
text-align: center;
|
|
border-radius: 40rpx;
|
|
font-size: 28rpx;
|
|
|
|
&.cancel {
|
|
background-color: #f5f5f5;
|
|
color: #666;
|
|
}
|
|
|
|
&.confirm {
|
|
background-color: #4C97E7;
|
|
color: #fff;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
.loading-mask {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background-color: rgba(0, 0, 0, 0.5);
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
|
|
.loading-content {
|
|
background-color: #fff;
|
|
padding: 40rpx;
|
|
border-radius: 12rpx;
|
|
text-align: center;
|
|
|
|
.loading-spinner {
|
|
width: 60rpx;
|
|
height: 60rpx;
|
|
border: 4rpx solid #f3f3f3;
|
|
border-top: 4rpx solid #4C97E7;
|
|
border-radius: 50%;
|
|
margin: 0 auto 20rpx;
|
|
animation: spin 1s linear infinite;
|
|
}
|
|
|
|
text {
|
|
font-size: 28rpx;
|
|
color: #333;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@keyframes spin {
|
|
0% {
|
|
transform: rotate(0deg);
|
|
}
|
|
|
|
100% {
|
|
transform: rotate(360deg);
|
|
}
|
|
}
|
|
</style> |