ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

uniapp蓝牙串口通信实战:从硬件接线到App控制物联网设备

uniapp蓝牙串口通信实战:从硬件接线到App控制物联网设备 前阵子接了个物联网的小项目设备端是一块STM32F103C8T6板子上就露了一组TTL串口需求却有点绕要用uniapp做的App通过手机控制设备上的继电器开关还要能实时看到传感器数据。最开始我想得挺简单——串口通信嘛直接打开串口读写不就行了结果在uniapp里翻了一圈发现根本没有直接的串口API。折腾了一周踩了一堆乱码、连接不稳定、iOS兼容性的坑最后通过蓝牙串口透传模块把设备端UART数据接到BLE再用uniapp蓝牙接口做读写才把整条链路跑通。这篇文章就把从硬件接线到App代码的完整过程摊开讲适合正在做物联网毕设、智能硬件控制App、或者第一次用uniapp接串口设备的开发者参考。代码我会直接贴出来配置项也备注明白你照着改就能用。1. 为什么uniapp没有现成的串口API三条可行技术路线的对比先解决一个很多人刚接触时会懵的问题uniapp到底能不能直接操作串口答案是“不能直接操作”。App跑在手机系统隔离的沙箱环境里uniapp作为跨端框架最终编译成Android/iOS原生App或小程序它对外暴露的JS API都是偏上层的不可能把系统底层串口驱动直接交给你。而设备端的串口UART/RS232/RS485是硬件外设接口和手机之间根本没有物理总线除非走蓝牙、WiFi、USB这类无线或有线通道。1.1 直连串口先认清App的运行环境和权限边界串口通信的本质是单片机通过Tx/Rx两根线按约定波特率收发二进制数据。手机App想跟单片机对话必须先把数据从单片机串口搬到手机系统支持的信道上。这个过程有点像两个人语言不通中间必须站一个翻译。App是“说蓝牙/网络协议的一方”单片机是“说串口协议的一方”中间那个翻译就是透传模块或USB转串口芯片。只有把数据传输路径想清楚后面的代码才不会乱。1.2 方案一BLE蓝牙透传本文采用BLE蓝牙透传是最常见的物联网设备控制方案。硬件上用一个BLE转串口模块比如HC-08、HM-10、JDY-31连接单片机串口模块内部会把串口收到的数据打包成BLE的GATT特征值手机App通过读写这个特征值就能间接实现和单片机串口的数据交互。这个方案最大的优势是Android和iOS都支持不需要OTG线开发起来也不用碰原生代码缺点是BLE的有效传输速率不高一般稳定在几KB/s级别不适合传音频、大文件但控制指令和传感器数据完全够用。我在项目里用的就是HC-08十几块钱稳定可靠。1.3 方案二USB串口原生插件仅Android如果你只想支持Android设备还可以用USB串口方案。手机通过OTG线连接CH340、CP2102、FT232这类USB转串口模块模块另一端接单片机串口然后Android App通过UsbSerial库直接读写USB设备。uniapp里需要把这个能力封装成原生插件在JS层调用。这个方案传输稳定、速度快但有两个致命问题iOS不支持USB Host直连基本只能Android用另外需要写Java/Kotlin原生插件开发成本高。除非你有高速日志传输、固件升级等特殊需求否则我不建议一上来就选它。1.4 方案三Web Serial API仅H5端Chrome如果uniapp项目只是编译到H5在Chrome、Edge浏览器里其实有个Web Serial API网页可以绕过App沙箱直接和本机串口通信。但问题也很明显微信小程序、App端都不支持而且用户在手机上打开H5时也连不了物理串口。这个方案我只能拿来当调试辅助不扛正式项目。1.5 三条路线对比与最终选型方案跨端支持传输速率开发成本适用场景BLE蓝牙透传Android/iOS/小程序低适合小数据包低纯JS物联网控制、传感器采集USB串口原生插件仅Android高稳定高需写原生插件高速传输、串口调试设备Web Serial API仅H5 Chrome取决于系统驱动低但平台受限H5调试工具不推荐正式业务我最终选BLE透传还是因为项目里iOS和Android都要用而且控制指令就几十个字节传感器数据也是秒级上报BLE完全扛得住。这就是标题里说的“uniapp串口通信”的真相App端走蓝牙物理串口由设备端蓝牙模块代理。理解了这一点后面的代码就是蓝牙API的操作了。2. 硬件准备与从串口到蓝牙的数据链路搭建软件写代码之前的硬件链路如果没搭好后面全是坑。尤其是串口参数不一致导致的乱码问题十有八九是硬件配置不对。我建议你把整条链路拆成三段来看单片机串口、蓝牙透传模块、手机App。先保证前两段通了再写App代码。2.1 串口参数决定一切串口通信必须提前约定好五要素波特率、数据位、停止位、校验位、流控。最常见的配置是9600 8N1也就是波特率9600、8位数据位、无校验、1位停止位、无流控。如果你的设备程序里用的是115200那蓝牙模块也必须配成115200不然收到的数据一定是乱码。蓝牙透传模块出厂默认波特率不一定相同像HC-08默认一般是9600HM-10也是9600JDY-31可能是9600或115200。所以拿到模块后最好先通过AT指令把模块的串口参数改成和设备端一致。HC-08进入AT模式的方法是把模块的KEY/EN引脚拉高再上电然后通过USB转TTL在电脑串口助手里发送ATBAUD88代表115200之类的指令具体指令集看模块手册。改完以后再切回透传模式模块才会按新参数和单片机通信。2.2 蓝牙透传模块选型与接线透传模块我推荐HC-08和HM-10两者都是BLE 4.0兼容性好资料多。接线非常简单就四根线但有一个细节必须注意模块和单片机必须共地否则信号电平参考点不同会出现丢字节甚至完全收不到数据的情况。单片机/串口设备蓝牙透传模块说明TXRX单片机发送到模块接收RXTX模块发送到单片机接收VCCVCC接3.3V或5V以模块规格为准GNDGND必须共地另外要注意TTL电平。STM32F103的串口是3.3V TTL电平HC-08也是3.3V逻辑可以直接接但如果你用的是5V单片机、5V供电的模块就要确认模块是否支持5V逻辑输入或者加电平转换。接反了或者电压超过模块上限轻则通信异常重则烧模块。2.3 设备端代码设计思路设备端代码其实不难核心就两件事从串口收到App发来的指令后执行动作把传感器数据通过串口发出去。我用的是ESP32 Arduino框架因为测试起来最方便。STM32F103用HAL库也是同样逻辑串口接收中断发送函数。我在测试板里写了一个最小逻辑App发送LED_ON设备点亮板载LED并回传OKApp发送LED_OFF设备关闭LED并回传OK设备每2秒自动发送一次模拟传感器数据TEMP:26.5。这个逻辑够验证整个链路。后面第5章会给完整代码。2.4 先不用App用BLE调试助手验证链路写App之前强烈建议先用手机上的BLE调试工具nRF Connect、LightBlue等验证硬件链路。方法是设备上电打开调试助手扫描找到HC-08的广播名一般是HC-08或HHJC-...连接后查看Service找到UUID为FFE0的服务里面通常有一个FFE1特征值。向FFE1写入LED_ON如果设备端LED亮起、串口那边也收到了说明单片机串口和蓝牙模块之间的链路已经通了。然后再订阅FFE1的通知看设备每隔2秒发来的传感器数据是否正常。我用这招排查过很多问题。如果直接写App出了问题很难分清是App代码问题、蓝牙问题还是设备端串口问题先用第三方工具确认硬件链路后面App开发就是纯调蓝牙API了。3. uniapp蓝牙串口通信完整实现权限、扫描、连接、读写硬件链路通之后就可以开始写uniapp了。我用的是Vue 3风格的uniapp项目但核心蓝牙API在Vue 2和Vue 3里是一样的你直接复制代码到自己的项目里都能用。3.1 manifest.json配置Android和iOS的蓝牙权限很多人第一步就卡在权限配置上。uniapp的蓝牙权限不会自动加必须在manifest.json里手动声明。// manifest.json - App模块配置 - 蓝牙 app-plus: { distribute: { android: { permissions: [ android.permission.BLUETOOTH, android.permission.BLUETOOTH_ADMIN, android.permission.BLUETOOTH_SCAN, android.permission.BLUETOOTH_CONNECT, android.permission.ACCESS_FINE_LOCATION ] }, ios: { privacyDescription: { NSBluetoothAlwaysUsageDescription: 需要蓝牙连接物联网设备 } } } }Android 6到11蓝牙扫描必须要有定位权限否则扫描不到设备Android 12以上又新增了BLUETOOTH_SCAN和BLUETOOTH_CONNECT两个运行时权限。iOS如果没有NSBluetoothAlwaysUsageDescription调用蓝牙API会直接闪退。这块配置好打包后真机测试才不踩坑。3.2 初始化蓝牙适配器与扫描设备我把蓝牙操作封装成了一个工具类utils/ble.js这样页面代码会干净很多。先看初始化和扫描部分// utils/ble.js let isScanning false function initBluetooth() { return new Promise((resolve, reject) { uni.openBluetoothAdapter({ success: (res) { console.log(蓝牙适配器初始化成功, res) resolve(res) }, fail: (err) { console.error(初始化失败, err) reject(err) } }) }) } function startScan(onDeviceFound) { return new Promise((resolve, reject) { if (isScanning) return uni.startBluetoothDevicesDiscovery({ allowDuplicatesKey: false, success: () { isScanning true uni.onBluetoothDeviceFound((res) { const devices res.devices || [] devices.forEach((device) { if (device.name onDeviceFound) { onDeviceFound(device) } }) }) resolve() }, fail: (err) { console.error(扫描失败, err) reject(err) } }) }) } function stopScan() { if (!isScanning) return uni.stopBluetoothDevicesDiscovery({ complete: () { isScanning false } }) } module.exports { initBluetooth, startScan, stopScan }在页面里用户点击“开始扫描”后先检查蓝牙是否开启再扫描并把设备名和设备ID加到列表里。注意uni.onBluetoothDeviceFound是全局监听同一个页面重复调用可能会收到重复设备所以你需要在回调里用deviceId去重。3.3 连接设备、获取服务与特征值扫描到设备后点击列表项开始连接。连接成功不是终点还要获取服务和特征值才能知道往哪个通道读写数据。HC-08这类透传模块的固定套路是Service UUID为FFE0Characteristic UUID为FFE1。function connectDevice(deviceId) { return new Promise((resolve, reject) { uni.createBLEConnection({ deviceId, success: () { setTimeout(async () { try { const services await getServices(deviceId) const characteristic await getCharacteristics(deviceId, services) resolve(characteristic) } catch (e) { reject(e) } }, 300) }, fail: (err) reject(err) }) }) } function getServices(deviceId) { return new Promise((resolve, reject) { uni.getBLEDeviceServices({ deviceId, success: (res) { const service res.services.find((s) s.uuid.toUpperCase().includes(FFE0)) if (!service) { reject(new Error(未找到FFE0服务)) return } resolve(service.uuid) }, fail: (err) reject(err) }) }) } function getCharacteristics(deviceId, serviceId) { return new Promise((resolve, reject) { uni.getBLEDeviceCharacteristics({ deviceId, serviceId, success: (res) { const char res.characteristics.find((c) c.uuid.toUpperCase().includes(FFE1)) if (!char) { reject(new Error(未找到FFE1特征值)) return } resolve({ serviceId, characteristicId: char.uuid, properties: char.properties }) }, fail: (err) reject(err) }) }) }连接成功后稍微延时一下再获取服务和特征值是因为有些蓝牙模块在连接建立后还没准备好GATT服务立刻查询会返回空列表。这是我实测中经常遇到的情况加300毫秒延时很管用。3.4 开启notify监听设备上报数据设备主动上报数据比如每隔2秒发送传感器温度App需要订阅通知。这里要分两步先调用uni.notifyBLECharacteristicValueChange打开通知开关再通过uni.onBLECharacteristicValueChange监听数据回调。function enableNotify(deviceId, serviceId, characteristicId, onData) { return new Promise((resolve, reject) { uni.notifyBLECharacteristicValueChange({ deviceId, serviceId, characteristicId, state: true, success: () { uni.onBLECharacteristicValueChange((res) { const data ab2str(res.value) onData(data) }) resolve() }, fail: (err) reject(err) }) }) }蓝牙API返回的数据都是ArrayBuffer需要转成字符串才好解析。最简单的方法是手动按UTF-8字节解码不推荐直接用String.fromCharCode因为它处理多字节中文会出问题。我用了一个轻量工具函数function ab2str(buffer) { const bytes new Uint8Array(buffer) let str for (let i 0; i bytes.length; i) { str String.fromCharCode(bytes[i]) } try { return decodeURIComponent(escape(str)) } catch (e) { return str } }这个函数对纯ASCII字符串和中文UTF-8都能正常解码。如果你设备端只发ASCII字符直接String.fromCharCode就够了。3.5 发送控制指令writeBLECharacteristicValue实操发送指令和接收数据一样要把字符串转成ArrayBuffer再写入特征值。这里最容易忽略的是写入数据的长度限制。BLE 4.0默认MTU是23字节去掉协议头一次最多写20字节。如果你的控制指令超过20字节要么缩短指令要么调用uni.setBLEMTU增大MTUAndroid支持iOS有限制要么在应用层做分包。function str2ab(str) { const bytes new Uint8Array(str.length) for (let i 0; i str.length; i) { bytes[i] str.charCodeAt(i) } return bytes.buffer } function writeData(deviceId, serviceId, characteristicId, str) { return new Promise((resolve, reject) { uni.writeBLECharacteristicValue({ deviceId, serviceId, characteristicId, value: str2ab(str), success: () resolve(true), fail: (err) reject(err) }) }) }调用示例await writeData(deviceId, serviceId, characteristicId, LED_ON)建议在指令后面加上换行符\n比如LED_ON\n。很多设备端代码会用Serial.parseInt()或readStringUntil(\n)来解析有换行符做结束符会省很多事。3.6 断开连接与生命周期管理页面退出时如果不清理蓝牙监听和连接下次进来会发现“连接失败”或“设备已连接”的脏状态。我通常在页面onUnload里做清理function disconnect(deviceId) { return new Promise((resolve) { uni.closeBLEConnection({ deviceId, complete: () resolve(true) }) }) } function closeBluetooth() { uni.closeBluetoothAdapter({ complete: () {} }) }页面里onUnload() { if (this.deviceId) { disconnect(this.deviceId) } stopScan() closeBluetooth() }这里有个容易踩的坑uni.closeBluetoothAdapter会关闭整个蓝牙适配器如果有其他页面也在用蓝牙会被你“误伤”。更好的做法是页面离开时只断开连接保留适配器。如果你做的是单设备控制工具类App问题不大但如果是多页面蓝牙应用建议用一个全局单例管理蓝牙生命周期。4. 实战中最容易踩的五个坑从乱码、粘包到iOS兼容代码写完了真机一跑才发现问题比想象中多。我把这一周里踩得最狠的坑总结成五类每一个都对应一个典型的“排查链路”你照着查能省很多时间。4.1 串口乱码波特率和校验位不一致是头号原因现象App收到数据是Çĸü或者ÿ之类的乱码偶尔夹杂正常字符。根因通常不是App代码而是硬件链路串口参数不一致。排查顺序是先用USB转TTL直接接单片机串口在电脑串口助手里确认设备实际发出的波特率再看蓝牙透传模块的AT配置是否和设备一致。我遇到过一块HC-08被之前的人改成了115200而单片机还是9600结果串口助手显示乱码AT指令也发不进去。解决办法是重新进入AT模式把模块参数改回和设备一致。4.2 数据粘包和分包BLE MTU限制与自定义协议BLE一次只能写20字节优化后可能更大但设备端串口可能一次发几十个字节蓝牙协议会自动分成多包App收到时往往是一股脑连续触发了多次onBLECharacteristicValueChange如果你不做分包数据就会粘在一起。处理方式有两种一种简单粗暴约定设备端每次只发不超过20字节的短帧另一种是自定义帧协议在数据流里加帧头、帧尾、长度和校验。比如我用过的协议帧头: 0xAA 0x55 长度: 1字节表示数据域长度 命令: 1字节 数据: N字节 校验: 1字节前面所有字节累加取低8位App端维护一个接收缓冲区每次收到蓝牙数据先concat再按协议帧头去截取完整帧这样即使多帧粘在一起也能正确拆包。4.3 notify不触发特征值UUID和启用通知的坑明明调用了uni.notifyBLECharacteristicValueChange但onBLECharacteristicValueChange就是一次都不触发。可能的原因有三个第一特征值的UUID找错了HC-08是FFE1但有些模块的写特征值和通知特征值是分开的两个UUID你要分别找第二部分蓝牙模块需要先向0x2902这个CCCD描述符写入0x0001才能启用通知uniapp没提供直接写描述符的高层API有些原生封装里会处理纯JS方案可以用uni.writeBLECharacteristicValue往0x2902写数据试试第三notifyBLECharacteristicValueChange必须在getBLEDeviceCharacteristics成功之后再调用顺序反了会失败。4.4 App切后台/断开重连的处理iOS系统对后台蓝牙连接管得很严App切后台几分钟连接很可能自动断开再回来如果直接发指令会报10002之类的连接错误。正确做法是监听uni.onBLEConnectionStateChange在回调里更新界面的连接状态如果发现处于断开状态就自动发起重连。不要写成死循环建议最多重连3次每次间隔1秒重连失败就提示用户手动连。uni.onBLEConnectionStateChange((res) { if (!res.connected) { // 提示并触发重连逻辑 this.deviceConnected false } })4.5 打包上架iOS蓝牙权限描述和Android权限申明本地真机调试没问题一打包就出问题多出在权限申明不全。Android 12以后的手机如果你在manifest.json里没写BLUETOOTH_SCAN调用蓝牙扫描会静默失败iOS审核时如果隐私权限描述含糊可能会被拒。另外uniapp在云打包时Android的蓝牙权限要在“App模块配置”里勾选“Bluetooth”iOS要勾选“Access Bluetooth”并填用途描述。这些配置项在本地自定义基座和正式打包之间是独立的有时候自定义基座能跑正式包反而不行先检查这一步。5. 附完整代码一个可直接跑的uniapp串口调试器最后给你一套完整的最小实现包含uniapp端工具类和页面示例以及设备端ESP32 Arduino代码。这套代码我自己在Android和iOS真机上跑通过你按步骤改完设备Name和UUID就能用。5.1 代码结构说明项目结构如下pages/ble/ble.vue // BLE调试页面 utils/ble.js // 蓝牙操作工具类页面包含四个区块扫描按钮、设备列表、连接状态、收发日志。你不需要从零写整个页面核心逻辑复制到自己的控制页面里就行。5.2 utils/ble.js完整代码// utils/ble.js let isScanning false let deviceConnected false function initBluetooth() { return new Promise((resolve, reject) { uni.openBluetoothAdapter({ success: (res) resolve(res), fail: (err) { uni.showModal({ title: 提示, content: 请打开手机蓝牙后重试, showCancel: false }) reject(err) } }) }) } function startScan(onDeviceFound) { return new Promise((resolve, reject) { if (isScanning) return uni.startBluetoothDevicesDiscovery({ allowDuplicatesKey: false, success: () { isScanning true uni.onBluetoothDeviceFound((res) { ;(res.devices || []).forEach((device) { if (device.name onDeviceFound) { onDeviceFound(device) } }) }) resolve() }, fail: (err) reject(err) }) }) } function stopScan() { if (!isScanning) return uni.stopBluetoothDevicesDiscovery({ complete: () { isScanning false } }) } function connectDevice(deviceId) { return new Promise((resolve, reject) { uni.createBLEConnection({ deviceId, success: () { setTimeout(async () { try { const serviceId await getFFE0Service(deviceId) const characteristicId await getFFE1Characteristic(deviceId, serviceId) deviceConnected true resolve({ deviceId, serviceId, characteristicId }) } catch (e) { reject(e) } }, 300) }, fail: (err) reject(err) }) }) } function getFFE0Service(deviceId) { return new Promise((resolve, reject) { uni.getBLEDeviceServices({ deviceId, success: (res) { const service res.services.find((s) s.uuid.toUpperCase().indexOf(FFE0) -1) if (service) { resolve(service.uuid) } else { reject(new Error(未找到FFE0服务)) } }, fail: (err) reject(err) }) }) } function getFFE1Characteristic(deviceId, serviceId) { return new Promise((resolve, reject) { uni.getBLEDeviceCharacteristics({ deviceId, serviceId, success: (res) { const characteristic res.characteristics.find((c) c.uuid.toUpperCase().indexOf(FFE1) -1) if (characteristic) { resolve(characteristic.uuid) } else { reject(new Error(未找到FFE1特征值)) } }, fail: (err) reject(err) }) }) } function enableNotify(deviceId, serviceId, characteristicId, onData) { return new Promise((resolve, reject) { uni.notifyBLECharacteristicValueChange({ deviceId, serviceId, characteristicId, state: true, success: () { uni.onBLECharacteristicValueChange((res) { const str ab2str(res.value) onData(str) }) resolve() }, fail: (err) reject(err) }) }) } function writeData(deviceId, serviceId, characteristicId, str) { const buffer str2ab(str) return new Promise((resolve, reject) { uni.writeBLECharacteristicValue({ deviceId, serviceId, characteristicId, value: buffer, success: () resolve(true), fail: (err) reject(err) }) }) } function disconnect(deviceId) { return new Promise((resolve) { uni.closeBLEConnection({ deviceId, complete: () { deviceConnected false resolve(true) } }) }) } function ab2str(buffer) { const bytes new Uint8Array(buffer) let str for (let i 0; i bytes.length; i) { str String.fromCharCode(bytes[i]) } try { return decodeURIComponent(escape(str)) } catch (e) { return str } } function str2ab(str) { const bytes new Uint8Array(str.length) for (let i 0; i str.length; i) { bytes[i] str.charCodeAt(i) } return bytes.buffer } function closeBluetooth() { uni.closeBluetoothAdapter({ complete: () {} }) } module.exports { initBluetooth, startScan, stopScan, connectDevice, enableNotify, writeData, disconnect, closeBluetooth }5.3 页面调用示例在pages/ble/ble.vue的script里按顺序调用这些方法import ble from /utils/ble.js export default { data() { return { deviceList: [], deviceId: , serviceId: , characteristicId: , connected: false, log: } }, methods: { async scan() { this.deviceList [] await ble.initBluetooth() await ble.startScan((device) { const exists this.deviceList.some((d) d.deviceId device.deviceId) if (!exists) { this.deviceList.push({ deviceId: device.deviceId, name: device.name || 未知设备 }) } }) }, async onConnect(item) { try { uni.showLoading({ title: 连接中 }) const conn await ble.connectDevice(item.deviceId) this.deviceId conn.deviceId this.serviceId conn.serviceId this.characteristicId conn.characteristicId this.connected true await ble.enableNotify(this.deviceId, this.serviceId, this.characteristicId, (data) { this.log 收到: data \n }) uni.hideLoading() } catch (e) { uni.hideLoading() uni.showToast({ title: e.message, icon: none }) } }, async send(value) { if (!this.connected) { uni.showToast({ title: 未连接, icon: none }) return } await ble.writeData(this.deviceId, this.serviceId, this.characteristicId, value) this.log 发送: value \n } } }页面模板里加一个输入框和按钮按钮绑定send(LED_ON\n)即可。如果你要把value换成文本框内容记得在发送前加上换行符比如this.inputValue \n。5.4 设备端最小验证代码ESP32 Arduino设备端我推荐先用ESP32验证不需要额外串口芯片板载USB转串口就能同时实现程序烧录和串口调试。以下代码实现接收串口指令控制LED并定时发送模拟温度数据。// ESP32 Arduino 串口透传最小验证 #define LED_PIN 2 void setup() { Serial.begin(115200); // 串口波特率和蓝牙模块保持一致 pinMode(LED_PIN, OUTPUT); digitalWrite(LED_PIN, LOW); delay(100); Serial.println(READY); } void loop() { // 读取串口指令遇到换行符解析 if (Serial.available()) { String cmd Serial.readStringUntil(\n); cmd.trim(); if (cmd LED_ON) { digitalWrite(LED_PIN, HIGH); Serial.println(OK); } else if (cmd LED_OFF) { digitalWrite(LED_PIN, LOW); Serial.println(OK); } else { Serial.println(UNKNOWN); } } // 每2秒发送一次模拟传感器数据 static unsigned long lastSend 0; if (millis() - lastSend 2000) { lastSend millis(); float temp 20.0 random(0, 100) / 10.0; Serial.print(TEMP:); Serial.println(temp); } }如果你是STM32F103C8T6直接把这个逻辑翻译成HAL库的串口接收中断和HAL_UART_Transmit即可串口引脚默认PA9是TXPA10是RX蓝牙模块接在这两个引脚上。5.5 从“调试器”到真正的物联网控制App跑通这套代码之后你基本上就有一个属于自己的BLE串口调试器了。再往后做正式项目我建议按这个方向扩展把utils/ble.js改成全局单例避免多个页面重复初始化蓝牙设备列表用本地缓存存起来下次自动连接最近设备把串口协议抽象成指令集页面上不要直接写LED_ON这种裸命令而是封装成sendCommand(LED_ON)方便以后接MQTT云平台做远程控制。我在实际项目中就是在这套代码基础上加了一个MySQL本地数据库存设备信息然后把控制逻辑抽到store里页面只管UI和状态。这样即使以后设备协议升级了只要改一个协议解析文件就行不用每个页面都翻一遍。这个思路比到处复制蓝牙代码要省心得多。
返回列表