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

1947 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">6155控制器</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 === 'device'}" @click="getDevice()">设备信息</button>
<button class="tab-btn" :class="{'active': currentTab === 'mode'}"
@click="currentTab = 'mode'">主灯模式</button>
<button class="tab-btn" :class="{'active': currentTab === 'fumode'}"
@click="currentTab = 'fumode'">副灯模式</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 === 'device'">
<view class="modes-area">
<view class="mode-title">固件版本:{{gujianVer}}</view>
<view class="mode-title">Mac地址:{{MacAdd}}</view>
<view class="modes-grid">
<view class="mode-item" :class="{'selected': currentMode === 'close'}" @click="ReStart()">
<text class="mode-name">设备重启</text>
</view>
<view class="mode-item" :class="{'selected': currentMode === 'close'}" @click="Restore()">
<text class="mode-name">恢复出厂设置</text>
</view>
</view>
</view>
</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 === 'close'}"
@click="setMode('close','main')">
<text class="mode-name">关闭</text>
</view>
<view class="mode-item" :class="{'selected': currentMode === 'low'}"
@click="setMode('low','main')">
<text class="mode-name">弱光</text>
</view>
<view class="mode-item" :class="{'selected': currentMode === 'work'}"
@click="setMode('work','main')">
<text class="mode-name">工作光</text>
</view>
<view class="mode-item" :class="{'selected': currentMode === 'high'}"
@click="setMode('high','main')">
<text class="mode-name">强光</text>
</view>
<view class="mode-item" :class="{'selected': currentMode === 'flash'}"
@click="setMode('flash','main')">
<text class="mode-name">爆闪</text>
</view>
</view>
</view>
</view>
<view v-if="currentTab === 'fumode'">
<view class="modes-area">
<view class="mode-title">选择灯光模式</view>
<view class="modes-grid">
<view class="mode-item" :class="{'selected': fucurrentMode === 'close'}"
@click="setMode('close','fu')">
<text class="mode-name">关闭</text>
</view>
<view class="mode-item" :class="{'selected': fucurrentMode === 'low'}"
@click="setMode('low','fu')">
<text class="mode-name">弱光</text>
</view>
<view class="mode-item" :class="{'selected': fucurrentMode === 'work'}"
@click="setMode('work','fu')">
<text class="mode-name">工作光</text>
</view>
<view class="mode-item" :class="{'selected': fucurrentMode === 'high'}"
@click="setMode('high','fu')">
<text class="mode-name">强光</text>
</view>
<view class="mode-item" :class="{'selected': fucurrentMode === 'flash'}"
@click="setMode('flash','fu')">
<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'">
<TextToHex class="TextToHex" ref="textToHex" :txts="textLines" :bgColor="'#000000'" :color="'#FFFFFF'"
:fontSize="16" />
<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';
import TextToHexVue from '@/components/TextToHex/TextToHex.vue';
export default {
comments: {
TextToHexVue
},
data() {
return {
gujianVer: '',
MacAdd: '',
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: '',
fucurrentMode: '',
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() {
this.initBluetoothAdapter();
},
methods: {
//获取设备信息
getDevice() {
this.currentTab = 'device';
// 1. 创建 ArrayBuffer 和 DataView
var buffer = new ArrayBuffer(4);
var dataView = new DataView(buffer);
dataView.setUint8(0, 0x70);
dataView.setUint8(1, 0x65);
dataView.setUint8(2, 0xA1);
dataView.setUint8(3, 0x00);
this.sendData(buffer).then(() => {
console.log("发送命令成功,等待设备响应");
}).catch(err => {
console.log("发送命令失败", err);
});
},
// 蓝牙初始化
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) => {
var device = res.devices[0];
if (!device.name && !device.localName) return;
// if ((device.name || device.localName) === this.targetDeviceName) {
// 检查是否已存在该设备
var 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;
}
// 查找目标服务
var targetService = res.services.find(service => service.uuid.indexOf('FFE0') > -1);
if (targetService) {
console.log(`找到目标服务: ${targetService.uuid}`);
this.serviceId = targetService.uuid;
this.getDeviceCharacteristics(deviceId, targetService.uuid);
}
},
fail: (err) => {
console.log(`获取设备服务失败: ${err.errMsg}`);
}
});
},
// 获取设备特征值
getDeviceCharacteristics(deviceId, serviceId) {
var these = this;
uni.getBLEDeviceCharacteristics({
deviceId: deviceId,
serviceId: serviceId,
success: (res) => {
console.log(`获取设备特征值成功,共发现 ${res.characteristics.length} 个特征值`);
console.log(`获取设备特征值成功${JSON.stringify(res.characteristics)}`);
// 查找可写特征值
var writeChar = res.characteristics.find(char =>
char.uuid.indexOf("FFE1") >= 1
);
// 查找通知特征值
var 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);
setTimeout(() => {
uni.notifyBLECharacteristicValueChange({
deviceId: deviceId,
serviceId: serviceId,
characteristicId: notifyChar.uuid,
state: true,
success: (info) => {
console.log('启用通知成功' + JSON.stringify(info));
uni.showToast({
title: "订阅消息成功",
icon: 'success'
});
// 监听特征值变化
setTimeout(() => {
uni.onBLECharacteristicValueChange((
receive) => {
console.log("receive=",
receive);
var hexs = [];
var bytesToHex =
function(bytes) {
return bytes.map(
byte =>
byte
.toString(
16)
.padStart(
2, '0')
);
}
var parseData = (
bytes) => {
try {
// 跳过帧头(第一二个字节),从第三个字节开始解析
let type =
bytes[2];
switch (type) {
case 0xA1: //获取设备信息
let v1 =
parseInt(
bytes[
3
],
16
);
let v2 =
parseInt(
bytes[
4
],
16
);
let vers =
`${v1}.${v2}`;
console
.log(
hexs[
5
]
.toString()
);
let mac =
`${hexs[5]}:${hexs[6]}:${hexs[7]}:${hexs[8]}:${hexs[9]}:${hexs[10]}`;
console
.log(
"解析成功,固件版本:" +
vers +
',MAC地址:' +
mac
);
these
.MacAdd =
mac;
these
.gujianVer =
vers;
break;
case 0xB2: //设备主动上报数据
let main =
bytes[
3
]; //主灯
if (main ==
0x00
) {
main =
'关闭';
these
.currentMode =
'close';
} else if (
main ==
0x01
) {
main =
'弱光模式';
these
.currentMode =
'low';
} else if (
main ==
0x02
) {
main =
'工作光模式';
these
.currentMode =
'work';
} else if (
main ==
0x03
) {
main =
'强光模式';
these
.currentMode =
'high';
} else if (
main ==
0x04
) {
main =
'爆闪模式';
these
.currentMode =
'flash';
}
let fu =
bytes[
4
]; //副灯
if (fu ==
0x00
) {
fu = '关闭'
} else if (
fu ==
0x01
) {
fu = '弱光模式'
} else if (
fu ==
0x02
) {
fu = '工作光模式'
} else if (
fu ==
0x03
) {
fu = '强光模式'
} else if (
fu ==
0x04
) {
fu = '爆闪模式'
}
let battery =
bytes[
5
]; //电量
let batteryLevel =
Math
.max(
0,
Math
.min(
100,
battery
)
); //电量百分比
let addBatt =
bytes[
6
]; //充电状态
if (addBatt ==
0x00
) {
addBatt
=
"未充电"
} else if (
addBatt ==
0x01
) {
addBatt
=
"充电中"
} else if (
addBatt ==
0x02
) {
addBatt
=
"已充满"
}
let lightingTime =
(bytes[
7
] <<
8
) |
bytes[
8
]; //续航时间
console
.log(
"解析成功,主灯模式:" +
main +
',副灯模式:' +
fu +
",电量:" +
batteryLevel +
"%,充电状态:" +
addBatt +
",续航时间:" +
lightingTime +
"分钟"
);
break;
default:
console
.log(
"收到了未知的类型数据,无法解析"
);
try {
var uint8Array =
new Uint8Array(
receive
.value
);
let str =
'';
for (
let i =
0; i <
uint8Array
.length; i++
) {
// 将每个字节转换为对应的字符
str +=
String
.fromCharCode(
uint8Array[
i
]
);
}
console
.log(
"将数据转文本",
str
);
} catch (
ex
) {
console
.log(
"将数据转文本失败",
ex
);
}
break;
}
} catch (error) {
console.error(
'数据解析错误:',
error);
}
}
var dataView =
new DataView(receive
.value);
console.log("dataView=",
dataView);
// 转换为字节数组
var bytes = [];
for (let i = 0; i <
dataView
.byteLength; i++) {
bytes.push(dataView
.getUint8(i));
}
// 保存原始数据用于调试
this.receivedData = bytes;
hexs = bytesToHex(bytes);
console.log('接收到原始数据:',
hexs.join(' '));
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("收到设备通知数据")
// 解析从设备接收的数据
var 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, type) {
if (!this.isConnected) {
uni.showToast({
title: '未连接设备',
icon: 'none'
});
return;
}
if (type == 'main') {
if (this.currentMode === mode) return;
type = 0x04;
} else if (type == 'fu') {
if (this.fucurrentMode === mode) return;
type = 0x05;
}
this.currentMode = mode;
let dataValue = 0;
switch (mode) {
case 'close':
dataValue = 0x00;
break;
case 'low':
dataValue = 0x01;
break;
case 'work':
dataValue = 0x02;
break;
case 'high':
dataValue = 0x03;
break;
case 'flash':
dataValue = 0x04;
break;
}
// 构建数据包
var buffer = new ArrayBuffer(4);
var dataView = new DataView(buffer);
dataView.setUint8(0, 0x70); // 帧头
dataView.setUint8(1, 0x65); // 帧头
dataView.setUint8(2, type); // 帧类型
dataView.setUint8(3, 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;
// 构建数据包
var buffer = new ArrayBuffer(4);
var dataView = new DataView(buffer);
let data = '0x' + parseInt(this.brightness).toString(16);
console.log("亮度:" + this.brightness + ',16进制:' + data);
dataView.setUint8(0, 0x70); // 帧头
dataView.setUint8(1, 0x65); // 帧类型:亮度调节
dataView.setUint8(2, 0x09); // 包序号
dataView.setUint8(3, 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() {
var frameWidth = 320; // 预览框宽度根据实际UI调整
var frameHeight = 640; // 预览框高度根据实际UI调整
var widthRatio = frameWidth / this.imageWidth;
var 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) {
var currentX = e.touches[0].clientX;
var currentY = e.touches[0].clientY;
// 计算位移
var deltaX = currentX - this.lastX;
var deltaY = currentY - this.lastY;
// 更新位置
this.translateX += deltaX;
this.translateY += deltaY;
// 限制移动范围
this.limitImagePosition();
// 更新最后位置
this.lastX = currentX;
this.lastY = currentY;
}
},
onTouchEnd() {
this.isDragging = false;
},
// 限制图片移动范围
limitImagePosition() {
var frameWidth = 320; // 预览框宽度根据实际UI调整
var frameHeight = 640; // 预览框高度根据实际UI调整
var maxX = 0;
var minX = frameWidth - this.imageWidth * this.scale;
var maxY = 0;
var 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用于处理图片
var 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格式的像素数据
var rgb565Data = this.convertToRGB565(pixels, 160, 80);
// console.log("rgb565Data=,",rgb565Data);
// 分包发送
this.sendImagePackets(rgb565Data).then(resolve).catch(reject);
});
},
// 将RGBA转换为RGB565
convertToRGB565(pixels, width, height) {
if (!width) {
width = 160;
}
if (!height) {
height = 80;
}
var result = new Uint16Array(width * height);
let index = 0;
for (let i = 0; i < pixels.length; i += 4) {
var r = pixels[i];
var g = pixels[i + 1];
var b = pixels[i + 2];
// 转换为RGB565 (R:5bit, G:6bit, B:5bit)
var 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 currentPacket = 1;
let packetSize = 120;
let mode = imageData.length % packetSize;
let totalPackets = parseInt(imageData.length / packetSize) + (mode > 0 ? 1 : 0)
let inteval = parseInt(this.inteval ? this.inteval : 100);
this.totalPackets = totalPackets;
// 发送单个数据包
var sendNextPacket = () => {
if (currentPacket > totalPackets) {
this.isSending = false;
resolve();
return;
}
var start = 0;
var dataLength = 0;
var bufferSize = 0;
if (currentPacket == 1) {
bufferSize = packetSize * 2 + 5;
dataLength = packetSize * 2;
start = 5;
} else if (currentPacket == totalPackets) {
bufferSize = mode > 0 ? (mode * 2) : (packetSize * 2);
dataLength = bufferSize
} else {
bufferSize = packetSize * 2;
dataLength = bufferSize;
}
// 创建数据包
var startIndex = (currentPacket - 1) * packetSize;
var endIndex = Math.min(startIndex + packetSize, imageData.length);
if (startIndex > endIndex) {
return;
}
var packetData = imageData.slice(startIndex,
endIndex); // imageData.subarray(startIndex, endIndex);
// 构建数据包
var buffer = new ArrayBuffer(bufferSize);
var dataView = new DataView(buffer);
if (currentPacket == 1) { //第一包要填充头部
// 填充头部
dataView.setUint8(0, 0x70); // 帧头
dataView.setUint8(1, 0x65); // 帧类型:开机画面
dataView.setUint8(2, 0x0A); // 包序号
dataView.setUint16(3, dataLength, false);
}
//console.log("packetSize="+packetSize+",startIndex="+startIndex+",endIndex="+endIndex+",imageData.length="+imageData.length+",start="+start+",packetData.length="+packetData.length);
for (let i = 0; i < packetData.length; i++) {
dataView.setUint16(start + i * 2, packetData[i], false); //大端字节序
}
//发送数据包
this.sendData(buffer).then(() => {
// 更新进度
this.currentPacket = currentPacket;
this.progress = Math.round((currentPacket / totalPackets) * totalPackets);
currentPacket++;
setTimeout(sendNextPacket, inteval);
}).catch(err => {
if (err.code == 10007) {
setTimeout(sendNextPacket, inteval);
return;
}
this.isSending = false;
reject(err);
});
};
//牵着你的手
// var HoldYouHand = () => {
// var str = "transmit start"; //握手的协议字符串
// // 1. 创建 ArrayBuffer 和 DataView
// var buffer = new ArrayBuffer(str.length);
// var 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);
// });
// }
sendNextPacket();
});
},
// 文字输入变化
onTextChanged(index) {
console.log(`${index + 1}行文字已更新`);
},
// 发送文字
async sendText() {
var sendTxtPackge = (rgbdata, type, str) => {
var promise = new Promise((resolve, reject) => {
try {
let packetSize = 120; //每包均分的数量
let mode = rgbdata.length % packetSize; //最后一包的数量
let cnt = parseInt(rgbdata.length / packetSize) + (mode > 0 ? 1 : 0); //总包数量
let curr = 1; //当前包序号
let sendNext = () => {
if (curr > cnt) {
resolve();
return;
}
console.log("正在发送" + curr + "/" + cnt)
let bufferSize = 0;
if (curr == 1) {
bufferSize = packetSize * 2 + 5;
} else if (curr == cnt) {
bufferSize = mode > 0 ? (mode * 2) : (packetSize * 2);
} else {
bufferSize = packetSize * 2;
}
let buffer = new ArrayBuffer(bufferSize);
let dataView = new DataView(buffer);
let startIndex = (curr - 1) * packetSize;
let endIndex = Math.min(startIndex + packetSize, rgbdata.length);
if (startIndex > endIndex) {
return;
}
let packetData = rgbdata.slice(startIndex, endIndex); //取一片数据发送
let start = 0;
if (curr == 1) {
dataView.setUint8(0, 0x70);
dataView.setUint8(1, 0x65);
dataView.setUint8(2, type);
dataView.setUint16(3, str.length, false);
start = 5;
}
for (let i = 0; i < packetData.length; i++) {
dataView.setUint16(start + i * 2, packetData[i], false); // 大端字节序
}
let inteval = parseInt(this.inteval ? this.inteval : 0);
//发送数据包
this.sendData(buffer).then(() => {
console.log("发送成功,准备发送下一包" + curr)
curr++;
setTimeout(sendNext, inteval);
}).catch(err => {
console.log("出现错误", err)
if (err.code == '10007') {
setTimeout(sendNext, inteval);
} else {
reject(err);
}
});
}
sendNext();
} catch (ex) {
console.log("出现异常", ex)
reject(ex);
}
});
return promise;
}
var result = await this.$refs.textToHex.drawAndGetPixels();
let h3dic = [0x06, 0x07, 0x08];
let pros = [];
let flag = true;
for (var i = 0; i < result.length; i++) {
let str = this.textLines[i];
if (str.length > 0) {
let width = str.length * 16;
var rgb = this.convertToRGB565(result[i].pixelData, width, 16);
try {
await sendTxtPackge(rgb, h3dic[i], str);
} catch (ex) {
flag = false;
break;
}
}
}
if (flag) {
uni.showModal({
title: "提示",
content: "发送成功"
})
} else {
uni.showModal({
title: "错误",
content: "出现了异常发送失败"
})
}
},
// 发送数据通用方法
sendData(buffer) {
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) {
},
ReStart() { //重启
if (!this.isConnected) return;
// 构建数据包
var buffer = new ArrayBuffer(4);
var dataView = new DataView(buffer);
let data = '0x' + parseInt(this.brightness).toString(16);
console.log("亮度:" + this.brightness + ',16进制:' + data);
dataView.setUint8(0, 0x70); // 帧头
dataView.setUint8(1, 0x65); // 帧类型:亮度调节
dataView.setUint8(2, 0x0C); // 包序号
dataView.setUint8(3, 0x01); // 数据
// 发送数据
this.sendData(buffer);
},
Restore() { //恢复出厂设置
if (!this.isConnected) return;
// 构建数据包
var buffer = new ArrayBuffer(4);
var dataView = new DataView(buffer);
let data = '0x' + parseInt(this.brightness).toString(16);
console.log("亮度:" + this.brightness + ',16进制:' + data);
dataView.setUint8(0, 0x70); // 帧头
dataView.setUint8(1, 0x65); // 帧类型:亮度调节
dataView.setUint8(2, 0x0D); // 包序号
dataView.setUint8(3, 0x01); // 数据
// 发送数据
this.sendData(buffer);
}
},
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;
margin-top: 20rpx;
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;
}
.TextToHex {
position: fixed;
top: -99999rpx;
left: -99999rpx;
visibility: hidden;
}
</style>