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

1925 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,device)">
<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 class="receivLog">
<view>数据更新时间{{receiveData.date}}</view>
<view class="w50 fleft">Mac地址:{{receiveData.macAddress}}</view>
<view class="w50 fleft">主灯模式:{{receiveData.mainMode}}</view>
<view class="w50 fleft">副灯模式:{{receiveData.fuMode}}</view>
<view class="w50 fleft">电池电量:{{receiveData.batteryCnt}}</view>
<view class="w50 fleft">充电状态:{{receiveData.batteryStatu}}</view>
<view class="w50 fleft">续航时间:{{receiveData.time}}</view>
<view class="clear"></view>
</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'">
<TextToHexV1 class="TextToHex" ref="textToHex" :txts="textLines" :bgColor="'#FFFFFF'" :color="'#000000'"
: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 class="logs">
<view class="logItem" v-for="item,index in logList">
{{item['date']}}:{{item['remark']}}
</view>
</view>
</view>
</template>
<script>
import gbk from '@/utils/gbk.js';
import TextToHexV1 from '@/components/TextToHex/TextToHexV1.vue';
import Common from '../../../utils/Common';
export default {
components: {
TextToHexV1
},
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,
receiveData: {
"date": "",
"macAddress": "",
"mainMode": "",
"fuMode": "",
"batteryCnt": "",
"batteryStatu": "",
"time": ""
},
}
},
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,device);
}
});
},
// 连接蓝牙设备
connectDevice(deviceId, deviceName,device) {
if (this.isConnected && this.connectedDeviceId === deviceId) return;
this.isLoading = true;
if(device){
console.log("device=",device);
}
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: 1024,
success: (info) => {
console.log("设置mtu成功",info)
},
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) => {
these.addLog("发现服务" + JSON.stringify(res.services));
if (res.services.length == 0) {
setTimeout(function() {
these.getDeviceServices(deviceId);
}, 1000);
return;
}
// 查找目标服务
var targetService = res.services.find((service) => {
return service.uuid.indexOf('FFE0') > -1 || service.uuid.indexOf(
'0000AE30-0000-1000-8000-00805F9B34FB') > -1;
});
if (targetService) {
these.addLog(`找到目标服务: ${targetService.uuid}`);
this.serviceId = targetService.uuid;
this.getDeviceCharacteristics(deviceId, targetService.uuid);
} else {
these.addLog("没找到目标服务");
}
},
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} 个特征值`);
these.addLog(`获取设备成功:${JSON.stringify(res.characteristics)}`);
// 查找可写特征值
var writeChar = res.characteristics.find(char => {
return char.uuid.indexOf("FFE1") > -1 || char.uuid.indexOf(
"0000AE03-0000-1000-8000-00805F9B34FB") > -1;
}
);
// 查找通知特征值
var notifyChar = res.characteristics.find(char => {
return (char.uuid.indexOf("FFE2") > -1 || char.uuid.indexOf(
"0000AE02-0000-1000-8000-00805F9B34FB") > -1) && 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) => {
console.log("bytes[0]==0xFC",bytes[0]==0xFC)
let date = new Date();
this.receiveData.date =date.getHours() +":" + date.getMinutes() +":" + date.getSeconds();
if(bytes[0]==0xFB && bytes[1]==0x64 && bytes.length >=8){
try {
// 跳过帧头(第一个字节),从第二个字节开始解析
let staticLevelByte = bytes[2];
let getName=function(type){
let name="";
switch ( type ) {
case 0x02:
name = '弱光';
break;
case 0x04:
name = '工作光';
break;
case 0x01:
name = '强光';
break;
case 0x03:
name = '爆闪';
break;
case 0x00:
name = '关闭';
break;
}
return name ;
}
let staticLevelText = getName(staticLevelByte);
// 解析照明档位
let lightingLevelByte = bytes[3];
let lightingLevelText = getName(lightingLevelByte);
// 解析剩余电量
let batteryLevelByte = bytes[4];
// 电量百分比范围检查
let batteryLevel = Math.max(0,Math.min(100, batteryLevelByte));
//充电状态
let warn=bytes[5];
if(warn==0x00){
warn='未充电';
}
else if(warn==0x01){
warn='充电中';
}
// 解析剩余照明时间(第三和第四字节,小端序)
let lightingTime ="";
let HH = Math.max(0,Math.min(100, bytes[6]));
let mm = Math.max(0,Math.min(100, bytes[7]));
lightingTime=HH+"小时"+mm+"分钟";
this.receiveData.mainMode=staticLevelText;
this.receiveData.fuMode=lightingLevelText;
this.receiveData.batteryCnt=batteryLevel+"%";
this.receiveData.batteryStatu=warn;
this.receiveData.time=lightingTime;
let arr=["主灯模式:"+this.receiveData.mainMode,
"副灯模式:"+this.receiveData.fuMode,
"电池电量:"+this.receiveData.batteryCnt,
"充电状态:"+this.receiveData.batteryStatu,
"续航时间:"+this.receiveData.time
];
this.logList=[];
this.addLog(arr.join(","));
} catch (error) {
console.log('数据解析错误:',error);
}
}
else if(bytes[0]==0xFC){
this.logList=[];
var bytesToHexString = (bytes) => {
return bytes.map(byte => byte.toString(16).padStart(2, '0')).join(' ');
}
let hexs = bytesToHexString(bytes);
let arr=hexs.split(' ');
if(arr.length>=7){
this.receiveData.macAddress=arr.slice(1,7).join(":");
this.addLog("设备mac地址:"+this.receiveData.macAddress);
}else{
this.addLog("收到无效的数据");
}
}
else {
this.addLog("收到无效的数据");
}
}
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) {
this.logList = [];
if (!this.isConnected) {
this.addLog("未连接设备")
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 = 0x02;
break;
case 'work':
dataValue = 0x04;
break;
case 'high':
dataValue = 0x01;
break;
case 'flash':
dataValue = 0x03;
break;
}
// 构建数据包
var buffer = new ArrayBuffer(6);
var dataView = new DataView(buffer);
dataView.setUint8(0, 0xFA); // 帧头
dataView.setUint8(1, dataValue); // 帧类型
dataView.setUint8(2, 0x00); // 数据
dataView.setUint8(3, 0x01); // 数据
dataView.setUint8(4, 0x00); // 数据
dataView.setUint8(5, 0xFF); // 数据
// 发送数据
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];
//var rgb565 = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
var bgr565 = (b >> 3) | ((g & 0xFC) << 3) | ((r & 0xF8) << 8);
result[index++] = bgr565;
}
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); //包长度
}
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);
});
};
sendNextPacket();
});
},
// 文字输入变化
onTextChanged(index) {
console.log(`${index + 1}行文字已更新`);
},
// 发送文字
async sendText() {
this.logList = [];
uni.showLoading({
title: "请稍候....",
mask: true
});
this.addLog({
date: Common.DateFormat(),
remark: '开始发送文字'
});
var sendTxtPackge = (rgbdata, type, str) => {
var promise = new Promise((resolve, reject) => {
try {
let packetSize = rgbdata.length; //每包均分的数量
let mode = rgbdata.length % packetSize; //最后一包的数量
let cnt = parseInt(rgbdata.length / packetSize) + (mode > 0 ? 1 : 0); //总包数量
let curr = 1; //当前包序号
this.addLog({
date: Common.DateFormat(),
remark: '当前包' + curr
});
let sendNext = () => {
if (curr > cnt) {
resolve();
return;
}
this.addLog({
date: Common.DateFormat(),
remark: "正在向设备发送" + curr + "/" + cnt
})
let bufferSize = 261;
// if (curr == 1) {
//bufferSize = packetSize + 7;
// } else if (curr == cnt) {
// bufferSize = mode > 0 ? (mode) : (packetSize);
// } else {
// bufferSize = packetSize;
// }
console.log("bufferSize=", bufferSize)
let buffer = new ArrayBuffer(bufferSize);
let dataView = new DataView(buffer);
let startIndex = (curr - 1) * packetSize;
this.addLog({
date: Common.DateFormat(),
remark: "分片大小" + bufferSize
})
let endIndex = Math.min(startIndex + packetSize, rgbdata.length);
if (startIndex > endIndex) {
this.addLog({
date: Common.DateFormat(),
remark: "发送完成"
})
return;
}
let packetData = rgbdata.slice(startIndex, endIndex); //取一片数据发送
this.addLog({
date: Common.DateFormat(),
remark: "获取到分片"
})
let start = 0;
if (curr == 1) {
dataView.setUint8(0, 0xFA);
dataView.setUint8(1, type);
dataView.setUint8(2, 0x01);
dataView.setUint8(3, 0x00);
// dataView.setUint16(2, str.length, false);
start = 4;
}
for (let i = 0; i < packetData.length; i++) {
dataView.setUint8(start + i, packetData[i]);
}
dataView.setUint8(bufferSize - 1, 0xFF);
this.addLog({
date: Common.DateFormat(),
remark: "数据准备就绪"
})
let inteval = parseInt(this.inteval ? this.inteval : 0);
//发送数据包
this.sendData(buffer).then(() => {
this.addLog({
date: Common.DateFormat(),
remark: "发送成功,准备发送下一包"
})
curr++;
setTimeout(sendNext, inteval);
}).catch(err => {
console.log("err=", err);
this.addLog({
date: Common.DateFormat(),
remark: "出现错误" + JSON.stringify(err)
});
if (err.code == '10007') {
setTimeout(sendNext, inteval);
} else {
reject(err);
}
});
}
sendNext();
} catch (ex) {
console.log("ex=", ex);
this.addLog({
date: Common.DateFormat(),
remark: "出现异常1" + JSON.stringify(ex)
})
reject(ex);
}
});
return promise;
}
// this.addLog({
// date: Common.DateFormat(),
// remark: '正在取模'
// });
var result = await this.$refs.textToHex.drawAndGetPixels();
// this.addLog({
// date: Common.DateFormat(),
// remark: '取模成功'
// });
result = result.map(level1 => {
return level1.flat(Infinity);
});
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 = result[i];
this.addLog({
date: Common.DateFormat(),
remark: '开始发送第' + (i + 1)
});
try {
await sendTxtPackge(rgb, h3dic[i], str);
} catch (ex) {
flag = false;
break;
}
}
}
uni.hideLoading();
if (flag) {
uni.showModal({
title: "提示",
content: "发送成功"
})
} else {
uni.showModal({
title: "错误",
content: "出现了异常发送失败"
})
}
},
// 发送数据通用方法
sendData(buffer) {
this.addLog("设备就绪,正在握手");
return new Promise((resolve, reject) => {
this.addLog("握手成功,正在发送");
this.addLog("设备id:" + this.connectedDeviceId);
this.addLog("服务id:" + this.serviceId);
this.addLog("特征id:" + this.writeCharacteristicId);
uni.writeBLECharacteristicValue({
deviceId: this.connectedDeviceId,
serviceId: this.serviceId,
characteristicId: this.writeCharacteristicId,
writeType:plus.os.name=='Android'?'writeNoResponse':'write',
value: buffer,
success: () => {
this.addLog({
date: Common.DateFormat(),
remark: "发送数据成功"
})
resolve();
},
fail: (err) => {
this.addLog({
date: Common.DateFormat(),
remark: "发送数据失败" + JSON.stringify(err)
})
reject(err);
},
complete: () => {
this.addLog({
date: Common.DateFormat(),
remark: "向设备写入数据完成"
})
}
});
});
},
// 添加日志
addLog(message) {
if (message.date) {
console.log(message.remark);
this.logList.push(message);
} else {
console.log(message);
this.logList.push({
date: Common.DateFormat(),
remark: 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>
.logs {
width: 100%;
height: auto;
min-height: 200rpx;
border: 1rpx solid #666;
padding: 30rpx;
}
.logs .logItem {
margin-bottom: 20rpx;
font-size: 28rpx;
font-weight: 400;
}
.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>