1267 lines
30 KiB
Vue
1267 lines
30 KiB
Vue
<template>
|
||
<view class="container">
|
||
<!-- 蓝牙设备列表页 -->
|
||
<view v-if="currentPage === 'deviceList'">
|
||
<view class="title">蓝牙设备列表</view>
|
||
|
||
<view class="status-bar">
|
||
<text>蓝牙状态: {{ bluetoothStatus }}</text>
|
||
<button class="btn" :disabled="isLoading" @click="toggleBluetooth">
|
||
{{ isBluetoothOpen ? '关闭蓝牙' : '开启蓝牙' }}
|
||
</button>
|
||
</view>
|
||
|
||
<view class="search-area">
|
||
<button class="btn search-btn" :disabled="!isBluetoothOpen || isSearching" @click="startSearch">
|
||
{{ isSearching ? '正在搜索...' : '搜索设备' }}
|
||
</button>
|
||
<text class="tips">{{ searchTips }}</text>
|
||
</view>
|
||
|
||
<view class="devices-list" v-if="deviceList.length > 0">
|
||
<view class="device-item" v-for="(device, index) in deviceList" :key="index"
|
||
:class="{'active': device.name === targetDeviceName}"
|
||
@click="connectDevice(device.deviceId, device.name)">
|
||
<text class="device-name">{{ device.name || '未知设备' }}</text>
|
||
<text class="device-rssi">{{ device.RSSI }}dBm</text>
|
||
<text class="device-status" v-if="device.name === targetDeviceName">已连接</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 主控制页面 -->
|
||
<view v-if="currentPage === 'controlPanel'">
|
||
|
||
|
||
<view class="status-bar">
|
||
<text>已连接: {{ targetDeviceName }}</text>
|
||
<button class="btn" @click="disconnectDevice(null)">断开连接</button>
|
||
</view>
|
||
<view class="status-bar">
|
||
<text>当前模式: {{ netMode=='ble'?'蓝牙模式':netMode=='mqtt'?'MQ模式':'Http模式' }}</text>
|
||
<button class="btn" @click="toggleNetMode()">模式切换</button>
|
||
</view>
|
||
<view class="receivLog">
|
||
<view>数据更新时间:{{receiveData.date}}</view>
|
||
<view class="w50 fleft">Mac:{{receiveData.macAddress}}</view>
|
||
<view class="w50 fleft">SOS模式:{{receiveData.SOS}}</view>
|
||
<view class="w50 fleft">箭头档位:{{receiveData.ArrowType}}</view>
|
||
<view class="w50 fleft">配组档位:{{receiveData.GroupType}}</view>
|
||
<view class="w50 fleft">电池电量:{{receiveData.batteryLevel}}</view>
|
||
<view class="clear"></view>
|
||
</view>
|
||
|
||
<view class="sending-progress" v-if="isSending">
|
||
|
||
<view class="progress-bar">
|
||
<view class="progress-fill" :style="{ width: progress + '%' }"></view>
|
||
</view>
|
||
<text>正在发送: {{ progress }}% ({{ currentPacket }}/{{ totalPackets }})</text>
|
||
</view>
|
||
<view v-show="currentTab === 'splash' || currentTab === 'text'">
|
||
<text>发送间隔:</text>
|
||
<input type="number" v-model="inteval" class="txt" />
|
||
</view>
|
||
|
||
|
||
<view class="function-tabs">
|
||
<button class="tab-btn" :class="{'active': currentTab === 'sos'}"
|
||
@click="currentTab = 'sos'">SOS档位</button>
|
||
<button class="tab-btn" :class="{'active': currentTab === 'arrow'}"
|
||
@click="currentTab = 'arrow'">箭头档位</button>
|
||
|
||
<button class="tab-btn" :class="{'active': currentTab === 'peizu'}"
|
||
@click="currentTab = 'peizu'">配组档位</button>
|
||
|
||
|
||
</view>
|
||
|
||
<!-- 模式选项卡 -->
|
||
<view v-if="currentTab === 'sos'">
|
||
<view class="modes-area">
|
||
<view class="mode-title">SOS档位控制</view>
|
||
<view class="modes-grid">
|
||
<view class="mode-item" :class="{'selected': currentMode === 'sos'}" @click="setMode('sos')">
|
||
|
||
<text class="mode-name">SOS</text>
|
||
</view>
|
||
<view class="mode-item" :class="{'selected': currentMode === 'fan'}" @click="setMode('fan')">
|
||
|
||
<text class="mode-name">泛光</text>
|
||
</view>
|
||
|
||
<view class="mode-item" :class="{'selected': currentMode === 'sos_off'}"
|
||
@click="setMode('sos_off')">
|
||
|
||
<text class="mode-name">关闭</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<!-- 箭头档位选项卡 -->
|
||
<view v-if="currentTab === 'arrow'">
|
||
<view class="brightness-area">
|
||
|
||
<button class="tab-btn" :class="{'active': brightnessStatu==='red_front'}"
|
||
@click="ArrowSetting('red_front')">红色超前</button>
|
||
<button class="tab-btn" :class="{'active': brightnessStatu==='green_back'}"
|
||
@click="ArrowSetting('green_back')">绿色朝后</button>
|
||
<button class="tab-btn" :class="{'active': brightnessStatu==='red_all'}"
|
||
@click="ArrowSetting('red_all')">全部红色</button>
|
||
<button class="tab-btn" :class="{'active': brightnessStatu==='green_all'}"
|
||
@click="ArrowSetting('green_all')">全部绿色</button>
|
||
|
||
<button class="tab-btn" :class="{'active': brightnessStatu==='arrow_off'}"
|
||
@click="ArrowSetting('arrow_off')">关闭</button>
|
||
</view>
|
||
</view>
|
||
<!-- 配组档位选项卡 -->
|
||
<view v-if="currentTab === 'peizu'">
|
||
<view class="brightness-area">
|
||
|
||
<button class="tab-btn" :class="{'active': peizuStatu==='red_group'}"
|
||
@click="groupSetting('red_group')">红配组</button>
|
||
<button class="tab-btn" :class="{'active': peizuStatu==='blue_group'}"
|
||
@click="groupSetting('blue_group')">蓝配组</button>
|
||
<button class="tab-btn" :class="{'active': peizuStatu==='green_group'}"
|
||
@click="groupSetting('green_group')">绿配组</button>
|
||
<button class="tab-btn" :class="{'active': peizuStatu==='yellow_group'}"
|
||
@click="groupSetting('yellow_group')">黄配组</button>
|
||
</view>
|
||
</view>
|
||
|
||
|
||
|
||
</view>
|
||
|
||
|
||
|
||
|
||
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import gbk from '@/utils/gbk.js';
|
||
import MqttClient from '@/utils/mqtt.js';
|
||
import {
|
||
request,
|
||
baseURL
|
||
} from '@/utils/request.js';
|
||
|
||
|
||
let sosDic = {
|
||
sos: ["sos", ""],
|
||
fan: ["fan", "泛光"],
|
||
sos_off: ["sos_off", "关闭"]
|
||
};
|
||
let arrowDic = {
|
||
red_front: ["red_front", "红色朝前"],
|
||
green_back: ["green_back", "绿色朝后"],
|
||
red_all: ["red_all", "全部红色"],
|
||
green_all: ["green_all", "全部绿色"],
|
||
arrow_off: ["arrow_off", "关闭"]
|
||
};
|
||
let groupDic = {
|
||
red_group: ["red_group", "红配组"],
|
||
blue_group: ["blue_group", "蓝配组"],
|
||
green_group: ["green_group", "绿配组"],
|
||
yellow_group: ["yellow_group", "黄配组"]
|
||
};
|
||
|
||
export default {
|
||
data() {
|
||
return {
|
||
brightnessStatu: '',
|
||
currentMode: '',
|
||
peizuStatu: '',
|
||
|
||
rgb565Data: null,
|
||
netMode: 'ble',
|
||
canvasTop: '-1000px',
|
||
canvasLeft: '-1000px',
|
||
|
||
inteval: 20,
|
||
isLoading: false,
|
||
isBluetoothOpen: false,
|
||
isSearching: false,
|
||
isConnected: false,
|
||
isSending: false,
|
||
isSendingText: false,
|
||
bluetoothStatus: '未初始化',
|
||
searchTips: '',
|
||
deviceList: [],
|
||
connectedDeviceId: '',
|
||
serviceId: '0xFFE1',
|
||
writeCharacteristicId: '0xFFE1',
|
||
notifyCharacteristicId: '0xFFE2',
|
||
|
||
logList: [],
|
||
packetCount: 1,
|
||
currentPage: 'deviceList',
|
||
targetDeviceName: ['JQZM-EF4651', 'XLCX-68455E'],
|
||
currentTab: 'sos',
|
||
brightness: 50,
|
||
brightnessTimer: null,
|
||
tempImagePath: '',
|
||
hasImage: false,
|
||
scale: 1,
|
||
translateX: 0,
|
||
translateY: 0,
|
||
lastX: 0,
|
||
lastY: 0,
|
||
isDragging: false,
|
||
|
||
imageWidth: 0,
|
||
imageHeight: 0,
|
||
textLines: ['你好中国', '你好中国', '你好中国', '你好中国'],
|
||
textBytes: ['', '', ''],
|
||
warnLines: ['现场危险,停止救援,', '紧急撤离至安全区域!'],
|
||
progress: 0,
|
||
currentPacket: 0,
|
||
totalPackets: 100,
|
||
textProgress: 0,
|
||
currentTextPacket: 0,
|
||
totalTextPackets: 4,
|
||
|
||
receiveData: {
|
||
date: "",
|
||
batteryLevel: "",
|
||
macAddress: "",
|
||
SOS: "",
|
||
ArrowType: "",
|
||
GroupType: "",
|
||
|
||
},
|
||
subscits: [],
|
||
videoPath: "",
|
||
videoWidth: "",
|
||
videoHeight: "",
|
||
videoDuration: "",
|
||
currentSOS: "",
|
||
reSendNumber: null
|
||
}
|
||
},
|
||
computed: {
|
||
|
||
},
|
||
onLoad() {
|
||
|
||
this.initBluetoothAdapter();
|
||
|
||
},
|
||
|
||
methods: {
|
||
parseData(receive) {
|
||
|
||
|
||
console.log("收到消息:", receive)
|
||
|
||
function bytesToHexString(bytes) {
|
||
return bytes.map(
|
||
byte =>
|
||
byte
|
||
.toString(
|
||
16)
|
||
.padStart(
|
||
2, '0')
|
||
).join(' ');
|
||
}
|
||
|
||
var todo = (bytes) => {
|
||
|
||
let date = new Date();
|
||
this.receiveData.date = date.getHours() + ':' + date.getMinutes() + ':' + date.getSeconds();
|
||
|
||
try {
|
||
console.log("111");
|
||
let uint8Array = new Uint8Array(receive.value);
|
||
let str = '';
|
||
console.log("111");
|
||
for (let i = 0; i < uint8Array.length; i++) {
|
||
// 将每个字节转换为对应的字符
|
||
str += String.fromCharCode(uint8Array[i]);
|
||
}
|
||
|
||
let json = JSON.parse(str);
|
||
console.log("获取并解析到文本,", json);
|
||
if ("sta_SOSType" in json) {
|
||
console.log("item=",sosDic[json.sta_SOSType]);
|
||
this.receiveData.SOS = sosDic[json.sta_SOSType][1];
|
||
this.currentMode = sosDic[json.sta_SOSType][0];
|
||
}
|
||
|
||
if ("sta_ArrowType" in json) {
|
||
console.log("item=",arrowDic[json.sta_ArrowType]);
|
||
this.receiveData.ArrowType = arrowDic[json.sta_ArrowType][1];
|
||
this.brightnessStatu = arrowDic[json.sta_ArrowType][0];
|
||
}
|
||
|
||
if ("sta_GroupType" in json) {
|
||
console.log("item=",groupDic[json.sta_GroupType]);
|
||
this.receiveData.GroupType = groupDic[json.sta_GroupType][1];
|
||
this.peizuStatu = groupDic[json.sta_GroupType][0];
|
||
}
|
||
|
||
if ('sta_PowerPercent' in json) {
|
||
this.receiveData.batteryLevel = json.sta_PowerPercent;
|
||
}
|
||
if ('sta_address' in json) {
|
||
this.receiveData.macAddress = json.sta_address;
|
||
}
|
||
|
||
console.log('1111111', json);
|
||
} catch (ex) {
|
||
console.log('将数据转文本失败', ex);
|
||
}
|
||
|
||
}
|
||
|
||
|
||
let dataView =
|
||
new DataView(receive
|
||
.value);
|
||
|
||
// 转换为字节数组
|
||
let bytes = [];
|
||
for (let i = 0; i < dataView.byteLength; i++) {
|
||
bytes.push(dataView.getUint8(i));
|
||
}
|
||
|
||
// 保存原始数据用于调试
|
||
this.receivedData = bytes;
|
||
console.log('接收到原始数据:', bytesToHexString(bytes));
|
||
|
||
todo(bytes);
|
||
|
||
|
||
},
|
||
setMode(mode) {
|
||
if (!this.isConnected) {
|
||
uni.showToast({
|
||
title: '未连接设备',
|
||
icon: 'none'
|
||
});
|
||
return;
|
||
}
|
||
|
||
if (this.currentMode === mode) return;
|
||
|
||
this.currentMode = mode;
|
||
|
||
|
||
|
||
var buffer = {
|
||
ins_SOSType: sosDic[mode][0]
|
||
}
|
||
// 发送数据
|
||
this.sendData(buffer);
|
||
},
|
||
ArrowSetting(mode) {
|
||
if (!this.isConnected) {
|
||
uni.showToast({
|
||
title: '未连接设备',
|
||
icon: 'none'
|
||
});
|
||
return;
|
||
}
|
||
|
||
if (this.brightnessStatu === mode) return;
|
||
|
||
this.brightnessStatu = mode;
|
||
|
||
var buffer = {
|
||
ins_ArrowType: arrowDic[mode][0]
|
||
}
|
||
|
||
// 发送数据
|
||
this.sendData(buffer);
|
||
|
||
},
|
||
groupSetting(mode) {
|
||
if (!this.isConnected) {
|
||
uni.showToast({
|
||
title: '未连接设备',
|
||
icon: 'none'
|
||
});
|
||
return;
|
||
}
|
||
|
||
if (this.peizuStatu === mode) return;
|
||
|
||
this.peizuStatu = mode;
|
||
|
||
|
||
|
||
var buffer = {
|
||
ins_GroupType: groupDic[mode][0]
|
||
}
|
||
|
||
|
||
// 发送数据
|
||
this.sendData(buffer);
|
||
|
||
},
|
||
|
||
// 蓝牙初始化
|
||
initBluetoothAdapter() {
|
||
console.log('开始初始化蓝牙适配器');
|
||
uni.openBluetoothAdapter({
|
||
success: (res) => {
|
||
this.isBluetoothOpen = true;
|
||
this.bluetoothStatus = '已开启';
|
||
console.log('蓝牙适配器初始化成功');
|
||
this.getBluetoothAdapterState();
|
||
|
||
// 自动开始搜索设备
|
||
this.startSearch();
|
||
},
|
||
fail: (err) => {
|
||
this.bluetoothStatus = '初始化失败';
|
||
console.log(`蓝牙适配器初始化失败: ${err.errMsg}`);
|
||
if (err.errCode === 10001) {
|
||
uni.onBluetoothAdapterStateChange((res) => {
|
||
if (res.available) {
|
||
this.isBluetoothOpen = true;
|
||
this.bluetoothStatus = '已开启';
|
||
console.log('蓝牙适配器已开启');
|
||
this.startSearch();
|
||
}
|
||
});
|
||
}
|
||
}
|
||
});
|
||
},
|
||
|
||
// 获取蓝牙适配器状态
|
||
getBluetoothAdapterState() {
|
||
uni.getBluetoothAdapterState({
|
||
success: (res) => {
|
||
this.isBluetoothOpen = res.available;
|
||
this.bluetoothStatus = res.available ? '已开启' : '已关闭';
|
||
if (!res.available) {
|
||
console.log('蓝牙适配器未开启');
|
||
}
|
||
}
|
||
});
|
||
},
|
||
|
||
// 开关蓝牙
|
||
toggleBluetooth() {
|
||
if (this.isBluetoothOpen) {
|
||
this.closeBluetoothAdapter();
|
||
} else {
|
||
this.initBluetoothAdapter();
|
||
}
|
||
},
|
||
|
||
// 关闭蓝牙适配器
|
||
closeBluetoothAdapter() {
|
||
|
||
|
||
this.stopSearch();
|
||
this.disconnectDevice(() => {
|
||
uni.closeBluetoothAdapter({
|
||
success: () => {
|
||
console.log("蓝牙已关闭");
|
||
this.isBluetoothOpen = false;
|
||
this.isSearching = false;
|
||
this.isConnected = false;
|
||
this.bluetoothStatus = '已关闭';
|
||
this.deviceList = [];
|
||
this.connectedDeviceId = '';
|
||
this.currentMode = '';
|
||
this.currentPage = 'deviceList';
|
||
}
|
||
})
|
||
});
|
||
|
||
|
||
|
||
},
|
||
|
||
// 开始搜索蓝牙设备
|
||
startSearch() {
|
||
if (this.isSearching) return;
|
||
|
||
this.isSearching = true;
|
||
this.deviceList = [];
|
||
this.searchTips = '搜索中...';
|
||
console.log('开始搜索蓝牙设备');
|
||
|
||
uni.startBluetoothDevicesDiscovery({
|
||
services: ["0000FFE0-0000-1000-8000-00805F9B34FB"],
|
||
allowDuplicatesKey: false,
|
||
success: (res) => {
|
||
console.log('开始搜索蓝牙设备成功');
|
||
this.onDeviceFound();
|
||
|
||
|
||
},
|
||
fail: (err) => {
|
||
this.isSearching = false;
|
||
this.searchTips = '搜索失败';
|
||
console.log(`搜索蓝牙设备失败: ${err.errMsg}`);
|
||
}
|
||
});
|
||
},
|
||
|
||
// 停止搜索蓝牙设备
|
||
stopSearch() {
|
||
this.isSearching = false;
|
||
this.searchTips = this.deviceList.length > 0 ? '搜索完成' : '未发现蓝牙设备';
|
||
|
||
uni.stopBluetoothDevicesDiscovery({
|
||
success: () => {
|
||
console.log('停止搜索蓝牙设备');
|
||
}
|
||
});
|
||
},
|
||
|
||
// 监听发现新设备
|
||
onDeviceFound() {
|
||
uni.onBluetoothDeviceFound((res) => {
|
||
let device = res.devices[0];
|
||
if (!device.name && !device.localName) return;
|
||
|
||
let index = this.deviceList.findIndex(item => item.deviceId === device.deviceId);
|
||
if (index === -1) {
|
||
this.deviceList.push({
|
||
deviceId: device.deviceId,
|
||
name: device.name || device.localName,
|
||
RSSI: device.RSSI
|
||
});
|
||
} else {
|
||
// 更新信号强度
|
||
this.deviceList[index].RSSI = device.RSSI;
|
||
|
||
}
|
||
// }
|
||
|
||
// 如果找到目标设备,自动连接
|
||
if (this.targetDeviceName.indexOf(device.name || device.localName) > -1) {
|
||
console.log(`发现目标设备: ${JSON.stringify(device)}`);
|
||
this.connectDevice(device.deviceId, device.name);
|
||
}
|
||
});
|
||
},
|
||
|
||
// 连接蓝牙设备
|
||
connectDevice(deviceId, deviceName) {
|
||
if (this.isConnected && this.connectedDeviceId === deviceId) return;
|
||
|
||
this.isLoading = true;
|
||
console.log(`开始连接设备: ${deviceId}`);
|
||
var these = this;
|
||
uni.createBLEConnection({
|
||
deviceId: deviceId,
|
||
timeout: 20000,
|
||
success: (res) => {
|
||
this.stopSearch();
|
||
this.isConnected = true;
|
||
this.connectedDeviceId = deviceId;
|
||
this.targetDeviceName = deviceName;
|
||
console.log(`连接设备 ${deviceName} 成功`);
|
||
|
||
// 连接成功后切换到控制面板
|
||
this.currentPage = 'controlPanel';
|
||
|
||
// setTimeout(function() {
|
||
uni.setBLEMTU({
|
||
deviceId: deviceId,
|
||
mtu: 5120,
|
||
success: (info) => {
|
||
console.log("设置mtu成功")
|
||
},
|
||
fail: (ex) => {
|
||
console.log("设置mtu失败" + JSON.stringify(ex));
|
||
},
|
||
complete() {
|
||
these.getDeviceServices(deviceId);
|
||
}
|
||
})
|
||
|
||
|
||
// }, 2000)
|
||
|
||
|
||
|
||
},
|
||
fail: (err) => {
|
||
console.log(`连接设备失败: ${JSON.stringify(err)}`);
|
||
uni.showToast({
|
||
title: '连接失败',
|
||
icon: 'none'
|
||
});
|
||
},
|
||
complete: () => {
|
||
this.isLoading = false;
|
||
}
|
||
});
|
||
},
|
||
|
||
// 断开蓝牙连接
|
||
disconnectDevice(callback) {
|
||
var these = this;
|
||
var promises = [];
|
||
for (var i = 0; i < these.subscits.length; i++) {
|
||
promises.push(new Promise((succ, err) => {
|
||
uni.notifyBLECharacteristicValueChange({
|
||
deviceId: these.connectedDeviceId,
|
||
serviceId: these.subscits[i].serviceId,
|
||
characteristicId: these.subscits[i].notifyId,
|
||
state: false,
|
||
success: () => {
|
||
console.log("取消订阅成功");
|
||
succ();
|
||
},
|
||
fail: () => {
|
||
err();
|
||
}
|
||
});
|
||
}));
|
||
}
|
||
these.subscits = [];
|
||
Promise.all(promises).then(() => {
|
||
if (!this.connectedDeviceId) {
|
||
this.isConnected = false;
|
||
this.connectedDeviceId = '';
|
||
this.currentMode = '';
|
||
this.currentPage = 'deviceList';
|
||
if (callback) {
|
||
callback();
|
||
}
|
||
return;
|
||
}
|
||
setTimeout(() => {
|
||
uni.closeBLEConnection({
|
||
deviceId: this.connectedDeviceId,
|
||
success: (res) => {
|
||
|
||
console.log('蓝牙连接已断开' + JSON.stringify(res));
|
||
|
||
|
||
},
|
||
fail: (ex) => {
|
||
let arr = [{
|
||
code: 0,
|
||
remark: '正常'
|
||
},
|
||
{
|
||
code: 10000,
|
||
remark: '未初始化蓝牙适配器'
|
||
},
|
||
{
|
||
code: 10001,
|
||
remark: '当前蓝牙适配器不可用'
|
||
},
|
||
{
|
||
code: 10002,
|
||
remark: '没有找到指定设备'
|
||
},
|
||
{
|
||
code: 10003,
|
||
remark: '连接失败'
|
||
},
|
||
{
|
||
code: 10004,
|
||
remark: '没有找到指定服务'
|
||
},
|
||
{
|
||
code: 10005,
|
||
remark: '没有找到指定特征值'
|
||
},
|
||
{
|
||
code: 10006,
|
||
remark: '当前连接已断开'
|
||
},
|
||
{
|
||
code: 10007,
|
||
remark: '当前特征值不支持此操作'
|
||
},
|
||
{
|
||
code: 10008,
|
||
remark: '其余所有系统上报的异常'
|
||
},
|
||
{
|
||
code: 10009,
|
||
remark: 'Android 系统特有,系统版本低于 4.3 不支持 BLE'
|
||
},
|
||
{
|
||
code: 10010,
|
||
remark: '已连接'
|
||
},
|
||
{
|
||
code: 10011,
|
||
remark: '配对设备需要配对码'
|
||
},
|
||
{
|
||
code: 10012,
|
||
remark: '连接超时'
|
||
},
|
||
{
|
||
code: 10013,
|
||
remark: '连接 deviceId 为空或者是格式不正确'
|
||
}
|
||
];
|
||
let f = arr.find(function(v) {
|
||
return v.code == ex.code;
|
||
})
|
||
|
||
console.log("无法断开蓝牙连接:" + f.remark);
|
||
|
||
uni.showToast({
|
||
title: "无法断开蓝牙连接:" + f.remark,
|
||
icon: 'fail'
|
||
});
|
||
|
||
},
|
||
complete: () => {
|
||
this.isConnected = false;
|
||
this.connectedDeviceId = '';
|
||
this.currentMode = '';
|
||
this.currentPage = 'deviceList';
|
||
if (callback) {
|
||
callback();
|
||
}
|
||
}
|
||
});
|
||
|
||
}, 200)
|
||
|
||
}).catch().finally();
|
||
|
||
|
||
|
||
|
||
|
||
},
|
||
|
||
// 获取设备服务
|
||
getDeviceServices(deviceId) {
|
||
console.log("deviceId=" + deviceId);
|
||
var these = this;
|
||
uni.getBLEDeviceServices({
|
||
deviceId: deviceId,
|
||
success: (res) => {
|
||
|
||
console.log("发现服务" + JSON.stringify(res.services))
|
||
if (res.services.length == 0) {
|
||
setTimeout(function() {
|
||
these.getDeviceServices(deviceId);
|
||
}, 200);
|
||
return;
|
||
}
|
||
// 查找目标服务
|
||
let targetService = res.services.find(service => service.uuid.indexOf('FFE0') > -1);
|
||
|
||
if (targetService) {
|
||
console.log(`找到目标服务: ${targetService.uuid}`);
|
||
this.serviceId = targetService.uuid;
|
||
this.getDeviceCharacteristics(deviceId, targetService.uuid);
|
||
}
|
||
|
||
|
||
|
||
|
||
},
|
||
fail: (err) => {
|
||
console.log(`获取设备服务失败: ${err.errMsg}`);
|
||
}
|
||
});
|
||
},
|
||
|
||
|
||
// 获取设备特征值
|
||
getDeviceCharacteristics(deviceId, serviceId) {
|
||
var these = this;
|
||
uni.getBLEDeviceCharacteristics({
|
||
deviceId: deviceId,
|
||
serviceId: serviceId,
|
||
success: (res) => {
|
||
console.log(`获取设备特征值成功,共发现 ${res.characteristics.length} 个特征值`);
|
||
console.log(`获取设备特征值成功${JSON.stringify(res.characteristics)}`);
|
||
// 查找可写特征值
|
||
let writeChar = res.characteristics.find(char =>
|
||
char.uuid.indexOf("FFE1") >= 1
|
||
);
|
||
|
||
// 查找通知特征值
|
||
let notifyChar = res.characteristics.find(char => {
|
||
return char.properties.notify;
|
||
}
|
||
|
||
);
|
||
|
||
if (writeChar) {
|
||
console.log(`找到可写特征值: ${writeChar.uuid}`);
|
||
this.writeCharacteristicId = writeChar.uuid;
|
||
} else {
|
||
console.log(`未找到可写特征值: ${this.writeCharacteristicId}`);
|
||
}
|
||
|
||
if (notifyChar) {
|
||
console.log(`找到通知特征值: ${notifyChar.uuid}`);
|
||
this.notifyCharacteristicId = notifyChar.uuid;
|
||
console.log("serviceId=" + serviceId);
|
||
console.log("characteristicId=" + notifyChar.uuid);
|
||
this.subscits.push({
|
||
deviceId: deviceId,
|
||
serviceId: serviceId,
|
||
notifyId: notifyChar.uuid
|
||
});
|
||
setTimeout(() => {
|
||
uni.notifyBLECharacteristicValueChange({
|
||
deviceId: deviceId,
|
||
serviceId: serviceId,
|
||
characteristicId: notifyChar.uuid,
|
||
state: true,
|
||
success: (info) => {
|
||
console.log('启用通知成功' + JSON.stringify(info));
|
||
uni.showToast({
|
||
title: "订阅消息成功",
|
||
icon: 'success'
|
||
});
|
||
// 监听特征值变化
|
||
setTimeout(() => {
|
||
uni.onBLECharacteristicValueChange((
|
||
receive) => {
|
||
console.log("1111111",
|
||
receive);
|
||
these.parseData(receive);
|
||
});
|
||
}, 100)
|
||
|
||
},
|
||
fail: (err) => {
|
||
console.error('启用通知失败', err);
|
||
}
|
||
});
|
||
}, 500);
|
||
|
||
} else {
|
||
console.log(`未找到通知特征值: ${this.notifyCharacteristicId}`);
|
||
}
|
||
|
||
|
||
|
||
},
|
||
fail: (err) => {
|
||
console.log(`获取设备特征值失败: ${err.errMsg}`);
|
||
}
|
||
});
|
||
},
|
||
|
||
// 将文字转换为GBK编码(使用第三方库或API)
|
||
convertToGBK(text) {
|
||
let arr = gbk.encode(text)
|
||
console.log("arr=", arr.join(" "));
|
||
let utf8Data = gbk.arr2hex(arr);
|
||
return utf8Data; // 实际项目中替换为GBK编码实现
|
||
},
|
||
|
||
// 发送数据通用方法
|
||
sendData(data, url) {
|
||
data = JSON.stringify(data);
|
||
|
||
let buffer = new ArrayBuffer(data.length);
|
||
let dataView = new DataView(buffer);
|
||
|
||
for (let i = 0; i < data.length; i++) {
|
||
dataView.setUint8(i, data.charCodeAt(i));
|
||
}
|
||
|
||
return this.sendBle(buffer);
|
||
},
|
||
sendBle(buffer) {
|
||
return new Promise(async (resolve, reject) => {
|
||
|
||
var promise = new Promise((succ, err) => {
|
||
uni.writeBLECharacteristicValue({
|
||
deviceId: this.connectedDeviceId,
|
||
serviceId: this.serviceId,
|
||
characteristicId: this.writeCharacteristicId,
|
||
value: buffer,
|
||
writeType: plus.os.name == 'iOS' ? 'write' : 'writeNoResponse',
|
||
success: () => {
|
||
//console.log("发送数据成功");
|
||
succ();
|
||
},
|
||
fail: (ex) => {
|
||
console.log("发送数据失败", ex);
|
||
|
||
err(ex);
|
||
},
|
||
complete: function() {
|
||
//console.log("123456");
|
||
}
|
||
});
|
||
});
|
||
if (plus.os.name == 'iOS') {
|
||
|
||
function timeout(ms) {
|
||
return new Promise((_, err) => {
|
||
setTimeout(() => {
|
||
err({
|
||
code: -1,
|
||
errMsg: '超时了'
|
||
})
|
||
}, ms);
|
||
});
|
||
}
|
||
|
||
Promise.race([promise, timeout(this.inteval ? this.inteval : 0)]).then(resolve)
|
||
.catch((
|
||
ex) => {
|
||
console.log("ex=", ex);
|
||
if (ex.code == -1) {
|
||
resolve();
|
||
} else {
|
||
reject(ex);
|
||
}
|
||
|
||
}).finally(() => {
|
||
console.log("完成了")
|
||
});
|
||
} else {
|
||
promise.then(resolve).catch(reject);
|
||
}
|
||
});
|
||
},
|
||
|
||
},
|
||
onUnload() {
|
||
this.closeBluetoothAdapter();
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style>
|
||
.receivLog {
|
||
padding: 20rpx;
|
||
width: 100%;
|
||
height: auto;
|
||
box-sizing: border-box;
|
||
border: 2rpx solid #e5e5e5;
|
||
border-radius: 20rpx;
|
||
font-size: 28rpx;
|
||
color: #2e2e2e;
|
||
}
|
||
|
||
.container {
|
||
padding: 20rpx;
|
||
background-color: #f8f8f8;
|
||
min-height: 100vh;
|
||
}
|
||
|
||
.title {
|
||
font-size: 36rpx;
|
||
font-weight: bold;
|
||
text-align: center;
|
||
padding: 20rpx 0;
|
||
color: #333;
|
||
}
|
||
|
||
.status-bar {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 15rpx 0;
|
||
border-bottom: 1rpx solid #eee;
|
||
}
|
||
|
||
.function-tabs {
|
||
display: flex;
|
||
margin: 20rpx 0;
|
||
}
|
||
|
||
.tab-btn {
|
||
flex: 1;
|
||
padding: 15rpx 0;
|
||
text-align: center;
|
||
font-size: 28rpx;
|
||
border-bottom: 2rpx solid #eee;
|
||
}
|
||
|
||
.tab-btn.active {
|
||
color: #409eff;
|
||
border-bottom: 2rpx solid #409eff;
|
||
}
|
||
|
||
/* 设备列表样式 */
|
||
.search-area {
|
||
padding: 20rpx 0;
|
||
}
|
||
|
||
.search-btn {
|
||
background-color: #409eff;
|
||
color: white;
|
||
}
|
||
|
||
.tips {
|
||
color: #999;
|
||
font-size: 24rpx;
|
||
margin-top: 10rpx;
|
||
}
|
||
|
||
.devices-list {
|
||
margin-top: 20rpx;
|
||
}
|
||
|
||
.device-item {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 20rpx;
|
||
background-color: white;
|
||
border-radius: 10rpx;
|
||
margin-bottom: 15rpx;
|
||
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
|
||
}
|
||
|
||
.device-item.active {
|
||
background-color: #e8f3ff;
|
||
border-left: 6rpx solid #409eff;
|
||
}
|
||
|
||
.device-name {
|
||
font-size: 28rpx;
|
||
max-width: 500rpx;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.device-rssi {
|
||
color: #999;
|
||
font-size: 24rpx;
|
||
}
|
||
|
||
.device-status {
|
||
color: #409eff;
|
||
font-size: 24rpx;
|
||
}
|
||
|
||
/* 模式设置样式 */
|
||
.modes-area {
|
||
margin-top: 30rpx;
|
||
}
|
||
|
||
.mode-title {
|
||
font-size: 28rpx;
|
||
font-weight: bold;
|
||
margin-bottom: 20rpx;
|
||
color: #333;
|
||
}
|
||
|
||
.modes-grid {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.mode-item {
|
||
width: 30%;
|
||
padding: 20rpx 0;
|
||
background-color: white;
|
||
border-radius: 10rpx;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
|
||
}
|
||
|
||
.mode-item.selected {
|
||
background-color: #e8f3ff;
|
||
border: 2rpx solid #409eff;
|
||
}
|
||
|
||
.mode-icon {
|
||
font-size: 40rpx;
|
||
margin-bottom: 10rpx;
|
||
}
|
||
|
||
.mode-name {
|
||
font-size: 26rpx;
|
||
}
|
||
|
||
/* 亮度调节样式 */
|
||
.brightness-area {
|
||
margin-top: 30rpx;
|
||
padding: 20rpx;
|
||
background-color: white;
|
||
border-radius: 10rpx;
|
||
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
|
||
}
|
||
|
||
.brightness-title {
|
||
font-size: 28rpx;
|
||
font-weight: bold;
|
||
margin-bottom: 20rpx;
|
||
color: #333;
|
||
}
|
||
|
||
.brightness-value {
|
||
font-size: 36rpx;
|
||
text-align: center;
|
||
margin-bottom: 30rpx;
|
||
color: #409eff;
|
||
}
|
||
|
||
.brightness-slider {
|
||
width: 100%;
|
||
}
|
||
|
||
/* 开机画面样式 */
|
||
.splash-controls {
|
||
display: flex;
|
||
justify-content: center;
|
||
gap: 20rpx;
|
||
margin: 20rpx 0;
|
||
}
|
||
|
||
.splash-preview {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
}
|
||
|
||
.splash-frame {
|
||
height: 320rpx;
|
||
width: 640rpx;
|
||
border: 2rpx solid #409eff;
|
||
border-radius: 8rpx;
|
||
overflow: hidden;
|
||
position: relative;
|
||
background-color: #f0f0f0;
|
||
}
|
||
|
||
.splash-frame image {
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
|
||
.empty-frame {
|
||
width: 100%;
|
||
height: 100%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
color: #999;
|
||
font-size: 24rpx;
|
||
}
|
||
|
||
.frame-info {
|
||
margin-top: 15rpx;
|
||
color: #666;
|
||
font-size: 24rpx;
|
||
}
|
||
|
||
.sending-progress {
|
||
margin-top: 30rpx;
|
||
width: 100%;
|
||
}
|
||
|
||
.progress-bar {
|
||
height: 12rpx;
|
||
background-color: #e0e0e0;
|
||
border-radius: 6rpx;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.progress-fill {
|
||
height: 100%;
|
||
background-color: #409eff;
|
||
transition: width 0.3s;
|
||
}
|
||
|
||
/* 文字设置样式 */
|
||
.text-inputs {
|
||
margin-top: 30rpx;
|
||
background-color: white;
|
||
border-radius: 10rpx;
|
||
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
|
||
padding: 20rpx;
|
||
}
|
||
|
||
.text-line {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.text-line text {
|
||
width: 80rpx;
|
||
font-size: 26rpx;
|
||
}
|
||
|
||
.text-line input {
|
||
flex: 1;
|
||
height: 60rpx;
|
||
padding: 0 15rpx;
|
||
border: 1rpx solid #eee;
|
||
border-radius: 8rpx;
|
||
font-size: 26rpx;
|
||
}
|
||
|
||
.send-text-btn {
|
||
margin-top: 20rpx;
|
||
background-color: #409eff;
|
||
color: white;
|
||
}
|
||
|
||
/* 日志区域样式 */
|
||
.log-area {
|
||
margin-top: 30rpx;
|
||
}
|
||
|
||
.log-title {
|
||
font-size: 28rpx;
|
||
font-weight: bold;
|
||
margin-bottom: 15rpx;
|
||
color: #333;
|
||
}
|
||
|
||
.log-content {
|
||
height: 200rpx;
|
||
background-color: white;
|
||
border-radius: 10rpx;
|
||
padding: 15rpx;
|
||
font-size: 24rpx;
|
||
color: #666;
|
||
}
|
||
|
||
.log-item {
|
||
margin-bottom: 8rpx;
|
||
}
|
||
|
||
.btn {
|
||
padding: 10rpx 20rpx;
|
||
border-radius: 8rpx;
|
||
font-size: 26rpx;
|
||
text-align: center;
|
||
}
|
||
|
||
.btn[disabled] {
|
||
opacity: 0.5;
|
||
}
|
||
|
||
.txt {
|
||
border: 1px solid #000000;
|
||
}
|
||
|
||
.w50 {
|
||
width: 50%;
|
||
color: #656363;
|
||
font-size: 26prx;
|
||
height: 60rpx;
|
||
}
|
||
|
||
.fleft {
|
||
float: left;
|
||
}
|
||
|
||
.clear {
|
||
clear: both;
|
||
}
|
||
</style> |