diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..a4e0bbef992c7bf2d352a1e40e2c0451cdf808bc --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/local.properties +/.idea +**/build +.gradle diff --git a/CHANGELOG b/CHANGELOG new file mode 100644 index 0000000000000000000000000000000000000000..4601392cd72fb590909845bd584282b536cf2180 --- /dev/null +++ b/CHANGELOG @@ -0,0 +1,10 @@ +- v1.0.0 +- 已实现功能 + 1.初始化 + 2.扫描 + 3.连接 + 4.取消扫描 + 5.打开蓝牙 + 6.关闭蓝牙 + 7.读Characteristic + 8.写Characteristic \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..a7ff5b2b91af239eac401bf073cbb3c902c5abc5 --- /dev/null +++ b/LICENSE @@ -0,0 +1,204 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2016 chenlijian(陈利健) + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + diff --git a/README.OpenSource b/README.OpenSource new file mode 100644 index 0000000000000000000000000000000000000000..def094f891a369edc631eef5043a57f38c071256 --- /dev/null +++ b/README.OpenSource @@ -0,0 +1,11 @@ +[ + { + "Name": "FastBle", + "License": "Apache-2.0 License", + "License File": " LICENSE ", + "Version Number": "2.4.0", + "Owner" : "Jasonchenlijian" + "Upstream URL": "https://github.com/Jasonchenlijian/FastBle", + "Description": "Android Bluetooth Low Energy (BLE) Fast Development Framework. It uses simple ways to filter, scan, connect, read ,write, notify, readRssi, setMTU, and multiConnection." + } +] diff --git a/README.en.md b/README.en.md deleted file mode 100644 index a75e6073578005a1e24dcab4749c2e674b01c7bb..0000000000000000000000000000000000000000 --- a/README.en.md +++ /dev/null @@ -1,36 +0,0 @@ -# FastBle-ETS - -#### Description -{**When you're done, you can delete the content in this README and update the file with details for others getting started with your repository**} - -#### Software Architecture -Software architecture description - -#### Installation - -1. xxxx -2. xxxx -3. xxxx - -#### Instructions - -1. xxxx -2. xxxx -3. xxxx - -#### Contribution - -1. Fork the repository -2. Create Feat_xxx branch -3. Commit your code -4. Create Pull Request - - -#### Gitee Feature - -1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md -2. Gitee blog [blog.gitee.com](https://blog.gitee.com) -3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore) -4. The most valuable open source project [GVP](https://gitee.com/gvp) -5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help) -6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) diff --git a/README.md b/README.md index e690fd07003a4de63d402cde64239bec883569b6..b82a4ad3a1b4259e095e7e99440ce35a1d63fb90 100644 --- a/README.md +++ b/README.md @@ -1,39 +1,192 @@ -# FastBle-ETS +# FastBle -#### 介绍 -{**以下是 Gitee 平台说明,您可以替换此简介** -Gitee 是 OSCHINA 推出的基于 Git 的代码托管平台(同时支持 SVN)。专为开发者提供稳定、高效、安全的云端软件开发协作平台 -无论是个人、团队、或是企业,都能够用 Gitee 实现代码托管、项目管理、协作开发。企业项目请看 [https://gitee.com/enterprises](https://gitee.com/enterprises)} +## 简介 -#### 软件架构 -软件架构说明 +FastBle可以对蓝牙BLE设备进行过滤,扫描,连接,读取,写入等操作。 +![gif](preview/preview.gif) -#### 安装教程 +## 下载安装 +```shell +npm install @ohos/wxFastBle --save +``` +OpenHarmony npm环境配置等更多内容,请参考 [如何安装OpenHarmony npm包](https://gitee.com/openharmony-tpc/docs/blob/master/OpenHarmony_npm_usage.md) 。 -1. xxxx -2. xxxx -3. xxxx +## 使用说明 +1. 初始化 +``` +BleManager.getInstance().init(); +``` +2. 初始化配置 +``` +BleManager.getInstance() + .enableLog(true) + .setReConnectCount(1, 5000) + .setConnectOverTime(20000) + .setOperateTimeout(5000); +``` +3. 配置扫描规则 +``` +let scanRuleConfig: BleScanRuleConfig = new BleScanRuleConfig.Builder() + .setServiceUuids(serviceUuids) // 只扫描指定的服务的设备,可选 + .setDeviceName(true, names) // 只扫描指定广播名的设备,可选 + .setDeviceMac(mac) // 只扫描指定mac的设备,可选 + .setAutoConnect(isAutoConnect) // 连接时的autoConnect参数,可选,默认false + .setScanTimeOut(10000) // 扫描超时时间,可选,默认10秒 + .build(); +BleManager.getInstance().initScanRule(scanRuleConfig); +``` +4. 扫描设备 +``` +BleManager.getInstance().scan(new class extends BleScanCallback { + @Override + onScanStarted(success: boolean): void { + console.log("onScanStarted"); + _this.clearScanDevice(); + _this.is_loading = true; + _this.loading_rotate = 360; + _this.btn_scan_text = $r('app.string.stop_scan'); + _this.connectedDevices = BleManager.getInstance().getAllConnectedDevice(); + } + + @Override + onLeScan(bleDevice: BleDevice): void { + console.log("onLeScan"); + } + + @Override + onScanning(bleDevice: BleDevice): void { + console.log("onScanning"); + ArrayHelper.add(_this.bleDeviceList, bleDevice); + } + + @Override + onScanFinished(scanResultList: Array): void { + console.log("onScanFinished"); + _this.is_loading = false; + _this.loading_rotate = 0; + _this.btn_scan_text = $r('app.string.start_scan'); + _this.connectedDevices = BleManager.getInstance().getAllConnectedDevice(); + } +}) +``` +5. 连接设备 +``` +BleManager.getInstance().connect(bleDevice, new class extends BleGattCallback { + public onStartConnect(): void { + _this.progressDialogCtrl.open(); + } + + public onConnectFail(bleDevice: BleDevice, exception: BleException): void { + _this.progressDialogCtrl.close(); + } + + public onConnectSuccess(bleDevice: BleDevice, gatt: /*bluetooth.GattClientDevice*/any, status: number): void { + _this.progressDialogCtrl.close(); + } + + public onDisConnected(isActiveDisConnected: boolean, device: BleDevice, gatt: /*bluetooth.GattClientDevice*/any, status: number): void { + _this.progressDialogCtrl.close(); + } +}); +``` +6. 取消扫描 +``` +BleManager.getInstance().cancelScan(); +``` +7. 读数据 +``` +BleManager.getInstance().read( + this.device, + this.characteristic.serviceUuid, + this.characteristic.characteristicUuid, + new class extends BleReadCallback { + + public onReadSuccess(data: Uint8Array): void { + setTimeout(()=> { + _this.appendDataText(HexUtil.formatHexString(data, true)); + }); + } + + public onReadFailure(exception: BleException): void { + setTimeout(()=> { + _this.appendDataText(exception.toString()); + }); + } + }); +``` +8. 写数据 +``` +BleManager.getInstance().write( + this.device, + this.characteristic.serviceUuid, + this.characteristic.characteristicUuid, + HexUtil.hexStringToBytes(hex), + true, + true, + 0, + new class extends BleWriteCallback { + + public onWriteSuccess(current: number, total: number, justWrite: Uint8Array): void { + setTimeout(()=> { + _this.appendDataText("write success, current: " + current + + " total: " + total + + " justWrite: " + HexUtil.formatHexString(justWrite, true)); + }); + } + + public onWriteFailure(exception: BleException): void { + setTimeout(()=> { + _this.appendDataText(exception.toString()); + }); + } + }); +``` -#### 使用说明 +## 接口说明 +1. 获取实例 + `public static getInstance(): BleManager;` +2. 配置扫描规则 + `public initScanRule(config: BleScanRuleConfig)` +3. 扫描设备 + `public scan(callback: BleScanCallback)` +4. 取消扫描 + `public cancelScan()` +5. 连接设备 + `public connect(device: BleDevice | string, bleGattCallback: BleGattCallback)` +6. 判断设备是否连接 + `public isConnected(device: BleDevice | string): boolean` +7. 断开连接 + `public disconnect(bleDevice: BleDevice)` +8. 读数据 + `public read(bleDevice: BleDevice, uuid_service: string, uuid_read: string, callback: BleReadCallback)` +9. 写数据 + `public write(bleDevice: BleDevice, uuid_service: string, uuid_write: string, data: Uint8Array, split: boolean=true, sendNextWhenLastSuccess: boolean=true, intervalBetweenTwoPackage: number=0, callback: BleWriteCallback)` -1. xxxx -2. xxxx -3. xxxx +## 兼容性 +支持 OpenHarmony API version 8 及以上版本。 -#### 参与贡献 +## 目录结构 +```` +|---- wxFastBle +| |---- entry # 示例代码文件夹 +| |---- wxFastBle # fastble库文件夹 +| |----src + |----main + |----ets + |----bluetooth 核心逻辑 + |----callback #回调处理 + |----exception #异常事件处理 + |----scan #蓝牙扫描实现 + |----data #数据处理实现 + |----utils #工具类 + |----BleManager.ets #蓝牙连接管理 +| |---- index.ets # 对外接口 +| |---- README.MD # 安装使用方法 +```` -1. Fork 本仓库 -2. 新建 Feat_xxx 分支 -3. 提交代码 -4. 新建 Pull Request +## 贡献代码 +使用过程中发现任何问题都可以提 [Issue](https://gitee.com/hihopeorg/FastBle-ETS/issues) 给我们,当然,我们也非常欢迎你给我们发 [PR](https://gitee.com/hihopeorg/FastBle-ETS/pulls) 。 - -#### 特技 - -1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md -2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com) -3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目 -4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目 -5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help) -6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) +## 开源协议 +本项目基于 [Apache License 2.0](https://gitee.com/hihopeorg/FastBle-ETS/blob/master/LICENSE) ,请自由地享受和参与开源。 diff --git a/build-profile.json5 b/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..08f8fedc94914a54e93ebae25e19bd4d35da675c --- /dev/null +++ b/build-profile.json5 @@ -0,0 +1,31 @@ +{ + "app": { + "signingConfigs": [], + "compileSdkVersion": 8, + "compatibleSdkVersion": 8, + "products": [ + { + "name": "default", + "signingConfig": "default", + } + ] + }, + "modules": [ + { + "name": "entry", + "srcPath": "./entry", + "targets": [ + { + "name": "default", + "applyToProducts": [ + "default" + ] + } + ] + }, + { + "name": "wxFastBle", + "srcPath": "./wxFastBle" + } + ] +} \ No newline at end of file diff --git a/entry/.gitignore b/entry/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..4f9a973815d0b5e49bc8547681a6b4bc7a178d12 --- /dev/null +++ b/entry/.gitignore @@ -0,0 +1,3 @@ +/node_modules +/.preview +/build \ No newline at end of file diff --git a/entry/build-profile.json5 b/entry/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..ae58d1d0a70c602c9cfe1909b00dfec899ba1944 --- /dev/null +++ b/entry/build-profile.json5 @@ -0,0 +1,13 @@ +{ + "apiType": 'faMode', + "buildOption": { + }, + "targets": [ + { + "name": "default", + }, + { + "name": "ohosTest", + } + ] +} \ No newline at end of file diff --git a/entry/hvigorfile.js b/entry/hvigorfile.js new file mode 100644 index 0000000000000000000000000000000000000000..bcec4c99653062cbf17702c40a2dd2a7b809b81a --- /dev/null +++ b/entry/hvigorfile.js @@ -0,0 +1,2 @@ +// Script for compiling build behavior. It is built in the build plug-in and cannot be modified currently. +module.exports = require('@ohos/hvigor-ohos-plugin').legacyHapTasks diff --git a/entry/package-lock.json b/entry/package-lock.json new file mode 100644 index 0000000000000000000000000000000000000000..940d00769c8c17d77dac05007344ae7aeefe0e1a --- /dev/null +++ b/entry/package-lock.json @@ -0,0 +1,11 @@ +{ + "name": "entry", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@ohos/wxFastBle": { + "version": "file:../wxFastBle" + } + } +} diff --git a/entry/package.json b/entry/package.json new file mode 100644 index 0000000000000000000000000000000000000000..1f36c83200269faa4950b42f71d69408f7c3b353 --- /dev/null +++ b/entry/package.json @@ -0,0 +1,15 @@ +{ + "name": "entry", + "version": "1.0.0", + "ohos": { + "org": "huawei", + "buildTool": "hvigor", + "directoryLevel": "module" + }, + "description": "example description", + "repository": {}, + "license": "Apache-2.0", + "dependencies": { + "@ohos/wxFastBle": "file:../wxFastBle" + } +} diff --git a/entry/src/main/config.json b/entry/src/main/config.json new file mode 100644 index 0000000000000000000000000000000000000000..7d103a3ab5cea2d623b79d566082df32d9981477 --- /dev/null +++ b/entry/src/main/config.json @@ -0,0 +1,82 @@ +{ + "app": { + "vendor": "hihope", + "bundleName": "com.hihope.wxFastBlesample", + "version": { + "code": 1000000, + "name": "1.0.0" + } + }, + "deviceConfig": {}, + "module": { + "mainAbility": ".MainAbility", + "deviceType": [ + "phone", + "tablet" + ], + "reqPermissions": [ + { + "name": "ohos.permission.USE_BLUETOOTH" + }, + { + "name": "ohos.permission.DISCOVER_BLUETOOTH" + }, + { + "name": "ohos.permission.LOCATION" + } + ], + "abilities": [ + { + "skills": [ + { + "entities": [ + "entity.system.home" + ], + "actions": [ + "action.system.home" + ] + } + ], + "orientation": "unspecified", + "visible": true, + "srcPath": "MainAbility", + "name": ".MainAbility", + "srcLanguage": "ets", + "icon": "$media:ic_launcher", + "formsEnabled": false, + "description": "$string:app_name", + "label": "$string:app_name", + "type": "page", + "launchType": "standard" + } + ], + "distro": { + "moduleType": "entry", + "installationFree": false, + "deliveryWithInstall": true, + "moduleName": "entry" + }, + "package": "com.hihope.wxFastBlesample", + "srcPath": "", + "name": ".entry", + "js": [ + { + "mode": { + "syntax": "ets", + "type": "pageAbility" + }, + "pages": [ + "pages/index", + "pages/ServiceListPage", + "pages/CharacteristicListPage", + "pages/CharacteristicOperationPage" + ], + "name": ".MainAbility", + "window": { + "designWidth": 720, + "autoDesignWidth": false + } + } + ] + } +} \ No newline at end of file diff --git a/entry/src/main/ets/MainAbility/app.ets b/entry/src/main/ets/MainAbility/app.ets new file mode 100644 index 0000000000000000000000000000000000000000..b7a0995c8e441cac86e21e06e7c9071664482b1c --- /dev/null +++ b/entry/src/main/ets/MainAbility/app.ets @@ -0,0 +1,8 @@ +export default { + onCreate() { + console.info('Application onCreate') + }, + onDestroy() { + console.info('Application onDestroy') + }, +} \ No newline at end of file diff --git a/entry/src/main/ets/MainAbility/pages/CharacteristicConsts.ets b/entry/src/main/ets/MainAbility/pages/CharacteristicConsts.ets new file mode 100644 index 0000000000000000000000000000000000000000..e64141d82e65b81142fbd5f2dd2a6207a66d86cf --- /dev/null +++ b/entry/src/main/ets/MainAbility/pages/CharacteristicConsts.ets @@ -0,0 +1,7 @@ +export class CharacteristicConsts { + public static readonly PROPERTY_READ = 1; + public static readonly PROPERTY_WRITE = 2; + public static readonly PROPERTY_WRITE_NO_RESPONSE = 3; + public static readonly PROPERTY_NOTIFY = 4; + public static readonly PROPERTY_INDICATE = 5; +} \ No newline at end of file diff --git a/entry/src/main/ets/MainAbility/pages/CharacteristicListPage.ets b/entry/src/main/ets/MainAbility/pages/CharacteristicListPage.ets new file mode 100644 index 0000000000000000000000000000000000000000..7baca5eec6150418e25a33cfda765487d52ad54b --- /dev/null +++ b/entry/src/main/ets/MainAbility/pages/CharacteristicListPage.ets @@ -0,0 +1,171 @@ +import router from '@system.router' +import resmgr from '@ohos.resourceManager'; +import bluetooth from '@ohos.bluetooth'; +import {BleDevice} from '@ohos/wxFastBle' +import {BluetoothGattCharacteristic} from '@ohos/wxFastBle' +import ListDialog from './ListDialog' +import ProgressDialog from './ProgressDialog' +import {CharacteristicConsts} from './CharacteristicConsts' + +class ListDialogInfo { + title: string | Resource; + propNameList: string[]; + propList: number[]; + characteristic: bluetooth.BLECharacteristic; +} + +@Entry +@Preview +@Component +struct Page { + + @State characteristic_name_text: string = ''; + + private device: BleDevice; + private service: bluetooth.GattService; + @State characteristicList: /*bluetooth.BLECharacteristic*/any[] = []; + + @State dlgInfo: ListDialogInfo = new ListDialogInfo(); + + dialogController: CustomDialogController = new CustomDialogController({ + builder: ListDialog({ + title: this.dlgInfo.title, + items: this.dlgInfo.propNameList, + onclick: (which) =>{ + console.log("onItemClick which:"+which); + router.push({ + uri: 'pages/CharacteristicOperationPage', + params: {device: this.device, characteristic: this.dlgInfo.characteristic, charaProp: this.dlgInfo.propList[which]} + }); + } + }), + autoCancel: true + }) + + private loadStrings() { + resmgr.getResourceManager().then(manager=>{ + manager.getString($r('app.string.characteristic').id).then(text=>{ + this.characteristic_name_text = text; + }) + }) + } + + build() { + Column() { + List(){ + ForEach(this.characteristicList.map((item1, index1)=> {return {index:index1, characteristic: item1};}), //(service: /*bluetooth.BLECharacteristic*/any, index) + item => { + ListItem() { + Row() { + Column(){ + Text(this.characteristic_name_text+'('+item.index+')').fontSize(14).fontWeight(FontWeight.Bold) + Text(item.characteristic.characteristicUuid).margin({top:5}).fontSize(12) + Text(this.characteristic_name_text+' ' + item.characteristic.propertyText).margin({top:5}).fontSize(12) + }.layoutWeight(1).padding({top:5, bottom: 5}).alignItems(HorizontalAlign.Start) + Image($r('app.media.ic_enter')).objectFit(ImageFit.Contain).width(15).height(15) + .visibility(item.characteristic.propertyText.length>0 ? Visibility.Visible : Visibility.None) + }.width('100%') + }.onClick(()=>this.onItemClick(item.characteristic)) + }, + item => item.toString()) + }.width('100%').layoutWeight(1).margin({top:15}).padding({left:10, right:10}) + .divider({ strokeWidth: 0.5, color: '#aaa' }) + } + .width('100%') + .height('100%') + } + + private getCharacteristicPropText(characteristic: bluetooth.BLECharacteristic): string { + let charaProp: number = 0xff/*characteristic.getProperties()*/; // TODO + let property: string = ''; + if ((charaProp & BluetoothGattCharacteristic.PROPERTY_READ) > 0) { + property = property.concat("Read"); + property = property.concat(" , "); + } + if ((charaProp & BluetoothGattCharacteristic.PROPERTY_WRITE) > 0) { + property = property.concat("Write"); + property = property.concat(" , "); + } + if ((charaProp & BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) > 0) { + property = property.concat("Write No Response"); + property = property.concat(" , "); + } + if ((charaProp & BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0) { + property = property.concat("Notify"); + property = property.concat(" , "); + } + if ((charaProp & BluetoothGattCharacteristic.PROPERTY_INDICATE) > 0) { + property = property.concat("Indicate"); + property = property.concat(" , "); + } + if (property.length > 1) { + property = property.substr(0, property.length - 2); + } + if (property.length > 0) { + property = "( " + property + ")"; + } + return property; + } + + private progressDialogCtrl: CustomDialogController = new CustomDialogController({ + builder: ProgressDialog() + }); + + private onItemClick(characteristic: bluetooth.BLECharacteristic): void { + let propList: Array = new Array(); + let propNameList: Array = new Array(); + let charaProp = 0xff;//characteristic.getProperties(); // TODO + if ((charaProp & BluetoothGattCharacteristic.PROPERTY_READ) > 0) { + propList.push(CharacteristicConsts.PROPERTY_READ); + propNameList.push("Read"); + } + if ((charaProp & BluetoothGattCharacteristic.PROPERTY_WRITE) > 0) { + propList.push(CharacteristicConsts.PROPERTY_WRITE); + propNameList.push("Write"); + } + if ((charaProp & BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) > 0) { + propList.push(CharacteristicConsts.PROPERTY_WRITE_NO_RESPONSE); + propNameList.push("Write No Response"); + } + if ((charaProp & BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0) { + propList.push(CharacteristicConsts.PROPERTY_NOTIFY); + propNameList.push("Notify"); + } + if ((charaProp & BluetoothGattCharacteristic.PROPERTY_INDICATE) > 0) { + propList.push(CharacteristicConsts.PROPERTY_INDICATE); + propNameList.push("Indicate"); + } + + console.log("onItemClick propList.length:"+propList.length); + if (propList.length > 1) { + this.dlgInfo.title = $r('app.string.select_operation_type'); + this.dlgInfo.propNameList = propNameList; + this.dlgInfo.propList = propList; + this.dlgInfo.characteristic = characteristic; + this.dialogController.open(); + } else if (propList.length > 0) { + router.push({ + uri: 'pages/CharacteristicOperationPage', + params: {device: this.device, characteristic: characteristic, charaProp: propList[0]} + }); + } + } + + private load(): void { + this.device = BleDevice.copy(router.getParams().device); + this.service = router.getParams().service; + this.characteristicList = this.service.characteristics; + for(let i=0; i{ + manager.getString($r('app.string.data_changed').id).then(text=>{ + this.title_text = this.characteristic.characteristicUuid + text; + }) + manager.getString($r('app.string.input_hex').id).then(text=>{ + this.input_hint = text; + }) + }) + } + + @State input_hint: string = ''; + @State input_text: string = ''; + @State input_text_visible: boolean = true; + @State button_text: Resource = $r('app.string.write'); + @State title_text: string = ''; + @State content_text: string = ''; + + build() { + Column() { + Row(){ + TextInput({ placeholder: this.input_hint, text: this.input_text }) + .layoutWeight(1) + .placeholderColor(Color.Gray) + .placeholderFont({ size: 14}) + .margin({right:10}) + .type(InputType.Normal) + .visibility(this.input_text_visible ? Visibility.Visible : Visibility.None) + .onChange((value: string) => { + this.input_text = value; + }) + Button(this.button_text) + .width('100') + .constraintSize({minWidth: 70}) + .height(80) + .fontSize(18) + .onClick(()=>{this.buttonClick()}) + }.width('100%').height('80') + Text(this.title_text).width('100%').padding(10).fontSize(12).fontWeight(FontWeight.Bold).textAlign(TextAlign.Start) + Text(this.content_text).width('100%').padding(10).fontSize(13) + } + .width('100%') + .height('100%') + .margin(10) + } + + private buttonClick() { + this.onclick(); + } + + private onclick: (event?: ClickEvent) => void = null; + + private load(): void { + this.device = BleDevice.copy(router.getParams().device); + this.characteristic = router.getParams().characteristic; + this.charaProp = router.getParams().charaProp; + console.log("charaUUID:" + this.characteristic.characteristicUuid + ", charaProp:"+this.charaProp); + + this.loadStrings(); + + let _this = this; + switch (this.charaProp) { + case CharacteristicConsts.PROPERTY_READ: { + console.log("load CharacteristicConsts.PROPERTY_READ"); + this.input_text_visible = false; + this.button_text = $r('app.string.read'); + this.onclick = () => { + console.log("PROPERTY_READ calling read serviceUuid:"+this.characteristic.serviceUuid+", characteristicUuid: "+this.characteristic.characteristicUuid); + BleManager.getInstance().read( + this.device, + this.characteristic.serviceUuid, + this.characteristic.characteristicUuid, + new class extends BleReadCallback { + + public onReadSuccess(data: Uint8Array): void { + console.log("PROPERTY_READ onReadSuccess"); +// setTimeout(()=> { + _this.appendDataText(HexUtil.formatHexString(data, true)); +// }); + } + + public onReadFailure(exception: BleException): void { + console.log("PROPERTY_READ onReadFailure" + exception.toString()); +// setTimeout(()=> { + _this.appendDataText(exception.toString()); +// }); + } + }); + }; + } + break; + + case CharacteristicConsts.PROPERTY_WRITE: { + console.log("load CharacteristicConsts.PROPERTY_WRITE serviceUuid:"+this.characteristic.serviceUuid+", characteristicUuid: "+this.characteristic.characteristicUuid); + this.input_text_visible = true; + this.button_text = $r('app.string.write'); + this.onclick = () => { + let hex: string = this.input_text; + if (TextUtils.isEmpty(hex)) { + return; + } + console.log("PROPERTY_WRITE calling write"); + BleManager.getInstance().write( + this.device, + this.characteristic.serviceUuid, + this.characteristic.characteristicUuid, + HexUtil.hexStringToBytes(hex), + true, + true, + 0, + new class extends BleWriteCallback { + + public onWriteSuccess(current: number, total: number, justWrite: Uint8Array): void { + console.log("PROPERTY_WRITE onWriteSuccess"); +// setTimeout(()=> { + _this.appendDataText("write success, current: " + current + + " total: " + total + + " justWrite: " + HexUtil.formatHexString(justWrite, true)); +// }); + } + + public onWriteFailure(exception: BleException): void { + console.log("PROPERTY_WRITE onWriteFailure"); +// setTimeout(()=> { + _this.appendDataText(exception.toString()); +// }); + } + }); + }; + } + break; + + case CharacteristicConsts.PROPERTY_WRITE_NO_RESPONSE: { + console.log("load CharacteristicConsts.PROPERTY_WRITE_NO_RESPONSE"); + this.input_text_visible = true; + this.button_text = $r('app.string.write'); + this.onclick = () => { + let hex: string = this.input_text; + if (TextUtils.isEmpty(hex)) { + return; + } + console.log("PROPERTY_WRITE_NO_RESPONSE calling write serviceUuid:"+this.characteristic.serviceUuid+", characteristicUuid: "+this.characteristic.characteristicUuid); + BleManager.getInstance().write( + this.device, + this.characteristic.serviceUuid, + this.characteristic.characteristicUuid, + HexUtil.hexStringToBytes(hex), + true, + true, + 0, + new class extends BleWriteCallback { + + public onWriteSuccess(current: number, total: number, justWrite: Uint8Array): void { + console.log("PROPERTY_WRITE_NO_RESPONSE onWriteFailure"); +// setTimeout(()=> { + _this.appendDataText("write success, current: " + current + + " total: " + total + + " justWrite: " + HexUtil.formatHexString(justWrite, true)); +// }); + } + + public onWriteFailure(exception: BleException): void { + console.log("PROPERTY_WRITE_NO_RESPONSE onWriteFailure"); +// setTimeout(()=> { + _this.appendDataText(exception.toString()); +// }); + } + }); + }; + } + break; + + case CharacteristicConsts.PROPERTY_NOTIFY: { + console.log("load CharacteristicConsts.PROPERTY_NOTIFY"); + this.input_text_visible = false; + this.button_text = $r('app.string.open_notification'); + this.onclick = () => { + if (this.button_text.id == $r('app.string.open_notification').id) { + this.button_text = $r('app.string.close_notification'); + console.log("PROPERTY_NOTIFY calling notify serviceUuid:"+this.characteristic.serviceUuid+", characteristicUuid: "+this.characteristic.characteristicUuid); + BleManager.getInstance().notify( + this.device, + this.characteristic.serviceUuid, + this.characteristic.characteristicUuid, + false, + new class extends BleNotifyCallback { + + public onNotifySuccess() { + console.log("PROPERTY_NOTIFY onNotifySuccess"); +// setTimeout(()=> { + _this.appendDataText("notify success"); +// }); + } + + public onNotifyFailure(exception: BleException) { + console.log("PROPERTY_NOTIFY onNotifyFailure"); +// setTimeout(()=> { + _this.appendDataText(exception.toString()); +// }); + } + + public onCharacteristicChanged(data: Uint8Array) { + console.log("PROPERTY_NOTIFY onCharacteristicChanged"); +// setTimeout(()=> { + _this.appendDataText(HexUtil.formatHexString(new Uint8Array(_this.characteristic.characteristicValue), true)); +// }); + } + }); + } else { + this.button_text = $r('app.string.open_notification'); + console.log("PROPERTY_NOTIFY calling stopNotify serviceUuid:"+this.characteristic.serviceUuid+", characteristicUuid: "+this.characteristic.characteristicUuid); + BleManager.getInstance().stopNotify( + this.device, + this.characteristic.serviceUuid, + this.characteristic.characteristicUuid); + } + }; + } + break; + + case CharacteristicConsts.PROPERTY_INDICATE: { + console.log("load CharacteristicConsts.PROPERTY_INDICATE"); + this.input_text_visible = false; + this.button_text = $r('app.string.open_notification'); + this.onclick = () => { + if (this.button_text.id == $r('app.string.open_notification').id) { + this.button_text = $r('app.string.close_notification'); + console.log("PROPERTY_INDICATE calling indicate serviceUuid:"+this.characteristic.serviceUuid+", characteristicUuid: "+this.characteristic.characteristicUuid); + BleManager.getInstance().indicate( + this.device, + this.characteristic.serviceUuid, + this.characteristic.characteristicUuid, + false, + new class extends BleIndicateCallback { + + public onIndicateSuccess(): void { + console.log("PROPERTY_INDICATE onIndicateSuccess"); +// setTimeout(()=> { + _this.appendDataText("indicate success"); +// }); + } + + public onIndicateFailure(exception: BleException): void { + console.log("PROPERTY_INDICATE onIndicateFailure"); +// setTimeout(()=> { + _this.appendDataText(exception.toString()); +// }); + } + + public onCharacteristicChanged(data: Uint8Array): void { + console.log("PROPERTY_INDICATE onCharacteristicChanged"); +// setTimeout(()=> { + _this.appendDataText(HexUtil.formatHexString(new Uint8Array(_this.characteristic.characteristicValue), true)); +// }); + } + }); + } else { + this.button_text = $r('app.string.open_notification'); + console.log("PROPERTY_INDICATE calling stopIndicate serviceUuid:"+this.characteristic.serviceUuid+", characteristicUuid: "+this.characteristic.characteristicUuid); + BleManager.getInstance().stopIndicate( + this.device, + this.characteristic.serviceUuid, + this.characteristic.characteristicUuid); + } + }; + } + break; + } + } + + private appendDataText(content: string): void { + this.content_text += content; + this.content_text += "\n"; + } + + private aboutToAppear() { + this.load(); + } + + private aboutToDisappear() { + } +} diff --git a/entry/src/main/ets/MainAbility/pages/ListDialog.ets b/entry/src/main/ets/MainAbility/pages/ListDialog.ets new file mode 100644 index 0000000000000000000000000000000000000000..be990119e822ac4e61475643c837a96cbba9b914 --- /dev/null +++ b/entry/src/main/ets/MainAbility/pages/ListDialog.ets @@ -0,0 +1,26 @@ +@CustomDialog +struct ListDialog { + controller: CustomDialogController + title: string | Resource = 'title'; + items: string[] = ['item1', 'item2']; + onclick: (which) => void + + build() { + Column() { + Text(this.title).fontSize(20).fontWeight(FontWeight.Bold) + List(){ + ForEach(this.items.map((text1, index1)=> {return {index:index1, text: text1};}), + item => { + ListItem() { + Column(){ + Text(item.text).fontSize(18).height(40) + }.width('100%').padding({top:5, bottom: 5}).alignItems(HorizontalAlign.Start) + }.onClick(()=>this.onclick(item.index)) + }, + item => item.toString()) + }.width('100%') + }.margin(10) + } +} + +export default ListDialog; \ No newline at end of file diff --git a/entry/src/main/ets/MainAbility/pages/ProgressDialog.ets b/entry/src/main/ets/MainAbility/pages/ProgressDialog.ets new file mode 100644 index 0000000000000000000000000000000000000000..38fa67bbd46bfd659f91ecbaf5128e0036eec038 --- /dev/null +++ b/entry/src/main/ets/MainAbility/pages/ProgressDialog.ets @@ -0,0 +1,16 @@ +@CustomDialog +struct ProgressDialog { + controller: CustomDialogController + + build() { + Stack() { + LoadingProgress() + .width(100) + .height(100) + .color(Color.Blue) + .alignSelf(ItemAlign.Center) + } + } +} + +export default ProgressDialog; \ No newline at end of file diff --git a/entry/src/main/ets/MainAbility/pages/ServiceListPage.ets b/entry/src/main/ets/MainAbility/pages/ServiceListPage.ets new file mode 100644 index 0000000000000000000000000000000000000000..d38092db39a7300463ca37beb009a050f4e94e78 --- /dev/null +++ b/entry/src/main/ets/MainAbility/pages/ServiceListPage.ets @@ -0,0 +1,97 @@ +import router from '@system.router' +import resmgr from '@ohos.resourceManager'; +import bluetooth from '@ohos.bluetooth'; + +import {BleDevice} from '@ohos/wxFastBle' +import {BleManager} from '@ohos/wxFastBle' + + +@Entry +@Preview +@Component +struct Page { + @State private device_name_text: string = ''; + @State private mac_text: string = ''; + @State private service_text: string = ''; + + private device: BleDevice; + @State private serviceList: bluetooth.GattService[] = []; + + private loadStrings() { + resmgr.getResourceManager().then(manager=>{ + manager.getString($r('app.string.name').id).then(text=>{ + this.device_name_text = text + this.device.getName(); + }) + manager.getString($r('app.string.mac').id).then(text=>{ + this.mac_text = text + this.device.getMac(); + }) + manager.getString($r('app.string.service').id).then(text=>{ + this.service_text = text; + }) + }) + } + + build() { + Column() { + Column() { + Text(this.device_name_text).margin({top:10}).padding({left:10, right: 10}).fontSize(14).fontWeight(FontWeight.Bold) + Text(this.mac_text).margin({top:5}).padding({left:10, right: 10}).fontSize(14).fontWeight(FontWeight.Bold) + }.width('100%').alignItems(HorizontalAlign.Start) + List(){ + ForEach(this.serviceList.map((item1, index1)=> {return {index:index1, service: item1};}), //(service: /*bluetooth.GattService*/any, index) + item => { + ListItem() { + Row() { + Column(){ + Text(this.service_text+'('+item.index+')').fontSize(14).fontWeight(FontWeight.Bold) + Text(item.service.serviceUuid).margin({top:5}).fontSize(12) + Text($r('app.string.type')).margin({top:5}).fontSize(12) + }.layoutWeight(1).padding({top:5, bottom: 5}).alignItems(HorizontalAlign.Start) + Image($r('app.media.ic_enter')).objectFit(ImageFit.Contain).width(15).height(15).margin({right: 10}) + }.width('100%') + }.onClick(()=>this.onItemClick(item.service)) + }, + item => item.toString()) + }.width('100%').layoutWeight(1).margin({top:15}).padding({left:10, right:10}) + .divider({ strokeWidth: 0.5, color: '#aaa' }) + } + .width('100%') + .height('100%') + } + + private onItemClick(service: bluetooth.GattService): void { + console.log("onItemClick service:"+service.serviceUuid); + router.push({ + uri: 'pages/CharacteristicListPage', + params: {device: this.device, service: service} + }); + } + + private load(): void { + this.device = BleDevice.copy(router.getParams().device); + + this.loadStrings(); + + if(BleManager.MOCK_DEVICE) { + for(let i=0; i<10; i++) { + let characteristicList = []; + for(let j=0; j<10; j++) { + characteristicList.push({characteristicUuid: '0000-8812-3352-3235'}); + } + this.serviceList.push({serviceUuid: '0000-8812-1000-0025', isPrimary: true, characteristics: characteristicList}); + } + return; + } + + BleManager.getInstance().getBluetoothGattServices(this.device, (err, services: bluetooth.GattService[])=>{ + this.serviceList = services; + }); + } + + private aboutToAppear() { + this.load(); + } + + private aboutToDisappear() { + } +} diff --git a/entry/src/main/ets/MainAbility/pages/Utils.ets b/entry/src/main/ets/MainAbility/pages/Utils.ets new file mode 100644 index 0000000000000000000000000000000000000000..c6536fc8293bc210b8269ca57b2c25e14ac5ffcb --- /dev/null +++ b/entry/src/main/ets/MainAbility/pages/Utils.ets @@ -0,0 +1,75 @@ +export class ArrayHelper { + public static add(array: Array, elem: T) { + array.push(elem); + } + + public static contains(array: Array, elem: T): boolean { + return array.indexOf(elem)>=0; + } + + public static remove(array: Array, elem: T) { + let index = array.indexOf(elem); + if(index>=0) { + delete array[index]; + } + } + + public static removeIndex(array: Array, index: number) { + if(index>=0 && index < array.length) { + array.splice(index, 1); + } + } +} + +import ability_featureAbility from '@ohos.ability.featureAbility' + +export class PermissionHelper { + + public static checkPermissions(permissions: string[], callback: (results: number[]) => void) { + let context = ability_featureAbility.getContext(); + let results = new Array(permissions.length); + let count = 0; + + for(let i=0; i{ + results[i] = result; // 0 permitted, -1 else. + count++; + if(count == permissions.length) { + callback(results); + } + }); + }; + } + + public static requestPermissions(permissions: string[], callback: (results: number[]) => void) { + let context = ability_featureAbility.getContext(); + PermissionHelper.checkPermissions(permissions, results1=> { + let requests: string[] = []; + for(let i=0; i{ + let results: number[] = results1; + for (let i = 0; i < requests.length; i++) { + let index = permissions.indexOf(requests[i]); + results[index] = results2.authResults[i]; + } + callback(results); + }); + }) + } +} + +export class TextUtils{ + public static isEmpty(text: string) { + return text == null || text == undefined || text.length==0; + } +} diff --git a/entry/src/main/ets/MainAbility/pages/index.ets b/entry/src/main/ets/MainAbility/pages/index.ets new file mode 100644 index 0000000000000000000000000000000000000000..0ec717921765413bb4e5b4fad489d796631dccd7 --- /dev/null +++ b/entry/src/main/ets/MainAbility/pages/index.ets @@ -0,0 +1,419 @@ +import router from '@system.router' +import bluetooth from '@ohos.bluetooth'; +import geolocation from '@ohos.geolocation'; +import ability_featureAbility from '@ohos.ability.featureAbility' +import prompt from '@system.prompt'; +import resmgr from '@ohos.resourceManager'; + +import {BleManager} from '@ohos/wxFastBle' +import {BleException} from '@ohos/wxFastBle' +import {BleDevice} from '@ohos/wxFastBle' +import {BleScanCallback} from '@ohos/wxFastBle'; +import {BleGattCallback} from '@ohos/wxFastBle'; +import {BleScanRuleConfig} from '@ohos/wxFastBle' +import {ArrayHelper, PermissionHelper} from './Utils' +import ProgressDialog from './ProgressDialog' + +@Entry +@Preview +@Component +struct Page { + @State private edit_text_name_hint: string = ''; + @State private edit_text_mac_hint: string = ''; + @State private edit_text_uuid_hint: string = ''; + + @State edit_text_uuid: string = ''; + @State edit_text_name: string = ''; + @State edit_text_mac: string = ''; + @State switch_auto_connect: boolean = false; + + @State setting_visibility: Visibility = Visibility.None; + @State search_setting_text: Resource = $r('app.string.expand_search_settings'); + @State btn_scan_text: Resource = $r('app.string.start_scan'); + @State is_loading: boolean = false; + @State loading_rotate: number = 0; + + @State private toast_open_bluetooth: string = ''; + @State private toast_connect_fail: string = ''; + @State private toast_disconnected: string = ''; + @State private toast_active_disconnected: string = ''; + + @State bleDeviceList: BleDevice[] = []; + @State connectedDevices: string[] = []; + + private loadStrings() { + resmgr.getResourceManager().then(manager=>{ + manager.getString($r('app.string.setting_name').id).then(text=>{ + this.edit_text_name_hint = text; + }) + manager.getString($r('app.string.setting_mac').id).then(text=>{ + this.edit_text_mac_hint = text; + }) + manager.getString($r('app.string.setting_uuid').id).then(text=>{ + this.edit_text_uuid_hint = text; + }) + manager.getString($r('app.string.please_open_blue').id).then(text=>{ + this.toast_open_bluetooth = text; + }) + manager.getString($r('app.string.connect_fail').id).then(text=>{ + this.toast_connect_fail = text; + }) + manager.getString($r('app.string.disconnected').id).then(text=>{ + this.toast_disconnected = text; + }) + manager.getString($r('app.string.active_disconnected').id).then(text=>{ + this.toast_active_disconnected = text; + }) + }) + } + + build() { + Column() { + Column() { + Text($r('app.string.scan_setting')).fontSize(14).fontColor($r('app.color.colorPrimary')).margin({bottom:10}) + TextInput({ placeholder: this.edit_text_name_hint, text: this.edit_text_name }) + .type(InputType.Normal) + .placeholderColor(Color.Gray) + .placeholderFont({ size: 14}) + .margin({bottom:10}) + .onChange((value: string) => { + this.edit_text_name = value; + }) + TextInput({ placeholder: this.edit_text_mac_hint, text: this.edit_text_mac }) + .type(InputType.Normal) + .placeholderColor(Color.Gray) + .placeholderFont({ size: 14}) + .margin({bottom:10}) + .onChange((value: string) => { + this.edit_text_mac = value; + }) + TextInput({ placeholder: this.edit_text_uuid_hint, text: this.edit_text_uuid }) + .type(InputType.Normal) + .placeholderColor(Color.Gray) + .placeholderFont({ size: 14}) + .margin({bottom:10}) + .onChange((value: string) => { + this.edit_text_uuid = value; + }) + Text('AutoConnect ?').fontSize(14) + }.width('100%').alignItems(HorizontalAlign.Start).padding(20).visibility(this.setting_visibility) + Button(this.search_setting_text) + .onClick(()=>this.onSearchSettingClick()) + .fontSize(18) + .height(80) + .margin({bottom:10}) + Stack() { + Button(this.btn_scan_text) + .onClick(()=>this.onSearchClick()) + .fontSize(18) + .height(80) + .fontWeight(FontWeight.Bold) + LoadingProgress().width(40).height('100%').align(Alignment.Center) + .color($r('app.color.colorPrimary')) + .markAnchor({ x: 40+10, y: 0 }) + .position({ x: '100%', y: 0 }) + .visibility(this.is_loading ? Visibility.Visible : Visibility.None) +// Image($r('app.media.ic_loading')).width(30).height('100%').align(Alignment.Center) +// .markAnchor({ x: 30+10, y: 0 }) +// .position({ x: '100%', y: 0 }) +// .objectFit(ImageFit.Contain) +// .rotate({x:0, y:0, z:1, centerX:'50%', centerY:'50%', angle:this.loading_rotate}) +// .animation({duration: 1000, iterations: -1, curve: Curve.Linear}) +// .visibility(this.is_loading ? Visibility.Visible : Visibility.None) + }.width('100%') + List(){ + ForEach(this.bleDeviceList, (device: BleDevice) => { + ListItem() { + Row() { + if (this.isConnected(device)) { + Image($r('app.media.ic_blue_connected')).objectFit(ImageFit.None).width(30).height(30) + Column(){ + Text(device.getName()).margin({bottom:2}).fontSize(14).fontColor($r('app.color.colorPrimary')) + Text(device.getMac()).margin({top:2}).fontSize(12).fontColor($r('app.color.colorPrimary')) + }.layoutWeight(1).margin({left:10}).alignItems(HorizontalAlign.Start) + Row() { + Text($r('app.string.connected')).fontColor($r('app.color.colorPrimary')).fontSize(14) + Button($r('app.string.disconnect')).width(100).height(80).margin({left: 5}).fontSize(12).onClick(()=>this.onDisconnectClick(device)) + Button($r('app.string.enter')).width(100).height(80).margin({left: 5}).fontSize(12).onClick(()=>this.onDetailClick(device)) + } + } else { + Image($r('app.media.ic_blue_remote')).objectFit(ImageFit.None).width(30).height(30) + Column(){ + Text(device.getName()).margin({bottom:2}).fontSize(14) + Text(device.getMac()).margin({top:2}).fontSize(12) + }.layoutWeight(1).margin({left:10}).alignItems(HorizontalAlign.Start) + Row() { + Text('' + device.getRssi()).fontSize(14) + Image($r('app.media.ic_rssi')).objectFit(ImageFit.ScaleDown).width(18).height(18).margin({left: 5}); + Button($r('app.string.connect')).width(100).height(80).margin({left: 5}).fontSize(12).onClick(()=>this.onConnectClick(device)) + } + } + }.width('100%') + } + }, (item: number) => item.toString()) + }.width('100%').layoutWeight(1).margin({top:10}).padding({left:5, right:5}) + .divider({ strokeWidth: 0.5, color: '#aaa' }) + } + .width('100%') + .height('100%') + } + + private isConnected(device: BleDevice): boolean { + return ArrayHelper.contains(this.connectedDevices, device.getMac()); + } + + private onSearchSettingClick(): void { + if(this.setting_visibility == Visibility.Visible) { + this.setting_visibility = Visibility.None; + this.search_setting_text = $r('app.string.expand_search_settings') + }else { + this.setting_visibility = Visibility.Visible; + this.search_setting_text = $r('app.string.retrieve_search_settings') + } + } + + private onSearchClick(): void { + if (this.btn_scan_text.id == $r('app.string.start_scan').id) { + if(BleManager.MOCK_DEVICE) { + this.mockCheckPermissions(); + } else { + this.checkPermissions(); + } + } else if (this.btn_scan_text.id == $r('app.string.stop_scan').id) { + BleManager.getInstance().cancelScan(); + } + } + + private onConnectClick(device: BleDevice) { + if (!BleManager.getInstance().isConnected(device)) { + BleManager.getInstance().cancelScan(); + this.connect(device); + } + } + + private onDisconnectClick(device: BleDevice) { + if (BleManager.MOCK_DEVICE || BleManager.getInstance().isConnected(device)) { + BleManager.getInstance().disconnect(device); + } + } + + private onDetailClick(device: BleDevice) { + console.log("onDetailClick deviceName:"+device.getName()); + router.push({ + uri: 'pages/ServiceListPage', + params: {device: device} + }); + } + + private mockCheckPermissions(): void { + this.doScan(); + } + + private checkPermissions(): void { + let result = bluetooth.getState(); + console.log("checkPermissions bluetooth.state: "+ result); + if(result == bluetooth.BluetoothState.STATE_OFF) { + prompt.showToast({message: this.toast_open_bluetooth, duration: 300,}) + return; + } + + let context = ability_featureAbility.getContext(); + let permissions = ["ohos.permission.LOCATION"]; + PermissionHelper.requestPermissions(permissions, results => { + for(let i=0; ivoid): void { + console.log("calling checkGPS") + geolocation.isLocationEnabled((err, enabled) => { + console.log("isLocationEnabled err:"+err+", enabled:"+enabled) + callback(!err ? enabled : false); + }); + } + + private onPermissionGranted(permission: string): void { + console.log("calling onPermissionGranted") +// this.checkGPS(result => { +// console.log("checkGPS result:"+result) +// if(result) { + this.doScan(); +// }else{ +// AlertDialog.show({ +// title: $r('app.string.notifyTitle'), +// message: $r('app.string.gpsNotifyMsg'), +// confirm: { +// value: $r('app.string.ok'), +// action: null +// } +// }) +// } +// }); + } + + private doScan() { + this.setScanRule(); + this.startScan(); + } + + private setScanRule(): void { + let uuids: string[]; + let str_uuid: string = this.edit_text_uuid; + if (!str_uuid) { + uuids = null; + } else { + uuids = str_uuid.split(","); + } + let serviceUuids: string[] = null; + if (uuids != null && uuids.length > 0) { + serviceUuids = new Array[uuids.length]; + for (let i = 0; i < uuids.length; i++) { + let name: string = uuids[i]; + let components: string[] = name.split("-"); + if (components.length != 5) { + serviceUuids[i] = null; + } else { + serviceUuids[i] = uuids[i]; + } + } + } + + let names: string[]; + let str_name: string = this.edit_text_name; + if (!str_name) { + names = null; + } else { + names = str_name.split(","); + } + + let mac: string = this.edit_text_mac; + + let isAutoConnect: boolean = this.switch_auto_connect; + + let scanRuleConfig: BleScanRuleConfig = new BleScanRuleConfig.Builder() + .setServiceUuids(serviceUuids) // 只扫描指定的服务的设备,可选 + .setDeviceName(true, names) // 只扫描指定广播名的设备,可选 + .setDeviceMac(mac) // 只扫描指定mac的设备,可选 + .setAutoConnect(isAutoConnect) // 连接时的autoConnect参数,可选,默认false + .setScanTimeOut(10000) // 扫描超时时间,可选,默认10秒 + .build(); + BleManager.getInstance().initScanRule(scanRuleConfig); + } + + + public clearConnectedDevice() { + for (let i = 0; i < this.bleDeviceList.length; i++) { + let device: BleDevice = this.bleDeviceList[i]; + if (BleManager.getInstance().isConnected(device)) { + ArrayHelper.removeIndex(this.bleDeviceList, i); + } + } + } + + public clearScanDevice() { + for (let i = 0; i < this.bleDeviceList.length; i++) { + let device: BleDevice = this.bleDeviceList[i]; + if (!BleManager.getInstance().isConnected(device)) { + ArrayHelper.removeIndex(this.bleDeviceList, i); + } + } + } + + public clear() { + this.bleDeviceList = []; + } + + private startScan(): void { + let _this = this; + console.log("startScan"); + BleManager.getInstance().scan(new class extends BleScanCallback { + //@Override + onScanStarted(success: boolean): void { + console.log("onScanStarted success:"+success); + _this.clearScanDevice(); + _this.is_loading = true; + _this.loading_rotate = 360; + _this.btn_scan_text = $r('app.string.stop_scan'); + _this.connectedDevices = BleManager.getInstance().getAllConnectedDevice(); + } + + //@Override + onLeScan(bleDevice: BleDevice): void { + console.log("onLeScan"); + } + + //@Override + onScanning(bleDevice: BleDevice): void { + console.log("onScanning"); + ArrayHelper.add(_this.bleDeviceList, bleDevice); + } + + //@Override + onScanFinished(scanResultList: Array): void { + console.log("onScanFinished"); + _this.is_loading = false; + _this.loading_rotate = 0; + _this.btn_scan_text = $r('app.string.start_scan'); + _this.connectedDevices = BleManager.getInstance().getAllConnectedDevice(); + } + }); + } + + private progressDialogCtrl: CustomDialogController = new CustomDialogController({ + builder: ProgressDialog() + }); + + private connect(bleDevice: BleDevice): void { + let _this = this; + BleManager.getInstance().connect(bleDevice, new class extends BleGattCallback { + public onStartConnect(): void { + _this.progressDialogCtrl.open(); + } + + public onConnectFail(bleDevice: BleDevice, exception: BleException): void { + _this.progressDialogCtrl.close(); + prompt.showToast({message: _this.toast_connect_fail, duration: 300,}) + } + + public onConnectSuccess(bleDevice: BleDevice, gatt: bluetooth.GattClientDevice, status: number): void { + _this.progressDialogCtrl.close(); +// if (BleManager.MOCK_DEVICE) { + ArrayHelper.add(_this.connectedDevices, bleDevice.getMac()); +// } else { +// _this.connectedDevices = BleManager.getInstance().getAllConnectedDevice(); +// } + } + + public onDisConnected(isActiveDisConnected: boolean, device: BleDevice, gatt: bluetooth.GattClientDevice, status: number): void { + _this.progressDialogCtrl.close(); + _this.connectedDevices = BleManager.getInstance().getAllConnectedDevice(); + + if (isActiveDisConnected) { + prompt.showToast({message: _this.toast_active_disconnected, duration: 300,}) + } else { + prompt.showToast({message: _this.toast_disconnected, duration: 300,}) + } + } + }); + } + + private aboutToAppear() { + BleManager.getInstance().init(); + BleManager.getInstance() + .enableLog(true) + .setReConnectCount(1, 5000) + .setConnectOverTime(20000) + .setOperateTimeout(5000); + this.loadStrings(); + } + + private aboutToDisappear() { + BleManager.getInstance().disconnectAllDevice(); + BleManager.getInstance().destroy(); + } +} diff --git a/entry/src/main/resources/base/element/color.json b/entry/src/main/resources/base/element/color.json new file mode 100644 index 0000000000000000000000000000000000000000..e707df728447f70bb32873a67468844a97472841 --- /dev/null +++ b/entry/src/main/resources/base/element/color.json @@ -0,0 +1,16 @@ +{ + "color": [ + { + "name": "colorPrimary", + "value": "#1DE9B6" + }, + { + "name": "colorPrimaryDark", + "value": "#1DE9B6" + }, + { + "name": "colorAccent", + "value": "#FF5722" + } + ] +} \ No newline at end of file diff --git a/entry/src/main/resources/base/element/string.json b/entry/src/main/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..b178bdc8c424b77e1fb22ac4920fa5a2a6d283a5 --- /dev/null +++ b/entry/src/main/resources/base/element/string.json @@ -0,0 +1,152 @@ +{ + "string": [ + { + "name": "app_name", + "value": "FastBLE" + }, + { + "name": "notifyTitle", + "value": "提示" + }, + { + "name": "gpsNotifyMsg", + "value": "当前手机扫描蓝牙需要打开定位功能。" + }, + { + "name": "setting", + "value": "前往设置" + }, + { + "name": "cancel", + "value": "取消" + }, + { + "name": "start_scan", + "value": "开始扫描" + }, + { + "name": "stop_scan", + "value": "停止扫描" + }, + { + "name": "expand_search_settings", + "value": "展开搜索设置" + }, + { + "name": "retrieve_search_settings", + "value": "收起搜索设置" + }, + { + "name": "connect_fail", + "value": "连接失败" + }, + { + "name": "active_disconnected", + "value": "断开了" + }, + { + "name": "disconnected", + "value": "连接断开" + }, + { + "name": "please_open_blue", + "value": "请先打开蓝牙" + }, + { + "name": "select_operation_type", + "value": "选择操作类型" + }, + { + "name": "characteristic", + "value": "特性" + }, + { + "name": "data_changed", + "value": "的数据变化:" + }, + { + "name": "read", + "value": "读" + }, + { + "name": "write", + "value": "写" + }, + { + "name": "open_notification", + "value": "打开通知" + }, + { + "name": "close_notification", + "value": "关闭通知" + }, + { + "name": "service_list", + "value": "服务列表" + }, + { + "name": "characteristic_list", + "value": "特征列表" + }, + { + "name": "console", + "value": "操作控制台" + }, + { + "name": "name", + "value": "设备广播名:" + }, + { + "name": "mac", + "value": "MAC:" + }, + { + "name": "service", + "value": "服务" + }, + { + "name": "type", + "value": "服务类型(主服务)" + }, + { + "name": "scan_setting", + "value": "在下面你可以配置需要扫描的设备的条件,可以为空" + }, + { + "name": "setting_name", + "value": "输入蓝牙设备广播名,多个之间以英文逗号隔开" + }, + { + "name": "setting_mac", + "value": "输入蓝牙设备mac地址" + }, + { + "name": "setting_uuid", + "value": "输入蓝牙设备UUID,多个之间以英文逗号隔开" + }, + { + "name": "input_hex", + "value": "请输入HEX格式指令" + }, + { + "name": "connect", + "value": "连接" + }, + { + "name": "connected", + "value": "已连接" + }, + { + "name": "disconnect", + "value": "断开连接" + }, + { + "name": "enter", + "value": "进入操作" + }, + { + "name": "ok", + "value": "确定" + } + ] +} \ No newline at end of file diff --git a/entry/src/main/resources/base/media/icon.png b/entry/src/main/resources/base/media/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ce307a8827bd75456441ceb57d530e4c8d45d36c Binary files /dev/null and b/entry/src/main/resources/base/media/icon.png differ diff --git a/entry/src/main/resources/mdpi/media/ic_launcher.png b/entry/src/main/resources/mdpi/media/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..6b6fa0d61999ca0216cea8d7931e92e8a5188e27 Binary files /dev/null and b/entry/src/main/resources/mdpi/media/ic_launcher.png differ diff --git a/entry/src/main/resources/xldpi/media/ic_launcher.png b/entry/src/main/resources/xldpi/media/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..d098226b9e142e9e370f5f3e6d9812142f1185b7 Binary files /dev/null and b/entry/src/main/resources/xldpi/media/ic_launcher.png differ diff --git a/entry/src/main/resources/xxldpi/media/ic_blue_connected.png b/entry/src/main/resources/xxldpi/media/ic_blue_connected.png new file mode 100644 index 0000000000000000000000000000000000000000..47b91920f506a893f87a23a0d82ec22be0602d2c Binary files /dev/null and b/entry/src/main/resources/xxldpi/media/ic_blue_connected.png differ diff --git a/entry/src/main/resources/xxldpi/media/ic_blue_remote.png b/entry/src/main/resources/xxldpi/media/ic_blue_remote.png new file mode 100644 index 0000000000000000000000000000000000000000..3fb4ef894cfb4340b213a956787debca471305b6 Binary files /dev/null and b/entry/src/main/resources/xxldpi/media/ic_blue_remote.png differ diff --git a/entry/src/main/resources/xxldpi/media/ic_enter.png b/entry/src/main/resources/xxldpi/media/ic_enter.png new file mode 100644 index 0000000000000000000000000000000000000000..00ce252d2f6b475cbf31696767a72ec7f317eccd Binary files /dev/null and b/entry/src/main/resources/xxldpi/media/ic_enter.png differ diff --git a/entry/src/main/resources/xxldpi/media/ic_launcher.png b/entry/src/main/resources/xxldpi/media/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..e1b51615e8990ff9fec33c14bda9603cc5b0f2a7 Binary files /dev/null and b/entry/src/main/resources/xxldpi/media/ic_launcher.png differ diff --git a/entry/src/main/resources/xxldpi/media/ic_loading.png b/entry/src/main/resources/xxldpi/media/ic_loading.png new file mode 100644 index 0000000000000000000000000000000000000000..6edbeaa8a43cb55f47845ae7350efedebf990d87 Binary files /dev/null and b/entry/src/main/resources/xxldpi/media/ic_loading.png differ diff --git a/entry/src/main/resources/xxldpi/media/ic_rssi.png b/entry/src/main/resources/xxldpi/media/ic_rssi.png new file mode 100644 index 0000000000000000000000000000000000000000..fe7af6b5ad7822737abb783123e79b313bba8d22 Binary files /dev/null and b/entry/src/main/resources/xxldpi/media/ic_rssi.png differ diff --git a/entry/src/main/resources/xxldpi/media/ic_scan.png b/entry/src/main/resources/xxldpi/media/ic_scan.png new file mode 100644 index 0000000000000000000000000000000000000000..e3f02539bbb64ba76a9951b991146a70f58a2b58 Binary files /dev/null and b/entry/src/main/resources/xxldpi/media/ic_scan.png differ diff --git a/entry/src/main/resources/xxxldpi/media/ic_launcher.png b/entry/src/main/resources/xxxldpi/media/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..182f68b5ceb27ef1dc825a2113f406487bbdcbe9 Binary files /dev/null and b/entry/src/main/resources/xxxldpi/media/ic_launcher.png differ diff --git a/entry/src/ohosTest/config.json b/entry/src/ohosTest/config.json new file mode 100644 index 0000000000000000000000000000000000000000..63dcd79eaeb1ecacf731ba2a1637a8b82f7068fb --- /dev/null +++ b/entry/src/ohosTest/config.json @@ -0,0 +1,68 @@ +{ + "app": { + "bundleName": "com.hihope.blesample", + "vendor": "example", + "version": { + "code": 1000000, + "name": "1.0.0" + } + }, + "deviceConfig": {}, + "module": { + "package": "com.example.entry_test", + "name": ".entry_test", + "mainAbility": ".TestAbility", + "srcPath": "", + "deviceType": [ + "phone", + "tablet" + ], + "distro": { + "deliveryWithInstall": true, + "moduleName": "entry_test", + "moduleType": "feature", + "installationFree": false + }, + "abilities": [ + { + "skills": [ + { + "entities": [ + "entity.system.home" + ], + "actions": [ + "action.system.home" + ] + } + ], + "orientation": "unspecified", + "visible": true, + "srcPath": "TestAbility", + "name": ".TestAbility", + "srcLanguage": "ets", + "icon": "$media:icon", + "description": "$string:description_TestAbility", + "formsEnabled": false, + "label": "$string:entry_TestAbility", + "type": "page", + "launchType": "standard" + } + ], + "js": [ + { + "mode": { + "syntax": "ets", + "type": "pageAbility" + }, + "pages": [ + "pages/index" + ], + "name": ".TestAbility", + "window": { + "designWidth": 720, + "autoDesignWidth": false + } + } + ] + } +} \ No newline at end of file diff --git a/entry/src/ohosTest/ets/TestAbility/app.ets b/entry/src/ohosTest/ets/TestAbility/app.ets new file mode 100644 index 0000000000000000000000000000000000000000..4db82cf120fbec47c43b3086571011383464e10a --- /dev/null +++ b/entry/src/ohosTest/ets/TestAbility/app.ets @@ -0,0 +1,18 @@ +import AbilityDelegatorRegistry from '@ohos.application.abilityDelegatorRegistry' +import { Hypium } from 'hypium/index' +import testsuite from '../test/List.test' + +export default { + onCreate() { + console.info('Application onCreate') + var abilityDelegator: any + abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator() + var abilityDelegatorArguments: any + abilityDelegatorArguments = AbilityDelegatorRegistry.getArguments() + console.info('start run testcase!!!') + Hypium.hypiumTest(abilityDelegator, abilityDelegatorArguments, testsuite) + }, + onDestroy() { + console.info('Application onDestroy') + }, +} \ No newline at end of file diff --git a/entry/src/ohosTest/ets/TestAbility/pages/index.ets b/entry/src/ohosTest/ets/TestAbility/pages/index.ets new file mode 100644 index 0000000000000000000000000000000000000000..539eb9ae7b7cb0870b26b1a69d20fb977b55291d --- /dev/null +++ b/entry/src/ohosTest/ets/TestAbility/pages/index.ets @@ -0,0 +1,35 @@ +import router from '@system.router'; + +@Entry +@Component +struct Index { + aboutToAppear() { + console.info('TestAbility index aboutToAppear') + } + + @State message: string = 'Hello World' + build() { + Row() { + Column() { + Text(this.message) + .fontSize(50) + .fontWeight(FontWeight.Bold) + Button() { + Text('next page') + .fontSize(20) + .fontWeight(FontWeight.Bold) + }.type(ButtonType.Capsule) + .margin({ + top: 20 + }) + .backgroundColor('#0D9FFB') + .width('35%') + .height('5%') + .onClick(()=>{ + }) + } + .width('100%') + } + .height('100%') + } + } \ No newline at end of file diff --git a/entry/src/ohosTest/ets/TestRunner/OpenHarmonyTestRunner.ts b/entry/src/ohosTest/ets/TestRunner/OpenHarmonyTestRunner.ts new file mode 100644 index 0000000000000000000000000000000000000000..ed3ba0df1ea2e0e53c5dfe5065292f2e657a3b5c --- /dev/null +++ b/entry/src/ohosTest/ets/TestRunner/OpenHarmonyTestRunner.ts @@ -0,0 +1,63 @@ +import TestRunner from '@ohos.application.testRunner' +import AbilityDelegatorRegistry from '@ohos.application.abilityDelegatorRegistry' + +var abilityDelegator = undefined +var abilityDelegatorArguments = undefined + +function translateParamsToString(parameters) { + const keySet = new Set([ + '-s class', '-s notClass', '-s suite', '-s itName', + '-s level', '-s testType', '-s size', '-s timeout', + '-s package' + ]) + let targetParams = ''; + for (const key in parameters) { + if (keySet.has(key)) { + targetParams += ' ' + key + ' ' + parameters[key] + } + } + return targetParams.trim() +} + +async function onAbilityCreateCallback() { + console.log('onAbilityCreateCallback'); +} + +async function addAbilityMonitorCallback(err: any) { + console.info('addAbilityMonitorCallback : ' + JSON.stringify(err)) +} + +export default class OpenHarmonyTestRunner implements TestRunner { + constructor() { + } + + onPrepare() { + console.info('OpenHarmonyTestRunner OnPrepare') + } + + onRun() { + console.log('OpenHarmonyTestRunner onRun run') + abilityDelegatorArguments = AbilityDelegatorRegistry.getArguments() + abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator() + + let lMonitor = { + abilityName: testAbilityName, + onAbilityCreate: onAbilityCreateCallback, + }; + var testAbilityName = abilityDelegatorArguments.parameters['-p'] + '.TestAbility' + abilityDelegator.addAbilityMonitor(lMonitor, addAbilityMonitorCallback) + var cmd = 'aa start -d 0 -a ' + testAbilityName + ' -b ' + abilityDelegatorArguments.bundleName + cmd += ' '+translateParamsToString(abilityDelegatorArguments.parameters) + console.info('cmd : '+cmd) + abilityDelegator.executeShellCommand(cmd, + (err: any, d: any) => { + console.info('executeShellCommand : err : ' + JSON.stringify(err)); + console.info('executeShellCommand : data : ' + d.stdResult); + console.info('executeShellCommand : data : ' + d.exitCode); + }) + console.info('OpenHarmonyTestRunner onRun call abilityDelegator.getAppContext') + var context = abilityDelegator.getAppContext() + console.info('getAppContext : ' + JSON.stringify(context)) + console.info('OpenHarmonyTestRunner onRun end') + } +}; \ No newline at end of file diff --git a/entry/src/ohosTest/ets/test/Ability.test.ets b/entry/src/ohosTest/ets/test/Ability.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..1236e0ce6077de4c6a4dbc22c79b1298173c07ac --- /dev/null +++ b/entry/src/ohosTest/ets/test/Ability.test.ets @@ -0,0 +1,13 @@ +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from 'hypium/index' + +export default function abilityTest() { + describe('ActsAbilityTest', function () { + it('assertContain',0, function () { + console.info("it begin") + let a = 'abc' + let b = 'b' + expect(a).assertContain(b) + expect(a).assertEqual(a) + }) + }) +} \ No newline at end of file diff --git a/entry/src/ohosTest/ets/test/List.test.ets b/entry/src/ohosTest/ets/test/List.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..d766fe249dfc3ada636f27e64d9b64451ce32c93 --- /dev/null +++ b/entry/src/ohosTest/ets/test/List.test.ets @@ -0,0 +1,5 @@ +import abilityTest from './Ability.test' + +export default function testsuite() { + abilityTest() +} \ No newline at end of file diff --git a/entry/src/ohosTest/resources/base/element/string.json b/entry/src/ohosTest/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..a0901cfced5abc1cb836b55896884b769adc7175 --- /dev/null +++ b/entry/src/ohosTest/resources/base/element/string.json @@ -0,0 +1,12 @@ +{ + "string": [ + { + "name": "description_TestAbility", + "value": "eTS_Empty Ability" + }, + { + "name": "entry_TestAbility", + "value": "entry_TestAbility" + } + ] +} \ No newline at end of file diff --git a/entry/src/ohosTest/resources/base/media/icon.png b/entry/src/ohosTest/resources/base/media/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ce307a8827bd75456441ceb57d530e4c8d45d36c Binary files /dev/null and b/entry/src/ohosTest/resources/base/media/icon.png differ diff --git a/hvigorfile.js b/hvigorfile.js new file mode 100644 index 0000000000000000000000000000000000000000..cff9f0dfcf8cb00cca34e7f50d61380cf5496868 --- /dev/null +++ b/hvigorfile.js @@ -0,0 +1,2 @@ +// Script for compiling build behavior. It is built in the build plug-in and cannot be modified currently. +module.exports = require('@ohos/hvigor-ohos-plugin').legacyAppTasks \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000000000000000000000000000000000000..39cdb6cc3e3710f176b211a8849862bdc63d8939 --- /dev/null +++ b/package.json @@ -0,0 +1,17 @@ +{ + "name": "fastble", + "version": "1.0.0", + "ohos": { + "org": "huawei", + "buildTool": "hvigor", + "directoryLevel": "project" + }, + "description": "example description", + "repository": {}, + "license": "Apache-2.0", + "dependencies": { + "hypium": "^1.0.0", + "@ohos/hvigor": "1.0.6", + "@ohos/hvigor-ohos-plugin": "1.0.6" + } +} diff --git a/preview/preview.gif b/preview/preview.gif new file mode 100644 index 0000000000000000000000000000000000000000..197ef0333dc652c2d122100fdb22752c1ca93205 Binary files /dev/null and b/preview/preview.gif differ diff --git a/wxFastBle/.gitignore b/wxFastBle/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..4f9a973815d0b5e49bc8547681a6b4bc7a178d12 --- /dev/null +++ b/wxFastBle/.gitignore @@ -0,0 +1,3 @@ +/node_modules +/.preview +/build \ No newline at end of file diff --git a/wxFastBle/build-profile.json5 b/wxFastBle/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..107d8c761a767ae4bd6c3798021d9fad61751005 --- /dev/null +++ b/wxFastBle/build-profile.json5 @@ -0,0 +1,5 @@ +{ + "apiType": "faMode", + "buildOption": { + } +} diff --git a/wxFastBle/hvigorfile.js b/wxFastBle/hvigorfile.js new file mode 100644 index 0000000000000000000000000000000000000000..3a7c40cd644527fdf586c81b110e346329f38c9a --- /dev/null +++ b/wxFastBle/hvigorfile.js @@ -0,0 +1,3 @@ +// Script for compiling build behavior. It is built in the build plug-in and cannot be modified currently. +module.exports = require('@ohos/hvigor-ohos-plugin').legacyHarTasks + diff --git a/wxFastBle/index.ets b/wxFastBle/index.ets new file mode 100644 index 0000000000000000000000000000000000000000..3335b9b6626e9351ad2ed48bf61bb926704d92dd --- /dev/null +++ b/wxFastBle/index.ets @@ -0,0 +1,13 @@ +export {default as BleManager} from './src/main/ets/BleManager'; +export {default as BleException} from './src/main/ets/exception/BleException' +export {default as BleDevice} from './src/main/ets/data/BleDevice' +export {default as BleScanCallback} from './src/main/ets/callback/BleScanCallback'; +export {default as BleGattCallback} from './src/main/ets/callback/BleGattCallback'; +export {default as BleReadCallback} from './src/main/ets/callback/BleReadCallback'; +export {default as BleWriteCallback} from './src/main/ets/callback/BleWriteCallback'; +export {default as BleIndicateCallback} from './src/main/ets/callback/BleIndicateCallback'; +export {default as BleNotifyCallback} from './src/main/ets/callback/BleNotifyCallback'; +export {default as BleScanRuleConfig} from './src/main/ets/scan/BleScanRuleConfig' +export {BluetoothGattCharacteristic} from './src/main/ets/utils/BluetoothConsts' +export {default as HexUtil} from './src/main/ets/utils/HexUtil' +export {default as TextUtils} from './src/main/ets/utils/TextUtils' \ No newline at end of file diff --git a/wxFastBle/package-lock.json b/wxFastBle/package-lock.json new file mode 100644 index 0000000000000000000000000000000000000000..699c53e8218b9041ede6d1cc6c87de2ecb78b46c --- /dev/null +++ b/wxFastBle/package-lock.json @@ -0,0 +1,5 @@ +{ + "name": "wxFastBle", + "version": "1.0.0", + "lockfileVersion": 1 +} diff --git a/wxFastBle/package.json b/wxFastBle/package.json new file mode 100644 index 0000000000000000000000000000000000000000..906ba8d009dae6f04b7d7e7d388b43e2a1d3084d --- /dev/null +++ b/wxFastBle/package.json @@ -0,0 +1,13 @@ +{ + "name": "wxFastBle", + "description": "Bluetooth Low Energy (BLE) Fast Development Framework. It uses simple ways to filter, scan, connect, read ,write, notify, readRssi, setMTU, and multiConnection", + "ohos": { + "org": "" + }, + "version": "1.0.0", + "main": "index.ets", + "types": "", + "repository": {}, + "license": "Apache-2.0", + "dependencies": {} +} diff --git a/wxFastBle/src/main/config.json b/wxFastBle/src/main/config.json new file mode 100644 index 0000000000000000000000000000000000000000..a6c0565dd7545ff1b154cd6d6208f218e911dcab --- /dev/null +++ b/wxFastBle/src/main/config.json @@ -0,0 +1,24 @@ +{ + "app": { + "bundleName": "com.hihope.blesample", + "vendor": "example", + "version": { + "code": 1000000, + "name": "1.0.0" + } + }, + "deviceConfig": {}, + "module": { + "package": "com.example.wxFastBle", + "deviceType": [ + "phone", + "tablet" + ], + "distro": { + "deliveryWithInstall": true, + "moduleName": "wxFastBle", + "moduleType": "har" + }, + "uiSyntax": "ets" + } +} diff --git a/wxFastBle/src/main/ets/BleManager.ets b/wxFastBle/src/main/ets/BleManager.ets new file mode 100644 index 0000000000000000000000000000000000000000..22dfe6fa6a4f107f6b287f393d060977be32c843 --- /dev/null +++ b/wxFastBle/src/main/ets/BleManager.ets @@ -0,0 +1,724 @@ +import bluetooth from '@ohos.bluetooth'; +import BleBluetooth from './bluetooth/BleBluetooth'; +import MultipleBluetoothController from './bluetooth/MultipleBluetoothController'; +import SplitWriter from './bluetooth/SplitWriter'; +import BleGattCallback from './callback/BleGattCallback' +import BleIndicateCallback from './callback/BleIndicateCallback'; +import BleMtuChangedCallback from './callback/BleMtuChangedCallback' +import BleNotifyCallback from './callback/BleNotifyCallback' +import BleReadCallback from './callback/BleReadCallback'; +import BleRssiCallback from './callback/BleRssiCallback' +import BleScanAndConnectCallback from './callback/BleScanAndConnectCallback' +import BleScanCallback from './callback/BleScanCallback'; +import BleWriteCallback from './callback/BleWriteCallback'; +import BleDevice from './data/BleDevice'; +import BleScanState from './data/BleScanState'; +import ConnectException from './exception/ConnectException' +import OtherException from './exception/OtherException' +import BleScanRuleConfig from './scan/BleScanRuleConfig' +import BleScanner from './scan/BleScanner'; +import BleLog from './utils/BleLog' +import mockBluetooth from './bluetooth/mockBluetooth' + +var sBleManager: BleManager; +type UUID = string; +type byte = number; + +export default class BleManager { + public static readonly MOCK_DEVICE: boolean = false; + public static readonly mockBluetooth = new mockBluetooth(); + + private bleScanRuleConfig: BleScanRuleConfig; + private multipleBluetoothController: MultipleBluetoothController; + + public static readonly DEFAULT_SCAN_TIME = 10000; + private static readonly DEFAULT_MAX_MULTIPLE_DEVICE = 7; + private static readonly DEFAULT_OPERATE_TIME = 5000; + private static readonly DEFAULT_CONNECT_RETRY_COUNT = 0; + private static readonly DEFAULT_CONNECT_RETRY_INTERVAL = 5000; + private static readonly DEFAULT_MTU = 23; + private static readonly DEFAULT_MAX_MTU = 512; + private static readonly DEFAULT_WRITE_DATA_SPLIT_COUNT = 20; + private static readonly DEFAULT_CONNECT_OVER_TIME = 10000; + private maxConnectCount = BleManager.DEFAULT_MAX_MULTIPLE_DEVICE; + private operateTimeout = BleManager.DEFAULT_OPERATE_TIME; + private reConnectCount = BleManager.DEFAULT_CONNECT_RETRY_COUNT; + private reConnectInterval = BleManager.DEFAULT_CONNECT_RETRY_INTERVAL; + private splitWriteNum = BleManager.DEFAULT_WRITE_DATA_SPLIT_COUNT; + private connectOverTime = BleManager.DEFAULT_CONNECT_OVER_TIME; + + //private static sBleManager: BleManager; + + private constructor() { + console.log("BleManager.constructor this:" + this); + } + + public static getInstance(): BleManager { + if (!sBleManager) { + sBleManager = new BleManager(); + } + return sBleManager; + } + + public init(): void { + this.multipleBluetoothController = new MultipleBluetoothController(); + this.bleScanRuleConfig = new BleScanRuleConfig(); + } + + /** + * get the ScanRuleConfig + * + * @return + */ + public getScanRuleConfig(): BleScanRuleConfig { + return this.bleScanRuleConfig; + } + + /** + * Get the multiple Bluetooth Controller + * + * @return + */ + public getMultipleBluetoothController(): MultipleBluetoothController { + return this.multipleBluetoothController; + } + + /** + * Configure scan and connection properties + * + * @param config + */ + public initScanRule(config: BleScanRuleConfig) { + this.bleScanRuleConfig = config; + } + + /** + * Get the maximum number of connections + * + * @return + */ + public getMaxConnectCount(): number { + return this.maxConnectCount; + } + + /** + * Set the maximum number of connections + * + * @param count + * @return BleManager + */ + public setMaxConnectCount(count: number): BleManager { + if (count > BleManager.DEFAULT_MAX_MULTIPLE_DEVICE) + count = BleManager.DEFAULT_MAX_MULTIPLE_DEVICE; + this.maxConnectCount = count; + return this; + } + + /** + * Get operate timeout + * + * @return + */ + public getOperateTimeout(): number { + return this.operateTimeout; + } + + /** + * Set operate timeout + * + * @param count + * @return BleManager + */ + public setOperateTimeout(count: number): BleManager { + this.operateTimeout = count; + return this; + } + + /** + * Get connect retry count + * + * @return + */ + public getReConnectCount(): number { + return this.reConnectCount; + } + + /** + * Get connect retry interval + * + * @return + */ + public getReConnectInterval(): number { + return this.reConnectInterval; + } + + /** + * Set connect retry count and interval + * + * @param count + * @return BleManager + */ + public setReConnectCount(count: number, interval: number=BleManager.DEFAULT_CONNECT_RETRY_INTERVAL): BleManager{ + if (count > 10) + count = 10; + if (interval < 0) + interval = 0; + this.reConnectCount = count; + this.reConnectInterval = interval; + return this; + } + + + /** + * Get operate split Write Num + * + * @return + */ + public getSplitWriteNum(): number { + return this.splitWriteNum; + } + + /** + * Set split Writ eNum + * + * @param num + * @return BleManager + */ + public setSplitWriteNum(num: number): BleManager{ + if (num > 0) { + this.splitWriteNum = num; + } + return this; + } + + /** + * Get operate connect Over Time + * + * @return + */ + public getConnectOverTime(): number { + return this.connectOverTime; + } + + /** + * Set connect Over Time + * + * @param time + * @return BleManager + */ + public setConnectOverTime(time: number): BleManager { + if (time <= 0) { + time = 100; + } + this.connectOverTime = time; + return this; + } + + /** + * print log? + * + * @param enable + * @return BleManager + */ + public enableLog(enable: boolean): BleManager { + BleLog.isPrint = enable; + return this; + } + + /** + * scan device around + * + * @param callback + */ + public scan(callback: BleScanCallback) { + if (callback == null) { + throw new Error("BleScanCallback can not be Null!"); + } + + if (!this.isBlueEnable()) { + BleLog.e("Bluetooth not enable!"); + callback.onScanStarted(false); + return; + } + + let serviceUuids: UUID[] = this.bleScanRuleConfig.getServiceUuids(); + let deviceNames: string[] = this.bleScanRuleConfig.getDeviceNames(); + let deviceMac: string = this.bleScanRuleConfig.getDeviceMac(); + let fuzzy: boolean = this.bleScanRuleConfig.isFuzzy(); + let timeOut: number = this.bleScanRuleConfig.getScanTimeOut(); + + BleScanner.getInstance().scan(serviceUuids, deviceNames, deviceMac, fuzzy, timeOut, callback); + } + + /** + * scan device then connect + * + * @param callback + */ + public scanAndConnect(callback: BleScanAndConnectCallback) { + if (callback == null) { + throw new Error("BleScanAndConnectCallback can not be Null!"); + } + + if (!this.isBlueEnable()) { + BleLog.e("Bluetooth not enable!"); + callback.onScanStarted(false); + return; + } + + let serviceUuids: UUID[] = this.bleScanRuleConfig.getServiceUuids(); + let deviceNames: string[] = this.bleScanRuleConfig.getDeviceNames(); + let deviceMac: string = this.bleScanRuleConfig.getDeviceMac(); + let fuzzy: boolean = this.bleScanRuleConfig.isFuzzy(); + let timeOut: number = this.bleScanRuleConfig.getScanTimeOut(); + + BleScanner.getInstance().scanAndConnect(serviceUuids, deviceNames, deviceMac, fuzzy, timeOut, callback); + } + + /** + * connect a device through its mac without scan,whether or not it has been connected + * + * @param device BleDevice or mac address + * @param bleGattCallback + * @return + */ + public connect(device: BleDevice | string, bleGattCallback: BleGattCallback): void { + let bleDevice: BleDevice; + if(device instanceof BleDevice) { + bleDevice = device; + }else{ + bleDevice = new BleDevice(device, '', 0, null, 0); + } + console.log("BleManager.connect device:" + bleDevice.getMac()); + if (bleGattCallback == null) { + throw new EvalError("BleGattCallback can not be Null!"); + } + + if (!this.isBlueEnable()) { + BleLog.e("Bluetooth not enable!"); + bleGattCallback.onConnectFail(bleDevice, new OtherException("Bluetooth not enable!")); + return; + } + + if (bleDevice == null) { + bleGattCallback.onConnectFail(bleDevice, new OtherException("Not Found Device Exception Occurred!")); + } else { + let bleBluetooth: BleBluetooth = this.multipleBluetoothController.buildConnectingBle(bleDevice); + let autoConnect: boolean = this.bleScanRuleConfig.isAutoConnect(); + bleBluetooth.connect(bleDevice, autoConnect, bleGattCallback); + } + } + + + /** + * Cancel scan + */ + public cancelScan() { + BleScanner.getInstance().stopLeScan(); + } + + /** + * notify + * + * @param bleDevice + * @param uuid_service + * @param uuid_notify + * @param useCharacteristicDescriptor + * @param callback + */ + public notify(bleDevice: BleDevice, uuid_service: string, uuid_notify: string, useCharacteristicDescriptor: boolean=false, callback: BleNotifyCallback) { + if (callback == null) { + throw new Error("BleNotifyCallback can not be Null!"); + } + + let bleBluetooth: BleBluetooth = this.multipleBluetoothController.getBleBluetooth(bleDevice); + if (bleBluetooth == null) { + callback.onNotifyFailure(new OtherException("This device not connect!")); + } else { + bleBluetooth.newBleConnector() + .withUUIDString(uuid_service, uuid_notify) + .enableCharacteristicNotify(callback, uuid_notify, useCharacteristicDescriptor); + } + } + + /** + * indicate + * + * @param bleDevice + * @param uuid_service + * @param uuid_indicate + * @param useCharacteristicDescriptor + * @param callback + */ + public indicate(bleDevice: BleDevice, uuid_service: string, uuid_indicate: string, useCharacteristicDescriptor: boolean=false, callback: BleIndicateCallback) { + if (callback == null) { + throw new EvalError("BleIndicateCallback can not be Null!"); + } + + let bleBluetooth: BleBluetooth = this.multipleBluetoothController.getBleBluetooth(bleDevice); + if (bleBluetooth == null) { + callback.onIndicateFailure(new OtherException("This device not connect!")); + } else { + bleBluetooth.newBleConnector() + .withUUIDString(uuid_service, uuid_indicate) + .enableCharacteristicIndicate(callback, uuid_indicate, useCharacteristicDescriptor); + } + } + + /** + * stop notify, remove callback + * + * @param bleDevice + * @param uuid_service + * @param uuid_notify + * @param useCharacteristicDescriptor + * @return + */ + public stopNotify(bleDevice: BleDevice, uuid_service: string, uuid_notify: string, useCharacteristicDescriptor: boolean=false): boolean { + let bleBluetooth: BleBluetooth = this.multipleBluetoothController.getBleBluetooth(bleDevice); + if (bleBluetooth == null) { + return false; + } + let success: boolean = bleBluetooth.newBleConnector() + .withUUIDString(uuid_service, uuid_notify) + .disableCharacteristicNotify(useCharacteristicDescriptor); + if (success) { + bleBluetooth.removeNotifyCallback(uuid_notify); + } + return success; + } + + /** + * stop indicate, remove callback + * + * @param bleDevice + * @param uuid_service + * @param uuid_indicate + * @param useCharacteristicDescriptor + * @return + */ + public stopIndicate(bleDevice: BleDevice, uuid_service: string, uuid_indicate: string, useCharacteristicDescriptor: boolean=false): boolean { + let bleBluetooth: BleBluetooth = this.multipleBluetoothController.getBleBluetooth(bleDevice); + if (bleBluetooth == null) { + return false; + } + let success: boolean = bleBluetooth.newBleConnector() + .withUUIDString(uuid_service, uuid_indicate) + .disableCharacteristicIndicate(useCharacteristicDescriptor); + if (success) { + bleBluetooth.removeIndicateCallback(uuid_indicate); + } + return success; + } + + /** + * write + * + * @param bleDevice + * @param uuid_service + * @param uuid_write + * @param data + * @param split + * @param sendNextWhenLastSuccess + * @param intervalBetweenTwoPackage + * @param callback + */ + public write(bleDevice: BleDevice, uuid_service: string, uuid_write: string, data: Uint8Array, split: boolean=true, sendNextWhenLastSuccess: boolean=true, intervalBetweenTwoPackage: number=0, callback: BleWriteCallback) { + if (callback == null) { + throw new Error("BleWriteCallback can not be Null!"); + } + + if (data == null) { + BleLog.e("data is Null!"); + callback.onWriteFailure(new OtherException("data is Null!")); + return; + } + + if (data.length > 20 && !split) { + BleLog.w("Be careful: data's length beyond 20! Ensure MTU higher than 23, or use spilt write!"); + } + + let bleBluetooth: BleBluetooth = this.multipleBluetoothController.getBleBluetooth(bleDevice); + if (bleBluetooth == null) { + callback.onWriteFailure(new OtherException("This device not connect!")); + } else { + if (split && data.length > this.getSplitWriteNum()) { + new SplitWriter().splitWrite(bleBluetooth, uuid_service, uuid_write, data, + sendNextWhenLastSuccess, intervalBetweenTwoPackage, callback); + } else { + bleBluetooth.newBleConnector() + .withUUIDString(uuid_service, uuid_write) + .writeCharacteristic(data, callback, uuid_write); + } + } + } + + /** + * read + * + * @param bleDevice + * @param uuid_service + * @param uuid_read + * @param callback + */ + public read(bleDevice: BleDevice, uuid_service: string, uuid_read: string, callback: BleReadCallback) { + if (callback == null) { + throw new EvalError("BleReadCallback can not be Null!"); + } + + let bleBluetooth: BleBluetooth = this.multipleBluetoothController.getBleBluetooth(bleDevice); + if (bleBluetooth == null) { + callback.onReadFailure(new OtherException("This device is not connected!")); + } else { + bleBluetooth.newBleConnector() + .withUUIDString(uuid_service, uuid_read) + .readCharacteristic(callback, uuid_read); + } + } + + /** + * read Rssi + * + * @param bleDevice + * @param callback + */ + public readRssi(bleDevice: BleDevice, callback: BleRssiCallback) { + if (callback == null) { + throw new Error("BleRssiCallback can not be Null!"); + } + + let bleBluetooth: BleBluetooth = this.multipleBluetoothController.getBleBluetooth(bleDevice); + if (bleBluetooth == null) { + callback.onRssiFailure(new OtherException("This device is not connected!")); + } else { + bleBluetooth.newBleConnector().readRemoteRssi(callback); + } + } + + /** + * set Mtu + * + * @param bleDevice + * @param mtu + * @param callback + */ + public setMtu(bleDevice: BleDevice, mtu: number, callback: BleMtuChangedCallback) { + if (callback == null) { + throw new Error("BleMtuChangedCallback can not be Null!"); + } + + if (mtu > BleManager.DEFAULT_MAX_MTU) { + BleLog.e("requiredMtu should lower than 512 !"); + callback.onSetMTUFailure(new OtherException("requiredMtu should lower than 512 !")); + return; + } + + if (mtu < BleManager.DEFAULT_MTU) { + BleLog.e("requiredMtu should higher than 23 !"); + callback.onSetMTUFailure(new OtherException("requiredMtu should higher than 23 !")); + return; + } + + let bleBluetooth: BleBluetooth = this.multipleBluetoothController.getBleBluetooth(bleDevice); + if (bleBluetooth == null) { + callback.onSetMTUFailure(new OtherException("This device is not connected!")); + } else { + bleBluetooth.newBleConnector().setMtu(mtu, callback); + } + } + + /** + * requestConnectionPriority + * + * @param connectionPriority Request a specific connection priority. Must be one of + * {@link BluetoothGatt#CONNECTION_PRIORITY_BALANCED}, + * {@link BluetoothGatt#CONNECTION_PRIORITY_HIGH} + * or {@link BluetoothGatt#CONNECTION_PRIORITY_LOW_POWER}. + * @throws IllegalArgumentException If the parameters are outside of their + * specified range. + */ + public requestConnectionPriority(bleDevice: BleDevice, connectionPriority: number): boolean { +// let bleBluetooth: BleBluetooth = this.multipleBluetoothController.getBleBluetooth(bleDevice); +// if (bleBluetooth == null) { +// return false; +// } else { +// return bleBluetooth.newBleConnector().requestConnectionPriority(connectionPriority); +// } + return false; // not supported + } + + /** + * is support ble? + * + * @return + */ + public isSupportBle(): boolean { + return true; //TODO + } + + /** + * Open bluetooth + */ + public enableBluetooth() { + //TODO + } + + /** + * Disable bluetooth + */ + public disableBluetooth() { + //TODO + } + + /** + * judge Bluetooth is enable + * + * @return + */ + public isBlueEnable(): boolean { + if (BleManager.MOCK_DEVICE) { + return true; + } + return bluetooth.getState() == 2; // BluetoothState.STATE_ON + } + + public convertBleDevice(scanResult: /*bluetooth.ScanResult*/any): BleDevice { + if (scanResult == null) { + throw new Error("scanResult can not be Null!"); + } + let deviceId: string = scanResult.deviceId; + let rssi: number = scanResult.rssi; + let scanRecord: ArrayBuffer = scanResult.data; + let bytes: Uint8Array = new Uint8Array(scanRecord); + let timestampNanos: number = 0; + return new BleDevice(deviceId, '', rssi, bytes, timestampNanos); + } + + public getBleBluetooth(bleDevice: BleDevice): BleBluetooth { + if (this.multipleBluetoothController != null) { + return this.multipleBluetoothController.getBleBluetooth(bleDevice); + } + return null; + } + + private getBluetoothGatt(bleDevice: BleDevice): bluetooth.GattClientDevice { + let bleBluetooth: BleBluetooth = this.getBleBluetooth(bleDevice); + if (bleBluetooth != null) + return bleBluetooth.getBluetoothGatt(); + return null; + } + + public getBluetoothGattServices(bleDevice: BleDevice, callback: /*AsyncCallback>*/any): void { + let gattClientDevice = this.getBluetoothGatt(bleDevice); + if (gattClientDevice != null) { + return gattClientDevice.getServices((err, services)=>{ + if(!err.code) { + this.getBleBluetooth(bleDevice).setServices(services); + } + callback(err, services); + }); + } + return null; + } + + public removeConnectGattCallback(bleDevice: BleDevice) { + let bleBluetooth: BleBluetooth = this.getBleBluetooth(bleDevice); + if (bleBluetooth != null) + bleBluetooth.removeConnectGattCallback(); + } + + public removeRssiCallback(bleDevice: BleDevice) { + let bleBluetooth: BleBluetooth = this.getBleBluetooth(bleDevice); + if (bleBluetooth != null) + bleBluetooth.removeRssiCallback(); + } + + public removeMtuChangedCallback(bleDevice: BleDevice) { + let bleBluetooth: BleBluetooth = this.getBleBluetooth(bleDevice); + if (bleBluetooth != null) + bleBluetooth.removeMtuChangedCallback(); + } + + public removeNotifyCallback(bleDevice: BleDevice, uuid_notify: string) { + let bleBluetooth: BleBluetooth = this.getBleBluetooth(bleDevice); + if (bleBluetooth != null) + bleBluetooth.removeNotifyCallback(uuid_notify); + } + + public removeIndicateCallback(bleDevice: BleDevice, uuid_indicate: string) { + let bleBluetooth: BleBluetooth = this.getBleBluetooth(bleDevice); + if (bleBluetooth != null) + bleBluetooth.removeIndicateCallback(uuid_indicate); + } + + public removeWriteCallback(bleDevice: BleDevice, uuid_write: string) { + let bleBluetooth: BleBluetooth = this.getBleBluetooth(bleDevice); + if (bleBluetooth != null) + bleBluetooth.removeWriteCallback(uuid_write); + } + + public removeReadCallback(bleDevice: BleDevice, uuid_read: string) { + let bleBluetooth: BleBluetooth = this.getBleBluetooth(bleDevice); + if (bleBluetooth != null) + bleBluetooth.removeReadCallback(uuid_read); + } + + public clearCharacterCallback(bleDevice: BleDevice) { + let bleBluetooth: BleBluetooth = this.getBleBluetooth(bleDevice); + if (bleBluetooth != null) + bleBluetooth.clearCharacterCallback(); + } + + public getScanSate(): BleScanState{ + return BleScanner.getInstance().getScanState(); + } + + public getAllConnectedDevice(): Array { + if (BleManager.MOCK_DEVICE) { + return BleManager.mockBluetooth.getConnectedBLEDevices(); + } + return bluetooth.BLE.getConnectedBLEDevices(); + } + + /** + * @param bleDevice + * @return State of the profile connection. One of + * {@link ProfileConnectionState#STATE_DISCONNECTED 0}, + * {@link ProfileConnectionState#STATE_CONNECTING 1}, + * {@link ProfileConnectionState#STATE_CONNECTED 2}, + * {@link ProfileConnectionState#STATE_DISCONNECTING 3} + */ + public getConnectState(gattClientDevice: /*bluetooth.GattClientDevice*/any, + /*Callback*/callback: (err: number, state: number) => void): void { + return gattClientDevice.on('BLEConnectionStateChange', callback); + } + + public isConnected(device: BleDevice | string): boolean { + let list = this.getAllConnectedDevice(); + if(device instanceof BleDevice) { + return list.indexOf(device.getMac()) >=0; + }else { + return list.indexOf(device) >= 0; + } + } + + public disconnect(bleDevice: BleDevice) { + if (this.multipleBluetoothController != null) { + this.multipleBluetoothController.disconnect(bleDevice); + } + } + + public disconnectAllDevice() { + if (this.multipleBluetoothController != null) { + this.multipleBluetoothController.disconnectAllDevice(); + } + } + + public destroy() { + if (this.multipleBluetoothController != null) { + this.multipleBluetoothController.destroy(); + } + } + + +} \ No newline at end of file diff --git a/wxFastBle/src/main/ets/bluetooth/BleBluetooth.ets b/wxFastBle/src/main/ets/bluetooth/BleBluetooth.ets new file mode 100644 index 0000000000000000000000000000000000000000..49d6b4fff4af77ac4016f3c71799dd648910a689 --- /dev/null +++ b/wxFastBle/src/main/ets/bluetooth/BleBluetooth.ets @@ -0,0 +1,576 @@ +import bluetooth from '@ohos.bluetooth'; + +import BleManager from '../BleManager'; +import BleConnector from '../bluetooth/BleConnector'; +import BleGattCallback from '../callback/BleGattCallback'; +import BleIndicateCallback from '../callback/BleIndicateCallback'; +import BleMtuChangedCallback from '../callback/BleMtuChangedCallback'; +import BleNotifyCallback from '../callback/BleNotifyCallback'; +import BleReadCallback from '../callback/BleReadCallback'; +import BleRssiCallback from '../callback/BleRssiCallback'; +import BleWriteCallback from '../callback/BleWriteCallback'; +import BleConnectStateParameter from '../data/BleConnectStateParameter'; +import BleDevice from '../data/BleDevice'; +import BleMsg from '../data/BleMsg'; +import ConnectException from '../exception/ConnectException'; +import OtherException from '../exception/OtherException'; +import TimeoutException from '../exception/TimeoutException'; +import BleLog from '../utils/BleLog'; +import TextUtils from '../utils/TextUtils'; +import {Message, Handler} from '../utils/Handler'; + +enum LastState { + CONNECT_IDLE, + CONNECT_CONNECTING, + CONNECT_CONNECTED, + CONNECT_FAILURE, + CONNECT_DISCONNECT +}; + +export default class BleBluetooth { + + private bleGattCallback: BleGattCallback; + private bleRssiCallback: BleRssiCallback; + private bleMtuChangedCallback: BleMtuChangedCallback; + private bleNotifyCallbackHashMap = new Map(); + private bleIndicateCallbackHashMap = new Map(); + private bleWriteCallbackHashMap = new Map(); + private bleReadCallbackHashMap = new Map(); + + private lastState: LastState; + private isActiveDisconnect: boolean = false; + private bleDevice: BleDevice; + private gattClientDevice: bluetooth.GattClientDevice; + private mainHandler: Handler; + private connectRetryCount: number = 0; + + private services: bluetooth.GattService[]; + + public constructor(bleDevice: BleDevice) { + this.bleDevice = bleDevice; + this.initHandler(); + } + + public newBleConnector(): BleConnector { + return new BleConnector(this); + } + + public addConnectGattCallback(callback: BleGattCallback): void { + this.bleGattCallback = callback; + } + + public removeConnectGattCallback(): void { + this.bleGattCallback = null; + } + + public addNotifyCallback(uuid: string, bleNotifyCallback: BleNotifyCallback): void { + this.bleNotifyCallbackHashMap.set(uuid, bleNotifyCallback); + } + + public addIndicateCallback(uuid: string, bleIndicateCallback: BleIndicateCallback): void { + this.bleIndicateCallbackHashMap.set(uuid, bleIndicateCallback); + } + + public addWriteCallback(uuid: string, bleWriteCallback: BleWriteCallback): void { + this.bleWriteCallbackHashMap.set(uuid, bleWriteCallback); + } + + public addReadCallback(uuid: string, bleReadCallback: BleReadCallback): void { + this.bleReadCallbackHashMap.set(uuid, bleReadCallback); + } + + public removeNotifyCallback(uuid: string): void { + if (this.bleNotifyCallbackHashMap.has(uuid)) + this.bleNotifyCallbackHashMap.delete(uuid); + } + + public removeIndicateCallback(uuid: string): void { + if (this.bleIndicateCallbackHashMap.has(uuid)) + this.bleIndicateCallbackHashMap.delete(uuid); + } + + public removeWriteCallback(uuid: string): void { + if (this.bleWriteCallbackHashMap.has(uuid)) + this.bleWriteCallbackHashMap.delete(uuid); + } + + public removeReadCallback(uuid: string): void { + if (this.bleReadCallbackHashMap.has(uuid)) + this.bleReadCallbackHashMap.delete(uuid); + } + + public clearCharacterCallback(): void { + this.bleNotifyCallbackHashMap.clear(); + this.bleIndicateCallbackHashMap.clear(); + this.bleWriteCallbackHashMap.clear(); + this.bleReadCallbackHashMap.clear(); + } + + public addRssiCallback(callback: BleRssiCallback): void { + this.bleRssiCallback = callback; + } + + public removeRssiCallback(): void { + this.bleRssiCallback = null; + } + + public addMtuChangedCallback(callback: BleMtuChangedCallback): void { + this.bleMtuChangedCallback = callback; + } + + public removeMtuChangedCallback(): void { + this.bleMtuChangedCallback = null; + } + + + public getDeviceKey(): string { + return this.bleDevice.getKey(); + } + + public getDevice(): BleDevice { + return this.bleDevice; + } + + public getBluetoothGatt(): bluetooth.GattClientDevice { + return this.gattClientDevice; + } + + public setServices(services: bluetooth.GattService[]) { + this.services = services; + } + + public getServices(): bluetooth.GattService[] { + return this.services; + } + + public connect(bleDevice: BleDevice, autoConnect: boolean, callback: BleGattCallback, connectRetryCount: number=0): bluetooth.GattClientDevice { + BleLog.i("BleBluetooth.connect device: " + bleDevice.getName() + + ", mac: " + bleDevice.getMac() + + ", autoConnect: " + autoConnect + + ", connectCount:" + (connectRetryCount + 1)); + if (connectRetryCount == 0) { + this.connectRetryCount = 0; + } + + this.addConnectGattCallback(callback); + + this.lastState = LastState.CONNECT_CONNECTING; + + if (BleManager.MOCK_DEVICE) { + setTimeout(()=>{ + this.bleGattCallback.onStartConnect(); + }, 500); + let message = this.mainHandler.obtainMessage(); + message.what = BleMsg.MSG_DISCOVER_SERVICES; + this.mainHandler.sendMessageDelayed(message, 1000); + return null; + } + this.gattClientDevice = bluetooth.BLE.createGattClientDevice(bleDevice.getMac()); + this.gattClientDevice.on('BLEConnectionStateChange', this.connectStateChangeCallback); + if (this.gattClientDevice.connect()) { + if (this.bleGattCallback != null) { + this.bleGattCallback.onStartConnect(); + } + let message = this.mainHandler.obtainMessage(); + message.what = BleMsg.MSG_CONNECT_OVER_TIME; + this.mainHandler.sendMessageDelayed(message, BleManager.getInstance().getConnectOverTime()); + } else { + this.disconnectGatt(); + this.refreshDeviceCache(); + this.closeBluetoothGatt(); + this.lastState = LastState.CONNECT_FAILURE; + BleManager.getInstance().getMultipleBluetoothController().removeConnectingBle(this); + if (this.bleGattCallback != null) { + this.bleGattCallback.onConnectFail(bleDevice, new OtherException("GATT connect exception occurred!")); + } + } + return this.gattClientDevice; + } + + public disconnect(): void { + this.isActiveDisconnect = true; + this.disconnectGatt(); + } + + public destroy(): void { + this.lastState = LastState.CONNECT_IDLE; + this.disconnectGatt(); + this.refreshDeviceCache(); + this.closeBluetoothGatt(); + this.removeConnectGattCallback(); + this.removeRssiCallback(); + this.removeMtuChangedCallback(); + this.clearCharacterCallback(); + this.mainHandler.removeAllMessage(); + } + + private disconnectGatt(): void { + if(BleManager.MOCK_DEVICE) { + let message: Message = this.mainHandler.obtainMessage(); + message.what = BleMsg.MSG_DISCONNECTED; + message.obj = new BleConnectStateParameter(0); + this.mainHandler.sendMessage(message); + return; + } + if (!this.gattClientDevice) { + this.gattClientDevice.disconnect(); + } + } + + private refreshDeviceCache(): void { +// try { +// final Method refresh = gattClientDevice.class.getMethod("refresh"); +// if (!refresh && !this.gattClientDevice) { +// boolean success = (Boolean) refresh.invoke(this.gattClientDevice); +// BleLog.i("refreshDeviceCache, is success: " + success); +// } +// } catch (e: Error) { +// BleLog.i("exception occur while refreshing device: " + e.getMessage()); +// e.printStackTrace(); +// } + } + + private closeBluetoothGatt(): void { + if (this.gattClientDevice != null) { + this.gattClientDevice.close(); + } + } + + private handleMessage(msg: Message): void { + let _this = this; + switch (msg.what) { + case BleMsg.MSG_CONNECT_FAIL: { + console.log("handleMessage MSG_CONNECT_FAIL"); + _this.disconnectGatt(); + _this.refreshDeviceCache(); + _this.closeBluetoothGatt(); + + if (_this.connectRetryCount < BleManager.getInstance().getReConnectCount()) { + BleLog.e("Connect fail, try reconnect " + BleManager.getInstance().getReConnectInterval() + " millisecond later"); + ++_this.connectRetryCount; + + let message: Message = _this.mainHandler.obtainMessage(); + message.what = BleMsg.MSG_RECONNECT; + _this.mainHandler.sendMessageDelayed(message, BleManager.getInstance().getReConnectInterval()); + } else { + _this.lastState = LastState.CONNECT_FAILURE; + BleManager.getInstance().getMultipleBluetoothController().removeConnectingBle(_this); + + let para: BleConnectStateParameter = msg.obj; + let status: number = para.getStatus(); + if (_this.bleGattCallback != null) { + _this.bleGattCallback.onConnectFail(_this.bleDevice, new ConnectException(_this.gattClientDevice, status)); + } + } + } + break; + + case BleMsg.MSG_DISCONNECTED: { + console.log("handleMessage MSG_DISCONNECTED"); + _this.lastState = LastState.CONNECT_DISCONNECT; + BleManager.getInstance().getMultipleBluetoothController().removeBleBluetooth(_this); + + _this.disconnect(); + _this.refreshDeviceCache(); + _this.closeBluetoothGatt(); + _this.removeRssiCallback(); + _this.removeMtuChangedCallback(); + _this.clearCharacterCallback(); + _this.mainHandler.removeAllMessage(); + + let para: BleConnectStateParameter = msg.obj; + let isActive: boolean = para.isActive(); + let status: number = para.getStatus(); + if (_this.bleGattCallback != null) { + _this.bleGattCallback.onDisConnected(isActive, _this.bleDevice, _this.gattClientDevice, status); + } + } + break; + + case BleMsg.MSG_RECONNECT: { + console.log("handleMessage MSG_RECONNECT"); + _this.connect(_this.bleDevice, false, _this.bleGattCallback, _this.connectRetryCount); + } + break; + + case BleMsg.MSG_CONNECT_OVER_TIME: { + console.log("handleMessage MSG_CONNECT_OVER_TIME"); + _this.disconnectGatt(); + _this.refreshDeviceCache(); + _this.closeBluetoothGatt(); + + _this.lastState = LastState.CONNECT_FAILURE; + BleManager.getInstance().getMultipleBluetoothController().removeConnectingBle(_this); + + if (_this.bleGattCallback != null) { + _this.bleGattCallback.onConnectFail(_this.bleDevice, new TimeoutException()); + } + } + break; + + case BleMsg.MSG_DISCOVER_SERVICES: { + console.log("handleMessage MSG_DISCOVER_SERVICES"); + if(BleManager.MOCK_DEVICE) { + let message = this.mainHandler.obtainMessage(); + message.what = BleMsg.MSG_DISCOVER_SUCCESS; + message.obj = new BleConnectStateParameter(0); + this.mainHandler.sendMessageDelayed(message, 1000); + return; + } + if (_this.gattClientDevice != null) { + _this.gattClientDevice.getServices(this.serviceDiscoverCallback); + } else { + let message: Message = _this.mainHandler.obtainMessage(); + message.what = BleMsg.MSG_DISCOVER_FAIL; + _this.mainHandler.sendMessage(message); + } + } + break; + + case BleMsg.MSG_DISCOVER_FAIL: { + console.log("handleMessage MSG_DISCOVER_FAIL"); + _this.disconnectGatt(); + _this.refreshDeviceCache(); + _this.closeBluetoothGatt(); + + _this.lastState = LastState.CONNECT_FAILURE; + BleManager.getInstance().getMultipleBluetoothController().removeConnectingBle(_this); + + if (_this.bleGattCallback != null) { + _this.bleGattCallback.onConnectFail(_this.bleDevice, + new OtherException("GATT discover services exception occurred!")); + } + } + break; + + case BleMsg.MSG_DISCOVER_SUCCESS: { + console.log("handleMessage MSG_DISCOVER_SUCCESS"); + _this.lastState = LastState.CONNECT_CONNECTED; + _this.isActiveDisconnect = false; + BleManager.getInstance().getMultipleBluetoothController().removeConnectingBle(_this); + BleManager.getInstance().getMultipleBluetoothController().addBleBluetooth(_this); + + let para: BleConnectStateParameter = msg.obj; + let status: number = para.getStatus(); + console.log("_this.bleGattCallback:" + _this.bleGattCallback); + if (_this.bleGattCallback != null) { + _this.bleGattCallback.onConnectSuccess(_this.bleDevice, _this.gattClientDevice, status); + } + } + break; + default: + break; + } + } + + private initHandler(): void { + let _this = this; + this.mainHandler = new class extends Handler { + public handleMessage(msg: Message): void { + _this.handleMessage(msg); + } + } + } + + private connectStateChangeCallback = (newStatus: bluetooth.BLEConnectChangedState) => { //Callback + BleLog.i("onConnectionStateChange " + + "newStatus: " + newStatus.state + /*+ '\n' + "currentThread: " + Thread.currentThread().getId()*/); + + this.mainHandler.removeMessages(BleMsg.MSG_CONNECT_OVER_TIME); + + if (newStatus.state == bluetooth.ProfileConnectionState.STATE_CONNECTED) { + let message: Message = this.mainHandler.obtainMessage(); + message.what = BleMsg.MSG_DISCOVER_SERVICES; + this.mainHandler.sendMessageDelayed(message, 500); + } else if (newStatus.state == bluetooth.ProfileConnectionState.STATE_DISCONNECTED) { + if (this.lastState == LastState.CONNECT_CONNECTING) { + let message: Message = this.mainHandler.obtainMessage(); + message.what = BleMsg.MSG_CONNECT_FAIL; + message.obj = new BleConnectStateParameter(0); + this.mainHandler.sendMessage(message); + } else if (this.lastState == LastState.CONNECT_CONNECTED) { + let message: Message = this.mainHandler.obtainMessage(); + message.what = BleMsg.MSG_DISCONNECTED; + let para: BleConnectStateParameter = new BleConnectStateParameter(0); + para.setActive(this.isActiveDisconnect); + message.obj = para; + this.mainHandler.sendMessage(message); + } + } + } + + private serviceDiscoverCallback = (err, services: bluetooth.GattService[]) => { //AsyncCallback> + BleLog.i("onServicesDiscovered " + + "err: " + err.code + /*+ '\n' + "currentThread: " + Thread.currentThread().getId()*/); + + if (!err.code) { + let message: Message = this.mainHandler.obtainMessage(); + message.what = BleMsg.MSG_DISCOVER_SUCCESS; + message.obj = new BleConnectStateParameter(err.code); + this.mainHandler.sendMessage(message); + } else { + let message: Message = this.mainHandler.obtainMessage(); + message.what = BleMsg.MSG_DISCOVER_FAIL; + this.mainHandler.sendMessage(message); + } + } + + private characteristicChangedCallback = (err, characteristic) => { //Callback + BleLog.i("onCharacteristicChanged " + + "err: " + err.code + /*+ '\n' + "currentThread: " + Thread.currentThread().getId()*/); + + for (let callback of this.bleNotifyCallbackHashMap.values()) { + if (callback instanceof BleNotifyCallback) { + let bleNotifyCallback: BleNotifyCallback = callback; + if (TextUtils.equalsIgnoreCase(characteristic.characteristicUuid, bleNotifyCallback.getKey())) { + let handler: Handler = bleNotifyCallback.getHandler(); + if (handler != null) { + let message: Message = handler.obtainMessage(); + message.what = BleMsg.MSG_CHA_NOTIFY_DATA_CHANGE; + message.obj = bleNotifyCallback; + let bundle = new Map(); + bundle.set(BleMsg.KEY_NOTIFY_BUNDLE_VALUE, characteristic.characteristicValue); + message.setData(bundle); + handler.sendMessage(message); + } + } + } + } + + for (let callback of this.bleIndicateCallbackHashMap.values()) { + if (callback instanceof BleIndicateCallback) { + let bleIndicateCallback: BleIndicateCallback = callback; + if (TextUtils.equalsIgnoreCase(characteristic.characteristicUuid, bleIndicateCallback.getKey())) { + let handler: Handler = bleIndicateCallback.getHandler(); + if (handler != null) { + let message: Message = handler.obtainMessage(); + message.what = BleMsg.MSG_CHA_INDICATE_DATA_CHANGE; + message.obj = bleIndicateCallback; + let bundle = new Map(); + bundle.set(BleMsg.KEY_INDICATE_BUNDLE_VALUE, characteristic.characteristicValue); + message.setData(bundle); + handler.sendMessage(message); + } + } + } + } + } + + + private descriptorWriteCallback = (err, descriptor) => { + + for (let callback of this.bleNotifyCallbackHashMap.values()) { + if (callback instanceof BleNotifyCallback) { + let bleNotifyCallback: BleNotifyCallback = callback; + if (TextUtils.equalsIgnoreCase(descriptor.characteristicUuid, bleNotifyCallback.getKey())) { + let handler: Handler = bleNotifyCallback.getHandler(); + if (handler != null) { + let message: Message = handler.obtainMessage(); + message.what = BleMsg.MSG_CHA_NOTIFY_RESULT; + message.obj = bleNotifyCallback; + let bundle = new Map(); + bundle.set(BleMsg.KEY_NOTIFY_BUNDLE_STATUS, err); + message.setData(bundle); + handler.sendMessage(message); + } + } + } + } + + for (let callback of this.bleIndicateCallbackHashMap.values()) { + if (callback instanceof BleIndicateCallback) { + let bleIndicateCallback: BleIndicateCallback = callback; + if (TextUtils.equalsIgnoreCase(descriptor.characteristicUuid, bleIndicateCallback.getKey())) { + let handler: Handler = bleIndicateCallback.getHandler(); + if (handler != null) { + let message: Message = handler.obtainMessage(); + message.what = BleMsg.MSG_CHA_INDICATE_RESULT; + message.obj = bleIndicateCallback; + let bundle = new Map(); + bundle.set(BleMsg.KEY_INDICATE_BUNDLE_STATUS, err); + message.setData(bundle); + handler.sendMessage(message); + } + } + } + } + } + + + private characteristicWriteCallback = (err, characteristic) => { + for (let callback of this.bleWriteCallbackHashMap.values()) { + if (callback instanceof BleWriteCallback) { + let bleWriteCallback: BleWriteCallback = callback; + if (TextUtils.equalsIgnoreCase(characteristic.characteristicUuid, bleWriteCallback.getKey())) { + let handler: Handler = bleWriteCallback.getHandler(); + if (handler != null) { + let message: Message = handler.obtainMessage(); + message.what = BleMsg.MSG_CHA_WRITE_RESULT; + message.obj = bleWriteCallback; + let bundle = new Map(); + bundle.set(BleMsg.KEY_WRITE_BUNDLE_STATUS, err); + bundle.set(BleMsg.KEY_WRITE_BUNDLE_VALUE, characteristic.characteristicValue); + message.setData(bundle); + handler.sendMessage(message); + } + } + } + } + } + + private characteristicReadCallback = (err, characteristic) => { //AsyncCallback + for (let callback of this.bleReadCallbackHashMap.values()) { + if (callback instanceof BleReadCallback) { + let bleReadCallback: BleReadCallback = callback; + if (TextUtils.equalsIgnoreCase(characteristic.characteristicUuid, bleReadCallback.getKey())) { + let handler = bleReadCallback.getHandler(); + if (handler != null) { + let message = handler.obtainMessage(); + message.what = BleMsg.MSG_CHA_READ_RESULT; + message.obj = bleReadCallback; + let bundle = new Map(); + bundle.set(BleMsg.KEY_READ_BUNDLE_STATUS, err); + bundle.set(BleMsg.KEY_READ_BUNDLE_VALUE, characteristic.characteristicValue); + message.setData(bundle); + handler.sendMessage(message); + } + } + } + } + } + + private readRemoteRssiCallback = (err, rssi: number) => { //AsyncCallback + if (this.bleRssiCallback != null) { + let handler = this.bleRssiCallback.getHandler(); + if (handler != null) { + let message = handler.obtainMessage(); + message.what = BleMsg.MSG_READ_RSSI_RESULT; + message.obj = this.bleRssiCallback; + let bundle = new Map(); + bundle.set(BleMsg.KEY_READ_RSSI_BUNDLE_STATUS, err); + bundle.set(BleMsg.KEY_READ_RSSI_BUNDLE_VALUE, rssi); + message.setData(bundle); + handler.sendMessage(message); + } + } + } + + private setMtuCallback = (err, mtu: number) => { + if (this.bleMtuChangedCallback != null) { + let handler = this.bleMtuChangedCallback.getHandler(); + if (handler != null) { + let message = handler.obtainMessage(); + message.what = BleMsg.MSG_SET_MTU_RESULT; + message.obj = this.bleMtuChangedCallback; + let bundle = new Map(); + bundle.set(BleMsg.KEY_SET_MTU_BUNDLE_STATUS, err); + bundle.set(BleMsg.KEY_SET_MTU_BUNDLE_VALUE, mtu); + message.setData(bundle); + handler.sendMessage(message); + } + } + } +} diff --git a/wxFastBle/src/main/ets/bluetooth/BleConnector.ets b/wxFastBle/src/main/ets/bluetooth/BleConnector.ets new file mode 100644 index 0000000000000000000000000000000000000000..acea9df19316251be4d7e906e96488bf0d83c998 --- /dev/null +++ b/wxFastBle/src/main/ets/bluetooth/BleConnector.ets @@ -0,0 +1,631 @@ +import bluetooth from '@ohos.bluetooth'; +import BleManager from '../BleManager'; +import BleBluetooth from '../bluetooth/BleBluetooth'; +import BleIndicateCallback from '../callback/BleIndicateCallback'; +import BleMtuChangedCallback from '../callback/BleMtuChangedCallback'; +import BleNotifyCallback from '../callback/BleNotifyCallback'; +import BleReadCallback from '../callback/BleReadCallback'; +import BleRssiCallback from '../callback/BleRssiCallback'; +import BleWriteCallback from '../callback/BleWriteCallback'; +import BleMsg from '../data/BleMsg'; +import BleWriteState from '../data/BleWriteState'; +import GattException from '../exception/GattException'; +import OtherException from '../exception/OtherException'; +import TimeoutException from '../exception/TimeoutException'; +import {Message, Handler} from '../utils/Handler'; + +type UUID = string; +type byte = number; + +export default class BleConnector { + + private static readonly UUID_CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR = "00002902-0000-1000-8000-00805f9b34fb"; + + private mBluetoothGatt: bluetooth.GattClientDevice; + private mGattService: bluetooth.GattService; + private mCharacteristic: bluetooth.BLECharacteristic; + private mBleBluetooth: BleBluetooth; + private mHandler: Handler; + + constructor(bleBluetooth: BleBluetooth) { + this.mBleBluetooth = bleBluetooth; + this.mBluetoothGatt = bleBluetooth.getBluetoothGatt(); + let _this = this; + this.mHandler = new class extends Handler { + public handleMessage(msg: Message): void { + switch (msg.what) { + + case BleMsg.MSG_CHA_NOTIFY_START: { + let notifyCallback: BleNotifyCallback = msg.obj; + if (notifyCallback != null) + notifyCallback.onNotifyFailure(new TimeoutException()); + break; + } + + case BleMsg.MSG_CHA_NOTIFY_RESULT: { + _this.notifyMsgInit(); + + let notifyCallback: BleNotifyCallback = msg.obj; + let status: number = msg.getData().get(BleMsg.KEY_NOTIFY_BUNDLE_STATUS); + if (notifyCallback != null) { + if (status == 0) { + notifyCallback.onNotifySuccess(); + } else { + notifyCallback.onNotifyFailure(new GattException(status)); + } + } + break; + } + + case BleMsg.MSG_CHA_NOTIFY_DATA_CHANGE: { + let notifyCallback: BleNotifyCallback = msg.obj; + let value: Uint8Array = msg.getData().get(BleMsg.KEY_NOTIFY_BUNDLE_VALUE); + if (notifyCallback != null) { + notifyCallback.onCharacteristicChanged(value); + } + break; + } + + case BleMsg.MSG_CHA_INDICATE_START: { + let indicateCallback: BleIndicateCallback = msg.obj; + if (indicateCallback != null) + indicateCallback.onIndicateFailure(new TimeoutException()); + break; + } + + case BleMsg.MSG_CHA_INDICATE_RESULT: { + _this.indicateMsgInit(); + + let indicateCallback: BleIndicateCallback = msg.obj; + let status: number = msg.getData().get(BleMsg.KEY_INDICATE_BUNDLE_STATUS); + if (indicateCallback != null) { + if (status == 0) { + indicateCallback.onIndicateSuccess(); + } else { + indicateCallback.onIndicateFailure(new GattException(status)); + } + } + break; + } + + case BleMsg.MSG_CHA_INDICATE_DATA_CHANGE: { + let indicateCallback: BleIndicateCallback = msg.obj; + let value: Uint8Array = msg.getData().get(BleMsg.KEY_INDICATE_BUNDLE_VALUE); + if (indicateCallback != null) { + indicateCallback.onCharacteristicChanged(value); + } + break; + } + + case BleMsg.MSG_CHA_WRITE_START: { + let writeCallback: BleWriteCallback = msg.obj; + if (writeCallback != null) { + writeCallback.onWriteFailure(new TimeoutException()); + } + break; + } + + case BleMsg.MSG_CHA_WRITE_RESULT: { + _this.writeMsgInit(); + + let writeCallback: BleWriteCallback = msg.obj; + let status: number = msg.getData().get(BleMsg.KEY_WRITE_BUNDLE_STATUS); + let value: Uint8Array = msg.getData().get(BleMsg.KEY_WRITE_BUNDLE_VALUE); + if (writeCallback != null) { + if (status == 0) { + writeCallback.onWriteSuccess(BleWriteState.DATA_WRITE_SINGLE, BleWriteState.DATA_WRITE_SINGLE, value); + } else { + writeCallback.onWriteFailure(new GattException(status)); + } + } + break; + } + + case BleMsg.MSG_CHA_READ_START: { + let readCallback: BleReadCallback = msg.obj; + if (readCallback != null) + readCallback.onReadFailure(new TimeoutException()); + break; + } + + case BleMsg.MSG_CHA_READ_RESULT: { + _this.readMsgInit(); + + let readCallback: BleReadCallback = msg.obj; + let status: number = msg.getData().get(BleMsg.KEY_READ_BUNDLE_STATUS); + let value: Uint8Array = msg.getData().get(BleMsg.KEY_READ_BUNDLE_VALUE); + if (readCallback != null) { + if (status == 0) { + readCallback.onReadSuccess(value); + } else { + readCallback.onReadFailure(new GattException(status)); + } + } + break; + } + + case BleMsg.MSG_READ_RSSI_START: { + let rssiCallback: BleRssiCallback = msg.obj; + if (rssiCallback != null) + rssiCallback.onRssiFailure(new TimeoutException()); + break; + } + + case BleMsg.MSG_READ_RSSI_RESULT: { + _this.rssiMsgInit(); + + let rssiCallback: BleRssiCallback = msg.obj; + let status: number = msg.getData().get(BleMsg.KEY_READ_RSSI_BUNDLE_STATUS); + let value: number = msg.getData().get(BleMsg.KEY_READ_RSSI_BUNDLE_VALUE); + if (rssiCallback != null) { + if (status == 0) { + rssiCallback.onRssiSuccess(value); + } else { + rssiCallback.onRssiFailure(new GattException(status)); + } + } + break; + } + + case BleMsg.MSG_SET_MTU_START: { + let mtuChangedCallback: BleMtuChangedCallback = msg.obj; + if (mtuChangedCallback != null) + mtuChangedCallback.onSetMTUFailure(new TimeoutException()); + break; + } + + case BleMsg.MSG_SET_MTU_RESULT: { + _this.mtuChangedMsgInit(); + + let mtuChangedCallback: BleMtuChangedCallback = msg.obj; + let status: number = msg.getData().get(BleMsg.KEY_SET_MTU_BUNDLE_STATUS); + let value: number = msg.getData().get(BleMsg.KEY_SET_MTU_BUNDLE_VALUE); + if (mtuChangedCallback != null) { + if (status == 0) { + mtuChangedCallback.onMtuChanged(value); + } else { + mtuChangedCallback.onSetMTUFailure(new GattException(status)); + } + } + break; + } + } + } + } + } + + private withUUID(serviceUUID: UUID, characteristicUUID: UUID): BleConnector { + if (serviceUUID != null && this.mBluetoothGatt != null) { + let services = this.mBleBluetooth.getServices(); + for(let i=0; i 0*/) { + + this.handleCharacteristicNotifyCallback(bleNotifyCallback, uuid_notify); + this.setCharacteristicNotification(this.mBluetoothGatt, this.mCharacteristic, userCharacteristicDescriptor, true, bleNotifyCallback); + } else { + if (bleNotifyCallback != null) { + bleNotifyCallback.onNotifyFailure(new OtherException("this characteristic not support notify!")); + } + } + } + + /** + * stop notify + */ + public disableCharacteristicNotify(useCharacteristicDescriptor: boolean): boolean { + if (this.mCharacteristic != null + /*&& (this.mCharacteristic.getProperties() | bluetooth.BLECharacteristic.PROPERTY_NOTIFY) > 0*/) { + return this.setCharacteristicNotification(this.mBluetoothGatt, this.mCharacteristic, + useCharacteristicDescriptor, false, null); + } else { + return false; + } + } + + getDescriptor(characteristic/*: bluetooth.BLECharacteristic*/, uuid: UUID) /*: bluetooth.BLEDescriptor*/ { + for (let descriptor of characteristic.descriptors) { + if (descriptor.descriptorUuid == uuid) { + return descriptor; + } + } + return null; + } + + /** + * Value used to enable notification for a client configuration descriptor + */ + public static readonly ENABLE_NOTIFICATION_VALUE: byte[] = [0x01, 0x00]; + + /** + * Value used to enable indication for a client configuration descriptor + */ + public static readonly ENABLE_INDICATION_VALUE: byte[] = [0x02, 0x00]; + + /** + * Value used to disable notifications or indicatinos + */ + public static readonly DISABLE_NOTIFICATION_VALUE: byte[] = [0x00, 0x00]; + + /** + * notify setting + */ + private setCharacteristicNotification(gatt: bluetooth.GattClientDevice, + characteristic: bluetooth.BLECharacteristic, + useCharacteristicDescriptor: boolean, + enable: boolean, + bleNotifyCallback: BleNotifyCallback): boolean { + if (gatt == null || characteristic == null) { + this.notifyMsgInit(); + if (bleNotifyCallback != null) { + bleNotifyCallback.onNotifyFailure(new OtherException("gatt or characteristic equal null")); + } + return false; + } + + let success1: boolean = gatt.setNotifyCharacteristicChanged(characteristic, enable); + if (!success1) { + this.notifyMsgInit(); + if (bleNotifyCallback != null) { + bleNotifyCallback.onNotifyFailure(new OtherException("gatt setNotifyCharacteristicChanged fail")); + } + return false; + } + + let descriptor: /*bluetooth.BLEDescriptor*/any; + if (useCharacteristicDescriptor) { + descriptor = this.getDescriptor(characteristic, characteristic.characteristicUuid); + } else { + descriptor = this.getDescriptor(characteristic, BleConnector.UUID_CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR); + } + if (descriptor == null) { + this.notifyMsgInit(); + if (bleNotifyCallback != null) { + bleNotifyCallback.onNotifyFailure(new OtherException("descriptor equals null")); + } + return false; + } else { + descriptor.setValue(enable ? BleConnector.ENABLE_NOTIFICATION_VALUE : + BleConnector.DISABLE_NOTIFICATION_VALUE); + let success2: boolean = gatt.writeDescriptorValue(descriptor); + if (!success2) { + this.notifyMsgInit(); + if (bleNotifyCallback != null) { + bleNotifyCallback.onNotifyFailure(new OtherException("gatt writeDescriptor fail")); + } + } + return success2; + } + } + + /** + * indicate + */ + public enableCharacteristicIndicate(bleIndicateCallback: BleIndicateCallback, uuid_indicate: string, + useCharacteristicDescriptor: boolean): void { + if (this.mCharacteristic != null + /*&& (this.mCharacteristic.getProperties() | bluetooth.BLECharacteristic.PROPERTY_NOTIFY) > 0*/) { + this.handleCharacteristicIndicateCallback(bleIndicateCallback, uuid_indicate); + this.setCharacteristicIndication(this.mBluetoothGatt, this.mCharacteristic, + useCharacteristicDescriptor, true, bleIndicateCallback); + } else { + if (bleIndicateCallback != null) + bleIndicateCallback.onIndicateFailure(new OtherException("this characteristic not support indicate!")); + } + } + + + /** + * stop indicate + */ + public disableCharacteristicIndicate(userCharacteristicDescriptor: boolean): boolean { + if (this.mCharacteristic != null + /*&& (this.mCharacteristic.getProperties() | bluetooth.BLECharacteristic.PROPERTY_NOTIFY) > 0*/) { + return this.setCharacteristicIndication(this.mBluetoothGatt, this.mCharacteristic, + userCharacteristicDescriptor, false, null); + } else { + return false; + } + } + + /** + * indicate setting + */ + private setCharacteristicIndication(gatt: bluetooth.GattClientDevice, + characteristic: bluetooth.BLECharacteristic, + useCharacteristicDescriptor: boolean, + enable: boolean, + bleIndicateCallback: BleIndicateCallback): boolean { + if (gatt == null || characteristic == null) { + this.indicateMsgInit(); + if (bleIndicateCallback != null) { + bleIndicateCallback.onIndicateFailure(new OtherException("gatt or characteristic equal null")); + } + return false; + } + + let success1: boolean = gatt.setNotifyCharacteristicChanged(characteristic, enable); + if (!success1) { + this.indicateMsgInit(); + if (bleIndicateCallback != null) { + bleIndicateCallback.onIndicateFailure(new OtherException("gatt setNotifyCharacteristicChanged fail")); + } + return false; + } + + let descriptor/*: BLEDescriptor*/; + if (useCharacteristicDescriptor) { + descriptor = this.getDescriptor(characteristic, characteristic.characteristicUuid); + } else { + descriptor = this.getDescriptor(characteristic, BleConnector.UUID_CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR); + } + if (descriptor == null) { + this.indicateMsgInit(); + if (bleIndicateCallback != null) { + bleIndicateCallback.onIndicateFailure(new OtherException("descriptor equals null")); + } + return false; + } else { + descriptor.descriptorValue = enable ? BleConnector.ENABLE_INDICATION_VALUE : BleConnector.DISABLE_NOTIFICATION_VALUE; + let success2: boolean = gatt.writeDescriptorValue(descriptor); + if (!success2) { + this.indicateMsgInit(); + if (bleIndicateCallback != null) { + bleIndicateCallback.onIndicateFailure(new OtherException("gatt writeDescriptor fail")); + } + } + return success2; + } + } + + /** + * write + */ + public writeCharacteristic(data: Uint8Array, bleWriteCallback: BleWriteCallback, uuid_write: string): void { + if (data == null || data.length <= 0) { + if (bleWriteCallback != null) { + bleWriteCallback.onWriteFailure(new OtherException("the data to be written is empty")); + } + return; + } + + if (this.mCharacteristic == null + /*|| (this.mCharacteristic.getProperties() & (bluetooth.BLECharacteristic.PROPERTY_WRITE | bluetooth.BLECharacteristic.PROPERTY_WRITE_NO_RESPONSE)) == 0*/) { + if (bleWriteCallback != null) { + bleWriteCallback.onWriteFailure(new OtherException("this characteristic not support write!")); + } + return; + } + + this.mCharacteristic.characteristicValue = data; + this.handleCharacteristicWriteCallback(bleWriteCallback, uuid_write); + console.log("BleConnetor.writeCharacteristic calling gatt.writeCharacteristic"); + let success = this.mBluetoothGatt.writeCharacteristicValue(this.mCharacteristic); + console.log("success: "+ success); + + if (bleWriteCallback != null) { + this.writeMsgInit(); + if (success) { + bleWriteCallback.onWriteSuccess(data.length, data.length, data); + }else { + bleWriteCallback.onWriteFailure(new OtherException("gatt writeCharacteristic fail")); + } + } + } + + /** + * read + */ + public readCharacteristic(bleReadCallback: BleReadCallback, uuid_read: string): void { + if (this.mCharacteristic != null + /*&& (this.mCharacteristic.getProperties() & bluetooth.BLECharacteristic.PROPERTY_READ) > 0*/) { + + this.handleCharacteristicReadCallback(bleReadCallback, uuid_read); + this.mBluetoothGatt.readCharacteristicValue(this.mCharacteristic, (err, characteristic: bluetooth.BLECharacteristic)=>{ + this.readMsgInit(); + if (bleReadCallback != null) { + if(!err.code) { + bleReadCallback.onReadSuccess(new Uint8Array(characteristic.characteristicValue)); + }else{ + bleReadCallback.onReadFailure(new OtherException("gatt readCharacteristic fail:"+err.message)); + } + } + }); + } else { + if (bleReadCallback != null) { + bleReadCallback.onReadFailure(new OtherException("this characteristic not support read!")); + } + } + } + + /** + * rssi + */ + public readRemoteRssi(bleRssiCallback: BleRssiCallback): void { + this.handleRSSIReadCallback(bleRssiCallback); + this.mBluetoothGatt.getRssiValue((err, number)=>{ + this.rssiMsgInit(); + if (bleRssiCallback != null) { + if(!err.code) { + bleRssiCallback.onRssiSuccess(number); + }else{ + bleRssiCallback.onRssiFailure(new OtherException("gatt readRemoteRssi fail:"+err.message)); + } + } + }); + } + + /** + * set mtu + */ + public setMtu(requiredMtu: number, bleMtuChangedCallback: BleMtuChangedCallback): void { + this.handleSetMtuCallback(bleMtuChangedCallback); + if (!this.mBluetoothGatt.setBLEMtuSize(requiredMtu)) { + this.mtuChangedMsgInit(); + if (bleMtuChangedCallback != null) { + bleMtuChangedCallback.onSetMTUFailure(new OtherException("gatt requestMtu fail")); + } + } + } + + /** + * requestConnectionPriority + * + * @param connectionPriority Request a specific connection priority. Must be one of + * {@link BluetoothGatt#CONNECTION_PRIORITY_BALANCED}, + * {@link BluetoothGatt#CONNECTION_PRIORITY_HIGH} + * or {@link BluetoothGatt#CONNECTION_PRIORITY_LOW_POWER}. + * @throws IllegalArgumentException If the parameters are outside of their + * specified range. + */ + public requestConnectionPriority(connectionPriority: number): boolean { +// return this.mBluetoothGatt.requestConnectionPriority(connectionPriority); + return false; // not supported + } + + + /**************************************** Handle call back ******************************************/ + + /** + * notify + */ + private handleCharacteristicNotifyCallback(bleNotifyCallback: BleNotifyCallback, uuid_notify: string): void { + if (bleNotifyCallback != null) { + this.notifyMsgInit(); + bleNotifyCallback.setKey(uuid_notify); + bleNotifyCallback.setHandler(this.mHandler); + this.mBleBluetooth.addNotifyCallback(uuid_notify, bleNotifyCallback); + this.mHandler.sendMessageDelayed( + this.mHandler.obtainMessage(BleMsg.MSG_CHA_NOTIFY_START, bleNotifyCallback), + BleManager.getInstance().getOperateTimeout()); + } + } + + /** + * indicate + */ + private handleCharacteristicIndicateCallback(bleIndicateCallback: BleIndicateCallback, uuid_indicate: string): void { + if (bleIndicateCallback != null) { + this.indicateMsgInit(); + bleIndicateCallback.setKey(uuid_indicate); + bleIndicateCallback.setHandler(this.mHandler); + this.mBleBluetooth.addIndicateCallback(uuid_indicate, bleIndicateCallback); + this.mHandler.sendMessageDelayed( + this.mHandler.obtainMessage(BleMsg.MSG_CHA_INDICATE_START, bleIndicateCallback), + BleManager.getInstance().getOperateTimeout()); + } + } + + /** + * write + */ + private handleCharacteristicWriteCallback(bleWriteCallback: BleWriteCallback, uuid_write: string): void { + if (bleWriteCallback != null) { + this.writeMsgInit(); + bleWriteCallback.setKey(uuid_write); + bleWriteCallback.setHandler(this.mHandler); + this.mBleBluetooth.addWriteCallback(uuid_write, bleWriteCallback); + this.mHandler.sendMessageDelayed( + this.mHandler.obtainMessage(BleMsg.MSG_CHA_WRITE_START, bleWriteCallback), + BleManager.getInstance().getOperateTimeout()); + } + } + + /** + * read + */ + private handleCharacteristicReadCallback(bleReadCallback: BleReadCallback, uuid_read: string): void { + if (bleReadCallback != null) { + this.readMsgInit(); + bleReadCallback.setKey(uuid_read); + bleReadCallback.setHandler(this.mHandler); + this.mBleBluetooth.addReadCallback(uuid_read, bleReadCallback); + this.mHandler.sendMessageDelayed( + this.mHandler.obtainMessage(BleMsg.MSG_CHA_READ_START, bleReadCallback), + BleManager.getInstance().getOperateTimeout()); + } + } + + /** + * rssi + */ + private handleRSSIReadCallback(bleRssiCallback: BleRssiCallback): void { + if (bleRssiCallback != null) { + this.rssiMsgInit(); + bleRssiCallback.setHandler(this.mHandler); + this.mBleBluetooth.addRssiCallback(bleRssiCallback); + this.mHandler.sendMessageDelayed( + this.mHandler.obtainMessage(BleMsg.MSG_READ_RSSI_START, bleRssiCallback), + BleManager.getInstance().getOperateTimeout()); + } + } + + /** + * set mtu + */ + private handleSetMtuCallback(bleMtuChangedCallback: BleMtuChangedCallback): void { + if (bleMtuChangedCallback != null) { + this.mtuChangedMsgInit(); + bleMtuChangedCallback.setHandler(this.mHandler); + this.mBleBluetooth.addMtuChangedCallback(bleMtuChangedCallback); + this.mHandler.sendMessageDelayed( + this.mHandler.obtainMessage(BleMsg.MSG_SET_MTU_START, bleMtuChangedCallback), + BleManager.getInstance().getOperateTimeout()); + } + } + + public notifyMsgInit(): void { + this.mHandler.removeMessages(BleMsg.MSG_CHA_NOTIFY_START); + } + + public indicateMsgInit(): void { + this.mHandler.removeMessages(BleMsg.MSG_CHA_INDICATE_START); + } + + public writeMsgInit(): void { + this.mHandler.removeMessages(BleMsg.MSG_CHA_WRITE_START); + } + + public readMsgInit(): void { + this.mHandler.removeMessages(BleMsg.MSG_CHA_READ_START); + } + + public rssiMsgInit(): void { + this.mHandler.removeMessages(BleMsg.MSG_READ_RSSI_START); + } + + public mtuChangedMsgInit(): void { + this.mHandler.removeMessages(BleMsg.MSG_SET_MTU_START); + } + +} diff --git a/wxFastBle/src/main/ets/bluetooth/MultipleBluetoothController.ets b/wxFastBle/src/main/ets/bluetooth/MultipleBluetoothController.ets new file mode 100644 index 0000000000000000000000000000000000000000..7ebca5c39a87960702113ba37598797d713b16cb --- /dev/null +++ b/wxFastBle/src/main/ets/bluetooth/MultipleBluetoothController.ets @@ -0,0 +1,124 @@ +import BleManager from '../BleManager'; +import BleBluetooth from '../bluetooth/BleBluetooth'; +import BleDevice from '../data/BleDevice'; +import BleLruHashMap from '../utils/BleLruHashMap'; +import TextUtils from '../utils/TextUtils'; +import ArrayUtils from '../utils/ArrayUtils' + +export default class MultipleBluetoothController { + + private bleLruHashMap: BleLruHashMap; + private bleTempHashMap: Map; + + public constructor() { + this.bleLruHashMap = new BleLruHashMap(BleManager.getInstance().getMaxConnectCount()); + this.bleTempHashMap = new Map(); + } + + public buildConnectingBle(bleDevice: BleDevice): BleBluetooth { + let bleBluetooth = new BleBluetooth(bleDevice); + if (!this.bleTempHashMap.has(bleBluetooth.getDeviceKey())) { + this.bleTempHashMap.set(bleBluetooth.getDeviceKey(), bleBluetooth); + } + return bleBluetooth; + } + + public removeConnectingBle(bleBluetooth: BleBluetooth): void { + if (bleBluetooth == null) { + return; + } + if (this.bleTempHashMap.has(bleBluetooth.getDeviceKey())) { + this.bleTempHashMap.delete(bleBluetooth.getDeviceKey()); + } + } + + public addBleBluetooth(bleBluetooth: BleBluetooth): void { + if (bleBluetooth == null) { + return; + } + if (!this.bleLruHashMap.has(bleBluetooth.getDeviceKey())) { + this.bleLruHashMap.set(bleBluetooth.getDeviceKey(), bleBluetooth); + } + } + + public removeBleBluetooth(bleBluetooth: BleBluetooth): void { + if (bleBluetooth == null) { + return; + } + if (this.bleLruHashMap.has(bleBluetooth.getDeviceKey())) { + this.bleLruHashMap.delete(bleBluetooth.getDeviceKey()); + } + } + + public isContainDevice(bleDevice: BleDevice): boolean { + return bleDevice != null && this.bleLruHashMap.has(bleDevice.getKey()); + } + +// public isContainDevice(bluetoothDevice: BluetoothDevice): boolean { +// return bluetoothDevice != null && this.bleLruHashMap.has(bluetoothDevice.getName() + bluetoothDevice.getAddress()); +// } + + public getBleBluetooth(bleDevice: BleDevice): BleBluetooth { + if (bleDevice != null) { + if (this.bleLruHashMap.has(bleDevice.getKey())) { + return this.bleLruHashMap.get(bleDevice.getKey()); + } + } + return null; + } + + public disconnect(bleDevice: BleDevice): void { + if (this.isContainDevice(bleDevice)) { + this.getBleBluetooth(bleDevice).disconnect(); + } + } + + public disconnectAllDevice(): void { + this.bleLruHashMap.forEach((value, key, map)=>{ + value.disconnect(); + }) + this.bleLruHashMap.clear(); + } + + public destroy(): void { + this.bleLruHashMap.forEach((value, key, map)=>{ + value.destroy(); + }); + this.bleLruHashMap.clear(); + + + this.bleTempHashMap.forEach((value, key, map)=>{ + value.destroy(); + }); + this.bleTempHashMap.clear(); + } + + public getBleBluetoothList(): Array { + let bleBluetoothList = ArrayUtils.getAll(this.bleLruHashMap.values()); + bleBluetoothList.sort((lhs, rhs) => { + return TextUtils.compareToIgnoreCase(lhs.getDeviceKey(), rhs.getDeviceKey()); + }); + return bleBluetoothList; + } + + public getDeviceList(): Array { + this.refreshConnectedDevice(); + let deviceList = new Array(); + for (let bleBluetooth of this.getBleBluetoothList()){ + if (bleBluetooth != null) { + deviceList.push(bleBluetooth.getDevice()); + } + } + return deviceList; + } + + public refreshConnectedDevice(): void { + let bluetoothList: Array = this.getBleBluetoothList(); + for (let i = 0; bluetoothList != null && i < bluetoothList.length; i++) { + let bleBluetooth: BleBluetooth = bluetoothList[i]; + if (!BleManager.getInstance().isConnected(bleBluetooth.getDevice())) { + this.removeBleBluetooth(bleBluetooth); + } + } + } +} diff --git a/wxFastBle/src/main/ets/bluetooth/SplitWriter.ets b/wxFastBle/src/main/ets/bluetooth/SplitWriter.ets new file mode 100644 index 0000000000000000000000000000000000000000..e165dd115e02b642a1e40818b86001605c0c4ae6 --- /dev/null +++ b/wxFastBle/src/main/ets/bluetooth/SplitWriter.ets @@ -0,0 +1,146 @@ +import BleManager from '../BleManager'; +import BleBluetooth from '../bluetooth/BleBluetooth'; +import BleWriteCallback from '../callback/BleWriteCallback'; +import BleMsg from '../data/BleMsg'; +import BleException from '../exception/BleException'; +import OtherException from '../exception/OtherException'; +import BleLog from '../utils/BleLog'; +import {Message, Handler} from '../utils/Handler'; +import {Queue} from '../utils/Queue' + +type byte = number; + +export default class SplitWriter { + + private mHandler: Handler; + + private mBleBluetooth: BleBluetooth; + private mUuid_service: string; + private mUuid_write: string; + private mData: Uint8Array; + private mCount: number; + private mSendNextWhenLastSuccess: boolean; + private mIntervalBetweenTwoPackage: number; + private mCallback: BleWriteCallback; + private mDataQueue: Queue; + private mTotalNum: number; + + public constructor() { + let _this = this; + this.mHandler = new class extends Handler { + public handleMessage(msg: Message): void { + if (msg.what == BleMsg.MSG_SPLIT_WRITE_NEXT) { + _this.write(); + } + } + }; + } + + public splitWrite(bleBluetooth: BleBluetooth, + uuid_service: string, + uuid_write: string, + data: Uint8Array, + sendNextWhenLastSuccess: boolean, + intervalBetweenTwoPackage: number, + callback: BleWriteCallback): void { + this.mBleBluetooth = bleBluetooth; + this.mUuid_service = uuid_service; + this.mUuid_write = uuid_write; + this.mData = data; + this.mSendNextWhenLastSuccess = sendNextWhenLastSuccess; + this.mIntervalBetweenTwoPackage = intervalBetweenTwoPackage; + this.mCount = BleManager.getInstance().getSplitWriteNum(); + this.mCallback = callback; + + this.splitWriteInternal(); + } + + private splitWriteInternal(): void { + if (this.mData == null) { + throw new EvalError("data is Null!"); + } + if (this.mCount < 1) { + throw new EvalError("split count should higher than 0!"); + } + this.mDataQueue = SplitWriter.splitByte(this.mData, this.mCount); + this.mTotalNum = this.mDataQueue.size(); + this.write(); + } + + private write(): void { + if (this.mDataQueue.peek() == null) { + this.release(); + return; + } + + let _this = this; + let data: Uint8Array = this.mDataQueue.poll(); + this.mBleBluetooth.newBleConnector() + .withUUIDString(this.mUuid_service, this.mUuid_write) + .writeCharacteristic( + data, + new class extends BleWriteCallback { + public onWriteSuccess(current: number, total: number, justWrite: Uint8Array): void { + let position = _this.mTotalNum - _this.mDataQueue.size(); + if (_this.mCallback != null) { + _this.mCallback.onWriteSuccess(position, _this.mTotalNum, justWrite); + } + if (_this.mSendNextWhenLastSuccess) { + let message: Message = _this.mHandler.obtainMessage(BleMsg.MSG_SPLIT_WRITE_NEXT); + _this.mHandler.sendMessageDelayed(message, _this.mIntervalBetweenTwoPackage); + } + } + + public onWriteFailure(exception: BleException): void { + if (_this.mCallback != null) { + _this.mCallback.onWriteFailure(new OtherException("exception occur while writing: " + exception.getDescription())); + } + if (_this.mSendNextWhenLastSuccess) { + let message: Message = _this.mHandler.obtainMessage(BleMsg.MSG_SPLIT_WRITE_NEXT); + _this.mHandler.sendMessageDelayed(message, _this.mIntervalBetweenTwoPackage); + } + } + }, + _this.mUuid_write); + + if (!_this.mSendNextWhenLastSuccess) { + let message = _this.mHandler.obtainMessage(BleMsg.MSG_SPLIT_WRITE_NEXT); + _this.mHandler.sendMessageDelayed(message, _this.mIntervalBetweenTwoPackage); + } + } + + private release(): void { + this.mHandler.removeAllMessage(); + } + + private static splitByte(data: Uint8Array, count: number): Queue { + if (count > 20) { + BleLog.w("Be careful: split count beyond 20! Ensure MTU higher than 23!"); + } + let byteQueue: Queue = new Queue(); + let pkgCount: number; + if (data.length % count == 0) { + pkgCount = data.length / count; + } else { + pkgCount = Math.round(data.length / count + 1); + } + + if (pkgCount > 0) { + for (let i = 0; i < pkgCount; i++) { + let dataPkg: Uint8Array; + let j: number; + if (pkgCount == 1 || i == pkgCount - 1) { + j = data.length % count == 0 ? count : data.length % count; +// System.arraycopy(data, i * count, dataPkg = new byte[j], 0, j); + } else { +// System.arraycopy(data, i * count, dataPkg = new byte[count], 0, count); + } + byteQueue.offer(dataPkg); + } + } + + return byteQueue; + } + + +} diff --git a/wxFastBle/src/main/ets/bluetooth/mockBluetooth.ets b/wxFastBle/src/main/ets/bluetooth/mockBluetooth.ets new file mode 100644 index 0000000000000000000000000000000000000000..e65787ed9bd7d49643fe443a5b779f478dbc5b08 --- /dev/null +++ b/wxFastBle/src/main/ets/bluetooth/mockBluetooth.ets @@ -0,0 +1,36 @@ +export default class mockBluetooth { + private scanCallback; + private deviceCount = 10; + private scanHandle; + + public onScan(callback: (data) => void): void { + console.log('mockBluetooth.onScan'); + this.scanCallback = callback; + } + + public startBLEScan(filters: any, options?: any): void { + console.log('mockBluetooth.startBLEScan'); + this.scanHandle = setInterval(() => { + let devices = [ + { + deviceId: '00:00:00:00:' + this.deviceCount, + deviceName: 'test' + this.deviceCount, + rssi: -100 * Math.random(), + data: 'abcdefg' + }]; + this.scanCallback(devices); + if (this.deviceCount++ == 100) { + clearInterval(this.scanHandle); + } + }, 500); + } + + public stopBLEScan(): void { + console.log('mockBluetooth.stopBLEScan'); + clearInterval(this.scanHandle); + } + + public getConnectedBLEDevices(): string[] { + return ['00:00:00:00:10']; + } +} \ No newline at end of file diff --git a/wxFastBle/src/main/ets/callback/BleBaseCallback.ets b/wxFastBle/src/main/ets/callback/BleBaseCallback.ets new file mode 100644 index 0000000000000000000000000000000000000000..aeafda32babd491b39935e94e245cc3debcbd31f --- /dev/null +++ b/wxFastBle/src/main/ets/callback/BleBaseCallback.ets @@ -0,0 +1,23 @@ +import {Handler} from '../utils/Handler'; + +export default abstract class BleBaseCallback { + + private key: string; + private handler: Handler; + + public getKey(): string { + return this.key; + } + + public setKey(key: string): void { + this.key = key; + } + + public getHandler(): Handler { + return this.handler; + } + + public setHandler(handler: Handler): void { + this.handler = handler; + } +} diff --git a/wxFastBle/src/main/ets/callback/BleGattCallback.ets b/wxFastBle/src/main/ets/callback/BleGattCallback.ets new file mode 100644 index 0000000000000000000000000000000000000000..a6c3bb247de7e4742358fc794eacc3b5b71fb762 --- /dev/null +++ b/wxFastBle/src/main/ets/callback/BleGattCallback.ets @@ -0,0 +1,13 @@ +import bluetooth from '@ohos.bluetooth'; +import BleDevice from '../data/BleDevice' +import BleException from '../exception/BleException' + +export default abstract class BleGattCallback { + public abstract onStartConnect(); + + public abstract onConnectFail(bleDevice: BleDevice, exception: BleException); + + public abstract onConnectSuccess(bleDevice: BleDevice, gatt: bluetooth.GattClientDevice, status: number); + + public abstract onDisConnected(isActiveDisConnected: boolean, device: BleDevice, gatt: bluetooth.GattClientDevice, status: number); +} \ No newline at end of file diff --git a/wxFastBle/src/main/ets/callback/BleIndicateCallback.ets b/wxFastBle/src/main/ets/callback/BleIndicateCallback.ets new file mode 100644 index 0000000000000000000000000000000000000000..650c383e066ab4d7a2738b7500a5a40c10a4f3f1 --- /dev/null +++ b/wxFastBle/src/main/ets/callback/BleIndicateCallback.ets @@ -0,0 +1,10 @@ +import BleException from '../exception/BleException' +import BleBaseCallback from './BleBaseCallback' + +export default abstract class BleIndicateCallback extends BleBaseCallback { + public abstract onIndicateSuccess(); + + public abstract onIndicateFailure(exception: BleException); + + public abstract onCharacteristicChanged(data: Uint8Array); +} \ No newline at end of file diff --git a/wxFastBle/src/main/ets/callback/BleMtuChangedCallback.ets b/wxFastBle/src/main/ets/callback/BleMtuChangedCallback.ets new file mode 100644 index 0000000000000000000000000000000000000000..b1c75b58800faf4b3f4173a7566f935164451716 --- /dev/null +++ b/wxFastBle/src/main/ets/callback/BleMtuChangedCallback.ets @@ -0,0 +1,8 @@ +import BleException from '../exception/BleException' +import BleBaseCallback from './BleBaseCallback' + +export default abstract class BleMtuChangedCallback extends BleBaseCallback { + public abstract onSetMTUFailure(exception: BleException); + + public abstract onMtuChanged(mtu: number); +} \ No newline at end of file diff --git a/wxFastBle/src/main/ets/callback/BleNotifyCallback.ets b/wxFastBle/src/main/ets/callback/BleNotifyCallback.ets new file mode 100644 index 0000000000000000000000000000000000000000..653367d193889142504ae82073d4df621586514b --- /dev/null +++ b/wxFastBle/src/main/ets/callback/BleNotifyCallback.ets @@ -0,0 +1,10 @@ +import BleException from '../exception/BleException' +import BleBaseCallback from './BleBaseCallback' + +export default abstract class BleNotifyCallback extends BleBaseCallback { + public abstract onNotifySuccess(); + + public abstract onNotifyFailure(exception: BleException); + + public abstract onCharacteristicChanged(data: Uint8Array); +} \ No newline at end of file diff --git a/wxFastBle/src/main/ets/callback/BleReadCallback.ets b/wxFastBle/src/main/ets/callback/BleReadCallback.ets new file mode 100644 index 0000000000000000000000000000000000000000..42bc6da68b441301b2947abc33f27589da1dc13e --- /dev/null +++ b/wxFastBle/src/main/ets/callback/BleReadCallback.ets @@ -0,0 +1,8 @@ +import BleException from '../exception/BleException' +import BleBaseCallback from './BleBaseCallback' + +export default abstract class BleReadCallback extends BleBaseCallback { + public abstract onReadSuccess(data: Uint8Array); + + public abstract onReadFailure(exception: BleException); +} \ No newline at end of file diff --git a/wxFastBle/src/main/ets/callback/BleRssiCallback.ets b/wxFastBle/src/main/ets/callback/BleRssiCallback.ets new file mode 100644 index 0000000000000000000000000000000000000000..c19df6aa7bfb2c3e8c7ce78164707afa8bddfe28 --- /dev/null +++ b/wxFastBle/src/main/ets/callback/BleRssiCallback.ets @@ -0,0 +1,8 @@ +import BleException from '../exception/BleException' +import BleBaseCallback from './BleBaseCallback' + +export default abstract class BleRssiCallback extends BleBaseCallback { + public abstract onRssiFailure(exception: BleException); + + public abstract onRssiSuccess(rssi: number); +} \ No newline at end of file diff --git a/wxFastBle/src/main/ets/callback/BleScanAndConnectCallback.ets b/wxFastBle/src/main/ets/callback/BleScanAndConnectCallback.ets new file mode 100644 index 0000000000000000000000000000000000000000..c2a9c05a135ef9969ed0c689fde3792bc6b1a346 --- /dev/null +++ b/wxFastBle/src/main/ets/callback/BleScanAndConnectCallback.ets @@ -0,0 +1,13 @@ +import BleDevice from '../data/BleDevice' +import BleScanPresenterImp from '../callback/BleScanPresenterImp' +import BleGattCallback from '../callback/BleGattCallback' + +interface BleScanAndConnectCallback extends BleScanPresenterImp{} + +abstract class BleScanAndConnectCallback extends BleGattCallback implements BleScanPresenterImp{ + public abstract onScanFinished(scanResult: BleDevice); + + public abstract onLeScan(bleDevice: BleDevice); +} + +export default BleScanAndConnectCallback; \ No newline at end of file diff --git a/wxFastBle/src/main/ets/callback/BleScanCallback.ets b/wxFastBle/src/main/ets/callback/BleScanCallback.ets new file mode 100644 index 0000000000000000000000000000000000000000..d882b2f8a93f39a4ac546afedc77d2a64848fb04 --- /dev/null +++ b/wxFastBle/src/main/ets/callback/BleScanCallback.ets @@ -0,0 +1,12 @@ +import BleDevice from '../data/BleDevice' +import BleScanPresenterImp from '../callback/BleScanPresenterImp' + +interface BleScanCallback extends BleScanPresenterImp {} + +abstract class BleScanCallback implements BleScanPresenterImp { + public abstract onScanFinished(scanResultList: Array); + + public abstract onLeScan(bleDevice: BleDevice); +} + +export default BleScanCallback; \ No newline at end of file diff --git a/wxFastBle/src/main/ets/callback/BleScanPresenterImp.ets b/wxFastBle/src/main/ets/callback/BleScanPresenterImp.ets new file mode 100644 index 0000000000000000000000000000000000000000..0b93684c279eeda89a23330628506d751bd9d3b1 --- /dev/null +++ b/wxFastBle/src/main/ets/callback/BleScanPresenterImp.ets @@ -0,0 +1,8 @@ +import BleDevice from '../data/BleDevice' + +export default interface BleScanPresenterImp { + + onScanStarted(success: boolean): void; + + onScanning(bleDevice: BleDevice): void; +} diff --git a/wxFastBle/src/main/ets/callback/BleWriteCallback.ets b/wxFastBle/src/main/ets/callback/BleWriteCallback.ets new file mode 100644 index 0000000000000000000000000000000000000000..0efdb75a253018c42f93101a7ca084c22b176f58 --- /dev/null +++ b/wxFastBle/src/main/ets/callback/BleWriteCallback.ets @@ -0,0 +1,8 @@ +import BleException from '../exception/BleException' +import BleBaseCallback from './BleBaseCallback' + +export default abstract class BleWriteCallback extends BleBaseCallback { + public abstract onWriteSuccess(current: number, total: number, justWrite: Uint8Array); + + public abstract onWriteFailure(exception: BleException); +} \ No newline at end of file diff --git a/wxFastBle/src/main/ets/data/BleConnectStateParameter.ets b/wxFastBle/src/main/ets/data/BleConnectStateParameter.ets new file mode 100644 index 0000000000000000000000000000000000000000..90d9075fde7803ee91256b5e8d8be2944cad10ec --- /dev/null +++ b/wxFastBle/src/main/ets/data/BleConnectStateParameter.ets @@ -0,0 +1,24 @@ +export default class BleConnectStateParameter { + private status: number; + private isActive_: boolean; + + public constructor(status: number) { + this.status = status; + } + + public getStatus(): number { + return this.status; + } + + public setStatus(status: number) { + this.status = status; + } + + public isActive(): boolean { + return this.isActive_; + } + + public setActive(active: boolean) { + this.isActive_ = active; + } +} diff --git a/wxFastBle/src/main/ets/data/BleDevice.ets b/wxFastBle/src/main/ets/data/BleDevice.ets new file mode 100644 index 0000000000000000000000000000000000000000..010fbfda85225b7956a0eb274d846f5dcd52f7c0 --- /dev/null +++ b/wxFastBle/src/main/ets/data/BleDevice.ets @@ -0,0 +1,72 @@ +export default class BleDevice { + mDeviceName: string; + mDeviceId: string; //mac + mScanRecord: Uint8Array; + mRssi: number; + mTimestampNanos: number; + + public constructor(deviceId: string, name: string, rssi?: number, scanRecord ?: Uint8Array, timestampNanos ?: number) { + this.mDeviceName = name; + this.mDeviceId = deviceId; + if (scanRecord) { + this.mScanRecord = scanRecord; + } + if (rssi) { + this.mRssi = rssi; + } + if (timestampNanos) { + this.mTimestampNanos = timestampNanos; + } + } + + public static copy(device: BleDevice): BleDevice { + return new BleDevice(device.mDeviceId, device.mDeviceName, device.mRssi, device.mScanRecord, device.mTimestampNanos); + } + + public getName(): string { + return this.mDeviceName; + } + + public getMac(): string { + return this.mDeviceId; + } + + public getKey(): string { + if (this.mDeviceName != null && this.mDeviceId != null) { + return this.mDeviceName + this.mDeviceId; + } + return ""; + } + +// public getDevice(): bluetooth.GattClientDevice { +// return bluetooth.BLE.createGattClientDevice(this.mDeviceId); +// } +// +// public setDevice(device: bluetooth.GattClientDevice) { +// this.mDevice = device; +// } + + public getScanRecord(): Uint8Array { + return this.mScanRecord; + } + + public setScanRecord(scanRecord: Uint8Array) { + this.mScanRecord = scanRecord; + } + + public getRssi(): number { + return this.mRssi; + } + + public setRssi(rssi: number) { + this.mRssi = rssi; + } + + public getTimestampNanos(): number { + return this.mTimestampNanos; + } + + public setTimestampNanos(timestampNanos: number) { + this.mTimestampNanos = timestampNanos; + } +} \ No newline at end of file diff --git a/wxFastBle/src/main/ets/data/BleMsg.ets b/wxFastBle/src/main/ets/data/BleMsg.ets new file mode 100644 index 0000000000000000000000000000000000000000..c1cdc7c78993f47bf9180206e319de87393d06f3 --- /dev/null +++ b/wxFastBle/src/main/ets/data/BleMsg.ets @@ -0,0 +1,52 @@ +export default class BleMsg { + // Scan + public static readonly MSG_SCAN_DEVICE: number = 0X00; + + // Connect + public static readonly MSG_CONNECT_FAIL: number = 0x01; + public static readonly MSG_DISCONNECTED: number = 0x02; + public static readonly MSG_RECONNECT: number = 0x03; + public static readonly MSG_DISCOVER_SERVICES: number = 0x04; + public static readonly MSG_DISCOVER_FAIL: number = 0x05; + public static readonly MSG_DISCOVER_SUCCESS: number = 0x06; + public static readonly MSG_CONNECT_OVER_TIME: number = 0x07; + + // Notify + public static readonly MSG_CHA_NOTIFY_START: number = 0x11; + public static readonly MSG_CHA_NOTIFY_RESULT: number = 0x12; + public static readonly MSG_CHA_NOTIFY_DATA_CHANGE: number = 0x13; + public static readonly KEY_NOTIFY_BUNDLE_STATUS: string = "notify_status"; + public static readonly KEY_NOTIFY_BUNDLE_VALUE: string = "notify_value"; + + // Indicate + public static readonly MSG_CHA_INDICATE_START: number = 0x21; + public static readonly MSG_CHA_INDICATE_RESULT: number = 0x22; + public static readonly MSG_CHA_INDICATE_DATA_CHANGE: number = 0x23; + public static readonly KEY_INDICATE_BUNDLE_STATUS: string = "indicate_status"; + public static readonly KEY_INDICATE_BUNDLE_VALUE: string = "indicate_value"; + + // Write + public static readonly MSG_CHA_WRITE_START: number = 0x31; + public static readonly MSG_CHA_WRITE_RESULT: number = 0x32; + public static readonly MSG_SPLIT_WRITE_NEXT: number = 0x33; + public static readonly KEY_WRITE_BUNDLE_STATUS: string = "write_status"; + public static readonly KEY_WRITE_BUNDLE_VALUE: string = "write_value"; + + // Read + public static readonly MSG_CHA_READ_START: number = 0x41; + public static readonly MSG_CHA_READ_RESULT: number = 0x42; + public static readonly KEY_READ_BUNDLE_STATUS: string = "read_status"; + public static readonly KEY_READ_BUNDLE_VALUE: string = "read_value"; + + // Rssi + public static readonly MSG_READ_RSSI_START: number = 0x51; + public static readonly MSG_READ_RSSI_RESULT: number = 0x52; + public static readonly KEY_READ_RSSI_BUNDLE_STATUS: string = "rssi_status"; + public static readonly KEY_READ_RSSI_BUNDLE_VALUE: string = "rssi_value"; + + // Mtu + public static readonly MSG_SET_MTU_START: number = 0x61; + public static readonly MSG_SET_MTU_RESULT: number = 0x62; + public static readonly KEY_SET_MTU_BUNDLE_STATUS: string = "mtu_status"; + public static readonly KEY_SET_MTU_BUNDLE_VALUE: string = "mtu_value"; +} diff --git a/wxFastBle/src/main/ets/data/BleScanState.ets b/wxFastBle/src/main/ets/data/BleScanState.ets new file mode 100644 index 0000000000000000000000000000000000000000..73354fb3f70ee6bf8a8bf0de3466792e7f4735d0 --- /dev/null +++ b/wxFastBle/src/main/ets/data/BleScanState.ets @@ -0,0 +1,18 @@ +enum BleScanState { + STATE_IDLE = -1, + STATE_SCANNING = 0X01, +} +export default BleScanState; + +// +//export namespace BleScanState { +//// let code; +// BleScanState(code: number) { +// this.code = code; +// } +// +// public getCode(): number { +// return this.code; +// } +//} + diff --git a/wxFastBle/src/main/ets/data/BleWriteState.ets b/wxFastBle/src/main/ets/data/BleWriteState.ets new file mode 100644 index 0000000000000000000000000000000000000000..bc596a9aa94731311a289c3ccfe83985129f556e --- /dev/null +++ b/wxFastBle/src/main/ets/data/BleWriteState.ets @@ -0,0 +1,3 @@ +export default class BleWriteState { + public static readonly DATA_WRITE_SINGLE = 1; +} diff --git a/wxFastBle/src/main/ets/exception/BleException.ets b/wxFastBle/src/main/ets/exception/BleException.ets new file mode 100644 index 0000000000000000000000000000000000000000..6f717802c6e2e08ba345f4ea0730ae082a0ea9a6 --- /dev/null +++ b/wxFastBle/src/main/ets/exception/BleException.ets @@ -0,0 +1,41 @@ +export default abstract class BleException { + + private static readonly serialVersionUID = 8004414918500865564; + + public static readonly ERROR_CODE_TIMEOUT = 100; + public static readonly ERROR_CODE_GATT = 101; + public static readonly ERROR_CODE_OTHER = 102; + + private code: number; + private description: string; + + public constructor(code: number, description: string) { + this.code = code; + this.description = description; + } + + public getCode(): number { + return this.code; + } + + public setCode(code: number): BleException { + this.code = code; + return this; + } + + public getDescription(): string { + return this.description; + } + + public setDescription(description: string): BleException { + this.description = description; + return this; + } + + public toString(): string { + return "BleException { " + + "code=" + this.code + + ", description='" + this.description + '\'' + + '}'; + } +} diff --git a/wxFastBle/src/main/ets/exception/ConnectException.ets b/wxFastBle/src/main/ets/exception/ConnectException.ets new file mode 100644 index 0000000000000000000000000000000000000000..c3dce970ceaa1a93eafa6f887ad5a10f9ddfb431 --- /dev/null +++ b/wxFastBle/src/main/ets/exception/ConnectException.ets @@ -0,0 +1,38 @@ +import BleException from './BleException' + +export default class ConnectException extends BleException { + + private bluetoothGatt: /*bluetooth.GattClientDevice*/any; + private gattStatus: number; + + public constructor(bluetoothGatt: /*bluetooth.GattClientDevice*/any, gattStatus: number) { + super(BleException.ERROR_CODE_GATT, "Gatt Exception Occurred! "); + this.bluetoothGatt = bluetoothGatt; + this.gattStatus = gattStatus; + } + + public getGattStatus(): number { + return this.gattStatus; + } + + public setGattStatus(gattStatus: number): ConnectException { + this.gattStatus = gattStatus; + return this; + } + + public getBluetoothGatt(): /*bluetooth.GattClientDevice*/any { + return this.bluetoothGatt; + } + + public setBluetoothGatt(bluetoothGatt: /*bluetooth.GattClientDevice*/any): ConnectException { + this.bluetoothGatt = bluetoothGatt; + return this; + } + + public toString(): string { + return "ConnectException{" + + "gattStatus=" + this.gattStatus + + ", bluetoothGatt=" + this.bluetoothGatt + + "} " + super.toString(); + } +} diff --git a/wxFastBle/src/main/ets/exception/GattException.ets b/wxFastBle/src/main/ets/exception/GattException.ets new file mode 100644 index 0000000000000000000000000000000000000000..33821d78573754a0157a0d4c41e94da0091aa3df --- /dev/null +++ b/wxFastBle/src/main/ets/exception/GattException.ets @@ -0,0 +1,26 @@ +import BleException from './BleException' + +export default class GattException extends BleException { + + private gattStatus: number; + + public constructor(gattStatus: number) { + super(BleException.ERROR_CODE_GATT, "Gatt Exception Occurred! "); + this.gattStatus = gattStatus; + } + + public getGattStatus(): number { + return this.gattStatus; + } + + public setGattStatus(gattStatus: number): GattException { + this.gattStatus = gattStatus; + return this; + } + + public toString(): string { + return "GattException{" + + "gattStatus=" + this.gattStatus + + "} " + super.toString(); + } +} diff --git a/wxFastBle/src/main/ets/exception/OtherException.ets b/wxFastBle/src/main/ets/exception/OtherException.ets new file mode 100644 index 0000000000000000000000000000000000000000..665a4828886513e0f793a8fb9f1a7f51c38bb210 --- /dev/null +++ b/wxFastBle/src/main/ets/exception/OtherException.ets @@ -0,0 +1,9 @@ +import BleException from './BleException' + +export default class OtherException extends BleException { + + public constructor(description: string) { + super(BleException.ERROR_CODE_OTHER, description); + } + +} diff --git a/wxFastBle/src/main/ets/exception/TimeoutException.ets b/wxFastBle/src/main/ets/exception/TimeoutException.ets new file mode 100644 index 0000000000000000000000000000000000000000..924df154c93ab08ac2606149de0cac33eb5a9cf3 --- /dev/null +++ b/wxFastBle/src/main/ets/exception/TimeoutException.ets @@ -0,0 +1,9 @@ +import BleException from './BleException' + +export default class TimeoutException extends BleException { + + public constructor() { + super(BleException.ERROR_CODE_TIMEOUT, "Timeout Exception Occurred!"); + } + +} diff --git a/wxFastBle/src/main/ets/scan/BleScanPresenter.ets b/wxFastBle/src/main/ets/scan/BleScanPresenter.ets new file mode 100644 index 0000000000000000000000000000000000000000..978e300bfda20cd7fe8ca60ece4e92a54c202f33 --- /dev/null +++ b/wxFastBle/src/main/ets/scan/BleScanPresenter.ets @@ -0,0 +1,146 @@ +import BleScanner from '../scan/BleScanner' +import BleDevice from '../data/BleDevice' +import BleScanPresenterImp from '../callback/BleScanPresenterImp' +import BleLog from '../utils/BleLog' +import HexUtil from '../utils/HexUtil' +import TextUtils from '../utils/TextUtils' +import ArrayUitls from '../utils/ArrayUtils' + +abstract class BleScanPresenter { + private mDeviceNames: string[]; + private mDeviceMac: string; + private mFuzzy: boolean; + private mNeedConnect: boolean; + private mScanTimeout: number; + private mBleScanPresenterImp: BleScanPresenterImp; + private mBleDeviceList: BleDevice[] = []; + + public prepare(names: string[], mac: string, fuzzy: boolean, needConnect: boolean, + timeOut: number, bleScanPresenterImp: BleScanPresenterImp): void { + this.mDeviceNames = names; + this.mDeviceMac = mac; + this.mFuzzy = fuzzy; + this.mNeedConnect = needConnect; + this.mScanTimeout = timeOut; + this.mBleScanPresenterImp = bleScanPresenterImp; + } + + public isNeedConnect(): boolean { + return this.mNeedConnect; + } + + public getBleScanPresenterImp(): BleScanPresenterImp { + return this.mBleScanPresenterImp; + } + + private handleResult(bleDevice: BleDevice): void { +// setTimeout(() => { + this.onLeScan(bleDevice); +// }) + this.checkDevice(bleDevice); + } + + public handleScanResults(data: Array< /*bluetooth.ScanResult*/ + any>): void { + for (let i = 0; i < data.length; i++) { + let item = data[i]; + let device: BleDevice = new BleDevice(item.deviceId, item.deviceName, item.rssi, item.data, new Date().getTime()); + this.handleResult(device); + } + } + + private checkDevice(bleDevice: BleDevice): void { + if (TextUtils.isEmpty(this.mDeviceMac) && ArrayUitls.isEmpty(this.mDeviceNames)) { + this.correctDeviceAndNextStep(bleDevice); + return; + } + + if (!TextUtils.isEmpty(this.mDeviceMac)) { + if (this.mDeviceMac.toLowerCase() != bleDevice.getMac().toLowerCase()) + return; + } + + if (!ArrayUitls.isEmpty(this.mDeviceNames)) { + let found = false; + for (let name in this.mDeviceNames) { + let remoteName: string = bleDevice.getName(); + if (remoteName == null || remoteName == undefined) + remoteName = ""; + if (this.mFuzzy ? (remoteName.indexOf(name) >= 0) : (remoteName == name)) { + found = true; + } + } + if (!found) { + return; + } + } + + this.correctDeviceAndNextStep(bleDevice); + } + + private correctDeviceAndNextStep(bleDevice: BleDevice): void { + if (this.mNeedConnect) { + BleLog.i("devices detected ------" + + " name:" + bleDevice.getName() + + " mac:" + bleDevice.getMac() + + " Rssi:" + bleDevice.getRssi() + + " scanRecord:" + HexUtil.formatHexString(bleDevice.getScanRecord())); + + this.mBleDeviceList.push(bleDevice); +// setTimeout(() => { + BleScanner.getInstance().stopLeScan(); +// }); + + } else { + let hasFound: boolean = false; + for (let i = 0; i < this.mBleDeviceList.length; i++) { + if (this.mBleDeviceList[i].getMac() == bleDevice.getMac()) { + hasFound = true; + break; + } + } + if (!hasFound) { + BleLog.i("device detected ------" + + " name: " + bleDevice.getName() + + " mac: " + bleDevice.getMac() + + " Rssi: " + bleDevice.getRssi() + + " scanRecord: " + HexUtil.formatHexString(bleDevice.getScanRecord(), true)); + + this.mBleDeviceList.push(bleDevice); +// setTimeout(() => { + this.onScanning(bleDevice); +// }); + } + } + } + + public notifyScanStarted(success: boolean): void { + this.mBleDeviceList = []; + + if (success && this.mScanTimeout > 0) { + setTimeout(() => { + BleScanner.getInstance().stopLeScan(); + }, this.mScanTimeout); + } + +// setTimeout(() => { + this.onScanStarted(success); +// }); + } + + public notifyScanStopped(): void { +// setTimeout(() => { + this.onScanFinished(this.mBleDeviceList); +// }); + } + + public abstract onScanStarted(success: boolean): void; + + public abstract onLeScan(bleDevice: BleDevice): void; + + public abstract onScanning(bleDevice: BleDevice): void; + + public abstract onScanFinished(bleDeviceList: BleDevice[]): void; +} + +export default BleScanPresenter; \ No newline at end of file diff --git a/wxFastBle/src/main/ets/scan/BleScanRuleConfig.ets b/wxFastBle/src/main/ets/scan/BleScanRuleConfig.ets new file mode 100644 index 0000000000000000000000000000000000000000..2a71afc13990bbe39f1b7d8d9b3af43767637a9f --- /dev/null +++ b/wxFastBle/src/main/ets/scan/BleScanRuleConfig.ets @@ -0,0 +1,88 @@ +import BleManager from "../BleManager" +type UUID = string; + +export default class BleScanRuleConfig { + + private mServiceUuids: UUID[] = null; + private mDeviceNames: string[] = null; + private mDeviceMac: string = null; + private mAutoConnect: boolean = false; + private mFuzzy: boolean = false; + private mScanTimeOut: number = BleManager.DEFAULT_SCAN_TIME; + + public getServiceUuids(): UUID[] { + return this.mServiceUuids; + } + + public getDeviceNames(): string[] { + return this.mDeviceNames; + } + + public getDeviceMac(): string { + return this.mDeviceMac; + } + + public isAutoConnect(): boolean { + return this.mAutoConnect; + } + + public isFuzzy(): boolean { + return this.mFuzzy; + } + + public getScanTimeOut(): number { + return this.mScanTimeOut; + } + + public static Builder = class { + mServiceUuids: UUID[] = null; + mDeviceNames: string[] = null; + mDeviceMac: string = null; + mAutoConnect: boolean = false; + mFuzzy: boolean = false; + mTimeOut: number = BleManager.DEFAULT_SCAN_TIME; + + public constructor(){} + + public setServiceUuids(uuids: UUID[]) { + this.mServiceUuids = uuids; + return this; + } + + public setDeviceName(fuzzy: boolean, name: string[]) { + this.mFuzzy = fuzzy; + this.mDeviceNames = name; + return this; + } + + public setDeviceMac(mac: string) { + this.mDeviceMac = mac; + return this; + } + + public setAutoConnect(autoConnect: boolean) { + this.mAutoConnect = autoConnect; + return this; + } + + public setScanTimeOut(timeOut: number) { + this.mTimeOut = timeOut; + return this; + } + + applyConfig(config: BleScanRuleConfig) { + config.mServiceUuids = this.mServiceUuids; + config.mDeviceNames = this.mDeviceNames; + config.mDeviceMac = this.mDeviceMac; + config.mAutoConnect = this.mAutoConnect; + config.mFuzzy = this.mFuzzy; + config.mScanTimeOut = this.mTimeOut; + } + + public build(): BleScanRuleConfig { + let config = new BleScanRuleConfig(); + this.applyConfig(config); + return config; + } + } +} diff --git a/wxFastBle/src/main/ets/scan/BleScanner.ets b/wxFastBle/src/main/ets/scan/BleScanner.ets new file mode 100644 index 0000000000000000000000000000000000000000..9e49532dddb6d3889031a9ffda786a310837b470 --- /dev/null +++ b/wxFastBle/src/main/ets/scan/BleScanner.ets @@ -0,0 +1,156 @@ +import bluetooth from '@ohos.bluetooth'; +import BleManager from '../BleManager' +import BleScanState from '../data/BleScanState'; +import BleScanPresenterImp from '../callback/BleScanPresenterImp' +import BleScanCallback from '../callback/BleScanCallback' +import BleScanAndConnectCallback from '../callback/BleScanAndConnectCallback' +import BleScanPresenter from './BleScanPresenter' +import BleDevice from '../data/BleDevice' +import BleLog from '../utils/BleLog' + +type UUID = string; + +export default class BleScanner { + private static instance: BleScanner; + + private constructor() { + } + + public static getInstance(): BleScanner { + if (!BleScanner.instance) { + BleScanner.instance = new BleScanner(); + } + return BleScanner.instance; + } + + private mBleScanState: BleScanState = BleScanState.STATE_IDLE; + private readonly mBleScanPresenter: BleScanPresenter = new class extends BleScanPresenter { + public onScanStarted(success: boolean): void { + let callback: BleScanPresenterImp = this.getBleScanPresenterImp(); + if (callback != null) { + callback.onScanStarted(success); + } + } + + public onLeScan(bleDevice: BleDevice) { + if (this.isNeedConnect()) { + let callback: BleScanAndConnectCallback = this.getBleScanPresenterImp(); + if (callback != null) { + callback.onLeScan(bleDevice); + } + } else { + let callback: BleScanCallback = this.getBleScanPresenterImp(); + if (callback != null) { + callback.onLeScan(bleDevice); + } + } + } + + public onScanning(result: BleDevice): void { + let callback: BleScanPresenterImp = this.getBleScanPresenterImp(); + if (callback != null) { + callback.onScanning(result); + } + } + + public onScanFinished(bleDeviceList: BleDevice[]): void { + if (this.isNeedConnect()) { + let callback: BleScanAndConnectCallback = this.getBleScanPresenterImp(); + if (bleDeviceList == null || bleDeviceList == undefined || bleDeviceList.length < 1) { + if (callback != null) { + callback.onScanFinished(null); + } + } else { + if (callback != null) { + callback.onScanFinished(bleDeviceList[0]); + } + let list: BleDevice[] = bleDeviceList; + setTimeout(() => { + BleManager.getInstance().connect(list[0], callback); + }, 100) + } + } else { + let callback: BleScanCallback = this.getBleScanPresenterImp(); + if (callback != null) { + callback.onScanFinished(bleDeviceList); + } + } + } + }; + + public scan(serviceUuids: UUID[], names: string[], mac: string, fuzzy: boolean, + timeOut: number, callback: BleScanCallback): void { + this.startLeScan(serviceUuids, names, mac, fuzzy, false, timeOut, callback); + } + + public scanAndConnect(serviceUuids: UUID[], names: string[], mac: string, fuzzy: boolean, + timeOut: number, callback: BleScanAndConnectCallback): void { + this.startLeScan(serviceUuids, names, mac, fuzzy, true, timeOut, callback); + } + + public mockStartLeScan(serviceUuids: UUID[], names: string[], mac: string, fuzzy: boolean, needConnect: boolean, + timeOut: number, imp: BleScanPresenterImp) { + console.log("mockStartLeScan") + } + + public startLeScan(serviceUuids: UUID[], names: string[], mac: string, fuzzy: boolean, + needConnect: boolean, timeOut: number, imp: BleScanPresenterImp): void { + console.log("startLeScan") + + if (this.mBleScanState != BleScanState.STATE_IDLE) { + BleLog.w("scan action already exists, complete the previous scan action first"); + if (imp != null) { + imp.onScanStarted(false); + } + return; + } + + this.mBleScanPresenter.prepare(names, mac, fuzzy, needConnect, timeOut, imp); + let callback = (data) => { + this.mBleScanPresenter.handleScanResults(data); + }; + console.log("before on") + if (BleManager.MOCK_DEVICE) { + BleManager.mockBluetooth.onScan(callback); + } else { + bluetooth.BLE.on('BLEDeviceFind', callback); + } + console.log("after on") + + let filterArry = new Array(); + for (let uuid in serviceUuids) { + let filter = { + 'serviceUuid': uuid + }; + filterArry.push(filter); + } + var scanOption = { + "dutyMode": 0, + "interval": 0, + "matchMode": 1 + }; + if (BleManager.MOCK_DEVICE) { + BleManager.mockBluetooth.startBLEScan(filterArry, scanOption); + } else { + bluetooth.BLE.startBLEScan(filterArry, scanOption); + } + + this.mBleScanState = BleScanState.STATE_SCANNING; + this.mBleScanPresenter.notifyScanStarted(true); + } + + public stopLeScan(): void { + if (BleManager.MOCK_DEVICE) { + BleManager.mockBluetooth.stopBLEScan(); + } else { + bluetooth.BLE.stopBLEScan(); + } + this.mBleScanState = BleScanState.STATE_IDLE; + this.mBleScanPresenter.notifyScanStopped(); + } + + public getScanState(): BleScanState { + return this.mBleScanState; + } +} + diff --git a/wxFastBle/src/main/ets/utils/ArrayUtils.ets b/wxFastBle/src/main/ets/utils/ArrayUtils.ets new file mode 100644 index 0000000000000000000000000000000000000000..d02df71374e79bfaa00e84df0a76dbd725e8f017 --- /dev/null +++ b/wxFastBle/src/main/ets/utils/ArrayUtils.ets @@ -0,0 +1,28 @@ +export default class ArrayUtils{ + public static isEmpty(array: Array): boolean { + return array == null || array == undefined || array.length==0; + } + + public static getAllKeys(map: Map) : Array{ + let array = new Array(); + for(let item of map.keys()) { + array.push(item) + } + return array; + } + public static getAllValues(map: Map) : Array{ + let array = new Array(); + for(let item of map.values()) { + array.push(item) + } + return array; + } + + public static getAll(iterator: IterableIterator) : Array{ + let array = new Array(); + for(let item of iterator) { + array.push(item) + } + return array; + } +} \ No newline at end of file diff --git a/wxFastBle/src/main/ets/utils/BleLog.ets b/wxFastBle/src/main/ets/utils/BleLog.ets new file mode 100644 index 0000000000000000000000000000000000000000..f892e2c9d2a584fbab861e361f879b1b43f23e63 --- /dev/null +++ b/wxFastBle/src/main/ets/utils/BleLog.ets @@ -0,0 +1,27 @@ +export default class BleLog { + private static readonly DOMAIN: number = 0xFF00; + + public static isPrint: boolean = true; + private static readonly defaultTag: string = "FastBle"; + + public static d(msg: string): void { + if (BleLog.isPrint && msg != null) + console.log(msg); + } + + public static i(msg: string): void { + if (BleLog.isPrint && msg != null) + console.log(msg); + } + + public static w(msg: string): void { + if (BleLog.isPrint && msg != null) + console.log(msg); + } + + public static e(msg: string): void { + if (BleLog.isPrint && msg != null) + console.log(msg); + } + +} diff --git a/wxFastBle/src/main/ets/utils/BleLruHashMap.ets b/wxFastBle/src/main/ets/utils/BleLruHashMap.ets new file mode 100644 index 0000000000000000000000000000000000000000..2908a1937a9a24d3b2805be378c2f7b46dcf5cd7 --- /dev/null +++ b/wxFastBle/src/main/ets/utils/BleLruHashMap.ets @@ -0,0 +1,18 @@ +export default class BleLruHashMap extends Map { + + private cacheSize: number = 1000; + + public constructor(cacheSize: number) { + super(); + this.cacheSize = cacheSize; + } + + public set(key: K, value: V): this{ + this.delete(key); + super.set(key, value); + if(super.size > this.cacheSize) { + this.delete(this.keys().next().value); + } + return this; + } +} diff --git a/wxFastBle/src/main/ets/utils/BluetoothConsts.ets b/wxFastBle/src/main/ets/utils/BluetoothConsts.ets new file mode 100644 index 0000000000000000000000000000000000000000..72d0b49fa6df599d2653cac78f6dd73ca40b3954 --- /dev/null +++ b/wxFastBle/src/main/ets/utils/BluetoothConsts.ets @@ -0,0 +1,138 @@ +export class BluetoothGattCharacteristic { + /** + * Characteristic proprty: Characteristic is broadcastable. + */ + public static readonly PROPERTY_BROADCAST = 0x01; + + /** + * Characteristic property: Characteristic is readable. + */ + public static readonly PROPERTY_READ = 0x02; + + /** + * Characteristic property: Characteristic can be written without response. + */ + public static readonly PROPERTY_WRITE_NO_RESPONSE = 0x04; + + /** + * Characteristic property: Characteristic can be written. + */ + public static readonly PROPERTY_WRITE = 0x08; + + /** + * Characteristic property: Characteristic supports notification + */ + public static readonly PROPERTY_NOTIFY = 0x10; + + /** + * Characteristic property: Characteristic supports indication + */ + public static readonly PROPERTY_INDICATE = 0x20; + + /** + * Characteristic property: Characteristic supports write with signature + */ + public static readonly PROPERTY_SIGNED_WRITE = 0x40; + + /** + * Characteristic property: Characteristic has extended properties + */ + public static readonly PROPERTY_EXTENDED_PROPS = 0x80; + + /** + * Characteristic read permission + */ + public static readonly PERMISSION_READ = 0x01; + + /** + * Characteristic permission: Allow encrypted read operations + */ + public static readonly PERMISSION_READ_ENCRYPTED = 0x02; + + /** + * Characteristic permission: Allow reading with man-in-the-middle protection + */ + public static readonly PERMISSION_READ_ENCRYPTED_MITM = 0x04; + + /** + * Characteristic write permission + */ + public static readonly PERMISSION_WRITE = 0x10; + + /** + * Characteristic permission: Allow encrypted writes + */ + public static readonly PERMISSION_WRITE_ENCRYPTED = 0x20; + + /** + * Characteristic permission: Allow encrypted writes with man-in-the-middle + * protection + */ + public static readonly PERMISSION_WRITE_ENCRYPTED_MITM = 0x40; + + /** + * Characteristic permission: Allow signed write operations + */ + public static readonly PERMISSION_WRITE_SIGNED = 0x80; + + /** + * Characteristic permission: Allow signed write operations with + * man-in-the-middle protection + */ + public static readonly PERMISSION_WRITE_SIGNED_MITM = 0x100; + + /** + * Write characteristic, requesting acknoledgement by the remote device + */ + public static readonly WRITE_TYPE_DEFAULT = 0x02; + + /** + * Write characteristic without requiring a response by the remote device + */ + public static readonly WRITE_TYPE_NO_RESPONSE = 0x01; + + /** + * Write characteristic including authentication signature + */ + public static readonly WRITE_TYPE_SIGNED = 0x04; + + /** + * Characteristic value format type uint8 + */ + public static readonly FORMAT_UINT8 = 0x11; + + /** + * Characteristic value format type uint16 + */ + public static readonly FORMAT_UINT16 = 0x12; + + /** + * Characteristic value format type uint32 + */ + public static readonly FORMAT_UINT32 = 0x14; + + /** + * Characteristic value format type sint8 + */ + public static readonly FORMAT_SINT8 = 0x21; + + /** + * Characteristic value format type sint16 + */ + public static readonly FORMAT_SINT16 = 0x22; + + /** + * Characteristic value format type sint32 + */ + public static readonly FORMAT_SINT32 = 0x24; + + /** + * Characteristic value format type sfloat (16-bit float) + */ + public static readonly FORMAT_SFLOAT = 0x32; + + /** + * Characteristic value format type float (32-bit float) + */ + public static readonly FORMAT_FLOAT = 0x34; +} \ No newline at end of file diff --git a/wxFastBle/src/main/ets/utils/Handler.ets b/wxFastBle/src/main/ets/utils/Handler.ets new file mode 100644 index 0000000000000000000000000000000000000000..af40f06d5ec46421118e23cb962806f3d7c3cf06 --- /dev/null +++ b/wxFastBle/src/main/ets/utils/Handler.ets @@ -0,0 +1,64 @@ +export class Message { + what: number; + obj: any; + data = new Map(); + + constructor(what?: number, obj?: any, data?: any) { + this.what = what; + this.obj = obj; + this.data = data; + } + + public setData(data: Map) { + this.data = data; + } + + public getData(): Map { + return this.data; + } +} + +export abstract class Handler { + public abstract handleMessage(msg: Message): void; + + private messageTimoutMap = new Map(); // + + public sendMessage(msg: Message) { + this.sendMessageDelayed(msg, 0); + } + + public sendMessageDelayed(msg: Message, delay: number) { + let _this = this; + let handle = setTimeout(function() { + _this.messageTimoutMap.delete(msg.what); + _this.handleMessage(msg); + }, delay); + this.messageTimoutMap.set(msg.what, handle); + } + + public removeMessages(what: number): void { + if (this.messageTimoutMap.has(what)) { + clearTimeout(this.messageTimoutMap.get(what)); + this.messageTimoutMap.delete(what); + } + } + + public removeAllMessage(): void { + for (let [key, value] of this.messageTimoutMap.entries()) { + this.messageTimoutMap.delete(key); + clearTimeout(value); + } + } + + public obtainMessage(what?: number, obj?: any): Message { + if (what != undefined) { + if (obj != undefined) { + return new Message(what, obj); + } else { + return new Message(what); + } + } else { + return new Message(); + } + } +} \ No newline at end of file diff --git a/wxFastBle/src/main/ets/utils/HexUtil.ets b/wxFastBle/src/main/ets/utils/HexUtil.ets new file mode 100644 index 0000000000000000000000000000000000000000..ad960609fc5bb30e59677a1eb08688b5a3a0fe0b --- /dev/null +++ b/wxFastBle/src/main/ets/utils/HexUtil.ets @@ -0,0 +1,120 @@ +import TextUtils from './TextUtils' + +type char = string; +type byte = number; + +export default class HexUtil { + + private static readonly DIGITS_LOWER: char[] = ['0', '1', '2', '3', '4', '5', + '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f']; + + private static readonly DIGITS_UPPER: char[] = ['0', '1', '2', '3', '4', '5', + '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F']; + + public static encodeHex(data: byte[], toLowerCase: boolean = true): char[] { + return HexUtil.encodeHexInner(data, toLowerCase ? HexUtil.DIGITS_LOWER : HexUtil.DIGITS_UPPER); + } + + protected static encodeHexInner(data: byte[], toDigits: char[]): char[] { + if (!data) + return null; + let l: number = data.length; + let out: char[] = new Array(l << 1); + for (let i = 0, j = 0; i < l; i++) { + out[j++] = toDigits[(0xF0 & data[i]) >>> 4]; + out[j++] = toDigits[0x0F & data[i]]; + } + return out; + } + + private static byteToString(data: char[]): string { + let str = ''; + for (let i = 0; i < data.length; i++) { + str += data[i]; + } + return str; + } + + + public static encodeHexStr(data: byte[], toLowerCase: boolean = true): string{ + return HexUtil.encodeHexStrInner(data, toLowerCase ? HexUtil.DIGITS_LOWER : HexUtil.DIGITS_UPPER); + } + + + protected static encodeHexStrInner(data: byte[], toDigits: char[]): string { + return HexUtil.byteToString(HexUtil.encodeHexInner(data, toDigits)); + } + + public static formatHexString(data: Uint8Array, addSpace: boolean = false): string { + if (!data || data.length < 1) + return null; + let sb: string = ''; + for (let i: number = 0; i < data.length; i++) { + let hex: String = (data[i] & 0xFF).toString(16); + if (hex.length == 1) { + hex = '0' + hex; + } + sb = sb + hex; + if (addSpace) + sb = sb + " "; + } + return sb; + } + + public static decodeHex(data: char[]): byte[] { + let len: number = data.length; + + if ((len & 0x01) != 0) { + throw new Error("Odd number of characters."); + } + + let out: byte[] = new Array(len >> 1); + + // two characters form the hex value. + for (let i: number = 0, j = 0; j < len; i++) { + let f: number = HexUtil.toDigit(data[j], j) << 4; + j++; + f = f | HexUtil.toDigit(data[j], j); + j++; + out[i] = (f & 0xFF); + } + + return out; + } + + + protected static toDigit(ch: char, index: number): number { + let digit: number = HexUtil.charToByte(ch.toUpperCase()); //Character.digit(ch, 16); + if (digit == -1) { + throw new Error("Illegal hexadecimal character " + ch + + " at index " + index); + } + return digit; + } + + + public static hexStringToBytes(hexString: string): Uint8Array { + if (TextUtils.isEmpty(hexString)) { + return null; + } + hexString = hexString.trim(); + hexString = hexString.toUpperCase(); + let length: number = hexString.length / 2; + let hexChars: char[] = TextUtils.toCharArray(hexString); + let d: byte[] = new Array(length); + for (let i = 0; i < length; i++) { + let pos = i * 2; + d[i] = (HexUtil.charToByte(hexChars[pos]) << 4 | HexUtil.charToByte(hexChars[pos + 1])); + } + return new Uint8Array(d); + } + + public static charToByte(c: char): byte { + return "0123456789ABCDEF".indexOf(c); + } + + public static extractData(data: Uint8Array, position: number): String { + return HexUtil.formatHexString(new Uint8Array([data[position]])); + } + +} diff --git a/wxFastBle/src/main/ets/utils/Queue.ets b/wxFastBle/src/main/ets/utils/Queue.ets new file mode 100644 index 0000000000000000000000000000000000000000..1c9c3cda9c76e4e9a059aa7719563fb260db7d3b --- /dev/null +++ b/wxFastBle/src/main/ets/utils/Queue.ets @@ -0,0 +1,76 @@ +export class Queue { + private items: Array + private capacity: number | undefined; + + public constructor(capacity?: number) { + this.items = new Array(); + this.capacity = capacity; + } + + public add(item: T): boolean { + if (item == null) { + return false; + } + + if (this.capacity != undefined && this.capacity == this.items.length) { + this.items.pop(); + } + + this.items.unshift(item); + return true; + } + + public offer(item: T): boolean { + if (item == null) { + return false; + } + + if (this.capacity != undefined && this.capacity == this.items.length) { + return false; + } + + this.items.unshift(item); + return true; + } + + public remove(): T{ + if (this.items.length == 0) { + return this.items.pop(); + } + throw EvalError('NoSuchElement'); + } + + public poll(): T{ + if (this.items.length > 0) { + return this.items.pop(); + } + return null; + } + + public element(): T{ + if (this.items.length > 0) { + return this.items[this.items.length-1]; + } + throw EvalError('NoSuchElement'); + } + + public peek(): T{ + if (this.items.length > 0) { + return this.items[this.items.length-1]; + } + return null; + } + + public size(): number { + return this.items.length; + } + + public empty(): boolean{ + return this.size() == 0; + } + + public clear() { + delete this.items; + this.items = new Array(); + } +} diff --git a/wxFastBle/src/main/ets/utils/TextUtils.ets b/wxFastBle/src/main/ets/utils/TextUtils.ets new file mode 100644 index 0000000000000000000000000000000000000000..f6cc16dd52db7f36908ca038cf905ba91df71d33 --- /dev/null +++ b/wxFastBle/src/main/ets/utils/TextUtils.ets @@ -0,0 +1,35 @@ +export default class TextUtils{ + public static isEmpty(text: string): boolean { + return text == null || text == undefined || text.length==0; + } + + public static toCharArray(text: string): Array { + let arr: string[] = new Array(text.length); + for(let i=0; i