├── .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 | 
509 | 
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 |
--------------------------------------------------------------------------------