1
0
forked from dyf/APP
Files
APP/pages/BlueTooth/ModeSetting/ModeSetting.vue
2025-08-27 11:06:49 +08:00

1499 lines
39 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>
<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 === '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>
<view class="brightness-value">{{ brightness }}%</view>
<slider class="brightness-slider" :value="brightness" min="0" max="100"
@changing="onBrightnessChanging" @change="onBrightnessChanged" />
</view>
</view>
<!-- 开机画面选项卡 -->
<view v-if="currentTab === 'splash'">
<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>
<!-- 隐藏的canvas用于图片处理 -->
<canvas canvas-id="splashCanvas" style="width: 160px; height: 80px; z-index: 9999;position: fixed; "
:style="{top:canvasTop,left:canvasLeft}" ></canvas>
</view>
</template>
<script>
import gbk from '@/utils/gbk.js';
export default {
data() {
return {
canvasTop: '-1000px',
canvasLeft: '-1000px',
inteval: 100,
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',
currentTab: 'mode',
brightness: 50,
brightnessTimer: null,
tempImagePath: '',
hasImage: false,
scale: 1,
translateX: 0,
translateY: 0,
lastX: 0,
lastY: 0,
isDragging: false,
progress: 0,
currentPacket: 0,
totalPackets: 100,
imageWidth: 0,
imageHeight: 0,
textLines: ['你好中国', '你好中国', '你好中国', '你好中国'],
textProgress: 0,
currentTextPacket: 0,
totalTextPackets: 4
}
},
computed: {
imageStyle() {
return `transform: scale(${this.scale}) translate(${this.translateX}px, ${this.translateY}px); transform-origin: 0 0;`;
}
},
onLoad() {
// debugger;
// // const 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: {
// 蓝牙初始化
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: ["0xFFE0"],
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) => {
const device = res.devices[0];
if (!device.name && !device.localName) return;
// if ((device.name || device.localName) === this.targetDeviceName) {
// 检查是否已存在该设备
const 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 ((device.name || device.localName) === this.targetDeviceName) {
console.log(`发现目标设备: ${JSON.stringify(device)}`);
this.connectDevice(device.deviceId, this.targetDeviceName);
}
});
},
// 连接蓝牙设备
connectDevice(deviceId, deviceName) {
if (this.isConnected && this.connectedDeviceId === deviceId) return;
this.isLoading = true;
console.log(`开始连接设备: ${deviceName}`);
var these = this;
uni.createBLEConnection({
deviceId: deviceId,
timeout: 10000,
success: (res) => {
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));
}
})
these.getDeviceServices(deviceId);
}, 2000)
// 获取服务和特征值
// 启用通知
this.stopSearch();
},
fail: (err) => {
console.log(`连接设备失败: ${JSON.stringify(err)}`);
uni.showToast({
title: '连接失败',
icon: 'none'
});
},
complete: () => {
this.isLoading = false;
}
});
},
// 断开蓝牙连接
disconnectDevice(callback) {
var these = this;
if(!this.connectedDeviceId){
this.isConnected = false;
this.connectedDeviceId = '';
this.currentMode = '';
this.currentPage = 'deviceList';
if (callback) {
callback();
}
return;
}
uni.closeBLEConnection({
deviceId: this.connectedDeviceId,
success: () => {
console.log('蓝牙连接已断开');
},
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();
}
}
});
},
// 获取设备服务
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);
}, 1000);
return;
}
// 查找目标服务
const 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)}`);
// 查找可写特征值
const writeChar = res.characteristics.find(char =>
char.uuid.indexOf("FFE1") >= 1
);
// 查找通知特征值
const notifyChar = res.characteristics.find(char =>
char.uuid.indexOf("FFE2") > -1
);
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;
} 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("收到设备通知数据")
// 解析从设备接收的数据
const 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;
}
// 构建数据包
const buffer = new ArrayBuffer(6);
const 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;
// 构建数据包
const buffer = new ArrayBuffer(6);
const 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() {
const frameWidth = 320; // 预览框宽度根据实际UI调整
const frameHeight = 640; // 预览框高度根据实际UI调整
const widthRatio = frameWidth / this.imageWidth;
const 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) {
const currentX = e.touches[0].clientX;
const currentY = e.touches[0].clientY;
// 计算位移
const deltaX = currentX - this.lastX;
const deltaY = currentY - this.lastY;
// 更新位置
this.translateX += deltaX;
this.translateY += deltaY;
// 限制移动范围
this.limitImagePosition();
// 更新最后位置
this.lastX = currentX;
this.lastY = currentY;
}
},
onTouchEnd() {
this.isDragging = false;
},
// 限制图片移动范围
limitImagePosition() {
const frameWidth = 320; // 预览框宽度根据实际UI调整
const frameHeight = 640; // 预览框高度根据实际UI调整
const maxX = 0;
const minX = frameWidth - this.imageWidth * this.scale;
const maxY = 0;
const 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用于处理图片
const 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格式的像素数据
const 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对应的位数
const r5 = Math.round((r / 255) * 31); // 5位0-31
const g6 = Math.round((g / 255) * 63); // 6位0-63
const b5 = Math.round((b / 255) * 31); // 5位0-31
// 合并为16位整数
return (r5 << 11) | (g6 << 5) | b5;
}
const result = new Uint16Array(160 * 80);
let index = 0;
console.log("pixels.length=" + pixels.length);
for (let i = 0; i < pixels.length; i += 4) {
const r = pixels[i];
const g = pixels[i + 1];
const b = pixels[i + 2];
// 转换为RGB565 (R:5bit, G:6bit, B:5bit)
const 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;
// 总数据包数
const totalPackets = 52;
let currentPacket = 1;
this.totalPackets=totalPackets;
// 发送单个数据包
const sendNextPacket = () => {
if (currentPacket > totalPackets) {
this.isSending = false;
resolve();
return;
}
// 计算当前包的数据
let packetSize = 250;
if (currentPacket <= 51) {
packetSize = 250; // 前51个包每个500字节
} else {
packetSize = 50; // 最后一个包100字节
}
// 创建数据包
const startIndex = (currentPacket - 1) * packetSize;
const endIndex = Math.min(startIndex + packetSize, imageData.length);
if (startIndex > endIndex) {
return;
}
const packetData = imageData.slice(startIndex,
endIndex); // imageData.subarray(startIndex, endIndex);
console.log("imageData.length=" + imageData.length + ",startIndex=" + startIndex +
",endIndex=" + endIndex + ",数据包长度" + (endIndex - startIndex) +
',packetData.length=' + packetData.length);
// 构建数据包
const bufferSize = 5 + packetData.length * 2; // 头部5字节 + 数据部分
const buffer = new ArrayBuffer(bufferSize);
const 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)}`
)
//发送数据包
this.sendData(buffer).then(() => {
// 更新进度
this.currentPacket = currentPacket;
this.progress = Math.round((currentPacket / totalPackets) * 52);
// console.log(`发送开机画面数据包 ${currentPacket}/${totalPackets}`);
// 发送下一个包(添加延迟避免蓝牙缓冲区溢出)
currentPacket++;
let inteval = parseInt(this.inteval ? this.inteval : 100);
// if ((currentPacket - 1) % 16 == 1) {
// inteval += 150;
// console.log("当前是第" + (currentPacket - 1) + "完成,额外多停留150ms,inteval=" +
// inteval);
// } else {
// console.log("interval=" + inteval)
// }
setTimeout(sendNextPacket, inteval);
}).catch(err => {
this.isSending = false;
reject(err);
});
};
//牵着你的手
var HoldYouHand = () => {
var str = "transmit start"; //握手的协议字符串
// 1. 创建 ArrayBuffer 和 DataView
const buffer = new ArrayBuffer(str.length);
const 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("握手成功了,等待500ms");
setTimeout(sendNextPacket, 500);
}).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;
// 总数据包数
const totalPackets = this.textLines.length;
let currentPacket = 1;
// 发送单个数据包
const sendNextPacket = () => {
if (currentPacket > totalPackets) {
this.isSendingText = false;
console.log('文字发送完成');
uni.showToast({
title: '发送成功',
icon: 'success'
});
return;
}
// 获取当前行文字
const text = this.textLines[currentPacket - 1] || '';
// 将文字转换为GBK编码
this.convertToGBK(text).then(gbkData => {
console.log(JSON.stringify(gbkData));
// 构建数据包
const bufferSize = 5 + text.length * 2; // 头部4字节 + 数据部分
const buffer = new ArrayBuffer(bufferSize);
const 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++) {
const highNibble = gbkData[i * 2 - 2]; // 高4位
const lowNibble = gbkData[i * 2 - 1]; // 低4位如果是奇数长度补0
console.log("i=" + i + "," + highNibble + lowNibble)
// 组合高低位并转换为字节值
const 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 : 100);
}).catch(err => {
this.isSendingText = false;
uni.showToast({
title: '文字发送失败',
icon: 'none'
});
});
}).catch(err => {
this.isSendingText = false;
console.log(`文字编码失败: ${err}`);
uni.showToast({
title: '编码失败',
icon: 'none'
});
});
};
// 开始发送第一个包
sendNextPacket();
},
// 将文字转换为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) {
console.log("deviceId=" + this.connectedDeviceId);
console.log("serviceId=" + this.serviceId);
console.log("characteristicId=" + this.writeCharacteristicId);
return new Promise((resolve, reject) => {
uni.writeBLECharacteristicValue({
deviceId: this.connectedDeviceId,
serviceId: this.serviceId,
characteristicId: this.writeCharacteristicId,
value: buffer,
success: () => {
console.log("发送数据成功");
resolve();
},
fail: (err) => {
console.log("发送数据失败");
reject(`发送数据失败: ${err.errMsg}`);
}
});
});
},
// 添加日志
addLog(message) {
const time = new Date().toLocaleTimeString();
this.logList.unshift({
time,
message
});
console.log(message);
// 限制日志数量
if (this.logList.length > 20) {
this.logList.pop();
}
}
},
onUnload() {
this.closeBluetoothAdapter();
}
}
</script>
<style>
.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;
}
</style>