微信小程序BLE广播扫描与解析:Beacon展位导览完整方案

发布时间:2026/9/29 18:12:38
微信小程序BLE广播扫描与解析:Beacon展位导览完整方案 年初做室内展位导览项目时遇到一个很典型的需求一台自研Beacon设备摆在展位旁边用户拿出微信小程序扫码进场页面要自动识别当前展位并推送对应内容。技术链路并不复杂——Beacon在广播数据小程序在扫描附近的蓝牙广播数据流。可真正落地时我在广播格式、设备兼容性、权限授权上反复折腾了好几周。今天把整套方案整理出来包括原理、代码、踩坑实录和合规边界希望能让后来人少走点弯路。这套内容适合三类人正在做室内定位、到店打卡或智慧导览的开发者手里有ESP32、nRF52832这类BLE模块、想对接小程序的硬件玩家以及想搞明白“蓝牙连接”和“蓝牙广播”到底有什么区别的产品和测试同学。零基础也能读API参数我会一个个解释清楚涉及代码的部分直接复制就能跑。1. 先理清需求小程序蓝牙广播能做什么、不能做什么很多开发者一听到“蓝牙”第一反应就是“连接设备、收发数据”。但广播和连接其实是两条完全不同的技术路线。我们这次的核心需求只是“感知到设备存在并读取设备主动喊出来的那几行数据”根本不需要建立连接也不该建立连接。1.1 BLE广播到底在广播什么BLE协议栈里有一层叫GAPGeneric Access Profile专门管“发现设备和被设备发现”。设备可以周期性地在37、38、39三个广播信道上往外发数据包任何处于扫描状态的设备都能听到不需要配对不需要授权也不需要先连接。这个模型就像校园公告栏——有人往上面贴告示路过的人看一眼双方不需要互相认识。每个传统广播包最多31字节BLE 5.0的扩展广播可以到255字节但实际工程里我们通常按20字节以内去设计自定义协议。广播包内容通常包含设备名称、厂商自定义数据、服务UUID、发射功率等。小程序里通过wx.onBluetoothDeviceFound拿到的device对象就携带了这些广播信息的解析结果。我经常用一个类比解释为什么广播适合轻量信息广播是“喊话”适合传递“我在这里、我是谁、我的状态如何”这类短消息连接是“打电话”适合传输大量数据。在展位导览这类场景里业务上只需要知道“用户附近是哪台设备”99%的数据都可以等用户点击后走HTTP请求获取广播链路根本不需要承载复杂业务。1.2 这些场景天然适合“广播数据”而不是“连接传输”根据我接触过的项目广播数据在小程序里的典型应用有这几类室内定位与展位导览Beacon部署在各个展位小程序扫描到广播后解出设备ID再映射到具体展位和内容。到店打卡与签到门店摆放Beacon用户到店后小程序自动识别完成打卡。低功耗传感器状态上报温湿度计、电量监测模块持续广播当前数值小程序扫到就能显示。防伪溯源辅助设备广播一段动态标识小程序读到后与服务端校验。这些场景的共同点是设备端不需要知道谁在监听也不需要回传数据广播出去即可。如果强行走“连接”每次都要经历扫描、连接、发现服务、发现特征值、订阅通知、断开这一整套流程耗时长、功耗高而且一个手机不能同时连太多设备反而把简单问题做复杂了。1.3 小程序做蓝牙广播的3条能力边界先说边界免得大家走弯路。第一小程序只能做“扫描方”不能做“广播方”。微信官方没有开放任何让小程序主动对外广播数据的接口哪怕是最新基础库也一样。原因是小程序运行在宿主App里iOS和Android系统层面都不允许第三方App随意占用广播信道所以想实现“小程序自身变成Beacon”这条路从根上就走不通。第二广播数据不等于设备所有信息。有的设备广播包很短只带一个厂商ID和一个服务UUID有的设备把广告数据塞得满满当当厂商自定义数据、服务数据、设备名称全放进去。能不能拿到完整数据取决于发射端怎么设也取决于手机系统和微信基础库怎么裁剪。第三经典蓝牙协议BR/EDR不属于这条链路。像HC-05、HC-06这类经典蓝牙模块通常走串口透明传输必须先连接才能收发数据它们不支持BLE广播。如果项目里想用HC-05做广播源硬件选型就错了。A2DP音频传输、SCO语音通道也都和“小程序扫描广播”无关。真要对接这类模块得走wx.createBLEConnection连接之后读写特征值或者干脆换支持BLE的模块比如HM-10、CC2541、ESP32。1.4 “不连接”方案对比连接方案的优势我把两种方案在关键维度上做过对比差异非常明显对比维度广播扫描方案建立连接方案建立通信耗时秒级扫到即有数据通常3~8秒甚至更久手机并发设备数理论上可同时监听多台连接数受限iOS一般个位数功耗扫描功耗可控停止即恢复连接保持期间持续耗电配对要求不需要部分设备需要配对码适合数据量小适合标识和状态大适合OTA、串口透传设备端复杂度只广播逻辑简单需要处理连接、服务发现、断线重连在展位导览这个项目里我一开始也想用“连接后读特征值”的方式结果现场试了10台设备发现每次连接都有概率失败而且用户走两步断开还要重连体验很差。后来全部改成广播扫描页面打开不到1秒就能识别当前位置问题一下少了大半。广播方案不是万能的但它非常适合“发现附近设备”这个动作。2. 小程序蓝牙API全景先看能力边界再写代码微信小程序蓝牙API其实不算多核心链路就是“初始化蓝牙适配器→开始扫描→监听设备发现→停止扫描→关闭适配器”。但很多人上来就复制网上的代码连返回值里有哪些字段都没搞清楚遇到advertisData为空就懵了。这一节把调用链和字段逐一说透。2.1 微信小程序蓝牙API调用链标准调用顺序是这样wx.openBluetoothAdapter()打开蓝牙适配器失败时会返回错误码比如10001表示蓝牙未打开10000表示系统不支持蓝牙。wx.startBluetoothDevicesDiscovery()开始扫描附近的BLE设备。可以传services数组来过滤也可以不传扫全部。wx.onBluetoothDeviceFound()注册设备发现监听。注意这个接口是持续回调的扫描期间可能多次返回同一台设备。wx.stopBluetoothDevicesDiscovery()停止扫描。wx.closeBluetoothAdapter()关闭适配器释放蓝牙资源。我踩过的第一个坑是很多人只调start不调stop和close页面反复进出后蓝牙资源一直被占用后续openBluetoothAdapter就失败。正确做法是进入页面需要扫描时openstart离开页面时stopclose在onBluetoothDeviceFound回调里做业务判断完成后立刻停止扫描不要一直空转。这里还要注意startBluetoothDevicesDiscovery的参数。services只接收蓝牙服务UUID字符串数组文档标准格式是“0000180D-0000-1000-8000-00805F9B34FB”但实际兼容性测试中发现有的硬件广播的是16位UUID传“180D”这种短格式也能匹配。allowDuplicatesKey表示是否允许重复上报同一设备默认false表示同一设备只上报一次。需要持续采集RSSI做测距时我会设成true然后自己在业务层做节流。2.2 三个关键字段advertisData、manufacturerData、serviceDatawx.onBluetoothDeviceFound回调的res.devices数组里每个设备对象常见字段有deviceId、name、RSSI、advertisData、manufacturerData、serviceData、localName。前三个好理解后面三个才是真正需要解析的“广播数据”。advertisData是ArrayBuffer类型保存的是设备原始的广播报文也就是设备在广播信道里发出来的那整段数据。不同厂商、不同协议这段报文的排列方式完全不同。比如iBeacon的advertisData通常包含Apple厂商ID、iBeacon类型标识、UUID、Major、Minor和发射功率。manufacturerData是一个对象key是厂商ID十进制字符串value是ArrayBuffer。这是厂商自定义数据区适合存放自己的协议内容。很多Beacon会把温度、电量、设备状态塞在厂商数据里。serviceData同样是一个对象key是服务UUIDvalue是ArrayBuffer适合存放和特定服务相关的数据。我在项目里最常用的解析入口是manufacturerData发射端用自定义厂商ID广播小程序按厂商ID取到ArrayBuffer再用DataView按字节解出各字段。这样解析逻辑最干净不会因为广播包里多了设备名称前缀而偏移。2.3 iOS与Android的兼容性差异这是蓝牙广播项目里最需要提前做心理建设的地方。iOS对蓝牙权限的展示文案、系统级去重策略、后台扫描限制和Android差别非常大。第一权限模型不同。iOS在用户首次调用蓝牙相关API时会弹出“允许使用蓝牙”的授权框如果用户拒绝wx.openBluetoothAdapter会失败Android 12及以上版本除了蓝牙权限通常还需要定位权限因为系统认为扫描蓝牙设备可能推断出用户位置。真机调试时如果突然扫不到设备先检查是不是权限没给全。第二重复回调策略不同。iOS在allowDuplicatesKey设置为true时部分版本表现不稳定扫描一段时间后就不再重复回调同一台设备了。我在iPhone 14、iOS 17上实测连续扫描30秒后设备列表基本静止只有新设备出现才回调。Android这边一般比较听话设了allowDuplicatesKeytrue就会持续回报RSSI。所以不要指望两端行为完全一致业务逻辑必须做本地去重和轮询兜底。第三Android厂商对广播报文的裁剪差异很大。同一台Beacon华为手机上advertisData能完整取出小米手机上可能只有名称没有厂商数据换到OPPO又恢复正常。这种情况不是代码问题是系统蓝牙栈的差异需要靠“多次扫描”“过滤再扫描”来兜底。2.4 开发者工具与真机的差异微信开发者工具里的蓝牙API只是模拟实现很多情况下根本不会真正走系统蓝牙协议栈。我见过有人在开发者工具里扫描到了“模拟设备”拿到一段假数据以为自己成功了结果一上真机就空白。开发者工具能帮你验证的只有API调用顺序是否正确、参数类型是否合法、业务层解析逻辑是否能跑通。但advertisData的内容、系统权限弹窗、真实扫描频率全部得以真机为准。我的习惯是先把逻辑在开发者工具里跑通再拿一台Android和一台iPhone做双端验证。Android负责看完整广播报文iPhone负责看系统权限和去重行为。如果在小程序后台“开发管理—接口设置”里没有声明蓝牙相关接口或者隐私保护指引里没有写明收集蓝牙信息的目的真机调用时甚至会被直接拦截报错信息看起来像是“权限不足”。遇到这种问题先别急着改代码去后台检查接口权限声明。3. 一套可直接抄的广播数据监听实现前两节把原理讲清楚了现在进入动手环节。我会给出一套从扫描到解析的完整代码方案分硬件侧和软件侧两部分。硬件侧决定广播源发什么软件侧决定小程序怎么收、怎么解。3.1 广播源选型不是所有蓝牙模块都能广播想用小程序的广播扫描方案发射端必须支持BLE广播。我推荐几类常见的nRF52832、nRF52840Nordic家的经典BLE芯片做Beacon首选资料多、功耗低。ESP32、ESP32-S3自带WiFi和BLE开发方便适合做带业务逻辑的广播源。HM-10、CC2541老牌BLE从机模块能用但协议栈比较旧扩展帧支持有限。DA14580低功耗做得好不过开发资料相对少。如果你手里只有HC-05或HC-06就先别折腾了。它们是经典蓝牙模块本身不支持BLE广播小程序扫描不到。真要临时验证可以用手机安装一个Beacon发射App把手机当成广播源小程序扫描端代码逻辑不变先把链路调通再换硬件。这里给一个ESP32 Arduino环境下的极简Beacon广播示例只广播一条自定义厂商数据#include BLEDevice.h #include BLEUtils.h #include BLEServer.h BLEAdvertising *pAdvertising; void setup() { BLEDevice::init(DemoBeacon); pAdvertising BLEDevice::getAdvertising(); // 厂商ID 0xFFFF数据区0x01版本号 0xA1设备类型 0x0A温度 0x64电量 uint8_t customData[6] {0xFF, 0xFF, 0x01, 0xA1, 0x0A, 0x64}; BLEAdvertisementData advData; advData.setManufacturerData(std::string((char*)customData, 6)); pAdvertising-setAdvertisementData(advData); pAdvertising-start(); Serial.println(Beacon broadcasting...); } void loop() { delay(1000); }广播数据里放了厂商ID 0xFFFF后面跟着4个字节的自定义数据。小程序端按相同协议解析即可。注意这里使用的是BLE厂商数据区不要和iBeacon的Apple厂商ID混在一起。3.2 封装一个BleBroadcastScanner工具类直接在一堆页面里裸写wx蓝牙API代码会散得到处都是。项目里我习惯封装一个工具类把初始化、扫描、去重、销毁集中管理。核心职责有5个打开蓝牙适配器处理失败分支。开始扫描支持传入自定义过滤服务UUID。监听设备发现做首次过滤和设备去重。提供回调给业务层传入解析后的设备对象。停止扫描并释放资源。这个类不负责具体广播协议解析解析逻辑单独放在parse.js里方便不同项目复用。下面给出工具类代码。// bleBroadcastScanner.js class BleBroadcastScanner { constructor(options {}) { this.onDevice options.onDevice || function () {}; this.onError options.onError || function () {}; this._discoveryStarted false; this._adapterOpened false; this._seenDeviceIds new Set(); this._onDeviceFoundHandler null; } openAdapter() { return new Promise((resolve, reject) { wx.openBluetoothAdapter({ success: (res) { this._adapterOpened true; resolve(res); }, fail: (err) { this._adapterOpened false; this.onError(err); reject(err); } }); }); } startScan(options {}) { if (!this._adapterOpened) { return this.openAdapter().then(() this._doStartScan(options)); } return this._doStartScan(options); } _doStartScan(options {}) { return new Promise((resolve, reject) { const services options.services || []; const allowDuplicates options.allowDuplicatesKey || true; this._onDeviceFoundHandler (res) { const devices (res.devices || []).filter((item) { if (!item.advertisData !item.manufacturerData !item.serviceData) { return false; } if (options.filter) { return options.filter(item); } return true; }); devices.forEach((device) { if (!this._seenDeviceIds.has(device.deviceId)) { this._seenDeviceIds.add(device.deviceId); this.onDevice(device); } }); }; wx.onBluetoothDeviceFound(this._onDeviceFoundHandler); wx.startBluetoothDevicesDiscovery({ services: services, allowDuplicatesKey: allowDuplicates, success: (res) { this._discoveryStarted true; resolve(res); }, fail: (err) { this.onError(err); reject(err); } }); }); } stopScan() { if (this._onDeviceFoundHandler) { wx.offBluetoothDeviceFound(this._onDeviceFoundHandler); this._onDeviceFoundHandler null; } this._seenDeviceIds.clear(); if (this._discoveryStarted) { wx.stopBluetoothDevicesDiscovery({ complete: () {} }); this._discoveryStarted false; } } close() { this.stopScan(); if (this._adapterOpened) { wx.closeBluetoothAdapter({ complete: () {} }); this._adapterOpened false; } } } module.exports BleBroadcastScanner;代码里做了两件事值得注意。第一在_onDeviceFoundHandler里用Set去重避免同一台设备重复触发onDevice业务回调。第二stopScan里用wx.offBluetoothDeviceFound注销监听防止页面onHide后事件泄漏。如果你不注销监听页面退出后回调还在执行后续页面可能出现莫名setData报错。有一点需要说明startBluetoothDevicesDiscovery的services在iOS上如果传了不存在的服务UUID可能扫描结果为空传空数组则等价于扫描所有设备。我的建议是开发阶段先不传services完整扫描确认能收到广播后再按需要过滤。3.3 初始化与开始扫描的完整代码有了工具类调用侧就很干净了。页面onLoad里创建实例onShow里开始扫描onHide里销毁。注意iPhone的权限弹窗出现时机是openBluetoothAdapter第一次调用前要做个开关状态提示。// 页面内使用示例 const BleBroadcastScanner require(./bleBroadcastScanner); Page({ data: { deviceList: [], scanning: false, }, onLoad() { this.scanner new BleBroadcastScanner({ onDevice: (device) this.handleDevice(device), onError: (err) this.handleScanError(err), }); }, onShow() { this.startBleScan(); }, onHide() { this.stopBleScanAndClose(); }, startBleScan() { this.scanner.startScan({ allowDuplicatesKey: true, }).then(() { this.setData({ scanning: true }); }).catch(() { wx.showToast({ title: 蓝牙初始化失败, icon: none }); }); }, handleDevice(device) { const parsed this.parseDevice(device); if (!parsed) return; // 这里可以加入节流避免同一设备高频刷新列表 const key parsed.deviceId _ parsed.rssi; this.setData({ deviceList: [...this.data.deviceList.filter((d) d.key ! key), { key, deviceId: parsed.deviceId, rssi: parsed.rssi, payload: parsed.payload, }].slice(-10), }); }, handleScanError(err) { console.error(扫描失败, err); }, stopBleScanAndClose() { this.scanner.close(); }, parseDevice(device) { // 解析逻辑见下一小节 return parseBluetoothDevice(device); }, });这段代码里我刻意让业务层只做展示相关的事。解析函数单独抽出来后续无论换iBeacon协议还是自定义协议页面代码都不用动。还有一点搜索结果数组用按key去重后再slice(-10)是防止设备太多时列表无限膨胀界面卡顿。如果你需要在后台持续监听到店状态纯粹的页面级扫描是不够的。小程序退到后台后蓝牙扫描基本会被系统挂起所以我的建议是只在页面展示期间扫描到店判定由用户主动打开小程序时触发。3.4 广播数据的解析代码与协议设计现在写parseBluetoothDevice。这个函数要兼容两种常见广播格式iBeacon格式和自定义厂商数据格式。先看iBeacon解析。iBeacon的标准广播数据格式是Apple厂商ID0x004C、iBeacon类型0x02、长度0x15、16字节UUID、2字节Major、2字节Minor、1字节发射功率。反射在advertisData里典型报文前面还有02 01 1A 1A FF这样的前导内容。不同平台的advertisData起始偏移可能差几个字节所以代码里最好采用“搜索特征签名”的方式而不是固定下标读取。// parse.js function parseBluetoothDevice(device) { const result { deviceId: device.deviceId, name: device.name || device.localName || , rssi: device.RSSI, }; // 优先解析manufacturerData if (device.manufacturerData) { const keys Object.keys(device.manufacturerData); if (keys.length 0) { const companyId parseInt(keys[0], 10); const data device.manufacturerData[keys[0]]; result.custom parseCustomManufacturerData(companyId, data); return result; } } // 其次解析advertisData里的iBeacon if (device.advertisData) { const beacon parseIBeacon(device.advertisData); if (beacon) { result.beacon beacon; return result; } } return result; }iBeacon解析函数如下。从advertisData中查找0x02 0x15这个特征序列找到后从后往前取字段。function parseIBeacon(arrayBuffer) { const view new DataView(arrayBuffer); const bytes new Uint8Array(arrayBuffer); // 搜索iBeacon特征: 0x02 0x15 const signature [0x02, 0x15]; let startIndex -1; for (let i 0; i bytes.length - 1; i) { if (bytes[i] signature[0] bytes[i 1] signature[1]) { startIndex i - 2; // 回到厂商ID位置 break; } } if (startIndex 2) return null; const uuidBytes new Uint8Array(arrayBuffer, startIndex 4, 16); const uuid formatUuid(uuidBytes); const major view.getUint16(startIndex 20, false); const minor view.getUint16(startIndex 22, false); const txPower view.getInt8(startIndex 24); return { uuid, major, minor, txPower }; } function formatUuid(bytes) { const hex Array.from(bytes).map((b) b.toString(16).padStart(2, 0)).join(); return hex.slice(0, 8) - hex.slice(8, 12) - hex.slice(12, 16) - hex.slice(16, 20) - hex.slice(20); }自定义厂商数据的解析更灵活。我建议在硬件端就把协议字节布局设计好比如字节偏移长度字段说明01协议版本固定0x01方便后续升级11设备类型0xA1展位设备0xA2门禁设备22设备编号高字节在前41温度有符号整数单位℃51电量0~100整数对应解析代码function parseCustomManufacturerData(companyId, arrayBuffer) { if (arrayBuffer.byteLength 6) return null; const view new DataView(arrayBuffer); const version view.getUint8(0); const deviceType view.getUint8(1); const deviceId view.getUint16(2, false); const temperature view.getInt8(4); const battery view.getUint8(5); return { companyId, version, deviceType, deviceId, temperature, battery, }; }解析逻辑的核心原则是发射端和接收端必须对同一份字节布局达成一致。硬件端升级固件后如果协议字节变了老版本小程序解析会错位。所以在协议头里放一个版本号字段小程序端根据版本号走不同解析分支这是最保险的做法。3.5 广播数据解析的边界条件处理真机环境里广播包经常“不干净”。我见过三种典型情况广播包只有设备名称没有广告数据广播包长度够但内容全是0同一设备在不同时刻解析出的manufacturerData长度不一样。因此解析函数里必须加空值判断和长度校验解析不出来就返回null而不是抛异常。另外RSSI信号强度变化很快同一台设备一秒内可能从-50跳到-75。如果你把首个RSSI值直接当成距离判断依据业务误判率会很高。我习惯对同一设备RSSI做滑动平均至少取最近5次扫描值再参与距离换算这样展位切换才会稳定。// rssiFilter.js 简单滑动平均 class RssiFilter { constructor(windowSize 5) { this.windowSize windowSize; this.values []; } push(rssi) { this.values.push(rssi); if (this.values.length this.windowSize) { this.values.shift(); } const sum this.values.reduce((a, b) a b, 0); return sum / this.values.length; } }这个过滤器不要放在全局最好每个deviceId维护一个实例不然多台设备的RSSI会互相污染。4. 真机调试与线上排错常见坑与解决实录蓝牙项目百分之八十的时间花在排错上。我把过去踩过的问题整理成速查表和现场实录很多问题在官方文档里根本找不到答案只能靠真机一步步试出来。4.1 常遇到的5个问题速查表现象可能原因解决思路扫描不到任何设备系统蓝牙未打开定位权限缺失Android定位服务关闭检查系统设置重新授权确认定位开关能扫到设备但advertisData为空Android机型裁剪广播未匹配过滤条件指定services过滤换不同机型对比iOS一段时间后不再回调iOS系统去重机制stop后重新start短周期扫描开发者工具有数据真机无数据模拟器不走真实蓝牙协议栈以真机调试为准后台配置接口权限设备反复回调导致业务重复触发allowDuplicatesKeytrue 未做去重工具类里Set去重业务层幂等表中第一条和第二条最容易一起出现。Android 12以上如果没开定位服务蓝牙扫描会静默失败不报任何明显错误。可以在失败回调里把errCode打印出来对照微信官方错误码表逐项排查。4.2 三个现场踩坑实录实录一华为手机取不到完整广播包。某台Beacon在iPhone上能正常解析出厂商数据华为P系列手机上却只有设备名和RSSI。排查后发现华为系统只对“匹配了预期服务UUID”的设备返回完整广播数据。解决方案是startBluetoothDevicesDiscovery时传入广播里的service UUID扫描结果里就带上完整advertisData了。如果业务可以用service过滤建议一开始就传services参数。实录二iOS用户扫码后一直识别不到展位。现场反馈是“有时候灵有时候不灵”。后来发现iOS在连续扫描同一设备时会停止重复上报而展会现场Beacon部署密度高用户走到新展位时旧设备还在有效距离内系统又把旧设备回调了一次业务层判断成了旧展位。解决方法是每次切换判断都先stop扫描清空本地去重Set再重新start强制系统刷新周边设备列表。实录三误触发展位识别。两台Beacon距离只有3米站在中间位置时小程序一会儿判定A展位一会儿判定B展位。我检查了原始RSSIA和B的差值经常只有2~3dB完全在噪声范围内。后面加了三重处理RSSI滑窗平均、区域信号强度阈值、连续3次扫描结果一致才触发切换误触发率从20%降到几乎没有。4.3 扫描性能与耗电实测小程序里持续蓝牙扫描对手机功耗影响是比较明显的。测试用的Android手机上连续扫描10分钟机身后盖能感觉到明显发热电量下降速度比平时快不少。iOS稍好但长时间占用蓝牙也会被系统“教育”。我给团队的规范是不要一进页面就开扫描然后永远不关。正确的姿势是扫描到目标设备并完成业务判断后立刻调用stopScan页面切后台时调用close需要刷新设备列表时再重新打开。这种按需启停的方式同名页面的功耗比持续扫描降低了60%以上。再说说回调频率。allowDuplicatesKeytrue时微信底层并不会以固定频率回调不同手机差异很大Android大概每1~2秒一批设备iOS表现更稀疏。所以业务上不要依赖“每秒都拿到最新RSSI”而是用短周期扫描轮询比如每10秒扫描一轮每轮持续3秒这样既省电也能拿到足够新的信号数据。5. 广播数据安全边界与合规要点广播方案好用但安全边界和合规要求很容易被忽略。这一节讲两个层面的问题广播数据本身怎么设计才安全小程序上架时权限和隐私声明怎么处理。5.1 广播信道是“广场”不是“密室”任何附近的BLE扫描设备都能收到广播包这就是广播的本质。你要默认广播数据是公开的所有人都能读到。因此设备ID、温度、电量这类无敏感信息可以放广播包里订单号、手机号、用户Token这类隐私数据绝不能放进去。如果需要做简单鉴权可以设计一个动态变化的小程序专用密钥广播包里放设备ID和一个短时效的随机数小程序拿到后上报云端云端根据设备当前状态校验随机数是否合法。但这种方案只防“顺手伪造”不防“专业设备重放”因为攻击者完全可以把广播内容录制下来原样重放。真正要防重放得在业务层做更高强度的加密和时效校验信标本身提供的安全能力有限。5.2 小程序权限与隐私合规用户在真机上打开小程序时如果涉及蓝牙扫描微信会按平台要求弹出授权框。iOS弹出“允许使用蓝牙”Android 12以上还需要“精确位置”或“附近设备”权限。小程序后台“用户隐私保护指引”里必须声明收集“位置信息”和“蓝牙设备信息”否则审核或真机调用可能被拦截。代码层面要注意的是不要在用户还没同意授权时就去调startBluetoothDevicesDiscovery。可以先检测蓝牙开关状态如果关闭就引导用户打开不要偷偷尝试扫描。合规角度上扫描结果只用于当前页面业务不要传给他方。app.json里如果需要声明后台定位或位置权限相关接口按微信官方私有接口配置来。一个常见误区是只是扫描蓝牙不调用wx.getLocation以为不需要位置权限。但很多Android机型不授权位置就真的扫不到设备所以该申请的权限要提前在隐私指引里说明。5.3 31字节内的协议设计建议传统广播包最多31字节去掉前导、厂商ID、UUID等固定头真正给自定义数据的空间可能只有十几字节。设计协议时我建议协议头至少包含魔数和版本号魔数用来识别“这是自家设备”版本号保证后续兼容升级。设备ID要精简能用一个整数表示就不要放一串字符串。传感器数据按固定字节布局排列不要用字符串拼接。末尾加一个校验字节避免当收到损坏的广播包时解析出错误数据。比如一个精简的自定义协议可以是| 字段 | 字节数 | 示例 | | --- | --- | | 魔数 | 2 | 0xAA55 | | 版本 | 1 | 0x01 | | 设备ID | 2 | 0x0003 | | 设备状态 | 1 | 0x00正常 | | 电量 | 1 | 0x64 | | 校验和 | 1 | 前7字节求和取低8位 |一共8个字节能覆盖绝大多数“识别设备状态上报”场景。如果项目需要上报多个传感器值就压缩成“每个字段1字节、固定单位”的方式来控制长度。比如温度用Int8存整数度湿度用Uint8存百分比精度不够时道歉连字节都省不下。6. 从“收广播”到“做业务”的联动扩展广播扫描只是整条链路的第一公里。设备识别出来之后业务层要做什么、数据怎么和云端对接这部分设计决定方案的最终效果。6.1 广播负责“发现”连接负责“交互”广播扫描最擅长的是“告诉我周围有什么设备”。如果用户需要和设备进行深度交互比如读取历史数据、配置设备参数、进行OTA升级那还是要走BLE连接。我建议把整个流程拆成两段第一段小程序通过广播扫描拿到目标设备的deviceId展示“附近发现智能秤”的提示。第二段用户点击“连接这台设备”小程序用wx.createBLEConnection连接该deviceId然后按GATT协议读写特征值。这样既避免了长时间扫描耗电又不会让连接失败影响一开始的识别体验。这里有个顺序问题连接时传入的deviceId必须是刚才扫描得到的deviceId不要用systemId或自定义ID。真机上同一个设备的蓝牙地址在不同手机表现可能不同但deviceId在微信API内部是稳定的用deviceId贯穿整个流程没错。6.2 与云端联动的设计思路广播数据里只有设备标识和状态值业务上真正需要的是“这个展位对应什么内容”。在小程序里维护一张静态映射表看起来简单但现场设备位置调整、内容替换都要发版很不方便。我更推荐把映射关系放云端小程序扫到设备ID后调用自己的后端接口把这个ID上报后端返回展位信息、活动内容、门店状态等。RSSI也可以一起上报后端可以统计用户分布、停留时长。这种模式的好处是业务变更只动后端小程序端不用跟着发版。如果有极简需求也可以用云开发数据库直接按设备ID查内容省去自建服务端。广播数据里的动态字段比如电量、传感器值上报后端后可以形成设备监控大屏。我还见过有人用一套Beacon做门店客流分析小程序扫描到设备后上报“已到店”事件后端据此做签到记录。这里要注意用户隐私合规采集位置和行为信息前要得到用户明确授权并在隐私协议里写清楚用途。6.3 RSSI测距与多Beacon优化方向广播数据里最容易被忽略的宝藏是RSSI。RSSI可以用来估算手机和Beacon之间的距离经典公式是d 10 ^ ((TxPower - RSSI) / (10 * n))。TxPower是设备广播的发射功率n是环境衰减因子取值通常在2到4之间。实际项目中单纯用这个测距精度不高因为人体遮挡、墙体反射都会影响RSSI。想提升精度可以用多Beacon三角定位至少同时收集3台设备的RSSI再做加权融合。我在一个展厅项目里实测单Beacon距离误差在3米以上部署密度提高、用3台设备三角融合后可以在2米范围内做到展位级识别。再配合前述的RSSI滑窗和触发阈值业务上已经完全够用。如果要进一步优化方向有这些部署Beacon时做好点位间距和功率规划避免同区域信号互相干扰扫描策略改成短周期轮询降低功耗在管理后台记录每个Beacon的物理位置动态调整触发阈值。这套方案后续还可以扩展到室内ToB场景比如商场导航、医院科室指引、学校智慧教室大体路线都是一样。我个人在实际项目里最大的体会是广播数据的价值不在于传输多少字节而在于把“设备在线、人在附近”这个信号以极低成本暴露出来。小程序端策略性地扫描、短平快地解析、及时释放资源整个方案就能稳定跑起来。如果你也正在做类似功能建议先从最简单的“扫到设备、打印全部原始字段”开始确认自家Beacon发了什么再逐步加上协议解析和业务映射别一上来就套复杂框架。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询