尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

微信小程序+蓝牙BLE充电桩开发实战:从协议设计到状态机管理

微信小程序+蓝牙BLE充电桩开发实战:从协议设计到状态机管理 简介本资源是一套完整的微信蓝牙小程序实战项目代码面向前端开发者及新能源IoT应用学习者聚焦充电桩场景下的蓝牙通信与微信生态集成。项目实现了附近桩搜索、实时状态查看、扫码启动、在线支付与预约充电等核心功能解决了新能源车主找桩难、操作繁琐的痛点适用于智慧出行、能源管理类项目开发参考。压缩包含262个文件总大小2.27MB其中82个JS文件承载蓝牙连接、设备发现与指令交互逻辑44个WXSS与33个WXML构成响应式UI组件58张PNG图标与6张JPG图示覆盖设备界面与二维码展示另有38个JSON配置与1份README说明文档。已有76人下载学习代码结构清晰、模块职责分明附带实际运行效果视频B站链接便于理解蓝牙协议对接细节与小程序生命周期管理实践。 最近在做一个新能源充电桩的配套微信小程序从协议梳理到蓝牙通信再到业务状态机踩了不少坑也沉淀下来一套能直接用的方案。项目源码统一打成了rar包里面是一套完整的前端小程序工程包含蓝牙扫描、连接、指令下发、充电状态上报这些核心逻辑。这篇文章就把整套方案的思路和关键实现拆开讲清楚给正在做硬件类小程序开发的朋友参考。内容既涉及微信小程序的BLE接口使用细节也覆盖充电桩业务层的协议设计适合有基础前端能力、想快速上手物联网场景的开发者。1. 项目全貌为什么是微信小程序蓝牙充电桩这个组合1.1 充电桩蓝牙方案的适用场景很多人一听到充电桩第一反应是4G联网、云平台远程控制那套方案。确实大型公共充电站走4G云平台是主流因为要接入电网调度、计费清分和跨区域运营。但充电桩不只是公共场所有需求还有大量场景其实用不着上云比如家用慢充桩、小区地下车库的车位桩、单位内部停车场、临时的移动充电设备这些场景如果非要走4G反而会带来一系列麻烦SIM卡要续费、地下车库信号不好、云平台开发周期长、数据安全也要考虑。蓝牙方案在这个场景下优势非常明显。手机和充电桩之间直接建立BLE连接不依赖任何外部网络哪怕是地下三层车库、手机完全没有蜂窝信号的极端环境也能正常完成启动充电、停止充电、读取状态这些操作。成本上一颗BLE蓝牙模块比如国产的泰凌微、Nordic方案比4G模组便宜不少而且不需要流量资费对家用充电桩这种对成本敏感的产品来说是一个很务实的选型。另外一个容易忽略的点是安全性。蓝牙是点对点通信充电桩和手机绑定之后别的设备扫描到了也拿不到控制权天然有设备鉴权的属性。相比之下如果设备直连公网反而要花大力气去做加密、防重放、防控制劫持这些安全机制。所以我的结论是公共运营型充电桩做云端方案私有充电场景做蓝牙方案两者不是替代关系而是互补关系。1.2 微信小程序相比App的核心优势既然选蓝牙那手机端为什么不用原生App非要用小程序我从实际开发和用户转化两个角度聊。开发成本上一套小程序代码同时覆盖iOS和Android两端不用各自写一套原生逻辑。BLE API在小程序平台上的封装非常完整wx.openBluetoothAdapter、wx.createBLEConnection、wx.writeBLECharacteristicValue这些接口在两端表现基本一致不用处理底层系统差异这对小团队和硬件初创公司来说太重要了。我见过太多团队花两个月做原生App结果用户下载率感人而且蓝牙权限、系统兼容性问题要比小程序多得多。用户触达上小程序扫码即用是杀手级场景。充电桩外壳贴一个二维码用户微信扫一扫直接进入小程序授权蓝牙权限10秒内完成连接和启动充电。不需要去应用商店搜索下载、注册账号、登录整个链条短得舒服。而且小程序有潜力直接关联附近充电桩这种LBS能力后续扩展预约、分享、消息通知充电完成推送都非常自然。技术栈上来讲小程序对BLE的支持已经非常成熟不只是简单的收发数据还包括获取信号强度RSSI做距离判断、监听设备广播、后台断线重连。这套能力组合足够覆盖充电桩业务90%的需求。唯一要注意的是小程序在系统蓝牙开关关闭时需要引导用户去设置页打开蓝牙这个后面我会专门讲。2. 蓝牙BLE通信原理与充电桩侧适配2.1 BLE协议基础回顾服务、特征值、UUID做小程序蓝牙开发之前务必要把BLE的四层模型搞明白物理层负责2.4GHz无线收发链路层负责连接广播和数据分组L2CAP层做逻辑通道复用再往上ATT/GATT层就是应用开发者接触最多的了。GATT层定义了服务Service和特征值Characteristic两个核心概念。理解GATT结构的一个生活化类比把蓝牙设备想象成一栋楼楼里有很多房间服务每个房间里有不同的开关和显示屏特征值你通过大楼前台GATT Server去访问某个房间里的某个开关就可以控制设备了。比如说充电桩设备定义了充电控制服务里面有两个特征值一个用来接收手机下发的控制指令可写一个用来向手机推送充电状态可通知。每个特征值都有一个UUID标识16位UUID是蓝牙SIG标准定义好的比如电池服务0x180F而自定义业务功能要使用128位UUID比如0000FFE0-0000-1000-8000-00805F9B34FB这种格式。这里有一个很常见的坑很多硬件工程师直接用标准蓝牙调试助手测试时使用的是16位UUID的简写比如FFE0、FFE1拿到小程序里却发现services和characteristics的UUID全带上了128位前缀。实际上这是同一回事微信API返回时会自动把16位UUID扩展成128位格式你判断时要么都转成完整字符串比较要么统一取后四位否则很容易出现明明连接上了但找不到服务的诡异问题。2.2 充电桩蓝牙模块选型与硬件适配充电桩的MCU和蓝牙模块之间最常见的是串口UART透传方式。蓝牙模块上电后自动转发串口数据和蓝牙通道之间的双向数据。我在这个项目里用到的模块支持AT指令配置可以设置广播间隔、设备名称、发射功率这些参数。硬件选型时要注意几个点模块要支持BLE 4.2及以上协议保证传输速率和稳定性供电电压要匹配MCU逻辑电平必须支持GATT的多个连接参数协商避免iOS和Android的连接行为差异导致兼容问题。特别提一下广播参数配置对小程序扫描的影响。广播间隔建议设置在100ms到300ms之间太密费电且容易造成信道拥塞太疏会导致手机扫描命中率低。发射功率根据充电桩安装位置来调室内空旷环境0dBm就够了室外可能要调到6dBm以确保10米以上的连接距离。另外广播包中建议携带一个独特的Service UUID这样小程序扫描时可以根据Service UUID直接过滤而不是扫描到一堆蓝牙音箱、手环之后再逐个判断这对用户体验影响极大。2.3 自定义服务与特征值定义协议表先行做蓝牙项目我强烈建议先画协议表再写代码。协议表是连接硬件工程师和前端工程师的桥梁也是后期排查问题的最重要文档。下面是一个简化版的充电桩GATT协议示例服务UUID特征值UUID属性说明0000FFE0-0000-1000-8000-00805F9B34FB0000FFE1-0000-1000-8000-00805F9B34FB写Write手机下发控制指令0000FFE0-0000-1000-8000-00805F9B34FB0000FFE2-0000-1000-8000-00805F9B34FB通知Notify充电桩主动上报状态0000FFE0-0000-1000-8000-00805F9B34FB0000FFE3-0000-1000-8000-00805F9B34FB读Read手机读取设备出厂信息特征值FFE1承载下行指令FFE2承载上行状态上报FFE3用来读取设备序列号和固件版本。有读者可能会问为什么要单独搞一个读的特征值因为有些状态在连接建立时就需要主动查询一次比如充电桩当前是否在充电、上次充电有没有故障记录这时候用读比等设备上报更可靠。报文结构方面我采用的是精简的帧格式帧头0xA5 长度1字节 指令1字节 数据区N字节 校验1字节。校验位用累加和就是把长度、指令、数据区所有字节相加取低8位。这套格式虽然简单但足够应对充电桩场景的数据量。启动充电指令数据区包含充电模式快充/慢充、目标电量百分比停止充电指令数据区为空状态查询指令数据区为空。充电桩回复的状态帧数据区固定15个字节包含当前电压、电流、功率、已充百分比、电池温度、充电状态、故障码设备端每隔2秒主动上报一次正好满足小程序实时展示的刷新频率。3. 小程序蓝牙核心流程实现3.1 初始化与权限处理先过系统关卡小程序里的蓝牙开发第一步不是写代码而是处理系统的权限政策。这里我把iOS和Android的差异一并说清楚。iOS从13.0开始强制要求蓝牙权限描述小程序manifest里要在IOSPrivilegedPlugins或permission描述中体现NSBluetoothAlwaysUsageDescription。Android从6.0开始蓝牙扫描需要定位权限ACCESS_FINE_LOCATIONAndroid 12及以上还要进一步申请附近的设备权限BLUETOOTH_SCAN和BLUETOOTH_CONNECT。很多安卓用户反馈点了没反应八成就是权限没申请全。小程序的权限申请流程是先调用wx.getSetting获取当前的授权状态如果蓝牙权限未开启弹窗引导用户去scope.bluetooth对应的授权页。这里有一个体验优化的细节当用户之前拒绝过权限再调用wx.authorize时不会重新弹窗而是直接进入fail回调这时需要使用wx.openSetting引导用户手动开启权限。我封装了一个权限准备函数在页面的onLoad里调用核心逻辑如下function ensureBluetoothPermission() { return new Promise((resolve, reject) { wx.getSetting({ success: (res) { if (res.authSetting[scope.bluetooth]) { resolve(); } else if (res.authSetting[scope.bluetooth] false) { wx.showModal({ title: 提示, content: 需要蓝牙权限才能连接充电桩是否前往设置开启, success: (modalRes) { if (modalRes.confirm) { wx.openSetting({ success: (settingRes) { if (settingRes.authSetting[scope.bluetooth]) { resolve(); } else { reject(new Error(用户未授权蓝牙)); } } }); } else { reject(new Error(用户取消授权)); } } }); } else { wx.authorize({ scope: scope.bluetooth, success: () resolve(), fail: () reject(new Error(授权失败)) }); } } }); }); }权限申请通过之后再调用wx.openBluetoothAdapter初始化蓝牙适配器。如果失败大概率是手机系统蓝牙没有打开这时候要提示用户去系统设置打开。注意fail回调里除了提示信息最好把错误码也打出来方便线上排查。3.2 设备扫描用Service UUID过滤别做全量扫描扫描是用户感知最直观的一步也是性能优化空间最大的地方。很多同学一上来就调用wx.startBluetoothDevicesDiscovery然后把所有扫描到的设备都展示在列表里连接的时候再逐个判断名称。这种写法不是不行但扫描结果会非常脏同一区域可能有几十个蓝牙设备在广播用户要在一堆乱码设备名里找到充电桩体验很糟糕。正确的做法是让扫描接口直接带上services参数这样系统层就会按广播包里的Service UUID做过滤。小程序startBluetoothDevicesDiscovery支持services数组参数示例const PILE_SERVICE_UUID 0000FFE0-0000-1000-8000-00805F9B34FB; wx.startBluetoothDevicesDiscovery({ services: [PILE_SERVICE_UUID], allowDuplicatesKey: true, interval: 0, success: () { wx.onBluetoothDeviceFound((res) { const devices res.devices || []; devices.forEach((device) { if (device.advertisServiceUUIDs.includes(PILE_SERVICE_UUID)) { updateDeviceList(device); } }); }); } });allowDuplicatesKey设为true时同一个设备会重复触发onBluetoothDeviceFound这样我们可以利用RSSI信号强度来优化列表排序。充电桩的蓝牙距离一般不超过10米如果RSSI低于-90dBm说明设备太远或者中间有遮挡可以在UI上提示用户靠近设备避免用户在一个基本连不上的状态里反复操作。扫描超时控制也要做。建议扫描持续8到10秒后主动调用wx.stopBluetoothDevicesDiscovery避免蓝牙模块持续工作带来的耗电问题。如果10秒内没有发现设备给用户一个未发现充电桩请确认蓝牙已开启并靠近设备后重试的友好提示。实测下来这个超时设定在绝大多数场景都能兼顾体验和功耗。一个小提示Android上如果没有执行stopBluetoothDevicesDiscovery就立刻去createBLEConnection经常会出现连接失败。官方文档虽然没有明确说明但实测在Android 11以上的机型上停止扫描后再发起连接的成功率会高很多。所以我在项目里统一做成了停止扫描 - 等待500ms - 发起连接的流程。3.3 连接、发现服务与订阅通知顺序不能乱蓝牙连接这个环节看起来简单但却是排查时长最多的部分。我先画出标准流程再逐个说明wx.createBLEConnection建立基础连接wx.getBLEDeviceServices获取服务列表wx.getBLEDeviceCharacteristics获取特征值详情wx.notifyBLECharacteristicValueChange订阅通知wx.onBLECharacteristicValueChange监听数据上报第一步建立连接后不要马上调用getBLEDeviceServices因为系统可能还没准备好GATT信息。实测在连接成功的success回调里直接获取服务偶发会返回空数组。稳妥做法是延迟300ms再获取或者使用setTimeout包裹这不算hack而是给协议栈一点反应时间。网上很多把这个问题归咎于手机兼容性其实多半是时序问题。第二步获取服务列表后遍历查找目标Service UUID。这里要特别注意大小写和格式设备固件返回的UUID可能是全大写我们代码里定义的是小写比较时要统一toUpperCase()之后再比对。我后来把所有UUID比较都封装成函数避免在多个地方手动比较产生遗漏function isTargetService(uuid) { return uuid.toUpperCase() PILE_SERVICE_UUID.toUpperCase(); }第三步获取特征值后先判断特征值的properties是否包含notify属性。如果设备定义了通知特征值但小程序侧没订阅那设备上报状态时小程序完全收不到。正式订阅调用wx.notifyBLECharacteristicValueChange({ deviceId: this.deviceId, serviceId: serviceId, characteristicId: notifyCharId, state: true, success: () { wx.onBLECharacteristicValueChange((res) { const data ArrayBufferToBytes(res.value); this.handlePileData(data); }); } });还有一点是重复订阅问题。如果页面onShow时又执行了一次notify设备端可能重复上报状态导致UI刷新频繁。我在代码里用了一个布尔标志位只在连接并且未订阅的状态下才执行订阅避免重复绑定。3.4 连接状态管理全局单例还是页面局部连接状态管理是小程序蓝牙项目里一个很容易被轻视的点。如果在每一个页面里都维护蓝牙对象的生命周期页面跳转、退到后台再回来很容易出现连接丢失、回调混乱的问题。我的方案是在App.js里挂一个全局的BluetoothManager单例统一管理适配器状态、设备ID、服务ID、特征值ID、订阅状态以及对外暴露connect、disconnect、writeData等方法。页面只依赖这个单例的接口不做底层API调用。这样带来的好处是页面A发起连接并启动充电后用户跳转到页面B查看充电进度B可以直接复用连接不需要重新握手充电桩也不会因为页面切换而断开连接。全局管理还有一个重要任务是监听系统蓝牙断开事件wx.onBLEConnectionStateChange。当充电桩断电、距离超出信号范围时系统会回调这个事件这时候要主动更新全局状态为disconnected并通知当前页面弹出连接已断开提示。如果不做状态同步用户看到UI上还显示充电中实际上充电桩早就不理你了这种体验属于严重bug级别。下面给一个简化的断开重连策略收到断线事件后自动尝试重连一次如果在3秒内重连成功静默继续当前业务如果失败主动提示用户重新扫码或者手动连接。重连次数不要超过一次否则在信号不稳定的区域会造成反复弹窗适得其反。wx.onBLEConnectionStateChange((res) { if (!res.connected) { this.connectionStatus disconnected; this.autoReconnectIfNeeded(); } });4. 充电桩业务逻辑与协议处理4.1 充电流程状态机设计充电业务不能简单理解成发指令、收状态它天然是一个状态机待机、启动中、充电中、暂停、完成、异常。如果代码里不做状态机管理而是满屏的if else判断当前界面显示什么后面需求一旦增加比如预约充电、错峰充电就会迅速失控。我把状态机定义如下状态触发事件动作下一状态IDLE用户点击启动发送启动指令STARTINGSTARTING收到充电桩确认等待状态上报CHARGINGCHARGING达到目标电量发送停止指令STOPPINGCHARGING用户点击停止发送停止指令STOPPINGSTOPPING收到充电桩停止确认展示费用与电量IDLEFAULT收到故障码展示故障信息IDLE在小程序里我用一个currentStatus字段保存状态值每次收到设备上报都会走一个统一的状态处理函数。该函数首先解析帧数据里的状态字节更新全局状态然后根据状态值决定UI跳转和弹窗。这样逻辑集中排查问题时只要关注状态机的入口和出口不需要在多个页面里到处翻回调。简单描述状态机处理的核心代码结构handlePileData(bytes) { const status bytes[4]; const payload bytes.slice(5, bytes.length - 1); switch (status) { case PILE_STATUS.IDLE: this.changeStatus(IDLE); break; case PILE_STATUS.CHARGING: this.changeStatus(CHARGING, payload); break; case PILE_STATUS.FAULT: this.changeStatus(FAULT, payload); break; } }4.2 指令协议封装字节序、校验、超时重试蓝牙数据传输本质是字节流指令的拼装与解析必须非常严谨。我这套协议规定了小端字节序低字节在前所以在用JavaScript处理时需要明确使用DataView并设置littleEndian参数。这个很容易踩坑JavaScript本身没有字节序概念ArrayBuffer里的数据只是字节序列只有用DataView按特定格式读取时才有大小端之分。如果硬件工程师的协议文档没写明字节序强烈建议先做一个握手指令测试确认字节序方向后再批量开发。校验和的计算也必须和硬件严格一致。我的场景里校验和是整个数据包所有字节的和取低8位。在PC端调试小程序时能用console打印每个字节和校验结果但在真机上就麻烦一些所以我在代码里封装了debug函数把收发内容统一打印到面板开发时开启上线时关闭。发指令给设备后设备可能因为蓝牙模块忙、总线冲突等原因没有响应。这时候必须做超时重试机制。我做了一个简单可靠的方式每次writeData之前记录当前时间并启动一个2秒的setTimeout如果2秒内没有收到对应的ACK帧就自动重发最多重发3次。超时重试要和业务逻辑解耦做成一个纯工具函数。这里有一个细节发送后的setTimeout要保存在实例上收到ACK时清除否则重发和ACK响应顺序错乱会导致业务重复触发。实际项目中用的启动充电指令是一个7字节的帧A5 06 01 00 00 64 0A其中A5是帧头06是数据长度从指令到校验之前的字节数01是启动充电指令码00是模式快充00是预留64是目标电量100%0A是校验和。小程序发送时先构造ArrayBuffer再用DataView写入字节最后通过writeBLECharacteristicValue发送。注意微信API要求写入的数据类型是ArrayBuffer不能直接传普通数组function buildFrame(cmd, payload) { const length 2 payload.length 1; const buffer new ArrayBuffer(length 1); const view new DataView(buffer); view.setUint8(0, 0xA5); view.setUint8(1, payload.length 2); view.setUint8(2, cmd); for (let i 0; i payload.length; i) { view.setUint8(3 i, payload[i]); } let checksum 0; for (let i 1; i length; i) { checksum view.getUint8(i); } view.setUint8(length, checksum 0xff); return buffer; }4.3 状态上报解析与UI数据绑定充电桩通过Notify特征值每2秒上报一次状态帧。状态帧的数据区包含电压、电流、功率等数值但注意这些值都是原始值需要在业务层换算成展示单位。比如电压字段是16位无符号整数单位0.1V实际展示时除以10电流是16位无符号整数单位0.01A展示时除以100功率可以直接由电压和电流相乘得到也可以设备端上报。这里有一个容易出错的地方由于DataView只有getUint8、getInt16这些方法读取多字节整型时要正确选择调用方式。我用getUint16(offset, true)来读小端序的16位数据。很多同事直接用getUint8拿两个字节再手动拼接容易漏掉符号位和大小端问题代码还难看。UI绑定方面小程序本身是数据驱动模式setData是单向数据流。我在页面的data里定义了一个pileStatus对象每次状态解析完成后更新这个对象并调用setData。为了避免频繁的全量setData影响性能我只更新变化的字段不做整对象重建。充电进度条则用一个环形进度组件绑定已充百分比进度动画用CSS transition实现平滑好看又不用额外引入图表库。收到充电桩上报的故障码时需要根据故障码表映射成中文提示。比如故障码01表示过温保护02表示输出过流03表示电池连接异常。故障弹窗设计成模态框用户点击确认后进入待机状态。4.4 费用估算与充电记录展示蓝牙充电桩一般不是计费终点但小程序里依然要有费用估算能力方便用户在充电途中了解大概费用。我用的是分时电价模型深谷、低谷、平时、高峰、尖峰五个时段电价不同每收到一次状态上报就根据当时时间和当前功率计算这一段时间2秒的电量消耗累加到本次充电的总电量中再乘以对应时段电价。这个算法很简单但要注意用户充电可能跨越多个电价时段不能拿一个固定电价去套整个充电过程。我把每个上报周期单独计算代码上用reduce累加。费用展示保留两位小数并在页面底部标注估算费用以实际账单为准规避误解。当然如果产品后续接入支付这里就会替换成云端的真实计费结果小程序端负责展示即可。充电结束后把本次充电的开始时间、结束时间、总电量、总费用、平均功率组装成记录通过wx.setStorageSync存在本地。开发者工具里也可以扩展成调用后端接口上报方便运营侧做统计分析。我展示记录的原生页面用了一个简单的列表从本地缓存读取后渲染。5. 常见问题与排查技巧实录5.1 连接不稳定80%是广播与权限的锅蓝牙连接不稳定的现象非常普遍但大部分情况下不是模块硬件坏了而是软件配置或环境因素。我遇到过两个典型场景。第一个是安卓手机扫描不到设备。排查了很久发现安卓系统要求定位服务处于开启状态才能扫描低功耗蓝牙。很多用户的定位权限已授权其实只是应用授权但系统定位服务被用户关掉了。所以小程序的提示文案要写清楚请开启系统定位服务后重试不能只提示请确保蓝牙已开启。第二个是设备连接后十几秒内自动断开重连一次又断开。用逻辑分析仪抓设备端数据后发现是设备端的连接参数Connection Interval和手机期望的不匹配手机侧请求了较小的连接间隔设备没有响应系统在超时后主动断开。解决方法是把设备端的连接参数调整到兼容区间比如连接间隔30ms到50ms从机延迟0超时时间2000ms并在小程序里做好断线重连兜底。硬件端的同事如果看到类似问题先查这两处比反复换手机测试效率高。5.2 数据丢失或乱码字节序、分包、写入频率蓝牙BLE单次数据包的理论上限是MTU大小一般为23字节扣除ATT协议头实际有效数据只有20字节左右。如果充电桩上报的状态帧超过20字节就会出现截断或分片小程序端收到的是不完整数据。我的解决方案有两个方向一是精简协议把状态帧压缩到20字节以内二是启用MTU协商在连接成功后通过wx.setBLEMTU请求更大的MTU值比如247字节但注意Android和iOS对这个接口的支持有差异Android可以通过setBLEMTU扩展iOS一般自动协商且不开放手动设置。写入频率过高也是丢包的重灾区。有些开发者为了UI刷新流畅每500ms就发一次查询指令这会给BLE链路造成压力设备端如果处理不过来就会丢失指令或响应延迟。我实测下来查询类指令间隔1.5秒以上比较稳妥状态上报由设备主动推送理论上不吃查询指令所以充电场景下发指令频率很低真正考验的是设备端上报的连续性。另外连续两条写指令之间建议间隔至少100ms蓝牙芯片在Received数据后需要时间处理连续写入会导致缓冲区溢出。乱码问题大概率是字节序或错位解析。排查时最有效的工具不是盲改代码而是提前打印原始字节流。我在项目里写了一个helperArrayBuffer转十六进制字符串网络上的抓包数据也能直接对得上。出现乱码时先确认收到的字节和硬件测试工具收到的字节是否一致如果一致说明传输没问题问题一定在解析逻辑上如果不一致再去查分包和MTU。这个排查思路能省掉大量时间。5.3 多机型兼容性Android碎片化避坑指南小程序BLE开发最大的痛点不是iOS而是Android的碎片化。不同厂商的ROM对BLE的支持天差地别我整理了以下常见问题机型/系统现象解决方案小米/Redmi扫描不到设备确认系统定位服务开启尝试关闭MIUI优化后再试华为/荣耀连接后获取服务为空连接后延迟500ms再getBLEDeviceServicesOPPO/vivo连接成功率低关闭省电模式蓝牙优化策略设为兼容模式Android 13权限提示不弹出在app.json中声明androidPermissions需要的蓝牙权限Android 14后台扫描被限制前台运行时扫描避免页面hide后仍持续扫描针对Android扫描不到的常见解决方法我习惯在进入扫码页时先调用wx.getBluetoothAdapterState获取系统蓝牙状态再开始扫描。如果返回蓝牙未打开直接引导去设置页打开。这样能拦截掉一大批底锅问题让后续流程更专注。还有一个开发环境的小提示微信开发者工具中模拟蓝牙扫描是走电脑的蓝牙适配器很多场景下电脑没有BLE能力所以模拟器里调用openBluetoothAdapter会失败这是正常现象。真正的蓝牙联调必须在真机上。很多新手在这里卡住以为代码写错了实际上只是开发环境的物理限制。我建议从一开始就把蓝牙相关页面设为真机调试模式在工具栏选择预览扫码能够极大减少误判。5.4 真机调试与定位问题的实用技巧小程序开发工具自带真机调试和预览两个入口第一次跑蓝牙项目时务必选真机调试模式因为它会连接开发机把console日志实时同步过来真机上的蓝牙日志直接在开发工具的Console面板可见排查效率非常高。还有一个技巧是善用第三方BLE调试助手做标准答案。手机上装一个nRF Connect或者LightBlue先手动连接充电桩一步步操作写入指令、接收通知看回复是否和预期帧一致。如果调试助手能正常收发、小程序不行那问题在小程序代码如果调试助手也不行问题大概率在设备端或协议定义。这个二分法几乎适用于所有蓝牙联调场景。最后再说一个我在项目里做得比较晚但效果显著的改进把蓝牙上报数据的解析和页面UI解耦做成独立的library模块。这样即使不打开页面小程序在后台用户切到其他页面也能继续接收状态数据并更新全局状态。后来用户从充电进度页切到首页再切回来状态不会丢体验提升了一个等级。我强烈建议所有做蓝牙小程序的朋友都往这个方向重构代码。6. 想清楚这几点你的充电桩小程序能少走一半弯路在这套项目真正跑起来之前有几个问题我希望再强调一遍因为它们决定项目的整体走向比单纯写代码更重要。先想清楚产品定位你的充电桩是面向私人家庭用户还是面向园区、停车场这类准公共场景家庭用户对蓝牙方案非常友好扫码即用、数据本地化几乎没有运维成本。但如果你计划做共享运营就一定要在架构上预留云端同步通道蓝牙负责本地控制云平台负责计费和远程运维两者并存而不是二选一。这个架构决策需要在项目早期确定否则后期切换成本极高。其次是安全与容错设计。充电桩控制的是大功率设备指令下发必须可靠。我在协议中加入了指令ACK机制设备收到指令后无论成功失败都要回一个确认帧失败要带原因码。小程序的超时重试必须设置上限避免在故障状态下反复给设备发指令。用户侧的权限管控也要做连接蓝牙前判断设备MAC地址是否在白名单内或者要求用户在页面上输入充电桩侧贴的配对码这两种方式都可以有效防止陌生设备被试探控制。然后是维护与升级思路。蓝牙设备端的固件升级是绕不开的话题。小程序在技术上是可以直接给设备做OTA的通过蓝牙通道把固件包分片传输给设备的Bootloader。虽然这里没有展开执行细节但建议前期就在协议中预留固件升级服务和特征值哪怕一开始不开发升级页面也要把协议位占好否则后期追加升级功能时发现特征值不够用或者适配表冲突改动会牵一发而动全身。我个人在实际操作中最深刻的体会是硬件类小程序开发和纯前端开发完全是两种节奏。纯前端你只要拿到接口文档就能开写但蓝牙项目里前端工程师必须理解设备端的工作方式、广播行为、连接参数、数据协议甚至要会看一点逻辑分析仪的输出。不要怕这些跨界知识它们恰恰是硬件小程序开发中最有价值的部分。多和硬件工程师吵几次协议你写出来的代码会比闷头开发健壮得多。这套充电桩小程序的思路不仅适用于新能源充电桩所有基于BLE的低功耗硬件控制场景——便携设备、健康设备、智能家居——几乎都可以复用。你只要把Service UUID换成自己的、协议帧按硬件规格调整剩下整个蓝牙连接管理、状态机设计、断线重连、真机调试方法论都能平移过去。这也是我把这套结构分享出来最大的价值所在。本文还有配套的精品资源点击获取
返回列表