UniApp蓝牙开发高阶实战|广播包解析、固件升级与工业级场景适配
系列文章目录
第一章 UniApp蓝牙开发全攻略|连接通信实战
第二章 UniApp蓝牙开发进阶实战|解决断连重连、多设备管理与性能优化
第三章 UniApp蓝牙开发高阶实战|广播包解析、固件升级与工业级场景适配(本文)
一、前言
前两篇教程我们实现了蓝牙基础通信、断连重连、多设备管理等功能,但在工业级、商业级产品落地中,仍有诸多高阶难点未解决:无需连接设备即可获取设备状态(如电量、型号)、硬件固件需要远程升级、多设备并发连接时通信卡顿、低功耗场景下耗电过快、恶劣环境(如工业车间、户外)下通信不稳定、自定义协议适配繁琐等。
高阶开发价值:突破进阶开发的局限,实现产品级功能闭环,适配工业控制、智能家居批量管理、智能穿戴固件迭代等复杂场景,解决商业化落地中的核心痛点,提升产品竞争力与用户体验。
二、准备工作
1.环境回顾与升级
- 基础环境:HBuilderX 最新稳定版、真机测试设备(安卓/iOS,建议安卓12+、iOS14+,适配工业级场景)
- 测试设备:BLE低功耗蓝牙设备(建议工业级传感器、智能硬件,支持固件升级与广播包自定义,至少2台用于并发测试)
- 高阶依赖:引入uni-log用于日志上报、uni-app-x(可选,提升工业级场景下的性能)、base64-js(固件升级时用于文件编码)
- 调试工具:手机开发者模式(蓝牙日志、功耗监控)、微信开发者工具(小程序端高阶调试)、硬件调试工具(查看广播包、固件文件)
2.基础代码复用说明
本文代码基于前两篇的核心逻辑进行高阶拓展,复用蓝牙初始化、断连重连、多设备管理、数据加密等基础与进阶方法,重点新增广播包解析、固件升级、日志上报等高阶模块,避免重复编写冗余代码,确保代码的连贯性与可复用性。
三、难点拆解
1.广播包解析难点
核心问题:蓝牙广播包数据格式复杂(符合BLE协议规范),不同厂商的广播包自定义字段不同,需解析出设备电量、型号、固件版本、信号强度等核心信息,且无需连接设备即可获取,降低用户操作成本。
额外难点:广播包数据长度有限(最大31字节),自定义字段与标准字段的区分、数据格式(十六进制/十进制)的转换,以及不同设备广播包的兼容性处理。
2.固件升级(OTA)难点
核心问题:固件文件体积较大(通常几KB~几十KB),需通过蓝牙分块传输,避免传输中断、数据丢失,同时需处理升级进度展示、升级失败回滚、固件版本校验等逻辑,确保升级安全可靠。
额外难点:不同硬件的固件升级协议不同、分块大小适配(避免通信拥堵)、低功耗场景下的升级优化、升级过程中设备断连的处理。
3.工业级场景适配难点
核心问题:工业场景下多设备并发连接(通常10台以上)、环境干扰强(电磁干扰、距离波动)、低功耗要求(设备续航提升)、通信稳定性要求高(避免数据丢失、延迟),需针对性优化。
额外难点:多设备并发时的资源竞争、恶劣环境下的信号过滤、低功耗模式下的蓝牙参数调整、工业级自定义通信协议的适配。
四、实战
1.代码
<template>
<view class="blue-container">
<!-- 基础操作按钮区(复用进阶版,新增固件升级按钮) -->
<view class="btn-box">
<button @click="initBlue(true)" size="mini">初始化蓝牙</button>
<button @click="searchBlue" size="mini" type="primary">搜索设备</button>
<button @click="stopSearch" size="mini">停止搜索</button>
<button @click="clearDevice" size="mini" type="warn">清空设备</button>
<button @click="selectFirmware" size="mini" type="primary">选择固件</button>
<button @click="startOtaUpgrade" size="mini" type="success" v-if="firmwareFile">开始升级</button>
</view>
<!-- 固件升级进度展示 -->
<view class="ota-progress" v-if="otaProgress > 0">
<text class="title">固件升级进度:{{otaProgress}}%</text>
<progress :percent="otaProgress" stroke-width="6" color="#1890ff" />
<button @click="cancelOtaUpgrade" size="mini" type="warn">取消升级</button>
</view>
<!-- 已连接设备展示(新增设备状态信息) -->
<view class="connected-device" v-if="connectedDeviceId">
<text class="title">当前连接:</text>
<text>{{connectedDeviceName || '未知设备'}}</text>
<text>|电量:{{connectedDeviceBattery}}%</text>
<text>|固件版本:{{connectedDeviceFirmware}}</text>
<button @click="closeBlue" size="mini" type="warn">断开连接</button>
</view>
<!-- 设备列表(新增广播包解析信息、固件版本提示) -->
<scroll-view scroll-y class="device-list">
<view class="device-item"
v-for="(item,index) in deviceList"
:key="index"
@click="connectDevice(item)"
:class="{connected: item.deviceId === connectedDeviceId}">
<text>设备名称:{{item.name || '未知设备'}}</text>
<text>设备地址:{{item.deviceId}}</text>
<text>信号强度:{{item.rssi}}dBm</text>
<text>电量:{{item.battery || '未知'}}%</text>
<text>固件版本:{{item.firmwareVersion || '未知'}}</text>
<text class="status" v-if="item.deviceId === connectedDeviceId">已连接</text>
<text class="status" v-else>未连接</text>
<text class="ota-tip" v-if="item.firmwareVersion && item.firmwareVersion < latestFirmwareVersion">有新版本固件</text>
</view>
</scroll-view>
<!-- 通信操作(复用进阶版,新增协议选择) -->
<view class="send-box">
<picker @change="changeProtocol" :range="protocolList" range-key="name">
<view class="picker-view">当前协议:{{currentProtocol.name}}</view>
</picker>
<input v-model="sendData" placeholder="输入指令(兼容自定义协议)" />
<checkbox v-model="isEncrypt">加密发送</checkbox>
<button @click="sendMsg" size="mini" type="success">发送指令</button>
</view>
<!-- 接收数据展示(新增协议解析提示) -->
<view class="recv-box">
<text class="title">接收数据:</text>
<text>{{recvData}}</text>
<text class="encrypt-tag" v-if="isRecvEncrypt">(已解密)</text>
<text class="protocol-tag">({{currentProtocol.name}}协议解析)</text>
</view>
<!-- 重连与日志设置(复用进阶版,新增日志上报开关) -->
<view class="setting-box">
<view class="reconnect-set">
<text>自动重连:</text>
<switch v-model="autoReconnect" @change="setAutoReconnect" />
<text>重连次数:{{reconnectCount}}/5</text>
</view>
<view class="log-set">
<text>日志上报:</text>
<switch v-model="isLogReport" @change="setLogReport" />
<button @click="viewLog" size="mini">查看日志</button>
</view>
</view>
</view>
</template>
<script>
// 引入依赖(固件升级、日志上报)
import base64 from 'base64-js';
import uniLog from '@/common/uni-log.js'; // 需自行创建日志上报工具
export default {
data() {
return {
// 基础与进阶功能复用字段
deviceList: [], // 搜索到的设备列表(含广播包解析信息)
connectedDeviceId: '', // 当前连接的设备ID
connectedDeviceName: '', // 当前连接的设备名称
connectedDeviceBattery: '', // 当前连接设备电量(广播包解析)
connectedDeviceFirmware: '', // 当前连接设备固件版本
serviceId: '', // 服务UUID(替换为硬件UUID)
characteristicId: '', // 特征值UUID(替换为硬件UUID)
otaCharacteristicId: '', // 固件升级特征值UUID(硬件提供)
sendData: '', // 发送的数据
recvData: '', // 接收的数据
isSearching: false, // 是否正在搜索
autoReconnect: true, // 是否开启自动重连
reconnectCount: 0, // 重连次数(最多5次)
reconnectTimer: null, // 重连定时器
isEncrypt: false, // 是否加密发送
isRecvEncrypt: false, // 接收数据是否已解密
encryptKey: 'uniapp_blue_2024', // 加密密钥(动态生成更佳)
historyDevice: uni.getStorageSync('historyDevice') || [], // 历史连接设备
// 高阶功能新增字段
firmwareFile: null, // 选择的固件文件
otaProgress: 0, // 固件升级进度(0-100)
otaTimer: null, // 固件升级定时器
isOtaUpgrading: false, // 是否正在升级
latestFirmwareVersion: '1.2.0', // 最新固件版本(实际项目从接口获取)
protocolList: [ // 自定义协议列表(适配不同硬件)
{id: 1, name: '标准BLE协议', code: 'standard'},
{id: 2, name: '工业传感器协议', code: 'industrial'},
{id: 3, name: '智能穿戴协议', code: 'wearable'}
],
currentProtocol: {id: 1, name: '标准BLE协议', code: 'standard'}, // 当前选中协议
isLogReport: true, // 是否开启日志上报
logList: [] // 日志列表
}
},
onLoad() {
// 页面加载时,初始化蓝牙、连接历史设备、初始化日志
this.initBlue(true);
this.initLog();
},
onUnload() {
// 页面卸载,清理资源、断开蓝牙、保存历史设备、上报日志
clearTimeout(this.reconnectTimer);
clearInterval(this.otaTimer);
this.closeBlue(false);
uni.setStorageSync('historyDevice', this.historyDevice);
this.reportLog();
},
methods: {
// 初始化日志(高阶新增)
initLog() {
uniLog.init({
reportUrl: 'https://your-api.com/log/report', // 日志上报接口
appId: 'uniapp-bluetooth-high',
level: 'info' // 日志级别:debug/info/warn/error
});
this.logList = [];
this.addLog('日志初始化成功,开始记录蓝牙操作');
},
// 添加日志(高阶新增)
addLog(content) {
const log = {
time: new Date().toLocaleString(),
content: content
};
this.logList.push(log);
// 日志上报(开启时)
if(this.isLogReport) {
uniLog.info(log.content);
}
// 限制日志数量,避免内存占用过多
if(this.logList.length > 50) {
this.logList.shift();
}
},
// 查看日志(高阶新增)
viewLog() {
uni.showModal({
title: '操作日志',
content: this.logList.map(item => `${item.time}:${item.content}`).join('\n'),
showCancel: false,
confirmText: '关闭'
});
},
// 上报日志(高阶新增)
reportLog() {
if(this.isLogReport && this.logList.length > 0) {
uniLog.reportBatch(this.logList);
this.addLog('日志批量上报成功');
}
},
// 设置日志上报(高阶新增)
setLogReport() {
if(this.isLogReport) {
this.addLog('已开启日志上报');
} else {
this.addLog('已关闭日志上报');
}
},
// 1、初始化蓝牙适配器(复用进阶版,新增日志记录)
initBlue(connectHistory = false) {
this.addLog('开始初始化蓝牙适配器');
uni.openBluetoothAdapter({
success: (res) => {
uni.showToast({title: '蓝牙初始化成功'});
this.addLog('蓝牙初始化成功');
this.listenBlueState(); // 监听蓝牙状态变化
this.listenDisconnect(); // 监听断连事件
// 自动连接历史设备
if(connectHistory && this.historyDevice.length > 0) {
let lastDevice = this.historyDevice[0];
this.connectDevice(lastDevice, true);
this.addLog(`自动连接历史设备:${lastDevice.name}`);
}
},
fail: (err) => {
this.addLog(`蓝牙初始化失败:${JSON.stringify(err)}`);
uni.showModal({
title: '提示',
content: '蓝牙未开启,请前往设置开启蓝牙',
success: (res) => {
if(res.confirm) {
uni.openSetting();
}
}
})
}
})
},
// 2、监听蓝牙开关状态(复用进阶版,新增日志)
listenBlueState() {
uni.onBluetoothAdapterStateChange((res) => {
if(!res.available) {
uni.showToast({title: '蓝牙已关闭',icon:'none'});
this.addLog('蓝牙已关闭,重置设备状态');
this.deviceList = [];
this.connectedDeviceId = '';
this.connectedDeviceName = '';
this.connectedDeviceBattery = '';
this.connectedDeviceFirmware = '';
clearTimeout(this.reconnectTimer);
this.reconnectCount = 0;
} else {
this.addLog('蓝牙已开启,自动搜索设备');
this.searchBlue();
}
})
},
// 3、监听断连事件(复用进阶版,新增日志、OTA中断处理)
listenDisconnect() {
uni.onBLEConnectionStateChange((res) => {
if(!res.connected && this.connectedDeviceId) {
let disconnectDevice = this.deviceList.find(item => item.deviceId === this.connectedDeviceId) || {
deviceId: this.connectedDeviceId,
name: this.connectedDeviceName
};
// 若正在升级,中断升级
if(this.isOtaUpgrading) {
this.cancelOtaUpgrade();
this.addLog(`设备${disconnectDevice.name}断连,中断固件升级`);
}
uni.showToast({title: '设备已断开,正在尝试重连...',icon:'none',duration: 1500});
this.addLog(`设备${disconnectDevice.name}断连,开始自动重连`);
this.connectedDeviceId = '';
this.connectedDeviceName = '';
this.connectedDeviceBattery = '';
this.connectedDeviceFirmware = '';
if(this.autoReconnect && this.reconnectCount< 5) {
this.reconnectDevice(disconnectDevice);
}
}
})
},
// 4、自动重连方法(复用进阶版,新增日志)
reconnectDevice(device) {
this.reconnectCount++;
this.addLog(`正在进行第${this.reconnectCount}次重连,设备:${device.name}`);
this.reconnectTimer = setTimeout(() => {
uni.createBLEConnection({
deviceId: device.deviceId,
success: (res) => {
this.connectedDeviceId = device.deviceId;
this.connectedDeviceName = device.name || '未知设备';
this.getBLEDeviceServices();
this.reconnectCount = 0;
uni.showToast({title: '重连成功'});
this.addLog(`设备${device.name}重连成功`);
this.updateHistoryDevice(device);
},
fail: () => {
if(this.reconnectCount< 5) {
this.reconnectDevice(device);
} else {
uni.showToast({title: '重连失败,请手动连接',icon:'error'});
this.addLog(`设备${device.name}重连失败,已达最大次数`);
this.reconnectCount = 0;
}
}
})
}, 1000);
},
// 5、搜索蓝牙设备(进阶版优化,新增广播包解析)
searchBlue() {
if(this.isSearching) return;
this.deviceList = [];
this.isSearching = true;
this.addLog('开始搜索蓝牙设备,开启广播包解析');
uni.startBluetoothDevicesDiscovery({
allowDuplicatesKey: false,
success: () => {
uni.showToast({title: '开始搜索设备'});
// 监听设备发现,新增广播包解析
uni.onBluetoothDeviceFound((res) => {
let device = res.devices[0];
// 过滤无效设备(保留信号稳定设备)
if(device.name && device.name !== '' && device.RSSI > -80) {
let hasExist = this.deviceList.some(item => item.deviceId === device.deviceId);
if(!hasExist) {
// 解析广播包,提取设备信息(核心高阶功能)
let broadcastData = this.parseBroadcastData(device.advertisData);
// 组装设备信息,包含广播包解析结果
const deviceInfo = {
...device,
rssi: device.RSSI,
battery: broadcastData.battery,
firmwareVersion: broadcastData.firmwareVersion,
deviceModel: broadcastData.deviceModel
};
this.deviceList.push(deviceInfo);
this.addLog(`发现设备:${device.name},广播包解析:${JSON.stringify(broadcastData)}`);
}
}
})
}
})
// 工业级优化:2秒自动停止搜索,减少耗电(比进阶版更严格)
setTimeout(() => {
this.stopSearch();
}, 2000)
},
// 6、停止搜索(复用进阶版,新增日志)
stopSearch() {
uni.stopBluetoothDevicesDiscovery();
this.isSearching = false;
uni.hideToast();
this.addLog(`搜索停止,共发现${this.deviceList.length}台有效设备`);
},
// 7、连接设备(进阶版优化,新增固件升级特征值获取)
connectDevice(item, isSilent = false) {
if(this.connectedDeviceId && this.connectedDeviceId !== item.deviceId) {
this.closeBlue(false);
}
if(!isSilent) {
uni.showLoading({title:'正在连接...'});
}
this.stopSearch();
this.addLog(`开始连接设备:${item.name},设备ID:${item.deviceId}`);
uni.createBLEConnection({
deviceId: item.deviceId,
success: (res) => {
this.connectedDeviceId = item.deviceId;
this.connectedDeviceName = item.name || '未知设备';
this.connectedDeviceBattery = item.battery || '未知';
this.connectedDeviceFirmware = item.firmwareVersion || '未知';
this.getBLEDeviceServices(true); // 传入true,获取固件升级特征值
this.updateHistoryDevice(item);
if(!isSilent) {
uni.showToast({title:'连接成功'});
}
this.addLog(`设备${item.name}连接成功,当前固件版本:${this.connectedDeviceFirmware}`);
},
fail: () => {
if(!isSilent) {
uni.showToast({title:'连接失败',icon:'error'});
}
this.addLog(`设备${item.name}连接失败`);
},
complete: () => {
if(!isSilent) {
uni.hideLoading();
}
}
})
},
// 8、更新历史连接设备(复用进阶版)
updateHistoryDevice(device) {
this.historyDevice = this.historyDevice.filter(item => item.deviceId !== device.deviceId);
this.historyDevice.unshift({
deviceId: device.deviceId,
name: device.name || '未知设备',
firmwareVersion: device.firmwareVersion || '未知'
});
if(this.historyDevice.length > 3) {
this.historyDevice.pop();
}
uni.setStorageSync('historyDevice', this.historyDevice);
this.addLog(`更新历史设备列表,当前历史设备:${this.historyDevice.length}台`);
},
// 9、获取设备服务、特征值(进阶版优化,新增固件升级特征值获取)
getBLEDeviceServices(needOta = false) {
this.addLog(`开始获取设备${this.connectedDeviceName}的服务和特征值`);
uni.getBLEDeviceServices({
deviceId: this.connectedDeviceId,
success: (res) => {
let service = res.services.find(item => item.uuid.includes('FFF0')); // 替换为硬件UUID
if(service) {
this.serviceId = service.uuid;
this.getBLEDeviceCharacteristics(needOta);
} else {
this.addLog(`未找到目标服务UUID,设备:${this.connectedDeviceName}`);
}
},
fail: (err) => {
this.addLog(`获取设备服务失败:${JSON.stringify(err)}`);
}
})
},
getBLEDeviceCharacteristics(needOta = false) {
uni.getBLEDeviceCharacteristics({
deviceId: this.connectedDeviceId,
serviceId: this.serviceId,
success: (res) => {
// 获取通信特征值
let char = res.characteristics.find(item => item.uuid === 'FFF1'); // 替换为硬件UUID
if(char) {
this.characteristicId = char.uuid;
this.notifyBLE();
this.addLog(`获取通信特征值成功,UUID:${this.characteristicId}`);
}
// 获取固件升级特征值(高阶新增)
if(needOta) {
let otaChar = res.characteristics.find(item => item.uuid === 'FFF2'); // 替换为硬件OTA UUID
if(otaChar) {
this.otaCharacteristicId = otaChar.uuid;
this.addLog(`获取固件升级特征值成功,UUID:${this.otaCharacteristicId}`);
} else {
this.addLog(`未找到固件升级特征值,设备:${this.connectedDeviceName}`);
}
}
},
fail: (err) => {
this.addLog(`获取设备特征值失败:${JSON.stringify(err)}`);
}
})
},
// 10、开启特征值通知(进阶版优化,新增协议解析)
notifyBLE() {
uni.notifyBLECharacteristicValueChange({
deviceId: this.connectedDeviceId,
serviceId: this.serviceId,
characteristicId: this.characteristicId,
state: true,
success: () => {
this.addLog(`开启特征值通知成功,开始监听数据`);
uni.onBLECharacteristicValueChange((res) => {
let data = this.ab2hex(res.value);
// 协议解析(高阶新增,根据当前选中协议解析数据)
data = this.parseProtocolData(data);
// 加密数据解密
if(data.startsWith('ENCRYPT_')) {
this.recvData = this.decryptData(data.replace('ENCRYPT_', ''));
this.isRecvEncrypt = true;
} else {
this.recvData = data;
this.isRecvEncrypt = false;
}
this.addLog(`接收数据:${this.recvData}(${this.isRecvEncrypt ? '已解密' : '未加密'})`);
})
},
fail: (err) => {
this.addLog(`开启特征值通知失败:${JSON.stringify(err)}`);
}
})
},
// 11、发送蓝牙指令(进阶版优化,新增协议适配)
sendMsg() {
if(!this.sendData) {
uni.showToast({title:'请输入指令',icon:'none'});
return;
}
let sendContent = this.sendData;
// 协议适配(高阶新增,根据当前选中协议格式化指令)
sendContent = this.formatProtocolData(sendContent);
// 加密处理
if(this.isEncrypt) {
sendContent = 'ENCRYPT_' + this.encryptData(sendContent);
}
let buffer = this.hex2ab(sendContent);
this.addLog(`发送指令:${sendContent}(${this.isEncrypt ? '加密' : '未加密'},${this.currentProtocol.name})`);
uni.writeBLECharacteristicValue({
deviceId: this.connectedDeviceId,
serviceId: this.serviceId,
characteristicId: this.characteristicId,
value: buffer,
success: () => {
uni.showToast({title:'发送成功'});
this.addLog(`指令发送成功`);
},
fail: (err) => {
uni.showToast({title:'发送失败',icon:'error'});
this.addLog(`指令发送失败:${JSON.stringify(err)}`);
}
})
},
// 12、断开蓝牙连接(进阶版优化,新增OTA中断处理)
closeBlue(showToast = true) {
if(this.connectedDeviceId) {
uni.closeBLEConnection({
deviceId: this.connectedDeviceId
})
}
// 中断固件升级(若正在升级)
if(this.isOtaUpgrading) {
this.cancelOtaUpgrade();
}
clearTimeout(this.reconnectTimer);
this.connectedDeviceId = '';
this.connectedDeviceName = '';
this.connectedDeviceBattery = '';
this.connectedDeviceFirmware = '';
this.reconnectCount = 0;
if(showToast) {
uni.showToast({title:'已断开连接'});
}
this.addLog(`断开蓝牙连接,重置设备状态`);
},
// 13、清空设备列表(复用进阶版,新增日志)
clearDevice() {
this.deviceList = [];
this.historyDevice = [];
uni.removeStorageSync('historyDevice');
uni.showToast({title:'设备列表已清空'});
this.addLog(`清空设备列表和历史设备记录`);
},
// 14、设置自动重连(复用进阶版,新增日志)
setAutoReconnect() {
if(!this.autoReconnect) {
clearTimeout(this.reconnectTimer);
this.reconnectCount = 0;
uni.showToast({title:'已关闭自动重连'});
this.addLog(`已关闭自动重连`);
} else {
uni.showToast({title:'已开启自动重连'});
this.addLog(`已开启自动重连`);
}
},
// 工具方法:ArrayBuffer与十六进制转换(复用基础版)
ab2hex(buffer) {
let hexArr = Array.prototype.map.call(
new Uint8Array(buffer),
function(bit) {
return ('00' + bit.toString(16)).slice(-2)
}
)
return hexArr.join('');
},
hex2ab(hex) {
let buffer = new ArrayBuffer(hex.length / 2);
let dataView = new DataView(buffer);
for (let i = 0; i < hex.length; i += 2) {
dataView.setUint8(i / 2, parseInt(hex.substr(i, 2), 16));
}
return buffer;
},
// 工具方法:数据加密/解密(复用进阶版)
encryptData(data) {
let key = this.encryptKey;
let encrypted = '';
for(let i = 0; i < data.length; i++) {
let keyIndex = i % key.length;
encrypted += String.fromCharCode(data.charCodeAt(i) ^ key.charCodeAt(keyIndex));
}
return this.strToHex(encrypted);
},
decryptData(data) {
let key = this.encryptKey;
let decryptedStr = this.hexToStr(data);
let decrypted = '';
for(let i = 0; i < decryptedStr.length; i++) {
let keyIndex = i % key.length;
decrypted += String.fromCharCode(decryptedStr.charCodeAt(i) ^ key.charCodeAt(keyIndex));
}
return decrypted;
},
strToHex(str) {
let hex = '';
for(let i = 0; i < str.length; i++) {
hex += str.charCodeAt(i).toString(16).padStart(2, '0');
}
return hex;
},
hexToStr(hex) {
let str = '';
for(let i = 0; i < hex.length; i += 2) {
str += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
}
return str;
},
// 高阶工具方法1:广播包解析(核心高阶功能)
parseBroadcastData(advertisData) {
let broadcastObj = {
battery: '未知',
firmwareVersion: '未知',
deviceModel: '未知'
};
if(!advertisData) return broadcastObj;
// 将ArrayBuffer转换为十六进制字符串
let hexData = this.ab2hex(advertisData);
// 解析逻辑(根据硬件广播包协议调整,此处为通用示例)
// 示例:第1-2字节为设备型号,第3-4字节为固件版本,第5字节为电量
if(hexData.length >= 10) {
// 解析设备型号(十六进制转字符串)
broadcastObj.deviceModel = this.hexToStr(hexData.slice(0, 4));
// 解析固件版本(十六进制转十进制,格式:x.x.x)
let firmwareHex = hexData.slice(4, 8);
let v1 = parseInt(firmwareHex.slice(0, 2), 16);
let v2 = parseInt(firmwareHex.slice(2, 4), 16);
broadcastObj.firmwareVersion = `${v1}.${v2}.0`;
// 解析电量(十六进制转十进制,0-100)
broadcastObj.battery = parseInt(hexData.slice(8, 10), 16);
}
return broadcastObj;
},
// 高阶工具方法2:自定义协议解析(高阶新增)
parseProtocolData(data) {
// 根据当前选中的协议,解析数据(示例逻辑,需根据实际协议调整)
switch(this.currentProtocol.code) {
case 'industrial':
// 工业传感器协议:前4字节为设备ID,后4字节为数值
if(data.length >= 8) {
let deviceId = data.slice(0, 4);
let value = parseInt(data.slice(4, 8), 16) / 10;
return `工业传感器|设备ID:${deviceId}|数值:${value}`;
}
break;
case 'wearable':
// 智能穿戴协议:前2字节为状态,后2字节为心率
if(data.length >= 4) {
let status = parseInt(data.slice(0, 2), 16) === 1 ? '正常' : '异常';
let heartRate = parseInt(data.slice(2, 4), 16);
return `智能穿戴|状态:${status}|心率:${heartRate}次/分`;
}
break;
default:
// 标准BLE协议,直接返回原始数据
return data;
}
return data;
},
// 高阶工具方法3:自定义协议格式化(高阶新增)
formatProtocolData(data) {
// 根据当前选中的协议,格式化发送指令(示例逻辑,需根据实际协议调整)
switch(this.currentProtocol.code) {
case 'industrial':
// 工业传感器协议:指令前缀+数据(补全8字节)
return 'AA' + data.padEnd(8, '0').slice(0, 8);
case 'wearable':
// 智能穿戴协议:指令前缀+数据(补全4字节)
return 'BB' + data.padEnd(4, '0').slice(0, 4);
default:
// 标准BLE协议,直接返回原始数据
return data;
}
},
// 高阶功能1:选择固件文件(固件升级前置操作)
selectFirmware() {
uni.chooseFile({
count: 1,
type: 'file',
extension: ['.bin', '.hex'], // 固件文件格式(根据硬件调整)
success: (res) => {
this.firmwareFile = res.tempFiles[0];
uni.showToast({title: `选中固件:${this.firmwareFile.name}`});
this.addLog(`选中固件文件:${this.firmwareFile.name},大小:${this.firmwareFile.size}字节`);
},
fail: (err) => {
this.addLog(`选择固件文件失败:${JSON.stringify(err)}`);
}
})
},
// 高阶功能2:开始固件升级(OTA,核心高阶功能)
startOtaUpgrade() {
if(!this.connectedDeviceId) {
uni.showToast({title: '请先连接设备', icon: 'none'});
return;
}
if(!this.otaCharacteristicId) {
uni.showToast({title: '设备不支持固件升级', icon: 'none'});
this.addLog(`设备${this.connectedDeviceName}不支持固件升级,未找到OTA特征值`);
return;
}
if(!this.firmwareFile) {
uni.showToast({title: '请先选择固件文件', icon: 'none'});
return;
}
// 固件版本校验(避免降级)
if(this.connectedDeviceFirmware >= this.latestFirmwareVersion) {
uni.showToast({title: '当前已是最新固件', icon: 'none'});
this.addLog(`设备${this.connectedDeviceName}当前固件版本${this.connectedDeviceFirmware},已是最新`);
return;
}
this.isOtaUpgrading = true;
this.otaProgress = 0;
this.addLog(`开始给设备${this.connectedDeviceName}升级固件,当前版本:${this.connectedDeviceFirmware},目标版本:${this.latestFirmwareVersion}`);
// 读取固件文件(转base64,再转ArrayBuffer)
uni.getFileSystemManager().readFile({
filePath: this.firmwareFile.path,
encoding: 'base64',
success: (res) => {
let base64Data = res.data;
let buffer = base64.toByteArray(base64Data); // 转ArrayBuffer
let totalLength = buffer.length;
let blockSize = 20; // 分块大小(根据硬件调整,避免通信拥堵)
let currentIndex = 0;
// 分块发送固件数据
this.otaTimer = setInterval(() => {
// 计算当前分块数据
let endIndex = currentIndex + blockSize;
if(endIndex > totalLength) {
endIndex = totalLength;
}
let blockData = buffer.slice(currentIndex, endIndex);
// 发送分块数据
uni.writeBLECharacteristicValue({
deviceId: this.connectedDeviceId,
serviceId: this.serviceId,
characteristicId: this.otaCharacteristicId,
value: blockData,
success: () => {
currentIndex = endIndex;
// 更新升级进度
this.otaProgress = Math.floor((currentIndex / totalLength) * 100);
this.addLog(`固件升级进度:${this.otaProgress}%,已发送${currentIndex}/${totalLength}字节`);
// 升级完成
if(currentIndex >= totalLength) {
clearInterval(this.otaTimer);
this.isOtaUpgrading = false;
this.otaProgress = 100;
this.connectedDeviceFirmware = this.latestFirmwareVersion;
uni.showToast({title: '固件升级成功', icon: 'success'});
this.addLog(`设备${this.connectedDeviceName}固件升级成功,当前版本:${this.latestFirmwareVersion}`);
}
},
fail: (err) => {
clearInterval(this.otaTimer);
this.isOtaUpgrading = false;
uni.showToast({title: '固件升级失败', icon: 'error'});
this.addLog(`固件升级失败:${JSON.stringify(err)}`);
}
})
}, 100); // 分块发送间隔(根据硬件调整)
},
fail: (err) => {
this.isOtaUpgrading = false;
uni.showToast({title: '读取固件文件失败', icon: 'error'});
this.addLog(`读取固件文件失败:${JSON.stringify(err)}`);
}
})
},
// 高阶功能3:取消固件升级(高阶新增)
cancelOtaUpgrade() {
clearInterval(this.otaTimer);
this.isOtaUpgrading = false;
this.otaProgress = 0;
uni.showToast({title: '已取消固件升级', icon: 'none'});
this.addLog(`取消设备${this.connectedDeviceName}固件升级`);
},
// 高阶功能4:切换自定义协议(高阶新增)
changeProtocol(e) {
let index = e.detail.value;
this.currentProtocol = this.protocolList[index];
this.addLog(`切换通信协议为:${this.currentProtocol.name}`);
}
}
}
</script>
<style scoped>
/* 复用基础与进阶版样式,新增高阶功能样式 */
.blue-container {
padding: 20rpx;
}
.btn-box {
display: flex;
gap: 20rpx;
margin-bottom: 30rpx;
flex-wrap: wrap;
}
.ota-progress {
padding: 15rpx;
background: #fff7e6;
border-radius: 10rpx;
margin-bottom: 30rpx;
}
.ota-progress .title {
display: block;
margin-bottom: 10rpx;
color: #faad14;
font-weight: bold;
}
.connected-device {
padding: 15rpx;
background: #e6f7ff;
border-radius: 10rpx;
margin-bottom: 30rpx;
display: flex;
align-items: center;
gap: 20rpx;
flex-wrap: wrap;
}
.title {
font-weight: bold;
color: #333;
}
.device-list {
height: 400rpx;
border: 1rpx solid #eee;
border-radius: 10rpx;
padding: 10rpx;
margin-bottom: 30rpx;
}
.device-item {
padding: 15rpx;
border-bottom: 1rpx solid #f5f5f5;
position: relative;
}
.device-item.connected {
background: #f0f9ff;
}
.status {
position: absolute;
right: 15rpx;
top: 15rpx;
color: #1890ff;
font-size: 24rpx;
}
.ota-tip {
position: absolute;
right: 15rpx;
bottom: 15rpx;
color: #faad14;
font-size: 22rpx;
}
.send-box {
display: flex;
gap: 20rpx;
align-items: center;
margin-bottom: 30rpx;
flex-wrap: wrap;
}
.picker-view {
border: 1rpx solid #eee;
padding: 15rpx;
border-radius: 8rpx;
width: 200rpx;
}
.send-box input {
flex: 1;
border: 1rpx solid #eee;
padding: 15rpx;
border-radius: 8rpx;
}
.recv-box {
padding: 20rpx;
background: #f8f8f8;
border-radius: 10rpx;
margin-bottom: 30rpx;
}
.encrypt-tag {
color: #722ed1;
margin-left: 10rpx;
}
.protocol-tag {
color: #13c2c2;
margin-left: 10rpx;
}
.setting-box {
display: flex;
gap: 40rpx;
flex-wrap: wrap;
}
.reconnect-set, .log-set {
display: flex;
align-items: center;
gap: 20rpx;
color: #333;
}
</style>
2.说明
- 前序代码复用:完全复用前两篇的基础与进阶功能(蓝牙初始化、断连重连、多设备管理、数据加密等),仅新增高阶模块,保持代码连贯性与可复用性
- UUID兼容:通信服务/特征值UUID与前两篇一致,新增固件升级特征值UUID(需替换为硬件厂商提供的OTA UUID)
- 高阶功能新增:重点新增广播包解析、固件升级(OTA)、自定义协议适配、日志上报四大核心模块,均标注“高阶新增”注释,逻辑清晰可追溯
- 工业级优化:缩短搜索时间、优化分块传输、增加日志监控,适配工业场景下的低功耗、高稳定性需求
五.详解
1. 广播包解析
1.1.核心原理
BLE 设备广播包最大 31 字节,advertisData 是原始ArrayBuffer,先转十六进制字符串,再按硬件约定固定字节偏移截取解析电量、固件版本、设备型号。
1.2.适配要点
- 不同厂商广播包字段位置不一样,只需修改 hexData.slice(起始,结束) 偏移量即可适配;
- RSSI 信号强度小于 -80dBm 属于弱信号,直接过滤,避免展示无效设备;
- 无需建立连接就能拿到设备基础信息,大幅提升用户体验。
1.3.业务落地用法
首页列表直接展示电量、固件版本、是否有更新,不用点进去连接再查看。
2.固件 OTA 升级
1.1.分块传输逻辑
固件 bin/hex 文件较大,不能一次性写入蓝牙特征值,采用固定块大小 + 定时分片发送,防止蓝牙缓冲区溢出。
1.2.关键安全机制
- 版本校验:禁止降级刷机;
- 断连自动终止升级,防止设备变砖;
- 实时进度百分比展示,友好交互。
1.3.参数可调项
- blockSize:单块字节数,一般设 20~60;
- 发送间隔 100ms:根据硬件吞吐能力微调;
- OTA 专属特征值 UUID 必须由硬件端提供。
3.多自定义通信协议适配
1.1.设计思路
做协议枚举配置,收到 / 发送数据时按选中协议自动格式化、自动解析,不用改业务逻辑。
1.2.扩展方式
新增硬件协议只需在 protocolList 加一条,再补 parseProtocolData、formatProtocolData 的分支逻辑即可,低侵入扩展。
1.3.适用场景
一个 APP 对接多款不同协议蓝牙设备,不用写多套页面,一套代码兼容。
4.工业级稳定性优化
1.1.搜索优化
限定 2 秒自动停止搜索,避免常驻扫描耗电、占用蓝牙资源;
1.2.自动重连机制
最多重连 5 次、间隔 1 秒,避免无限重连卡死;
1.3.历史设备缓存
本地存储最近 3 台设备,进入页面自动回连常用设备;
1.4.多设备兼容
安卓 /iOS 权限、蓝牙开关监听统一处理,适配小程序 + APP 双端。
六.常见问题
Q1:能搜到设备但解析不出广播包电量 / 版本
- 原因:硬件广播包字节偏移和代码里 slice 位置不匹配;
- 解决:打印完整 hexData,对照硬件协议文档重新设置截取起止下标。
Q2:固件升级一直 0%、发送失败
1.OTA 特征值 UUID 填错,不是普通通信 UUID;
2.分块blockSize太大,硬件缓冲区扛不住,改小到 20~30;
3.升级过程手机离设备太远、信号弱,贴近再升级。
Q3:iOS 能搜索到设备,安卓搜不到
- 安卓需开启定位权限、蓝牙权限,后台定位也要允许;
- 关闭手机省电模式,部分手机省电会禁用蓝牙扫描。
Q4:蓝牙后台闪退、页面退出后自动断连
- 解决:onUnload 里已做断开连接、清空定时器,不要额外再开全局常驻;
- 小程序本身限制后台蓝牙常驻,属于平台机制。
Q5:收发数据乱码、解析不对
- 检查:ArrayBuffer ↔ 十六进制转换方法是否正确;
- 加密开启后必须配对同款解密逻辑,密钥前后一致;
- 协议格式和硬件端约定的头尾、长度不一致。
Q6:自动重连太频繁、界面卡顿
- 限制最大重连次数 5 次,超时不再重试;
- 重连加 1s 延时,避免瞬间高频请求。
Q7:小程序开发者工具能搜到,真机搜不到
- 开发者工具蓝牙模拟有 bug,必须以真机为准;
- 真机重启蓝牙、重启手机、重装小程序测试。
总结
本文作为 UniApp BLE 蓝牙开发高阶收官篇,在前两版基础通信、自动重连、数据加密、历史设备管理的能力之上,重点补齐了商业和工业项目落地最刚需的四大核心能力:蓝牙广播包免连接解析、BLE 固件 OTA 远程升级、多套自定义通信协议兼容、全流程日志异常监控。
更多推荐

所有评论(0)