Files
APP/pages/BlueTooth/ModeSetting/HBY650_1.vue

1899 lines
50 KiB
Vue
Raw Normal View History

2025-08-27 11:06:49 +08:00
<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>