简介一套基于HBuilderX、经实测可用的HTML5蓝牙通信Demo工程面向前端与混合应用开发者主要用于解决Web端蓝牙设备连接、数据收发与状态监控问题同时兼顾Android原生蓝牙实现适合物联网、智能硬件及跨平台App开发场景参考。压缩包共258个文件、约11.45MB以class、java、xml、js等源码与配置类文件为主并包含png截图、pdf硬件手册、gradle构建配置及apk安装包目录结构同时覆盖H5示例、Android原生工程与MLT-BT05蓝牙模块资料。目前已有5166人学习下载。项目演示了Web Bluetooth API的调用流程也提供Android原生蓝牙适配代码可对比两种技术路线的差异附带串口调试工具与可直接安装的apk便于快速验证和二次开发能显著降低蓝牙功能联调与硬件对接的门槛实用性和启发性都比较强。1. HBuilder 蓝牙通讯html5-bluetooth-demo 能解决什么问题手头有个 BLE 温湿度采集器要接进 H5 页面用原生写要同时维护 Kotlin 和 Swift 两套代码而这份 HBuilder 蓝牙通讯 demo 让我在一天内跑通了扫描、连接、读写和 notify 监听。它基于 H5 的 plus.bluetooth 模块同一套 JavaScript 代码在 Android 和 iOS 真机上都能跑这正是 html5-bluetooth-demo 的核心价值把 BLE 蓝牙传输源码从原生黑匣子变成了前端可调用的 JS 接口。适合两类人一是给现有 H5 App 快速加蓝牙功能的 uni-app 或 H5 开发者二是不想碰原生蓝牙协议栈、只想拿到数据的前端工程师。接下来几章都是我在真机上一路踩过来后的实测记录照着走基本能复现完整链路。2. 真机环境与权限配置为什么必须用 HBuilderX 跑这个 demo2.1 选型边界plus.bluetooth 依赖 5 Runtime先泼一盆冷水这份资源不是纯浏览器里能跑的前端页面。plus.bluetooth 是 HTML5 规范封装的原生能力由 HBuilderX 打包进 5 Runtime运行时把蓝牙适配器、GATT 连接、特征读写这些原生操作桥接给 JavaScript 层。所以你在 VS Code 里写业务代码完全没问题但真机运行、打包、注入 plus 对象绕不开 HBuilderX 或者依赖 5 Runtime 的 uni-app 工程。有人问过我用 VS Code 装插件连接安卓模拟器是不是能替代 HBuilderX我的回答是编辑器层面可以运行时层面不行。这个 demo 里的 plus.bluetooth 对象只会出现在 HBuilderX 生成的真机调试包里Chrome 和普通 WebView 里根本没有这个对象强行调用会直接报 plus is not defined。选它而不是 Web Bluetooth API 的理由也很简单Web Bluetooth 对 Android 页面协议和 HTTPS 有限制iOS 上根本不用想而 plus.bluetooth 在两端都是同一套调用方式。2.2 manifest.json 权限配置与运行时申请蓝牙在移动端的权限坑比接口本身多。Android 从 6.0 开始BLE 扫描会返回周边设备信号被系统视为位置信息所以扫描必须申请定位权限iOS 13 以上则必须在打包配置里声明蓝牙使用描述否则 openBluetoothAdapter 一调用就直接失败。常用权限配置见下表平台权限 / 描述说明AndroidBLUETOOTH、BLUETOOTH_ADMIN、ACCESS_FINE_LOCATION扫描 BLE 必须带定位权限定位开关也要打开Android 12BLUETOOTH_SCAN、BLUETOOTH_CONNECT按 targetSdk 补充targetSdk 31 以上建议补上否则部分机型会拦截调用iOSNSBluetoothAlwaysUsageDescriptioniOS 13 必须声明描述文案会出现在系统弹窗里在 HBuilderX 里这些大多能在 manifest.json 的可视化界面勾选底层写进配置文件的形态大致如下{ permissions: { Bluetooth: { description: 使用蓝牙与设备进行数据传输 } }, app-plus: { distribute: { android: { permissions: [ uses-permission android:name\android.permission.BLUETOOTH\/, uses-permission android:name\android.permission.BLUETOOTH_ADMIN\/, uses-permission android:name\android.permission.ACCESS_FINE_LOCATION\/, uses-permission android:name\android.permission.BLUETOOTH_SCAN\/, uses-permission android:name\android.permission.BLUETOOTH_CONNECT\/ ] }, ios: { privacyDescription: { NSBluetoothAlwaysUsageDescription: 需要使用蓝牙与设备通讯 } } } } }这段配置里Android 的 BLUETOOTH_SCAN 和 BLUETOOTH_CONNECT 是给 targetSdk 31 准备的如果你的打包版本较老只保留前三项也能跑。iOS 的 privacyDescription 字段一旦缺失真机上蓝牙初始化会静默失败而且报错信息很容易让你误判成外设问题。配置完静态权限还不够Android 运行时定位权限要在代码里主动申请一次因为 BLE 扫描被归类到危险权限。我一般会在调用蓝牙前走一遍这个逻辑function ensureAndroidPermission(callback) { if (plus.os.name Android) { plus.android.requestPermissions( [android.permission.ACCESS_FINE_LOCATION], function(resultObj) { if (resultObj.granted resultObj.granted.length 0) { callback() } else { plus.nativeUI.toast(需要定位权限才能扫描蓝牙设备) } }, function(error) { console.error(权限申请失败: JSON.stringify(error)) } ) } else { callback() } }这里的 plus.android.requestPermissions 是 HTML5 提供的原生 API 封装第一个参数是权限数组第二个参数是授权回调第三个是拒绝回调。注意判断 granted 数组的长度部分机型会返回已授权和未授权混合列表只看 success 回调并不保险。真机跑通的标准信号首次打开页面弹定位授权框同意后蓝牙扫描能列出外设权限这关就算过了。真机运行的流程很简单手机打开开发者模式并连接 HBuilderX菜单栏选“运行 → 运行到手机或模拟器”HBuilderX 会自动编译、安装并启动调试基座。第一次跑建议用标准基座等这个 demo 完全跑通再考虑打自定义调试基座因为自定义基座出问题时你分不清是权限问题还是基座配置问题。3. 扫描与连接从 openBluetoothAdapter 到连接状态机3.1 扫描 APIstartBluetoothDevicesDiscovery 参数与回调蓝牙扫描的完整链路是“开适配器 → 开始发现 → 监听设备回调 → 停止发现”。这个流程里最容易被忽略的是适配器状态检查我见过不少人直接调用 startBluetoothDevicesDiscovery结果设备列表为空还不知道原因。先看初始化这段function initBle() { plus.bluetooth.openBluetoothAdapter({ success: function(res) { console.log(adaptor ready:, JSON.stringify(res)) startScan() }, fail: function(err) { if (err err.errMsg err.errMsg.indexOf(already) -1) { startScan() } else { plus.nativeUI.toast(蓝牙初始化失败请检查蓝牙开关) } } }) } function startScan() { plus.bluetooth.startBluetoothDevicesDiscovery({ services: [], allowDuplicatesKey: false, interval: 50, success: function() { console.log(scan started) }, fail: function(err) { console.error(scan fail: JSON.stringify(err)) } }) plus.bluetooth.onBluetoothDeviceFound(handleDeviceFound) }参数说明services 为空数组表示扫描所有 BLE 服务如果你提前知道外设广播的 service UUID把它填进去能显著缩小扫描范围、降低功耗allowDuplicatesKey 控制是否接收重复设备上报false 表示同一个设备只上报一次配合列表渲染正合适interval 是两次扫描之间的间隔毫秒数设太小会耗电设太大发现新设备变慢50 到 100 是平衡区间。注意 openBluetoothAdapter 在部分 Android 机型上重复调用会报 already openfail 回调里要按 errMsg 判断一下而不是直接提示失败。设备回调里能拿到的字段主要有 deviceId、name、RSSI 信号强度。扫到设备后先别急着连接把 deviceId 存下来。这里有个经验广播名可能为空比如某些自制的 BLE 外设没写广播名判断设备就要靠 deviceId 加 RSSI 组合或者让设备广播可识别的 service UUID。3.2 连接createBLEConnection 与连接状态监听点击列表里的设备发起连接代码长这样function connectDevice(deviceId) { plus.bluetooth.createBLEConnection({ deviceId: deviceId, timeout: 10000, success: function() { console.log(connect command sent) }, fail: function(err) { console.error(connect fail: JSON.stringify(err)) } }) plus.bluetooth.onBLEConnectionStateChange(function(res) { console.log(connection state: JSON.stringify(res)) if (res.connected) { currentDeviceId res.deviceId discoverServices(res.deviceId) } else { plus.nativeUI.toast(连接已断开) } }) }createBLEConnection 的 timeout 参数默认是 20000 毫秒某些外设响应慢但设太短会导致连接超时误报。关键点在 onBLEConnectionStateChange这个回调里的 connected 字段为 true 时才表示 GATT 连接真正建立这时候才能去拉服务列表。很多人翻车在 createBLEConnection 的 success 回调里立刻调 getBLEDeviceServices结果拿到空数组因为 success 只代表连接指令发出去了不代表连接状态稳定。连接成功后要记得停掉扫描一方面省电另一方面避免扫描和连接的无线资源冲突。示例plus.bluetooth.stopBluetoothDevicesDiscovery({ success: function() {}, fail: function() {} })如果扫描和连接串行执行建议把 stopBluetoothDevicesDiscovery 放在 onBLEConnectionStateChange 确认 connected 之后如果业务允许并行也要在外设连上后尽快停。连接状态机总结下来就是openBluetoothAdapter → startBluetoothDevicesDiscovery → onBluetoothDeviceFound 刷列表 → createBLEConnection → onBLEConnectionStateChange 判断 connected → stopBluetoothDevicesDiscovery → 进入服务发现。顺序颠倒就会出现各种莫名其妙的空数据问题。4. 服务发现与数据收发把特征值真正用起来4.1 枚举服务与特征值UUID 补全与 properties 判断BLE 外设的数据组织方式是“服务 Service → 特征值 Characteristic”一个服务里挂着多个特征值读写操作都落在特征值上。连接成功后先拉服务列表function discoverServices(deviceId) { plus.bluetooth.getBLEDeviceServices({ deviceId: deviceId, success: function(res) { res.services.forEach(function(item) { console.log(service: item.uuid) }) }, fail: function(err) { console.error(get services fail: JSON.stringify(err)) } }) }拿到服务列表后再逐个服务去枚举特征值function discoverCharacteristics(deviceId, serviceId) { plus.bluetooth.getBLEDeviceCharacteristics({ deviceId: deviceId, serviceId: serviceId, success: function(res) { res.characteristics.forEach(function(ch) { console.log(char uuid: ch.uuid) console.log(properties: JSON.stringify(ch.properties)) }) }, fail: function(err) { console.error(get characteristics fail: JSON.stringify(err)) } }) }这里有两个极易踩的细节。第一UUID 格式必须规范。设备厂商文档里常写 16 位短 UUID比如 FFE0但 API 要求 128 位完整 UUID需要补全成 0000FFE0-0000-1000-8000-00805F9B34FB而且统一用小写大写和缺连字符都会导致匹配失败。第二properties 字段决定了这个特征值能做什么read 可读、write 可写、notify 可通知。写之前一定要看 properties如果对只读特征值调 write 接口回调必失败。4.2 写入与 notify 监听ArrayBuffer 构造、20 字节分包与 CCCD写入操作的 value 参数必须是 ArrayBuffer不能直接传字符串或普通数组。把字节数组转成 ArrayBuffer 再写入的完整形态function writeData(deviceId, serviceId, characteristicId, bytes) { var buffer new ArrayBuffer(bytes.length) var dataView new DataView(buffer) for (var i 0; i bytes.length; i) { dataView.setUint8(i, bytes[i]) } plus.bluetooth.writeBLECharacteristicValue({ deviceId: deviceId, serviceId: serviceId, characteristicId: characteristicId, value: buffer, success: function() { console.log(write ok, length: bytes.length) }, fail: function(err) { console.error(write fail: JSON.stringify(err)) } }) }注意 BLE 协议默认 MTU 是 23 字节扣除协议头单次写入的数据部分一般只有 20 字节。超过 20 字节的指令要手工分包发送常见做法是每 20 字节切一段按顺序发送发完一段等 write 回调成功再发下一段避免外设端数据错乱。write 回调成功只代表数据通过协议栈发出去了不代表外设业务上处理完了需要外设确认的指令还得靠 notify 回调收应答。接收数据走 notify 通道配置和监听的代码如下function startNotify(deviceId, serviceId, characteristicId) { plus.bluetooth.notifyBLECharacteristicValueChange({ deviceId: deviceId, serviceId: serviceId, characteristicId: characteristicId, state: true, success: function() { console.log(notify enabled) }, fail: function(err) { console.error(notify enable fail: JSON.stringify(err)) } }) } plus.bluetooth.onBLECharacteristicValueChange(function(res) { var bytes new Uint8Array(res.value) var receivedText for (var i 0; i bytes.length; i) { receivedText String.fromCharCode(bytes[i]) } console.log(recv: receivedText) })notifyBLECharacteristicValueChange 的 state 参数置为 true 表示开启通知false 表示关闭。部分外设要求先向 0x2902 描述符写入开关值才能收到通知不过 plus.bluetooth 内部通常会自动处理这一步如果你遇到开启 notify 成功但始终收不到数据就需要用 nRF Connect 这类工具手动连一次外设对比确认是不是外设端要求先启 Notification 再发数据。onBLECharacteristicValueChange 里拿到的 res.value 同样是 ArrayBuffer我用 Uint8Array 包装成字节数组方便解析。收到数据后先以 hex 或文本形式打日志确认字节序和编码再做业务解析。5. 避坑指南真机蓝牙调试高频翻车点与排查顺序5.1 先看日志再动代码排查的三个前置动作蓝牙调试和普通前端调试最大的不同是链路长问题可能出现在 JS 层、系统蓝牙服务层、外设固件层任何一处。我每次排查都按固定顺序来先看 openBluetoothAdapter 是否成功确认系统蓝牙可用再看 startBluetoothDevicesDiscovery 的 success 回调有没有进确认扫描指令被系统接受最后才看有没有设备回调。很多“为什么连不上”的问题追到第一步就发现适配器压根没打开。还有个影响排查效率的细节HBuilderX 运行到 iPhone 真机时console.log 经常在控制台不打印。遇到这种情况先把 console.log 换成 plus.console.log同时在 HBuilderX 控制台的日志级别里把显示等级调到 Verbose否则你会在排查时白白浪费大量时间。这个不是 demo 的问题是真机调试通道的怪癖知道有这回事就行。5.2 五条踩坑记录现象、原因、解决第一条扫描不到任何设备onBluetoothDeviceFound 一直不触发现象startBluetoothDevicesDiscovery 的 success 回调执行了但设备列表空回调一次都不进。原因Android 6.0 以上 BLE 扫描依赖定位权限权限只是静态配置还不够手机系统定位开关必须处于打开状态部分机型还必须先申请到精确定位权限才回传设备。解决检查 manifest 是否包含 ACCESS_FINE_LOCATION代码里用 plus.android.requestPermissions 运行时申请再确认手机状态栏定位图标亮着。iOS 端则检查 NSBluetoothAlwaysUsageDescription 是否配置没配置会直接报授权失败。第二条openBluetoothAdapter 报错但手机蓝牙明明开着现象适配器初始化失败错误信息含糊看不出是权限还是系统原因。原因Android 上常见于 targetSdk 31 但 manifest 缺少 BLUETOOTH_CONNECT 权限或系统蓝牙服务异常需要重启iOS 上常见于首次弹窗点了拒绝。解决Android 补全 BLUETOOTH_SCAN 和 BLUETOOTH_CONNECT 权限并重新打包iOS 端提示用户去系统设置里打开蓝牙授权。判断依据很简单用 plus.bluetooth.getBluetoothAdapterState 查一下返回状态。第三条createBLEConnection 成功但 getBLEDeviceServices 返回空数组现象连接成功回调走了服务列表却是空的或者列表里只有一两个 service找不到目标服务。原因最常见的是在连接指令刚发出时就拉服务GATT 连接还没就绪另一种是外设启动慢服务广播还没准备好。解决在 onBLEConnectionStateChange 里确认 connected 为 true 后再拉服务如果稳定复现空列表尝试延迟 300 到 500 毫秒或断开重连一次。第四条writeBLECharacteristicValue 一直失败现象写特征值回调报错外设没反应。原因没确认特征值的 properties 里有没有 write 属性value 不是 ArrayBuffer单包写入超过 20 字节被外设拒绝。解决先遍历 getBLEDeviceCharacteristics 返回的 properties确认可写再操作value 严格用 ArrayBuffer 包装超过 20 字节拆包发送。第五条notify 开启成功但收不到任何主动上报现象notifyBLECharacteristicValueChange 返回成功外设主动发数据onBLECharacteristicValueChange 一直静默。原因外设端特性需要先启用 CCCD 描述符而部分外设固件要求先写入 0x2902 描述符再打开 NotificationAPI 内部的处理顺序和外设预期不一致。解决先用 nRF Connect 连接外设手动打开 notify排除外设自身广播问题如果外设收不到通知开关指令考虑在开启 notify 前先做一次特征值读操作部分固件有这个依赖。6. 再进一步把 demo 封装成可复用的 BLE 工具类6.1 Promise 化封装与调用示例demo 跑通后的下一步是把散落的 API 调用整理成一个小工具类让业务代码里只关心“扫描”“连接”“发送”“接收”这四个动作。我给一个最小可用的封装结构class BleHelper { constructor() { this.deviceId this.serviceId this.writeCharId this.notifyCharId } initialize() { return new Promise(function(resolve, reject) { plus.bluetooth.openBluetoothAdapter({ success: resolve, fail: reject }) }) } scan() { return new Promise(function(resolve, reject) { plus.bluetooth.startBluetoothDevicesDiscovery({ services: [], allowDuplicatesKey: false, interval: 50, success: resolve, fail: reject }) }) } connect(deviceId) { var self this return new Promise(function(resolve, reject) { plus.bluetooth.createBLEConnection({ deviceId: deviceId, timeout: 10000, success: function() { self.deviceId deviceId }, fail: reject }) }) } write(bytes) { var buffer new ArrayBuffer(bytes.length) var view new DataView(buffer) for (var i 0; i bytes.length; i) view.setUint8(i, bytes[i]) return new Promise(function(resolve, reject) { plus.bluetooth.writeBLECharacteristicValue({ deviceId: self.deviceId, serviceId: self.serviceId, characteristicId: self.writeCharId, value: buffer, success: resolve, fail: reject }) }) } }封装的核心思路是 Promise 化把 success 回调映射到 resolvefail 回调映射到 reject业务层就能用 async/await 串起整个蓝牙流程。我在实际项目里会把包长度校验也放进 write 方法里超过 20 字节自动分包并维护一个发送队列上一包回调成功再发下一包。这个工具类再往后加一个心跳重连机制基本就能覆盖绝大多数 BLE 透传设备的应用场景。从拿到这份 html5-bluetooth-demo 到封装成上面这个类我最大的教训是蓝牙调试的每一步都要先确认前一步的异步回调真的完成了再往下走状态机比代码风格重要得多。从那以后我每次做 BLE 联调都强制先跑一遍“适配器状态 → 权限 → 扫描 → 连接 → 服务发现”的日志链路确认每一步都亮绿灯再开始写业务逻辑希望帮到你。本文还有配套的精品资源点击获取
企业数字化 ERP 产品动态
相关推荐
长期生酮饮食伤心脏?机制解析与护心自救底线 生酮群里的打卡记录往往分两种:一种是体重秤的数字持续下滑,配一张满足的餐盘;另一种是同一个ID过几周又冒出来,问“最近心慌得厉害、早搏也变多了,还在坚持生酮,要不要紧”。大多数回答是:你是… · 2026/9/26 4:56:27
16V磷酸铁锂电池串数选择:不是数学题,而是工程判断题 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 4:56:27
Linphone图片中转服务lft.php从原理到部署避坑指南 简介:面向Linphone局域网或私有服务器部署场景,压缩包内提供重写后的图片消息中转服务端lft.php源码,解决自定义部署中依赖官方服务器中转带来的外网依赖问题,适合需要在隔离网络或自有服务器上搭建安全可控通信环境的开发与运维人… · 2026/9/26 4:56:08
Steam游戏启动卡在正在启动?17步底层诊断与修复指南 1. 项目概述:为什么“正在启动”成了Steam玩家最熟悉的等待界面 你点开《赛博朋克2077》,鼠标悬停在“播放”按钮上,指尖一按——屏幕右下角弹出小窗口:“正在启动”,进度条纹丝不动。你盯着它看了30秒、60秒、两分钟… · 2026/9/26 5:25:48
【行空板K10】从环境搭建到用华为云码道生成「中秋快乐」 文章目录一、前言二、软件安装与工程配置2.1 安装 PlatformIO(以 VSCode 为例)2.2 新建工程并配置 platformio.ini2.3 跑通官方测试代码三、踩坑记录:中文路径/文件名导致的编译错误四、用华为云码道(CodeArts)生成「中秋快乐」彩色文字4.1 需… · 2026/9/26 5:25:48
SSM后端+微信小程序:社区垃圾回收管理系统全栈实战教程 简介:一套基于微信小程序的社区垃圾回收管理系统SSM后端毕业设计源码案例,面向计算机专业毕业生、课程设计学习者及微信小程序/后端开发爱好者。系统涵盖用户管理、垃圾回收请求提交、垃圾分类指导、任务分配、进度跟踪与数据统计等核心功能,… · 2026/9/26 5:25:48
SSM+微信小程序社区养老服务系统:环境搭建、业务走读与避坑指南 简介:基于微信小程序与SSM后端的高分毕业设计完整源码包可用于毕业设计、课程设计及期末大作业,面向计算机专业毕业生和需要项目实战练习的学习者。项目以社区养老服务为业务场景,围绕护理预约、健康管理、日常生活照料、文化娱乐活动等模块展… · 2026/9/26 5:25:48
120套财务分析报告模板RAR实战指南:从解压安全到Excel合并分析 我一直觉得,做财务这行的人,谁电脑里没几个“模板大礼包”都说不过去。今天要聊的这份《120套财务分析报告模板.rar》,可能你也在某个资料群里见过。问题在于,很多人把文件下载完、解压完、看一眼目录,然后就没有然后了… · 2026/9/26 5:25:48
VS Code 从C语言到嵌入式与AI编程:一套可复现的完整配置指南 简介:微软Visual Studio Code(简称VS Code)是微软推出的免费开源代码编辑器,长期活跃于Web前端、服务端脚本、桌面与移动应用等各类开发场景,既适合初学者熟悉编码流程,也适合专业开发者进行多项目协同与复… · 2026/9/26 5:25:42
数据库课后习题答案别硬背:当测试用例集刷,效率翻倍 简介:万常选版《数据库原理与设计》课后习题答案资源,覆盖第2至6章及第9章,适合正在学习关系模型、数据库建模、关系数据理论与模式求精的本科生、自学者作为复习与自测材料。压缩包共7个文件,含3个doc参考答案、2个sql示例脚本、… · 2026/9/26 0:00:21
OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 0:00:40
向下兼容与向上兼容:接口设计中的兼容性策略与工程实践 一次版本升级事故,是很多团队绕不过去的坎。线上环境里,服务端明明已经上线了新版接口,老的移动端还在照着旧文档传参数。请求一到网关,校验直接拒绝,用户操作失败,客服群炸了锅,开发群里开始互… · 2026/9/26 0:00:46