├── .gitignore ├── LICENSE ├── README.md ├── _config.yml ├── app.js ├── app.json ├── app.wxss ├── log ├── ChangeLog.md └── QA.md ├── modules └── bluetooth │ ├── lb-ble-common-connection │ ├── README.md │ ├── base │ │ ├── abstract-bluetooth.js │ │ ├── base-bluetooth-imp.js │ │ ├── base-bluetooth.js │ │ ├── common-ble-connection-operation.js │ │ ├── inter │ │ │ └── i-ble-operator.js │ │ └── wx │ │ │ └── apis.js │ ├── index.js │ ├── lb-bluetooth-manager.js │ ├── package.json │ └── utils │ │ ├── ble-observer.js │ │ ├── device-connection-manager.js │ │ └── mix.js │ ├── lb-ble-common-protocol-body │ ├── README.md │ ├── i-protocol-receive-body.js │ ├── i-protocol-send-body.js │ └── index.js │ ├── lb-ble-common-protocol-operator │ ├── README.md │ ├── index.js │ └── lb-bluetooth-protocol-operator.js │ ├── lb-ble-common-state │ ├── README.md │ ├── index.js │ └── state.js │ ├── lb-ble-common-tool │ ├── README.md │ ├── index.js │ └── tools.js │ ├── lb-ble-example-protocol-body │ ├── README.md │ ├── receive-body.js │ └── send-body.js │ ├── lb-bluetooth-state-example.js │ ├── lb-example-bluetooth-manager.js │ └── lb-example-bluetooth-protocol.js ├── pages └── bluetooth │ ├── bluetooth.js │ ├── bluetooth.json │ ├── bluetooth.wxml │ ├── bluetooth.wxss │ └── ui.js └── view └── toast.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | /package.json 3 | /project.config.json 4 | /package-lock.json 5 | /.idea/ 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 刘彪 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wx-simple-bluetooth 2 | 3 | 名称:wx-simple-bluetooth 4 | 5 | 适用平台:微信小程序 6 | 7 | 蓝牙:低功耗蓝牙 8 | 9 | 项目版本:2.0.1 10 | 11 | 这个项目从蓝牙连接、蓝牙协议通信、状态订阅及通知三个层面进行设计,可以很方便的定制您自己的小程序的蓝牙开发。主要功能如下: 12 | 13 | **!!!需要先开启微信开发工具的增强编译!!!** 14 | 15 | `这个项目从蓝牙连接、蓝牙协议通信、状态订阅及通知三个层面进行设计,可以很方便的定制您自己的小程序的蓝牙开发。主要功能如下`: 16 | 17 | 以下是在手机开启了蓝牙功能、GPS以及微信定位权限的情况下: 18 | - 执行`getAppBLEManager.connect()`会自动扫描周围的蓝牙设备,每`350ms`扫描一次,在该次内连接信号最强的设备。 19 | - 可设置扫描周边蓝牙设备时,主 `service` 的 `uuid` 列表,以及对应的用于通信的服务id;还可额外添加蓝牙设备名称来进一步筛选设备。 20 | - 可订阅蓝牙连接状态更新事件,并同步通知前端。 21 | - 可订阅获取接收到的蓝牙协议事件。依据您的配置,框架内部会自行处理,并只返回最需要的数据给前端。 22 | - `注意:目前在发送数据时大于20包的数据会被裁剪为20包`。 23 | 24 | ## 已更新为2.x.x版本! 25 | ### 新的版本包括多种新的特性: 26 | - 提供了完整的示例及较为详细的注释。 27 | - 重构了蓝牙连接以及重连的整个流程,使其更加稳定和顺畅,也提高了部分场景下重连的速度。`(有些蓝牙连接问题是微信兼容或是手机问题,目前是无法解决的。如错误码10003以及部分华为手机蓝牙连接或重连困难。如果您有很好的解决方案,还请联系我,十分感谢)` 28 | - 只有在连接到蓝牙设备并使用特征值注册了read、write、notify监听后才算连接成功。 29 | - 在扫描周围设备时,可按自定义的规则过滤多余设备,连接指定设备。详见示例`lb-example-bluetooth-manager.js`。 30 | - 新增蓝牙协议配置文件,可以很方便的发送和接收蓝牙协议。 31 | - 小程序退入后台会自动缓存要发送的协议,待回到前台后重新发送(如果[小程序冷启动](https://developers.weixin.qq.com/miniprogram/dev/framework/runtime/operating-mechanism.html)了,则协议队列会被重新初始化)。 32 | - 优化了蓝牙状态更新和蓝牙协议更新的订阅方式。现在可以更清晰的区分是蓝牙的状态更新还是接收到了新的协议(以及接收到的协议数据是什么),并且会过滤掉与上一条完全相同的通知。 33 | - 可随时获取到最新的蓝牙连接状态。 34 | - 各个业务均高度模块化,在深入了解后,可以很方便的拓展。 35 | 36 | ### 微信小程序官方蓝牙的部分说明: 37 | 1. 小程序不会对写入数据包大小做限制,但系统与蓝牙设备会限制蓝牙4.0单次传输的数据大小,超过最大字节数后会发生写入错误,建议每次写入不超过20字节。 38 | 2. 若单次写入数据过长,iOS 上存在系统不会有任何回调的情况(包括错误回调)。 39 | 3. wx.writeBLECharacteristicValue并行调用多次会存在写失败的可能性。 40 | 41 | **所以基于这方面的考虑,本框架有以下约束:** 42 | > 1. 必须按照约定的协议格式来制定协议,才能正常使用该框架。 43 | > 2. 协议一包数据最多20个字节。该框架不支持大于20个字节的协议格式。如果数据超出限制,建议拆分为多次发送。 44 | > 3. 建议以串行方式执行写操作。 45 | > 4. 建议先了解清楚[小程序的官方蓝牙文档](https://developers.weixin.qq.com/miniprogram/dev/api/device/bluetooth-ble/wx.writeBLECharacteristicValue.html),方便理解框架的使用。 46 | 47 | ``` 48 | 协议约定格式:[...命令字之前的数据(非必需), 命令字(必需), ...有效数据(非必需 如控制灯光发送255,255,255), 有效数据之后的数据(非必需 如协议结束标志校、验位等) 49 | 协议格式示例:[170(帧头), 10(命令字), 1(灯光开启),255,255,255(三个255,白色灯光),233(协议结束标志,有的协议中没有这一位),18(校验位,我胡乱写的)] 50 | 51 | 有效数据是什么: 52 | 在刚刚的这个示例中,帧头、协议结束标志是固定的值,校验位是按固定算法生成的,这些不是有效数据。而1,255,255,255这四个字节是用于控制蓝牙设备的,属于有效数据。 53 | ``` 54 | 该项目如有帮助,希望在GitHub上给个star! 55 | 56 | ## 快速集成示例 57 | 58 | - 使用前必须检查手机是否开启蓝牙功能、定位功能、微信一定要给予定位权限(较新的iOS版微信要给予蓝牙权限) 59 | - 导入项目下的`modules`文件夹到你的项目 60 | 61 | ### 1、在小程序页面中设置事件订阅及蓝牙连接、断开示例 62 | 63 | ``` 64 | import Toast from "../../view/toast"; 65 | import UI from './ui'; 66 | import {ConnectState} from "../../modules/bluetooth/lb-bluetooth-state-example"; 67 | import {getAppBLEProtocol} from "../../modules/bluetooth/lb-example-bluetooth-protocol"; 68 | import {getAppBLEManager} from "../../modules/bluetooth/lb-example-bluetooth-manager"; 69 | 70 | const app = getApp(); 71 | Page({ 72 | 73 | /** 74 | * 页面的初始数据 75 | */ 76 | data: { 77 | connectState: ConnectState.UNAVAILABLE 78 | }, 79 | 80 | /** 81 | * 生命周期函数--监听页面加载 82 | */ 83 | onLoad(options) { 84 | this.ui = new UI(this); 85 | console.log(app); 86 | //监听蓝牙连接状态、订阅蓝牙协议接收事件 87 | //多次订阅只会在最新订阅的函数中生效。 88 | //建议在app.js中订阅,以实现全局的事件通知 89 | getAppBLEManager.setBLEListener({ 90 | onConnectStateChanged: async (res) => { 91 | const {connectState} = res; 92 | console.log('蓝牙连接状态更新', res); 93 | this.ui.setState({state: connectState}); 94 | switch (connectState) { 95 | case ConnectState.CONNECTED: 96 | //在连接成功后,紧接着设置灯光颜色和亮度 97 | //发送协议,官方提醒并行调用多次会存在写失败的可能性,所以建议使用串行方式来发送 98 | await getAppBLEProtocol.setColorLightAndBrightness({ 99 | brightness: 100, 100 | red: 255, 101 | green: 0, 102 | blue: 0 103 | }); 104 | 105 | break; 106 | default: 107 | 108 | break; 109 | } 110 | 111 | }, 112 | 113 | /** 114 | * 接收到的蓝牙设备传给手机的有效数据,只包含你最关心的那一部分 115 | * protocolState和value具体的内容是在lb-example-bluetooth-protocol.js中定义的 116 | * 117 | * @param protocolState 蓝牙协议状态值,string类型,值是固定的几种,详情示例见: 118 | * @param value 传递的数据,对应lb-example-bluetooth-protocol.js中的{effectiveData}字段 119 | */ 120 | onReceiveData: ({protocolState, value}) => { 121 | console.log('蓝牙协议接收到新的 protocolState:', protocolState, 'value:', value); 122 | } 123 | }); 124 | 125 | //这里执行连接后,程序会按照你指定的规则(位于getAppBLEManager中的setFilter中指定的),自动连接到距离手机最近的蓝牙设备 126 | getAppBLEManager.connect(); 127 | }, 128 | 129 | /** 130 | * 断开连接 131 | * @param e 132 | * @returns {Promise} 133 | */ 134 | async disconnectDevice(e) { 135 | // closeAll() 会断开蓝牙连接、关闭适配器 136 | await getAppBLEManager.closeAll(); 137 | this.setData({ 138 | device: {} 139 | }); 140 | setTimeout(Toast.success, 0, '已断开连接'); 141 | }, 142 | 143 | /** 144 | * 连接到最近的设备 145 | */ 146 | connectHiBreathDevice() { 147 | getAppBLEManager.connect(); 148 | }, 149 | 150 | async onUnload() { 151 | await getAppBLEManager.closeAll(); 152 | }, 153 | }); 154 | 155 | ``` 156 | 157 | ### 2、接下来是如何定制你自己的蓝牙业务: 158 | #### 1. 设置你自己的`setFilter`函数参数、扫描过滤规则(可选) 159 | 文件位于`./modules/bluetooth/lb-example-bluetooth-manager.js` 160 | 161 | ``` 162 | 163 | import {LBlueToothManager} from "./lb-ble-common-connection/index"; 164 | import {getAppBLEProtocol} from "./lb-example-bluetooth-protocol"; 165 | 166 | /** 167 | * 蓝牙连接方式管理类 168 | * 初始化蓝牙连接时需筛选的设备,重写蓝牙连接规则 169 | */ 170 | export const getAppBLEManager = new class extends LBlueToothManager { 171 | constructor() { 172 | super(); 173 | //setFilter详情见父类 174 | super.setFilter({ 175 | services: ['0000xxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'],//必填 176 | targetServiceArray: [{ 177 | serviceId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',//必填 178 | writeCharacteristicId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxE',//必填 179 | notifyCharacteristicId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxF',//必填 180 | readCharacteristicId: '',//非必填 181 | }], 182 | targetDeviceName: '目标蓝牙设备的广播数据段中的 LocalName 数据段,如:smart-voice',//非必填,在判断时是用String.prototype.includes()函数来处理的,所以targetDeviceName不必是全称 183 | scanInterval: 350//扫描周围设备,重复上报的时间间隔,毫秒制,非必填,默认是350ms 184 | }); 185 | super.initBLEProtocol({bleProtocol: getAppBLEProtocol}); 186 | //setMyFindTargetDeviceNeedConnectedFun函数调用可选,不实现过滤规则框架会按默认规则执行 187 | super.setMyFindTargetDeviceNeedConnectedFun({ 188 | /** 189 | * 重复上报时的过滤规则,并返回过滤结果 190 | * 在执行完该过滤函数,并且该次连接蓝牙有了最终结果后,才会在下一次上报结果回调时,再次执行该函数。 191 | * 所以如果在一次过滤过程中或是连接蓝牙,耗时时间很长,导致本次连接结果还没得到,就接收到了下一次的上报结果,则会忽略下一次{scanFilterRuler}的执行。 192 | * 如果不指定这个函数,则会使用默认的连接规则 193 | * 默认的连接规则详见 lb-ble-common-connection/utils/device-connection-manager.js的{defaultFindTargetDeviceNeedConnectedFun} 194 | * @param devices {*}是wx.onBluetoothDeviceFound(cb)中返回的{devices} 195 | * @param targetDeviceName {string}是{setFilter}中的配置项 196 | * @returns targetDevice 最终返回对象{targetDevice},是数组{devices}其中的一个元素;{targetDevice}可返回null,意思是本次扫描结果未找到指定设备 197 | */ 198 | scanFilterRuler: ({devices, targetDeviceName}) => { 199 | console.log('执行自定义的扫描过滤规则'); 200 | const tempFilterArray = []; 201 | for (let device of devices) { 202 | if (device.localName?.includes(targetDeviceName)) { 203 | tempFilterArray.push(device); 204 | } 205 | } 206 | if (tempFilterArray.length) { 207 | const device = tempFilterArray.reduce((pre, cur) => { 208 | return pre.RSSI > cur.RSSI ? pre : cur; 209 | }); 210 | return {targetDevice: device}; 211 | } 212 | return {targetDevice: null}; 213 | } 214 | }) 215 | } 216 | 217 | /** 218 | * 获取本机蓝牙适配器状态 219 | * @returns {Promise<*>} 返回值见小程序官网 wx.getBluetoothAdapterState 220 | */ 221 | async getBLEAdapterState() { 222 | return await super.getBLEAdapterState(); 223 | } 224 | 225 | /** 226 | * 获取最新的蓝牙连接状态 227 | * @returns {*} 228 | */ 229 | getBLELatestConnectState() { 230 | return super.getBLELatestConnectState(); 231 | } 232 | 233 | 234 | }(); 235 | 236 | 237 | ``` 238 | #### 2. 按约定的的蓝牙协议格式组装你自己的收发数据。 239 | 240 | 文件位于`./modules/bluetooth/lb-ble-example-protocol-body` 241 | 242 | ``` 243 | //send-body.js 244 | 245 | import {IBLEProtocolSendBody} from "../lb-ble-common-protocol-body/index"; 246 | import {HexTools} from "../lb-ble-common-tool/index"; 247 | 248 | /** 249 | * 组装蓝牙协议发送数据示例 250 | * 该框架的蓝牙协议必须按照约定格式来制定,最多20个字节 251 | */ 252 | export default class SendBody extends IBLEProtocolSendBody { 253 | 254 | getDataBeforeCommandData({command, effectiveData} = {}) { 255 | //有效数据前的数据 该示例只返回了帧头110 256 | return [110]; 257 | } 258 | 259 | getDataAfterEffectiveData({command, effectiveData} = {}) { 260 | //协议结束标志 261 | const endFlag = 233; 262 | //该示例中checkSum的生成规则是计算协议从第0个元素累加到结束标志 263 | let checkSum = endFlag + HexTools.hexToNum(command); 264 | for (let item of this.getDataBeforeCommandData()) { 265 | checkSum += item; 266 | } 267 | for (let item of effectiveData) { 268 | checkSum += item; 269 | } 270 | //生成有效数据之后的数据 271 | return [endFlag, checkSum]; 272 | } 273 | } 274 | ``` 275 | ----------------------------------- 276 | 277 | ``` 278 | //receive-body.js 279 | 280 | import {IBLEProtocolReceiveBody} from "../lb-ble-common-protocol-body/index"; 281 | 282 | /** 283 | * 组装蓝牙协议接收数据示例 284 | * 该框架的蓝牙协议必须按照约定格式来制定,最多20个字节 285 | */ 286 | export default class ReceiveBody extends IBLEProtocolReceiveBody { 287 | constructor() { 288 | //commandIndex 命令字位置索引 289 | //effectiveDataStartIndex 有效数据开始索引,比如:填写0,{getEffectiveReceiveDataLength}中返回20,则会在{LBlueToothProtocolOperator}的子类{getReceiveAction}实现中,在参数中返回所有数据 290 | super({commandIndex: 1, effectiveDataStartIndex: 0}); 291 | } 292 | 293 | /** 294 | * 获取有效数据的字节长度 295 | * 该长度可根据接收到的数据动态获取或是计算,或是写固定值均可 296 | * 有效数据字节长度是指,在协议中由你的业务规定的具有特定含义的值的总字节长度 297 | * 有效数据更多的说明,以及该长度的计算规则示例,见 IBLEProtocolReceiveBody 类的 {getEffectiveReceiveData}函数 298 | * 299 | * @param receiveArray 接收到的一整包数据 300 | * @returns {number} 有效数据的字节长度 301 | */ 302 | getEffectiveReceiveDataLength({receiveArray}) { 303 | return 20; 304 | } 305 | } 306 | ``` 307 | 308 | #### 3. 实现对有效数据的发送、接收处理 309 | 位于`modules/bluetooth/lb-example-bluetooth-protocol.js` 310 | ``` 311 | import {LBlueToothProtocolOperator} from "./lb-ble-common-protocol-operator/index"; 312 | import SendBody from "./lb-ble-example-protocol-body/send-body"; 313 | import ReceiveBody from "./lb-ble-example-protocol-body/receive-body"; 314 | import {ProtocolState} from "./lb-bluetooth-state-example"; 315 | 316 | /** 317 | * 蓝牙协议管理类 318 | * 在这个类中,以配置的方式来编写读操作和写操作 319 | * 配置方式见下方示例 320 | */ 321 | export const getAppBLEProtocol = new class extends LBlueToothProtocolOperator { 322 | constructor() { 323 | super({protocolSendBody: new SendBody(), protocolReceiveBody: new ReceiveBody()}); 324 | } 325 | 326 | /** 327 | * 写操作(仅示例) 328 | */ 329 | getSendAction() { 330 | return { 331 | /** 332 | * 0x01:设置灯色(写操作) 333 | * @param red 0x00 - 0xff 334 | * @param green 0x00 - 0xff 335 | * @param blue 0x00 - 0xff 336 | * @returns {Promise} 337 | */ 338 | '0x01': async ({red, green, blue}) => { 339 | return await this.sendProtocolData({command: '0x01', effectiveData: [red, green, blue]}); 340 | }, 341 | 342 | /** 343 | * 0x02:设置灯亮度(写操作) 344 | * @param brightness 灯亮度值 0~100 对应最暗和最亮 345 | * @returns {Promise} 346 | */ 347 | '0x02': async ({brightness}) => { 348 | //data中的数据,填写多少个数据都可以,可以像上面的3位,也可以像这条6位。你只要能保证data的数据再加上你其他的数据,数组总长度别超过20个就行。 349 | return await this.sendProtocolData({command: '0x02', effectiveData: [brightness, 255, 255, 255, 255, 255]}); 350 | }, 351 | } 352 | } 353 | 354 | /** 355 | * 读操作(仅示例) 356 | * {dataArray}是一个数组,包含了您要接收的有效数据。 357 | * {dataArray}的内容是在lb-ble-example-protocol-body.js中的配置的。 358 | * 是由您配置的 dataStartIndex 和 getEffectiveReceiveDataLength 共同决定的 359 | */ 360 | getReceiveAction() { 361 | return { 362 | /** 363 | * 获取设备当前的灯色(读) 364 | * 可return蓝牙协议状态protocolState和接收到的数据effectiveData, 365 | * 该方法的返回值,只要拥有非空的protocolState,该框架便会同步地通知前端同protocolState类型的消息 366 | * 当然是在你订阅了setBLEListener({onReceiveData})时才会在订阅的地方接收到消息。 367 | */ 368 | '0x10': ({dataArray}) => { 369 | const [red, green, blue] = dataArray; 370 | return {protocolState: ProtocolState.RECEIVE_COLOR, effectiveData: {red, green, blue}}; 371 | }, 372 | /** 373 | * 获取设备当前的灯亮度(读) 374 | */ 375 | '0x11': ({dataArray}) => { 376 | const [brightness] = dataArray; 377 | return {protocolState: ProtocolState.RECEIVE_BRIGHTNESS, effectiveData: {brightness}}; 378 | }, 379 | /** 380 | * 接收到设备主动发送的灯光关闭消息 381 | * 模拟的场景是,用户关闭了设备灯光,设备需要主动推送灯光关闭事件给手机 382 | */ 383 | '0x12': () => { 384 | //你可以不传递effectiveData 385 | return {protocolState: ProtocolState.RECEIVE_LIGHT_CLOSE}; 386 | }, 387 | /** 388 | * 接收到蓝牙设备的其他一些数据 389 | */ 390 | '0x13': ({dataArray}) => { 391 | //do something 392 | //你可以不返回任何值 393 | } 394 | }; 395 | } 396 | 397 | /** 398 | * 设置灯亮度和颜色 399 | * @param brightness 400 | * @param red 401 | * @param green 402 | * @param blue 403 | * @returns {Promise<[unknown, unknown]>} 404 | */ 405 | async setColorLightAndBrightness({brightness, red, green, blue}) { 406 | //发送协议,小程序官方提醒并行调用多次会存在写失败的可能性,所以建议使用串行方式来发送,哪种方式由你权衡 407 | //但我这里是并行发送了两条0x01和0x02两条协议,仅演示用 408 | return Promise.all([this.sendAction['0x01']({red, green, blue}), this.sendAction['0x02']({brightness})]); 409 | } 410 | 411 | }(); 412 | ``` 413 | 414 | #### 4.(非必须)拓展蓝牙连接和协议状态。 415 | 文件位于`modules/bluetooth/lb-bluetooth-state-example.js` 416 | 417 | ``` 418 | import {CommonConnectState, CommonProtocolState} from "./lb-ble-common-state/index"; 419 | 420 | //特定的蓝牙设备的协议状态,用于拓展公共的蓝牙协议状态 421 | //使用场景: 422 | //在手机接收到蓝牙数据成功或失败后,该框架会生成一条消息,包含了对应的蓝牙协议状态值{protocolState}以及对应的{effectiveData}(effectiveData示例见 lb-example-bluetooth-protocol.js), 423 | //在{setBLEListener}的{onReceiveData}回调函数中,对应参数{protocolState}和{value}(value就是effectiveData) 424 | const ProtocolState = { 425 | ...CommonProtocolState, 426 | RECEIVE_COLOR: 'receive_color',//获取到设备的颜色值 427 | RECEIVE_BRIGHTNESS: 'receive_brightness',//获取到设备的亮度 428 | RECEIVE_LIGHT_CLOSE: 'receive_close',//获取到设备灯光关闭事件 429 | }; 430 | 431 | export { 432 | ProtocolState, CommonConnectState as ConnectState 433 | }; 434 | ``` 435 | 436 | ## 深入了解框架 437 | 438 | | 业务 | 对应文件夹 | 示例文件 | 439 | | ---- | ---- | -----| 440 | | 蓝牙连接 | `lb-ble-common-connection`(连接、断连、重连事件的处理) | `abstract-bluetooth.js`(最简单的、调用平台API的连接、断开蓝牙等处理)
`base-bluetooth.js`(记录连接到的设备的deviceId、特征值、连接状态等信息,处理蓝牙数据的发送、蓝牙重连)
`base-bluetooth-imp.js`(对蓝牙连接结果的捕获,监听蓝牙扫描周围设备、连接、适配器状态事件并给予相应处理) | * | 441 | | 蓝牙协议的组装 | `lb-ble-common-protocol-body`(实现协议收发格式的组装) | `i-protocol-receive-body.js`
`i-protocol-send-body.js` | 442 | | 蓝牙协议的收发 | `lb-ble-common-protocol-operator`(处理发送数据和接收数据的代理) | `lb-bluetooth-protocol-operator.js` | 443 | | 蓝牙协议的重发 | `lb-ble-common-connection` | `lb-bluetooth-manager.js`(详见`LBlueToothCommonManager`) | 444 | | 蓝牙状态及协议状态 | `lb-ble-common-state` | `lb-bluetooth-state-example.js`,可额外拓展新的状态 | 445 | | 蓝牙连接和协议状态事件的订阅 | `lb-ble-common-connection/base` | `base-bluetooth-imp.js` | 446 | 447 | 下面讲下蓝牙连接和协议状态的分发 448 | 449 | ### 蓝牙连接状态事件的分发 450 | 文件位于`lb-ble-common-connection/base/base-bluetooth.js` 451 | 452 | 1. 某一时刻连接状态改变,将新的状态赋值给`latestConnectState`对象。 453 | 2. 触发其`setter`函数`set latestConnectState`。 454 | 3. 执行`setter`内部的`_onConnectStateChanged`函数回调。 455 | 4. 在`getAppBLEManager.setBLEListener`的`onConnectStateChanged({connectState})`函数中接收到连接状态。 456 | 457 | 458 | ### 蓝牙协议状态事件的分发 459 | 460 | `onBLECharacteristicValueChange`位于`lb-ble-common-connection/abstract-bluetooth.js` 461 | `receiveOperation`位于`lb-ble-common-protocol-operator/lb-bluetooth-protocol-operator.js` 462 | 463 | 在`onBLECharacteristicValueChange`函数中,我在接收到数据后,将数据按`receive-body.js`来截取有效数据,并按`lb-example-bluetooth-protocol.js`中`getReceiveAction`的配置方式来处理有效数据,生产出对应的`value, protocolState`。 464 | `filter`是在接收到未知协议时会生成。 465 | ``` 466 | onBLECharacteristicValueChange((res) => { 467 | console.log('接收到消息', res); 468 | if (!!valueChangeListener) { 469 | const {value, protocolState, filter} = this.dealReceiveData({receiveBuffer: res.value}); 470 | !filter && valueChangeListener({protocolState, value}); 471 | } 472 | }); 473 | 474 | ``` 475 | 476 | 这段代码看起来简单,但背后要经历很多流程。 477 | 最关键的是这一行`const {value, protocolState, filter} = this.dealReceiveData({receiveBuffer: res.value});`。 478 | 下面我详细的讲一下这一行做了哪些事儿: 479 | 480 | 1. 执行`dealReceiveData`函数处理协议数据。这里的`dealReceiveData`,最终交由`lb-bluetooth-manager.js`中的`dealReceiveData`函数来处理数据。 481 | 2. 在`dealReceiveData`中执行`this.bluetoothProtocol.receive({receiveBuffer})`来生成有效数据和协议状态。这个`receive`最终交由`receiveOperation`函数执行。 482 | 3. `receiveOperation`在执行时会引用到`LBlueToothProtocolOperator`的子类的配置项`getReceiveAction`(子类是`lb-example-bluetooth-protocol.js`)。 483 | 4. `getReceiveAction`按开发者自己的实现最终返回约定对象`{protocolState,effectiveData}`,该对象返回给`receiveOperation`后进行一次检查(对未在`getReceiveAction`中配置的协议`protocolState`按`CommonProtocolState.UNKNOWN`处理),将该约定对象返回给`dealReceiveData`函数中的局部变量`effectiveData, protocolState`。 484 | 5. `protocolState!==CommonProtocolState.UNKNOWN`的对应对象,会被标记为`filter:true`;否则将约定对象返回给`onBLECharacteristicValueChange`函数中的局部变量`value, protocolState`。 485 | 486 | 以上是这一行代码所做的所有事情。 487 | 488 | 约定对象,会作为参数传入`valueChangeListener({protocolState, value})`并执行回调。 489 | 之后前端就能接收到订阅的事件啦,即在`getAppBLEManager.setBLEListener`的`onReceiveData({protocolState, value})`函数中接收到协议类型和`value`对象。 490 | 491 | 492 | ## LINK 493 | 494 | [Document](https://blog.csdn.net/sinat_27612147/article/details/84634432) 495 | 496 | [ChangeLog](https://github.com/unmagic/wx-simple-bluetooth/blob/master/log/ChangeLog.md) 497 | 498 | [QA](https://github.com/unmagic/wx-simple-bluetooth/blob/master/log/QA.md) 499 | 500 | [LICENSE](https://github.com/unmagic/wx-simple-bluetooth/blob/master/LICENSE) 501 | 502 | ## 交流 503 | 504 | 技术交流请加QQ群:821711186 505 | 506 | ## 欢迎打赏 507 | 508 | ![微信打赏码](https://github.com/unmagic/.gif/blob/master/admire/weixin.png) 509 | ![支付宝二维码](https://github.com/unmagic/.gif/blob/master/admire/zhifubao.png) 510 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | App({ 2 | onLaunch() { 3 | 4 | 5 | }, 6 | globalData: {} 7 | }); 8 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages": [ 3 | "pages/bluetooth/bluetooth" 4 | ], 5 | "window": { 6 | "navigationBarTextStyle": "black", 7 | "navigationBarTitleText": "蓝牙演示", 8 | "navigationBarBackgroundColor": "#F8F8F8", 9 | "backgroundColor": "#F8F8F8" 10 | }, 11 | "networkTimeout": { 12 | "request": 10000, 13 | "connectSocket": 10000, 14 | "uploadFile": 10000, 15 | "downloadFile": 10000 16 | }, 17 | "debug": true, 18 | "sitemapLocation": "sitemap.json" 19 | } -------------------------------------------------------------------------------- /app.wxss: -------------------------------------------------------------------------------- 1 | /* reset */ 2 | page { 3 | background-color: #F8F8F8; 4 | height: 100%; 5 | font-size: 32rpx; 6 | line-height: 1.6; 7 | } 8 | -------------------------------------------------------------------------------- /log/ChangeLog.md: -------------------------------------------------------------------------------- 1 | 2 | ### 更新日志: 3 | 4 | 2020-03-26 5 | - 新增了接口,可自行实现扫描过滤规则。 6 | - 新增了上报时间间隔参数的配置。 7 | -------------------------------------------------------------------------------- /log/QA.md: -------------------------------------------------------------------------------- 1 | ### 常见问题及解决方案 2 | 依据反馈更新 3 | 4 | #### 疑难问题及解答 5 | ``` 6 | 2.x.x版本 7 | 8 | 9 | ``` 10 | 11 | #### bug 12 | ``` 13 | ``` 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /modules/bluetooth/lb-ble-common-connection/README.md: -------------------------------------------------------------------------------- 1 | # 小程序端蓝牙模块 2 | -------------------------------------------------------------------------------- /modules/bluetooth/lb-ble-common-connection/base/abstract-bluetooth.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 微信小程序蓝牙功能的底层封装 3 | * 该类的所有业务均为最基础的部分,是不需要进行修改的 4 | * 呵呵哒认为这个类是抽象的,这就意味着该类只能被继承(虽然JS中没有抽象类) 5 | * 6 | */ 7 | 8 | import { 9 | closeBLEConnection, 10 | closeBlueToothAdapter, 11 | createBLEConnection, 12 | getBlueToothAdapterState, 13 | getConnectedBlueToothDevices, 14 | notifyBLE, 15 | onBLECharacteristicValueChange, 16 | openBlueToothAdapter, 17 | startBlueToothDevicesDiscovery, 18 | stopBlueToothDevicesDiscovery, 19 | writeBLECharacteristicValue 20 | } from "./wx/apis"; 21 | import IBLEOperator from "./inter/i-ble-operator"; 22 | import {scanInterval} from "../utils/device-connection-manager"; 23 | 24 | 25 | // function dontNeedOperation({errMsg}) { 26 | // console.warn(errMsg); 27 | // return Promise.resolve({errMsg}); 28 | // } 29 | // const bleDiscovery = { 30 | // isStartDiscovery: false, 31 | // /** 32 | // * 停止蓝牙扫描 33 | // * @returns {Promise} 34 | // */ 35 | // async stopBlueToothDevicesDiscovery() { 36 | // if (this.isStartDiscovery) { 37 | // const result = await stopBlueToothDevicesDiscovery(); 38 | // this.isStartDiscovery = false; 39 | // console.log('关闭扫描周围设备'); 40 | // return result; 41 | // } else { 42 | // return dontNeedOperation({errMsg: '已关闭了扫描周围蓝牙设备,无需再次关闭'}); 43 | // } 44 | // }, 45 | // async startBlueToothDevicesDiscovery() { 46 | // if (!this.isStartDiscovery) { 47 | // const result = await startBlueToothDevicesDiscovery({ 48 | // services: this.UUIDs, 49 | // allowDuplicatesKey: true, 50 | // interval: 300 51 | // }); 52 | // console.log('开始扫描周围设备'); 53 | // this.isStartDiscovery = true; 54 | // return result; 55 | // } else { 56 | // return dontNeedOperation({errMsg: '正在扫描周围蓝牙设备,无需再次开启扫描'}); 57 | // } 58 | // 59 | // } 60 | // }; 61 | // 62 | // const bleAdapter = { 63 | // isOpenAdapter: false, 64 | // /** 65 | // * 打开蓝牙适配器 66 | // * 只有蓝牙开启的状态下,才可执行成功 67 | // * @returns {Promise} 68 | // */ 69 | // async openAdapter() { 70 | // if (!this.isOpenAdapter) { 71 | // const result = await openBlueToothAdapter(); 72 | // this.isOpenAdapter = true; 73 | // console.log('打开蓝牙适配器成功'); 74 | // return result; 75 | // } else { 76 | // return dontNeedOperation({errMsg: '已打开了蓝牙适配器,无需重复打开'}); 77 | // } 78 | // }, 79 | // 80 | // /** 81 | // * 关闭蓝牙适配器 82 | // * @returns {Promise} 83 | // */ 84 | // async closeAdapter() { 85 | // if (this.isOpenAdapter) { 86 | // const result = await closeBlueToothAdapter(); 87 | // this.isOpenAdapter = false; 88 | // console.log('关闭蓝牙适配器成功'); 89 | // return result; 90 | // } else { 91 | // return dontNeedOperation({errMsg: '已关闭了蓝牙适配器,无需重复关闭'}); 92 | // } 93 | // } 94 | // }; 95 | 96 | export default class AbstractBlueTooth extends IBLEOperator { 97 | constructor() { 98 | super(); 99 | this.UUIDs = []; 100 | this._targetServiceArray = {};//暂时是service及write\read\notify特征值 101 | } 102 | 103 | async openAdapter() { 104 | return await openBlueToothAdapter(); 105 | } 106 | 107 | async closeAdapter() { 108 | return await closeBlueToothAdapter(); 109 | } 110 | 111 | resetAllBLEFlag() { 112 | // bleAdapter.isOpenAdapter = false; 113 | // bleDiscovery.isStartDiscovery = false; 114 | } 115 | 116 | 117 | async createBLEConnection({deviceId, valueChangeListener}) { 118 | // 操作之前先监听,保证第一时间获取数据 119 | await createBLEConnection({deviceId, timeout: 7000}); 120 | onBLECharacteristicValueChange((res) => { 121 | console.log('接收到消息', res); 122 | if (!!valueChangeListener) { 123 | const {value, protocolState, filter} = this.dealReceiveData({receiveBuffer: res.value}); 124 | !filter && valueChangeListener({protocolState, value}); 125 | } 126 | }); 127 | const {serviceId, writeCharacteristicId, notifyCharacteristicId, readCharacteristicId} = this._targetServiceArray; 128 | return await notifyBLE({ 129 | deviceId, 130 | targetServiceUUID: serviceId, 131 | targetCharacteristics: {writeCharacteristicId, notifyCharacteristicId, readCharacteristicId} 132 | }); 133 | } 134 | 135 | 136 | async closeBLEConnection({deviceId}) { 137 | return await closeBLEConnection({deviceId}); 138 | } 139 | 140 | setFilter({services = [], targetServiceArray = []}) { 141 | if (Array.isArray(targetServiceArray) && targetServiceArray.length > 0) { 142 | this._targetServiceArray = targetServiceArray[0]; 143 | } else { 144 | throw new Error('the type of targetServiceMap is not Array!Please check it out.'); 145 | } 146 | if (Array.isArray(services)) { 147 | this.UUIDs = services; 148 | } else { 149 | AbstractBlueTooth._throwUUIDsIsNotArrayError(); 150 | } 151 | } 152 | 153 | async sendData({buffer, deviceId, serviceId, characteristicId}) { 154 | return await writeBLECharacteristicValue({ 155 | deviceId, 156 | serviceId, 157 | characteristicId, 158 | value: buffer.slice(0, 20) 159 | }); 160 | } 161 | 162 | async startBlueToothDevicesDiscovery() { 163 | return await startBlueToothDevicesDiscovery({services: this.UUIDs, allowDuplicatesKey: true, interval: scanInterval}); 164 | } 165 | 166 | async stopBlueToothDevicesDiscovery() { 167 | return await stopBlueToothDevicesDiscovery(); 168 | } 169 | 170 | async getConnectedBlueToothDevices() { 171 | if (!Array.isArray(this.UUIDs)) { 172 | AbstractBlueTooth._throwUUIDsIsNotArrayError(); 173 | } 174 | return await getConnectedBlueToothDevices({services: this.UUIDs}); 175 | } 176 | 177 | async getBlueToothAdapterState() { 178 | return await getBlueToothAdapterState(); 179 | } 180 | 181 | static _throwUUIDsIsNotArrayError() { 182 | throw new Error('the type of services is not Array!Please check it out.'); 183 | } 184 | 185 | } 186 | -------------------------------------------------------------------------------- /modules/bluetooth/lb-ble-common-connection/base/base-bluetooth-imp.js: -------------------------------------------------------------------------------- 1 | import BaseBlueTooth from "./base-bluetooth"; 2 | import {onBLEConnectionStateChange, onBluetoothAdapterStateChange, onBluetoothDeviceFound} from "./wx/apis"; 3 | import {CommonConnectState} from "../../lb-ble-common-state/state"; 4 | import {findTargetDeviceNeedConnectedFun} from "../utils/device-connection-manager"; 5 | 6 | const BLECloseRemindDialog = Symbol('BLECloseRemindDialog'); 7 | 8 | /** 9 | * 蓝牙核心业务的封装 10 | */ 11 | export default class BaseBlueToothImp extends BaseBlueTooth { 12 | 13 | constructor() { 14 | super(); 15 | this._targetDeviceName = ''; 16 | onBluetoothAdapterStateChange((function () { 17 | let available = true; 18 | return async (res) => { 19 | console.log('适配器状态changed, now is', res); 20 | // discovering 21 | const {available: nowAvailable} = res; 22 | if (!nowAvailable) { 23 | this.dealBLEUnavailableScene(); 24 | } else if (!available) {//当前适配器状态是可用的,但上一次是不可用的,说明是用户刚刚重新打开了 25 | await this.closeAdapter(); 26 | await this.openAdapterAndConnectLatestBLE(); 27 | } 28 | available = nowAvailable; 29 | } 30 | }).call(this)); 31 | onBLEConnectionStateChange((res) => { 32 | // 该方法回调中可以用于处理连接意外断开等异常情况 33 | const {deviceId, connected} = res; 34 | console.log(`device ${deviceId} state has changed, connected: ${connected}`); 35 | if (this._onBLEConnectionStateChangeListener) { 36 | this._onBLEConnectionStateChangeListener({deviceId, connected}); 37 | } else { 38 | console.log('未设置蓝牙连接状态变更事件监听,只有主动断开连接时才会触发该事件,所以本次事件不进行重新连接'); 39 | this.latestConnectState = {value: CommonConnectState.DISCONNECT}; 40 | } 41 | }); 42 | 43 | onBluetoothDeviceFound(async (res) => { 44 | console.log('开始扫描周边设备', res); 45 | if (!this._isConnectBindDevice) { 46 | this._isConnectBindDevice = true; 47 | try { 48 | const {devices} = res, {targetDevice} = findTargetDeviceNeedConnectedFun({ 49 | devices, 50 | targetDeviceName: this._targetDeviceName ?? '' 51 | }); 52 | if (targetDevice) { 53 | const {deviceId} = targetDevice; 54 | console.log('baseDeviceFindAction 扫描到目标设备,并开始连接', deviceId, targetDevice); 55 | try { 56 | await this._updateBLEConnectFinalState({promise: super.createBLEConnection({deviceId})}); 57 | } catch (e) { 58 | console.log('连接出现异常', e); 59 | this._isConnectBindDevice = false; 60 | } 61 | } else { 62 | console.log('本周期内未找到指定设备,开始下一个扫描周期'); 63 | this._isConnectBindDevice = false; 64 | } 65 | } catch (e) { 66 | console.error('请在connectTargetFun函数中捕获异常并消费掉,同事最后要返回对象{targetDevice}'); 67 | this._isConnectBindDevice = false; 68 | } 69 | } else { 70 | console.log('正在尝试连接中,所有忽略本次扫描结果'); 71 | } 72 | }); 73 | } 74 | 75 | setDefaultOnBLEConnectionStateChangeListener() { 76 | if (!this._onBLEConnectionStateChangeListener) { 77 | this._onBLEConnectionStateChangeListener = async ({deviceId, connected}) => { 78 | console.log('监听到蓝牙连接状态改变 deviceId=', deviceId, 'connected', connected); 79 | if (!connected) { 80 | this.latestConnectState = {value: CommonConnectState.DISCONNECT, filter: true}; 81 | await this.openAdapterAndConnectLatestBLE(); 82 | // this.latestConnectState = CommonConnectState.DISCONNECT; 83 | // this.openAdapterAndConnectLatestBLE(); 84 | } 85 | }; 86 | } 87 | } 88 | 89 | setFilter({services, targetDeviceName = '', targetServiceArray}) { 90 | this._targetDeviceName = targetDeviceName; 91 | super.setFilter({services, targetServiceArray}); 92 | } 93 | 94 | clearConnectedBLE() { 95 | return super.clearConnectedBLE(); 96 | } 97 | 98 | /** 99 | * 打开蓝牙适配器并扫描蓝牙设备,或是试图连接上一次的蓝牙设备 100 | * 通过判断this._deviceId来确定是否为首次连接。 101 | * 如果是第一次连接,则需要开启蓝牙扫描,通过uuid过滤设备,来连接到对应的蓝牙设备, 102 | * 如果之前已经连接过了,则这次会按照持久化的deviceId直接连接 103 | * @returns {*} 104 | */ 105 | async openAdapterAndConnectLatestBLE() { 106 | const {value: latestConnectState} = this.latestConnectState; 107 | if (latestConnectState === CommonConnectState.CONNECTING || latestConnectState === CommonConnectState.CONNECTED) { 108 | console.warn(`openAdapterAndConnectLatestBLE 尝试蓝牙连接。蓝牙当前的连接状态为:${latestConnectState},所以取消本次连接`); 109 | return; 110 | } 111 | console.warn('openAdapterAndConnectLatestBLE 连接前,读取最新的蓝牙状态:', latestConnectState || '未初始化'); 112 | try { 113 | await this._updateBLEConnectFinalState({promise: super.openAdapter()}); 114 | this.latestConnectState = {value: CommonConnectState.CONNECTING}; 115 | // const connectedDeviceId = super.getConnectedDeviceId(); 116 | // if (connectedDeviceId) { 117 | // console.log(`上次连接过设备${connectedDeviceId},现在直接连接该设备`); 118 | // await this._updateBLEConnectFinalState({promise: await super.createBLEConnection({deviceId: connectedDeviceId})}); 119 | // } else { 120 | // console.log('上次未连接过设备或直连失败,现开始扫描周围设备'); 121 | console.log('openAdapterAndConnectLatestBLE 现开始扫描周围设备'); 122 | await this.startBlueToothDevicesDiscovery(); 123 | } catch (e) { 124 | switch (e.errorCode) { 125 | case 10000: 126 | case 10001: 127 | this.dealBLEUnavailableScene(); 128 | this[BLECloseRemindDialog](); 129 | break; 130 | } 131 | } 132 | 133 | } 134 | 135 | async startBlueToothDevicesDiscovery() { 136 | this._isConnectBindDevice = false; 137 | try { 138 | return await super.startBlueToothDevicesDiscovery(); 139 | } catch (e) { 140 | return await Promise.reject(e); 141 | } 142 | } 143 | 144 | /** 145 | * 统一处理一次蓝牙连接流程 146 | * 如果接收到失败,则是需要重新执行一遍扫描连接流程的情况 147 | * @param promise 148 | * @returns {Promise<*>} 149 | * @private 150 | */ 151 | async _updateBLEConnectFinalState({promise}) { 152 | try { 153 | const result = await promise; 154 | if (result.isConnected && !result.filter) { 155 | this.latestConnectState = {value: CommonConnectState.CONNECTED}; 156 | } 157 | return result; 158 | } catch (e) { 159 | console.warn('_updateBLEConnectFinalState 蓝牙连接出现问题', e); 160 | return await Promise.reject(e); 161 | } 162 | } 163 | 164 | [BLECloseRemindDialog]() { 165 | wx.showModal({title: '提示', content: '请先打开蓝牙', showCancel: false}); 166 | } 167 | 168 | dealBLEUnavailableScene() { 169 | this.latestConnectState = {value: CommonConnectState.UNAVAILABLE}; 170 | super.resetAllBLEFlag(); 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /modules/bluetooth/lb-ble-common-connection/base/base-bluetooth.js: -------------------------------------------------------------------------------- 1 | import AbstractBlueTooth from "./abstract-bluetooth"; 2 | import {getStorageSync, removeStorageSync, setStorageSync} from "./wx/apis"; 3 | import {CommonConnectState} from "../../lb-ble-common-state/state"; 4 | 5 | 6 | function* entries(obj) { 7 | for (let key of Object.keys(obj)) { 8 | yield [key, obj[key]]; 9 | } 10 | } 11 | 12 | export default class BaseBlueTooth extends AbstractBlueTooth { 13 | constructor() { 14 | super(); 15 | this._onConnectStateChanged = null; 16 | this._onReceiveData = null; 17 | this._deviceId = this.getConnectedDeviceId(); 18 | this._serviceId = ''; 19 | this._characteristicId = ''; 20 | this._latestConnectState = {value: CommonConnectState.UNAVAILABLE}; 21 | this._latestProtocolObj = {protocolState: '', value: {}}; 22 | this._onBLEConnectionStateChangeListener = null; 23 | } 24 | 25 | /** 26 | * 在连接前,一定要先设置BLE监听 27 | * 28 | * @param onConnectStateChanged onConnectStateChanged中的参数包括{connectState:''} 29 | * @param onReceiveData onReceiveData中的参数包括了 {protocolState:'',value:{}} 30 | */ 31 | setBLEListener({onConnectStateChanged, onReceiveData}) { 32 | this._onConnectStateChanged = onConnectStateChanged; 33 | this._onReceiveData = onReceiveData; 34 | } 35 | 36 | /** 37 | * 子类重写,用于监听蓝牙连接状态事件 38 | */ 39 | setDefaultOnBLEConnectionStateChangeListener() { 40 | 41 | } 42 | 43 | set latestConnectState({value, filter = false}) { 44 | if (this._latestConnectState.value !== value) { 45 | console.warn('蓝牙连接状态更新', value); 46 | !filter && this._onConnectStateChanged({connectState: value}); 47 | this._latestConnectState.value = value; 48 | } 49 | } 50 | 51 | get latestConnectState() { 52 | return this._latestConnectState; 53 | } 54 | 55 | set latestProtocolInfo({protocolState, value}) { 56 | if (this._latestProtocolObj.protocolState !== protocolState) { 57 | this._onReceiveData({protocolState, value}); 58 | } else { 59 | const {value: latestValue} = this._latestProtocolObj; 60 | if (Object.getOwnPropertyNames(latestValue) === Object.getOwnPropertyNames(value)) { 61 | for (let [key, item] of entries(latestValue)) { 62 | if (item !== value[key]) { 63 | this._onReceiveData({protocolState, value}); 64 | return; 65 | } 66 | } 67 | } else { 68 | this._onReceiveData({protocolState, value}); 69 | } 70 | } 71 | } 72 | 73 | async createBLEConnection({deviceId}) { 74 | try { 75 | try { 76 | await super.stopBlueToothDevicesDiscovery(); 77 | } catch (e) { 78 | console.warn('连接前,stopBlueToothDevicesDiscovery error', e); 79 | } 80 | this.setDefaultOnBLEConnectionStateChangeListener(); 81 | const {serviceId, characteristicId} = await super.createBLEConnection({ 82 | deviceId, 83 | valueChangeListener: this._onReceiveData 84 | }); 85 | this._serviceId = serviceId; 86 | this._characteristicId = characteristicId; 87 | this.setDeviceId({deviceId}); 88 | 89 | return {isConnected: true}; 90 | } catch (error) { 91 | switch (error.errCode) { 92 | case -1: 93 | console.log('已连接上,无需重新连接'); 94 | await super.stopBlueToothDevicesDiscovery(); 95 | return {isConnected: true}; 96 | case 10000: 97 | case 10001: 98 | this.latestConnectState = {value: CommonConnectState.UNAVAILABLE}; 99 | super.resetAllBLEFlag(); 100 | return await Promise.reject(error); 101 | case 10003: 102 | // return new Promise(resolve => { 103 | // setTimeout(async () => { 104 | // await super.stopBlueToothDevicesDiscovery(); 105 | // setTimeout(async () => { 106 | // await this.startBlueToothDevicesDiscovery(); 107 | // }); 108 | // resolve({isConnected: false, filter: true}); 109 | // }, 3000); 110 | // }); 111 | case 10012: 112 | console.warn('连接不上', error); 113 | // console.log('现重启蓝牙适配器'); 114 | // await super.closeAdapter(); 115 | // await super.openAdapter(); 116 | console.warn('准备开始重新扫描连接'); 117 | await this.startBlueToothDevicesDiscovery();//这里调用的是子类的startBlueToothDevicesDiscovery,因为子类有实现 118 | return {isConnected: false, filter: true};//这种是需要重新执行一遍扫描连接流程的,filter是否过滤掉本次事件 119 | case 10004: 120 | await super.closeBLEConnection({deviceId}); 121 | return await this.createBLEConnection({deviceId}); 122 | default: 123 | console.warn('连接失败,重新连接', error); 124 | return await this.createBLEConnection({deviceId}); 125 | } 126 | } 127 | } 128 | 129 | async sendData({buffer}) { 130 | return super.sendData({ 131 | buffer, 132 | deviceId: this._deviceId, 133 | serviceId: this._serviceId, 134 | characteristicId: this._characteristicId 135 | }); 136 | } 137 | 138 | async closeCurrentBLEConnection() { 139 | const {value} = this.latestConnectState; 140 | console.log('closeCurrentBLEConnection前,连接状态', value); 141 | this._onBLEConnectionStateChangeListener = null; 142 | switch (value) { 143 | case CommonConnectState.CONNECTED: 144 | return super.closeBLEConnection({deviceId: this._deviceId}); 145 | case CommonConnectState.CONNECTING: 146 | return this.stopBlueToothDevicesDiscovery(); 147 | } 148 | return Promise.resolve(); 149 | } 150 | 151 | /** 152 | * 获取连接过的设备id 153 | * @returns {string|*} 154 | */ 155 | getConnectedDeviceId() { 156 | if (!this._deviceId) { 157 | this._deviceId = getStorageSync('lb_ble_$deviceId') ?? ''; 158 | } 159 | return this._deviceId; 160 | } 161 | 162 | setDeviceId({deviceId}) { 163 | try { 164 | setStorageSync('lb_ble_$deviceId', this._deviceId = deviceId); 165 | } catch (e) { 166 | console.log('setDeviceMacAddress()出现错误 deviceId=', this._deviceId); 167 | setStorageSync('lb_ble_$deviceId', this._deviceId = deviceId); 168 | console.log('setDeviceMacAddress()重新存储成功'); 169 | } 170 | } 171 | 172 | /** 173 | * 清除上一次连接的蓝牙设备 174 | * 这会导致断开目前连接的蓝牙设备 175 | * @returns {*|Promise} 176 | */ 177 | async clearConnectedBLE() { 178 | await this.closeCurrentBLEConnection(); 179 | await super.closeAdapter(); 180 | removeStorageSync('lb_ble_$deviceId'); 181 | this._deviceId = ''; 182 | this._serviceId = ''; 183 | this._characteristicId = ''; 184 | return Promise.resolve(); 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /modules/bluetooth/lb-ble-common-connection/base/common-ble-connection-operation.js: -------------------------------------------------------------------------------- 1 | import BaseBlueToothImp from "./base-bluetooth-imp"; 2 | 3 | const bluetoothManager = Symbol('bluetoothManager'); 4 | 5 | export default class CommonBLEConnectionOperation { 6 | constructor() { 7 | this[bluetoothManager] = new BaseBlueToothImp(); 8 | this[bluetoothManager].dealReceiveData = this.dealReceiveData.bind(this); 9 | 10 | } 11 | 12 | /** 13 | * 14 | * 订阅蓝牙连接状态变化事件和接收到新的蓝牙协议事件 15 | * 可只订阅其中一个 16 | * @param onConnectStateChanged 蓝牙连接状态变化事件 17 | * @param onReceiveData 接收到新的蓝牙协议事件 18 | */ 19 | setBLEListener({onConnectStateChanged, onReceiveData}) { 20 | this[bluetoothManager].setBLEListener(arguments[0]); 21 | } 22 | 23 | /** 24 | * 在扫描周围蓝牙设备时,设置用于过滤无关设备的信息 25 | * 正常来说,该函数只需要调用一次 26 | * @param services 必填 要搜索的蓝牙设备主 service 的 uuid 列表。详情见微信小程序官网,对于wx.startBluetoothDevicesDiscovery接口的介绍 27 | * @param targetServiceArray 必填 在通信过程中,需要用到的服务uuid及对应的特征值、notify、read、write属性,目前只会与传入的第一组通信,后续会增加与多组服务通信的功能 28 | * @param targetDeviceName 非必填 蓝牙设备名称 与localName一致即可,区分大小写。如果不填写这一项或填写为空字符串,则将要连接的设备是经services过滤后的扫描到的第一个设备 29 | */ 30 | setFilter({services, targetServiceArray, targetDeviceName}) { 31 | this[bluetoothManager].setFilter({services, targetDeviceName, targetServiceArray}); 32 | } 33 | 34 | sendData({buffer}) { 35 | return this[bluetoothManager].sendData({buffer}); 36 | } 37 | 38 | /** 39 | * 连接蓝牙 40 | * 默认的蓝牙扫描和连接规则是,同一设备重复上报,上报周期是250ms,在这一个周期内,去连接信号最强的设备 41 | * 如果连接失败了,会重新扫描、连接(重连的不一定是上一个设备) 42 | * 注意!!程序每次都会重新扫描周围设备再连接,并不会缓存上一次连接的设备直接用deviceId来连接 43 | * 异步执行 44 | * 可在子类中重写蓝牙扫描连接规则 详情见 lb-example-bluetooth-manager.js overwriteFindTargetDeviceForConnected 45 | */ 46 | connect() { 47 | this[bluetoothManager].openAdapterAndConnectLatestBLE(); 48 | } 49 | 50 | 51 | getConnectDevices() { 52 | return this[bluetoothManager].getConnectedBlueToothDevices(); 53 | } 54 | 55 | getBLEAdapterState() { 56 | return this[bluetoothManager].getBlueToothAdapterState(); 57 | } 58 | 59 | /** 60 | * 关闭蓝牙适配器 61 | * @returns {Promise} 62 | */ 63 | async closeAll() { 64 | await this[bluetoothManager].closeCurrentBLEConnection(); 65 | return this[bluetoothManager].closeAdapter(); 66 | } 67 | 68 | clearConnectedBLE() { 69 | return this[bluetoothManager].clearConnectedBLE(); 70 | } 71 | 72 | updateBLEConnectState({connectState}) { 73 | this[bluetoothManager].latestConnectState = {value: connectState}; 74 | } 75 | 76 | getBLELatestConnectState() { 77 | return this[bluetoothManager].latestConnectState.value; 78 | } 79 | 80 | executeBLEReceiveDataCallBack({protocolState, value}) { 81 | this[bluetoothManager].latestProtocolInfo = {protocolState, value}; 82 | } 83 | 84 | /** 85 | * 处理从连接的蓝牙中接收到的数据 86 | * 该函数必须在子类中重写! 87 | * 也千万不要忘了在重写时给这个函数一个返回值,作为处理数据后,传递给UI层的数据 88 | * @param receiveBuffer 从连接的蓝牙中接收到的数据 89 | * @returns 传递给UI层的数据 90 | */ 91 | dealReceiveData({receiveBuffer}) { 92 | 93 | } 94 | }; 95 | -------------------------------------------------------------------------------- /modules/bluetooth/lb-ble-common-connection/base/inter/i-ble-operator.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 蓝牙连接及收发数据接口类,只定义接口,需被实现 3 | */ 4 | export default class IBLEOperator { 5 | /** 6 | * 处理从连接的蓝牙中接收到的数据 7 | * 该函数必须在子类中重写! 8 | * 也千万不要忘了在重写时给这个函数一个返回值,作为处理数据后,传递给UI层的数据 9 | * 可以参考xxx.类的实现方式 10 | * @param receiveBuffer 从连接的蓝牙中接收到的数据 11 | * @returns 传递给UI层的数据 12 | */ 13 | dealReceiveData({receiveBuffer}) { 14 | 15 | } 16 | 17 | /** 18 | * 打开蓝牙适配器 19 | * @returns {Promise} 20 | */ 21 | async openAdapter() { 22 | 23 | } 24 | 25 | /** 26 | * 关闭蓝牙适配器 27 | * @returns {Promise} 28 | */ 29 | async closeAdapter() { 30 | } 31 | 32 | /** 33 | * 建立蓝牙连接 34 | * @param deviceId 35 | * @param valueChangeListener 36 | * @returns {Promise<{serviceId, characteristicId: *, deviceId: *}>} 37 | */ 38 | async createBLEConnection({deviceId, valueChangeListener}) { 39 | } 40 | 41 | /** 42 | * 断开处于连接状态的蓝牙连接 43 | * @returns {Promise} 44 | */ 45 | async closeBLEConnection({deviceId}) { 46 | } 47 | 48 | /** 49 | * 设置蓝牙扫描和连接时的过滤信息 50 | * 这会让你在扫描蓝牙设备时,只保留该services数组的蓝牙设备,过滤掉其他的所有设备,提高扫描效率 51 | * @param services 数组 不被过滤掉的蓝牙设备的广播服务UUID 52 | * @param targetServiceUUID 目标蓝牙设备用于通信服务的UUID 53 | */ 54 | setFilter({services, targetServiceUUID}) { 55 | } 56 | 57 | /** 58 | * 发送二进制数据 59 | * @param buffer ArrayBuffer 60 | * @param deviceId 61 | * @param serviceId 62 | * @param characteristicId 63 | * @returns {Promise} 64 | */ 65 | async sendData({buffer, deviceId, serviceId, characteristicId}) { 66 | } 67 | 68 | 69 | async startBlueToothDevicesDiscovery() { 70 | } 71 | 72 | async stopBlueToothDevicesDiscovery() { 73 | } 74 | 75 | /** 76 | * 根据 uuid 获取处于已连接状态的设备。 77 | * @returns {Promise} 78 | */ 79 | async getConnectedBlueToothDevices() { 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /modules/bluetooth/lb-ble-common-connection/base/wx/apis.js: -------------------------------------------------------------------------------- 1 | export function createBLEConnection({deviceId, timeout}) { 2 | return new Promise((resolve, reject) => wx.createBLEConnection({ 3 | deviceId, 4 | timeout, 5 | success: resolve, 6 | fail: reject 7 | })); 8 | } 9 | 10 | export function closeBLEConnection({deviceId}) { 11 | return new Promise((resolve, reject) => wx.closeBLEConnection({deviceId, success: resolve, fail: reject})); 12 | } 13 | 14 | export function writeBLECharacteristicValue({deviceId, serviceId, characteristicId, value}) { 15 | return new Promise((resolve, reject) => wx.writeBLECharacteristicValue({ 16 | ...arguments[0], 17 | success: resolve, 18 | fail: reject 19 | })); 20 | } 21 | 22 | export function readBLECharacteristicValue({deviceId, serviceId, characteristicId}) { 23 | return new Promise((resolve, reject) => wx.readBLECharacteristicValue({ 24 | serviceId, deviceId, characteristicId, 25 | success: resolve, 26 | fail: reject 27 | })); 28 | } 29 | 30 | export function getBLEDeviceServices({deviceId}) { 31 | return new Promise((resolve, reject) => wx.getBLEDeviceServices({ 32 | deviceId, success: res => { 33 | const {services} = res; 34 | console.log('device services:', services); 35 | resolve({services}); 36 | }, fail: reject 37 | })); 38 | } 39 | 40 | export function getBLEDeviceCharacteristics({deviceId, serviceId}) { 41 | return new Promise((resolve, reject) => wx.getBLEDeviceCharacteristics({ 42 | deviceId, 43 | serviceId, 44 | success: res => { 45 | const {characteristics} = res; 46 | console.log('device getBLEDeviceCharacteristics:', characteristics); 47 | resolve({characteristics, serviceId}); 48 | }, 49 | fail: reject 50 | })); 51 | } 52 | 53 | export function startBlueToothDevicesDiscovery({services, allowDuplicatesKey, interval}) { 54 | return new Promise((resolve, reject) => wx.startBluetoothDevicesDiscovery({ 55 | ...arguments[0], 56 | success: resolve, 57 | fail: reject 58 | })); 59 | } 60 | 61 | export function stopBlueToothDevicesDiscovery() { 62 | return new Promise((resolve, reject) => wx.stopBluetoothDevicesDiscovery({success: resolve, fail: reject})); 63 | } 64 | 65 | export function openBlueToothAdapter() { 66 | let isBugPhone = false; 67 | try { 68 | const {model} = wx.getSystemInfoSync(); 69 | isBugPhone = model.indexOf('iPhone 6') !== -1 || model.indexOf('iPhone 7') !== -1; 70 | } catch (e) { 71 | console.error('wx.getSystemInfoSync() error', e); 72 | } 73 | return (openBlueToothAdapter = function () { 74 | return new Promise((resolve, reject) => { 75 | if (!isBugPhone) { 76 | wx.openBluetoothAdapter({success: resolve, fail: reject}); 77 | } else { 78 | setTimeout(() => { 79 | wx.openBluetoothAdapter({success: resolve, fail: reject}); 80 | }, 150); 81 | } 82 | }); 83 | })(); 84 | 85 | 86 | } 87 | 88 | export function closeBlueToothAdapter() { 89 | return new Promise((resolve, reject) => wx.closeBluetoothAdapter({success: resolve, fail: reject})); 90 | } 91 | 92 | export function getConnectedBlueToothDevices({services}) { 93 | return new Promise((resolve, reject) => wx.getConnectedBluetoothDevices({ 94 | services, 95 | success: resolve, 96 | fail: reject 97 | })); 98 | } 99 | 100 | export function onBLECharacteristicValueChange(cb) { 101 | wx.onBLECharacteristicValueChange(cb); 102 | } 103 | 104 | /** 105 | * 获取在蓝牙模块生效期间所有已发现的蓝牙设备。包括已经和本机处于连接状态的设备 106 | * @returns {Promise} 107 | */ 108 | export function getBlueToothDevices() { 109 | return new Promise((resolve, reject) => wx.getBluetoothDevices({success: resolve, fail: reject})); 110 | } 111 | 112 | export function getBlueToothAdapterState() { 113 | return new Promise((resolve, reject) => wx.getBluetoothAdapterState({success: resolve, fail: reject})); 114 | } 115 | 116 | export function notifyBLECharacteristicValueChange({deviceId, serviceId, characteristicId, state}) { 117 | return new Promise((resolve, reject) => wx.notifyBLECharacteristicValueChange({ 118 | deviceId, serviceId, characteristicId, state, 119 | success: resolve, 120 | fail: reject 121 | })); 122 | } 123 | 124 | /** 125 | * 注册读写notify监听,该事件要发生在连接上设备之后 126 | * @param deviceId 已连接的设备id 127 | * @param targetServiceUUID 目标蓝牙服务UUID 128 | * @param targetCharacteristics 目标蓝牙服务对应的特征值,包括writeCharacteristicId, notifyCharacteristicId, readCharacteristicId 129 | * @returns {Promise<{serviceId, characteristicId: *, deviceId: *}>} 130 | */ 131 | export async function notifyBLE({deviceId, targetServiceUUID, targetCharacteristics}) { 132 | const {characteristics, serviceId} = await findTargetServiceByUUID({deviceId, targetServiceUUID}); 133 | const {writeCharacteristicId, notifyCharacteristicId, readCharacteristicId} = targetCharacteristics; 134 | let characteristicId = ''; 135 | const notifyItem = characteristics.find(({uuid}) => uuid === notifyCharacteristicId); 136 | if (notifyItem) { 137 | await notifyBLECharacteristicValueChange({ 138 | deviceId, 139 | serviceId, 140 | characteristicId: notifyCharacteristicId, 141 | state: true 142 | }); 143 | console.warn('已注册notify事件 characteristicId:', notifyCharacteristicId); 144 | } 145 | const readItem = characteristics.find(({uuid}) => uuid === readCharacteristicId); 146 | if (readItem) { 147 | await readBLECharacteristicValue({deviceId, serviceId, characteristicId: readCharacteristicId}); 148 | console.warn('本次读特征值是 characteristicId:', readCharacteristicId); 149 | } 150 | const writeItem = characteristics.find(({uuid}) => uuid === writeCharacteristicId); 151 | if (writeItem) { 152 | characteristicId = writeCharacteristicId; 153 | console.warn('本次写特征值是 characteristicId:', writeCharacteristicId); 154 | } 155 | return {serviceId, characteristicId}; 156 | } 157 | 158 | 159 | export function setStorageSync(key, data) { 160 | wx.setStorageSync(key, data); 161 | } 162 | 163 | export function getStorageSync(key) { 164 | return wx.getStorageSync(key); 165 | } 166 | 167 | export function removeStorageSync(key) { 168 | return wx.removeStorageSync(key); 169 | } 170 | 171 | export function onBluetoothAdapterStateChange(cb) { 172 | wx.onBluetoothAdapterStateChange(cb); 173 | } 174 | 175 | export function onBLEConnectionStateChange(cb) { 176 | wx.onBLEConnectionStateChange(cb); 177 | } 178 | 179 | export function onBluetoothDeviceFound(cb) { 180 | wx.onBluetoothDeviceFound(cb); 181 | } 182 | 183 | async function findTargetServiceByUUID({deviceId, targetServiceUUID}) { 184 | const {services} = await getBLEDeviceServices({deviceId}); 185 | for (const {isPrimary, uuid} of services) { 186 | if (isPrimary && uuid.toUpperCase() === targetServiceUUID) { 187 | console.log('即将建立通信的服务uuid:', uuid); 188 | return await getBLEDeviceCharacteristics({deviceId, serviceId: uuid}); 189 | } 190 | } 191 | } 192 | 193 | -------------------------------------------------------------------------------- /modules/bluetooth/lb-ble-common-connection/index.js: -------------------------------------------------------------------------------- 1 | import LBlueToothManager from "./lb-bluetooth-manager"; 2 | 3 | export { 4 | LBlueToothManager 5 | }; 6 | -------------------------------------------------------------------------------- /modules/bluetooth/lb-ble-common-connection/lb-bluetooth-manager.js: -------------------------------------------------------------------------------- 1 | import CommonBLEConnectionOperation from "./base/common-ble-connection-operation"; 2 | import {CommonConnectState, CommonProtocolState} from "../lb-ble-common-state/state"; 3 | import {setMyFindTargetDeviceNeedConnectedFun, setScanInterval} from "./utils/device-connection-manager"; 4 | 5 | const MAX_WRITE_NUM = 5, isDebug = Symbol('isDebug'), BLEPush = Symbol('BLEPush'), 6 | reWriteIndex = Symbol('reWriteIndex'), isAppOnShow = Symbol('isAppOnShow'); 7 | 8 | class LBlueToothCommonManager extends CommonBLEConnectionOperation { 9 | 10 | constructor({debug = true} = {}) { 11 | super(); 12 | this[isDebug] = debug; 13 | this[BLEPush] = []; 14 | this[reWriteIndex] = 0; 15 | this[isAppOnShow] = false; 16 | wx.onAppShow(() => { 17 | this[isAppOnShow] = true; 18 | if (this.getBLELatestConnectState() === CommonConnectState.CONNECTED) { 19 | setTimeout(async () => { 20 | await this.resendBLEData(); 21 | }, 20); 22 | } else { 23 | this[BLEPush].splice(0, this[BLEPush].length); 24 | } 25 | }); 26 | wx.onAppHide(() => { 27 | this[isAppOnShow] = false; 28 | }); 29 | } 30 | 31 | async resendBLEData() { 32 | const blePushArrayLength = this[BLEPush]?.length; 33 | if (blePushArrayLength) { 34 | let item; 35 | while (!!(item = this[BLEPush].shift())) { 36 | this[isDebug] && console.warn('回到前台,重新发送蓝牙协议', item); 37 | await this._sendData(item); 38 | } 39 | } 40 | } 41 | 42 | /** 43 | * 发送数据细节的封装 44 | * 这里根据你自己的业务自行实现 45 | * @param buffer 46 | */ 47 | sendData({buffer}) { 48 | return new Promise((resolve, reject) => { 49 | this.sendDataCatchError({buffer}).then(resolve).catch(e => { 50 | if (!e.needReconnect) { 51 | return reject(e); 52 | } 53 | }); 54 | }); 55 | } 56 | 57 | sendDataCatchError({buffer}) { 58 | return new Promise(async (resolve, reject) => { 59 | // if (buffer && buffer.byteLength) { 60 | if (this[isAppOnShow]) { 61 | 62 | await this._sendData({buffer, resolve, reject}); 63 | } else { 64 | this[BLEPush].push({buffer, resolve, reject}); 65 | this[isDebug] && console.warn('程序进入后台,停止发送蓝牙数据,数据放入队列', this[BLEPush]); 66 | } 67 | }); 68 | } 69 | 70 | async _sendData({buffer, resolve, reject}) { 71 | try { 72 | const result = await super.sendData({buffer}); 73 | this.reWriteIndex = 0; 74 | if (this[isDebug]) { 75 | console.log('writeBLECharacteristicValue success成功', result.errMsg); 76 | const dataView = new DataView(buffer, 0); 77 | const byteLength = buffer.byteLength; 78 | for (let i = 0; i < byteLength; i++) { 79 | console.log(dataView.getUint8(i)); 80 | } 81 | } 82 | resolve(); 83 | } catch (e) { 84 | if (e.errCode === 10008 && this[reWriteIndex] <= MAX_WRITE_NUM) { 85 | this.reWriteIndex++; 86 | this[isDebug] && console.log('写入失败,错误代码10008,尝试重新写入;尝试次数', this.reWriteIndex); 87 | await this._sendData({buffer, resolve, reject}); 88 | } else { 89 | const {available} = super.getBLEAdapterState(); 90 | if (!available) { 91 | this.reWriteIndex = 0; 92 | this[isDebug] && console.log('写入失败,手机未开启蓝牙,详细原因', e); 93 | reject({...e, needReconnect: false}); 94 | } else { 95 | this.reWriteIndex = 0; 96 | this[BLEPush].push({buffer, resolve, reject}); 97 | this[isDebug] && console.log('写入失败,错误详情', e); 98 | await this.closeAll(); 99 | this.connect(); 100 | reject({needReconnect: true}); 101 | } 102 | } 103 | } 104 | } 105 | 106 | /** 107 | * 关闭蓝牙适配器 108 | * 调用此接口会先断开蓝牙连接,停止蓝牙设备的扫描,并关闭蓝牙适配器 109 | * @returns {PromiseLike | Promise} 110 | */ 111 | // closeAll() { 112 | // this.bluetoothProtocol.clearSendProtocol(); 113 | // return super.closeAll(); 114 | // } 115 | 116 | /** 117 | * 处理从蓝牙设备接收到的数据的具体实现 118 | * 这里会将处理后的数据,作为参数传递给setBLEListener的receiveDataListener监听函数。 119 | * @param receiveBuffer ArrayBuffer类型 接收到的数据的最原始对象,该参数为从微信的onBLECharacteristicValueChange函数的回调参数 120 | * @returns {*} 121 | */ 122 | dealReceiveData({receiveBuffer}) { 123 | const {effectiveData, protocolState} = this.bluetoothProtocol.receive({receiveBuffer}); 124 | if (CommonProtocolState.UNKNOWN === protocolState) { 125 | return {filter: true}; 126 | } 127 | this.logReceiveData({receiveBuffer}); 128 | return {value: effectiveData, protocolState}; 129 | } 130 | 131 | /** 132 | * 打印接收到的数据 133 | * @param receiveBuffer 134 | */ 135 | logReceiveData({receiveBuffer}) { 136 | if (this[isDebug]) { 137 | const byteLength = receiveBuffer.byteLength; 138 | const dataView = new DataView(receiveBuffer, 0); 139 | for (let k = 0; k < byteLength; k++) { 140 | console.log(`接收到的数据索引:${k} 值:${dataView.getUint8(k)}`); 141 | } 142 | } 143 | } 144 | } 145 | 146 | const commonManager = Symbol(); 147 | export default class LBlueToothManager { 148 | constructor({debug = true} = {}) { 149 | this[commonManager] = new LBlueToothCommonManager({debug}); 150 | } 151 | 152 | initBLEProtocol({bleProtocol}) { 153 | if (!this[commonManager].bluetoothProtocol) { 154 | bleProtocol.setBLEManager(this[commonManager]); 155 | this[commonManager].bluetoothProtocol = bleProtocol; 156 | } 157 | } 158 | 159 | /** 160 | * 重复上报时的过滤规则,并返回过滤结果 161 | * 在执行完该过滤函数,并且该次连接蓝牙有了最终结果后,才会在下一次上报结果回调时,再次执行该函数。 162 | * 所以如果在一次过滤过程中或是连接蓝牙,耗时时间很长,导致本次连接结果还没得到,就接收到了下一次的上报结果,则会忽略下一次{scanFilterRuler}的执行。 163 | * 如果不指定这个函数,则会使用默认的连接规则 164 | * 默认的连接规则详见 lb-ble-common-connection/utils/device-connection-manager.js的{defaultFindTargetDeviceNeedConnectedFun} 165 | * {scanFilterRuler}会传递两个参数{devices,targetDeviceName} 166 | * {devices}是wx.onBluetoothDeviceFound(cb)中返回的{devices} 167 | * {targetDeviceName}是{setFilter}中的配置项 168 | * {scanFilterRuler}最终返回对象{targetDevice},是数组{devices}其中的一个元素;{targetDevice}可返回null,意思是本次扫描结果未找到指定设备 169 | * @param scanFilterRuler {function} 扫描过滤规则函数,需返回过滤结果 170 | */ 171 | setMyFindTargetDeviceNeedConnectedFun({scanFilterRuler}) { 172 | setMyFindTargetDeviceNeedConnectedFun({scanFilterRuler}); 173 | } 174 | 175 | /** 176 | * 连接蓝牙 177 | * 默认的蓝牙扫描和连接规则是,同一设备重复上报,上报周期是250ms,在这一个周期内,去连接信号最强的设备 178 | * 只有在获取到特征值并订阅了read和notify成功后,才会在{setBLEListener}中通知蓝牙连接成功 179 | * 如果连接失败了,会重新扫描、连接(重连的不一定是上一个设备) 180 | * 注意!!程序每次都会重新扫描周围设备再连接,并不会缓存上一次连接的设备直接用deviceId来连接 181 | * 连接结果不在该函数中返回,请在{setBLEListener}中订阅连接状态变化事件,来知晓连接结果 182 | * 可在子类中重写蓝牙扫描连接规则 详情见 lb-example-bluetooth-manager.js overwriteFindTargetDeviceForConnected 183 | */ 184 | connect() { 185 | this[commonManager].connect(); 186 | } 187 | 188 | /** 189 | * 订阅蓝牙连接状态变化事件和接收到新的蓝牙协议事件 190 | * 只有在获取到特征值并订阅了read和notify成功后,才会在{setBLEListener}中通知蓝牙连接成功 191 | * 可只订阅其中一个事件 192 | * @param onConnectStateChanged 蓝牙连接状态变化事件 193 | * @param onReceiveData 接收到新的蓝牙协议事件 194 | */ 195 | setBLEListener({onConnectStateChanged, onReceiveData}) { 196 | this[commonManager].setBLEListener(arguments[0]); 197 | } 198 | 199 | /** 200 | * 在扫描周围蓝牙设备时,设置用于过滤无关设备的信息 201 | * 正常来说,该函数只需要调用一次 202 | * @param services {array}必填 要搜索的蓝牙设备主 service 的 uuid 列表。详情见微信小程序官网,对于wx.startBluetoothDevicesDiscovery接口的介绍 203 | * @param targetServiceArray {array}必填 用于通信的服务uuid及对应的特征值、notify、read、write属性,目前只会与传入的第一组通信,后续会增加与多组服务通信的功能 204 | * @param targetDeviceName {string}非必填 蓝牙设备名称 与localName一致即可,区分大小写。如果不填写这一项或填写为空字符串,则将要连接的设备是经services过滤后的扫描到的第一个设备 205 | * @param scanInterval {number}非必填,扫描周围设备,重复上报的时间间隔,毫秒制,默认是350ms 206 | */ 207 | setFilter({services, targetServiceArray, targetDeviceName, scanInterval = 350}) { 208 | setScanInterval(scanInterval); 209 | this[commonManager].setFilter({services, targetDeviceName, targetServiceArray}); 210 | } 211 | 212 | /** 213 | * 关闭蓝牙适配器 214 | * 调用此接口会先断开蓝牙连接,停止蓝牙设备的扫描,并关闭蓝牙适配器 215 | * @returns {PromiseLike | Promise} 216 | */ 217 | async closeAll() { 218 | return await this[commonManager].closeAll(); 219 | } 220 | 221 | 222 | /** 223 | * 获取最新的蓝牙连接状态 224 | * @returns {*} 225 | */ 226 | getBLELatestConnectState() { 227 | return this[commonManager].getBLELatestConnectState(); 228 | } 229 | 230 | /** 231 | * 获取本机蓝牙适配器状态 232 | * @returns {Promise<*>} 返回值见小程序官网 wx.getBluetoothAdapterState 233 | */ 234 | getBLEAdapterState() { 235 | return this[commonManager].getBLEAdapterState(); 236 | } 237 | 238 | } 239 | -------------------------------------------------------------------------------- /modules/bluetooth/lb-ble-common-connection/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "_args": [ 3 | [ 4 | "heheda-bluetooth@2.0.1", 5 | "/Users/biao/WXProject/hibox" 6 | ] 7 | ], 8 | "_from": "heheda-bluetooth@2.0.1", 9 | "_id": "heheda-bluetooth@2.0.1", 10 | "_inBundle": false, 11 | "_integrity": "sha512-sfJ+f9es3rIkJk1i2ZXvH81TkQOl+8bA6MCrgurGO5Mdo4UpwitIclmgbegKXgcXF9mcWREC8m0uoYx18OFG4g==", 12 | "_location": "/heheda-bluetooth", 13 | "_phantomChildren": {}, 14 | "_requested": { 15 | "type": "version", 16 | "registry": true, 17 | "raw": "heheda-bluetooth@2.0.1", 18 | "name": "heheda-bluetooth", 19 | "escapedName": "heheda-bluetooth", 20 | "rawSpec": "2.0.1", 21 | "saveSpec": null, 22 | "fetchSpec": "2.0.1" 23 | }, 24 | "_requiredBy": [ 25 | "/" 26 | ], 27 | "_resolved": "http://192.168.1.56:4873/heheda-bluetooth/-/heheda-bluetooth-2.0.1.tgz", 28 | "_spec": "2.0.1", 29 | "_where": "/Users/biao/WXProject/hibox", 30 | "author": { 31 | "name": "liubiao" 32 | }, 33 | "dependencies": { 34 | "heheda-adapter": "^1.0.0", 35 | "heheda-bluetooth-state": "^1.0.2", 36 | "heheda-network": "^1.1.7", 37 | "heheda-update": "^1.0.0" 38 | }, 39 | "description": "小程序项目蓝牙通用组件", 40 | "license": "MIT", 41 | "main": "index.js", 42 | "name": "heheda-bluetooth", 43 | "scripts": { 44 | "test": "echo \"Error: no test specified\" && exit 1" 45 | }, 46 | "version": "2.0.1" 47 | } 48 | -------------------------------------------------------------------------------- /modules/bluetooth/lb-ble-common-connection/utils/ble-observer.js: -------------------------------------------------------------------------------- 1 | let pages = new Map(); 2 | 3 | 4 | export function notifyBLE({value,name}) { 5 | 6 | } 7 | 8 | export function registerBLEListener(name, page) { 9 | pages.set(page.route + '/ble_listener/' + name, page); 10 | } 11 | 12 | export function unregisterBLEListener(page, name) { 13 | pages.delete(page.route + '/ble_listener/' + name); 14 | } 15 | -------------------------------------------------------------------------------- /modules/bluetooth/lb-ble-common-connection/utils/device-connection-manager.js: -------------------------------------------------------------------------------- 1 | let findTargetDeviceNeedConnectedFun = defaultFindTargetDeviceNeedConnectedFun; 2 | let scanInterval = 350; 3 | 4 | function setMyFindTargetDeviceNeedConnectedFun({scanFilterRuler}) { 5 | findTargetDeviceNeedConnectedFun = typeof scanFilterRuler === 'function' ? scanFilterRuler : defaultFindTargetDeviceNeedConnectedFun; 6 | } 7 | 8 | function setScanInterval(interval) { 9 | scanInterval = interval ?? 350; 10 | } 11 | 12 | /** 13 | * 默认扫描过滤规则 14 | * 目的是找到需要连接的蓝牙设备 15 | * @param devices 一个周期内扫描到的蓝牙设备,周期时长是wx.startBlueToothDevicesDiscovery接口中指定的interval时长 16 | * @param targetDeviceName 目标设备名称,使用的String.prototype.includes()函数来处理的,所以不必是全称。 17 | * @returns {{targetDevice: null}|{targetDevice: *}} 18 | */ 19 | function defaultFindTargetDeviceNeedConnectedFun({devices, targetDeviceName}) { 20 | const tempFilterArray = []; 21 | for (let device of devices) { 22 | if (device.localName?.includes(targetDeviceName)) { 23 | // this._isConnectBindDevice = true; 24 | tempFilterArray.push(device); 25 | } 26 | } 27 | if (tempFilterArray.length) { 28 | const device = tempFilterArray.reduce((pre, cur) => { 29 | return pre.RSSI > cur.RSSI ? pre : cur; 30 | }); 31 | return {targetDevice: device}; 32 | } 33 | return {targetDevice: null}; 34 | } 35 | 36 | export {setMyFindTargetDeviceNeedConnectedFun, setScanInterval, findTargetDeviceNeedConnectedFun, scanInterval}; 37 | -------------------------------------------------------------------------------- /modules/bluetooth/lb-ble-common-connection/utils/mix.js: -------------------------------------------------------------------------------- 1 | export function mix(...mixins) { 2 | class Mix { 3 | constructor() { 4 | for (let mixin of mixins) { 5 | copyProperties(this, new mixin()); // 拷贝实例属性 6 | } 7 | } 8 | } 9 | 10 | for (let mixin of mixins) { 11 | copyProperties(Mix, mixin); // 拷贝静态属性 12 | copyProperties(Mix.prototype, mixin.prototype); // 拷贝原型属性 13 | } 14 | 15 | return Mix; 16 | } 17 | 18 | function copyProperties(target, source) { 19 | for (let key of Reflect.ownKeys(source)) { 20 | if ( key !== 'constructor' 21 | && key !== 'prototype' 22 | && key !== 'name' 23 | ) { 24 | let desc = Object.getOwnPropertyDescriptor(source, key); 25 | Object.defineProperty(target, key, desc); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /modules/bluetooth/lb-ble-common-protocol-body/README.md: -------------------------------------------------------------------------------- 1 | # 小程序收发蓝牙数据时的处理规则 2 | -------------------------------------------------------------------------------- /modules/bluetooth/lb-ble-common-protocol-body/i-protocol-receive-body.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 蓝牙接收数据后,截取有效数据及自定义的蓝牙协议状态给子类 3 | * 必须按照约定的协议格式来制定协议 4 | * 协议约定格式:[...命令字之前的数据(非必需), 命令字(必需), ...有效数据(非必需), 有效数据之后的数据(非必需 如协议结束标志校、验位等) 5 | */ 6 | export default class IBLEProtocolReceiveBody { 7 | /** 8 | * @param commandIndex 命令字所在位置的索引 9 | * @param effectiveDataStartIndex 有效数据开始的索引 10 | */ 11 | constructor({commandIndex, effectiveDataStartIndex}) { 12 | this.commandIndex = commandIndex; 13 | this.effectiveDataStartIndex = effectiveDataStartIndex; 14 | } 15 | 16 | /** 17 | * 在接收到的数据中,按你指定的规则获取有效数据 18 | * 啥叫有效数据,按照我目前制定的协议规则,是这种[...命令字前的数据,命令字,...有效数据,...有效数据之后的数据(包括校验位)] 19 | * 发送蓝牙协议也是按照这个规则来制定的。 20 | * 该项目允许发送协议和接收协议格式不同,因为都暴露出了可拓展的接口,只需继承对应的类,并重写相应函数即可。 21 | * 一般来说,除有效数据以外的其他数据,对于咱们业务上来说,是没有意义的。 22 | * 比如:接收到一组获取灯光开关状态和灯光颜色的蓝牙协议 [170(帧头), 10(命令字), 1(灯光开启),255,255,255(三个255,白色灯光),233(协议结束标志,有的协议中没有这一位),18(校验位,我胡乱写的)] 23 | * 在这一组数据中,跟咱们前端业务有关系的,只有灯光开关状态和灯光颜色[1,255,255,255],所以我称这一部分为有效数据,该项目文档中其他函数中涉及到有效数据概念的,都是这个意思。 24 | * 25 | * 此例更多的说明:命令字前的数据[170] 有效数据之后的数据[233,18] 26 | * 27 | * 在这个函数中可以看到调用了{getEffectiveReceiveDataLength},说明有效数据的字节长度是必须要生成的,你需要在{getEffectiveReceiveDataLength}中写死或是动态计算出有效数据的字节长度。 28 | * 多说一句,在这条协议中,233是结束标志,如果你在制定协议时,有结束标志,那么需要在{getEffectiveReceiveDataLength}中动态计算出有效数据的字节长度,计算结果是4。 29 | * 如果不需要结束标志,也可以这样:在命令字前的数据中,可以放置一个字节,代表有效数据的字节长度,这样{getEffectiveReceiveDataLength}中直接获取到这一位数并返回就行啦。 30 | * 方案是有很多种的,这里仅是提供思路。 31 | * 32 | * @param receiveArray {Array} 程序接收到蓝牙发送的所有数据 33 | * @returns {{dataArray: Array}} 34 | */ 35 | getEffectiveReceiveData({receiveArray}) { 36 | let effectiveReceiveDataLength = this.getEffectiveReceiveDataLength({receiveArray}), dataArray = []; 37 | if (effectiveReceiveDataLength > 0) { 38 | const endIndex = this.effectiveDataStartIndex + effectiveReceiveDataLength; 39 | dataArray = receiveArray.slice(this.effectiveDataStartIndex, endIndex); 40 | } 41 | return {dataArray}; 42 | } 43 | 44 | /** 45 | * 获取有效数据的字节长度 46 | * 该长度可根据接收到的数据动态获取或是计算,或是写固定值均可 47 | * 有效数据字节长度是指,在协议中由你的业务规定的具有特定含义的值的总字节长度 48 | * 有效数据更多的说明,以及该长度的计算规则示例,见 IBLEProtocolReceiveBody 类的 {getEffectiveReceiveData}函数 49 | * 50 | * @param receiveArray 接收到的一整包数据 51 | * @returns {number} 有效数据的字节长度 52 | */ 53 | getEffectiveReceiveDataLength({receiveArray}) { 54 | return 0; 55 | } 56 | } 57 | 58 | 59 | -------------------------------------------------------------------------------- /modules/bluetooth/lb-ble-common-protocol-body/i-protocol-send-body.js: -------------------------------------------------------------------------------- 1 | import {HexTools} from "../lb-ble-common-tool/index"; 2 | 3 | export default class IBLEProtocolSendBody { 4 | createBuffer({command, effectiveData}) { 5 | const dataBody = this.createDataBody({command, effectiveData}); 6 | return new Uint8Array(dataBody).buffer; 7 | } 8 | 9 | // createUpdateBuffer({index, data}) { 10 | // const dataBody = createUpdateDataBody({index, data}); 11 | // return new Uint8Array(dataBody).buffer; 12 | // } 13 | 14 | 15 | /** 16 | * 生成要发送的蓝牙协议 17 | * 格式是 [...有效数据前的数据,命令字,...有效数据,...有效数据之后的数据(包括校验位)] 18 | * 作者处理过的一些协议,是按照这个规则来制定的 19 | * 20 | * 该项目暴露的接口运行蓝牙发送协议和接收协议格式不同 21 | * 22 | * 关于有效数据前的数据、有效数据等相关的介绍,见 IBLEProtocolReceiveBody类的{getEffectiveReceiveData}函数的相关介绍 23 | * 24 | * @param command {String} 命令字 25 | * @param effectiveData {Array} 要发送的数据(即有效数据) 26 | * @returns {*[]} 27 | */ 28 | createDataBody({command = '', effectiveData = []}) { 29 | return [...this.getDataBeforeCommandData({ 30 | command, 31 | effectiveData 32 | }), 33 | HexTools.hexToNum(command), 34 | ...effectiveData, 35 | ...this.getDataAfterEffectiveData({ 36 | command, 37 | effectiveData 38 | })]; 39 | } 40 | 41 | 42 | /** 43 | * 命令字之前的数据 44 | * 45 | * @param command {String} 命令字 46 | * @param effectiveData {Array} 有效数据 47 | * @returns {Array} 48 | */ 49 | getDataBeforeCommandData({command, effectiveData} = {}) { 50 | return []; 51 | } 52 | 53 | /** 54 | * 获取有效数据之后的数据,包括校验位 55 | * 校验位的生成自行实现,只需在子类的getDataAfterEffectiveData返回值中加上这一个字节就行了 56 | * 关于获取有效数据之后的数据相关的介绍,见 IBLEProtocolReceiveBody类的{getEffectiveReceiveData}函数的相关介绍 57 | * 58 | * @param command 59 | * @param effectiveData 60 | * @returns {Array} 61 | */ 62 | getDataAfterEffectiveData({command, effectiveData} = {}) { 63 | return [] 64 | } 65 | } 66 | 67 | // function createUpdateDataBody({index, data = []}) { 68 | // const dataPart = []; 69 | // data.map(item => HexTools.numToHexArray(item)).forEach(item => dataPart.push(...item)); 70 | // let indexArray = HexTools.numToHexArray(index); 71 | // indexArray.length === 1 && indexArray.unshift(0); 72 | // return [170, ...indexArray, ...dataPart]; 73 | // } 74 | -------------------------------------------------------------------------------- /modules/bluetooth/lb-ble-common-protocol-body/index.js: -------------------------------------------------------------------------------- 1 | import IBLEProtocolReceiveBody from "./i-protocol-receive-body"; 2 | import IBLEProtocolSendBody from "./i-protocol-send-body"; 3 | 4 | export { 5 | IBLEProtocolReceiveBody, 6 | IBLEProtocolSendBody, 7 | }; 8 | -------------------------------------------------------------------------------- /modules/bluetooth/lb-ble-common-protocol-operator/README.md: -------------------------------------------------------------------------------- 1 | # 小程序端蓝牙协议收发公共模块 2 | -------------------------------------------------------------------------------- /modules/bluetooth/lb-ble-common-protocol-operator/index.js: -------------------------------------------------------------------------------- 1 | import LBlueToothProtocolOperator from "./lb-bluetooth-protocol-operator"; 2 | 3 | export {LBlueToothProtocolOperator}; 4 | -------------------------------------------------------------------------------- /modules/bluetooth/lb-ble-common-protocol-operator/lb-bluetooth-protocol-operator.js: -------------------------------------------------------------------------------- 1 | import {HexTools} from "../lb-ble-common-tool/index"; 2 | import {CommonProtocolState} from "../lb-ble-common-state/index"; 3 | 4 | const blueToothManager = Symbol("LBlueToothProtocolOperator blueToothManager"); 5 | export default class LBlueToothProtocolOperator { 6 | 7 | constructor({protocolSendBody, protocolReceiveBody}) { 8 | // this._protocolQueue = []; 9 | this.createBuffer = ({command, effectiveData}) => { 10 | return protocolSendBody.createBuffer({command, effectiveData}); 11 | }; 12 | this.sendProtocolData = ({command, effectiveData}) => { 13 | return this[blueToothManager].sendData({buffer: this.createBuffer({command, effectiveData})}); 14 | }; 15 | this.receive = ({receiveBuffer}) => { 16 | return this.receiveOperation({receiveBuffer, protocolReceiveBody}); 17 | }; 18 | 19 | this.receiveAction = this.getReceiveAction(); 20 | this.sendAction = this.getSendAction(); 21 | } 22 | 23 | /** 24 | * 处理接收的数据,返回截取到的有效数据、及{LBlueToothProtocolOperator}子类中配置的协议状态 25 | * @param receiveBuffer 从蓝牙底层获取到的蓝牙数据 26 | * @param protocolReceiveBody 27 | * @returns {{effectiveData:Array, protocolState: String}|{protocolState: string}} effectiveData 截取到的有效数据 protocolState 配置的协议状态 28 | */ 29 | receiveOperation({receiveBuffer, protocolReceiveBody}) { 30 | const receiveArray = [...new Uint8Array(receiveBuffer.slice(0, 20))]; 31 | let command = receiveArray[protocolReceiveBody.commandIndex]; 32 | let commandHex = `0x${HexTools.numToHex(command)}`; 33 | console.log('[LBlueToothProtocolOperator] the receive data command is:', commandHex); 34 | 35 | const {dataArray} = protocolReceiveBody.getEffectiveReceiveData({receiveArray}); 36 | 37 | const doAction = this.receiveAction[commandHex]; 38 | if (doAction) { 39 | const actionTemp = doAction({dataArray}); 40 | if (actionTemp?.protocolState) { 41 | const {protocolState, effectiveData} = actionTemp; 42 | return {protocolState, effectiveData}; 43 | } else { 44 | console.log('接收到的协议已处理完成,因[getReceiveAction]中对应的协议未返回协议状态protocolState,所以本次不通知协议状态'); 45 | return {protocolState: CommonProtocolState.UNKNOWN}; 46 | } 47 | } else { 48 | console.warn('接收到的协议无法在[getReceiveAction]中找到对应的协议,可能是您忘记添加了,或是接收到了无效协议,所以本次不通知协议状态'); 49 | return {protocolState: CommonProtocolState.UNKNOWN}; 50 | } 51 | } 52 | 53 | setBLEManager(manager) { 54 | this[blueToothManager] = manager; 55 | } 56 | 57 | /** 58 | * 清除未发送的蓝牙协议 59 | */ 60 | // clearSendProtocol() { 61 | // let temp; 62 | // while ((temp = this._protocolQueue.pop())) { 63 | // clearTimeout(temp); 64 | // } 65 | // } 66 | 67 | /** 68 | * 接收到协议数据后,需要执行的动作 69 | * 需要在子类中重写,详情见子类示例 70 | * @returns {{}} 71 | */ 72 | getReceiveAction() { 73 | return {}; 74 | } 75 | 76 | /** 77 | * 发送协议数据时,需要执行的动作 78 | * 需要在子类中重写,详情见子类示例 79 | * @returns {{}} 80 | */ 81 | getSendAction() { 82 | return {}; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /modules/bluetooth/lb-ble-common-state/README.md: -------------------------------------------------------------------------------- 1 | # 通用的蓝牙状态和蓝牙通信状态模块 2 | 3 | 4 | 记得修改其他模块的蓝牙状态引用为该模块 5 | -------------------------------------------------------------------------------- /modules/bluetooth/lb-ble-common-state/index.js: -------------------------------------------------------------------------------- 1 | import {CommonConnectState, CommonProtocolState} from "./state"; 2 | 3 | export { 4 | CommonConnectState, CommonProtocolState 5 | }; 6 | -------------------------------------------------------------------------------- /modules/bluetooth/lb-ble-common-state/state.js: -------------------------------------------------------------------------------- 1 | //公共的蓝牙连接状态,此为示例 2 | const CommonConnectState = { 3 | //蓝牙的常见状态值 4 | UNBIND: 'unbind',//未绑定 5 | UNAVAILABLE: 'unavailable',//蓝牙适配器不可用,通常是没有在手机设置中开启蓝牙,或是没有直接或间接调用父类中的openAdapter() 6 | DISCONNECT: 'disconnect',//蓝牙连接已断开 7 | CONNECTING: 'connecting',//正在连接蓝牙设备 8 | CONNECTED: 'connected',//已经正常连接到蓝牙设备 9 | NOT_SUPPORT: 'not_support',//当前Android系统版本小于4.3 10 | }; 11 | 12 | //公共的蓝牙协议状态,此为示例 13 | const CommonProtocolState = { 14 | UNKNOWN: 'unknown',//未知状态 15 | NORMAL_PROTOCOL: 'normal_protocol',//无需处理的协议 16 | CONNECTED_AND_BIND: 'connected_and_bind', 17 | QUERY_DATA_START: 'query_data_start',//开始与设备同步数据 18 | QUERY_DATA_ING: 'query_data_ing',//与设备同步数据状态中 19 | QUERY_DATA_FINISH: 'query_data_finish',//完成与设备同步数据的状态 20 | GET_CONNECTED_RESULT_SUCCESS: 'get_connected_result_success',//设备返回连接结果 21 | SEND_CONNECTED_REQUIRED: 'send_connected_required',//手机发送连接请求 22 | TIMESTAMP: 'timestamp',//设备获取时间戳 23 | FIND_DEVICE: 'find_device',//找到了设备 24 | }; 25 | 26 | export { 27 | CommonConnectState, CommonProtocolState 28 | } 29 | -------------------------------------------------------------------------------- /modules/bluetooth/lb-ble-common-tool/README.md: -------------------------------------------------------------------------------- 1 | # 小程序端蓝牙常用工具类模块 2 | -------------------------------------------------------------------------------- /modules/bluetooth/lb-ble-common-tool/index.js: -------------------------------------------------------------------------------- 1 | import {HexTools} from "./tools"; 2 | export { 3 | HexTools 4 | } 5 | -------------------------------------------------------------------------------- /modules/bluetooth/lb-ble-common-tool/tools.js: -------------------------------------------------------------------------------- 1 | export class HexTools { 2 | /** 3 | * 将16进制字符串转为十进制数字 4 | * 如 HexTools.hexToNum('0x11') //17 5 | * HexTools.hexToNum('21') //33 6 | * HexTools.hexToNum('0xffff') //65535 7 | * HexTools.hexToNum('ffff') //65535 8 | * @param str 可传入16进制的8位或16位字符串 9 | * @returns {number} 10 | */ 11 | static hexToNum(str = '') { 12 | if (str.indexOf('0x') === 0) { 13 | str = str.slice(2); 14 | } 15 | return parseInt(`0x${str}`); 16 | } 17 | 18 | /** 19 | * 十进制数字转为8位16进制字符串 20 | * @param num 21 | * @returns {string} 得到8位的16进制字符串 22 | */ 23 | static numToHex(num = 0) { 24 | return ('00' + num.toString(16)).slice(-2); 25 | } 26 | 27 | /** 28 | * hex数组转为num 29 | * 数组中每一元素都代表一个8位字节,10进制的数字 30 | * 比如:一个精确到毫秒位的时间戳数组为[ 1, 110, 254, 149, 130, 160 ],可以用这个函数来处理,得到十进制的时间戳1576229241504 31 | * 比如:一个精确到秒位的时间戳数组为[ 93, 243, 89, 121 ],可以用这个函数来处理,得到十进制的时间戳1576229241 32 | * 33 | * @param array 按高位在前,低位在后来排列的数组,数组中每一元素都代表一个8位字节,10进制的数字 34 | * @return {Number} 35 | */ 36 | static hexArrayToNum(array) { 37 | let count = 0, divideNum = array.length - 1; 38 | array.forEach((item, index) => count += item << (divideNum - index) * 8); 39 | return count; 40 | } 41 | /** 42 | * num转为hex数组 43 | * 与{hexArrayToNum}含义相反 44 | * @param num 45 | * @returns {*} 一个字节代表8位 46 | */ 47 | static numToHexArray(num) { 48 | if (num === void 0) { 49 | return []; 50 | } 51 | num = parseInt(num); 52 | if (num === 0) { 53 | return [0]; 54 | } 55 | let str = num.toString(16); 56 | str.length % 2 && (str = '0' + str); 57 | const array = []; 58 | for (let i = 0, len = str.length; i < len; i += 2) { 59 | array.push(parseInt(`0x${str.substr(i, 2)}`)); 60 | } 61 | return array; 62 | } 63 | /** 64 | * 获取数据的低八位 65 | * @param data 66 | * @returns {{lowLength: number, others: Array}} 67 | */ 68 | static getDataLowLength({data}) { 69 | const dataPart = []; 70 | data.map(item => HexTools.numToHexArray(item)).forEach(item => dataPart.push(...item)); 71 | const lowLength = HexTools.hexToNum((dataPart.length + 1).toString(16)); 72 | return {lowLength, others: dataPart}; 73 | } 74 | } 75 | 76 | -------------------------------------------------------------------------------- /modules/bluetooth/lb-ble-example-protocol-body/README.md: -------------------------------------------------------------------------------- 1 | # 小程序端某一特定设备的蓝牙协议收发规则(仅示例) 2 | 3 | 按照该框架约定的协议格式,去实现你自己的协议收发规则 4 | 5 | ``` 6 | 协议约定格式:[...命令字之前的数据(非必需), 命令字(必需), ...有效数据(非必需), 有效数据之后的数据(非必需 如协议结束标志校、验位等) 7 | 协议格式示例:[170(帧头), 10(命令字), 1(灯光开启),255,255,255(三个255,白色灯光),233(协议结束标志,有的协议中没有这一位),18(校验位,我胡乱写的)] 8 | 9 | ``` 10 | -------------------------------------------------------------------------------- /modules/bluetooth/lb-ble-example-protocol-body/receive-body.js: -------------------------------------------------------------------------------- 1 | import {IBLEProtocolReceiveBody} from "../lb-ble-common-protocol-body/index"; 2 | 3 | /** 4 | * 组装蓝牙协议接收数据示例 5 | * 该框架的蓝牙协议必须按照约定格式来制定,最多20个字节 6 | */ 7 | export default class ReceiveBody extends IBLEProtocolReceiveBody { 8 | constructor() { 9 | //commandIndex 命令字位置索引 10 | //effectiveDataStartIndex 有效数据开始索引,比如:填写0,{getEffectiveReceiveDataLength}中返回20,则会在{LBlueToothProtocolOperator}的子类{getReceiveAction}实现中,在参数中返回所有数据 11 | super({commandIndex: 1, effectiveDataStartIndex: 0}); 12 | } 13 | 14 | /** 15 | * 获取有效数据的字节长度 16 | * 该长度可根据接收到的数据动态获取或是计算,或是写固定值均可 17 | * 有效数据字节长度是指,在协议中由你的业务规定的具有特定含义的值的总字节长度 18 | * 有效数据更多的说明,以及该长度的计算规则示例,见 IBLEProtocolReceiveBody 类的 {getEffectiveReceiveData}函数 19 | * 20 | * @param receiveArray 接收到的一整包数据 21 | * @returns {number} 有效数据的字节长度 22 | */ 23 | getEffectiveReceiveDataLength({receiveArray}) { 24 | return 20; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /modules/bluetooth/lb-ble-example-protocol-body/send-body.js: -------------------------------------------------------------------------------- 1 | import {IBLEProtocolSendBody} from "../lb-ble-common-protocol-body/index"; 2 | import {HexTools} from "../lb-ble-common-tool/index"; 3 | 4 | 5 | /** 6 | * 组装蓝牙协议发送数据示例 7 | * 该框架的蓝牙协议必须按照约定格式来制定,最多20个字节 8 | */ 9 | export default class SendBody extends IBLEProtocolSendBody { 10 | 11 | getDataBeforeCommandData({command, effectiveData} = {}) { 12 | //有效数据前的数据 该示例只返回了帧头110 13 | return [110]; 14 | } 15 | 16 | getDataAfterEffectiveData({command, effectiveData} = {}) { 17 | //协议结束标志 18 | const endFlag = 233; 19 | //该示例中checkSum的生成规则是计算协议从第0个元素累加到结束标志 20 | let checkSum = endFlag + HexTools.hexToNum(command); 21 | for (let item of this.getDataBeforeCommandData()) { 22 | checkSum += item; 23 | } 24 | for (let item of effectiveData) { 25 | checkSum += item; 26 | } 27 | //生成有效数据之后的数据 28 | return [endFlag, checkSum]; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /modules/bluetooth/lb-bluetooth-state-example.js: -------------------------------------------------------------------------------- 1 | import {CommonConnectState, CommonProtocolState} from "./lb-ble-common-state/index"; 2 | 3 | //特定的蓝牙设备的协议状态,用于拓展公共的蓝牙协议状态 4 | //使用场景: 5 | //在手机接收到蓝牙数据成功或失败后,该框架会生成一条消息,包含了对应的蓝牙协议状态值{protocolState}以及对应的{effectiveData}(effectiveData示例见 lb-example-bluetooth-protocol.js), 6 | //在{setBLEListener}的{onReceiveData}回调函数中,对应参数{protocolState}和{value}(value就是effectiveData) 7 | const ProtocolState = { 8 | ...CommonProtocolState, 9 | RECEIVE_COLOR: 'receive_color',//获取到设备的颜色值 10 | RECEIVE_BRIGHTNESS: 'receive_brightness',//获取到设备的亮度 11 | RECEIVE_LIGHT_CLOSE: 'receive_close',//获取到设备灯光关闭事件 12 | }; 13 | 14 | export { 15 | ProtocolState, CommonConnectState as ConnectState 16 | }; 17 | -------------------------------------------------------------------------------- /modules/bluetooth/lb-example-bluetooth-manager.js: -------------------------------------------------------------------------------- 1 | import {LBlueToothManager} from "./lb-ble-common-connection/index"; 2 | import {getAppBLEProtocol} from "./lb-example-bluetooth-protocol"; 3 | 4 | /** 5 | * 蓝牙连接方式管理类 6 | * 初始化蓝牙连接时需筛选的设备,重写蓝牙连接规则 7 | */ 8 | export const getAppBLEManager = new class extends LBlueToothManager { 9 | constructor() { 10 | super(); 11 | //setFilter详情见父类 12 | super.setFilter({ 13 | services: ['0000xxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'],//必填 14 | targetServiceArray: [{ 15 | serviceId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',//必填 16 | writeCharacteristicId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxE',//必填 17 | notifyCharacteristicId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxF',//必填 18 | readCharacteristicId: '',//非必填 19 | }], 20 | targetDeviceName: '目标蓝牙设备的广播数据段中的 LocalName 数据段,如:smart-voice',//非必填,在判断时是用String.prototype.includes()函数来处理的,所以targetDeviceName不必是全称 21 | scanInterval: 350//扫描周围设备,重复上报的时间间隔,毫秒制,非必填,默认是350ms 22 | }); 23 | super.initBLEProtocol({bleProtocol: getAppBLEProtocol}); 24 | super.setMyFindTargetDeviceNeedConnectedFun({ 25 | /** 26 | * 重复上报时的过滤规则,并返回过滤结果 27 | * 在执行完该过滤函数,并且该次连接蓝牙有了最终结果后,才会在下一次上报结果回调时,再次执行该函数。 28 | * 所以如果在一次过滤过程中或是连接蓝牙,耗时时间很长,导致本次连接结果还没得到,就接收到了下一次的上报结果,则会忽略下一次{scanFilterRuler}的执行。 29 | * 如果不指定这个函数,则会使用默认的连接规则 30 | * 默认的连接规则详见 lb-ble-common-connection/utils/device-connection-manager.js的{defaultFindTargetDeviceNeedConnectedFun} 31 | * @param devices {*}是wx.onBluetoothDeviceFound(cb)中返回的{devices} 32 | * @param targetDeviceName {string}是{setFilter}中的配置项 33 | * @returns {{targetDevice: null}|{targetDevice: *}} 最终返回对象{targetDevice},是数组{devices}其中的一个元素;{targetDevice}可返回null,意思是本次扫描结果未找到指定设备 34 | */ 35 | scanFilterRuler: ({devices, targetDeviceName}) => { 36 | console.log('执行自定义的扫描过滤规则'); 37 | const tempFilterArray = []; 38 | for (let device of devices) { 39 | if (device.localName?.includes(targetDeviceName)) { 40 | tempFilterArray.push(device); 41 | } 42 | } 43 | if (tempFilterArray.length) { 44 | const device = tempFilterArray.reduce((pre, cur) => { 45 | return pre.RSSI > cur.RSSI ? pre : cur; 46 | }); 47 | return {targetDevice: device}; 48 | } 49 | return {targetDevice: null}; 50 | } 51 | }) 52 | } 53 | 54 | /** 55 | * 获取本机蓝牙适配器状态 56 | * @returns {Promise<*>} 返回值见小程序官网 wx.getBluetoothAdapterState 57 | */ 58 | async getBLEAdapterState() { 59 | return await super.getBLEAdapterState(); 60 | } 61 | 62 | /** 63 | * 获取最新的蓝牙连接状态 64 | * @returns {*} 65 | */ 66 | getBLELatestConnectState() { 67 | return super.getBLELatestConnectState(); 68 | } 69 | 70 | 71 | }(); 72 | -------------------------------------------------------------------------------- /modules/bluetooth/lb-example-bluetooth-protocol.js: -------------------------------------------------------------------------------- 1 | import {LBlueToothProtocolOperator} from "./lb-ble-common-protocol-operator/index"; 2 | import SendBody from "./lb-ble-example-protocol-body/send-body"; 3 | import ReceiveBody from "./lb-ble-example-protocol-body/receive-body"; 4 | import {ProtocolState} from "./lb-bluetooth-state-example"; 5 | 6 | /** 7 | * 蓝牙协议管理类 8 | * 在这个类中,以配置的方式来编写读操作和写操作 9 | * 配置方式见下方示例 10 | */ 11 | export const getAppBLEProtocol = new class extends LBlueToothProtocolOperator { 12 | constructor() { 13 | super({protocolSendBody: new SendBody(), protocolReceiveBody: new ReceiveBody()}); 14 | } 15 | 16 | /** 17 | * 写操作(仅示例) 18 | */ 19 | getSendAction() { 20 | return { 21 | /** 22 | * 0x01:设置灯色(写操作) 23 | * @param red 0x00 - 0xff 24 | * @param green 0x00 - 0xff 25 | * @param blue 0x00 - 0xff 26 | * @returns {Promise} 27 | */ 28 | '0x01': async ({red, green, blue}) => { 29 | return await this.sendProtocolData({command: '0x01', effectiveData: [red, green, blue]}); 30 | }, 31 | 32 | /** 33 | * 0x02:设置灯亮度(写操作) 34 | * @param brightness 灯亮度值 0~100 对应最暗和最亮 35 | * @returns {Promise} 36 | */ 37 | '0x02': async ({brightness}) => { 38 | //data中的数据,填写多少个数据都可以,可以像上面的3位,也可以像这条6位。你只要能保证data的数据再加上你其他的数据,数组总长度别超过20个就行。 39 | return await this.sendProtocolData({command: '0x02', effectiveData: [brightness, 255, 255, 255, 255, 255]}); 40 | }, 41 | } 42 | } 43 | 44 | /** 45 | * 读操作(仅示例) 46 | * {dataArray}是一个数组,包含了您要接收的有效数据。 47 | * {dataArray}的内容是在lb-ble-example-protocol-body.js中的配置的。 48 | * 是由您配置的 dataStartIndex 和 getEffectiveReceiveDataLength 共同决定的 49 | */ 50 | getReceiveAction() { 51 | return { 52 | /** 53 | * 获取设备当前的灯色(读) 54 | * 可return蓝牙协议状态protocolState和接收到的数据effectiveData, 55 | * 该方法的返回值,只要拥有非空的protocolState,该框架便会同步地通知前端同protocolState类型的消息 56 | * 当然是在你订阅了setBLEListener({onReceiveData})时才会在订阅的地方接收到消息。 57 | */ 58 | '0x10': ({dataArray}) => { 59 | const [red, green, blue] = dataArray; 60 | return {protocolState: ProtocolState.RECEIVE_COLOR, effectiveData: {red, green, blue}}; 61 | }, 62 | /** 63 | * 获取设备当前的灯亮度(读) 64 | */ 65 | '0x11': ({dataArray}) => { 66 | const [brightness] = dataArray; 67 | return {protocolState: ProtocolState.RECEIVE_BRIGHTNESS, effectiveData: {brightness}}; 68 | }, 69 | /** 70 | * 接收到设备主动发送的灯光关闭消息 71 | * 模拟的场景是,用户关闭了设备灯光,设备需要主动推送灯光关闭事件给手机 72 | */ 73 | '0x12': () => { 74 | //你可以不传递effectiveData 75 | return {protocolState: ProtocolState.RECEIVE_LIGHT_CLOSE}; 76 | }, 77 | /** 78 | * 接收到蓝牙设备的其他一些数据 79 | */ 80 | '0x13': ({dataArray}) => { 81 | //do something 82 | //你可以不返回任何值 83 | } 84 | }; 85 | } 86 | 87 | /** 88 | * 设置灯亮度和颜色 89 | * @param brightness 90 | * @param red 91 | * @param green 92 | * @param blue 93 | * @returns {Promise<[unknown, unknown]>} 94 | */ 95 | async setColorLightAndBrightness({brightness, red, green, blue}) { 96 | //发送协议,小程序官方提醒并行调用多次会存在写失败的可能性,所以建议使用串行方式来发送,哪种方式由你权衡 97 | //但我这里是并行发送了两条0x01和0x02两条协议,仅演示用 98 | return Promise.all([this.sendAction['0x01']({red, green, blue}), this.sendAction['0x02']({brightness})]); 99 | } 100 | 101 | }(); 102 | 103 | -------------------------------------------------------------------------------- /pages/bluetooth/bluetooth.js: -------------------------------------------------------------------------------- 1 | import Toast from "../../view/toast"; 2 | import UI from './ui'; 3 | import {ConnectState} from "../../modules/bluetooth/lb-bluetooth-state-example"; 4 | import {getAppBLEProtocol} from "../../modules/bluetooth/lb-example-bluetooth-protocol"; 5 | import {getAppBLEManager} from "../../modules/bluetooth/lb-example-bluetooth-manager"; 6 | 7 | const app = getApp(); 8 | Page({ 9 | 10 | /** 11 | * 页面的初始数据 12 | */ 13 | data: { 14 | connectState: ConnectState.UNAVAILABLE 15 | }, 16 | 17 | /** 18 | * 生命周期函数--监听页面加载 19 | */ 20 | onLoad(options) { 21 | this.ui = new UI(this); 22 | //监听蓝牙连接状态、订阅蓝牙协议接收事件 23 | //多次订阅只会在最新订阅的函数中生效。 24 | //建议在app.js中订阅,以实现全局的事件通知 25 | getAppBLEManager.setBLEListener({ 26 | onConnectStateChanged: async (res) => { 27 | const {connectState} = res; 28 | console.log('蓝牙连接状态更新', res); 29 | this.ui.setState({state: connectState}); 30 | switch (connectState) { 31 | case ConnectState.CONNECTED: 32 | //在连接成功后,紧接着设置灯光颜色和亮度 33 | //发送协议,官方提醒并行调用多次会存在写失败的可能性,所以建议使用串行方式来发送 34 | await getAppBLEProtocol.setColorLightAndBrightness({ 35 | brightness: 100, 36 | red: 255, 37 | green: 0, 38 | blue: 0 39 | }); 40 | 41 | break; 42 | default: 43 | 44 | break; 45 | } 46 | 47 | }, 48 | 49 | /** 50 | * 接收到的蓝牙设备传给手机的有效数据,只包含你最关心的那一部分 51 | * protocolState和value具体的内容是在lb-example-bluetooth-protocol.js中定义的 52 | * 53 | * @param protocolState 蓝牙协议状态值,string类型,值是固定的几种,详情示例见: 54 | * @param value 传递的数据,对应lb-example-bluetooth-protocol.js中的{effectiveData}字段 55 | */ 56 | onReceiveData: ({protocolState, value}) => { 57 | console.log('蓝牙协议接收到新的 protocolState:', protocolState, 'value:', value); 58 | } 59 | }); 60 | 61 | //这里执行连接后,程序会按照你指定的规则(位于getAppBLEManager中的setFilter中指定的),自动连接到距离手机最近的蓝牙设备 62 | getAppBLEManager.connect(); 63 | }, 64 | 65 | /** 66 | * 断开连接 67 | * @param e 68 | * @returns {Promise} 69 | */ 70 | async disconnectDevice(e) { 71 | // closeAll() 会断开蓝牙连接、关闭适配器 72 | await getAppBLEManager.closeAll(); 73 | this.setData({ 74 | device: {} 75 | }); 76 | setTimeout(Toast.success, 0, '已断开连接'); 77 | }, 78 | 79 | /** 80 | * 连接到最近的设备 81 | */ 82 | connectHiBreathDevice() { 83 | getAppBLEManager.connect(); 84 | }, 85 | 86 | async onUnload() { 87 | await getAppBLEManager.closeAll(); 88 | }, 89 | }); 90 | 91 | 92 | -------------------------------------------------------------------------------- /pages/bluetooth/bluetooth.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /pages/bluetooth/bluetooth.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 打开小程序自动连接,若未连接成功请打开调试模式定位问题 4 | 连接状态:{{connectState}} 5 | 6 | 7 | 8 | 11 | 12 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /pages/bluetooth/bluetooth.wxss: -------------------------------------------------------------------------------- 1 | /* pages/bluetooth/bluetooth.wxss */ -------------------------------------------------------------------------------- /pages/bluetooth/ui.js: -------------------------------------------------------------------------------- 1 | import {ConnectState} from "../../modules/bluetooth/lb-bluetooth-state-example"; 2 | 3 | export default class UI { 4 | constructor(page) { 5 | this.stateObj = {}; 6 | this.stateObj[`${ConnectState.CONNECTED}`] = function () { 7 | page.setData({ 8 | connectState: ConnectState.CONNECTED 9 | }); 10 | }; 11 | this.stateObj[`${ConnectState.DISCONNECT}`] = function () { 12 | page.setData({ 13 | connectState: ConnectState.DISCONNECT 14 | }); 15 | }; 16 | this.stateObj[`${ConnectState.UNAVAILABLE}`] = function () { 17 | page.setData({ 18 | connectState: ConnectState.UNAVAILABLE 19 | }); 20 | }; 21 | this.stateObj[`${ConnectState.CONNECTING}`] = function () { 22 | page.setData({ 23 | connectState: ConnectState.CONNECTING 24 | }); 25 | }; 26 | } 27 | 28 | setState({state}) { 29 | const fun = this.stateObj[state]; 30 | if (!!fun) { 31 | fun(); 32 | } else { 33 | console.log(`目前没有这种状态:${state}`); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /view/toast.js: -------------------------------------------------------------------------------- 1 | export default class Toast { 2 | 3 | static success(title, duration) { 4 | wx.showToast({ 5 | title: title, 6 | icon: 'success', 7 | duration: !!duration ? duration : 2000, 8 | }) 9 | } 10 | 11 | static warn(title, duration) { 12 | wx.showToast({ 13 | title: title, 14 | duration: !!duration ? duration : 2000, 15 | image: '/images/loading_fail.png' 16 | }) 17 | } 18 | 19 | static showLoading(text) { 20 | wx.showLoading({ 21 | title: text || '请稍后...', 22 | mask: true 23 | }) 24 | } 25 | 26 | static hiddenLoading() { 27 | wx.hideLoading(); 28 | } 29 | 30 | static hiddenToast() { 31 | wx.hideToast(); 32 | } 33 | } 34 | --------------------------------------------------------------------------------