系列文章目录

第一章 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 加一条,再补 parseProtocolDataformatProtocolData 的分支逻辑即可,低侵入扩展。

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 远程升级、多套自定义通信协议兼容、全流程日志异常监控。

更多推荐