Files
APP/pages/BlueTooth/ModeSetting/HBY650_1.vue
2025-08-27 11:06:49 +08:00

1899 lines
50 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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="title">蓝牙设备控制器</view>
<view class="status-bar">
<text>已连接: {{ targetDeviceName }}</text>
<button class="btn" @click="disconnectDevice(null)">断开连接</button>
</view>
<view class="receivLog">
<view>数据更新时间{{receiveData.date}}</view>
<view class="w50 fleft">Mac地址:{{receiveData.macAddress}}</view>
<view class="w50 fleft">静电探测档位:{{receiveData.staticLevel}}</view>
<view class="w50 fleft">剩余照明时间:{{receiveData.lightingTime}}</view>
<view class="w50 fleft">照明档位:{{receiveData.lightingLevel}}</view>
<view class="w50 fleft">电池电量:{{receiveData.batteryLevel}}</view>
<view class="w50 fleft">预警级别:{{receiveData.warnLevel}}</view>
<view class="clear"></view>
</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 === 'mode'}" @click="currentTab = 'mode'">
检测模式
</button>
<button class="tab-btn" :class="{'active': currentTab === 'brightness'}"
@click="currentTab = 'brightness'">
照明模式
</button>
<button class="tab-btn" :class="{'active': currentTab === 'splash'}" @click="currentTab = 'splash'">
开机画面
</button>
<button class="tab-btn" :class="{'active': currentTab === 'flash'}" @click="gotoSendVideo">开机动画</button>
<button class="tab-btn" :class="{'active': currentTab === 'text'}" @click="currentTab = 'text'">
文字设置
</button>
</view>
<!-- 模式设置选项卡 -->
<view v-if="currentTab === 'mode'">
<view class="modes-area">
<view class="mode-title">选择静电探测档位</view>
<view class="modes-grid">
<view class="mode-item" :class="{'selected': currentMode === 'high'}" @click="setMode('high')">
<text class="mode-icon">💡</text>
<text class="mode-name">高档模式</text>
</view>
<view class="mode-item" :class="{'selected': currentMode === 'low'}" @click="setMode('low')">
<text class="mode-icon">🔅</text>
<text class="mode-name">中档模式</text>
</view>
<view class="mode-item" :class="{'selected': currentMode === 'flash'}"
@click="setMode('flash')">
<text class="mode-icon">💥</text>
<text class="mode-name">低档模式</text>
</view>
</view>
</view>
</view>
<!-- 亮度调节选项卡 -->
<view v-if="currentTab === 'brightness'">
<view class="brightness-area">
<view class="brightness-title">照明模式</view>
<button class="tab-btn" :class="{'active': brightnessStatu}" @click="setBrightness(true)">
开启
</button>
<button class="tab-btn" :class="{'active': !brightnessStatu}" @click="setBrightness(false)">
关闭
</button>
</view>
</view>
<!-- 开机画面选项卡 -->
<view v-if="currentTab === 'splash'">
<canvas canvas-id="splashCanvas" style="width: 160px; height: 80px; z-index: 9999;position: fixed; "
:style="{top:canvasTop,left:canvasLeft}"></canvas>
<view class="splash-controls">
<button class="btn" @click="importImage">导入图片</button>
<button class="btn" :disabled="!hasImage || isSending" @click="sendSplashImage">确认发送</button>
</view>
<view class="splash-preview">
<view class="splash-frame">
<image v-if="tempImagePath" :src="tempImagePath" mode="aspectFill" :style="imageStyle"
@touchstart="onTouchStart" @touchmove="onTouchMove" @touchend="onTouchEnd"></image>
<view v-else class="empty-frame">点击"导入图片"选择开机画面</view>
</view>
<view class="frame-info">
<text>框选区域: 160×80像素</text>
<text v-if="hasImage">缩放: {{ scale.toFixed(2) }}x</text>
</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>
<!-- 文字设置选项卡 -->
<view v-if="currentTab === 'text'">
<view class="text-inputs">
<view class="text-line" v-for="(line, index) in textLines" :key="index">
<text>{{ index + 1 }}:</text>
<input v-model="textLines[index]" placeholder="请输入文字" maxlength="30"
@confirm="onTextChanged(index)" />
</view>
</view>
<button class="btn send-text-btn" :disabled="isSendingText" @click="sendText">发送文字</button>
<view class="sending-progress" v-if="isSendingText">
<view class="progress-bar">
<view class="progress-fill" :style="{ width: textProgress + '%' }"></view>
</view>
<text>正在发送: {{ textProgress }}% ({{ currentTextPacket }}/{{ totalTextPackets }})</text>
</view>
</view>
</view>
</view>
</template>
<script>
import gbk from '@/utils/gbk.js';
export default {
data() {
return {
canvasTop: '-1000px',
canvasLeft: '-1000px',
inteval: 0,
isLoading: false,
isBluetoothOpen: false,
isSearching: false,
isConnected: false,
isSending: false,
isSendingText: false,
bluetoothStatus: '未初始化',
searchTips: '',
deviceList: [],
connectedDeviceId: '',
serviceId: '0xFFE1',
writeCharacteristicId: '0xFFE1',
notifyCharacteristicId: '0xFFE2',
currentMode: '',
logList: [],
packetCount: 1,
currentPage: 'deviceList',
targetDeviceName: ['JQZM-EF4651', 'XLCX-68455E'],
currentTab: 'mode',
brightness: 50,
brightnessTimer: null,
tempImagePath: '',
hasImage: false,
scale: 1,
translateX: 0,
translateY: 0,
lastX: 0,
lastY: 0,
isDragging: false,
imageWidth: 0,
imageHeight: 0,
textLines: ['你好中国', '你好中国', '你好中国', '你好中国'],
progress: 0,
currentPacket: 0,
totalPackets: 52,
textProgress: 0,
currentTextPacket: 0,
totalTextPackets: 4,
brightnessStatu: false,
receiveData: {
"date": "",
"staticLevel": "",
"lightingLevel": "",
"lightingTime": "",
"batteryLevel": "",
"macAddress": "",
"warnLevel": ""
},
subscits: [],
videoPath: "",
videoWidth: "",
videoHeight: "",
videoDuration: ""
}
},
computed: {
imageStyle() {
return `transform: scale(${this.scale}) translate(${this.translateX}px, ${this.translateY}px); transform-origin: 0 0;`;
}
},
onLoad() {
// debugger;
// // let iconv = require('iconv-lite');
// // 假设我们有一个UTF-8编码的字符串
// let utf8String = '你好,世界!';
// // 使用iconv-lite将字符串从UTF-8编码转换为GBK编码
// let gbkBuffer = iconv.encode(utf8String, 'GBK');
// console.log('转换后的GBK编码Buffer对象:', gbkBuffer)
this.initBluetoothAdapter();
},
methods: {
//灯光打开与关闭
setBrightness(flag) {
if (this.brightnessStatu == flag) {
return;
}
this.brightnessStatu = flag;
setTimeout(() => {
let buffer = new ArrayBuffer(6);
let dataView = new DataView(buffer);
dataView.setUint8(0, 0x55); // 帧头
dataView.setUint8(1, 0x00); // 帧类型
dataView.setUint8(2, 0x01); // 包序号
dataView.setUint8(3, 0x00); // 数据长度
dataView.setUint8(4, 0x01); // 数据长度
dataView.setUint8(5, flag ? 0x6e : 0x6f); // 数据
// 发送数据
this.sendData(buffer);
}, 0);
},
// 蓝牙初始化
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: 10000,
success: (res) => {
this.stopSearch();
this.isConnected = true;
this.connectedDeviceId = deviceId;
this.targetDeviceName = deviceName;
console.log(`连接设备 ${deviceName} 成功`);
this.enableNotification(deviceId);
// 连接成功后切换到控制面板
this.currentPage = 'controlPanel';
setTimeout(function() {
uni.setBLEMTU({
deviceId: deviceId,
mtu: 512,
success: (info) => {
console.log("设置mtu成功")
},
fail: (ex) => {
console.log("设置mtu失败" + JSON.stringify(ex));
},
complete() {
}
})
}, 200)
these.getDeviceServices(deviceId);
},
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) {
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) => {
var bytesToHexString =function(bytes) {
return bytes.map(
byte =>byte.toString(16).padStart(2, '0')
).join(' ')
}
var parseData = (bytes) => {
if (bytes.length <7) {
console.log('数据包长度不足至少需要6个字节');
return;
}
let date = new Date();
this.receiveData.date =date.getHours() +":" + date.getMinutes() +":" + date.getSeconds();
if(bytes[0]==0x55){
try {
// 跳过帧头(第一个字节)从第二个字节开始解析
let staticLevelByte = bytes[1];
let staticLevelText = '未知';
switch ( staticLevelByte ) {
case 0x65:
staticLevelText = '高档';
break;
case 0x66:
staticLevelText = '中档';
break;
case 0x67:
staticLevelText = '低档';
break;
case 0x68:
staticLevelText = '关闭';
break;
}
// 解析照明档位
let lightingLevelByte = bytes[2];
let lightingLevelText = lightingLevelByte === 0x6e ? '开启':'关闭';
// 解析剩余照明时间第三和第四字节小端序
let lightingTime =(bytes[3] <<8) | bytes[4];
// 解析剩余电量
let batteryLevelByte = bytes[5];
// 电量百分比范围检查
let batteryLevel = Math.max(0,Math.min(100, batteryLevelByte));
let warn=bytes[6];
if(warn==0x00){
warn='无预警';
}
else if(warn==0x01){
warn='弱预警';
}
else if(warn==0x02){
warn='中预警';
}
else if(warn==0x03){
warn='强预警';
}
else if(warn==0x04){
warn='非常强预警';
}
console.log(warn)
console.log('解析结果:', {
staticLevel: staticLevelText,
lightingLevel: lightingLevelText,
lightingTime: `${lightingTime} 分钟`,
batteryLevel: `${batteryLevel}%`
});
this.receiveData.staticLevel =staticLevelText;
this.receiveData.lightingLevel =lightingLevelText;
this.receiveData.lightingTime =lightingTime +'分钟';
this.receiveData.batteryLevel =batteryLevel +'%';
this.receiveData.warnLevel=warn;
} catch (error) {
console.log('数据解析错误:',error);
}
}
else{
try{
let uint8Array = new Uint8Array(receive.value);
let str = '';
for (let i = 0; i < uint8Array.length; i++) {
// 将每个字节转换为对应的字符
str += String.fromCharCode(uint8Array[i]);
}
if(str.indexOf('mac address:')==0){
this.receiveData.macAddress=str.split(':')[1];
console.log("收到mac地址:",)
}else{
console.log("收到无法解析的字符串:",str)
}
}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));
parseData(bytes);
});
}, 100)
},
fail: (err) => {
console.error('启用通知失败', err);
}
});
}, 500);
} else {
console.log(`未找到通知特征值: ${this.notifyCharacteristicId}`);
}
},
fail: (err) => {
console.log(`获取设备特征值失败: ${err.errMsg}`);
}
});
},
// 启用通知
enableNotification(deviceId) {
// uni.notifyBLECharacteristicValueChange({
// deviceId: deviceId,
// serviceId: this.serviceId,
// characteristicId: this.notifyCharacteristicId,
// state: true,
// success: () => {
// console.log('启用通知成功');
// // 监听通知
// uni.onBLECharacteristicValueChange((res) => {
// this.handleNotification(res.value);
// });
// },
// fail: (err) => {
// console.log(`启用通知失败: ${err.errMsg}`);
// }
// });
},
// 处理通知数据
handleNotification(buffer) {
console.log("收到设备通知数据")
// 解析从设备接收的数据
let dataView = new DataView(buffer);
let result = '';
for (let i = 0; i < dataView.byteLength; i++) {
result += String.fromCharCode(dataView.getUint8(i));
}
console.log(`收到设备数据: ${result}`);
},
// 设置静电检测模式
setMode(mode) {
if (!this.isConnected) {
uni.showToast({
title: '未连接设备',
icon: 'none'
});
return;
}
if (this.currentMode === mode) return;
this.currentMode = mode;
let dataValue = 0;
switch (mode) {
case 'high':
dataValue = 0x65; // 高档模式
//console.log('开始切换到高档模式');
break;
case 'low':
dataValue = 0x66; // 中档模式
//console.log('开始切换到中档模式');
break;
case 'flash':
dataValue = 0x67; // 低档模式
//console.log('开始切换到低档模式');
break;
default:
dataValue = 0x68; // 低档模式
//console.log('开始切换到低档模式');
break;
}
// 构建数据包
let buffer = new ArrayBuffer(6);
let dataView = new DataView(buffer);
dataView.setUint8(0, 0x55); // 帧头
dataView.setUint8(1, 0x00); // 帧类型
dataView.setUint8(2, 0x01); // 包序号
dataView.setUint8(3, 0x00); // 数据长度
dataView.setUint8(4, 0x01); // 数据长度
dataView.setUint8(5, dataValue); // 数据
// 发送数据
this.sendData(buffer);
},
// 亮度调节
onBrightnessChanging(e) {
this.brightness = e.detail.value;
// 清除之前的定时器
if (this.brightnessTimer) {
clearTimeout(this.brightnessTimer);
}
// 设置新的定时器,控制发送频率
this.brightnessTimer = setTimeout(() => {
this.sendBrightness();
}, 100);
},
onBrightnessChanged(e) {
this.brightness = e.detail.value;
//this.sendBrightness();
},
sendBrightness() {
if (!this.isConnected) return;
// 构建数据包
let buffer = new ArrayBuffer(6);
let dataView = new DataView(buffer);
let data = '0x' + parseInt(this.brightness).toString(16);
console.log("亮度:" + this.brightness + ',16进制:' + data);
dataView.setUint8(0, 0x55); // 帧头
dataView.setUint8(1, 0x01); // 帧类型:亮度调节
dataView.setUint8(2, 0x01); // 包序号
dataView.setUint8(3, 0x00); // 数据长度
dataView.setUint8(4, 0x01); // 数据长度
dataView.setUint8(5, data); // 数据
// 发送数据
this.sendData(buffer);
},
// 导入图片
importImage() {
uni.chooseImage({
count: 1,
sourceType: ['album'],
success: (res) => {
this.tempImagePath = res.tempFilePaths[0];
this.hasImage = true;
this.scale = 1;
this.translateX = 0;
this.translateY = 0;
// 获取图片尺寸
uni.getImageInfo({
src: this.tempImagePath,
success: (info) => {
this.imageWidth = info.width;
this.imageHeight = info.height;
this.autoFitImage();
console.log('图片导入成功,请调整图片位置和大小');
},
fail: (err) => {
console.log(`获取图片信息失败: ${err.errMsg}`);
}
});
},
fail: (err) => {
console.log(`选择图片失败: ${err.errMsg}`);
}
});
},
// 自动适配图片到容器
autoFitImage() {
let frameWidth = 320; // 预览框宽度根据实际UI调整
let frameHeight = 640; // 预览框高度根据实际UI调整
let widthRatio = frameWidth / this.imageWidth;
let heightRatio = frameHeight / this.imageHeight;
// 取较小的缩放比例,确保图片能完全显示在容器内
this.scale = Math.min(widthRatio, heightRatio);
// 居中显示
this.translateX = (frameWidth - this.imageWidth * this.scale) / 2;
this.translateY = (frameHeight - this.imageHeight * this.scale) / 2;
},
// 触摸事件处理
onTouchStart(e) {
if (e.touches.length === 1) {
this.isDragging = true;
this.lastX = e.touches[0].clientX;
this.lastY = e.touches[0].clientY;
}
},
onTouchMove(e) {
if (this.isDragging && e.touches.length === 1) {
let currentX = e.touches[0].clientX;
let currentY = e.touches[0].clientY;
// 计算位移
let deltaX = currentX - this.lastX;
let deltaY = currentY - this.lastY;
// 更新位置
this.translateX += deltaX;
this.translateY += deltaY;
// 限制移动范围
this.limitImagePosition();
// 更新最后位置
this.lastX = currentX;
this.lastY = currentY;
}
},
onTouchEnd() {
this.isDragging = false;
},
// 限制图片移动范围
limitImagePosition() {
let frameWidth = 320; // 预览框宽度根据实际UI调整
let frameHeight = 640; // 预览框高度根据实际UI调整
let maxX = 0;
let minX = frameWidth - this.imageWidth * this.scale;
let maxY = 0;
let minY = frameHeight - this.imageHeight * this.scale;
this.translateX = Math.max(minX, Math.min(maxX, this.translateX));
this.translateY = Math.max(minY, Math.min(maxY, this.translateY));
},
// 发送开机画面
sendSplashImage() {
if (!this.hasImage) {
uni.showToast({
title: '请先导入图片',
icon: 'none'
});
return;
}
uni.showLoading({
title: '处理图片中...',
mask: true
});
// 将图片转换为RGB565格式并发送
this.convertAndSendImage().then(() => {
uni.hideLoading();
console.log('开机画面发送完成');
uni.showToast({
title: '开机画面发送完成',
icon: 'success'
});
}).catch((error) => {
uni.hideLoading();
console.log(`图片发送失败: ${error}`);
uni.showToast({
title: '图片发送失败',
icon: 'none'
});
});
},
// 转换图片并发送
convertAndSendImage() {
return new Promise((resolve, reject) => {
console.log("111111");
// 创建canvas用于处理图片
let ctx = uni.createCanvasContext('splashCanvas', this);
console.log("2222222");
// 绘制图片到canvas
ctx.drawImage(this.tempImagePath, 0, 0, 160, 80);
console.log("33333333");
try {
ctx.draw(false, () => {
console.log("4444444");
// 获取canvas像素数据
this.canvasTop = '0px';
this.canvasLeft = '0px';
setTimeout(() => {
uni.canvasGetImageData({
canvasId: 'splashCanvas',
x: 0,
y: 0,
width: 160,
height: 80,
success: (res) => {
// 处理像素数据并发送
console.log("res.data.length=" + res.data
.length);
this.processAndSendImageData(res.data).then(
resolve).catch(reject);
},
fail: (err) => {
reject(`获取canvas数据失败: ${err.errMsg}`);
}
});
}, 300); // 等待canvas绘制完成
});
} catch (ex) {
console.log("出现异常" + JSON.stringify(ex))
}
});
},
// 处理像素数据并发送
processAndSendImageData(pixels) {
return new Promise((resolve, reject) => {
// 创建RGB565格式的像素数据
let rgb565Data = this.convertToRGB565(pixels);
// 分包发送
this.sendImagePackets(rgb565Data).then(resolve).catch(reject);
});
},
// 将RGBA转换为RGB565
convertToRGB565(pixels) {
var rgbaToRgb565 = function(r, g, b, a = 255) {
// 忽略透明度(如果需要考虑透明度,请根据需求处理)
// 将8位颜色值压缩到RGB565对应的位数
let r5 = Math.round((r / 255) * 31); // 5位0-31
let g6 = Math.round((g / 255) * 63); // 6位0-63
let b5 = Math.round((b / 255) * 31); // 5位0-31
// 合并为16位整数
return (r5 << 11) | (g6 << 5) | b5;
}
let result = new Uint16Array(160 * 80);
let index = 0;
console.log("pixels.length=" + pixels.length);
for (let i = 0; i < pixels.length; i += 4) {
let r = pixels[i];
let g = pixels[i + 1];
let b = pixels[i + 2];
// 转换为RGB565 (R:5bit, G:6bit, B:5bit)
let rgb565 = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
//let rgb565=rgbaToRgb565(r,g,b);
result[index++] = rgb565;
}
return result;
},
// 分包发送图片数据
sendImagePackets(imageData) {
return new Promise((resolve, reject) => {
this.isSending = true;
this.progress = 0;
this.currentPacket = 0;
// 总数据包数
let totalPackets = 52;
let currentPacket = 1;
// 发送单个数据包
let sendNextPacket = () => {
let promises = []; {
//----------------------------------------------------------------------------------------------
// if (currentPacket > totalPackets) {
// this.isSending = false;
// resolve();
// return;
// }
// 计算当前包的数据
let packetSize = 250;
if (currentPacket <= 51) {
packetSize = 250; // 前51个包每个500字节
} else {
packetSize = 50; // 最后一个包100字节
}
// 创建数据包
let startIndex = (currentPacket - 1) * 250;
let endIndex = Math.min(startIndex + packetSize, imageData.length);
if (startIndex > endIndex) {
return;
}
//console.log("imageData="+imageData.length);
let packetData = imageData.slice(startIndex,
endIndex);
// 构建数据包
let bufferSize = 5 + packetData.length * 2; // 头部5字节 + 数据部分
let buffer = new ArrayBuffer(bufferSize);
let dataView = new DataView(buffer);
// 填充头部
dataView.setUint8(0, 0x55); // 帧头
dataView.setUint8(1, 0x02); // 帧类型:开机画面
dataView.setUint8(2, '0x' + currentPacket.toString(16).padStart(2, '0')); // 包序号
// dataView.setUint8(3, 0x01);
// dataView.setUint8(4, 0xFF);
if (packetData.length == 250) {
dataView.setUint8(3, 0x01);
dataView.setUint8(4, 0xF4);
} else {
dataView.setUint8(3, 0x00);
dataView.setUint8(4, 0x64);
}
// 填充数据每个RGB565值占2字节
for (let i = 0; i < packetData.length; i++) {
dataView.setUint16(5 + i * 2, packetData[i], false); // 大端字节序
}
console.log(
`发送数据包${currentPacket}/${totalPackets},${dataView.getUint8(0)} ${dataView.getUint8(1)} ${dataView.getUint8(2)}`
)
let inteval = parseInt(this.inteval ? this.inteval : 0);
//发送数据包
let promise = this.sendData(buffer);
promises.push(promise);
if (currentPacket > totalPackets) {
console.log("111111");
}
if (currentPacket % 20 == 0 || ((currentPacket >= totalPackets && promises.length >
0))) {
Promise.all(promises).then(() => {
console.log("完成了一软数据发送");
if (currentPacket >= totalPackets) {
console.log("结束数据发送");
this.isSending = false;
resolve();
return;
}
console.log("进入下一轮");
promises = [];
this.currentPacket = currentPacket;
this.progress = Math.round((currentPacket / totalPackets) * 52);
currentPacket++;
setTimeout(sendNextPacket, inteval);
}).catch((err) => {
this.isSending = false;
uni.hideLoading();
reject(err);
}).finally(() => {
console.log("本轮完成");
});
} else {
currentPacket++;
sendNextPacket();
}
//----------------------------------------------------------------------------------------------
}
};
//牵着你的手
var HoldYouHand = () => {
var str = "picture transmit start"; //握手的协议字符串
// 1. 创建 ArrayBuffer 和 DataView
let buffer = new ArrayBuffer(str.length);
let dataView = new DataView(buffer);
// 2. 将字符串转换为 ASCII 码并写入 DataView
for (let i = 0; i < str.length; i++) {
dataView.setUint8(i, str.charCodeAt(i));
}
this.sendData(buffer).then(() => {
// 开始发送第一个包
console.log("握手成功了,等待200ms");
setTimeout(sendNextPacket, 120);
}).catch(err => {
console.log("握手没有成功");
reject(err);
});
}
HoldYouHand();
});
},
// 文字输入变化
onTextChanged(index) {
console.log(`${index + 1}行文字已更新`);
},
// 发送文字
sendText() {
if (!this.isConnected) {
uni.showToast({
title: '未连接设备',
icon: 'none'
});
return;
}
this.isSendingText = true;
this.textProgress = 0;
this.currentTextPacket = 0;
// 总数据包数
let totalPackets = this.textLines.length;
let currentPacket = 1;
// 发送单个数据包
let sendNextPacket = () => {
if (currentPacket > totalPackets) {
this.isSendingText = false;
console.log('文字发送完成');
uni.showToast({
title: '发送成功',
icon: 'success'
});
return;
}
// 获取当前行文字
let text = this.textLines[currentPacket - 1] || '';
// 将文字转换为GBK编码
this.convertToGBK(text).then(gbkData => {
console.log(JSON.stringify(gbkData));
// 构建数据包
let bufferSize = 5 + text.length * 2; // 头部4字节 + 数据部分
let buffer = new ArrayBuffer(bufferSize);
let dataView = new DataView(buffer);
// 填充头部
dataView.setUint8(0, 0x55); // 帧头
dataView.setUint8(1, 0x03); // 帧类型:文字
dataView.setUint8(2, currentPacket.toString(16)); // 包序号
console.log(text.length.toString(16));
dataView.setUint16(3, (text.length * 2).toString(16)); // 数据长度
console.log("gbkData.length=" + gbkData.length);
let str = new Array();
// 填充数据
for (let i = 1; i <= text.length * 2; i++) {
let highNibble = gbkData[i * 2 - 2]; // 高4位
let lowNibble = gbkData[i * 2 - 1]; // 低4位如果是奇数长度补0
console.log("i=" + i + "," + highNibble + lowNibble)
// 组合高低位并转换为字节值
let byteValue = parseInt(highNibble + lowNibble, 16);
dataView.setUint8(5 + i - 1, byteValue);
}
console.log(str.join(" "));
console.log("dataView.length=" + dataView.byteLength)
// 发送数据包
this.sendData(buffer).then(() => {
// 更新进度
this.currentTextPacket = currentPacket;
this.textProgress = Math.round((currentPacket / totalPackets) * 100);
console.log(`发送文字数据包 ${currentPacket}/${totalPackets}: ${text}`);
// 发送下一个包
currentPacket++;
setTimeout(sendNextPacket, this.inteval ? this.inteval : 0);
}).catch(err => {
this.isSendingText = false;
uni.showToast({
title: '文字发送失败',
icon: 'none'
});
});
}).catch(err => {
this.isSendingText = false;
console.log(`文字编码失败: ${err}`);
uni.showToast({
title: '编码失败',
icon: 'none'
});
});
};
// 开始发送第一个包
//牵着你的手
var HoldYouHand = () => {
var str = "word transmit start"; //握手的协议字符串
// 1. 创建 ArrayBuffer 和 DataView
let buffer = new ArrayBuffer(str.length);
let dataView = new DataView(buffer);
// 2. 将字符串转换为 ASCII 码并写入 DataView
for (let i = 0; i < str.length; i++) {
dataView.setUint8(i, str.charCodeAt(i));
}
this.sendData(buffer).then(() => {
// 开始发送第一个包
console.log("握手成功了,等待200ms");
setTimeout(sendNextPacket, 120);
}).catch(err => {
console.log("握手没有成功");
uni.showToast({
title: '握手没有成功',
icon: 'none'
});
});
}
HoldYouHand();
},
// 将文字转换为GBK编码使用第三方库或API
convertToGBK(text) {
return new Promise((resolve, reject) => {
// 注意uniapp环境中可能需要使用特定的GBK编码库
// 这里使用模拟实现,实际项目中应使用适当的编码库
try {
// 模拟GBK编码转换
let arr = gbk.encode(text)
let utf8Data = gbk.arr2hex(arr);
// 注意这只是示例实际GBK编码需要使用专门的库
resolve(utf8Data); // 实际项目中替换为GBK编码实现
} catch (error) {
console.log("错误:" + JSON.stringify(error));
reject(error);
}
});
},
// 发送数据通用方法
sendData(buffer) {
let sendBuffer = () => {
return new Promise((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) => {
if (ex.code == '10007') {
console.log("失败重试");
setTimeout(() => {
sendBuffer().then(succ).catch(err);
}, this.inteval || 0)
// succ()
} else {
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(() => {
console.log("成功了");
resolve();
})
.catch((ex) => {
if (ex.code == -1) {
console.log("超时了")
resolve();
} else {
// reject(ex);
console.log("异常了", ex);
sendBuffer();
}
});
} else {
promise.then(() => {
console.log("then........")
resolve();
}).catch(() => {
console.log("catch.........")
reject()
});
}
});
}
return sendBuffer();
},
gotoSendVideo: function() {
this.currentTab = 'flash';
uni.navigateTo({
url: "/pages/BlueTooth/ModeSetting/VideoSend_1",
success: (res) => {
let channel = res.eventChannel;
channel.emit("receiveDevice", {
connectedDeviceId: this.connectedDeviceId,
serviceId: this.serviceId,
writeCharacteristicId: this.writeCharacteristicId,
notifyCharacteristicId: this.notifyCharacteristicId
})
}
})
}
},
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;
}
.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;
}
.fleft{
float: left;
}
.clear{
clear: both;
}
</style>