├── .gitignore ├── LICENSE ├── READAPI.md ├── README.md ├── example ├── antdemo │ ├── .tea │ │ └── entryFiles-development │ │ │ ├── config$.js │ │ │ ├── importScripts$.js │ │ │ ├── index$.remote.worker.js │ │ │ ├── index$.web.js │ │ │ └── index$.worker.js │ ├── app.acss │ ├── app.js │ ├── app.json │ ├── images │ │ └── arrow.png │ ├── pages │ │ ├── home │ │ │ ├── home.acss │ │ │ ├── home.axml │ │ │ ├── home.js │ │ │ └── home.json │ │ └── index │ │ │ ├── index.axml │ │ │ ├── index.js │ │ │ └── index.json │ ├── snapshot.png │ └── wx-ant-ble │ │ ├── READAPI.md │ │ ├── README.md │ │ ├── index.js │ │ ├── package.json │ │ └── src │ │ ├── bluetooth.js │ │ ├── btmanager.js │ │ ├── enum.js │ │ ├── extends.js │ │ ├── promisify.js │ │ └── tools.js └── wxdemo │ ├── app.js │ ├── app.json │ ├── app.wxss │ ├── images │ └── arrow.png │ ├── miniprogram_npm │ └── wx-ant-ble │ │ ├── index.js │ │ └── index.js.map │ ├── package-lock.json │ ├── package.json │ ├── pages │ ├── home │ │ ├── home.js │ │ ├── home.json │ │ ├── home.wxml │ │ └── home.wxss │ └── index │ │ ├── index.js │ │ ├── index.json │ │ ├── index.wxml │ │ └── index.wxss │ ├── project.config.json │ └── utils │ └── util.js ├── index.js ├── package.json ├── resource ├── QRcode.jpg └── powerpoint.gif └── src ├── bluetooth.js ├── btmanager.js ├── enum.js ├── extends.js ├── promisify.js └── tools.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | .DS_Store 64 | 65 | lncp.sh 66 | 67 | .npmignore 68 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 赵大海 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 | -------------------------------------------------------------------------------- /READAPI.md: -------------------------------------------------------------------------------- 1 | # wx-ant-ble 接口文档 2 | 3 | ### 目录 4 | - [初始化 `BTManager() `](#jump-init) 5 | - [扫描外设 `scan()`](#jump-scan) 6 | - [停止扫描 `stopScan()`](#jump-stopScan) 7 | - [连接外设 `connect()`](#jump-connect) 8 | - [断开连接 `disconnect()`](#jump-disconnect) 9 | - [读特征值 `read()`](#jump-read) 10 | - [写数据 `write()`](#jump-write) 11 | - [监听特征值 `notify()`](#jump-notify) 12 | - [注册状态更新回调 `registerDidUpdateConnectStatus()`](#jump-callback-connect-status) 13 | - [注册发现外设回调 `registerDidDiscoverDevice()`](#jump-callback-discover-device) 14 | - [注册特征值改变回调 `registerDidUpdateValueForCharacteristic()`](#jump-callback-value-update) 15 | 16 | 17 | ### 详细接口说明 18 | - #### 初始化 `BTManager(config)` 19 | > 参数:`config(Object)` 配置
20 | > 说明:初始化SDK管理实例,**单例模式**。 21 | > 22 | ```js 23 | this.bt = new BTManager({ 24 | debug: false 25 | }); 26 | ``` 27 | | 参数字段 | 类型 | 必填 | 默认 | 说明 | 28 | | :-- | :--: | :--: | :--: | :-- | 29 | | debug | Boolean | 否 | false | 是否开启打印调试 | 30 | 31 | 32 | - #### 扫描外设 `scan(options)` 33 | > 参数:`options(Object)` 扫描参数
34 | > 返回值:Promise对象,[调用成功](#jump-SuccessApiThen) | [调用失败](#jump-ErrorApiCatch)
35 | > 说明:开始扫描外设,注意实现返回对象的then和catch方法,监听接口是否调用成功。
36 | >     此操作比较耗费系统资源,请在搜索到设备后调用stopScan方法停止扫描。
37 | >     重复调用此接口,会清空之前设备存储,再次上报已上报的设备,能够起到刷新的作用。 38 | > 39 | ```js 40 | this.bt.scan({ 41 | services: [], 42 | allowDuplicatesKey: false, 43 | interval : 0, 44 | timeout: 15, 45 | deviceName: '', 46 | containName:'' 47 | }).then(res => { 48 | console.log('scan success', res); 49 | }).catch(e => { 50 | console.log('scan fail', e); 51 | }); 52 | ``` 53 | | 参数字段 | 类型 | 必填 | 默认 | 说明 | 54 | | :-- | :-- | :--: | :--: | :-- | 55 | | services | Array | 否 | [] |主service的uuid列表。确认在蓝牙广播中存在此服务id,可以通过服务id过滤掉其他设备 | 56 | | allowDuplicatesKey | Boolean | 否 | false | 是否允许重复上报设备 | 57 | | interval | Number | 否 | 0 | 上报新设备的间隔 | 58 | | timeout | Number | 否 | 15000 | 扫描超时时间,毫秒。在该时间内未扫描到符合要求的设备,上报超时。-1表示无限超时。[超时回调](#jump-callback-discover-device) | 59 | | deviceName | String | 否 | "" | 通过蓝牙名称过滤,需要匹配的设备名称 | 60 | | containName | String | 否 | "" | 通过蓝牙名称过滤,需要包含的设备名称 | 61 | 62 | 63 | - #### 停止扫描 `stopScan()` 64 | > 返回值:Promise对象,[调用成功](#jump-SuccessApiThen) | [调用失败](#jump-ErrorApiCatch)
65 | > 说明:停止扫描,取消超时延时。 66 | ```js 67 | this.bt.stopScan() 68 | .then(res => { 69 | console.log('stopScan success', res); 70 | }).catch(e => { 71 | console.log('stopScan fail', e); 72 | }) 73 | ``` 74 | 75 | - #### 连接外设 `connect(device , timeout)` 76 | > 参数:`device(Object)` 设备对象
77 | >     `timeout(Number)` 超时时间
78 | > 返回值:Promise对象,[调用成功](#jump-SuccessApiThen) | [调用失败](#jump-ErrorApiCatch)
79 | > 说明:连接指定的外设,需要传入外设对象。 80 | > 81 | ```js 82 | this.bt.connect(device) 83 | .then(res => { 84 | console.log('connect success', res); 85 | }).catch(e => { 86 | console.log('connect fail', e); 87 | }); 88 | ``` 89 | | 参数字段 | 类型 | 必填 | 默认 | 说明 | 90 | | :-- | :-- | :--: | :--: | :-- | 91 | | device | Object | 是 | -- | 指定连接的[外设对象](#jump-DeviceInfo),从[registerDidDiscoverDevice](#jump-callback-discover-device)注册的回调中得到 | 92 | | timeout | Number | 否 | 15000 | 连接超时时间,毫秒,支付宝小程序无效| 93 | 94 | 95 | - #### 断开连接 `disconnect()` 96 | > 返回值:Promise对象,[调用成功](#jump-SuccessApiThen) | [调用失败](#jump-ErrorApiCatch)
97 | > 98 | ```js 99 | this.bt.disconnect() 100 | .then(res => { 101 | console.log('disconnect success', res); 102 | }).catch(e => { 103 | console.log('disconnect fail', e); 104 | }) 105 | ``` 106 | 107 | - #### 读特征值 `read(params)` 108 | > 参数:`params(Object)` 参数
109 | > 返回值:Promise对象,[调用成功](#jump-SuccessApiThen) | [调用失败](#jump-ErrorApiCatch)
110 | > 说明:读某个特征值。 111 | > 112 | ```js 113 | this.bt.read({ 114 | suuid: 'xxxx', // 特征对应的服务uuid 115 | cuuid: 'xxxx' // 特征uuid 116 | }).then(res => { 117 | console.log('read success', res); 118 | }).catch(e => { 119 | console.log('read fail', e); 120 | }) 121 | ``` 122 | | 参数字段 | 类型 | 必填 | 说明 | 123 | | :--: | :--: | :--: | :--: | 124 | | suuid | String | 是 | 特征对应的服务uuid | 125 | | cuuid | String | 是 | 特征uuid | 126 | 127 | 128 | - #### 写数据 `write(params)` 129 | > 参数:`params(Object)` 参数
130 | > 返回值:Promise对象,[调用成功](#jump-SuccessApiThen) | [调用失败](#jump-ErrorApiCatch)
131 | > 说明:向蓝牙模块写入数据。 132 | > 133 | ```js 134 | this.bt.write({ 135 | suuid: 'xxxx', // 特征对应的服务uuid 136 | cuuid: 'xxxx', // 特征uuid 137 | value: 'FFFF' // 写入的数据 138 | }).then(res => { 139 | console.log('write success', res); 140 | }).catch(e => { 141 | console.log('write fail', e); 142 | }) 143 | ``` 144 | | 参数字段 | 类型 | 必填 | 说明 | 145 | | :--: | :--: | :--: | :--: | 146 | | suuid | String | 是 | 特征对应的服务uuid | 147 | | cuuid | String | 是 | 特征uuid | 148 | | value | String | 是 | 16进制字符串 | 149 | 150 | 151 | - #### 读特征值 `notify(params)` 152 | > 参数:`params(Object)` 参数
153 | > 返回值:Promise对象,[调用成功](#jump-SuccessApiThen) | [调用失败](#jump-ErrorApiCatch)
154 | > 说明:监听某个特征值变化。 155 | > 156 | ```js 157 | this.bt.notify({ 158 | suuid: 'xxxx', // 特征对应的服务uuid 159 | cuuid: 'xxxx', // 特征uuid 160 | state: true/false // 是否监听 161 | }).then(res => { 162 | console.log('notify success', res); 163 | }).catch(e => { 164 | console.log('notify fail', e); 165 | }) 166 | ``` 167 | | 参数字段 | 类型 | 必填 | 说明 | 168 | | :--: | :--: | :--: | :--: | 169 | | suuid | String | 是 | 特征对应的服务uuid | 170 | | cuuid | String | 是 | 特征uuid | 171 | | state | Boolean | 是 | 是否启用notify,可以通过重复调用接口改变此属性打开/关闭监听 | 172 | 173 | 174 | - #### 注册状态更新回调 `registerDidUpdateConnectStatus(callback)` 175 | > 参数:`callback(Function)` 回调函数
176 | > 说明:注册状态更新时的回调函数,当状态connectStatus发生变化时回调已注册的函数。 177 | > 178 | ```js 179 | this.bt.registerDidUpdateConnectStatus(res => { 180 | // 参数结构注意查看log 181 | console.log('registerDidUpdateConnectStatus', res); 182 | if (res.connectStatus === ConnectStatus.connected) { 183 | this.setData({ isConnected: true }); 184 | ... 185 | }else if (res.connectStatus === ConnectStatus.disconnected) { 186 | this.setData({ isConnected: false }); 187 | ... 188 | } 189 | }); 190 | ``` 191 | **返回示例** 192 | ```js 193 | { 194 | code: 222, 195 | connectStatus: 2, 196 | message: '连接成功', 197 | device: { 198 | RSSI: -70, // 蓝牙信号强度,越大越强 199 | name: 'XXXX', // 设备名称 200 | advertisData: 'XXXX', // 广播数据 201 | deviceId: 'XXXX', // 设备id 202 | ... 203 | } 204 | } 205 | ``` 206 | | 参数字段 | 类型 | 参考 | 必填 | 说明 | 207 | | :-- | :-- | :-- | :--: | :-- | 208 | | code | Number | [回调成功事件](#jump-SuccessCallbackEvent) [回调失败事件](#jump-ErrorCallbackEvent) | 是 | 连接状态码 | 209 | | connectStatus | ConnectStatus | [连接状态说明](#jump-ConnectStatus) | 是 | 连接状态 | 210 | | device | Object | [设备说明](#jump-DeviceInfo) | 否 | 设备信息,连接成功之后获取 | 211 | | message | String | [回调成功事件](#jump-SuccessCallbackEvent) [回调失败事件](#jump-ErrorCallbackEvent) | 是 | 状态描述 | 212 | 213 | 214 | - #### 注册发现外设回调 `registerDidDiscoverDevice(callback)` 215 | > 参数:`callback(Function)` 回调函数
216 | > 说明:当扫描到设备时回调,或者达到超时时间回调。 217 | > 218 | ```js 219 | this.bt.registerDidDiscoverDevice(res => { 220 | // 参数结构注意查看log 221 | console.log('registerDidDiscoverDevice', res); 222 | ... 223 | }); 224 | ``` 225 | **返回示例** 226 | ```js 227 | { 228 | code: 210, 229 | timeout: false, 230 | message: '发现外设', 231 | device: { 232 | RSSI: -70, // 蓝牙信号强度,越大越强 233 | name: 'XXXX', // 设备名称 234 | advertisData: 'XXXX', // 广播数据 235 | deviceId: 'XXXX', // 设备id 236 | ... 237 | } 238 | } 239 | ``` 240 | | 参数字段 | 类型 | 参考 | 必填 | 说明 | 241 | | :-- | :-- | :-- | :--: | :-- | 242 | | code | Number | [回调成功事件](#jump-SuccessCallbackEvent) [回调失败事件](#jump-ErrorCallbackEvent) | 是 | 扫描状态码 | 243 | | timeout | Boolean | -- | 是 | 扫描是否超时 | 244 | | device | Object | [设备说明](#jump-DeviceInfo) | 否 | 设备信息 | 245 | | message | String | [回调成功事件](#jump-SuccessCallbackEvent) [回调失败事件](#jump-ErrorCallbackEvent) | 是 | 状态描述 | 246 | 247 | 248 | - #### 注册状态更新回调 `registerDidUpdateValueForCharacteristic(callback)` 249 | > 参数:`callback(Function)` 回调函数
250 | > 说明:当监听的特征值改变时回调,或者读特征值时回调。 251 | > 252 | ```js 253 | this.bt.registerDidUpdateValueForCharacteristic(res => { 254 | // 参数结构注意查看log 255 | console.log('registerDidUpdateValueForCharacteristic', res); 256 | ... 257 | }); 258 | ``` 259 | **返回示例** 260 | ```js 261 | { 262 | characteristic: 'XXXX', 263 | serviceId: 'XXXX', 264 | deviceId: 'XXXX', 265 | value: 'FFFF' 266 | } 267 | ``` 268 | | 参数字段 | 类型 | 必填 | 说明 | 269 | | :-- | :--: | :--: | :-- | 270 | | characteristic | String | 是 | 特征uuid | 271 | | serviceId | String | 是 | 特征对应的服务uuid | 272 | | deviceId | String | 是 | 设备id | 273 | | value | String | 是 | 特征值 | 274 | 275 | 276 | ## 枚举类型说明 277 | 278 | - #### 设备信息 279 | | 属性字段 | 类型 | 必填 | 说明 | 280 | | :-- | :-- | :--: | :-- | 281 | | RSSI | Number | 是 | 设备信号强度 | 282 | | deviceId | String | 是 | 设备id | 283 | | name | String | 是 | 设备名称 | 284 | | localName | String | 否 | 设备名称 | 285 | | services | Array | 是 | 设备所有[服务](#jump-DeviceService) | 286 | | advertisData | String | 否 | 广播数据 | 287 | | advertisServiceUUIDs | Array | 否 | 广播中的服务UUID | 288 | 289 | 290 | - #### 服务 291 | | 属性字段 | 类型 | 必填 | 说明 | 292 | | :-- | :-- | :--: | :-- | 293 | | serviceId | String | 是 | 服务uuid | 294 | | characteristics | Array | 是 | 服务中的所有[特征](#jump-DeviceCharacteristic) | 295 | 296 | 297 | - #### 连接状态 `ConnectStatus` 298 | | 状态key | 状态值(Number)| 状态描述 | 299 | | :-- | :--: | :--: | 300 | | ConnectStatus.disconnected | 0 | 未连接或连接断开,允许连接 | 301 | | ConnectStatus.connecting | 1 | 正在连接,不允许再连接 | 302 | | ConnectStatus.connected | 2 | 已连接,不允许再连接 | 303 | 304 | 305 | - #### 特征 306 | | 属性字段 | 类型 | 必填 | 说明 | 307 | | :-- | :-- | :--: | :-- | 308 | | uuid | String | 是 | 特征uuid | 309 | | properties | Object | 是 | 特征的特性 indicate/notify/read/write | 310 | 311 | 312 | - #### 发现外设回调和连接状态改变回调成功事件 `SuccessCallbackEvent` 313 | | 事件码 | 事件key |事件描述 | 314 | | :--: | :-- | :-- | 315 | | 210 | SuccessCallbackEvent.Success_DiscoverDevice_CB_Discover | 发现外设 | 316 | | 211 | SuccessCallbackEvent.Success_DiscoverDevice_CB_ScanDone | 扫描完成 | 317 | | 220 | SuccessCallbackEvent.Success_ConnectStatus_CB_PowerOn | 蓝牙打开 | 318 | | 221 | SuccessCallbackEvent.Success_ConnectStatus_CB_Connecting| 正在连接 | 319 | | 222 | SuccessCallbackEvent.Success_ConnectStatus_CB_Connected | 连接成功 | 320 | | 223 | SuccessCallbackEvent.Success_ConnectStatus_CB_Stop | 断开成功 | 321 | 322 | 323 | - #### 发现外设回调和连接状态改变回调失败事件 `ErrorCallbackEvent` 324 | | 事件码 | 事件key |事件描述 | 325 | | :--: | :-- | :-- | 326 | | 410 | ErrorCallbackEvent.Error_DiscoverDevice_CB_Timeout | 扫描超时 | 327 | | 420 | ErrorCallbackEvent.Error_ConnectStatus_CB_PowerOff | 蓝牙关闭 | 328 | | 421 | ErrorCallbackEvent.Error_ConnectStatus_CB_ConnectFail | 连接失败 | 329 | | 422 | ErrorCallbackEvent.Error_ConnectStatus_CB_Disconnected | 连接断开 | 330 | 331 | 332 | - #### 接口调用成功事件`SuccessApiThen` 333 | | 事件码 | 事件key |事件描述 | 334 | | :--: | :-- | :-- | 335 | | 2010 | SuccessApiThen.Success_Scan | 扫描接口成功调用 | 336 | | 2020 | SuccessApiThen.Success_StopScan | 停止扫描接口成功调用 | 337 | | 2030 | SuccessApiThen.Success_Connect | 连接接口成功调用 | 338 | | 2040 | SuccessApiThen.Success_Disconnect | 断开接口成功调用 | 339 | | 2050 | SuccessApiThen.Success_Read | 读特征值接口成功调用 | 340 | | 2060 | SuccessApiThen.Success_Write | 写入数据接口成功调用 | 341 | | 2070 | SuccessApiThen.Success_Notify | 监听特征值接口成功调用 | 342 | 343 | 344 | - #### 接口调用失败事件`ErrorApiCatch` 345 | | 事件码 | 事件key |事件描述 | 346 | | :--: | :-- | :-- | 347 | | 4000 | ErrorApiCatch.Error_Low_Version | 当前基础库版本低,请更新微信版本 | 348 | | 4010 | ErrorApiCatch.Error_Scan_Failed | 扫描错误:扫描失败 | 349 | | 4011 | ErrorApiCatch.Error_Scan_PowerOff | 扫描错误:蓝牙被关闭 | 350 | | 4012 | ErrorApiCatch.Error_Scan_NoService | 扫描错误:没有找到指定服务 | 351 | | 4020 | ErrorApiCatch.Error_StopScan_Failed | 停止扫描错误:停止扫描失败 | 352 | | 4021 | ErrorApiCatch.Error_StopScan_PowerOff | 停止扫描错误:蓝牙被关闭 | 353 | | 4030 | ErrorApiCatch.Error_Connect_Failed | 连接错误:连接失败 | 354 | | 4031 | ErrorApiCatch.Error_Connect_PowerOff | 连接错误:蓝牙被关闭 | 355 | | 4032 | ErrorApiCatch.Error_Connect_AlreadyConnected | 连接错误:已经连接或正在连接 | 356 | | 4033 | ErrorApiCatch.Error_Connect_Timeout | 连接错误:连接超时 | 357 | | 4034 | ErrorApiCatch.Error_Connect_EmptyId | 连接错误:设备id不能为空 | 358 | | 4040 | ErrorApiCatch.Error_Disconnect_Failed | 断开错误:断开失败 | 359 | | 4050 | ErrorApiCatch.Error_Read_Failed | 读特征值错误:读特征值失败 | 360 | | 4051 | ErrorApiCatch.Error_Read_NotConnected | 读特征值错误:蓝牙未连接 | 361 | | 4052 | ErrorApiCatch.Error_Read_NotSupport | 读特征值错误:当前特征不支持读操作 | 362 | | 4053 | ErrorApiCatch.Error_Read_NoService | 读特征值错误:没有找到指定服务 | 363 | | 4054 | ErrorApiCatch.Error_Read_NoCharacteristic | 读特征值错误:没有找到指定特征值 | 364 | | 4060 | ErrorApiCatch.Error_Write_Failed | 写入数据错误:写入数据失败 | 365 | | 4061 | ErrorApiCatch.Error_Write_NotConnected | 写入数据错误:蓝牙未连接 | 366 | | 4062 | ErrorApiCatch.Error_Write_NotSupport | 写入数据错误:当前特征不支持写操作 | 367 | | 4063 | ErrorApiCatch.Error_Write_NoService | 写入数据错误:没有找到指定服务 | 368 | | 4064 | ErrorApiCatch.Error_Write_NoCharacteristic | 写入数据错误:没有找到指定特征值 | 369 | | 4070 | ErrorApiCatch.Error_Notify_Failed | 监听特征值错误:监听特征值错误失败 | 370 | | 4071 | ErrorApiCatch.Error_Notify_NotConnected | 监听特征值错误:蓝牙未连接 | 371 | | 4072 | ErrorApiCatch.Error_Notify_NotSupport | 监听特征值错误:当前特征不支持监听操作 | 372 | | 4073 | ErrorApiCatch.Error_Notify_NoService | 监听特征值错误:没有找到指定服务 | 373 | | 4074 | ErrorApiCatch.Error_Notify_NoCharacteristic | 监听特征值错误:没有找到指定特征值 | 374 | 375 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wx-ant-ble 2 | 3 | [![npm version](https://img.shields.io/npm/v/wx-ant-ble.svg?style=flat)](https://www.npmjs.com/package/wx-ant-ble) 4 | 5 | ### 微信、支付宝小程序BLE蓝牙SDK 6 | 7 | ### [详细接口说明](READAPI.md) 8 | 9 | ## Release Notes 10 | 11 | #### v1.1.0 12 | 2019/01/15
13 | 1、发布第一个可用版本。封装蓝牙接口,兼容微信和支付宝小程序。附有说明和demo。 14 | 15 | #### v1.1.1 16 | 2019/08/15
17 | 1、修复微信小程序安卓手机断开连接,状态更新不回调的bug。
18 | 2、已知另一个微信小程序蓝牙bug。安卓手机在设备异常断开时(比如断电),不会触发`onBLEConnectionStateChange`回调。除非在已经设置监听notify的情况下。 19 | 20 | ## Features 21 | - 兼容微信和支付宝小程序 22 | - 简洁但功能完整的Api,可以根据需求自由调用 23 | - 接口均有返回状态,判断是否调用成功 24 | - 单例模式 25 | - [完整例子](https://github.com/zhaodahai/wxble) 26 | 27 | ## Directory 28 | - ~/index.js SDK入口 29 | - ~/src SDK源码 30 | - ~/example 微信和支付宝小程序demo 31 | 32 | ## Installation 33 | - npm 34 | 35 | ```shell 36 | npm install wx-ant-ble 37 | 38 | // 微信小程序请查看npm文档,支付宝小程序仅下载 39 | ``` 40 | 41 | - 直接下载或者使用git克隆 42 | 43 | ```shell 44 | git clone https://github.com/zhaodahai/wx-ant-ble.git 45 | ``` 46 | 47 | ## Usage 48 | #### 1、引入SDK管理类和枚举 49 | ```js 50 | // 引入SDK管理类 微信小程序通过npm构建后 51 | import { BTManager, ConnectStatus } from 'wx-ant-ble'; 52 | // 引入SDK管理类 支付宝小程序 53 | import { BTManager, ConnectStatus } from '../../wx-ant-ble/index.js'; 54 | ``` 55 | 56 | #### 2、初始化蓝牙管理器 & 设置用户信息 57 | ```js 58 | // 初始化蓝牙管理器 单例模式 59 | this.bt = new BTManager({ 60 | debug: true // 是否开启打印调试 61 | }); 62 | ``` 63 | 64 | #### 3、注册回调 65 | ```js 66 | // 注册状态更新回调 67 | this.bt.registerDidUpdateConnectStatus(res => { 68 | // 参数结构注意查看log 69 | console.log('registerDidUpdateConnectStatus', res); 70 | if (res.connectStatus === ConnectStatus.connected) { 71 | this.setData({ isConnected: true }); 72 | ... 73 | }else if (res.connectStatus === ConnectStatus.disconnected) { 74 | this.setData({ isConnected: false }); 75 | ... 76 | } 77 | }); 78 | ``` 79 | ```js 80 | // 注册发现外设回调,当扫描到设备时回调,或者达到超时时间回调。 81 | this.bt.registerDidDiscoverDevice(res => { 82 | // 参数结构注意查看log 83 | console.log('registerDidDiscoverDevice', res); 84 | ... 85 | }); 86 | ``` 87 | ```js 88 | // 注册特征值改变回调,当监听的特征值改变时回调,或者读特征值时回调。 89 | this.bt.registerDidUpdateValueForCharacteristic(res => { 90 | // 参数结构注意查看log 91 | console.log('registerDidUpdateValueForCharacteristic', res); 92 | ... 93 | }); 94 | ``` 95 | 96 | #### 4、扫描 & 停止扫描 97 | ```js 98 | // 开始扫描 99 | this.bt.scan({ 100 | services: [], // 主service的uuid列表 101 | allowDuplicatesKey: false, // 是否允许重复上报设备 102 | interval: 0, // 上报新设备的间隔,默认为0 103 | timeout: 15000, // 扫描超时时间,毫秒 104 | deviceName: '', // 需要匹配的设备名称 105 | containName: '' // 需要包含的设备名称 106 | }).then(res => { 107 | console.log('scan success', res); 108 | }).catch(e => { 109 | console.log('scan fail', e); 110 | }); 111 | ``` 112 | ```js 113 | // 停止扫描 114 | this.bt.stopScan() 115 | .then(res => { 116 | console.log('stopScan success', res); 117 | }).catch(e => { 118 | console.log('stopScan fail', e); 119 | }) 120 | ``` 121 | #### 5、连接 & 断开连接 122 | ```js 123 | // 连接设备 124 | this.bt.connect(device) // device应为registerDidDiscoverDevice注册的方法上报的设备对象 125 | .then(res => { 126 | console.log('connect success', res); 127 | }).catch(e => { 128 | console.log('connect fail', e); 129 | }); 130 | ``` 131 | ```js 132 | // 断开连接 133 | this.bt.disconnect() 134 | .then(res => { 135 | console.log('disconnect success', res); 136 | }).catch(e => { 137 | console.log('disconnect fail', e); 138 | }) 139 | ``` 140 | #### 5、Read & Notify & Write 141 | ```js 142 | // 读特征值,读到的值从registerDidUpdateValueForCharacteristic注册的方法上报 143 | this.bt.read({ 144 | suuid: 'xxxx', // 特征对应的服务uuid 145 | cuuid: 'xxxx' // 特征uuid 146 | }).then(res => { 147 | console.log('read success', res); 148 | }).catch(e => { 149 | console.log('read fail', e); 150 | }) 151 | ``` 152 | ```js 153 | // 向蓝牙模块写入数据 154 | this.bt.write({ 155 | suuid: 'xxxx', // 特征对应的服务uuid 156 | cuuid: 'xxxx', // 特征uuid 157 | value: 'FFFF' // 写入的数据 158 | }).then(res => { 159 | console.log('write success', res); 160 | }).catch(e => { 161 | console.log('write fail', e); 162 | }) 163 | ``` 164 | ```js 165 | // 监听/停止监听 特征值改变,改变特征值从registerDidUpdateValueForCharacteristic注册的方法上报 166 | this.bt.notify({ 167 | suuid: 'xxxx', // 特征对应的服务uuid 168 | cuuid: 'xxxx', // 特征uuid 169 | state: true/false // 是否监听 170 | }).then(res => { 171 | console.log('notify success', res); 172 | }).catch(e => { 173 | console.log('notify fail', e); 174 | }) 175 | ``` 176 | 177 | ## WxApp 178 | [仓库地址](https://github.com/zhaodahai/wxble) 179 |
180 | BLE蓝牙开发助手二维码BLE蓝牙开发助手演示 181 |
182 | 183 | 184 | ## Notice 185 | - 具体使用请查看[demo](example)和[Api文档](READAPI.md) 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | -------------------------------------------------------------------------------- /example/antdemo/.tea/entryFiles-development/config$.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaodahai/wx-ant-ble/c689614f401ca2c52a1f5f34a036be2f2c3088ec/example/antdemo/.tea/entryFiles-development/config$.js -------------------------------------------------------------------------------- /example/antdemo/.tea/entryFiles-development/importScripts$.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaodahai/wx-ant-ble/c689614f401ca2c52a1f5f34a036be2f2c3088ec/example/antdemo/.tea/entryFiles-development/importScripts$.js -------------------------------------------------------------------------------- /example/antdemo/.tea/entryFiles-development/index$.remote.worker.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaodahai/wx-ant-ble/c689614f401ca2c52a1f5f34a036be2f2c3088ec/example/antdemo/.tea/entryFiles-development/index$.remote.worker.js -------------------------------------------------------------------------------- /example/antdemo/.tea/entryFiles-development/index$.web.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaodahai/wx-ant-ble/c689614f401ca2c52a1f5f34a036be2f2c3088ec/example/antdemo/.tea/entryFiles-development/index$.web.js -------------------------------------------------------------------------------- /example/antdemo/.tea/entryFiles-development/index$.worker.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaodahai/wx-ant-ble/c689614f401ca2c52a1f5f34a036be2f2c3088ec/example/antdemo/.tea/entryFiles-development/index$.worker.js -------------------------------------------------------------------------------- /example/antdemo/app.acss: -------------------------------------------------------------------------------- 1 | /**app.wxss**/ 2 | page { 3 | flex: 1; 4 | display: flex; 5 | background: #f7f7f7; 6 | flex-direction: column; 7 | align-items: center; 8 | } 9 | 10 | .absTopLeft0 { 11 | position: absolute; 12 | top: 0rpx; 13 | left: 0rpx; 14 | } 15 | 16 | /* 居中显示 */ 17 | .flex-center{ 18 | display: flex; 19 | align-items: center; 20 | justify-content: center; 21 | } 22 | 23 | /* 水平居中显示 */ 24 | .flex-row-center{ 25 | display: flex; 26 | flex-direction: row; 27 | align-items: center; 28 | } 29 | 30 | /* 垂直居中显示 */ 31 | .flex-column-center{ 32 | display: flex; 33 | flex-direction: column; 34 | align-items: center; 35 | } 36 | 37 | /* 垂直靠左显示 */ 38 | .flex-column-left{ 39 | display: flex; 40 | flex-direction: column; 41 | align-items: flex-start; 42 | } 43 | 44 | /* 水平靠左显示 */ 45 | .flex-row-left{ 46 | display: flex; 47 | flex-direction: row; 48 | align-items: flex-start; 49 | justify-content: center 50 | } 51 | 52 | /* 垂直居下显示 */ 53 | .flex-column-end{ 54 | display: flex; 55 | flex-direction: column; 56 | justify-content: flex-end; 57 | } 58 | 59 | ::-webkit-scrollbar { 60 | width: 0; 61 | height: 0; 62 | color: transparent; 63 | } -------------------------------------------------------------------------------- /example/antdemo/app.js: -------------------------------------------------------------------------------- 1 | App({ 2 | onLaunch(options) { 3 | // 第一次打开 4 | // options.query == {number:1} 5 | console.info('App onLaunch'); 6 | }, 7 | onShow(options) { 8 | // 从后台被 scheme 重新打开 9 | // options.query == {number:1} 10 | }, 11 | }); 12 | -------------------------------------------------------------------------------- /example/antdemo/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages": [ 3 | "pages/home/home", 4 | "pages/index/index" 5 | 6 | ], 7 | "window": { 8 | "defaultTitle": "My App" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /example/antdemo/images/arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaodahai/wx-ant-ble/c689614f401ca2c52a1f5f34a036be2f2c3088ec/example/antdemo/images/arrow.png -------------------------------------------------------------------------------- /example/antdemo/pages/home/home.acss: -------------------------------------------------------------------------------- 1 | /* pages/home/home.wxss */ 2 | 3 | .page { 4 | width: 100%; 5 | height: 100%; 6 | background: #eee; 7 | } 8 | 9 | .btn-view { 10 | margin-top: 30rpx; 11 | height: 100rpx; 12 | width: 95%; 13 | justify-content: space-around; 14 | } 15 | 16 | .btn-view .btn { 17 | width: 25%; 18 | height: 80%; 19 | background: #1E90FF; 20 | color: white; 21 | border-radius: 10rpx; 22 | } 23 | 24 | .status-view { 25 | height: 50rpx; 26 | width: 100%; 27 | margin-left: 50rpx; 28 | } 29 | 30 | .title-view { 31 | margin-top: 30rpx; 32 | height: 50rpx; 33 | width: 100%; 34 | margin-left: 50rpx; 35 | font-size: 40rpx; 36 | font-weight: 400; 37 | line-height: 40rpx; 38 | } 39 | 40 | scroll-view { 41 | height: 240rpx; 42 | width: 100%; 43 | background: white; 44 | } 45 | 46 | .cell { 47 | height: 80rpx; 48 | width: 100%; 49 | justify-content: space-between; 50 | } 51 | 52 | .cell .name{ 53 | font-size: 40rpx; 54 | font-weight: 300; 55 | margin-left: 30rpx; 56 | } 57 | 58 | .cell image { 59 | margin-right: 20rpx; 60 | height: 30rpx; 61 | width: 30rpx; 62 | } 63 | 64 | .cell .uuid { 65 | font-size: 27rpx; 66 | margin-left: 30rpx; 67 | font-weight: 300; 68 | } 69 | 70 | .cell .btn { 71 | width: 100rpx; 72 | color: #1E90FF; 73 | font-size: 30rpx; 74 | } 75 | 76 | scroll-view .line { 77 | height: 2rpx; 78 | width: 100%; 79 | background:#e0e0e0; 80 | } -------------------------------------------------------------------------------- /example/antdemo/pages/home/home.axml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 扫描 6 | 停止扫描 7 | 断开蓝牙 8 | 9 | 10 | {{connected?("Connected "+device.name):"Disconnected"}} 11 | 12 | Peripherals 13 | 14 | 15 | 16 | {{item.name}} 17 | 18 | 19 | 20 | 21 | 22 | 23 | Notify Characteristic 24 | 25 | 26 | 27 | {{item.cuuid}} 28 | {{item.listening?"Stop":"Notify"}} 29 | 30 | 31 | 32 | 33 | 34 | Read Characteristic 35 | 36 | 37 | 38 | {{item.cuuid}} 39 | Read 40 | 41 | 42 | 43 | 44 | 45 | Write Characteristic 46 | 47 | 48 | 49 | {{item.cuuid}} 50 | Write 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /example/antdemo/pages/home/home.js: -------------------------------------------------------------------------------- 1 | 2 | import { BTManager , ConnectStatus } from '../../wx-ant-ble/index.js'; 3 | 4 | Page({ 5 | data: { 6 | // 蓝牙是否连接 7 | connected: false, 8 | // 成功连接的设备 9 | device: {}, 10 | // 扫描到的设备 11 | devices:[], 12 | // 设备能够notify的特征 13 | notifyUUIDs: [], 14 | // 设备能够read的特征 15 | readUUIDs: [], 16 | // 设备能够write的特征 17 | writeUUIDs: [], 18 | }, 19 | 20 | onLoad: function (options) { 21 | // 初始化蓝牙管理器 22 | this.bt = new BTManager({ 23 | debug: true 24 | }); 25 | // 注册状态回调 26 | this.bt.registerDidUpdateConnectStatus(this.didUpdateConnectStatus.bind(this)); 27 | // 注册发现外设回调 28 | this.bt.registerDidDiscoverDevice(this.didDiscoverDevice.bind(this)); 29 | // 注册特征值改变回调 30 | this.bt.registerDidUpdateValueForCharacteristic(this.didUpdateValueForCharacteristic.bind(this)); 31 | }, 32 | 33 | // 状态改变回调 34 | didUpdateConnectStatus(res) { 35 | console.log('home registerDidUpdateConnectStatus', res); 36 | if (res.connectStatus === ConnectStatus.connected) { 37 | my.hideLoading(); 38 | this.setData({connected: true , device:res.device}); 39 | this.parseDeviceUUIDs(res.device); 40 | } else if (res.connectStatus === ConnectStatus.disconnected) { 41 | my.hideLoading(); 42 | my.showToast({ 43 | content: res.message, 44 | type: 'none' 45 | }) 46 | this.setData({ connected: false, notifyUUIDs: [], readUUIDs: [], writeUUIDs:[]}); 47 | } 48 | }, 49 | 50 | // 发现外设回调 51 | didDiscoverDevice(res) { 52 | console.log('home didDiscoverDevice', res); 53 | if (res.timeout) { 54 | console.log('home didDiscoverDevice', '扫描超时'); 55 | my.showToast({ 56 | content: res.message, 57 | type: 'none' 58 | }) 59 | } else { 60 | let device = res.device; 61 | let devices = this.data.devices; 62 | function checkDevice(d, ds) { 63 | for (let v of ds) { 64 | if (v.deviceId === d.deviceId) { 65 | return true; 66 | } 67 | } 68 | return false; 69 | } 70 | if (!checkDevice(device, devices)) { 71 | devices.push(device); 72 | } 73 | this.setData({ devices }); 74 | } 75 | }, 76 | 77 | // 特征值改变回调 78 | didUpdateValueForCharacteristic(res) { 79 | console.log('home registerDidUpdateValueForCharacteristic', res); 80 | }, 81 | 82 | parseDeviceUUIDs(device) { 83 | let { notifyUUIDs, readUUIDs, writeUUIDs } = this.data; 84 | for (let service of device.services) { 85 | for (let char of service.characteristics) { 86 | if (char.properties.notify) { 87 | notifyUUIDs.push({ 88 | suuid: service.serviceId, 89 | cuuid: char.characteristicId, 90 | listening: false 91 | }) 92 | } 93 | if (char.properties.read) { 94 | readUUIDs.push({ 95 | suuid: service.serviceId, 96 | cuuid: char.characteristicId, 97 | }) 98 | } 99 | if (char.properties.write) { 100 | writeUUIDs.push({ 101 | suuid: service.serviceId, 102 | cuuid: char.characteristicId, 103 | }) 104 | } 105 | } 106 | } 107 | this.setData({ notifyUUIDs, readUUIDs, writeUUIDs }); 108 | }, 109 | 110 | // 扫描 111 | _scan() { 112 | this.bt.scan({ 113 | services: [], 114 | allowDuplicatesKey: false, 115 | interval: 0, 116 | timeout: 15000, 117 | deviceName: '', 118 | containName: 'BIANLA' 119 | }).then(res => { 120 | console.log('home scan success', res); 121 | }).catch(e => { 122 | console.log('home scan fail', e); 123 | my.showToast({ 124 | content: e.message, 125 | type: 'none' 126 | }) 127 | }); 128 | }, 129 | 130 | // 停止扫描 131 | _stopScan() { 132 | this.bt.stopScan().then(res => { 133 | console.log('home stopScan success', res); 134 | }).catch(e => { 135 | console.log('home stopScan fail', e); 136 | }) 137 | }, 138 | 139 | // 连接 140 | _connect(e) { 141 | let index = e.currentTarget.id; 142 | this.bt.stopScan(); 143 | let device = this.data.devices[index]; 144 | this.bt.connect(device).then(res => { 145 | console.log('home connect success', res); 146 | }).catch(e => { 147 | my.showToast({ 148 | content: e.message, 149 | type: 'none' 150 | }) 151 | console.log('home connect fail', e); 152 | }); 153 | my.showLoading({ 154 | content: '连接' + device.name, 155 | }); 156 | }, 157 | 158 | // 断开连接 159 | _disconnect() { 160 | this.bt.disconnect().then(res => { 161 | console.log('home disconnect success', res); 162 | }).catch(e => { 163 | console.log('home disconnect fail', res); 164 | }) 165 | }, 166 | 167 | // 监听/停止监听 168 | _notify(e) { 169 | let index = e.currentTarget.id; 170 | let { suuid, cuuid, listening } = this.data.notifyUUIDs[index]; 171 | this.bt.notify({ 172 | suuid, cuuid, state: !listening 173 | }).then(res => { 174 | console.log('home notify success', res); 175 | this.setData({ [`notifyUUIDs[${index}].listening`]: !listening }); 176 | }).catch(e => { 177 | console.log('home notify fail', e); 178 | }) 179 | }, 180 | 181 | // 读特征值 182 | _read(e) { 183 | let index = e.currentTarget.id; 184 | let { suuid, cuuid } = this.data.readUUIDs[index]; 185 | this.bt.read({ 186 | suuid, cuuid 187 | }).then(res => { 188 | console.log('home read success', res); 189 | }).catch(e => { 190 | console.log('home read fail', e); 191 | }) 192 | }, 193 | 194 | // 向蓝牙模块写入数据,这里只做简单的例子,发送的是 'FFFF' 的十六进制字符串 195 | _write(e) { 196 | let index = e.currentTarget.id; 197 | let { suuid, cuuid } = this.data.writeUUIDs[index]; 198 | this.bt.write({ 199 | suuid, 200 | cuuid, 201 | value: 'FFFF' 202 | }).then(res => { 203 | console.log('home write success', res); 204 | }).catch(e => { 205 | console.log('home write fail', e); 206 | }) 207 | }, 208 | }); 209 | -------------------------------------------------------------------------------- /example/antdemo/pages/home/home.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /example/antdemo/pages/index/index.axml: -------------------------------------------------------------------------------- 1 | 2 | this is a blank page 3 | 4 | -------------------------------------------------------------------------------- /example/antdemo/pages/index/index.js: -------------------------------------------------------------------------------- 1 | Page({ 2 | onLoad(query) { 3 | // 页面加载 4 | console.info(`Page onLoad with query: ${JSON.stringify(query)}`); 5 | }, 6 | onReady() { 7 | // 页面加载完成 8 | }, 9 | onShow() { 10 | // 页面显示 11 | }, 12 | onHide() { 13 | // 页面隐藏 14 | }, 15 | onUnload() { 16 | // 页面被关闭 17 | }, 18 | onTitleClick() { 19 | // 标题被点击 20 | }, 21 | onPullDownRefresh() { 22 | // 页面被下拉 23 | }, 24 | onReachBottom() { 25 | // 页面被拉到底部 26 | }, 27 | onShareAppMessage() { 28 | // 返回自定义分享信息 29 | return { 30 | title: 'My App', 31 | desc: 'My App description', 32 | path: 'pages/index/index', 33 | }; 34 | }, 35 | }); 36 | -------------------------------------------------------------------------------- /example/antdemo/pages/index/index.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /example/antdemo/snapshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaodahai/wx-ant-ble/c689614f401ca2c52a1f5f34a036be2f2c3088ec/example/antdemo/snapshot.png -------------------------------------------------------------------------------- /example/antdemo/wx-ant-ble/READAPI.md: -------------------------------------------------------------------------------- 1 | # wx-ant-ble 接口文档 2 | 3 | ### 目录 4 | - [初始化 `BTManager() `](#jump-init) 5 | - [扫描外设 `scan()`](#jump-scan) 6 | - [停止扫描 `stopScan()`](#jump-stopScan) 7 | - [连接外设 `connect()`](#jump-connect) 8 | - [断开连接 `disconnect()`](#jump-disconnect) 9 | - [读特征值 `read()`](#jump-read) 10 | - [写数据 `write()`](#jump-write) 11 | - [监听特征值 `notify()`](#jump-notify) 12 | - [注册状态更新回调 `registerDidUpdateConnectStatus()`](#jump-callback-connect-status) 13 | - [注册发现外设回调 `registerDidDiscoverDevice()`](#jump-callback-discover-device) 14 | - [注册特征值改变回调 `registerDidUpdateValueForCharacteristic()`](#jump-callback-value-update) 15 | 16 | 17 | ### 详细接口说明 18 | - #### 初始化 `BTManager(config)` 19 | > 参数:`config(Object)` 配置
20 | > 说明:初始化SDK管理实例,**单例模式**。 21 | > 22 | ```js 23 | this.bt = new BTManager({ 24 | debug: false 25 | }); 26 | ``` 27 | | 参数字段 | 类型 | 必填 | 默认 | 说明 | 28 | | :-- | :--: | :--: | :--: | :-- | 29 | | debug | Boolean | 否 | false | 是否开启打印调试 | 30 | 31 | 32 | - #### 扫描外设 `scan(options)` 33 | > 参数:`options(Object)` 扫描参数
34 | > 返回值:Promise对象,[调用成功](#jump-SuccessApiThen) | [调用失败](#jump-ErrorApiCatch)
35 | > 说明:开始扫描外设,注意实现返回对象的then和catch方法,监听接口是否调用成功。
36 | >     此操作比较耗费系统资源,请在搜索到设备后调用stopScan方法停止扫描。
37 | >     重复调用此接口,会清空之前设备存储,再次上报已上报的设备,能够起到刷新的作用。 38 | > 39 | ```js 40 | this.bt.scan({ 41 | services: [], 42 | allowDuplicatesKey: false, 43 | interval : 0, 44 | timeout: 15, 45 | deviceName: '', 46 | containName:'' 47 | }).then(res => { 48 | console.log('scan success', res); 49 | }).catch(e => { 50 | console.log('scan fail', e); 51 | }); 52 | ``` 53 | | 参数字段 | 类型 | 必填 | 默认 | 说明 | 54 | | :-- | :-- | :--: | :--: | :-- | 55 | | services | Array | 否 | [] |主service的uuid列表。确认在蓝牙广播中存在此服务id,可以通过服务id过滤掉其他设备 | 56 | | allowDuplicatesKey | Boolean | 否 | false | 是否允许重复上报设备 | 57 | | interval | Number | 否 | 0 | 上报新设备的间隔 | 58 | | timeout | Number | 否 | 15000 | 扫描超时时间,毫秒。在该时间内未扫描到符合要求的设备,上报超时。-1表示无限超时。[超时回调](#jump-callback-discover-device) | 59 | | deviceName | String | 否 | "" | 通过蓝牙名称过滤,需要匹配的设备名称 | 60 | | containName | String | 否 | "" | 通过蓝牙名称过滤,需要包含的设备名称 | 61 | 62 | 63 | - #### 停止扫描 `stopScan()` 64 | > 返回值:Promise对象,[调用成功](#jump-SuccessApiThen) | [调用失败](#jump-ErrorApiCatch)
65 | > 说明:停止扫描,取消超时延时。 66 | ```js 67 | this.bt.stopScan() 68 | .then(res => { 69 | console.log('stopScan success', res); 70 | }).catch(e => { 71 | console.log('stopScan fail', e); 72 | }) 73 | ``` 74 | 75 | - #### 连接外设 `connect(device , timeout)` 76 | > 参数:`device(Object)` 设备对象
77 | >     `timeout(Number)` 超时时间
78 | > 返回值:Promise对象,[调用成功](#jump-SuccessApiThen) | [调用失败](#jump-ErrorApiCatch)
79 | > 说明:连接指定的外设,需要传入外设对象。 80 | > 81 | ```js 82 | this.bt.connect(device) 83 | .then(res => { 84 | console.log('connect success', res); 85 | }).catch(e => { 86 | console.log('connect fail', e); 87 | }); 88 | ``` 89 | | 参数字段 | 类型 | 必填 | 默认 | 说明 | 90 | | :-- | :-- | :--: | :--: | :-- | 91 | | device | Object | 是 | -- | 指定连接的[外设对象](#jump-DeviceInfo),从[registerDidDiscoverDevice](#jump-callback-discover-device)注册的回调中得到 | 92 | | timeout | Number | 否 | 15000 | 连接超时时间,毫秒,支付宝小程序无效| 93 | 94 | 95 | - #### 断开连接 `disconnect()` 96 | > 返回值:Promise对象,[调用成功](#jump-SuccessApiThen) | [调用失败](#jump-ErrorApiCatch)
97 | > 98 | ```js 99 | this.bt.disconnect() 100 | .then(res => { 101 | console.log('disconnect success', res); 102 | }).catch(e => { 103 | console.log('disconnect fail', e); 104 | }) 105 | ``` 106 | 107 | - #### 读特征值 `read(params)` 108 | > 参数:`params(Object)` 参数
109 | > 返回值:Promise对象,[调用成功](#jump-SuccessApiThen) | [调用失败](#jump-ErrorApiCatch)
110 | > 说明:读某个特征值。 111 | > 112 | ```js 113 | this.bt.read({ 114 | suuid: 'xxxx', // 特征对应的服务uuid 115 | cuuid: 'xxxx' // 特征uuid 116 | }).then(res => { 117 | console.log('read success', res); 118 | }).catch(e => { 119 | console.log('read fail', e); 120 | }) 121 | ``` 122 | | 参数字段 | 类型 | 必填 | 说明 | 123 | | :--: | :--: | :--: | :--: | 124 | | suuid | String | 是 | 特征对应的服务uuid | 125 | | cuuid | String | 是 | 特征uuid | 126 | 127 | 128 | - #### 写数据 `write(params)` 129 | > 参数:`params(Object)` 参数
130 | > 返回值:Promise对象,[调用成功](#jump-SuccessApiThen) | [调用失败](#jump-ErrorApiCatch)
131 | > 说明:向蓝牙模块写入数据。 132 | > 133 | ```js 134 | this.bt.write({ 135 | suuid: 'xxxx', // 特征对应的服务uuid 136 | cuuid: 'xxxx', // 特征uuid 137 | value: 'FFFF' // 写入的数据 138 | }).then(res => { 139 | console.log('write success', res); 140 | }).catch(e => { 141 | console.log('write fail', e); 142 | }) 143 | ``` 144 | | 参数字段 | 类型 | 必填 | 说明 | 145 | | :--: | :--: | :--: | :--: | 146 | | suuid | String | 是 | 特征对应的服务uuid | 147 | | cuuid | String | 是 | 特征uuid | 148 | | value | String | 是 | 16进制字符串 | 149 | 150 | 151 | - #### 读特征值 `notify(params)` 152 | > 参数:`params(Object)` 参数
153 | > 返回值:Promise对象,[调用成功](#jump-SuccessApiThen) | [调用失败](#jump-ErrorApiCatch)
154 | > 说明:监听某个特征值变化。 155 | > 156 | ```js 157 | this.bt.notify({ 158 | suuid: 'xxxx', // 特征对应的服务uuid 159 | cuuid: 'xxxx', // 特征uuid 160 | state: true/false // 是否监听 161 | }).then(res => { 162 | console.log('notify success', res); 163 | }).catch(e => { 164 | console.log('notify fail', e); 165 | }) 166 | ``` 167 | | 参数字段 | 类型 | 必填 | 说明 | 168 | | :--: | :--: | :--: | :--: | 169 | | suuid | String | 是 | 特征对应的服务uuid | 170 | | cuuid | String | 是 | 特征uuid | 171 | | state | Boolean | 是 | 是否启用notify,可以通过重复调用接口改变此属性打开/关闭监听 | 172 | 173 | 174 | - #### 注册状态更新回调 `registerDidUpdateConnectStatus(callback)` 175 | > 参数:`callback(Function)` 回调函数
176 | > 说明:注册状态更新时的回调函数,当状态connectStatus发生变化时回调已注册的函数。 177 | > 178 | ```js 179 | this.bt.registerDidUpdateConnectStatus(res => { 180 | // 参数结构注意查看log 181 | console.log('registerDidUpdateConnectStatus', res); 182 | if (res.connectStatus === ConnectStatus.connected) { 183 | this.setData({ isConnected: true }); 184 | ... 185 | }else if (res.connectStatus === ConnectStatus.disconnected) { 186 | this.setData({ isConnected: false }); 187 | ... 188 | } 189 | }); 190 | ``` 191 | **返回示例** 192 | ```js 193 | { 194 | code: 222, 195 | connectStatus: 2, 196 | message: '连接成功', 197 | device: { 198 | RSSI: -70, // 蓝牙信号强度,越大越强 199 | name: 'XXXX', // 设备名称 200 | advertisData: 'XXXX', // 广播数据 201 | deviceId: 'XXXX', // 设备id 202 | ... 203 | } 204 | } 205 | ``` 206 | | 参数字段 | 类型 | 参考 | 必填 | 说明 | 207 | | :-- | :-- | :-- | :--: | :-- | 208 | | code | Number | [回调成功事件](#jump-SuccessCallbackEvent) [回调失败事件](#jump-ErrorCallbackEvent) | 是 | 连接状态码 | 209 | | connectStatus | ConnectStatus | [连接状态说明](#jump-ConnectStatus) | 是 | 连接状态 | 210 | | device | Object | [设备说明](#jump-DeviceInfo) | 否 | 设备信息,连接成功之后获取 | 211 | | message | String | [回调成功事件](#jump-SuccessCallbackEvent) [回调失败事件](#jump-ErrorCallbackEvent) | 是 | 状态描述 | 212 | 213 | 214 | - #### 注册发现外设回调 `registerDidDiscoverDevice(callback)` 215 | > 参数:`callback(Function)` 回调函数
216 | > 说明:当扫描到设备时回调,或者达到超时时间回调。 217 | > 218 | ```js 219 | this.bt.registerDidDiscoverDevice(res => { 220 | // 参数结构注意查看log 221 | console.log('registerDidDiscoverDevice', res); 222 | ... 223 | }); 224 | ``` 225 | **返回示例** 226 | ```js 227 | { 228 | code: 210, 229 | timeout: false, 230 | message: '发现外设', 231 | device: { 232 | RSSI: -70, // 蓝牙信号强度,越大越强 233 | name: 'XXXX', // 设备名称 234 | advertisData: 'XXXX', // 广播数据 235 | deviceId: 'XXXX', // 设备id 236 | ... 237 | } 238 | } 239 | ``` 240 | | 参数字段 | 类型 | 参考 | 必填 | 说明 | 241 | | :-- | :-- | :-- | :--: | :-- | 242 | | code | Number | [回调成功事件](#jump-SuccessCallbackEvent) [回调失败事件](#jump-ErrorCallbackEvent) | 是 | 扫描状态码 | 243 | | timeout | Boolean | -- | 是 | 扫描是否超时 | 244 | | device | Object | [设备说明](#jump-DeviceInfo) | 否 | 设备信息 | 245 | | message | String | [回调成功事件](#jump-SuccessCallbackEvent) [回调失败事件](#jump-ErrorCallbackEvent) | 是 | 状态描述 | 246 | 247 | 248 | - #### 注册状态更新回调 `registerDidUpdateValueForCharacteristic(callback)` 249 | > 参数:`callback(Function)` 回调函数
250 | > 说明:当监听的特征值改变时回调,或者读特征值时回调。 251 | > 252 | ```js 253 | this.bt.registerDidUpdateValueForCharacteristic(res => { 254 | // 参数结构注意查看log 255 | console.log('registerDidUpdateValueForCharacteristic', res); 256 | ... 257 | }); 258 | ``` 259 | **返回示例** 260 | ```js 261 | { 262 | characteristic: 'XXXX', 263 | serviceId: 'XXXX', 264 | deviceId: 'XXXX', 265 | value: 'FFFF' 266 | } 267 | ``` 268 | | 参数字段 | 类型 | 必填 | 说明 | 269 | | :-- | :--: | :--: | :-- | 270 | | characteristic | String | 是 | 特征uuid | 271 | | serviceId | String | 是 | 特征对应的服务uuid | 272 | | deviceId | String | 是 | 设备id | 273 | | value | String | 是 | 特征值 | 274 | 275 | 276 | ## 枚举类型说明 277 | 278 | - #### 设备信息 279 | | 属性字段 | 类型 | 必填 | 说明 | 280 | | :-- | :-- | :--: | :-- | 281 | | RSSI | Number | 是 | 设备信号强度 | 282 | | deviceId | String | 是 | 设备id | 283 | | name | String | 是 | 设备名称 | 284 | | localName | String | 否 | 设备名称 | 285 | | services | Array | 是 | 设备所有[服务](#jump-DeviceService) | 286 | | advertisData | String | 否 | 广播数据 | 287 | | advertisServiceUUIDs | Array | 否 | 广播中的服务UUID | 288 | 289 | 290 | - #### 服务 291 | | 属性字段 | 类型 | 必填 | 说明 | 292 | | :-- | :-- | :--: | :-- | 293 | | serviceId | String | 是 | 服务uuid | 294 | | characteristics | Array | 是 | 服务中的所有[特征](#jump-DeviceCharacteristic) | 295 | 296 | 297 | - #### 连接状态 `ConnectStatus` 298 | | 状态key | 状态值(Number)| 状态描述 | 299 | | :-- | :--: | :--: | 300 | | ConnectStatus.disconnected | 0 | 未连接或连接断开,允许连接 | 301 | | ConnectStatus.connecting | 1 | 正在连接,不允许再连接 | 302 | | ConnectStatus.connected | 2 | 已连接,不允许再连接 | 303 | 304 | 305 | - #### 特征 306 | | 属性字段 | 类型 | 必填 | 说明 | 307 | | :-- | :-- | :--: | :-- | 308 | | uuid | String | 是 | 特征uuid | 309 | | properties | Object | 是 | 特征的特性 indicate/notify/read/write | 310 | 311 | 312 | - #### 发现外设回调和连接状态改变回调成功事件 `SuccessCallbackEvent` 313 | | 事件码 | 事件key |事件描述 | 314 | | :--: | :-- | :-- | 315 | | 210 | SuccessCallbackEvent.Success_DiscoverDevice_CB_Discover | 发现外设 | 316 | | 211 | SuccessCallbackEvent.Success_DiscoverDevice_CB_ScanDone | 扫描完成 | 317 | | 220 | SuccessCallbackEvent.Success_ConnectStatus_CB_PowerOn | 蓝牙打开 | 318 | | 221 | SuccessCallbackEvent.Success_ConnectStatus_CB_Connecting| 正在连接 | 319 | | 222 | SuccessCallbackEvent.Success_ConnectStatus_CB_Connected | 连接成功 | 320 | | 223 | SuccessCallbackEvent.Success_ConnectStatus_CB_Stop | 断开成功 | 321 | 322 | 323 | - #### 发现外设回调和连接状态改变回调失败事件 `ErrorCallbackEvent` 324 | | 事件码 | 事件key |事件描述 | 325 | | :--: | :-- | :-- | 326 | | 410 | ErrorCallbackEvent.Error_DiscoverDevice_CB_Timeout | 扫描超时 | 327 | | 420 | ErrorCallbackEvent.Error_ConnectStatus_CB_PowerOff | 蓝牙关闭 | 328 | | 421 | ErrorCallbackEvent.Error_ConnectStatus_CB_ConnectFail | 连接失败 | 329 | | 422 | ErrorCallbackEvent.Error_ConnectStatus_CB_Disconnected | 连接断开 | 330 | 331 | 332 | - #### 接口调用成功事件`SuccessApiThen` 333 | | 事件码 | 事件key |事件描述 | 334 | | :--: | :-- | :-- | 335 | | 2010 | SuccessApiThen.Success_Scan | 扫描接口成功调用 | 336 | | 2020 | SuccessApiThen.Success_StopScan | 停止扫描接口成功调用 | 337 | | 2030 | SuccessApiThen.Success_Connect | 连接接口成功调用 | 338 | | 2040 | SuccessApiThen.Success_Disconnect | 断开接口成功调用 | 339 | | 2050 | SuccessApiThen.Success_Read | 读特征值接口成功调用 | 340 | | 2060 | SuccessApiThen.Success_Write | 写入数据接口成功调用 | 341 | | 2070 | SuccessApiThen.Success_Notify | 监听特征值接口成功调用 | 342 | 343 | 344 | - #### 接口调用失败事件`ErrorApiCatch` 345 | | 事件码 | 事件key |事件描述 | 346 | | :--: | :-- | :-- | 347 | | 4000 | ErrorApiCatch.Error_Low_Version | 当前基础库版本低,请更新微信版本 | 348 | | 4010 | ErrorApiCatch.Error_Scan_Failed | 扫描错误:扫描失败 | 349 | | 4011 | ErrorApiCatch.Error_Scan_PowerOff | 扫描错误:蓝牙被关闭 | 350 | | 4012 | ErrorApiCatch.Error_Scan_NoService | 扫描错误:没有找到指定服务 | 351 | | 4020 | ErrorApiCatch.Error_StopScan_Failed | 停止扫描错误:停止扫描失败 | 352 | | 4021 | ErrorApiCatch.Error_StopScan_PowerOff | 停止扫描错误:蓝牙被关闭 | 353 | | 4030 | ErrorApiCatch.Error_Connect_Failed | 连接错误:连接失败 | 354 | | 4031 | ErrorApiCatch.Error_Connect_PowerOff | 连接错误:蓝牙被关闭 | 355 | | 4032 | ErrorApiCatch.Error_Connect_AlreadyConnected | 连接错误:已经连接或正在连接 | 356 | | 4033 | ErrorApiCatch.Error_Connect_Timeout | 连接错误:连接超时 | 357 | | 4034 | ErrorApiCatch.Error_Connect_EmptyId | 连接错误:设备id不能为空 | 358 | | 4040 | ErrorApiCatch.Error_Disconnect_Failed | 断开错误:断开失败 | 359 | | 4050 | ErrorApiCatch.Error_Read_Failed | 读特征值错误:读特征值失败 | 360 | | 4051 | ErrorApiCatch.Error_Read_NotConnected | 读特征值错误:蓝牙未连接 | 361 | | 4052 | ErrorApiCatch.Error_Read_NotSupport | 读特征值错误:当前特征不支持读操作 | 362 | | 4053 | ErrorApiCatch.Error_Read_NoService | 读特征值错误:没有找到指定服务 | 363 | | 4054 | ErrorApiCatch.Error_Read_NoCharacteristic | 读特征值错误:没有找到指定特征值 | 364 | | 4060 | ErrorApiCatch.Error_Write_Failed | 写入数据错误:写入数据失败 | 365 | | 4061 | ErrorApiCatch.Error_Write_NotConnected | 写入数据错误:蓝牙未连接 | 366 | | 4062 | ErrorApiCatch.Error_Write_NotSupport | 写入数据错误:当前特征不支持写操作 | 367 | | 4063 | ErrorApiCatch.Error_Write_NoService | 写入数据错误:没有找到指定服务 | 368 | | 4064 | ErrorApiCatch.Error_Write_NoCharacteristic | 写入数据错误:没有找到指定特征值 | 369 | | 4070 | ErrorApiCatch.Error_Notify_Failed | 监听特征值错误:监听特征值错误失败 | 370 | | 4071 | ErrorApiCatch.Error_Notify_NotConnected | 监听特征值错误:蓝牙未连接 | 371 | | 4072 | ErrorApiCatch.Error_Notify_NotSupport | 监听特征值错误:当前特征不支持监听操作 | 372 | | 4073 | ErrorApiCatch.Error_Notify_NoService | 监听特征值错误:没有找到指定服务 | 373 | | 4074 | ErrorApiCatch.Error_Notify_NoCharacteristic | 监听特征值错误:没有找到指定特征值 | 374 | 375 | -------------------------------------------------------------------------------- /example/antdemo/wx-ant-ble/README.md: -------------------------------------------------------------------------------- 1 | # wx-ant-ble 2 | 3 | [![npm version](https://img.shields.io/npm/v/wx-ant-ble.svg?style=flat)](https://www.npmjs.com/package/wx-ant-ble) 4 | 5 | ### 微信、支付宝小程序BLE蓝牙SDK 6 | 7 | ### [详细接口说明](READAPI.md) 8 | 9 | ## Features 10 | - 兼容微信和支付宝小程序 11 | - 简洁但功能完整的Api,可以根据需求自由调用 12 | - 接口均有返回状态,判断是否调用成功 13 | - 单例模式 14 | 15 | ## Directory 16 | - ~/index.js SDK入口 17 | - ~/src SDK源码 18 | - ~/example 微信和支付宝小程序demo 19 | 20 | ## Installation 21 | - npm 22 | 23 | ```shell 24 | npm install wx-ant-ble 25 | 26 | // 微信小程序请查看npm文档,支付宝小程序仅下载 27 | ``` 28 | 29 | - 直接下载或者使用git克隆 30 | 31 | ```shell 32 | git clone https://github.com/zhaodahai/wx-ant-ble.git 33 | ``` 34 | 35 | ## Usage 36 | #### 1、引入SDK管理类和枚举 37 | ```js 38 | // 引入SDK管理类 微信小程序通过npm构建后 39 | import { BTManager, ConnectStatus } from 'wx-ant-ble'; 40 | // 引入SDK管理类 支付宝小程序 41 | import { BTManager, ConnectStatus } from '../../wx-ant-ble/index.js'; 42 | ``` 43 | 44 | #### 2、初始化蓝牙管理器 & 设置用户信息 45 | ```js 46 | // 初始化蓝牙管理器 单例模式 47 | this.bt = new BTManager({ 48 | debug: true // 是否开启打印调试 49 | }); 50 | ``` 51 | 52 | #### 3、注册回调 53 | ```js 54 | // 注册状态更新回调 55 | this.bt.registerDidUpdateConnectStatus(res => { 56 | // 参数结构注意查看log 57 | console.log('registerDidUpdateConnectStatus', res); 58 | if (res.connectStatus === ConnectStatus.connected) { 59 | this.setData({ isConnected: true }); 60 | ... 61 | }else if (res.connectStatus === ConnectStatus.disconnected) { 62 | this.setData({ isConnected: false }); 63 | ... 64 | } 65 | }); 66 | ``` 67 | ```js 68 | // 注册发现外设回调,当扫描到设备时回调,或者达到超时时间回调。 69 | this.bt.registerDidDiscoverDevice(res => { 70 | // 参数结构注意查看log 71 | console.log('registerDidDiscoverDevice', res); 72 | ... 73 | }); 74 | ``` 75 | ```js 76 | // 注册特征值改变回调,当监听的特征值改变时回调,或者读特征值时回调。 77 | this.bt.registerDidUpdateValueForCharacteristic(res => { 78 | // 参数结构注意查看log 79 | console.log('registerDidUpdateValueForCharacteristic', res); 80 | ... 81 | }); 82 | ``` 83 | 84 | #### 4、扫描 & 停止扫描 85 | ```js 86 | // 开始扫描 87 | this.bt.scan({ 88 | services: [], // 主service的uuid列表 89 | allowDuplicatesKey: false, // 是否允许重复上报设备 90 | interval: 0, // 上报新设备的间隔,默认为0 91 | timeout: 15000, // 扫描超时时间,毫秒 92 | deviceName: '', // 需要匹配的设备名称 93 | containName: '' // 需要包含的设备名称 94 | }).then(res => { 95 | console.log('scan success', res); 96 | }).catch(e => { 97 | console.log('scan fail', e); 98 | }); 99 | ``` 100 | ```js 101 | // 停止扫描 102 | this.bt.stopScan() 103 | .then(res => { 104 | console.log('stopScan success', res); 105 | }).catch(e => { 106 | console.log('stopScan fail', e); 107 | }) 108 | ``` 109 | #### 5、连接 & 断开连接 110 | ```js 111 | // 连接设备 112 | this.bt.connect(device) // device应为registerDidDiscoverDevice注册的方法上报的设备对象 113 | .then(res => { 114 | console.log('connect success', res); 115 | }).catch(e => { 116 | console.log('connect fail', e); 117 | }); 118 | ``` 119 | ```js 120 | // 断开连接 121 | this.bt.disconnect() 122 | .then(res => { 123 | console.log('disconnect success', res); 124 | }).catch(e => { 125 | console.log('disconnect fail', e); 126 | }) 127 | ``` 128 | #### 5、Read & Notify & Write 129 | ```js 130 | // 读特征值,读到的值从registerDidUpdateValueForCharacteristic注册的方法上报 131 | this.bt.read({ 132 | suuid: 'xxxx', // 特征对应的服务uuid 133 | cuuid: 'xxxx' // 特征uuid 134 | }).then(res => { 135 | console.log('read success', res); 136 | }).catch(e => { 137 | console.log('read fail', e); 138 | }) 139 | ``` 140 | ```js 141 | // 向蓝牙模块写入数据 142 | this.bt.write({ 143 | suuid: 'xxxx', // 特征对应的服务uuid 144 | cuuid: 'xxxx', // 特征uuid 145 | value: 'FFFF' // 写入的数据 146 | }).then(res => { 147 | console.log('write success', res); 148 | }).catch(e => { 149 | console.log('write fail', e); 150 | }) 151 | ``` 152 | ```js 153 | // 监听/停止监听 特征值改变,改变特征值从registerDidUpdateValueForCharacteristic注册的方法上报 154 | this.bt.notify({ 155 | suuid: 'xxxx', // 特征对应的服务uuid 156 | cuuid: 'xxxx', // 特征uuid 157 | state: true/false // 是否监听 158 | }).then(res => { 159 | console.log('notify success', res); 160 | }).catch(e => { 161 | console.log('notify fail', e); 162 | }) 163 | ``` 164 | 165 | ## WxApp 166 | 167 |
168 | BLE蓝牙开发助手二维码BLE蓝牙开发助手演示 169 |
170 | 171 | 172 | ## ReleaseNotes 173 | 174 | ``` 175 | v1.1.0 176 | 2019/01/15 177 | 发布第一个可用版本。封装蓝牙接口,兼容微信和支付宝小程序。附有说明和demo。 178 | ``` 179 | 180 | ## Notice 181 | - 具体使用请查看[demo](example)和[Api文档](READAPI.md) 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | -------------------------------------------------------------------------------- /example/antdemo/wx-ant-ble/index.js: -------------------------------------------------------------------------------- 1 | 2 | import { BTManager } from './src/btmanager.js'; 3 | import { ConnectStatus, SuccessCallbackEvent, ErrorCallbackEvent, SuccessApiThen, ErrorApiCatch } from './src/enum.js'; 4 | const Version = '1.1.0'; 5 | 6 | export { 7 | Version, 8 | BTManager, 9 | ConnectStatus, 10 | SuccessCallbackEvent, 11 | ErrorCallbackEvent, 12 | SuccessApiThen, 13 | ErrorApiCatch 14 | } -------------------------------------------------------------------------------- /example/antdemo/wx-ant-ble/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "_from": "wx-ant-ble", 3 | "_id": "wx-ant-ble@1.1.0", 4 | "_inBundle": false, 5 | "_integrity": "sha512-+wriBP/UwGTn5gwwsNcDh/0AmT1rUwFpQFHsxMyeZPFFlsTPRlrgsExbGsY1mBLms0+/Q9tssc1OB7KX4KvxIw==", 6 | "_location": "/wx-ant-ble", 7 | "_phantomChildren": {}, 8 | "_requested": { 9 | "type": "tag", 10 | "registry": true, 11 | "raw": "wx-ant-ble", 12 | "name": "wx-ant-ble", 13 | "escapedName": "wx-ant-ble", 14 | "rawSpec": "", 15 | "saveSpec": null, 16 | "fetchSpec": "latest" 17 | }, 18 | "_requiredBy": [ 19 | "#USER", 20 | "/" 21 | ], 22 | "_resolved": "https://registry.npmjs.org/wx-ant-ble/-/wx-ant-ble-1.1.0.tgz", 23 | "_shasum": "f829913b988b48b2ec0dc6b6156bcbd3bcf71a96", 24 | "_spec": "wx-ant-ble", 25 | "_where": "/Users/bianla/Desktop/iGit/dahai/BLEHelper/test", 26 | "author": { 27 | "name": "zhaodahai" 28 | }, 29 | "bugs": { 30 | "url": "https://github.com/zhaodahai/wx-ant-ble/issues" 31 | }, 32 | "bundleDependencies": false, 33 | "deprecated": false, 34 | "description": "微信、支付宝小程序BLE蓝牙SDK", 35 | "homepage": "https://github.com/zhaodahai/wx-ant-ble#readme", 36 | "keywords": [ 37 | "ble", 38 | "bluetooth", 39 | "蓝牙", 40 | "小程序" 41 | ], 42 | "license": "ISC", 43 | "main": "index.js", 44 | "name": "wx-ant-ble", 45 | "repository": { 46 | "type": "git", 47 | "url": "git+https://github.com/zhaodahai/wx-ant-ble.git" 48 | }, 49 | "scripts": { 50 | "test": "echo \"Error: no test specified\" && exit 1" 51 | }, 52 | "version": "1.1.0" 53 | } 54 | -------------------------------------------------------------------------------- /example/antdemo/wx-ant-ble/src/bluetooth.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | import { ConnectStatus, SuccessCallbackEvent, ErrorCallbackEvent, SuccessApiThen, ErrorApiCatch} from './enum.js'; 4 | import _ from './tools.js'; 5 | 6 | export default class Bluetooth { 7 | 8 | constructor(btmanager) { 9 | this.bm = btmanager; 10 | // 蓝牙适配器是否初始化完成 11 | this.isInitializedAdapter = false; 12 | // 蓝牙适配器是否可用 13 | this.isAvailableAdapter = false; 14 | // 初始化蓝牙适配器 15 | this.openAndListenBluetoothAdapter().then(__=>__).catch(__=>__); 16 | // 扫描到的设备 17 | this.scanDevices = []; 18 | } 19 | 20 | /** 21 | * 打开和监听蓝牙适配器 22 | * 23 | * @return Promise对象 24 | */ 25 | openAndListenBluetoothAdapter() { 26 | 27 | // 未初始化蓝牙适配器,打开蓝牙适配器 28 | if (!this.isInitializedAdapter) { 29 | 30 | // 监听蓝牙适配器状态 31 | _.api('offBluetoothAdapterStateChange').then(__ => __).catch(__ => __); 32 | _.on('onBluetoothAdapterStateChange','' ,res => { 33 | this.bm.log('onBluetoothAdapterStateChange', res); 34 | if (res.available && !this.isAvailableAdapter) { 35 | this.isAvailableAdapter = true; 36 | this.callBackConnectStatus(SuccessCallbackEvent.Success_ConnectStatus_CB_PowerOn); 37 | } else if (!res.available) { 38 | this.isAvailableAdapter = false; 39 | // 支付宝小程序当蓝牙适配器关闭,再次进行蓝牙操作需要重新打开,微信只需要打开一次就行 40 | _.getAppPlatform() === 'ant' && (this.isInitializedAdapter = false); 41 | this.bm.connectStatus = ConnectStatus.disconnected; 42 | this.callBackConnectStatus(ErrorCallbackEvent.Error_ConnectStatus_CB_PowerOff); 43 | } 44 | }); 45 | 46 | // 先关闭再打开蓝牙适配器,避免出现某些机型打开无效的情况 47 | return _.api('closeBluetoothAdapter') 48 | .then(__ => { 49 | // 打开蓝牙适配器 50 | return _.api('openBluetoothAdapter') 51 | }).then(res => { 52 | this.bm.log('openBluetoothAdapter success', res); 53 | this.isInitializedAdapter = true; 54 | this.isAvailableAdapter = true; 55 | return Promise.resolve(); 56 | }).catch(e => { 57 | this.bm.log('openBluetoothAdapter fail', e); 58 | this.isInitializedAdapter = false; 59 | this.isAvailableAdapter = false; 60 | this.bm.connectStatus = ConnectStatus.disconnected; 61 | return Promise.reject(e); 62 | }) 63 | } else { 64 | return Promise.resolve(); 65 | } 66 | } 67 | 68 | /** 69 | * 扫描外设 70 | * 71 | * @param {object} options 扫描参数 72 | * @property {array} services 主service的uuid列表。确认在蓝牙广播中存在此服务id,可以通过服务id过滤掉其他设备 73 | * @property {boolean} allowDuplicatesKey 是否允许重复上报设备 74 | * @property {number} interval 上报新设备的间隔,默认为0 75 | * @property {number} timeout 扫描超时时间,毫秒。在该时间内未扫描到符合要求的设备,上报超时。默认1500ms,-1表示无限超时 76 | * @property {string} deviceName 通过蓝牙名称过滤,需要匹配的设备名称 77 | * @property {string} containName 通过蓝牙名称过滤,需要包含的设备名称 78 | * 79 | * @return Promise对象 80 | * 81 | * @discussion 开始扫描外设,注意实现返回对象的then和catch方法,监听接口是否调用成功。 82 | * 此操作比较耗费系统资源,请在搜索到设备后调用stopScan方法停止扫描。 83 | * 重复调用此接口,会清空之前设备存储,再次上报已上报的设备,能够起到刷新的作用。 84 | * 85 | * @see registerDidDiscoverDevice 86 | */ 87 | scanDevice(options) { 88 | 89 | // 解构参数 90 | let { services, allowDuplicatesKey, interval, timeout, deviceName, containName} = options; 91 | 92 | // 打开和监听蓝牙适配器 93 | return this.openAndListenBluetoothAdapter() 94 | .then(__ => { 95 | // 清空存储的设备 96 | this.scanDevices = []; 97 | // 销毁扫描延时 98 | this.destoryTimer(); 99 | // 设置扫描超时 100 | this.scanTimeoutTimer = timeout!==-1 ? setTimeout(() => { 101 | this.stopScan(); 102 | if (this.scanDevices.length === 0) { // 扫描超时 103 | this.bm.log('startBluetoothDevicesDiscovery fail ' , 'timeout'); 104 | this.callBackDiscoverDevice(null, ErrorCallbackEvent.Error_DiscoverDevice_CB_Timeout, true); 105 | } else {// 扫描时间结束 106 | this.callBackDiscoverDevice(null, SuccessCallbackEvent.Success_DiscoverDevice_CB_ScanDone, false); 107 | } 108 | }, timeout || 15000) : null; 109 | // 开始扫描 110 | return _.api('startBluetoothDevicesDiscovery', '' ,{ 111 | services, 112 | allowDuplicatesKey, 113 | interval 114 | }) 115 | }).then(res => { 116 | this.bm.log('startBluetoothDevicesDiscovery success', res); 117 | // 取消设备监听,仅支付宝小程序有效 118 | _.api('offBluetoothDeviceFound').then(__ => __).catch(__ => __); 119 | // 监听扫描到外设 120 | _.on('onBluetoothDeviceFound','', res => { 121 | // this.bm.log('onBluetoothDeviceFound' , res); 122 | let devices = res.devices || res; 123 | // 过滤、格式化、存储、上报设备 124 | for (let device of devices) { 125 | if (Array.isArray(device)) device = devices[0][0]; 126 | // 信号强度为127表示RSSI不可用 127 | if (device.RSSI === 127) return; 128 | // 匹配名称,过滤设备 129 | let name = device.name || device.deviceName; 130 | device.name = name; 131 | if (deviceName && (!name || name !== deviceName)) return; 132 | if (containName && (!name || !~name.indexOf(containName))) return; 133 | // 格式化广播数据 134 | if (typeof device.advertisData !== 'string') device.advertisData = _.ab2str(device.advertisData); 135 | // 上报设备 136 | this.callBackDiscoverDevice(device, SuccessCallbackEvent.Success_DiscoverDevice_CB_Discover, false); 137 | // 更新不重复记录设备 138 | for (let v of this.scanDevices) { 139 | if (v.deviceId === device.deviceId) { 140 | Object.assign(v, device); 141 | return; 142 | } 143 | } 144 | // 存储新设备 145 | this.scanDevices.push(device); 146 | } 147 | }) 148 | return Promise.resolve(SuccessApiThen.Success_Scan); 149 | }).catch(e => { 150 | this.bm.log('startBluetoothDevicesDiscovery fail', e); 151 | if (e.code === 12 || e.code === 10001) { 152 | return Promise.reject(ErrorApiCatch.Error_Scan_PowerOff); 153 | } else if (e.code === 10012 || e.code === 10004) { 154 | return Promise.reject(ErrorApiCatch.Error_Scan_NoService); 155 | } else { 156 | return Promise.reject(ErrorApiCatch.Error_Scan_Failed); 157 | } 158 | }) 159 | } 160 | 161 | /** 162 | * 停止扫描 163 | * 164 | * @return Promise对象 165 | * 166 | * @discussion 停止扫描,取消超时延时。 167 | */ 168 | stopScan() { 169 | // 销毁扫描延时 170 | this.destoryTimer(); 171 | // 取消设备监听,仅支付宝小程序有效 172 | _.api('offBluetoothDeviceFound').then(__ => __).catch(__ => __); 173 | // 停止扫描 174 | return _.api('stopBluetoothDevicesDiscovery') 175 | .then(res => { 176 | this.bm.log('stopBluetoothDevicesDiscovery success', res); 177 | return Promise.resolve(SuccessApiThen.Success_StopScan); 178 | }).catch(e => { 179 | this.bm.log('stopBluetoothDevicesDiscovery fail', e); 180 | if (e.code === 12 || e.code === 10001) { 181 | return Promise.reject(ErrorApiCatch.Error_StopScan_PowerOff); 182 | } else { 183 | return Promise.reject(ErrorApiCatch.Error_StopScan_Failed); 184 | } 185 | }) 186 | } 187 | 188 | /** 189 | * 连接外设 190 | * 191 | * @param {object} device 指定连接的外设对象,从registerDidDiscoverDevice注册的回调中得到 192 | * @param {number} timeout 连接超时时间,毫秒,默认15000ms,支付宝小程序无效 193 | * 194 | * @return Promise对象 195 | * 196 | * @discussion 连接指定的外设,需要传入外设对象。 197 | * 注意实现返回对象的then和catch方法,监听接口是否调用成功。 198 | */ 199 | connect(device , timeout) { 200 | // 判断是否已经连接 201 | if (this.bm.connectStatus !== ConnectStatus.disconnected) { 202 | this.bm.logwarn('connect fail', 'Is already connected'); 203 | return Promise.reject(ErrorApiCatch.Error_Connect_AlreadyConnected); 204 | } 205 | // 判断设备id是否为空 206 | if (_.isEmpty(device.deviceId)) { 207 | return Promise.reject(ErrorApiCatch.Error_Connect_EmptyId); 208 | } 209 | 210 | this.bm.deviceInfo = device; 211 | this.bm.connectStatus = ConnectStatus.connecting; 212 | this.callBackConnectStatus(SuccessCallbackEvent.Success_ConnectStatus_CB_Connecting); 213 | let deviceId = device.deviceId; 214 | 215 | // 打开和监听蓝牙适配器 216 | return this.openAndListenBluetoothAdapter() 217 | .then(__ => { 218 | // 连接设备 219 | return _.api('createBLEConnection', 'connectBLEDevice', { 220 | deviceId , 221 | timeout: timeout || 15000 , 222 | }) 223 | }).then( res => { 224 | this.bm.log('connectBLEDevice success', res); 225 | // 取消蓝牙连接状态监听,仅支付宝小程序有效 226 | _.api('offBLEConnectionStateChanged').then(__ => __).catch(__ => __); 227 | // 蓝牙连接状态监听 228 | _.on('onBLEConnectionStateChange', 'onBLEConnectionStateChanged',(res) => { 229 | this.bm.log('onBLEConnectionStateChange', res); 230 | if (!res.connected && this.bm.connectStatus !== ConnectStatus.disconnected) { 231 | this.bm.connectStatus = ConnectStatus.disconnected; 232 | if (res.errorCode === 0) { 233 | this.callBackConnectStatus(SuccessCallbackEvent.Success_ConnectStatus_CB_Stop); 234 | } else if (res.errorCode === 10003) { 235 | this.callBackConnectStatus(ErrorCallbackEvent.Error_ConnectStatus_CB_Disconnected); 236 | } else if (_.getAppPlatform() === 'ant'){ 237 | this.callBackConnectStatus(ErrorCallbackEvent.Error_ConnectStatus_CB_Disconnected); 238 | } 239 | } 240 | }) 241 | 242 | // 获取设备所有服务 243 | _.api('getBLEDeviceServices', '', { deviceId }) 244 | .then(res => { 245 | this.bm.log('getBLEDeviceServices success', res); 246 | // 存储所有服务promise 247 | let sPromises = []; 248 | // 获取所有服务的所有特征 249 | device.services = res.services.map(server => { 250 | let sUUID = server.uuid || server.serviceId; 251 | let sPromise = _.api('getBLEDeviceCharacteristics', '', { 252 | deviceId, 253 | serviceId: sUUID 254 | }) 255 | sPromises.push(sPromise); 256 | return { serviceId: sUUID }; 257 | }) 258 | return Promise.all(sPromises); 259 | }).then(res => { 260 | this.bm.log('getBLEDeviceCharacteristics success', res); 261 | device.services = res.map((v, i) => { 262 | let service = device.services[i]; 263 | service.characteristics = v.characteristics; 264 | return service; 265 | }) 266 | // 获取特征成功之后才算连接成功 267 | this.bm.deviceInfo = device; 268 | this.bm.connectStatus = ConnectStatus.connected; 269 | this.callBackConnectStatus(SuccessCallbackEvent.Success_ConnectStatus_CB_Connected); 270 | }).catch(e => { 271 | this.bm.log('api connecting error', e); 272 | // 出现错误断开蓝牙,避免出现已连接成功未找到服务或者特征出错时,再次连接状态不正确 273 | _.api('closeBLEConnection', 'disconnectBLEDevice', { 274 | deviceId: this.bm.deviceInfo.deviceId 275 | }).then(__ => __).catch(__ => __); 276 | this.bm.connectStatus = ConnectStatus.disconnected; 277 | this.callBackConnectStatus(e); 278 | }) 279 | 280 | // 开始连接接口调用成功 281 | return Promise.resolve(SuccessApiThen.Success_Connect); 282 | }).catch(e => { 283 | this.bm.log('api connect error', e); 284 | // 未知错误,直接报连接失败 285 | if (e.code === 100000) e = ErrorCallbackEvent.Error_ConnectStatus_CB_ConnectFail; 286 | this.bm.connectStatus = ConnectStatus.disconnected; 287 | this.callBackConnectStatus(e); 288 | if (e.code === 12 || e.code === 10001) { 289 | return Promise.reject(ErrorApiCatch.Error_Connect_PowerOff); 290 | } else if (~e.message.indexOf('超时') || (e.errMsg && ~e.errMsg.indexOf('time out'))){ 291 | return Promise.reject(ErrorApiCatch.Error_Connect_Timeout); 292 | } else { 293 | return Promise.reject(ErrorApiCatch.Error_Connect_Failed); 294 | } 295 | }) 296 | } 297 | 298 | /** 299 | * 断开连接 300 | * 301 | * @return Promise对象 302 | */ 303 | disconnect() { 304 | 305 | this.bm.connectStatus === ConnectStatus.connected && 306 | _.api('closeBLEConnection','disconnectBLEDevice',{ 307 | deviceId: this.bm.deviceInfo.deviceId 308 | }).then(res => { 309 | this.bm.log('closeBLEConnection success', res); 310 | }).catch(e => { 311 | this.bm.log('closeBLEConnection fail', e); 312 | }) 313 | 314 | _.api('closeBluetoothAdapter') 315 | .then(res => { 316 | this.bm.log('closeBluetoothAdapter success', res); 317 | this.isInitializedAdapter = false; 318 | if (this.bm.connectStatus !== ConnectStatus.disconnected) { 319 | this.bm.connectStatus = ConnectStatus.disconnected; 320 | this.callBackConnectStatus(SuccessCallbackEvent.Success_ConnectStatus_CB_Stop); 321 | }; 322 | this.bm.connectStatus = ConnectStatus.disconnected; 323 | }).catch(e => { 324 | this.bm.log('closeBluetoothAdapter fail', e); 325 | }) 326 | 327 | return Promise.resolve(SuccessApiThen.Success_Disconnect); 328 | } 329 | 330 | /** 331 | * 读特征值 332 | * 333 | * @param {object} params 参数 334 | * @property {string} suuid 特征对应的服务uuid 335 | * @property {string} cuuid 写入特征uuid 336 | * 337 | * @discussion 读某个服务下的某个特征值。 338 | */ 339 | read(params) { 340 | if (this.bm.connectStatus === ConnectStatus.connected) { 341 | let { suuid, cuuid } = params; 342 | return _.api('readBLECharacteristicValue', '', { 343 | deviceId: this.bm.deviceInfo.deviceId, 344 | serviceId: suuid, 345 | characteristicId: cuuid, 346 | }).then(res => { 347 | this.bm.log('readBLECharacteristicValue success', res); 348 | return Promise.resolve(SuccessApiThen.Success_Read); 349 | }).catch(e => { 350 | this.bm.log('readBLECharacteristicValue fail', e); 351 | if (e.code === 10007) { 352 | return Promise.reject(ErrorApiCatch.Error_Read_NotSupport); 353 | } else if (e.code === 10004) { 354 | return Promise.reject(ErrorApiCatch.Error_Read_NoService); 355 | } else if (e.code === 10005) { 356 | return Promise.reject(ErrorApiCatch.Error_Read_NoCharacteristic); 357 | } else { 358 | return Promise.reject(ErrorApiCatch.Error_Read_Failed); 359 | } 360 | }) 361 | } else { 362 | return Promise.reject(ErrorApiCatch.Error_Read_NotConnected); 363 | } 364 | } 365 | 366 | /** 367 | * 向蓝牙模块写入数据 368 | * 369 | * @param {object} params 参数 370 | * @property {string} suuid 特征对应的服务uuid 371 | * @property {string} cuuid 写入特征uuid 372 | * @property {Hex string} value 16进制字符串 373 | * 374 | * @return Promise对象 375 | * 376 | * @discussion 向蓝牙模块写入数据。 377 | */ 378 | write(params) { 379 | let {suuid , cuuid , value} = params; 380 | if (this.bm.connectStatus === ConnectStatus.connected) { 381 | if (_.getAppPlatform() === 'wx') { 382 | if (typeof value === 'string') { 383 | value = _.str2ab(value); 384 | } else { 385 | value = typedArrayToArrayBuffer(value); 386 | } 387 | } else if (typeof value !== 'string'){ 388 | value = _.ab2str(value); 389 | } 390 | this.bm.log('writeCmdToDevice', _.ab2str(value)); 391 | return _.api('writeBLECharacteristicValue', '', { 392 | deviceId: this.bm.deviceInfo.deviceId, 393 | serviceId: suuid, 394 | characteristicId: cuuid, 395 | value, 396 | }).then(res => { 397 | this.bm.log('writeBLECharacteristicValue success', res); 398 | return Promise.resolve(SuccessApiThen.Success_Write); 399 | }).catch(e => { 400 | this.bm.log('writeBLECharacteristicValue fail', e); 401 | if (e.code === 10007) { 402 | return Promise.reject(ErrorApiCatch.Error_Write_NotSupport); 403 | } else if (e.code === 10004) { 404 | return Promise.reject(ErrorApiCatch.Error_Write_NoService); 405 | } else if (e.code === 10005) { 406 | return Promise.reject(ErrorApiCatch.Error_Write_NoCharacteristic); 407 | } else { 408 | return Promise.reject(ErrorApiCatch.Error_Write_Failed); 409 | } 410 | }) 411 | } else { 412 | return Promise.reject(ErrorApiCatch.Error_Write_NotConnected); 413 | } 414 | } 415 | 416 | /** 417 | * 监听特征值改变 418 | * 419 | * @param {object} params 参数 420 | * @property {string} suuid 特征对应的服务uuid 421 | * @property {string} cuuid 写入特征uuid 422 | * @property {boolean} state 是否启用notify,可以通过重复调用接口改变此属性打开/关闭监听 423 | * 424 | * @return Promise对象 425 | * 426 | * @discussion 监听某个特征值变化。 427 | */ 428 | notify(params) { 429 | if (this.bm.connectStatus === ConnectStatus.connected) { 430 | let { suuid, cuuid, state } = params; 431 | return _.api('notifyBLECharacteristicValueChange', '', { 432 | deviceId: this.bm.deviceInfo.deviceId, 433 | serviceId: suuid, 434 | characteristicId: cuuid, 435 | state 436 | }).then(res => { 437 | this.bm.log('readBLECharacteristicValue success', res); 438 | return Promise.resolve(SuccessApiThen.Success_Notify); 439 | }).catch(e => { 440 | this.bm.log('readBLECharacteristicValue fail', e); 441 | if (e.code === 10007) { 442 | return Promise.reject(ErrorApiCatch.Error_Notify_NotSupport); 443 | } else if (e.code === 10004) { 444 | return Promise.reject(ErrorApiCatch.Error_Notify_NoService); 445 | } else if (e.code === 10005) { 446 | return Promise.reject(ErrorApiCatch.Error_Notify_NoCharacteristic); 447 | } else { 448 | return Promise.reject(ErrorApiCatch.Error_Notify_Failed); 449 | } 450 | }) 451 | } else { 452 | return Promise.reject(ErrorApiCatch.Error_Notify_NotConnected); 453 | } 454 | } 455 | 456 | /** 457 | * 注册状态改变回调 458 | * 459 | * @param {function} cb 回调函数 460 | * 461 | * @discussion 连接状态发生改变时,回调此方法。 462 | */ 463 | registerDidUpdateConnectStatus(cb) { 464 | this._didUpdateStatusCB = cb; 465 | } 466 | 467 | /** 468 | * 注册发现外设回调 469 | * 470 | * @param {function} cb 回调函数 471 | * 472 | * @discussion 当扫描到设备时回调 473 | */ 474 | registerDidDiscoverDevice(cb) { 475 | this._didDiscoverDeviceCB = cb; 476 | } 477 | 478 | /** 479 | * 注册特征值改变回调 480 | * 481 | * @param {function} cb 回调函数 482 | * 483 | * @discussion 当监听的特征值改变时回调 484 | */ 485 | registerDidUpdateValueForCharacteristic(cb) { 486 | this._didUpdateValueCB = cb; 487 | _.api('offBLECharacteristicValueChange').then(__ => __).catch(__ => __); 488 | _.on('onBLECharacteristicValueChange', '' ,characteristic => { 489 | if (typeof characteristic.value === 'string') { 490 | this._didUpdateValueCB(characteristic); 491 | } else { 492 | characteristic.value = _.ab2str(characteristic.value); 493 | this._didUpdateValueCB(characteristic); 494 | } 495 | }) 496 | } 497 | 498 | /** 499 | * 回调蓝牙连接状态 500 | */ 501 | callBackConnectStatus(status) { 502 | this._didUpdateStatusCB && this._didUpdateStatusCB({ 503 | ...status, 504 | device:this.bm.deviceInfo, 505 | connectStatus:this.bm.connectStatus 506 | }); 507 | } 508 | 509 | /** 510 | * 回调发现外设 511 | * 512 | * @param device 扫描到的设备 513 | */ 514 | callBackDiscoverDevice(device , event , timeout) { 515 | this._didDiscoverDeviceCB && this._didDiscoverDeviceCB( 516 | device ? { 517 | ...event, 518 | timeout, 519 | device, 520 | } : { 521 | ...event, 522 | timeout, 523 | } 524 | ) 525 | } 526 | 527 | /** 528 | * 销毁延时 529 | */ 530 | destoryTimer() { 531 | this.scanTimeoutTimer && clearTimeout(this.scanTimeoutTimer); 532 | this.scanTimeoutTimer = null; 533 | } 534 | } -------------------------------------------------------------------------------- /example/antdemo/wx-ant-ble/src/btmanager.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | import { Log } from './extends.js'; 4 | import { ConnectStatus } from './enum.js'; 5 | import Bluetooth from './bluetooth.js'; 6 | 7 | export class BTManager { 8 | 9 | /** 10 | * 构造函数 11 | * 12 | * @param {object} config 配置 13 | * @property {boolean} debug 是否开启打印调试,默认不开启 14 | * 15 | * @discussion 单例模式。 16 | */ 17 | constructor(config = {}) { 18 | if (!BTManager.instance) { 19 | BTManager.instance = this; 20 | // 初始化log 21 | Log.call(BTManager.prototype); 22 | // 初始化设备信息 23 | this.deviceInfo = {}; 24 | // 初始化连接状态 25 | this.connectStatus = ConnectStatus.disconnected; 26 | // 初始化蓝牙管理器 27 | this._bt = new Bluetooth(this); 28 | } 29 | // 合并配置 30 | Object.assign(BTManager.instance, config); 31 | return BTManager.instance; 32 | } 33 | 34 | /** 35 | * 扫描外设 36 | * 37 | * @param {object} options 扫描参数 38 | * @property {array} services 主service的uuid列表。确认在蓝牙广播中存在此服务id,可以通过服务id过滤掉其他设备 39 | * @property {boolean} allowDuplicatesKey 是否允许重复上报设备 40 | * @property {number} interval 上报新设备的间隔,默认为0 41 | * @property {number} timeout 扫描超时时间,毫秒。在该时间内未扫描到符合要求的设备,上报超时。默认15000ms,-1表示无限超时 42 | * @property {string} deviceName 通过蓝牙名称过滤,需要匹配的设备名称 43 | * @property {string} containName 通过蓝牙名称过滤,需要包含的设备名称 44 | * 45 | * @return Promise对象 46 | * 47 | * @discussion 开始扫描外设,注意实现返回对象的then和catch方法,监听接口是否调用成功。 48 | * 此操作比较耗费系统资源,请在搜索到设备后调用stopScan方法停止扫描。 49 | * 重复调用此接口,会清空之前设备存储,再次上报已上报的设备,能够起到刷新的作用。 50 | * 51 | * @see registerDidDiscoverDevice 52 | */ 53 | scan( options = { 54 | services: [], 55 | allowDuplicatesKey: false, 56 | interval : 0, 57 | timeout: 15000, 58 | deviceName: '', 59 | containName:'' 60 | }) 61 | { 62 | return this._bt.scanDevice(options); 63 | } 64 | 65 | /** 66 | * 停止扫描 67 | * 68 | * @return Promise对象 69 | * 70 | * @discussion 停止扫描,取消超时延时。 71 | */ 72 | stopScan() { 73 | return this._bt.stopScan(); 74 | } 75 | 76 | /** 77 | * 连接外设 78 | * 79 | * @param {object} device 指定连接的外设对象,从registerDidDiscoverDevice注册的回调中得到 80 | * @param {number} timeout 连接超时时间,毫秒,默认15000ms,支付宝小程序无效 81 | * 82 | * @return Promise对象 83 | * 84 | * @discussion 连接指定的外设,需要传入外设对象。 85 | * 注意实现返回对象的then和catch方法,监听接口是否调用成功。 86 | */ 87 | connect(device , timeout) { 88 | if (!device) throw new Error('device is undefiend'); 89 | return this._bt.connect(device); 90 | } 91 | 92 | /** 93 | * 断开连接 94 | * 95 | * @return Promise对象 96 | */ 97 | disconnect() { 98 | return this._bt.disconnect(); 99 | } 100 | 101 | /** 102 | * 读特征值 103 | * 104 | * @param {object} params 参数 105 | * @property {string} suuid 特征对应的服务uuid 106 | * @property {string} cuuid 特征uuid 107 | * 108 | * @return Promise对象 109 | * 110 | * @discussion 读某个服务下的某个特征值。 111 | */ 112 | read(params = { 113 | suuid: '', 114 | cuuid: '' 115 | }) 116 | { 117 | return this._bt.read(params); 118 | } 119 | 120 | /** 121 | * 向蓝牙模块写入数据 122 | * 123 | * @param {object} params 参数 124 | * @property {string} suuid 特征对应的服务uuid 125 | * @property {string} cuuid 特征uuid 126 | * @property {Hex string} value 16进制字符串 127 | * 128 | * @return Promise对象 129 | * 130 | * @discussion 向蓝牙模块写入数据。 131 | */ 132 | write(params = { 133 | suuid: '', 134 | cuuid: '', 135 | value: '' 136 | }) 137 | { 138 | return this._bt.write(params); 139 | } 140 | 141 | /** 142 | * 监听特征值改变 143 | * 144 | * @param {object} params 参数 145 | * @property {string} suuid 特征对应的服务uuid 146 | * @property {string} cuuid 特征uuid 147 | * @property {boolean} state 是否启用notify,可以通过重复调用接口改变此属性打开/关闭监听 148 | * 149 | * @return Promise对象 150 | * 151 | * @discussion 监听某个特征值变化。 152 | */ 153 | notify(params = { 154 | suuid: '', 155 | cuuid: '', 156 | state: true, 157 | }) 158 | { 159 | return this._bt.notify(params); 160 | } 161 | 162 | /** 163 | * 注册状态改变回调 164 | * 165 | * @param {function} cb 回调函数 166 | * 167 | * @discussion 连接状态发生改变时,回调此方法。 168 | */ 169 | registerDidUpdateConnectStatus(cb) { 170 | if (typeof cb !== 'function') throw new TypeError('connectStatus callback expect function'); 171 | this._bt.registerDidUpdateConnectStatus(cb); 172 | } 173 | 174 | /** 175 | * 注册发现外设回调 176 | * 177 | * @param {function} cb 回调函数 178 | * 179 | * @discussion 当扫描到设备时回调,或者达到超时时间回调。 180 | */ 181 | registerDidDiscoverDevice(cb) { 182 | if (typeof cb !== 'function') throw new TypeError('discoverDevice callback expect function'); 183 | this._bt.registerDidDiscoverDevice(cb); 184 | } 185 | 186 | /** 187 | * 注册特征值改变回调 188 | * 189 | * @param {function} cb 回调函数 190 | * 191 | * @discussion 当监听的特征值改变时回调,或者读特征值时回调。 192 | */ 193 | registerDidUpdateValueForCharacteristic(cb) { 194 | if (typeof cb !== 'function') throw new TypeError('updateValueForCharacteristic callback expect function'); 195 | this._bt.registerDidUpdateValueForCharacteristic(cb); 196 | } 197 | 198 | } -------------------------------------------------------------------------------- /example/antdemo/wx-ant-ble/src/enum.js: -------------------------------------------------------------------------------- 1 | 2 | // 连接状态 3 | export const ConnectStatus = { 4 | // 未连接或连接断开,允许连接 5 | disconnected: 0, 6 | // 正在连接,不允许再连接 7 | connecting: 1, 8 | // 已连接,不允许再连接 9 | connected: 2, 10 | } 11 | 12 | // 发现外设回调和连接状态改变回调成功事件 13 | export const SuccessCallbackEvent = { 14 | Success_DiscoverDevice_CB_Discover: { code: 210, message: '发现外设' }, 15 | Success_DiscoverDevice_CB_ScanDone: { code: 211, message: '扫描完成' }, 16 | Success_ConnectStatus_CB_PowerOn: { code: 220, message: '蓝牙打开' }, 17 | Success_ConnectStatus_CB_Connecting: { code: 221, message: '正在连接' }, 18 | Success_ConnectStatus_CB_Connected: { code: 222, message: '连接成功' }, 19 | Success_ConnectStatus_CB_Stop: { code: 223, message: '断开成功' }, 20 | } 21 | 22 | // 发现外设回调和连接状态改变回调失败事件 23 | export const ErrorCallbackEvent = { 24 | Error_DiscoverDevice_CB_Timeout: { code: 410, message: '扫描超时' }, 25 | Error_ConnectStatus_CB_PowerOff: { code: 420, message: '蓝牙关闭' }, 26 | Error_ConnectStatus_CB_ConnectFail: { code: 421, message: '连接失败' }, 27 | Error_ConnectStatus_CB_Disconnected: { code: 422, message: '连接断开' }, 28 | } 29 | 30 | // 接口调用成功事件 31 | export const SuccessApiThen = { 32 | Success_Scan: { code: 2010, message: '扫描接口成功调用' }, 33 | Success_StopScan: { code: 2020, message: '停止扫描接口成功调用' }, 34 | Success_Connect: { code: 2030, message: '连接接口成功调用' }, 35 | Success_Disconnect: { code: 2040, message: '断开接口成功调用' }, 36 | Success_Read: { code: 2050, message: '读特征值接口成功调用' }, 37 | Success_Write: { code: 2060, message: '写入数据接口成功调用' }, 38 | Success_Notify: { code: 2070, message: '监听特征值接口成功调用' }, 39 | } 40 | 41 | // 接口调用失败事件 42 | export const ErrorApiCatch = { 43 | // 基础库版本低 44 | Error_Low_Version: { code: 4000, message: '当前基础库版本低,请更新微信版本' }, 45 | // Api: scan 46 | Error_Scan_Failed: { code: 4010, message: '扫描错误:扫描失败' }, 47 | Error_Scan_PowerOff: { code: 4011, message: '扫描错误:蓝牙被关闭' }, 48 | Error_Scan_NoService: { code: 4012, message: '扫描错误:没有找到指定服务' }, 49 | // Api: stopScan 50 | Error_StopScan_Failed: { code: 4020, message: '停止扫描错误:停止扫描失败' }, 51 | Error_StopScan_PowerOff: { code: 4021, message: '停止扫描错误:蓝牙被关闭' }, 52 | // Api: connect 53 | Error_Connect_Failed: { code: 4030, message: '连接错误:连接失败' }, 54 | Error_Connect_PowerOff: { code: 4031, message: '连接错误:蓝牙被关闭' }, 55 | Error_Connect_AlreadyConnected: { code: 4032, message: '连接错误:已经连接或正在连接' }, 56 | Error_Connect_Timeout: { code: 4033, message: '连接错误:连接超时' }, 57 | Error_Connect_EmptyId: { code: 4034, message: '连接错误:设备id不能为空' }, 58 | // Api: disconnect 59 | Error_Disconnect_Failed: { code: 4040, message: '断开错误:断开失败' }, 60 | // Api: read 61 | Error_Read_Failed: { code: 4050, message: '读特征值错误:读特征值失败'}, 62 | Error_Read_NotConnected: { code: 4051, message: '读特征值错误:蓝牙未连接' }, 63 | Error_Read_NotSupport: { code: 4052, message: '读特征值错误:当前特征不支持读操作' }, 64 | Error_Read_NoService: { code: 4053, message: '读特征值错误:没有找到指定服务' }, 65 | Error_Read_NoCharacteristic: { code: 4054, message: '读特征值错误:没有找到指定特征值' }, 66 | // Api: write 67 | Error_Write_Failed: { code: 4060, message: '写入数据错误:写入数据失败'}, 68 | Error_Write_NotConnected: { code: 4061, message: '写入数据错误:蓝牙未连接' }, 69 | Error_Write_NotSupport: { code: 4062, message: '写入数据错误:当前特征不支持写操作' }, 70 | Error_Write_NoService: { code: 4063, message: '写入数据错误:没有找到指定服务' }, 71 | Error_Write_NoCharacteristic: { code: 4064, message: '写入数据错误:没有找到指定特征值' }, 72 | // Api: notify 73 | Error_Notify_Failed: { code: 4070, message: '监听特征值错误:监听特征值错误失败' }, 74 | Error_Notify_NotConnected: { code: 4071, message: '监听特征值错误:蓝牙未连接' }, 75 | Error_Notify_NotSupport: { code: 4072, message: '监听特征值错误:当前特征不支持监听操作' }, 76 | Error_Notify_NoService: { code: 4073, message: '监听特征值错误:没有找到指定服务' }, 77 | Error_Notify_NoCharacteristic: { code: 4074, message: '监听特征值错误:没有找到指定特征值' }, 78 | } 79 | 80 | -------------------------------------------------------------------------------- /example/antdemo/wx-ant-ble/src/extends.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | export function Log() { 4 | 5 | this.log = function(identifier, msg='') { 6 | this.debug && console.log(`BLE:(${identifier}):` , msg); 7 | } 8 | 9 | this.loginfo = function(identifier,msg) { 10 | this.debug && console.info(`BLE:(${identifier}):` ,msg); 11 | } 12 | 13 | this.logwarn = function (identifier, msg) { 14 | this.debug && console.warn(`BLE:(${identifier}):`, msg); 15 | } 16 | 17 | this.logerror = function (identifier, msg) { 18 | this.debug && console.error(`BLE:(${identifier}):`, msg); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /example/antdemo/wx-ant-ble/src/promisify.js: -------------------------------------------------------------------------------- 1 | 2 | // 判断是微信小程序还是支付宝小程序 3 | let ii, mini; 4 | try { 5 | ii = wx; 6 | mini = 'wx'; 7 | } catch (e) { 8 | ii = my; 9 | mini = 'ant'; 10 | } 11 | 12 | import { ErrorApiCatch} from './enum.js'; 13 | 14 | // 微信系统错误 15 | const ERROR_WX = { 16 | NOT_INIT: { code: 10000, message: '未初始化蓝牙适配器' }, 17 | NOT_AVAILABLE: { code: 10001, message: '当前蓝牙适配器不可用' }, 18 | NO_DEVICE: { code: 10002, message: '没有找到指定设备' }, 19 | CONNECTION_FAIL: { code: 10003, message: '连接失败' }, 20 | NO_SERVICE: { code: 10004, message: '没有找到指定服务' }, 21 | NO_CHARACTERISTIC: { code: 10005, message: '没有找到指定特征值' }, 22 | NO_CONNECTION: { code: 10006, message: '当前连接已断开' }, 23 | PROPERTY_NOT_SUPPORT: { code: 10007, message: '当前特征值不支持此操作' }, 24 | SYSTEM_ERROR: { code: 10008, message: '系统异常' }, 25 | SYSTEM_NOT_SUPPORT: { code: 10009, message: 'Android 系统版本低于 4.3 不支持 BLE' }, 26 | OPERATE_TIMEOUT: { code: 10012, message: '操作超时' }, 27 | INVALID_PARAMETER: { code: 10013, message: '无效参数' }, 28 | ALREADY_CONNECTED: { code: -1 , message: '蓝牙已连接,不能再连接' }, 29 | UN_KNOWN: { code: 100000,message: '未知' }, 30 | } 31 | 32 | // 支付宝系统错误 33 | const ERROR_ANT = { 34 | POWER_OFF: { code: 12, message: '蓝牙未打开' }, 35 | LOST_SERVICE: { code: 13, message: '与系统服务的链接暂时丢失' }, 36 | UNAUTH_BLE: { code: 14, message: '未授权支付宝使用蓝牙功能' }, 37 | UNKNOWN_ERROR: { code: 15, message: '未知错误' }, 38 | NOT_INIT: { code: 10000, message: '未初始化蓝牙适配器' }, 39 | NOT_AVAILABLE: { code: 10001, message: '当前蓝牙适配器不可用' }, 40 | NO_DEVICE: { code: 10002, message: '没有找到指定设备' }, 41 | CONNECTION_FAIL: { code: 10003, message: '连接失败' }, 42 | NO_SERVICE: { code: 10004, message: '没有找到指定服务' }, 43 | NO_CHARACTERISTIC: { code: 10005, message: '没有找到指定特征值' }, 44 | NO_CONNECTION: { code: 10006, message: '当前连接已断开' }, 45 | PROPERTY_NOT_SUPPORT: { code: 10007, message: '当前特征值不支持此操作' }, 46 | SYSTEM_ERROR: { code: 10008, message: '系统异常' }, 47 | SYSTEM_NOT_SUPPORT: { code: 10009, message: 'Android 系统版本低于 4.3 不支持 BLE' }, 48 | SYMBOL_UNFOUND: { code: 10010, message: '没有找到指定描述符' }, 49 | DEVICE_ID_INVALID: { code: 10011, message: '设备 id 不可用/为空' }, 50 | SERVICE_ID_INVALID: { code: 10012, message: '服务 id 不可用/为空' }, 51 | CHARACTERISTIC_ID_INVALID: { code: 10013, message: '特征 id 不可用/为空' }, 52 | CMD_FORMAT_ERROR: { code: 10014, message: '发送的数据为空或格式错误' }, 53 | OPERATION_TIMEOUT: { code: 10015, message: '操作超时' }, 54 | LACK_PARAMETER: { code: 10016, message: '缺少参数' }, 55 | WRITE_ERROR: { code: 10017, message: '写入特征值失败' }, 56 | READ_ERROR: { code: 10018, message: '读取特征值失败' }, 57 | } 58 | 59 | const ERROR_TYPES = mini==='wx' ? ERROR_WX : ERROR_ANT; 60 | 61 | /** 62 | * 获取错误类型 63 | */ 64 | function getErrorType(code) { 65 | for (let key of Object.keys(ERROR_TYPES)) { 66 | if (code === ERROR_TYPES[key].code) { 67 | return ERROR_TYPES[key]; 68 | } 69 | } 70 | return ERROR_TYPES.UN_KNOWN; 71 | } 72 | 73 | /** 74 | * 获取小程序平台 wx||ant 75 | */ 76 | export function getAppPlatform() { 77 | return mini; 78 | } 79 | 80 | /** 81 | * 重写异步API 82 | * 83 | * @param {string} fn1 方法名1 84 | * @param {string} fn2 方法名2 85 | * @param {object} options 可选参数 86 | * 87 | * @return Promise对象 88 | */ 89 | export function api(fn1, fn2, options) { 90 | let func = ii[fn1] || ii[fn2]; 91 | return new Promise((resolve, reject) => { 92 | let params = { 93 | success(res) { 94 | resolve(res); 95 | }, 96 | fail(res) { 97 | if (res.errorMessage) { 98 | reject({ 99 | code: res.error, 100 | message: res.errorMessage 101 | }); 102 | } else { 103 | reject({ 104 | ...getErrorType(res.errCode), 105 | errMsg: res.errMsg 106 | }); 107 | } 108 | }, 109 | complete(res) { 110 | //... 111 | } 112 | } 113 | if (options) { 114 | params = Object.assign(params, options); 115 | } 116 | if (func) { 117 | func(params); 118 | } else { 119 | reject(ErrorApiCatch.Error_Low_Version); 120 | } 121 | }) 122 | } 123 | 124 | /** 125 | * 重写回调API 126 | * 127 | * @param {string} fn1 方法名1 128 | * @param {string} fn2 方法名2 129 | * @param {function} cb 回调函数 130 | */ 131 | export function on(fn1, fn2, cb) { 132 | let func = ii[fn1] || ii[fn2]; 133 | if (func) { 134 | if (mini === 'wx') { 135 | func(cb); 136 | } else { 137 | func({ 138 | success: cb, 139 | }) 140 | } 141 | } 142 | } -------------------------------------------------------------------------------- /example/antdemo/wx-ant-ble/src/tools.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | import { getAppPlatform , api , on} from './promisify.js'; 5 | 6 | /** 7 | * 判断字符串是否为空或者空格 8 | */ 9 | function isEmpty(str = '') { 10 | return !str || str == '' || str.replace(/(^\s*)|(\s*$)/g, "") == ""; 11 | } 12 | 13 | /** 14 | * 判断是否为null或者未定义 15 | */ 16 | function isNullOrUndefined(obj) { 17 | return obj === undefined || obj === null; 18 | } 19 | 20 | /** 21 | * ArrayBuffer类型转换为16进制字符串 22 | */ 23 | function ab2str(buffer) { 24 | return Array.prototype.map.call(new Uint8Array(buffer), x => ('00' + x.toString(16)).slice(-2)).join(''); 25 | } 26 | 27 | /** 28 | * 字符串转为ArrayBuffer对象,参数为字符串 29 | */ 30 | function str2ab(str) { 31 | var buf = new ArrayBuffer(str.length / 2); 32 | var bufView = new Uint8Array(buf); 33 | for (var i = 0; i < str.length; i += 2) { 34 | bufView[parseInt(i / 2)] = char2Hex(str.charCodeAt(i)) << 4 | char2Hex(str.charCodeAt(i + 1)); 35 | } 36 | return buf; 37 | } 38 | 39 | /** 40 | * 字符转十六进制 41 | */ 42 | function char2Hex(bChar) { 43 | if ((bChar >= 0x30) && (bChar <= 0x39)) { // 数字 44 | bChar -= 0x30; 45 | } else if ((bChar >= 0x41) && (bChar <= 0x46)) { // 大写字母 46 | bChar -= 0x37; 47 | } else if ((bChar >= 0x61) && (bChar <= 0x66)) { // 小写字母 48 | bChar -= 0x57; 49 | } else { 50 | bChar = 0xff; 51 | } 52 | return bChar; 53 | } 54 | 55 | /** 56 | * TypedArray转为ArrayBuffer 57 | */ 58 | function typedArray2ArrayBuffer(pbuff) { 59 | let buffer = new ArrayBuffer(pbuff.byteLength) 60 | let uInit8 = new Uint8Array(buffer) 61 | uInit8.set(pbuff); 62 | return buffer; 63 | } 64 | 65 | /** 66 | * 获取异或校验数值 67 | * 68 | * @param p 参与运算的字符数组指针 69 | * @param len 参与运算的字符数组长度 70 | */ 71 | function createXOR(b, p, len) { 72 | let i = 0; 73 | let ckc = 0; 74 | for (; i < len; i++) { 75 | ckc = ckc ^ b[p + i]; 76 | } 77 | return ckc; 78 | } 79 | 80 | /** 81 | * UUID128位转换为16位 82 | * 83 | * @param uuid128 128位的uuid 84 | */ 85 | function uuid128to16(uuid128) { 86 | let arr = uuid128.split('-'); 87 | if (arr.length === 5 && arr[4] === '00805F9B34FB') { 88 | return arr[0].slice(3); 89 | } else { 90 | return uuid128; 91 | } 92 | } 93 | 94 | export default { 95 | getAppPlatform, 96 | api, 97 | on, 98 | isEmpty, 99 | isNullOrUndefined, 100 | ab2str, 101 | str2ab, 102 | typedArray2ArrayBuffer, 103 | createXOR, 104 | uuid128to16 105 | } 106 | 107 | 108 | -------------------------------------------------------------------------------- /example/wxdemo/app.js: -------------------------------------------------------------------------------- 1 | //app.js 2 | App({ 3 | onLaunch: function () { 4 | // 展示本地存储能力 5 | var logs = wx.getStorageSync('logs') || [] 6 | logs.unshift(Date.now()) 7 | wx.setStorageSync('logs', logs) 8 | 9 | // 登录 10 | wx.login({ 11 | success: res => { 12 | // 发送 res.code 到后台换取 openId, sessionKey, unionId 13 | } 14 | }) 15 | // 获取用户信息 16 | wx.getSetting({ 17 | success: res => { 18 | if (res.authSetting['scope.userInfo']) { 19 | // 已经授权,可以直接调用 getUserInfo 获取头像昵称,不会弹框 20 | wx.getUserInfo({ 21 | success: res => { 22 | // 可以将 res 发送给后台解码出 unionId 23 | this.globalData.userInfo = res.userInfo 24 | 25 | // 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回 26 | // 所以此处加入 callback 以防止这种情况 27 | if (this.userInfoReadyCallback) { 28 | this.userInfoReadyCallback(res) 29 | } 30 | } 31 | }) 32 | } 33 | } 34 | }) 35 | }, 36 | globalData: { 37 | userInfo: null 38 | } 39 | }) -------------------------------------------------------------------------------- /example/wxdemo/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages": [ 3 | "pages/home/home", 4 | "pages/index/index", 5 | "pages/logs/logs" 6 | 7 | ], 8 | "window": { 9 | "backgroundTextStyle": "light", 10 | "navigationBarBackgroundColor": "#fff", 11 | "navigationBarTitleText": "WeChat", 12 | "navigationBarTextStyle": "black" 13 | } 14 | } -------------------------------------------------------------------------------- /example/wxdemo/app.wxss: -------------------------------------------------------------------------------- 1 | /**app.wxss**/ 2 | page { 3 | width: 100%; 4 | height: 100%; 5 | display: flex; 6 | background: #f7f7f7; 7 | flex-direction: column; 8 | align-items: center; 9 | } 10 | 11 | .absTopLeft0 { 12 | position: absolute; 13 | top: 0rpx; 14 | left: 0rpx; 15 | } 16 | 17 | /* 居中显示 */ 18 | .flex-center{ 19 | display: flex; 20 | align-items: center; 21 | justify-content: center; 22 | } 23 | 24 | /* 水平居中显示 */ 25 | .flex-row-center{ 26 | display: flex; 27 | flex-direction: row; 28 | align-items: center; 29 | } 30 | 31 | /* 垂直居中显示 */ 32 | .flex-column-center{ 33 | display: flex; 34 | flex-direction: column; 35 | align-items: center; 36 | } 37 | 38 | /* 垂直靠左显示 */ 39 | .flex-column-left{ 40 | display: flex; 41 | flex-direction: column; 42 | align-items: flex-start; 43 | } 44 | 45 | /* 水平靠左显示 */ 46 | .flex-row-left{ 47 | display: flex; 48 | flex-direction: row; 49 | align-items: flex-start; 50 | justify-content: center 51 | } 52 | 53 | /* 垂直居下显示 */ 54 | .flex-column-end{ 55 | display: flex; 56 | flex-direction: column; 57 | justify-content: flex-end; 58 | } 59 | 60 | ::-webkit-scrollbar { 61 | width: 0; 62 | height: 0; 63 | color: transparent; 64 | } -------------------------------------------------------------------------------- /example/wxdemo/images/arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaodahai/wx-ant-ble/c689614f401ca2c52a1f5f34a036be2f2c3088ec/example/wxdemo/images/arrow.png -------------------------------------------------------------------------------- /example/wxdemo/miniprogram_npm/wx-ant-ble/index.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["index.js","src/btmanager.js","src/extends.js","src/enum.js","src/bluetooth.js","src/tools.js","src/promisify.js"],"names":[],"mappings":";;;;;;;AAAA;AACA;AACA;AACA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA,ACHA;AFOA,ACHA,ACHA;AFOA,ACHA,ACHA;AFOA,ACHA,AENA,ADGA;AFOA,ACHA,AENA,ADGA;AFOA,ACHA,AENA,ADGA;AFOA,AIZA,AHSA,AENA,ADGA;AFOA,AIZA,AHSA,AENA,ADGA;AELA,AHSA,AENA,ADGA;AELA,AHSA,AENA,ADGA,AGTA;ADIA,AHSA,AENA,ADGA,AGTA;ADIA,AHSA,AENA,ADGA,AGTA;ADIA,AHSA,AENA,ADGA,AIZA,ADGA;ADIA,AHSA,AENA,ADGA,AIZA,ADGA;ADIA,AHSA,AENA,ADGA,AIZA,ADGA;ADIA,AHSA,AENA,ADGA,AIZA,ADGA;ADIA,AHSA,AENA,ADGA,AIZA,ADGA;ADIA,AHSA,AENA,ADGA,AIZA,ADGA;ADIA,AHSA,AENA,ADGA,AIZA,ADGA;ADIA,AHSA,AENA,ADGA,AIZA,ADGA;ADIA,AHSA,AENA,ADGA,AIZA,ADGA;ADIA,AHSA,AENA,ADGA,AIZA,ADGA;ADIA,AHSA,AENA,AGTA,ADGA;ADIA,AHSA,AENA,AGTA,ADGA;ADIA,AHSA,AENA,AGTA,ADGA;ADIA,AHSA,AENA,AGTA,ADGA;ADIA,AHSA,AENA,AGTA,ADGA;ADIA,AHSA,AENA,AGTA,ADGA;ADIA,AHSA,AENA,AGTA,ADGA;ADIA,AHSA,AENA,AGTA,ADGA;ADIA,AHSA,AENA,AGTA,ADGA;ADIA,AHSA,AENA,AGTA,ADGA;ADIA,AHSA,AENA,AGTA,ADGA;ADIA,AHSA,AENA,AGTA,ADGA;ADIA,AHSA,AENA,AGTA,ADGA;ADIA,AHSA,AENA,AGTA,ADGA;ADIA,AHSA,AENA,AGTA,ADGA;ADIA,AHSA,AENA,AGTA,ADGA;ADIA,AHSA,AENA,AGTA,ADGA;ADIA,AHSA,AENA,AGTA,ADGA;ADIA,AHSA,AENA,AGTA,ADGA;ADIA,AHSA,AENA,AGTA,ADGA;ADIA,AHSA,AENA,AGTA,ADGA;ADIA,AHSA,AENA,AGTA,ADGA;ADIA,AHSA,AENA,AGTA,ADGA;ADIA,AHSA,AENA,AGTA,ADGA;ADIA,AHSA,AENA,AGTA,ADGA;ADIA,AHSA,AENA,AGTA,ADGA;ADIA,AHSA,AENA,AGTA,ADGA;ADIA,AHSA,AENA,AGTA,ADGA;ADIA,AHSA,AENA,AGTA,ADGA;ADIA,AHSA,AENA,AGTA,ADGA;ADIA,AHSA,AENA,AGTA,ADGA;ADIA,AHSA,AENA,AGTA,ADGA;ADIA,AHSA,AENA,AGTA,ADGA;ADIA,AHSA,AENA,AGTA,ADGA;ADIA,AHSA,AENA,AGTA,ADGA;ADIA,AHSA,AENA,AGTA,ADGA;ADIA,AHSA,AENA,AGTA,ADGA;ADIA,AHSA,AENA,AGTA,ADGA;ADIA,AHSA,AENA,AGTA,ADGA;ADIA,AHSA,AENA,AGTA,ADGA;ADIA,AHSA,AENA,AGTA,ADGA;ADIA,AHSA,AENA,AGTA,ADGA;ADIA,AHSA,AENA,AGTA,ADGA;ADIA,AHSA,AENA,AGTA,ADGA;ADIA,AHSA,AENA,AGTA,ADGA;ADIA,AHSA,AENA,AGTA,ADGA;ADIA,AHSA,AENA,AGTA,ADGA;ADIA,AHSA,AENA,AGTA,ADGA;ADIA,AHSA,AENA,AGTA,ADGA;ADIA,AHSA,AENA,AGTA,ADGA;ADIA,AHSA,AENA,AGTA,ADGA;ADIA,AHSA,AENA,AGTA,ADGA;ADIA,AHSA,AENA,AGTA,ADGA;ADIA,AHSA,AENA,AGTA,ADGA;ADIA,AHSA,AENA,AGTA,ADGA;ADIA,AHSA,AENA,AGTA,ADGA;ADIA,AHSA,AENA,AGTA,ADGA;ADIA,AHSA,AENA,AGTA,ADGA;ADIA,AHSA,AENA,AGTA,ADGA;ADIA,AHSA,AENA,AGTA,ADGA;ADIA,AHSA,AENA,AGTA,ADGA;ADIA,AHSA,AKfA,ADGA;ADIA,AHSA,AKfA,ADGA;ADIA,AHSA,AKfA,ADGA;ADIA,AHSA,AKfA,ADGA;ADIA,AHSA,AKfA,ADGA;ADIA,AHSA,AKfA,ADGA;ADIA,AHSA,AKfA,ADGA;ADIA,AHSA,AKfA,ADGA;ADIA,AHSA,AKfA,ADGA;ADIA,AHSA,AKfA,ADGA;ADIA,AHSA,AKfA,ADGA;ADIA,AHSA,AKfA,ADGA;ADIA,AHSA,AKfA,ADGA;ADIA,AHSA,AKfA,ADGA;ADIA,AHSA,AKfA,ADGA;ADIA,AHSA,AKfA,ADGA;ADIA,AHSA,AKfA,ADGA;ADIA,AHSA,AKfA,ADGA;ADIA,AHSA,AKfA,ADGA;ADIA,AHSA,AKfA,ADGA;ADIA,AHSA,AKfA,ADGA;ADIA,AHSA,AKfA,ADGA;ADIA,AHSA,AKfA,ADGA;ADIA,AHSA,AKfA,ADGA;ADIA,AHSA,AKfA,ADGA;ADIA,AHSA,AKfA,ADGA;ADIA,AHSA,AKfA,ADGA;ADIA,AHSA,AKfA,ADGA;ADIA,AHSA,AKfA,ADGA;ADIA,AHSA,AKfA,ADGA;ADIA,AHSA,AKfA,ADGA;ADIA,AHSA,AKfA,ADGA;ADIA,AHSA,AKfA,ADGA;ADIA,AHSA,AKfA,ADGA;ADIA,AHSA,AKfA;AFOA,AHSA,AKfA;AFOA,AHSA,AKfA;AFOA,AHSA,AKfA;AFOA,AHSA,AKfA;AFOA,AHSA,AKfA;AFOA,AHSA,AKfA;AFOA,AHSA,AKfA;AFOA,AHSA,AKfA;AFOA,AHSA,AKfA;AFOA,AHSA,AKfA;AFOA,AHSA,AKfA;AFOA,AHSA,AKfA;AFOA,AHSA,AKfA;AFOA,AHSA,AKfA;AFOA,AHSA,AKfA;AFOA,AHSA,AKfA;AFOA,AHSA,AKfA;AFOA,AHSA,AKfA;AFOA,AHSA,AKfA;AFOA,AHSA,AKfA;AFOA,AHSA,AKfA;AFOA,AHSA,AKfA;AFOA,AHSA,AKfA;AFOA,AHSA,AKfA;AFOA,AHSA,AKfA;AFOA,AHSA,AKfA;AFOA,AHSA,AKfA;AFOA,AHSA,AKfA;AFOA,AHSA,AKfA;AFOA,AHSA,AKfA;AFOA,AHSA,AKfA;AFOA,AHSA,AKfA;AFOA,AHSA,AKfA;AFOA,AHSA,AKfA;AFOA,AHSA,AKfA;AFOA,AHSA,AKfA;AFOA,AHSA;AGRA,AHSA;AGRA,AHSA;AGRA,AHSA;AGRA,AHSA;AGRA,AHSA;AGRA,AHSA;AGRA,AHSA;AGRA,AHSA;AGRA,AHSA;AGRA,AHSA;AGRA,AHSA;AGRA,AHSA;AGRA,AHSA;AGRA,AHSA;AGRA,AHSA;AGRA,AHSA;AGRA,AHSA;AGRA,AHSA;AGRA,AHSA;AGRA,AHSA;AGRA,AHSA;AGRA,AHSA;AGRA,AHSA;AGRA,AHSA;AGRA,AHSA;AGRA,AHSA;AGRA,AHSA;AGRA,AHSA;AGRA,AHSA;AGRA,AHSA;AGRA,AHSA;AGRA,AHSA;AGRA,AHSA;AGRA,AHSA;AGRA,AHSA;AGRA,AHSA;AGRA,AHSA;AGRA,AHSA;AGRA,AHSA;AGRA,AHSA;AGRA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"index.js","sourcesContent":["\nvar __TEMP__ = require('./src/btmanager.js');var BTManager = __TEMP__['BTManager'];\nvar __TEMP__ = require('./src/enum.js');var ConnectStatus = __TEMP__['ConnectStatus'];var SuccessCallbackEvent = __TEMP__['SuccessCallbackEvent'];var ErrorCallbackEvent = __TEMP__['ErrorCallbackEvent'];var SuccessApiThen = __TEMP__['SuccessApiThen'];var ErrorApiCatch = __TEMP__['ErrorApiCatch'];\nconst Version = '1.1.0';\n\nif (!exports.__esModule) Object.defineProperty(exports, \"__esModule\", { value: true });Object.defineProperty(exports, 'Version', { enumerable: true, get: function() { return Version; } });Object.defineProperty(exports, 'BTManager', { enumerable: true, get: function() { return BTManager; } });Object.defineProperty(exports, 'ConnectStatus', { enumerable: true, get: function() { return ConnectStatus; } });Object.defineProperty(exports, 'SuccessCallbackEvent', { enumerable: true, get: function() { return SuccessCallbackEvent; } });Object.defineProperty(exports, 'ErrorCallbackEvent', { enumerable: true, get: function() { return ErrorCallbackEvent; } });Object.defineProperty(exports, 'SuccessApiThen', { enumerable: true, get: function() { return SuccessApiThen; } });Object.defineProperty(exports, 'ErrorApiCatch', { enumerable: true, get: function() { return ErrorApiCatch; } });\n\n\n\n\n\n\n\n","\n\nvar __TEMP__ = require('./extends.js');var Log = __TEMP__['Log'];\nvar __TEMP__ = require('./enum.js');var ConnectStatus = __TEMP__['ConnectStatus'];\nvar __TEMP__ = require('./bluetooth.js');var Bluetooth = __REQUIRE_DEFAULT__(__TEMP__);\n\nif (!exports.__esModule) Object.defineProperty(exports, \"__esModule\", { value: true });exports.BTManager = class BTManager {\n\n /**\n * 构造函数\n *\n * @param {object} config 配置\n * @property {boolean} debug 是否开启打印调试,默认不开启\n * \n * @discussion 单例模式。\n */\n constructor(config = {}) {\n if (!BTManager.instance) {\n BTManager.instance = this;\n // 初始化log\n Log.call(BTManager.prototype);\n // 初始化设备信息\n this.deviceInfo = {};\n // 初始化连接状态\n this.connectStatus = ConnectStatus.disconnected;\n // 初始化蓝牙管理器\n this._bt = new Bluetooth(this);\n }\n // 合并配置\n Object.assign(BTManager.instance, config);\n return BTManager.instance;\n }\n\n /**\n * 扫描外设\n *\n * @param {object} options 扫描参数\n * @property {array} services 主service的uuid列表。确认在蓝牙广播中存在此服务id,可以通过服务id过滤掉其他设备\n * @property {boolean} allowDuplicatesKey 是否允许重复上报设备\n * @property {number} interval 上报新设备的间隔,默认为0\n * @property {number} timeout 扫描超时时间,毫秒。在该时间内未扫描到符合要求的设备,上报超时。默认15000ms,-1表示无限超时\n * @property {string} deviceName 通过蓝牙名称过滤,需要匹配的设备名称\n * @property {string} containName 通过蓝牙名称过滤,需要包含的设备名称\n * \n * @return Promise对象\n * \n * @discussion 开始扫描外设,注意实现返回对象的then和catch方法,监听接口是否调用成功。\n * 此操作比较耗费系统资源,请在搜索到设备后调用stopScan方法停止扫描。\n * 重复调用此接口,会清空之前设备存储,再次上报已上报的设备,能够起到刷新的作用。\n * \n * @see registerDidDiscoverDevice\n */\n scan( options = { \n services: [],\n allowDuplicatesKey: false,\n interval : 0,\n timeout: 15000,\n deviceName: '', \n containName:''\n }) \n {\n return this._bt.scanDevice(options);\n }\n\n /**\n * 停止扫描\n *\n * @return Promise对象\n * \n * @discussion 停止扫描,取消超时延时。\n */\n stopScan() {\n return this._bt.stopScan();\n }\n\n /**\n * 连接外设\n * \n * @param {object} device 指定连接的外设对象,从registerDidDiscoverDevice注册的回调中得到\n * @param {number} timeout 连接超时时间,毫秒,默认15000ms,支付宝小程序无效\n * \n * @return Promise对象\n * \n * @discussion 连接指定的外设,需要传入外设对象。\n * 注意实现返回对象的then和catch方法,监听接口是否调用成功。\n */\n connect(device , timeout) {\n if (!device) throw new Error('device is undefiend');\n return this._bt.connect(device);\n }\n\n /**\n * 断开连接\n * \n * @return Promise对象\n */\n disconnect() {\n return this._bt.disconnect();\n }\n\n /**\n * 读特征值\n * \n * @param {object} params 参数\n * @property {string} suuid 特征对应的服务uuid\n * @property {string} cuuid 特征uuid\n * \n * @return Promise对象\n * \n * @discussion 读某个服务下的某个特征值。\n */\n read(params = {\n suuid: '',\n cuuid: ''\n }) \n {\n return this._bt.read(params);\n }\n\n /**\n * 向蓝牙模块写入数据\n * \n * @param {object} params 参数\n * @property {string} suuid 特征对应的服务uuid\n * @property {string} cuuid 特征uuid\n * @property {Hex string} value 16进制字符串 \n * \n * @return Promise对象\n * \n * @discussion 向蓝牙模块写入数据。\n */\n write(params = {\n suuid: '',\n cuuid: '',\n value: ''\n })\n {\n return this._bt.write(params);\n }\n\n /**\n * 监听特征值改变\n * \n * @param {object} params 参数\n * @property {string} suuid 特征对应的服务uuid\n * @property {string} cuuid 特征uuid\n * @property {boolean} state 是否启用notify,可以通过重复调用接口改变此属性打开/关闭监听 \n * \n * @return Promise对象\n * \n * @discussion 监听某个特征值变化。\n */\n notify(params = {\n suuid: '',\n cuuid: '',\n state: true,\n }) \n {\n return this._bt.notify(params);\n }\n\n /**\n * 注册状态改变回调\n *\n * @param {function} cb 回调函数\n * \n * @discussion 连接状态发生改变时,回调此方法。\n */\n registerDidUpdateConnectStatus(cb) {\n if (typeof cb !== 'function') throw new TypeError('connectStatus callback expect function');\n this._bt.registerDidUpdateConnectStatus(cb);\n }\n\n /**\n * 注册发现外设回调\n *\n * @param {function} cb 回调函数\n * \n * @discussion 当扫描到设备时回调,或者达到超时时间回调。\n */\n registerDidDiscoverDevice(cb) {\n if (typeof cb !== 'function') throw new TypeError('discoverDevice callback expect function');\n this._bt.registerDidDiscoverDevice(cb);\n }\n\n /**\n * 注册\b特征值改变回调\n *\n * @param {function} cb 回调函数\n * \n * @discussion 当监听的特征值改变时回调,或者读特征值时回调。\n */\n registerDidUpdateValueForCharacteristic(cb) {\n if (typeof cb !== 'function') throw new TypeError('updateValueForCharacteristic callback expect function');\n this._bt.registerDidUpdateValueForCharacteristic(cb);\n }\n\n};","\n\nif (!exports.__esModule) Object.defineProperty(exports, \"__esModule\", { value: true });exports.Log = function Log() {\n\n this.log = function(identifier, msg='') {\n this.debug && console.log(`BLE:(${identifier}):` , msg);\n }\n\n this.loginfo = function(identifier,msg) {\n this.debug && console.info(`BLE:(${identifier}):` ,msg);\n }\n\n this.logwarn = function (identifier, msg) {\n this.debug && console.warn(`BLE:(${identifier}):`, msg);\n }\n\n this.logerror = function (identifier, msg) {\n this.debug && console.error(`BLE:(${identifier}):`, msg);\n }\n\n};\n","\n// 连接状态\nif (!exports.__esModule) Object.defineProperty(exports, \"__esModule\", { value: true });var ConnectStatus = exports.ConnectStatus = {\n // 未连接或连接断开,允许连接\n disconnected: 0,\n // 正在连接,不允许再连接\n connecting: 1,\n // 已连接,不允许再连接\n connected: 2,\n};\n\n// 发现外设回调和连接状态改变回调成功事件\nif (!exports.__esModule) Object.defineProperty(exports, \"__esModule\", { value: true });var SuccessCallbackEvent = exports.SuccessCallbackEvent = {\n Success_DiscoverDevice_CB_Discover: { code: 210, message: '发现外设' },\n Success_DiscoverDevice_CB_ScanDone: { code: 211, message: '扫描完成' },\n Success_ConnectStatus_CB_PowerOn: { code: 220, message: '蓝牙打开' },\n Success_ConnectStatus_CB_Connecting: { code: 221, message: '正在连接' },\n Success_ConnectStatus_CB_Connected: { code: 222, message: '连接成功' },\n Success_ConnectStatus_CB_Stop: { code: 223, message: '断开成功' },\n};\n\n// 发现外设回调和连接状态改变回调失败事件\nif (!exports.__esModule) Object.defineProperty(exports, \"__esModule\", { value: true });var ErrorCallbackEvent = exports.ErrorCallbackEvent = {\n Error_DiscoverDevice_CB_Timeout: { code: 410, message: '扫描超时' },\n Error_ConnectStatus_CB_PowerOff: { code: 420, message: '蓝牙关闭' },\n Error_ConnectStatus_CB_ConnectFail: { code: 421, message: '连接失败' },\n Error_ConnectStatus_CB_Disconnected: { code: 422, message: '连接断开' },\n};\n\n// 接口调用成功事件\nif (!exports.__esModule) Object.defineProperty(exports, \"__esModule\", { value: true });var SuccessApiThen = exports.SuccessApiThen = {\n Success_Scan: { code: 2010, message: '\b扫描接口成功调用' },\n Success_StopScan: { code: 2020, message: '\b停止扫描接口成功调用' },\n Success_Connect: { code: 2030, message: '\b连接接口成功调用' },\n Success_Disconnect: { code: 2040, message: '\b断开接口成功调用' },\n Success_Read: { code: 2050, message: '\b读特征值接口成功调用' },\n Success_Write: { code: 2060, message: '\b写入数据接口成功调用' },\n Success_Notify: { code: 2070, message: '\b监听特征值接口成功调用' },\n};\n\n// 接口调用失败事件\nif (!exports.__esModule) Object.defineProperty(exports, \"__esModule\", { value: true });var ErrorApiCatch = exports.ErrorApiCatch = {\n // 基础库版本低\n Error_Low_Version: { code: 4000, message: '当前基础库版本低,请更新微信版本' },\n // Api: scan\n Error_Scan_Failed: { code: 4010, message: '扫描错误:扫描失败' },\n Error_Scan_PowerOff: { code: 4011, message: '扫描错误:蓝牙被关闭' },\n Error_Scan_NoService: { code: 4012, message: '扫描错误:没有找到指定服务' },\n // Api: stopScan\n Error_StopScan_Failed: { code: 4020, message: '停止扫描错误:停止扫描失败' },\n Error_StopScan_PowerOff: { code: 4021, message: '停止扫描错误:蓝牙被关闭' },\n // Api: connect\n Error_Connect_Failed: { code: 4030, message: '连接错误:连接失败' },\n Error_Connect_PowerOff: { code: 4031, message: '连接错误:蓝牙被关闭' },\n Error_Connect_AlreadyConnected: { code: 4032, message: '连接错误:已经连接或正在连接' },\n Error_Connect_Timeout: { code: 4033, message: '连接错误:连接超时' },\n Error_Connect_EmptyId: { code: 4034, message: '连接错误:设备id不能为空' },\n // Api: disconnect\n Error_Disconnect_Failed: { code: 4040, message: '断开错误:断开失败' },\n // Api: read\n Error_Read_Failed: { code: 4050, message: '\b读特征值错误:读特征值失败'},\n Error_Read_NotConnected: { code: 4051, message: '读特征值错误:蓝牙未连接' },\n Error_Read_NotSupport: { code: 4052, message: '读特征值错误:当前特征不支持读操作' },\n Error_Read_NoService: { code: 4053, message: '读特征值错误:没有找到指定服务' },\n Error_Read_NoCharacteristic: { code: 4054, message: '读特征值错误:没有找到指定特征值' },\n // Api: write\n Error_Write_Failed: { code: 4060, message: '\b写入数据错误:写入数据失败'},\n Error_Write_NotConnected: { code: 4061, message: '写入数据错误:蓝牙未连接' },\n Error_Write_NotSupport: { code: 4062, message: '写入数据错误:当前特征不支持写操作' },\n Error_Write_NoService: { code: 4063, message: '写入数据错误:没有找到指定服务' },\n Error_Write_NoCharacteristic: { code: 4064, message: '写入数据错误:没有找到指定特征值' },\n // Api: notify\n Error_Notify_Failed: { code: 4070, message: '\b监听特征值错误:监听特征值错误失败' },\n Error_Notify_NotConnected: { code: 4071, message: '监听特征值错误:蓝牙未连接' },\n Error_Notify_NotSupport: { code: 4072, message: '监听特征值错误:当前特征不支持监听操作' },\n Error_Notify_NoService: { code: 4073, message: '监听特征值错误:没有找到指定服务' },\n Error_Notify_NoCharacteristic: { code: 4074, message: '监听特征值错误:没有找到指定特征值' },\n};\n\n","\n\nvar __TEMP__ = require('./enum.js');var ConnectStatus = __TEMP__['ConnectStatus'];var SuccessCallbackEvent = __TEMP__['SuccessCallbackEvent'];var ErrorCallbackEvent = __TEMP__['ErrorCallbackEvent'];var SuccessApiThen = __TEMP__['SuccessApiThen'];var ErrorApiCatch = __TEMP__['ErrorApiCatch'];\nvar __TEMP__ = require('./tools.js');var _ = __REQUIRE_DEFAULT__(__TEMP__);\n\nif (!exports.__esModule) Object.defineProperty(exports, \"__esModule\", { value: true });exports.default = class Bluetooth {\n\n constructor(btmanager) {\n this.bm = btmanager;\n // 蓝牙适配器是否初始化完成\n this.isInitializedAdapter = false;\n // 蓝牙适配器是否可用\n this.isAvailableAdapter = false;\n // 初始化蓝牙适配器\n this.openAndListenBluetoothAdapter().then(__=>__).catch(__=>__);\n // 扫描到的设备\n this.scanDevices = [];\n }\n\n /**\n * 打开和监听蓝牙适配器\n * \n * @return Promise对象\n */\n openAndListenBluetoothAdapter() {\n\n // 未初始化蓝牙适配器,打开蓝牙适配器\n if (!this.isInitializedAdapter) {\n\n // 监听蓝牙适配器状态\n _.api('offBluetoothAdapterStateChange').then(__ => __).catch(__ => __);\n _.on('onBluetoothAdapterStateChange','' ,res => {\n this.bm.log('onBluetoothAdapterStateChange', res);\n if (res.available && !this.isAvailableAdapter) {\n this.isAvailableAdapter = true;\n this.callBackConnectStatus(SuccessCallbackEvent.Success_ConnectStatus_CB_PowerOn);\n } else if (!res.available) {\n this.isAvailableAdapter = false;\n // 支付宝小程序当蓝牙适配器关闭,再次进行蓝牙操作需要重新打开,微信只需要打开一次就行\n _.getAppPlatform() === 'ant' && (this.isInitializedAdapter = false);\n this.bm.connectStatus = ConnectStatus.disconnected;\n this.callBackConnectStatus(ErrorCallbackEvent.Error_ConnectStatus_CB_PowerOff);\n }\n });\n\n // 先关闭再打开蓝牙适配器,避免出现某些机型打开无效的情况\n return _.api('closeBluetoothAdapter')\n .then(__ => {\n // 打开蓝牙适配器\n return _.api('openBluetoothAdapter')\n }).then(res => {\n this.bm.log('openBluetoothAdapter success', res);\n this.isInitializedAdapter = true;\n this.isAvailableAdapter = true;\n return Promise.resolve();\n }).catch(e => {\n this.bm.log('openBluetoothAdapter fail', e);\n this.isInitializedAdapter = false;\n this.isAvailableAdapter = false;\n this.bm.connectStatus = ConnectStatus.disconnected;\n return Promise.reject(e);\n })\n } else {\n return Promise.resolve();\n }\n }\n\n /**\n * 扫描外设\n *\n * @param {object} options 扫描参数\n * @property {array} services 主service的uuid列表。确认在蓝牙广播中存在此服务id,可以通过服务id过滤掉其他设备\n * @property {boolean} allowDuplicatesKey 是否允许重复上报设备\n * @property {number} interval 上报新设备的间隔,默认为0\n * @property {number} timeout 扫描超时时间,毫秒。在该时间内未扫描到符合要求的设备,上报超时。默认1500ms,-1表示无限超时\n * @property {string} deviceName 通过蓝牙名称过滤,需要匹配的设备名称\n * @property {string} containName 通过蓝牙名称过滤,需要包含的设备名称\n * \n * @return Promise对象\n * \n * @discussion 开始扫描外设,注意实现返回对象的then和catch方法,监听接口是否调用成功。\n * 此操作比较耗费系统资源,请在搜索到设备后调用stopScan方法停止扫描。\n * 重复调用此接口,会清空之前设备存储,再次上报已上报的设备,能够起到刷新的作用。\n * \n * @see registerDidDiscoverDevice\n */\n scanDevice(options) {\n\n // 解构参数\n let { services, allowDuplicatesKey, interval, timeout, deviceName, containName} = options;\n\n // 打开和监听蓝牙适配器\n return this.openAndListenBluetoothAdapter()\n .then(__ => {\n // 清空存储的设备\n this.scanDevices = [];\n // 销毁扫描延时\n this.destoryTimer();\n // 设置扫描超时\n this.scanTimeoutTimer = timeout!==-1 ? setTimeout(() => {\n this.stopScan();\n if (this.scanDevices.length === 0) { // 扫描超时\n this.bm.log('startBluetoothDevicesDiscovery fail ' , 'timeout');\n this.callBackDiscoverDevice(null, ErrorCallbackEvent.Error_DiscoverDevice_CB_Timeout, true);\n } else {// 扫描时间结束\n this.callBackDiscoverDevice(null, SuccessCallbackEvent.Success_DiscoverDevice_CB_ScanDone, false);\n }\n }, timeout || 15000) : null;\n // 开始扫描\n return _.api('startBluetoothDevicesDiscovery', '' ,{\n services,\n allowDuplicatesKey,\n interval\n })\n }).then(res => {\n this.bm.log('startBluetoothDevicesDiscovery success', res);\n // 取消设备监听,仅支付宝小程序有效\n _.api('offBluetoothDeviceFound').then(__ => __).catch(__ => __);\n // 监听扫描到外设\n _.on('onBluetoothDeviceFound','', res => {\n // this.bm.log('onBluetoothDeviceFound' , res);\n let devices = res.devices || res;\n // 过滤、格式化、存储、上报设备\n for (let device of devices) {\n if (Array.isArray(device)) device = devices[0][0];\n // 信号强度为127表示RSSI不可用\n if (device.RSSI === 127) return;\n // 匹配名称,过滤设备\n let name = device.name || device.deviceName;\n device.name = name;\n if (deviceName && (!name || name !== deviceName)) return;\n if (containName && (!name || !~name.indexOf(containName))) return;\n // 格式化广播数据\n if (typeof device.advertisData !== 'string') device.advertisData = _.ab2str(device.advertisData);\n // 上报设备\n this.callBackDiscoverDevice(device, SuccessCallbackEvent.Success_DiscoverDevice_CB_Discover, false);\n // 更新不重复记录设备\n for (let v of this.scanDevices) {\n if (v.deviceId === device.deviceId) {\n Object.assign(v, device);\n return;\n }\n }\n // 存储新设备\n this.scanDevices.push(device);\n }\n })\n return Promise.resolve(SuccessApiThen.Success_Scan);\n }).catch(e => {\n this.bm.log('startBluetoothDevicesDiscovery fail', e);\n if (e.code === 12 || e.code === 10001) {\n return Promise.reject(ErrorApiCatch.Error_Scan_PowerOff);\n } else if (e.code === 10012 || e.code === 10004) {\n return Promise.reject(ErrorApiCatch.Error_Scan_NoService);\n } else {\n return Promise.reject(ErrorApiCatch.Error_Scan_Failed);\n }\n })\n }\n\n /**\n * 停止扫描\n *\n * @return Promise对象\n * \n * @discussion 停止扫描,取消超时延时。\n */\n stopScan() {\n // 销毁扫描延时\n this.destoryTimer();\n // 取消设备监听,仅支付宝小程序有效\n _.api('offBluetoothDeviceFound').then(__ => __).catch(__ => __);\n // 停止扫描\n return _.api('stopBluetoothDevicesDiscovery')\n .then(res => {\n this.bm.log('stopBluetoothDevicesDiscovery success', res);\n return Promise.resolve(SuccessApiThen.Success_StopScan);\n }).catch(e => {\n this.bm.log('stopBluetoothDevicesDiscovery fail', e);\n if (e.code === 12 || e.code === 10001) {\n return Promise.reject(ErrorApiCatch.Error_StopScan_PowerOff);\n } else {\n return Promise.reject(ErrorApiCatch.Error_StopScan_Failed);\n }\n })\n }\n\n /**\n * 连接外设\n * \n * @param {object} device 指定连接的外设对象,从registerDidDiscoverDevice注册的回调中得到\n * @param {number} timeout 连接超时时间,毫秒,默认15000ms,支付宝小程序无效\n * \n * @return Promise对象\n * \n * @discussion 连接指定的外设,需要传入外设对象。\n * 注意实现返回对象的then和catch方法,监听接口是否调用成功。\n */\n connect(device , timeout) {\n // 判断是否已经连接\n if (this.bm.connectStatus !== ConnectStatus.disconnected) {\n this.bm.logwarn('connect fail', 'Is already connected');\n return Promise.reject(ErrorApiCatch.Error_Connect_AlreadyConnected);\n }\n // 判断设备id是否为空\n if (_.isEmpty(device.deviceId)) {\n return Promise.reject(ErrorApiCatch.Error_Connect_EmptyId);\n }\n\n this.bm.deviceInfo = device;\n this.bm.connectStatus = ConnectStatus.connecting;\n this.callBackConnectStatus(SuccessCallbackEvent.Success_ConnectStatus_CB_Connecting);\n let deviceId = device.deviceId;\n\n // 打开和监听蓝牙适配器\n return this.openAndListenBluetoothAdapter()\n .then(__ => {\n // 连接设备\n return _.api('createBLEConnection', 'connectBLEDevice', {\n deviceId ,\n timeout: timeout || 15000 ,\n })\n }).then( res => {\n this.bm.log('connectBLEDevice success', res);\n // 取消蓝牙连接状态监听,仅支付宝小程序有效\n _.api('offBLEConnectionStateChanged').then(__ => __).catch(__ => __);\n // 蓝牙连接状态监听\n _.on('onBLEConnectionStateChange', 'onBLEConnectionStateChanged',(res) => {\n this.bm.log('onBLEConnectionStateChange', res);\n if (!res.connected && this.bm.connectStatus !== ConnectStatus.disconnected) {\n this.bm.connectStatus = ConnectStatus.disconnected;\n if (res.errorCode === 0) {\n this.callBackConnectStatus(SuccessCallbackEvent.Success_ConnectStatus_CB_Stop);\n } else if (res.errorCode === 10003) {\n this.callBackConnectStatus(ErrorCallbackEvent.Error_ConnectStatus_CB_Disconnected);\n } else if (_.getAppPlatform() === 'ant'){\n this.callBackConnectStatus(ErrorCallbackEvent.Error_ConnectStatus_CB_Disconnected);\n }\n }\n })\n\n // 获取设备所有服务\n _.api('getBLEDeviceServices', '', { deviceId })\n .then(res => {\n this.bm.log('getBLEDeviceServices success', res);\n // 存储所有服务promise\n let sPromises = [];\n // 获取所有服务的所有特征\n device.services = res.services.map(server => {\n let sUUID = server.uuid || server.serviceId;\n let sPromise = _.api('getBLEDeviceCharacteristics', '', {\n deviceId,\n serviceId: sUUID\n })\n sPromises.push(sPromise);\n return { serviceId: sUUID };\n })\n return Promise.all(sPromises);\n }).then(res => {\n this.bm.log('getBLEDeviceCharacteristics success', res);\n device.services = res.map((v, i) => {\n let service = device.services[i];\n service.characteristics = v.characteristics;\n return service;\n })\n // 获取特征成功之后才算连接成功\n this.bm.deviceInfo = device;\n this.bm.connectStatus = ConnectStatus.connected;\n this.callBackConnectStatus(SuccessCallbackEvent.Success_ConnectStatus_CB_Connected);\n }).catch(e => {\n this.bm.log('api connecting error', e);\n // 出现错误断开蓝牙,避免出现已连接成功未找到服务或者特征出错时,再次连接状态不正确\n _.api('closeBLEConnection', 'disconnectBLEDevice', {\n deviceId: this.bm.deviceInfo.deviceId\n }).then(__ => __).catch(__ => __);\n this.bm.connectStatus = ConnectStatus.disconnected;\n this.callBackConnectStatus(e);\n })\n\n // 开始连接接口调用成功\n return Promise.resolve(SuccessApiThen.Success_Connect);\n }).catch(e => {\n this.bm.log('api connect error', e);\n // 未知错误,直接报连接失败\n if (e.code === 100000) e = ErrorCallbackEvent.Error_ConnectStatus_CB_ConnectFail;\n this.bm.connectStatus = ConnectStatus.disconnected;\n this.callBackConnectStatus(e);\n if (e.code === 12 || e.code === 10001) {\n return Promise.reject(ErrorApiCatch.Error_Connect_PowerOff);\n } else if (~e.message.indexOf('超时') || (e.errMsg && ~e.errMsg.indexOf('time out'))){\n return Promise.reject(ErrorApiCatch.Error_Connect_Timeout);\n } else {\n return Promise.reject(ErrorApiCatch.Error_Connect_Failed);\n }\n })\n }\n\n /**\n * 断开连接\n * \n * @return Promise对象\n */\n disconnect() {\n\n this.bm.connectStatus === ConnectStatus.connected && \n _.api('closeBLEConnection','disconnectBLEDevice',{\n deviceId: this.bm.deviceInfo.deviceId\n }).then(res => {\n this.bm.log('closeBLEConnection success', res);\n }).catch(e => {\n this.bm.log('closeBLEConnection fail', e);\n })\n \n _.api('closeBluetoothAdapter')\n .then(res => {\n this.bm.log('closeBluetoothAdapter success', res);\n this.isInitializedAdapter = false;\n if (this.bm.connectStatus !== ConnectStatus.disconnected) {\n this.bm.connectStatus = ConnectStatus.disconnected;\n this.callBackConnectStatus(SuccessCallbackEvent.Success_ConnectStatus_CB_Stop);\n };\n this.bm.connectStatus = ConnectStatus.disconnected;\n }).catch(e => {\n this.bm.log('closeBluetoothAdapter fail', e);\n })\n\n return Promise.resolve(SuccessApiThen.Success_Disconnect);\n }\n\n /**\n * 读特征值\n * \n * @param {object} params 参数\n * @property {string} suuid 特征对应的服务uuid\n * @property {string} cuuid 写入特征uuid\n * \n * @discussion 读某个服务下的某个特征值。\n */\n read(params) {\n if (this.bm.connectStatus === ConnectStatus.connected) {\n let { suuid, cuuid } = params;\n return _.api('readBLECharacteristicValue', '', {\n deviceId: this.bm.deviceInfo.deviceId,\n serviceId: suuid,\n characteristicId: cuuid,\n }).then(res => {\n this.bm.log('readBLECharacteristicValue success', res);\n return Promise.resolve(SuccessApiThen.Success_Read);\n }).catch(e => {\n this.bm.log('readBLECharacteristicValue fail', e);\n if (e.code === 10007) {\n return Promise.reject(ErrorApiCatch.Error_Read_NotSupport);\n } else if (e.code === 10004) {\n return Promise.reject(ErrorApiCatch.Error_Read_NoService);\n } else if (e.code === 10005) {\n return Promise.reject(ErrorApiCatch.Error_Read_NoCharacteristic);\n } else {\n return Promise.reject(ErrorApiCatch.Error_Read_Failed);\n }\n })\n } else {\n return Promise.reject(ErrorApiCatch.Error_Read_NotConnected);\n }\n }\n\n /**\n * 向蓝牙模块写入数据\n * \n * @param {object} params 参数\n * @property {string} suuid 特征对应的服务uuid\n * @property {string} cuuid 写入特征uuid\n * @property {Hex string} value 16进制字符串 \n * \n * @return Promise对象\n * \n * @discussion 向蓝牙模块写入数据。\n */\n write(params) {\n let {suuid , cuuid , value} = params;\n if (this.bm.connectStatus === ConnectStatus.connected) {\n if (_.getAppPlatform() === 'wx') {\n if (typeof value === 'string') {\n value = _.str2ab(value);\n } else {\n value = typedArrayToArrayBuffer(value);\n }\n } else if (typeof value !== 'string'){\n value = _.ab2str(value);\n }\n this.bm.log('writeCmdToDevice', _.ab2str(value));\n return _.api('writeBLECharacteristicValue', '', {\n deviceId: this.bm.deviceInfo.deviceId,\n serviceId: suuid,\n characteristicId: cuuid,\n value,\n }).then(res => {\n this.bm.log('writeBLECharacteristicValue success', res);\n return Promise.resolve(SuccessApiThen.Success_Write);\n }).catch(e => {\n this.bm.log('writeBLECharacteristicValue fail', e);\n if (e.code === 10007) {\n return Promise.reject(ErrorApiCatch.Error_Write_NotSupport);\n } else if (e.code === 10004) {\n return Promise.reject(ErrorApiCatch.Error_Write_NoService);\n } else if (e.code === 10005) {\n return Promise.reject(ErrorApiCatch.Error_Write_NoCharacteristic);\n } else {\n return Promise.reject(ErrorApiCatch.Error_Write_Failed);\n }\n })\n } else {\n return Promise.reject(ErrorApiCatch.Error_Write_NotConnected);\n }\n }\n\n /**\n * 监听特征值改变\n * \n * @param {object} params 参数\n * @property {string} suuid 特征对应的服务uuid\n * @property {string} cuuid 写入特征uuid\n * @property {boolean} state 是否启用notify,可以通过重复调用接口改变此属性打开/关闭监听 \n * \n * @return Promise对象\n * \n * @discussion 监听某个特征值变化。\n */\n notify(params) {\n if (this.bm.connectStatus === ConnectStatus.connected) {\n let { suuid, cuuid, state } = params;\n return _.api('notifyBLECharacteristicValueChange', '', {\n deviceId: this.bm.deviceInfo.deviceId,\n serviceId: suuid,\n characteristicId: cuuid,\n state\n }).then(res => {\n this.bm.log('readBLECharacteristicValue success', res);\n return Promise.resolve(SuccessApiThen.Success_Notify);\n }).catch(e => {\n this.bm.log('readBLECharacteristicValue fail', e);\n if (e.code === 10007) {\n return Promise.reject(ErrorApiCatch.Error_Notify_NotSupport);\n } else if (e.code === 10004) {\n return Promise.reject(ErrorApiCatch.Error_Notify_NoService);\n } else if (e.code === 10005) {\n return Promise.reject(ErrorApiCatch.Error_Notify_NoCharacteristic);\n } else {\n return Promise.reject(ErrorApiCatch.Error_Notify_Failed);\n }\n })\n } else {\n return Promise.reject(ErrorApiCatch.Error_Notify_NotConnected);\n }\n }\n\n /**\n * 注册状态改变回调\n *\n * @param {function} cb 回调函数\n * \n * @discussion 连接状态发生改变时,回调此方法。\n */\n registerDidUpdateConnectStatus(cb) {\n this._didUpdateStatusCB = cb;\n }\n\n /**\n * 注册发现外设回调\n *\n * @param {function} cb 回调函数\n * \n * @discussion 当扫描到设备时回调\n */\n registerDidDiscoverDevice(cb) {\n this._didDiscoverDeviceCB = cb;\n }\n\n /**\n * 注册\b特征值改变回调\n *\n * @param {function} cb 回调函数\n * \n * @discussion 当监听的特征值改变时回调\n */\n registerDidUpdateValueForCharacteristic(cb) {\n this._didUpdateValueCB = cb;\n _.api('offBLECharacteristicValueChange').then(__ => __).catch(__ => __);\n _.on('onBLECharacteristicValueChange', '' ,characteristic => {\n if (typeof characteristic.value === 'string') {\n this._didUpdateValueCB(characteristic);\n } else {\n characteristic.value = _.ab2str(characteristic.value);\n this._didUpdateValueCB(characteristic);\n }\n })\n }\n\n /**\n * 回调蓝牙连接状态\n */\n callBackConnectStatus(status) {\n this._didUpdateStatusCB && this._didUpdateStatusCB({\n ...status,\n device:this.bm.deviceInfo,\n connectStatus:this.bm.connectStatus\n });\n }\n\n /**\n * 回调发现外设\n * \n * @param device 扫描到的设备\n */\n callBackDiscoverDevice(device , event , timeout) {\n this._didDiscoverDeviceCB && this._didDiscoverDeviceCB(\n device ? {\n ...event,\n timeout,\n device,\n } : {\n ...event,\n timeout,\n }\n )\n }\n\n /**\n * 销毁延时\n */\n destoryTimer() {\n this.scanTimeoutTimer && clearTimeout(this.scanTimeoutTimer);\n this.scanTimeoutTimer = null;\n }\n};","\n\n\nvar __TEMP__ = require('./promisify.js');var getAppPlatform = __TEMP__['getAppPlatform'];var api = __TEMP__['api'];var on = __TEMP__['on'];\n\n/**\n * 判断字符串是否为空或者空格\n */\nfunction isEmpty(str = '') {\n return !str || str == '' || str.replace(/(^\\s*)|(\\s*$)/g, \"\") == \"\";\n}\n\n/**\n * 判断是否为null或者未定义\n */\nfunction isNullOrUndefined(obj) {\n return obj === undefined || obj === null;\n}\n\n/**\n * ArrayBuffer类型转换为16进制字符串\n */\nfunction ab2str(buffer) {\n return Array.prototype.map.call(new Uint8Array(buffer), x => ('00' + x.toString(16)).slice(-2)).join('');\n}\n\n/**\n * 字符串转为ArrayBuffer对象,参数为字符串\n */\nfunction str2ab(str) {\n var buf = new ArrayBuffer(str.length / 2);\n var bufView = new Uint8Array(buf);\n for (var i = 0; i < str.length; i += 2) {\n bufView[parseInt(i / 2)] = char2Hex(str.charCodeAt(i)) << 4 | char2Hex(str.charCodeAt(i + 1));\n }\n return buf;\n}\n\n/**\n * 字符转十六进制\n */\nfunction char2Hex(bChar) {\n if ((bChar >= 0x30) && (bChar <= 0x39)) { // 数字\n bChar -= 0x30;\n } else if ((bChar >= 0x41) && (bChar <= 0x46)) { // 大写字母\n bChar -= 0x37;\n } else if ((bChar >= 0x61) && (bChar <= 0x66)) { // 小写字母\n bChar -= 0x57;\n } else {\n bChar = 0xff;\n }\n return bChar;\n}\n\n/**\n * TypedArray转为ArrayBuffer\n */\nfunction typedArray2ArrayBuffer(pbuff) {\n let buffer = new ArrayBuffer(pbuff.byteLength)\n let uInit8 = new Uint8Array(buffer)\n uInit8.set(pbuff);\n return buffer;\n}\n\n/**\n * 获取异或校验数值\n *\n * @param p 参与运算的字符数组指针\n * @param len 参与运算的字符数组长度\n */\nfunction createXOR(b, p, len) {\n let i = 0;\n let ckc = 0;\n for (; i < len; i++) {\n ckc = ckc ^ b[p + i];\n }\n return ckc;\n}\n\n/**\n * UUID128位转换为16位\n * \n * @param uuid128 128位的uuid\n */\nfunction uuid128to16(uuid128) {\n let arr = uuid128.split('-');\n if (arr.length === 5 && arr[4] === '00805F9B34FB') {\n return arr[0].slice(3);\n } else {\n return uuid128;\n }\n}\n\nif (!exports.__esModule) Object.defineProperty(exports, \"__esModule\", { value: true });exports.default = {\n getAppPlatform,\n api,\n on,\n isEmpty,\n isNullOrUndefined,\n ab2str,\n str2ab,\n typedArray2ArrayBuffer,\n createXOR,\n uuid128to16\n};\n\n\n","\n// 判断是微信小程序还是支付宝小程序 \nlet ii, mini;\ntry {\n ii = wx;\n mini = 'wx';\n} catch (e) {\n ii = my;\n mini = 'ant';\n}\n\nvar __TEMP__ = require('./enum.js');var ErrorApiCatch = __TEMP__['ErrorApiCatch'];\n\n// 微信系统错误\nconst ERROR_WX = {\n NOT_INIT: { code: 10000, message: '未初始化蓝牙适配器' },\n NOT_AVAILABLE: { code: 10001, message: '当前蓝牙适配器不可用' },\n NO_DEVICE: { code: 10002, message: '没有找到指定设备' },\n CONNECTION_FAIL: { code: 10003, message: '连接失败' },\n NO_SERVICE: { code: 10004, message: '没有找到指定服务' },\n NO_CHARACTERISTIC: { code: 10005, message: '没有找到指定特征值' },\n NO_CONNECTION: { code: 10006, message: '当前连接已断开' },\n PROPERTY_NOT_SUPPORT: { code: 10007, message: '当前特征值不支持此操作' },\n SYSTEM_ERROR: { code: 10008, message: '系统异常' },\n SYSTEM_NOT_SUPPORT: { code: 10009, message: 'Android 系统版本低于 4.3 不支持 BLE' },\n OPERATE_TIMEOUT: { code: 10012, message: '操作超时' },\n INVALID_PARAMETER: { code: 10013, message: '无效参数' },\n ALREADY_CONNECTED: { code: -1 , message: '蓝牙已连接,不能再连接' },\n UN_KNOWN: { code: 100000,message: '未知' },\n}\n\n// 支付宝系统错误\nconst ERROR_ANT = {\n POWER_OFF: { code: 12, message: '蓝牙未打开' },\n LOST_SERVICE: { code: 13, message: '与系统服务的链接暂时丢失' },\n UNAUTH_BLE: { code: 14, message: '未授权支付宝使用蓝牙功能' },\n UNKNOWN_ERROR: { code: 15, message: '未知错误' },\n NOT_INIT: { code: 10000, message: '未初始化蓝牙适配器' },\n NOT_AVAILABLE: { code: 10001, message: '当前蓝牙适配器不可用' },\n NO_DEVICE: { code: 10002, message: '没有找到指定设备' },\n CONNECTION_FAIL: { code: 10003, message: '连接失败' },\n NO_SERVICE: { code: 10004, message: '没有找到指定服务' },\n NO_CHARACTERISTIC: { code: 10005, message: '没有找到指定特征值' },\n NO_CONNECTION: { code: 10006, message: '当前连接已断开' },\n PROPERTY_NOT_SUPPORT: { code: 10007, message: '当前特征值不支持此操作' },\n SYSTEM_ERROR: { code: 10008, message: '系统异常' },\n SYSTEM_NOT_SUPPORT: { code: 10009, message: 'Android 系统版本低于 4.3 不支持 BLE' },\n SYMBOL_UNFOUND: { code: 10010, message: '没有找到指定描述符' },\n DEVICE_ID_INVALID: { code: 10011, message: '设备 id 不可用/为空' },\n SERVICE_ID_INVALID: { code: 10012, message: '服务 id 不可用/为空' },\n CHARACTERISTIC_ID_INVALID: { code: 10013, message: '特征 id 不可用/为空' },\n CMD_FORMAT_ERROR: { code: 10014, message: '发送的数据为空或格式错误' },\n OPERATION_TIMEOUT: { code: 10015, message: '操作超时' },\n LACK_PARAMETER: { code: 10016, message: '缺少参数' },\n WRITE_ERROR: { code: 10017, message: '写入特征值失败' },\n READ_ERROR: { code: 10018, message: '读取特征值失败' },\n}\n\nconst ERROR_TYPES = mini==='wx' ? ERROR_WX : ERROR_ANT;\n\n/**\n * 获取错误类型\n */\nfunction getErrorType(code) {\n for (let key of Object.keys(ERROR_TYPES)) {\n if (code === ERROR_TYPES[key].code) {\n return ERROR_TYPES[key];\n }\n }\n return ERROR_TYPES.UN_KNOWN;\n}\n\n/**\n * 获取小程序平台 wx||ant\n */\nif (!exports.__esModule) Object.defineProperty(exports, \"__esModule\", { value: true });exports.getAppPlatform = function getAppPlatform() {\n return mini;\n};\n\n/**\n * 重写异步API\n * \n * @param {string} fn1 方法名1\n * @param {string} fn2 方法名2\n * @param {object} options 可选参数\n * \n * @return Promise对象\n */\nif (!exports.__esModule) Object.defineProperty(exports, \"__esModule\", { value: true });exports.api = function api(fn1, fn2, options) {\n let func = ii[fn1] || ii[fn2];\n return new Promise((resolve, reject) => {\n let params = {\n success(res) {\n resolve(res);\n },\n fail(res) {\n if (res.errorMessage) {\n reject({\n code: res.error,\n message: res.errorMessage\n });\n } else {\n reject({\n ...getErrorType(res.errCode),\n errMsg: res.errMsg\n });\n }\n },\n complete(res) {\n //...\n }\n }\n if (options) {\n params = Object.assign(params, options);\n }\n if (func) {\n func(params);\n } else {\n reject(ErrorApiCatch.Error_Low_Version);\n }\n })\n};\n\n/**\n * 重写回调API\n * \n * @param {string} fn1 方法名1\n * @param {string} fn2 方法名2\n * @param {function} cb 回调函数\n */\nif (!exports.__esModule) Object.defineProperty(exports, \"__esModule\", { value: true });exports.on = function on(fn1, fn2, cb) {\n let func = ii[fn1] || ii[fn2];\n if (func) {\n if (mini === 'wx') {\n func(cb);\n } else {\n func({\n success: cb,\n })\n }\n }\n};"]} -------------------------------------------------------------------------------- /example/wxdemo/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wxdemo", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "wx-ant-ble": { 8 | "version": "1.0.9", 9 | "resolved": "https://registry.npmjs.org/wx-ant-ble/-/wx-ant-ble-1.0.9.tgz", 10 | "integrity": "sha512-R4WQMly8uuRjeklGfK1QE1nfTJU+DOD1UC3yfrHLY1c4f1P5qrBW470j0Accv7v0QT/ZmYeS5zABCsscy2JiNA==" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/wxdemo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wxdemo", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "dependencies": { 7 | "wx-ant-ble": "^1.0.9" 8 | }, 9 | "devDependencies": {}, 10 | "scripts": { 11 | "test": "echo \"Error: no test specified\" && exit 1" 12 | }, 13 | "author": "", 14 | "license": "ISC" 15 | } 16 | -------------------------------------------------------------------------------- /example/wxdemo/pages/home/home.js: -------------------------------------------------------------------------------- 1 | // pages/home/home.js 2 | 3 | import { BTManager, ConnectStatus } from 'wx-ant-ble'; 4 | 5 | Page({ 6 | 7 | data: { 8 | // 蓝牙是否连接 9 | connected: false, 10 | // 成功连接的设备 11 | device: {}, 12 | // 扫描到的设备 13 | devices:[], 14 | // 设备能够notify的特征 15 | notifyUUIDs: [], 16 | // 设备能够read的特征 17 | readUUIDs: [], 18 | // 设备能够write的特征 19 | writeUUIDs: [], 20 | }, 21 | 22 | onLoad: function (options) { 23 | // 初始化蓝牙管理器 24 | this.bt = new BTManager({ 25 | debug: true 26 | }); 27 | // 注册状态回调 28 | this.bt.registerDidUpdateConnectStatus(this.didUpdateConnectStatus.bind(this)); 29 | // 注册发现外设回调 30 | this.bt.registerDidDiscoverDevice(this.didDiscoverDevice.bind(this)); 31 | // 注册特征值改变回调 32 | this.bt.registerDidUpdateValueForCharacteristic(this.didUpdateValueForCharacteristic.bind(this)); 33 | }, 34 | 35 | // 状态改变回调 36 | didUpdateConnectStatus(res) { 37 | console.log('home registerDidUpdateConnectStatus', res); 38 | if (res.connectStatus === ConnectStatus.connected) { 39 | wx.hideLoading(); 40 | this.setData({connected: true , device:res.device}); 41 | this.parseDeviceUUIDs(res.device); 42 | } else if (res.connectStatus === ConnectStatus.disconnected) { 43 | wx.hideLoading(); 44 | wx.showToast({ 45 | title: res.message, 46 | icon: 'none' 47 | }) 48 | this.setData({ connected: false, notifyUUIDs: [], readUUIDs: [], writeUUIDs:[]}); 49 | } 50 | }, 51 | 52 | // 发现外设回调 53 | didDiscoverDevice(res) { 54 | console.log('home didDiscoverDevice', res); 55 | if (res.timeout) { 56 | console.log('home didDiscoverDevice', '扫描超时'); 57 | wx.showToast({ 58 | title: res.message, 59 | icon: 'none' 60 | }) 61 | } else { 62 | let device = res.device; 63 | let devices = this.data.devices; 64 | function checkDevice(d, ds) { 65 | for (let v of ds) { 66 | if (v.deviceId === d.deviceId) { 67 | return true; 68 | } 69 | } 70 | return false; 71 | } 72 | if (!checkDevice(device, devices)) { 73 | devices.push(device); 74 | } 75 | this.setData({ devices }); 76 | } 77 | }, 78 | 79 | // 特征值改变回调 80 | didUpdateValueForCharacteristic(res) { 81 | console.log('home registerDidUpdateValueForCharacteristic', res); 82 | }, 83 | 84 | parseDeviceUUIDs(device) { 85 | let { notifyUUIDs, readUUIDs, writeUUIDs } = this.data; 86 | for (let service of device.services) { 87 | for (let char of service.characteristics) { 88 | if (char.properties.notify) { 89 | notifyUUIDs.push({ 90 | suuid: service.serviceId, 91 | cuuid: char.uuid, 92 | listening: false 93 | }) 94 | } 95 | if (char.properties.read) { 96 | readUUIDs.push({ 97 | suuid: service.serviceId, 98 | cuuid: char.uuid, 99 | }) 100 | } 101 | if (char.properties.write) { 102 | writeUUIDs.push({ 103 | suuid: service.serviceId, 104 | cuuid: char.uuid, 105 | }) 106 | } 107 | } 108 | } 109 | this.setData({ notifyUUIDs, readUUIDs, writeUUIDs }); 110 | }, 111 | 112 | // 扫描 113 | _scan() { 114 | this.bt.scan({ 115 | services: [], 116 | allowDuplicatesKey: false, 117 | interval: 0, 118 | timeout: 15000, 119 | deviceName: '', 120 | containName: '' 121 | }).then(res => { 122 | console.log('home scan success', res); 123 | }).catch(e => { 124 | console.log('home scan fail', e); 125 | wx.showToast({ 126 | title: e.message, 127 | icon: 'none' 128 | }); 129 | }); 130 | }, 131 | 132 | // 停止扫描 133 | _stopScan() { 134 | this.bt.stopScan().then(res => { 135 | console.log('home stopScan success', res); 136 | }).catch(e => { 137 | console.log('home stopScan fail', e); 138 | }) 139 | }, 140 | 141 | // 连接 142 | _connect(e) { 143 | let index = e.currentTarget.id; 144 | this.bt.stopScan(); 145 | let device = this.data.devices[index]; 146 | this.bt.connect(device).then(res => { 147 | console.log('home connect success', res); 148 | }).catch(e => { 149 | wx.showToast({ 150 | title: e.message, 151 | icon: 'none' 152 | }); 153 | console.log('home connect fail', e); 154 | }); 155 | wx.showLoading({ 156 | title: '连接' + device.name, 157 | }); 158 | }, 159 | 160 | // 断开连接 161 | _disconnect() { 162 | this.bt.disconnect().then(res => { 163 | console.log('home disconnect success', res); 164 | }).catch(e => { 165 | console.log('home disconnect fail', e); 166 | }) 167 | }, 168 | 169 | // 监听/停止监听 170 | _notify(e) { 171 | let index = e.currentTarget.id; 172 | let { suuid, cuuid, listening } = this.data.notifyUUIDs[index]; 173 | this.bt.notify({ 174 | suuid, cuuid, state: !listening 175 | }).then(res => { 176 | console.log('home notify success', res); 177 | this.setData({ [`notifyUUIDs[${index}].listening`]: !listening }); 178 | }).catch(e => { 179 | console.log('home notify fail', e); 180 | }) 181 | }, 182 | 183 | // 读特征值 184 | _read(e) { 185 | let index = e.currentTarget.id; 186 | let { suuid, cuuid } = this.data.readUUIDs[index]; 187 | this.bt.read({ 188 | suuid, cuuid 189 | }).then(res => { 190 | console.log('home read success', res); 191 | }).catch(e => { 192 | console.log('home read fail', e); 193 | }) 194 | }, 195 | 196 | // 向蓝牙模块写入数据,这里只做简单的例子,发送的是 'FFFF' 的十六进制字符串 197 | _write(e) { 198 | let index = e.currentTarget.id; 199 | let { suuid, cuuid } = this.data.writeUUIDs[index]; 200 | this.bt.write({ 201 | suuid, 202 | cuuid, 203 | value: 'FFFF' 204 | }).then(res => { 205 | console.log('home write success', res); 206 | }).catch(e => { 207 | console.log('home write fail', e); 208 | }) 209 | }, 210 | 211 | }) -------------------------------------------------------------------------------- /example/wxdemo/pages/home/home.json: -------------------------------------------------------------------------------- 1 | { 2 | "disableScroll": true, 3 | "usingComponents": {} 4 | } -------------------------------------------------------------------------------- /example/wxdemo/pages/home/home.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 扫描 6 | 停止扫描 7 | 断开蓝牙 8 | 9 | 10 | {{connected?("Connected "+device.name):"Disconnected"}} 11 | 12 | Peripherals 13 | 14 | 15 | 16 | {{item.name}} 17 | 18 | 19 | 20 | 21 | 22 | 23 | Notify Characteristic 24 | 25 | 26 | 27 | {{item.cuuid}} 28 | {{item.listening?"Stop":"Notify"}} 29 | 30 | 31 | 32 | 33 | 34 | Read Characteristic 35 | 36 | 37 | 38 | {{item.cuuid}} 39 | Read 40 | 41 | 42 | 43 | 44 | 45 | Write Characteristic 46 | 47 | 48 | 49 | {{item.cuuid}} 50 | Write 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /example/wxdemo/pages/home/home.wxss: -------------------------------------------------------------------------------- 1 | /* pages/home/home.wxss */ 2 | 3 | .page { 4 | width: 100%; 5 | height: 100%; 6 | background: #eee; 7 | } 8 | 9 | .btn-view { 10 | margin-top: 30rpx; 11 | height: 100rpx; 12 | width: 95%; 13 | justify-content: space-around; 14 | } 15 | 16 | .btn-view .btn { 17 | width: 25%; 18 | height: 80%; 19 | background: #1E90FF; 20 | color: white; 21 | border-radius: 10rpx; 22 | } 23 | 24 | .status-view { 25 | height: 50rpx; 26 | width: 100%; 27 | margin-left: 50rpx; 28 | } 29 | 30 | .title-view { 31 | margin-top: 30rpx; 32 | height: 50rpx; 33 | width: 100%; 34 | margin-left: 50rpx; 35 | font-size: 40rpx; 36 | font-weight: 400; 37 | line-height: 40rpx; 38 | } 39 | 40 | scroll-view { 41 | height: 240rpx; 42 | width: 100%; 43 | background: white; 44 | } 45 | 46 | .cell { 47 | height: 80rpx; 48 | width: 100%; 49 | justify-content: space-between; 50 | } 51 | 52 | .cell .name{ 53 | font-size: 40rpx; 54 | font-weight: 300; 55 | margin-left: 30rpx; 56 | } 57 | 58 | .cell image { 59 | margin-right: 20rpx; 60 | height: 30rpx; 61 | width: 30rpx; 62 | } 63 | 64 | .cell .uuid { 65 | font-size: 27rpx; 66 | margin-left: 30rpx; 67 | font-weight: 300; 68 | } 69 | 70 | .cell .btn { 71 | width: 100rpx; 72 | color: #1E90FF; 73 | font-size: 30rpx; 74 | } 75 | 76 | scroll-view .line { 77 | height: 2rpx; 78 | width: 100%; 79 | background:#e0e0e0; 80 | } -------------------------------------------------------------------------------- /example/wxdemo/pages/index/index.js: -------------------------------------------------------------------------------- 1 | //index.js 2 | //获取应用实例 3 | const app = getApp() 4 | 5 | Page({ 6 | data: { 7 | motto: 'Hello World', 8 | userInfo: {}, 9 | hasUserInfo: false, 10 | canIUse: wx.canIUse('button.open-type.getUserInfo') 11 | }, 12 | //事件处理函数 13 | bindViewTap: function() { 14 | wx.navigateTo({ 15 | url: '../logs/logs' 16 | }) 17 | }, 18 | onLoad: function () { 19 | if (app.globalData.userInfo) { 20 | this.setData({ 21 | userInfo: app.globalData.userInfo, 22 | hasUserInfo: true 23 | }) 24 | } else if (this.data.canIUse){ 25 | // 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回 26 | // 所以此处加入 callback 以防止这种情况 27 | app.userInfoReadyCallback = res => { 28 | this.setData({ 29 | userInfo: res.userInfo, 30 | hasUserInfo: true 31 | }) 32 | } 33 | } else { 34 | // 在没有 open-type=getUserInfo 版本的兼容处理 35 | wx.getUserInfo({ 36 | success: res => { 37 | app.globalData.userInfo = res.userInfo 38 | this.setData({ 39 | userInfo: res.userInfo, 40 | hasUserInfo: true 41 | }) 42 | } 43 | }) 44 | } 45 | }, 46 | getUserInfo: function(e) { 47 | console.log(e) 48 | app.globalData.userInfo = e.detail.userInfo 49 | this.setData({ 50 | userInfo: e.detail.userInfo, 51 | hasUserInfo: true 52 | }) 53 | } 54 | }) 55 | -------------------------------------------------------------------------------- /example/wxdemo/pages/index/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": {} 3 | } -------------------------------------------------------------------------------- /example/wxdemo/pages/index/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {{userInfo.nickName}} 8 | 9 | 10 | 11 | {{motto}} 12 | 13 | 14 | -------------------------------------------------------------------------------- /example/wxdemo/pages/index/index.wxss: -------------------------------------------------------------------------------- 1 | /**index.wxss**/ 2 | .userinfo { 3 | display: flex; 4 | flex-direction: column; 5 | align-items: center; 6 | } 7 | 8 | .userinfo-avatar { 9 | width: 128rpx; 10 | height: 128rpx; 11 | margin: 20rpx; 12 | border-radius: 50%; 13 | } 14 | 15 | .userinfo-nickname { 16 | color: #aaa; 17 | } 18 | 19 | .usermotto { 20 | margin-top: 200px; 21 | } -------------------------------------------------------------------------------- /example/wxdemo/project.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "项目配置文件", 3 | "packOptions": { 4 | "ignore": [] 5 | }, 6 | "setting": { 7 | "urlCheck": true, 8 | "es6": true, 9 | "postcss": true, 10 | "minified": true, 11 | "newFeature": true, 12 | "autoAudits": false, 13 | "nodeModules": true 14 | }, 15 | "compileType": "miniprogram", 16 | "libVersion": "2.4.3", 17 | "appid": "wx3b0039a76f9c0d03", 18 | "projectname": "wxdemo", 19 | "debugOptions": { 20 | "hidedInDevtools": [] 21 | }, 22 | "isGameTourist": false, 23 | "condition": { 24 | "search": { 25 | "current": -1, 26 | "list": [] 27 | }, 28 | "conversation": { 29 | "current": -1, 30 | "list": [] 31 | }, 32 | "game": { 33 | "currentL": -1, 34 | "list": [] 35 | }, 36 | "miniprogram": { 37 | "current": -1, 38 | "list": [] 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /example/wxdemo/utils/util.js: -------------------------------------------------------------------------------- 1 | const formatTime = date => { 2 | const year = date.getFullYear() 3 | const month = date.getMonth() + 1 4 | const day = date.getDate() 5 | const hour = date.getHours() 6 | const minute = date.getMinutes() 7 | const second = date.getSeconds() 8 | 9 | return [year, month, day].map(formatNumber).join('/') + ' ' + [hour, minute, second].map(formatNumber).join(':') 10 | } 11 | 12 | const formatNumber = n => { 13 | n = n.toString() 14 | return n[1] ? n : '0' + n 15 | } 16 | 17 | module.exports = { 18 | formatTime: formatTime 19 | } 20 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 2 | import { BTManager } from './src/btmanager.js'; 3 | import { ConnectStatus, SuccessCallbackEvent, ErrorCallbackEvent, SuccessApiThen, ErrorApiCatch } from './src/enum.js'; 4 | const Version = '1.1.1'; 5 | 6 | export { 7 | Version, 8 | BTManager, 9 | ConnectStatus, 10 | SuccessCallbackEvent, 11 | ErrorCallbackEvent, 12 | SuccessApiThen, 13 | ErrorApiCatch 14 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wx-ant-ble", 3 | "version": "1.1.1", 4 | "description": "微信、支付宝小程序BLE蓝牙SDK", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "zhaodahai", 10 | "license": "ISC", 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/zhaodahai/wx-ant-ble.git" 14 | }, 15 | "keywords": [ 16 | "ble", "bluetooth", "蓝牙","小程序" 17 | ], 18 | "bugs": { 19 | "url": "https://github.com/zhaodahai/wx-ant-ble/issues" 20 | }, 21 | "homepage": "https://github.com/zhaodahai/wx-ant-ble#readme" 22 | } 23 | -------------------------------------------------------------------------------- /resource/QRcode.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaodahai/wx-ant-ble/c689614f401ca2c52a1f5f34a036be2f2c3088ec/resource/QRcode.jpg -------------------------------------------------------------------------------- /resource/powerpoint.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaodahai/wx-ant-ble/c689614f401ca2c52a1f5f34a036be2f2c3088ec/resource/powerpoint.gif -------------------------------------------------------------------------------- /src/bluetooth.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | import { ConnectStatus, SuccessCallbackEvent, ErrorCallbackEvent, SuccessApiThen, ErrorApiCatch} from './enum.js'; 4 | import _ from './tools.js'; 5 | 6 | export default class Bluetooth { 7 | 8 | constructor(btmanager) { 9 | this.bm = btmanager; 10 | // 蓝牙适配器是否初始化完成 11 | this.isInitializedAdapter = false; 12 | // 蓝牙适配器是否可用 13 | this.isAvailableAdapter = false; 14 | // 初始化蓝牙适配器 15 | this.openAndListenBluetoothAdapter().then(__=>__).catch(__=>__); 16 | // 扫描到的设备 17 | this.scanDevices = []; 18 | } 19 | 20 | /** 21 | * 打开和监听蓝牙适配器 22 | * 23 | * @return Promise对象 24 | */ 25 | openAndListenBluetoothAdapter() { 26 | 27 | // 未初始化蓝牙适配器,打开蓝牙适配器 28 | if (!this.isInitializedAdapter) { 29 | 30 | // 监听蓝牙适配器状态 31 | _.api('offBluetoothAdapterStateChange').then(__ => __).catch(__ => __); 32 | _.on('onBluetoothAdapterStateChange','' ,res => { 33 | this.bm.log('onBluetoothAdapterStateChange', res); 34 | if (res.available && !this.isAvailableAdapter) { 35 | this.isAvailableAdapter = true; 36 | this.callBackConnectStatus(SuccessCallbackEvent.Success_ConnectStatus_CB_PowerOn); 37 | } else if (!res.available) { 38 | this.isAvailableAdapter = false; 39 | // 支付宝小程序当蓝牙适配器关闭,再次进行蓝牙操作需要重新打开,微信只需要打开一次就行 40 | _.getAppPlatform() === 'ant' && (this.isInitializedAdapter = false); 41 | this.bm.connectStatus = ConnectStatus.disconnected; 42 | this.callBackConnectStatus(ErrorCallbackEvent.Error_ConnectStatus_CB_PowerOff); 43 | } 44 | }); 45 | 46 | // 先关闭再打开蓝牙适配器,避免出现某些机型打开无效的情况 47 | return _.api('closeBluetoothAdapter') 48 | .then(__ => { 49 | // 打开蓝牙适配器 50 | return _.api('openBluetoothAdapter') 51 | }).then(res => { 52 | this.bm.log('openBluetoothAdapter success', res); 53 | this.isInitializedAdapter = true; 54 | this.isAvailableAdapter = true; 55 | return Promise.resolve(); 56 | }).catch(e => { 57 | this.bm.log('openBluetoothAdapter fail', e); 58 | this.isInitializedAdapter = false; 59 | this.isAvailableAdapter = false; 60 | this.bm.connectStatus = ConnectStatus.disconnected; 61 | return Promise.reject(e); 62 | }) 63 | } else { 64 | return Promise.resolve(); 65 | } 66 | } 67 | 68 | /** 69 | * 扫描外设 70 | * 71 | * @param {object} options 扫描参数 72 | * @property {array} services 主service的uuid列表。确认在蓝牙广播中存在此服务id,可以通过服务id过滤掉其他设备 73 | * @property {boolean} allowDuplicatesKey 是否允许重复上报设备 74 | * @property {number} interval 上报新设备的间隔,默认为0 75 | * @property {number} timeout 扫描超时时间,毫秒。在该时间内未扫描到符合要求的设备,上报超时。默认1500ms,-1表示无限超时 76 | * @property {string} deviceName 通过蓝牙名称过滤,需要匹配的设备名称 77 | * @property {string} containName 通过蓝牙名称过滤,需要包含的设备名称 78 | * 79 | * @return Promise对象 80 | * 81 | * @discussion 开始扫描外设,注意实现返回对象的then和catch方法,监听接口是否调用成功。 82 | * 此操作比较耗费系统资源,请在搜索到设备后调用stopScan方法停止扫描。 83 | * 重复调用此接口,会清空之前设备存储,再次上报已上报的设备,能够起到刷新的作用。 84 | * 85 | * @see registerDidDiscoverDevice 86 | */ 87 | scanDevice(options) { 88 | 89 | // 解构参数 90 | let { services, allowDuplicatesKey, interval, timeout, deviceName, containName} = options; 91 | 92 | // 打开和监听蓝牙适配器 93 | return this.openAndListenBluetoothAdapter() 94 | .then(__ => { 95 | // 清空存储的设备 96 | this.scanDevices = []; 97 | // 销毁扫描延时 98 | this.destoryTimer(); 99 | // 设置扫描超时 100 | this.scanTimeoutTimer = timeout!==-1 ? setTimeout(__ => { 101 | this.stopScan(); 102 | if (this.scanDevices.length === 0) { // 扫描超时 103 | this.bm.log('startBluetoothDevicesDiscovery fail ' , 'timeout'); 104 | this.callBackDiscoverDevice(null, ErrorCallbackEvent.Error_DiscoverDevice_CB_Timeout, true); 105 | } else {// 扫描时间结束 106 | this.callBackDiscoverDevice(null, SuccessCallbackEvent.Success_DiscoverDevice_CB_ScanDone, false); 107 | } 108 | }, timeout || 15000) : null; 109 | // 开始扫描 110 | return _.api('startBluetoothDevicesDiscovery', '' ,{ 111 | services, 112 | allowDuplicatesKey, 113 | interval 114 | }) 115 | }).then(res => { 116 | this.bm.log('startBluetoothDevicesDiscovery success', res); 117 | // 取消设备监听,仅支付宝小程序有效 118 | _.api('offBluetoothDeviceFound').then(__ => __).catch(__ => __); 119 | // 监听扫描到外设 120 | _.on('onBluetoothDeviceFound','', res => { 121 | // this.bm.log('onBluetoothDeviceFound' , res); 122 | let devices = res.devices || res; 123 | // 过滤、格式化、存储、上报设备 124 | for (let device of devices) { 125 | if (Array.isArray(device)) device = devices[0][0]; 126 | // 信号强度为127表示RSSI不可用 127 | if (device.RSSI === 127) return; 128 | // 匹配名称,过滤设备 129 | let name = device.name || device.deviceName; 130 | device.name = name; 131 | if (deviceName && (!name || name !== deviceName)) return; 132 | if (containName && (!name || !~name.indexOf(containName))) return; 133 | // 格式化广播数据 134 | if (typeof device.advertisData !== 'string') device.advertisData = _.ab2str(device.advertisData); 135 | // 上报设备 136 | this.callBackDiscoverDevice(device, SuccessCallbackEvent.Success_DiscoverDevice_CB_Discover, false); 137 | // 更新不重复记录设备 138 | for (let v of this.scanDevices) { 139 | if (v.deviceId === device.deviceId) { 140 | Object.assign(v, device); 141 | return; 142 | } 143 | } 144 | // 存储新设备 145 | this.scanDevices.push(device); 146 | } 147 | }) 148 | return Promise.resolve(SuccessApiThen.Success_Scan); 149 | }).catch(e => { 150 | this.bm.log('startBluetoothDevicesDiscovery fail', e); 151 | if (e.code === 12 || e.code === 10001) { 152 | return Promise.reject(ErrorApiCatch.Error_Scan_PowerOff); 153 | } else if (e.code === 10012 || e.code === 10004) { 154 | return Promise.reject(ErrorApiCatch.Error_Scan_NoService); 155 | } else { 156 | return Promise.reject(ErrorApiCatch.Error_Scan_Failed); 157 | } 158 | }) 159 | } 160 | 161 | /** 162 | * 停止扫描 163 | * 164 | * @return Promise对象 165 | * 166 | * @discussion 停止扫描,取消超时延时。 167 | */ 168 | stopScan() { 169 | // 销毁扫描延时 170 | this.destoryTimer(); 171 | // 取消设备监听,仅支付宝小程序有效 172 | _.api('offBluetoothDeviceFound').then(__ => __).catch(__ => __); 173 | // 停止扫描 174 | return _.api('stopBluetoothDevicesDiscovery') 175 | .then(res => { 176 | this.bm.log('stopBluetoothDevicesDiscovery success', res); 177 | return Promise.resolve(SuccessApiThen.Success_StopScan); 178 | }).catch(e => { 179 | this.bm.log('stopBluetoothDevicesDiscovery fail', e); 180 | if (e.code === 12 || e.code === 10001) { 181 | return Promise.reject(ErrorApiCatch.Error_StopScan_PowerOff); 182 | } else { 183 | return Promise.reject(ErrorApiCatch.Error_StopScan_Failed); 184 | } 185 | }) 186 | } 187 | 188 | /** 189 | * 连接外设 190 | * 191 | * @param {object} device 指定连接的外设对象,从registerDidDiscoverDevice注册的回调中得到 192 | * @param {number} timeout 连接超时时间,毫秒,默认15000ms,支付宝小程序无效 193 | * 194 | * @return Promise对象 195 | * 196 | * @discussion 连接指定的外设,需要传入外设对象。 197 | * 注意实现返回对象的then和catch方法,监听接口是否调用成功。 198 | */ 199 | connect(device , timeout) { 200 | // 判断是否已经连接 201 | if (this.bm.connectStatus !== ConnectStatus.disconnected) { 202 | this.bm.logwarn('connect fail', 'Is already connected'); 203 | return Promise.reject(ErrorApiCatch.Error_Connect_AlreadyConnected); 204 | } 205 | // 判断设备id是否为空 206 | if (_.isEmpty(device.deviceId)) { 207 | return Promise.reject(ErrorApiCatch.Error_Connect_EmptyId); 208 | } 209 | 210 | this.bm.deviceInfo = device; 211 | this.bm.connectStatus = ConnectStatus.connecting; 212 | this.callBackConnectStatus(SuccessCallbackEvent.Success_ConnectStatus_CB_Connecting); 213 | let deviceId = device.deviceId; 214 | 215 | // 打开和监听蓝牙适配器 216 | return this.openAndListenBluetoothAdapter() 217 | .then(__ => { 218 | // 连接设备 219 | return _.api('createBLEConnection', 'connectBLEDevice', { 220 | deviceId , 221 | timeout: timeout || 15000 , 222 | }) 223 | }).then( res => { 224 | this.bm.log('connectBLEDevice success', res); 225 | // 取消蓝牙连接状态监听,仅支付宝小程序有效 226 | _.api('offBLEConnectionStateChanged').then(__ => __).catch(__ => __); 227 | // 蓝牙连接状态监听 228 | _.on('onBLEConnectionStateChange', 'onBLEConnectionStateChanged',(res) => { 229 | this.bm.log('onBLEConnectionStateChange', res); 230 | if (!res.connected && this.bm.connectStatus !== ConnectStatus.disconnected) { 231 | this.bm.connectStatus = ConnectStatus.disconnected; 232 | if (res.errorCode === 0 || res.errorCode === undefined) { 233 | this.callBackConnectStatus(SuccessCallbackEvent.Success_ConnectStatus_CB_Stop); 234 | } else if (res.errorCode === 10003) { 235 | this.callBackConnectStatus(ErrorCallbackEvent.Error_ConnectStatus_CB_Disconnected); 236 | } else if (_.getAppPlatform() === 'ant'){ 237 | this.callBackConnectStatus(ErrorCallbackEvent.Error_ConnectStatus_CB_Disconnected); 238 | } 239 | } 240 | }) 241 | 242 | // 获取设备所有服务 243 | _.api('getBLEDeviceServices', '', { deviceId }) 244 | .then(res => { 245 | this.bm.log('getBLEDeviceServices success', res); 246 | // 存储所有服务promise 247 | let sPromises = []; 248 | // 获取所有服务的所有特征 249 | device.services = res.services.map(server => { 250 | let sUUID = server.uuid || server.serviceId; 251 | let sPromise = _.api('getBLEDeviceCharacteristics', '', { 252 | deviceId, 253 | serviceId: sUUID 254 | }) 255 | sPromises.push(sPromise); 256 | return { serviceId: sUUID }; 257 | }) 258 | return Promise.all(sPromises); 259 | }).then(res => { 260 | this.bm.log('getBLEDeviceCharacteristics success', res); 261 | device.services = res.map((v, i) => { 262 | let service = device.services[i]; 263 | service.characteristics = v.characteristics; 264 | return service; 265 | }) 266 | // 获取特征成功之后才算连接成功 267 | this.bm.deviceInfo = device; 268 | this.bm.connectStatus = ConnectStatus.connected; 269 | this.callBackConnectStatus(SuccessCallbackEvent.Success_ConnectStatus_CB_Connected); 270 | }).catch(e => { 271 | this.bm.logwarn('api connecting error', e); 272 | // 出现错误断开蓝牙,避免出现已连接成功未找到服务或者特征出错时,再次连接状态不正确 273 | _.api('closeBLEConnection', 'disconnectBLEDevice', { 274 | deviceId: this.bm.deviceInfo.deviceId 275 | }).then(__ => __).catch(__ => __); 276 | this.bm.connectStatus = ConnectStatus.disconnected; 277 | this.callBackConnectStatus(e); 278 | }) 279 | 280 | // 开始连接接口调用成功 281 | return Promise.resolve(SuccessApiThen.Success_Connect); 282 | }).catch(e => { 283 | this.bm.logwarn('api connect error', e); 284 | // 未知错误,直接报连接失败 285 | if (e.code === 100000) e = ErrorCallbackEvent.Error_ConnectStatus_CB_ConnectFail; 286 | this.bm.connectStatus = ConnectStatus.disconnected; 287 | this.callBackConnectStatus(e); 288 | if (e.code === 12 || e.code === 10001) { 289 | return Promise.reject(ErrorApiCatch.Error_Connect_PowerOff); 290 | } else if (~e.message.indexOf('超时') || (e.errMsg && ~e.errMsg.indexOf('time out'))){ 291 | return Promise.reject(ErrorApiCatch.Error_Connect_Timeout); 292 | } else { 293 | return Promise.reject(ErrorApiCatch.Error_Connect_Failed); 294 | } 295 | }) 296 | } 297 | 298 | /** 299 | * 断开连接 300 | * 301 | * @return Promise对象 302 | */ 303 | disconnect() { 304 | 305 | this.bm.connectStatus === ConnectStatus.connected && 306 | _.api('closeBLEConnection','disconnectBLEDevice',{ 307 | deviceId: this.bm.deviceInfo.deviceId 308 | }).then(res => { 309 | this.bm.log('closeBLEConnection success', res); 310 | }).catch(e => { 311 | this.bm.log('closeBLEConnection fail', e); 312 | }) 313 | 314 | _.api('closeBluetoothAdapter') 315 | .then(res => { 316 | this.bm.log('closeBluetoothAdapter success', res); 317 | this.isInitializedAdapter = false; 318 | if (this.bm.connectStatus !== ConnectStatus.disconnected) { 319 | this.bm.connectStatus = ConnectStatus.disconnected; 320 | this.callBackConnectStatus(SuccessCallbackEvent.Success_ConnectStatus_CB_Stop); 321 | }; 322 | this.bm.connectStatus = ConnectStatus.disconnected; 323 | }).catch(e => { 324 | this.bm.log('closeBluetoothAdapter fail', e); 325 | }) 326 | 327 | return Promise.resolve(SuccessApiThen.Success_Disconnect); 328 | } 329 | 330 | /** 331 | * 读特征值 332 | * 333 | * @param {object} params 参数 334 | * @property {string} suuid 特征对应的服务uuid 335 | * @property {string} cuuid 写入特征uuid 336 | * 337 | * @discussion 读某个服务下的某个特征值。 338 | */ 339 | read(params) { 340 | if (this.bm.connectStatus === ConnectStatus.connected) { 341 | let { suuid, cuuid } = params; 342 | return _.api('readBLECharacteristicValue', '', { 343 | deviceId: this.bm.deviceInfo.deviceId, 344 | serviceId: suuid, 345 | characteristicId: cuuid, 346 | }).then(res => { 347 | this.bm.log('readBLECharacteristicValue success', res); 348 | return Promise.resolve(SuccessApiThen.Success_Read); 349 | }).catch(e => { 350 | this.bm.log('readBLECharacteristicValue fail', e); 351 | if (e.code === 10007) { 352 | return Promise.reject(ErrorApiCatch.Error_Read_NotSupport); 353 | } else if (e.code === 10004) { 354 | return Promise.reject(ErrorApiCatch.Error_Read_NoService); 355 | } else if (e.code === 10005) { 356 | return Promise.reject(ErrorApiCatch.Error_Read_NoCharacteristic); 357 | } else { 358 | return Promise.reject(ErrorApiCatch.Error_Read_Failed); 359 | } 360 | }) 361 | } else { 362 | return Promise.reject(ErrorApiCatch.Error_Read_NotConnected); 363 | } 364 | } 365 | 366 | /** 367 | * 向蓝牙模块写入数据 368 | * 369 | * @param {object} params 参数 370 | * @property {string} suuid 特征对应的服务uuid 371 | * @property {string} cuuid 写入特征uuid 372 | * @property {Hex string} value 16进制字符串 373 | * 374 | * @return Promise对象 375 | * 376 | * @discussion 向蓝牙模块写入数据。 377 | */ 378 | write(params) { 379 | let {suuid , cuuid , value} = params; 380 | if (this.bm.connectStatus === ConnectStatus.connected) { 381 | if (_.getAppPlatform() === 'wx') { 382 | if (typeof value === 'string') { 383 | value = _.str2ab(value); 384 | } else { 385 | value = _.typedArray2ArrayBuffer(value); 386 | } 387 | } else if (typeof value !== 'string'){ 388 | value = _.ab2str(value); 389 | } 390 | this.bm.log('writeCmdToDevice', _.ab2str(value)); 391 | return _.api('writeBLECharacteristicValue', '', { 392 | deviceId: this.bm.deviceInfo.deviceId, 393 | serviceId: suuid, 394 | characteristicId: cuuid, 395 | value, 396 | }).then(res => { 397 | this.bm.log('writeBLECharacteristicValue success', res); 398 | return Promise.resolve(SuccessApiThen.Success_Write); 399 | }).catch(e => { 400 | this.bm.log('writeBLECharacteristicValue fail', e); 401 | if (e.code === 10007) { 402 | return Promise.reject(ErrorApiCatch.Error_Write_NotSupport); 403 | } else if (e.code === 10004) { 404 | return Promise.reject(ErrorApiCatch.Error_Write_NoService); 405 | } else if (e.code === 10005) { 406 | return Promise.reject(ErrorApiCatch.Error_Write_NoCharacteristic); 407 | } else { 408 | return Promise.reject(ErrorApiCatch.Error_Write_Failed); 409 | } 410 | }) 411 | } else { 412 | return Promise.reject(ErrorApiCatch.Error_Write_NotConnected); 413 | } 414 | } 415 | 416 | /** 417 | * 监听特征值改变 418 | * 419 | * @param {object} params 参数 420 | * @property {string} suuid 特征对应的服务uuid 421 | * @property {string} cuuid 写入特征uuid 422 | * @property {boolean} state 是否启用notify,可以通过重复调用接口改变此属性打开/关闭监听 423 | * 424 | * @return Promise对象 425 | * 426 | * @discussion 监听某个特征值变化。 427 | */ 428 | notify(params) { 429 | if (this.bm.connectStatus === ConnectStatus.connected) { 430 | let { suuid, cuuid, state } = params; 431 | return _.api('notifyBLECharacteristicValueChange', '', { 432 | deviceId: this.bm.deviceInfo.deviceId, 433 | serviceId: suuid, 434 | characteristicId: cuuid, 435 | state 436 | }).then(res => { 437 | this.bm.log('readBLECharacteristicValue success', res); 438 | return Promise.resolve(SuccessApiThen.Success_Notify); 439 | }).catch(e => { 440 | this.bm.log('readBLECharacteristicValue fail', e); 441 | if (e.code === 10007) { 442 | return Promise.reject(ErrorApiCatch.Error_Notify_NotSupport); 443 | } else if (e.code === 10004) { 444 | return Promise.reject(ErrorApiCatch.Error_Notify_NoService); 445 | } else if (e.code === 10005) { 446 | return Promise.reject(ErrorApiCatch.Error_Notify_NoCharacteristic); 447 | } else { 448 | return Promise.reject(ErrorApiCatch.Error_Notify_Failed); 449 | } 450 | }) 451 | } else { 452 | return Promise.reject(ErrorApiCatch.Error_Notify_NotConnected); 453 | } 454 | } 455 | 456 | /** 457 | * 注册状态改变回调 458 | * 459 | * @param {function} cb 回调函数 460 | * 461 | * @discussion 连接状态发生改变时,回调此方法。 462 | */ 463 | registerDidUpdateConnectStatus(cb) { 464 | this._didUpdateStatusCB = cb; 465 | } 466 | 467 | /** 468 | * 注册发现外设回调 469 | * 470 | * @param {function} cb 回调函数 471 | * 472 | * @discussion 当扫描到设备时回调 473 | */ 474 | registerDidDiscoverDevice(cb) { 475 | this._didDiscoverDeviceCB = cb; 476 | } 477 | 478 | /** 479 | * 注册特征值改变回调 480 | * 481 | * @param {function} cb 回调函数 482 | * 483 | * @discussion 当监听的特征值改变时回调 484 | */ 485 | registerDidUpdateValueForCharacteristic(cb) { 486 | this._didUpdateValueCB = cb; 487 | _.api('offBLECharacteristicValueChange').then(__ => __).catch(__ => __); 488 | _.on('onBLECharacteristicValueChange', '' ,characteristic => { 489 | if (typeof characteristic.value === 'string') { 490 | this._didUpdateValueCB(characteristic); 491 | } else { 492 | characteristic.value = _.ab2str(characteristic.value); 493 | this._didUpdateValueCB(characteristic); 494 | } 495 | }) 496 | } 497 | 498 | /** 499 | * 回调蓝牙连接状态 500 | */ 501 | callBackConnectStatus(status) { 502 | this._didUpdateStatusCB && this._didUpdateStatusCB({ 503 | ...status, 504 | device:this.bm.deviceInfo, 505 | connectStatus:this.bm.connectStatus 506 | }); 507 | } 508 | 509 | /** 510 | * 回调发现外设 511 | * 512 | * @param device 扫描到的设备 513 | */ 514 | callBackDiscoverDevice(device , event , timeout) { 515 | this._didDiscoverDeviceCB && this._didDiscoverDeviceCB( 516 | device ? { 517 | ...event, 518 | timeout, 519 | device, 520 | } : { 521 | ...event, 522 | timeout, 523 | } 524 | ) 525 | } 526 | 527 | /** 528 | * 销毁延时 529 | */ 530 | destoryTimer() { 531 | this.scanTimeoutTimer && clearTimeout(this.scanTimeoutTimer); 532 | this.scanTimeoutTimer = null; 533 | } 534 | } 535 | -------------------------------------------------------------------------------- /src/btmanager.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | import { Log } from './extends.js'; 4 | import { ConnectStatus } from './enum.js'; 5 | import Bluetooth from './bluetooth.js'; 6 | 7 | export class BTManager { 8 | 9 | /** 10 | * 构造函数 11 | * 12 | * @param {object} config 配置 13 | * @property {boolean} debug 是否开启打印调试,默认不开启 14 | * 15 | * @discussion 单例模式。 16 | */ 17 | constructor(config = {}) { 18 | if (!BTManager.instance) { 19 | BTManager.instance = this; 20 | // 初始化log 21 | Log.call(BTManager.prototype); 22 | // 初始化设备信息 23 | this.deviceInfo = {}; 24 | // 初始化连接状态 25 | this.connectStatus = ConnectStatus.disconnected; 26 | // 初始化蓝牙管理器 27 | this._bt = new Bluetooth(this); 28 | } 29 | // 合并配置 30 | Object.assign(BTManager.instance, config); 31 | return BTManager.instance; 32 | } 33 | 34 | /** 35 | * 扫描外设 36 | * 37 | * @param {object} options 扫描参数 38 | * @property {array} services 主service的uuid列表。确认在蓝牙广播中存在此服务id,可以通过服务id过滤掉其他设备 39 | * @property {boolean} allowDuplicatesKey 是否允许重复上报设备 40 | * @property {number} interval 上报新设备的间隔,默认为0 41 | * @property {number} timeout 扫描超时时间,毫秒。在该时间内未扫描到符合要求的设备,上报超时。默认15000ms,-1表示无限超时 42 | * @property {string} deviceName 通过蓝牙名称过滤,需要匹配的设备名称 43 | * @property {string} containName 通过蓝牙名称过滤,需要包含的设备名称 44 | * 45 | * @return Promise对象 46 | * 47 | * @discussion 开始扫描外设,注意实现返回对象的then和catch方法,监听接口是否调用成功。 48 | * 此操作比较耗费系统资源,请在搜索到设备后调用stopScan方法停止扫描。 49 | * 重复调用此接口,会清空之前设备存储,再次上报已上报的设备,能够起到刷新的作用。 50 | * 51 | * @see registerDidDiscoverDevice 52 | */ 53 | scan( options = { 54 | services: [], 55 | allowDuplicatesKey: false, 56 | interval : 0, 57 | timeout: 15000, 58 | deviceName: '', 59 | containName:'' 60 | }) 61 | { 62 | return this._bt.scanDevice(options); 63 | } 64 | 65 | /** 66 | * 停止扫描 67 | * 68 | * @return Promise对象 69 | * 70 | * @discussion 停止扫描,取消超时延时。 71 | */ 72 | stopScan() { 73 | return this._bt.stopScan(); 74 | } 75 | 76 | /** 77 | * 连接外设 78 | * 79 | * @param {object} device 指定连接的外设对象,从registerDidDiscoverDevice注册的回调中得到 80 | * @param {number} timeout 连接超时时间,毫秒,默认15000ms,支付宝小程序无效 81 | * 82 | * @return Promise对象 83 | * 84 | * @discussion 连接指定的外设,需要传入外设对象。 85 | * 注意实现返回对象的then和catch方法,监听接口是否调用成功。 86 | */ 87 | connect(device , timeout) { 88 | if (!device) throw new Error('device is undefiend'); 89 | return this._bt.connect(device); 90 | } 91 | 92 | /** 93 | * 断开连接 94 | * 95 | * @return Promise对象 96 | */ 97 | disconnect() { 98 | return this._bt.disconnect(); 99 | } 100 | 101 | /** 102 | * 读特征值 103 | * 104 | * @param {object} params 参数 105 | * @property {string} suuid 特征对应的服务uuid 106 | * @property {string} cuuid 特征uuid 107 | * 108 | * @return Promise对象 109 | * 110 | * @discussion 读某个服务下的某个特征值。 111 | */ 112 | read(params = { 113 | suuid: '', 114 | cuuid: '' 115 | }) 116 | { 117 | return this._bt.read(params); 118 | } 119 | 120 | /** 121 | * 向蓝牙模块写入数据 122 | * 123 | * @param {object} params 参数 124 | * @property {string} suuid 特征对应的服务uuid 125 | * @property {string} cuuid 特征uuid 126 | * @property {Hex string} value 16进制字符串 127 | * 128 | * @return Promise对象 129 | * 130 | * @discussion 向蓝牙模块写入数据。 131 | */ 132 | write(params = { 133 | suuid: '', 134 | cuuid: '', 135 | value: '' 136 | }) 137 | { 138 | return this._bt.write(params); 139 | } 140 | 141 | /** 142 | * 监听特征值改变 143 | * 144 | * @param {object} params 参数 145 | * @property {string} suuid 特征对应的服务uuid 146 | * @property {string} cuuid 特征uuid 147 | * @property {boolean} state 是否启用notify,可以通过重复调用接口改变此属性打开/关闭监听 148 | * 149 | * @return Promise对象 150 | * 151 | * @discussion 监听某个特征值变化。 152 | */ 153 | notify(params = { 154 | suuid: '', 155 | cuuid: '', 156 | state: true, 157 | }) 158 | { 159 | return this._bt.notify(params); 160 | } 161 | 162 | /** 163 | * 注册状态改变回调 164 | * 165 | * @param {function} cb 回调函数 166 | * 167 | * @discussion 连接状态发生改变时,回调此方法。 168 | */ 169 | registerDidUpdateConnectStatus(cb) { 170 | if (typeof cb !== 'function') throw new TypeError('connectStatus callback expect function'); 171 | this._bt.registerDidUpdateConnectStatus(cb); 172 | } 173 | 174 | /** 175 | * 注册发现外设回调 176 | * 177 | * @param {function} cb 回调函数 178 | * 179 | * @discussion 当扫描到设备时回调,或者达到超时时间回调。 180 | */ 181 | registerDidDiscoverDevice(cb) { 182 | if (typeof cb !== 'function') throw new TypeError('discoverDevice callback expect function'); 183 | this._bt.registerDidDiscoverDevice(cb); 184 | } 185 | 186 | /** 187 | * 注册特征值改变回调 188 | * 189 | * @param {function} cb 回调函数 190 | * 191 | * @discussion 当监听的特征值改变时回调,或者读特征值时回调。 192 | */ 193 | registerDidUpdateValueForCharacteristic(cb) { 194 | if (typeof cb !== 'function') throw new TypeError('updateValueForCharacteristic callback expect function'); 195 | this._bt.registerDidUpdateValueForCharacteristic(cb); 196 | } 197 | 198 | } -------------------------------------------------------------------------------- /src/enum.js: -------------------------------------------------------------------------------- 1 | 2 | // 连接状态 3 | export const ConnectStatus = { 4 | // 未连接或连接断开,允许连接 5 | disconnected: 0, 6 | // 正在连接,不允许再连接 7 | connecting: 1, 8 | // 已连接,不允许再连接 9 | connected: 2, 10 | } 11 | 12 | // 发现外设回调和连接状态改变回调成功事件 13 | export const SuccessCallbackEvent = { 14 | Success_DiscoverDevice_CB_Discover: { code: 210, message: '发现外设' }, 15 | Success_DiscoverDevice_CB_ScanDone: { code: 211, message: '扫描完成' }, 16 | Success_ConnectStatus_CB_PowerOn: { code: 220, message: '蓝牙打开' }, 17 | Success_ConnectStatus_CB_Connecting: { code: 221, message: '正在连接' }, 18 | Success_ConnectStatus_CB_Connected: { code: 222, message: '连接成功' }, 19 | Success_ConnectStatus_CB_Stop: { code: 223, message: '断开成功' }, 20 | } 21 | 22 | // 发现外设回调和连接状态改变回调失败事件 23 | export const ErrorCallbackEvent = { 24 | Error_DiscoverDevice_CB_Timeout: { code: 410, message: '扫描超时' }, 25 | Error_ConnectStatus_CB_PowerOff: { code: 420, message: '蓝牙关闭' }, 26 | Error_ConnectStatus_CB_ConnectFail: { code: 421, message: '连接失败' }, 27 | Error_ConnectStatus_CB_Disconnected: { code: 422, message: '连接断开' }, 28 | } 29 | 30 | // 接口调用成功事件 31 | export const SuccessApiThen = { 32 | Success_Scan: { code: 2010, message: '扫描接口成功调用' }, 33 | Success_StopScan: { code: 2020, message: '停止扫描接口成功调用' }, 34 | Success_Connect: { code: 2030, message: '连接接口成功调用' }, 35 | Success_Disconnect: { code: 2040, message: '断开接口成功调用' }, 36 | Success_Read: { code: 2050, message: '读特征值接口成功调用' }, 37 | Success_Write: { code: 2060, message: '写入数据接口成功调用' }, 38 | Success_Notify: { code: 2070, message: '监听特征值接口成功调用' }, 39 | } 40 | 41 | // 接口调用失败事件 42 | export const ErrorApiCatch = { 43 | // 基础库版本低 44 | Error_Low_Version: { code: 4000, message: '当前基础库版本低,请更新微信版本' }, 45 | // Api: scan 46 | Error_Scan_Failed: { code: 4010, message: '扫描错误:扫描失败' }, 47 | Error_Scan_PowerOff: { code: 4011, message: '扫描错误:蓝牙被关闭' }, 48 | Error_Scan_NoService: { code: 4012, message: '扫描错误:没有找到指定服务' }, 49 | // Api: stopScan 50 | Error_StopScan_Failed: { code: 4020, message: '停止扫描错误:停止扫描失败' }, 51 | Error_StopScan_PowerOff: { code: 4021, message: '停止扫描错误:蓝牙被关闭' }, 52 | // Api: connect 53 | Error_Connect_Failed: { code: 4030, message: '连接错误:连接失败' }, 54 | Error_Connect_PowerOff: { code: 4031, message: '连接错误:蓝牙被关闭' }, 55 | Error_Connect_AlreadyConnected: { code: 4032, message: '连接错误:已经连接或正在连接' }, 56 | Error_Connect_Timeout: { code: 4033, message: '连接错误:连接超时' }, 57 | Error_Connect_EmptyId: { code: 4034, message: '连接错误:设备id不能为空' }, 58 | // Api: disconnect 59 | Error_Disconnect_Failed: { code: 4040, message: '断开错误:断开失败' }, 60 | // Api: read 61 | Error_Read_Failed: { code: 4050, message: '读特征值错误:读特征值失败'}, 62 | Error_Read_NotConnected: { code: 4051, message: '读特征值错误:蓝牙未连接' }, 63 | Error_Read_NotSupport: { code: 4052, message: '读特征值错误:当前特征不支持读操作' }, 64 | Error_Read_NoService: { code: 4053, message: '读特征值错误:没有找到指定服务' }, 65 | Error_Read_NoCharacteristic: { code: 4054, message: '读特征值错误:没有找到指定特征值' }, 66 | // Api: write 67 | Error_Write_Failed: { code: 4060, message: '写入数据错误:写入数据失败'}, 68 | Error_Write_NotConnected: { code: 4061, message: '写入数据错误:蓝牙未连接' }, 69 | Error_Write_NotSupport: { code: 4062, message: '写入数据错误:当前特征不支持写操作' }, 70 | Error_Write_NoService: { code: 4063, message: '写入数据错误:没有找到指定服务' }, 71 | Error_Write_NoCharacteristic: { code: 4064, message: '写入数据错误:没有找到指定特征值' }, 72 | // Api: notify 73 | Error_Notify_Failed: { code: 4070, message: '监听特征值错误:监听特征值错误失败' }, 74 | Error_Notify_NotConnected: { code: 4071, message: '监听特征值错误:蓝牙未连接' }, 75 | Error_Notify_NotSupport: { code: 4072, message: '监听特征值错误:当前特征不支持监听操作' }, 76 | Error_Notify_NoService: { code: 4073, message: '监听特征值错误:没有找到指定服务' }, 77 | Error_Notify_NoCharacteristic: { code: 4074, message: '监听特征值错误:没有找到指定特征值' }, 78 | } 79 | 80 | -------------------------------------------------------------------------------- /src/extends.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | export function Log() { 4 | 5 | this.log = function(identifier, msg='') { 6 | this.debug && console.log(`BLE:(${identifier}):` , msg); 7 | } 8 | 9 | this.loginfo = function(identifier,msg) { 10 | this.debug && console.info(`BLE:(${identifier}):` ,msg); 11 | } 12 | 13 | this.logwarn = function (identifier, msg) { 14 | this.debug && console.warn(`BLE:(${identifier}):`, msg); 15 | } 16 | 17 | this.logerror = function (identifier, msg) { 18 | this.debug && console.error(`BLE:(${identifier}):`, msg); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/promisify.js: -------------------------------------------------------------------------------- 1 | 2 | // 判断是微信小程序还是支付宝小程序 3 | let ii, mini; 4 | try { 5 | ii = wx; 6 | mini = 'wx'; 7 | } catch (e) { 8 | ii = my; 9 | mini = 'ant'; 10 | } 11 | 12 | import { ErrorApiCatch} from './enum.js'; 13 | 14 | // 微信系统错误 15 | const ERROR_WX = { 16 | NOT_INIT: { code: 10000, message: '未初始化蓝牙适配器' }, 17 | NOT_AVAILABLE: { code: 10001, message: '当前蓝牙适配器不可用' }, 18 | NO_DEVICE: { code: 10002, message: '没有找到指定设备' }, 19 | CONNECTION_FAIL: { code: 10003, message: '连接失败' }, 20 | NO_SERVICE: { code: 10004, message: '没有找到指定服务' }, 21 | NO_CHARACTERISTIC: { code: 10005, message: '没有找到指定特征值' }, 22 | NO_CONNECTION: { code: 10006, message: '当前连接已断开' }, 23 | PROPERTY_NOT_SUPPORT: { code: 10007, message: '当前特征值不支持此操作' }, 24 | SYSTEM_ERROR: { code: 10008, message: '系统异常' }, 25 | SYSTEM_NOT_SUPPORT: { code: 10009, message: 'Android 系统版本低于 4.3 不支持 BLE' }, 26 | OPERATE_TIMEOUT: { code: 10012, message: '操作超时' }, 27 | INVALID_PARAMETER: { code: 10013, message: '无效参数' }, 28 | ALREADY_CONNECTED: { code: -1 , message: '蓝牙已连接,不能再连接' }, 29 | UN_KNOWN: { code: 100000,message: '未知' }, 30 | } 31 | 32 | // 支付宝系统错误 33 | const ERROR_ANT = { 34 | POWER_OFF: { code: 12, message: '蓝牙未打开' }, 35 | LOST_SERVICE: { code: 13, message: '与系统服务的链接暂时丢失' }, 36 | UNAUTH_BLE: { code: 14, message: '未授权支付宝使用蓝牙功能' }, 37 | UNKNOWN_ERROR: { code: 15, message: '未知错误' }, 38 | NOT_INIT: { code: 10000, message: '未初始化蓝牙适配器' }, 39 | NOT_AVAILABLE: { code: 10001, message: '当前蓝牙适配器不可用' }, 40 | NO_DEVICE: { code: 10002, message: '没有找到指定设备' }, 41 | CONNECTION_FAIL: { code: 10003, message: '连接失败' }, 42 | NO_SERVICE: { code: 10004, message: '没有找到指定服务' }, 43 | NO_CHARACTERISTIC: { code: 10005, message: '没有找到指定特征值' }, 44 | NO_CONNECTION: { code: 10006, message: '当前连接已断开' }, 45 | PROPERTY_NOT_SUPPORT: { code: 10007, message: '当前特征值不支持此操作' }, 46 | SYSTEM_ERROR: { code: 10008, message: '系统异常' }, 47 | SYSTEM_NOT_SUPPORT: { code: 10009, message: 'Android 系统版本低于 4.3 不支持 BLE' }, 48 | SYMBOL_UNFOUND: { code: 10010, message: '没有找到指定描述符' }, 49 | DEVICE_ID_INVALID: { code: 10011, message: '设备 id 不可用/为空' }, 50 | SERVICE_ID_INVALID: { code: 10012, message: '服务 id 不可用/为空' }, 51 | CHARACTERISTIC_ID_INVALID: { code: 10013, message: '特征 id 不可用/为空' }, 52 | CMD_FORMAT_ERROR: { code: 10014, message: '发送的数据为空或格式错误' }, 53 | OPERATION_TIMEOUT: { code: 10015, message: '操作超时' }, 54 | LACK_PARAMETER: { code: 10016, message: '缺少参数' }, 55 | WRITE_ERROR: { code: 10017, message: '写入特征值失败' }, 56 | READ_ERROR: { code: 10018, message: '读取特征值失败' }, 57 | } 58 | 59 | const ERROR_TYPES = mini==='wx' ? ERROR_WX : ERROR_ANT; 60 | 61 | /** 62 | * 获取错误类型 63 | */ 64 | function getErrorType(code) { 65 | for (let key of Object.keys(ERROR_TYPES)) { 66 | if (code === ERROR_TYPES[key].code) { 67 | return ERROR_TYPES[key]; 68 | } 69 | } 70 | return ERROR_TYPES.UN_KNOWN; 71 | } 72 | 73 | /** 74 | * 获取小程序平台 wx||ant 75 | */ 76 | export function getAppPlatform() { 77 | return mini; 78 | } 79 | 80 | /** 81 | * 重写异步API 82 | * 83 | * @param {string} fn1 方法名1 84 | * @param {string} fn2 方法名2 85 | * @param {object} options 可选参数 86 | * 87 | * @return Promise对象 88 | */ 89 | export function api(fn1, fn2, options) { 90 | let func = ii[fn1] || ii[fn2]; 91 | return new Promise((resolve, reject) => { 92 | let params = { 93 | success(res) { 94 | resolve(res); 95 | }, 96 | fail(res) { 97 | if (res.errorMessage) { 98 | reject({ 99 | code: res.error, 100 | message: res.errorMessage 101 | }); 102 | } else { 103 | reject({ 104 | ...getErrorType(res.errCode), 105 | errMsg: res.errMsg 106 | }); 107 | } 108 | }, 109 | complete(res) { 110 | //... 111 | } 112 | } 113 | if (options) { 114 | params = Object.assign(params, options); 115 | } 116 | if (func) { 117 | func(params); 118 | } else { 119 | reject(ErrorApiCatch.Error_Low_Version); 120 | } 121 | }) 122 | } 123 | 124 | /** 125 | * 重写回调API 126 | * 127 | * @param {string} fn1 方法名1 128 | * @param {string} fn2 方法名2 129 | * @param {function} cb 回调函数 130 | */ 131 | export function on(fn1, fn2, cb) { 132 | let func = ii[fn1] || ii[fn2]; 133 | if (func) { 134 | if (mini === 'wx') { 135 | func(cb); 136 | } else { 137 | func({ 138 | success: cb, 139 | }) 140 | } 141 | } 142 | } -------------------------------------------------------------------------------- /src/tools.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | import { getAppPlatform , api , on} from './promisify.js'; 5 | 6 | /** 7 | * 判断字符串是否为空或者空格 8 | */ 9 | function isEmpty(str = '') { 10 | return !str || str == '' || str.replace(/(^\s*)|(\s*$)/g, "") == ""; 11 | } 12 | 13 | /** 14 | * 判断是否为null或者未定义 15 | */ 16 | function isNullOrUndefined(obj) { 17 | return obj === undefined || obj === null; 18 | } 19 | 20 | /** 21 | * ArrayBuffer类型转换为16进制字符串 22 | */ 23 | function ab2str(buffer) { 24 | return Array.prototype.map.call(new Uint8Array(buffer), x => ('00' + x.toString(16)).slice(-2)).join(''); 25 | } 26 | 27 | /** 28 | * 字符串转为ArrayBuffer对象,参数为字符串 29 | */ 30 | function str2ab(str) { 31 | var buf = new ArrayBuffer(str.length / 2); 32 | var bufView = new Uint8Array(buf); 33 | for (var i = 0; i < str.length; i += 2) { 34 | bufView[parseInt(i / 2)] = char2Hex(str.charCodeAt(i)) << 4 | char2Hex(str.charCodeAt(i + 1)); 35 | } 36 | return buf; 37 | } 38 | 39 | /** 40 | * 字符转十六进制 41 | */ 42 | function char2Hex(bChar) { 43 | if ((bChar >= 0x30) && (bChar <= 0x39)) { // 数字 44 | bChar -= 0x30; 45 | } else if ((bChar >= 0x41) && (bChar <= 0x46)) { // 大写字母 46 | bChar -= 0x37; 47 | } else if ((bChar >= 0x61) && (bChar <= 0x66)) { // 小写字母 48 | bChar -= 0x57; 49 | } else { 50 | bChar = 0xff; 51 | } 52 | return bChar; 53 | } 54 | 55 | /** 56 | * TypedArray转为ArrayBuffer 57 | */ 58 | function typedArray2ArrayBuffer(pbuff) { 59 | let buffer = new ArrayBuffer(pbuff.byteLength) 60 | let uInit8 = new Uint8Array(buffer) 61 | uInit8.set(pbuff); 62 | return buffer; 63 | } 64 | 65 | /** 66 | * 获取异或校验数值 67 | * 68 | * @param p 参与运算的字符数组指针 69 | * @param len 参与运算的字符数组长度 70 | */ 71 | function createXOR(b, p, len) { 72 | let i = 0; 73 | let ckc = 0; 74 | for (; i < len; i++) { 75 | ckc = ckc ^ b[p + i]; 76 | } 77 | return ckc; 78 | } 79 | 80 | /** 81 | * UUID128位转换为16位 82 | * 83 | * @param uuid128 128位的uuid 84 | */ 85 | function uuid128to16(uuid128) { 86 | let arr = uuid128.split('-'); 87 | if (arr.length === 5 && arr[4] === '00805F9B34FB') { 88 | return arr[0].slice(3); 89 | } else { 90 | return uuid128; 91 | } 92 | } 93 | 94 | export default { 95 | getAppPlatform, 96 | api, 97 | on, 98 | isEmpty, 99 | isNullOrUndefined, 100 | ab2str, 101 | str2ab, 102 | typedArray2ArrayBuffer, 103 | createXOR, 104 | uuid128to16 105 | } 106 | 107 | 108 | --------------------------------------------------------------------------------