commit 3b2571d150ae300022a6352060e3a4a10888c59a Author: yuntang <123@qq.com> Date: Tue Jun 23 11:27:35 2026 +0800 2026-06-23T11:27:35 diff --git a/push.sh b/push.sh new file mode 100644 index 0000000..b654beb --- /dev/null +++ b/push.sh @@ -0,0 +1,22 @@ +#!/bin/bash +datetime=`date +%FT%T` + +echo "-------------------------------------" +echo "git add -A" +echo "-------------------------------------" +git add -A + +echo "-------------------------------------" +echo "git status" +echo "-------------------------------------" +git status + +echo "-------------------------------------" +echo "git commit -m ${datetime} " +echo "-------------------------------------" +git commit -m "${datetime}" + +echo "-------------------------------------" +echo "git push" +echo "-------------------------------------" +git push \ No newline at end of file diff --git a/src/.gitignore b/src/.gitignore new file mode 100644 index 0000000..2370852 --- /dev/null +++ b/src/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +unpackage/ +.hbuilderx/ +.DS_Store diff --git a/src/App.uvue b/src/App.uvue new file mode 100644 index 0000000..9101cf2 --- /dev/null +++ b/src/App.uvue @@ -0,0 +1,51 @@ + + + \ No newline at end of file diff --git a/src/android-logcat.log b/src/android-logcat.log new file mode 100644 index 0000000..08ade35 --- /dev/null +++ b/src/android-logcat.log @@ -0,0 +1,109 @@ +17:07:47.041 HBuilderX Version: 5.06 +17:07:47.042 项目 app_sp2 开始编译 +17:07:47.044 请注意运行模式下,因日志输出、sourcemap 以及未压缩源码等原因,性能和包体积,均不及发行模式。 +17:07:47.045 编译器版本:5.06(uni-app x) +17:07:47.047 正在编译中... +17:07:47.048 编译会生成大量临时文件,杀毒软件监控时会影响编译速度,并造成CPU升高。推荐把项目目录添加到杀毒软件的监控排除名单中。[添加] [帮助] +17:07:47.049 uts插件[sp2-bluetooth]文件未发生变化,跳过编译 +17:07:47.050 ​提示:uts插件[sp2-bluetooth]依赖的原生配置或三方SDK在运行至标准基座时不能生效,如需正常调用请使用自定义基座​ +17:07:47.051 检测到编译缓存部分失效,开始差量编译。详见:https://uniapp.dcloud.net.cn/uni-app-x/compiler/#cache +17:07:47.052 当前工程2个页面,正在编译为android class,此过程耗时较长. 17:07:47.054 当前工程2个页面,正在编译为android class,此过程耗时较长.. 17:07:47.055 当前工程2个页面,正在编译为android class,此过程耗时较长... 17:07:47.056 当前工程2个页面,正在编译为android class,此过程耗时较长. 17:07:47.057 当前工程2个页面,正在编译为android class,此过程耗时较长.. 17:07:47.058 当前工程2个页面,正在编译为android class,此过程耗时较长... 17:07:47.059 当前工程2个页面,正在编译为android class,此过程耗时较长. 17:07:47.060 当前工程2个页面,正在编译为android class,此过程耗时较长.. 17:07:47.060 当前工程2个页面,正在编译为android class,此过程耗时较长... 17:07:47.063 当前工程2个页面,正在编译为android class,此过程耗时较长. 17:07:47.065 当前工程2个页面,正在编译为android class,此过程耗时较长... +17:07:47.066 项目 app_sp2 UTS编译完毕。 +17:07:47.066 ready in 11771ms. +17:07:47.068 手机端调试基座版本号为5.06.14660,与本地版本相同,跳过更新 +17:07:47.069 正在建立手机连接... +17:07:47.069 正在同步手机端程序文件... +17:07:47.071 同步手机端程序文件成功 +17:07:47.072 正在启动uni-app x调试基座... +17:07:47.073 App Launch at App.uvue:7 +17:07:47.074 App Show at App.uvue:11 +17:07:47.075 应用启动到触发onLaunch耗时: 2278ms +17:07:47.075 进入页面:​/pages/index/index​ 。[{"创建dom元素个数":"15个","耗时":"13ms"},{"排版":"1次","耗时":"11ms"},{"渲染":"1次","耗时":"18ms"},{"跳转页面到onReady总耗时":"130ms"}] +17:07:47.078 应用【app_sp2】已启动 +17:07:47.079 App Hide at App.uvue:15 +17:07:47.080 App Show at App.uvue:11 +17:07:47.083 App Hide at App.uvue:15 +17:07:47.084 App Show at App.uvue:11 +17:07:47.085 App Hide at App.uvue:15 +17:07:47.086 App Launch at App.uvue:7 +17:07:47.086 App Show at App.uvue:11 +17:07:47.088 应用启动到触发onLaunch耗时: 245ms +17:07:47.089 进入页面:​/pages/index/index​ 。[{"创建dom元素个数":"15个","耗时":"15ms"},{"排版":"1次","耗时":"11ms"},{"渲染":"1次","耗时":"20ms"},{"跳转页面到onReady总耗时":"140ms"}] +17:07:47.089 开始差量编译... +17:07:47.091 正在同步手机端程序文件... +17:07:47.093 App Launch at App.uvue:7 +17:07:47.094 App Show at App.uvue:11 +17:07:47.096 应用启动到触发onLaunch耗时: 237ms +17:07:47.097 进入页面:​/pages/index/index​ 。[{"创建dom元素个数":"15个","耗时":"14ms"},{"排版":"1次","耗时":"11ms"},{"渲染":"1次","耗时":"18ms"},{"跳转页面到onReady总耗时":"110ms"}] +17:07:47.098 项目 app_sp2 UTS编译完毕。 +17:07:47.099 正在同步手机端程序文件... +17:07:47.100 App Launch at App.uvue:7 +17:07:47.100 App Show at App.uvue:11 +17:07:47.102 应用启动到触发onLaunch耗时: 221ms +17:07:47.103 进入页面:​/pages/index/index​ 。[{"创建dom元素个数":"15个","耗时":"14ms"},{"排版":"1次","耗时":"11ms"},{"渲染":"1次","耗时":"17ms"},{"跳转页面到onReady总耗时":"117ms"}] +17:07:47.105 App Hide at App.uvue:15 +17:07:47.105 App Launch at App.uvue:7 +17:07:47.106 App Show at App.uvue:11 +17:07:47.108 应用启动到触发onLaunch耗时: 304ms +17:07:47.109 进入页面:​/pages/index/index​ 。[{"创建dom元素个数":"15个","耗时":"14ms"},{"排版":"1次","耗时":"10ms"},{"渲染":"1次","耗时":"17ms"},{"跳转页面到onReady总耗时":"122ms"}] +17:07:47.110 开始差量编译... +17:07:47.112 正在同步手机端程序文件... +17:07:47.113 App Launch at App.uvue:7 +17:07:47.114 App Show at App.uvue:11 +17:07:47.115 应用启动到触发onLaunch耗时: 213ms +17:07:47.116 进入页面:​/pages/index/index​ 。[{"创建dom元素个数":"15个","耗时":"14ms"},{"排版":"1次","耗时":"10ms"},{"渲染":"1次","耗时":"17ms"},{"跳转页面到onReady总耗时":"120ms"}] +17:07:47.117 ​​warning: 'var value: ByteArray!' is deprecated. Deprecated in Java.​ +17:07:47.118 at uni_modules/sp2-bluetooth/utssdk/app-android/hybrid.kt:268:43 +17:07:47.119 266| +17:07:47.121 267| override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) { +17:07:47.121 268| val data = characteristic.value +17:07:47.123 | ^ +17:07:47.123 269| if (data != null && data.isNotEmpty()) { +17:07:47.126 270| val hexString = data.joinToString("") { "%02X".format(it) } +17:07:47.129 ​​warning: 'var value: ByteArray!' is deprecated. Deprecated in Java.​ +17:07:47.129 at uni_modules/sp2-bluetooth/utssdk/app-android/hybrid.kt:522:32 +17:07:47.130 520| BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE +17:07:47.132 521| } +17:07:47.133 522| descriptor.value = enableValue +17:07:47.134 | ^ +17:07:47.134 523| val writeResult = gatt.writeDescriptor(descriptor) +17:07:47.136 524| Log.i(TAG, "enableCharacteristicNotification: descriptor=${descriptor.uuid}, writeResult=$writeResult") +17:07:47.137 ​​warning: 'fun writeDescriptor(p0: BluetoothGattDescriptor!): Boolean' is deprecated. Deprecated in Java.​ +17:07:47.138 at uni_modules/sp2-bluetooth/utssdk/app-android/hybrid.kt:523:44 +17:07:47.139 521| } +17:07:47.141 522| descriptor.value = enableValue +17:07:47.142 523| val writeResult = gatt.writeDescriptor(descriptor) +17:07:47.143 | ^ +17:07:47.144 524| Log.i(TAG, "enableCharacteristicNotification: descriptor=${descriptor.uuid}, writeResult=$writeResult") +17:07:47.144 525| if (!writeResult) { +17:07:47.146 ​​warning: 'var value: ByteArray!' is deprecated. Deprecated in Java.​ +17:07:47.148 at uni_modules/sp2-bluetooth/utssdk/app-android/hybrid.kt:557:32 +17:07:47.148 555| selectedWriteServiceUuid = service.uuid.toString() +17:07:47.149 556| selectedWriteCharacteristicUuid = characteristic.uuid.toString() +17:07:47.150 557| characteristic.value = data.toByteArray(Charsets.UTF_8) +17:07:47.152 | ^ +17:07:47.152 558| val writeResult = gatt.writeCharacteristic(characteristic) +17:07:47.153 559| Log.i(TAG, "writeBluetoothData: service=${service.uuid}, characteristic=${characteristic.uuid}, payload=$data, writeResult=$writeResult") +17:07:47.156 ​​warning: 'fun writeCharacteristic(p0: BluetoothGattCharacteristic!): Boolean' is deprecated. Deprecated in Java.​ +17:07:47.158 at uni_modules/sp2-bluetooth/utssdk/app-android/hybrid.kt:558:40 +17:07:47.159 556| selectedWriteCharacteristicUuid = characteristic.uuid.toString() +17:07:47.161 557| characteristic.value = data.toByteArray(Charsets.UTF_8) +17:07:47.162 558| val writeResult = gatt.writeCharacteristic(characteristic) +17:07:47.162 | ^ +17:07:47.164 559| Log.i(TAG, "writeBluetoothData: service=${service.uuid}, characteristic=${characteristic.uuid}, payload=$data, writeResult=$writeResult") +17:07:47.165 560| } else { +17:07:47.166 ​提示:uts插件[sp2-bluetooth]依赖的原生配置或三方SDK在运行至标准基座时不能生效,如需正常调用请使用自定义基座​ +17:07:47.167 项目 app_sp2 UTS编译完毕。 +17:07:47.168 正在同步手机端程序文件... +17:07:47.169 App Launch at App.uvue:7 +17:07:47.169 App Show at App.uvue:11 +17:07:47.172 应用启动到触发onLaunch耗时: 211ms +17:07:47.173 进入页面:​/pages/index/index​ 。[{"创建dom元素个数":"15个","耗时":"14ms"},{"排版":"1次","耗时":"12ms"},{"渲染":"1次","耗时":"18ms"},{"跳转页面到onReady总耗时":"117ms"}] +17:07:47.174 进入页面:​/pages/data/data?deviceId=C6:91:6A:18:74:6A&deviceName=NB-C6916A18746A​ 。[{"创建dom元素个数":"26个","耗时":"17ms"},{"排版":"1次","耗时":"11ms"},{"渲染":"1次","耗时":"18ms"},{"跳转页面到onReady总耗时":"152ms"}] +17:07:47.175 App Hide at App.uvue:15 +17:07:47.175 App Launch at App.uvue:7 +17:07:47.177 App Show at App.uvue:11 +17:07:47.178 应用启动到触发onLaunch耗时: 217ms +17:07:47.179 进入页面:​/pages/index/index​ 。[{"创建dom元素个数":"15个","耗时":"14ms"},{"排版":"1次","耗时":"11ms"},{"渲染":"1次","耗时":"18ms"},{"跳转页面到onReady总耗时":"123ms"}] +17:07:47.179 进入页面:​/pages/data/data?deviceId=C6:91:6A:18:74:6A&deviceName=NB-C6916A18746A​ 。[{"创建dom元素个数":"26个","耗时":"17ms"},{"排版":"1次","耗时":"13ms"},{"渲染":"1次","耗时":"18ms"},{"跳转页面到onReady总耗时":"166ms"}] + diff --git a/src/index.html b/src/index.html new file mode 100644 index 0000000..8793fef --- /dev/null +++ b/src/index.html @@ -0,0 +1,20 @@ + + + + + + + + + + +
+ + + \ No newline at end of file diff --git a/src/launch-android.log b/src/launch-android.log new file mode 100644 index 0000000..cddd35a --- /dev/null +++ b/src/launch-android.log @@ -0,0 +1,119 @@ +16:54:58.262 HBuilderX Version: 5.06 +16:54:58.591 项目 app_sp2 开始编译 +16:54:59.968 请注意运行模式下,因日志输出、sourcemap 以及未压缩源码等原因,性能和包体积,均不及发行模式。 +16:54:59.969 编译器版本:5.06(uni-app x) +16:54:59.970 正在编译中... +16:54:59.972 编译会生成大量临时文件,杀毒软件监控时会影响编译速度,并造成CPU升高。推荐把项目目录添加到杀毒软件的监控排除名单中。[添加] [帮助] +16:55:04.443 uts插件[sp2-bluetooth]文件未发生变化,跳过编译 +16:55:04.445 ​提示:uts插件[sp2-bluetooth]依赖的原生配置或三方SDK在运行至标准基座时不能生效,如需正常调用请使用自定义基座​ +16:55:05.586 检测到编译缓存部分失效,开始差量编译。详见:https://uniapp.dcloud.net.cn/uni-app-x/compiler/#cache +16:55:06.134 当前工程2个页面,正在编译为android class,此过程耗时较长. 16:55:06.634 当前工程2个页面,正在编译为android class,此过程耗时较长.. 16:55:07.148 当前工程2个页面,正在编译为android class,此过程耗时较长... 16:55:07.649 当前工程2个页面,正在编译为android class,此过程耗时较长. 16:55:08.164 当前工程2个页面,正在编译为android class,此过程耗时较长.. 16:55:08.673 当前工程2个页面,正在编译为android class,此过程耗时较长... 16:55:09.186 当前工程2个页面,正在编译为android class,此过程耗时较长. 16:55:09.689 当前工程2个页面,正在编译为android class,此过程耗时较长.. 16:55:10.206 当前工程2个页面,正在编译为android class,此过程耗时较长... 16:55:10.720 当前工程2个页面,正在编译为android class,此过程耗时较长. 16:55:10.818 当前工程2个页面,正在编译为android class,此过程耗时较长... +16:55:10.825 项目 app_sp2 UTS编译完毕。 +16:55:10.840 ready in 11771ms. +16:55:11.510 手机端调试基座版本号为5.06.14660,与本地版本相同,跳过更新 +16:55:11.948 正在建立手机连接... +16:55:12.987 正在同步手机端程序文件... +16:55:13.144 同步手机端程序文件成功 +16:55:14.213 正在启动uni-app x调试基座... +16:55:14.413 App Launch at App.uvue:7 +16:55:14.416 App Show at App.uvue:11 +16:55:14.513 应用启动到触发onLaunch耗时: 2278ms +16:55:14.515 进入页面:​/pages/index/index​ 。[{"创建dom元素个数":"15个","耗时":"13ms"},{"排版":"1次","耗时":"11ms"},{"渲染":"1次","耗时":"18ms"},{"跳转页面到onReady总耗时":"130ms"}] +16:55:15.257 应用【app_sp2】已启动 +16:58:39.489 App Hide at App.uvue:15 +16:59:01.747 App Show at App.uvue:11 +16:59:08.193 App Hide at App.uvue:15 +16:59:47.677 App Show at App.uvue:11 +17:00:06.599 App Hide at App.uvue:15 +17:00:29.099 App Launch at App.uvue:7 +17:00:29.100 App Show at App.uvue:11 +17:00:29.126 应用启动到触发onLaunch耗时: 245ms +17:00:29.128 进入页面:​/pages/index/index​ 。[{"创建dom元素个数":"15个","耗时":"15ms"},{"排版":"1次","耗时":"11ms"},{"渲染":"1次","耗时":"20ms"},{"跳转页面到onReady总耗时":"140ms"}] +17:02:09.012 开始差量编译... +17:02:11.404 正在同步手机端程序文件... +17:02:12.134 App Launch at App.uvue:7 +17:02:12.137 App Show at App.uvue:11 +17:02:12.141 应用启动到触发onLaunch耗时: 237ms +17:02:12.154 进入页面:​/pages/index/index​ 。[{"创建dom元素个数":"15个","耗时":"14ms"},{"排版":"1次","耗时":"11ms"},{"渲染":"1次","耗时":"18ms"},{"跳转页面到onReady总耗时":"110ms"}] +17:02:13.077 项目 app_sp2 UTS编译完毕。 +17:02:13.084 正在同步手机端程序文件... +17:02:13.790 App Launch at App.uvue:7 +17:02:13.793 App Show at App.uvue:11 +17:02:13.807 应用启动到触发onLaunch耗时: 221ms +17:02:13.809 进入页面:​/pages/index/index​ 。[{"创建dom元素个数":"15个","耗时":"14ms"},{"排版":"1次","耗时":"11ms"},{"渲染":"1次","耗时":"17ms"},{"跳转页面到onReady总耗时":"117ms"}] +17:04:01.683 App Hide at App.uvue:15 +17:04:03.924 App Launch at App.uvue:7 +17:04:03.926 App Show at App.uvue:11 +17:04:03.955 应用启动到触发onLaunch耗时: 304ms +17:04:03.956 进入页面:​/pages/index/index​ 。[{"创建dom元素个数":"15个","耗时":"14ms"},{"排版":"1次","耗时":"10ms"},{"渲染":"1次","耗时":"17ms"},{"跳转页面到onReady总耗时":"122ms"}] +17:05:54.863 开始差量编译... +17:05:57.926 正在同步手机端程序文件... +17:05:58.667 App Launch at App.uvue:7 +17:05:58.670 App Show at App.uvue:11 +17:05:58.682 应用启动到触发onLaunch耗时: 213ms +17:05:58.684 进入页面:​/pages/index/index​ 。[{"创建dom元素个数":"15个","耗时":"14ms"},{"排版":"1次","耗时":"10ms"},{"渲染":"1次","耗时":"17ms"},{"跳转页面到onReady总耗时":"120ms"}] +17:06:01.307 ​​warning: 'var value: ByteArray!' is deprecated. Deprecated in Java.​ +17:06:01.312 at uni_modules/sp2-bluetooth/utssdk/app-android/hybrid.kt:268:43 +17:06:01.315 266| +17:06:01.317 267| override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) { +17:06:01.322 268| val data = characteristic.value +17:06:01.325 | ^ +17:06:01.327 269| if (data != null && data.isNotEmpty()) { +17:06:01.329 270| val hexString = data.joinToString("") { "%02X".format(it) } +17:06:01.332 ​​warning: 'var value: ByteArray!' is deprecated. Deprecated in Java.​ +17:06:01.335 at uni_modules/sp2-bluetooth/utssdk/app-android/hybrid.kt:522:32 +17:06:01.337 520| BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE +17:06:01.338 521| } +17:06:01.341 522| descriptor.value = enableValue +17:06:01.344 | ^ +17:06:01.345 523| val writeResult = gatt.writeDescriptor(descriptor) +17:06:01.347 524| Log.i(TAG, "enableCharacteristicNotification: descriptor=${descriptor.uuid}, writeResult=$writeResult") +17:06:01.350 ​​warning: 'fun writeDescriptor(p0: BluetoothGattDescriptor!): Boolean' is deprecated. Deprecated in Java.​ +17:06:01.352 at uni_modules/sp2-bluetooth/utssdk/app-android/hybrid.kt:523:44 +17:06:01.353 521| } +17:06:01.354 522| descriptor.value = enableValue +17:06:01.356 523| val writeResult = gatt.writeDescriptor(descriptor) +17:06:01.358 | ^ +17:06:01.360 524| Log.i(TAG, "enableCharacteristicNotification: descriptor=${descriptor.uuid}, writeResult=$writeResult") +17:06:01.360 525| if (!writeResult) { +17:06:01.363 ​​warning: 'var value: ByteArray!' is deprecated. Deprecated in Java.​ +17:06:01.366 at uni_modules/sp2-bluetooth/utssdk/app-android/hybrid.kt:557:32 +17:06:01.368 555| selectedWriteServiceUuid = service.uuid.toString() +17:06:01.370 556| selectedWriteCharacteristicUuid = characteristic.uuid.toString() +17:06:01.371 557| characteristic.value = data.toByteArray(Charsets.UTF_8) +17:06:01.373 | ^ +17:06:01.374 558| val writeResult = gatt.writeCharacteristic(characteristic) +17:06:01.376 559| Log.i(TAG, "writeBluetoothData: service=${service.uuid}, characteristic=${characteristic.uuid}, payload=$data, writeResult=$writeResult") +17:06:01.377 ​​warning: 'fun writeCharacteristic(p0: BluetoothGattCharacteristic!): Boolean' is deprecated. Deprecated in Java.​ +17:06:01.379 at uni_modules/sp2-bluetooth/utssdk/app-android/hybrid.kt:558:40 +17:06:01.381 556| selectedWriteCharacteristicUuid = characteristic.uuid.toString() +17:06:01.382 557| characteristic.value = data.toByteArray(Charsets.UTF_8) +17:06:01.384 558| val writeResult = gatt.writeCharacteristic(characteristic) +17:06:01.385 | ^ +17:06:01.388 559| Log.i(TAG, "writeBluetoothData: service=${service.uuid}, characteristic=${characteristic.uuid}, payload=$data, writeResult=$writeResult") +17:06:01.389 560| } else { +17:06:02.190 ​提示:uts插件[sp2-bluetooth]依赖的原生配置或三方SDK在运行至标准基座时不能生效,如需正常调用请使用自定义基座​ +17:06:03.302 项目 app_sp2 UTS编译完毕。 +17:06:03.309 正在同步手机端程序文件... +17:06:03.973 App Launch at App.uvue:7 +17:06:03.974 App Show at App.uvue:11 +17:06:03.986 应用启动到触发onLaunch耗时: 211ms +17:06:04.001 进入页面:​/pages/index/index​ 。[{"创建dom元素个数":"15个","耗时":"14ms"},{"排版":"1次","耗时":"12ms"},{"渲染":"1次","耗时":"18ms"},{"跳转页面到onReady总耗时":"117ms"}] +17:06:23.305 进入页面:​/pages/data/data?deviceId=C6:91:6A:18:74:6A&deviceName=NB-C6916A18746A​ 。[{"创建dom元素个数":"26个","耗时":"17ms"},{"排版":"1次","耗时":"11ms"},{"渲染":"1次","耗时":"18ms"},{"跳转页面到onReady总耗时":"152ms"}] +17:07:08.011 App Hide at App.uvue:15 +17:07:10.200 App Launch at App.uvue:7 +17:07:10.201 App Show at App.uvue:11 +17:07:10.225 应用启动到触发onLaunch耗时: 217ms +17:07:10.228 进入页面:​/pages/index/index​ 。[{"创建dom元素个数":"15个","耗时":"14ms"},{"排版":"1次","耗时":"11ms"},{"渲染":"1次","耗时":"18ms"},{"跳转页面到onReady总耗时":"123ms"}] +17:07:14.858 进入页面:​/pages/data/data?deviceId=C6:91:6A:18:74:6A&deviceName=NB-C6916A18746A​ 。[{"创建dom元素个数":"26个","耗时":"17ms"},{"排版":"1次","耗时":"13ms"},{"渲染":"1次","耗时":"18ms"},{"跳转页面到onReady总耗时":"166ms"}] +17:09:52.869 App Hide at App.uvue:15 +17:10:10.104 App Launch at App.uvue:7 +17:10:10.105 App Show at App.uvue:11 +17:10:10.122 应用启动到触发onLaunch耗时: 226ms +17:10:10.123 进入页面:​/pages/index/index​ 。[{"创建dom元素个数":"15个","耗时":"13ms"},{"排版":"1次","耗时":"11ms"},{"渲染":"1次","耗时":"18ms"},{"跳转页面到onReady总耗时":"119ms"}] +17:10:13.312 进入页面:​/pages/data/data?deviceId=C6:91:6A:18:74:6A&deviceName=NB-C6916A18746A​ 。[{"创建dom元素个数":"26个","耗时":"17ms"},{"排版":"1次","耗时":"12ms"},{"渲染":"1次","耗时":"18ms"},{"跳转页面到onReady总耗时":"149ms"}] +17:11:12.674 App Hide at App.uvue:15 +17:15:57.583 App Show at App.uvue:11 +17:17:41.191 App Hide at App.uvue:15 +17:26:19.684 已停止运行... + diff --git a/src/main.uts b/src/main.uts new file mode 100644 index 0000000..8bdcc86 --- /dev/null +++ b/src/main.uts @@ -0,0 +1,9 @@ +import App from './App.uvue' + +import { createSSRApp } from 'vue' +export function createApp() { + const app = createSSRApp(App) + return { + app + } +} \ No newline at end of file diff --git a/src/manifest.json b/src/manifest.json new file mode 100644 index 0000000..a7b38f7 --- /dev/null +++ b/src/manifest.json @@ -0,0 +1,64 @@ +{ + "name": "app_sp2", + "appid": "__UNI__D50239E", + "description": "", + "versionName": "1.0.0", + "versionCode": "100", + "uni-app-x": {}, + /* 快应用特有相关 */ + "quickapp": {}, + /* 小程序特有相关 */ + "mp-weixin": { + "appid": "", + "setting": { + "urlCheck": false + }, + "usingComponents": true + }, + "mp-alipay": { + "usingComponents": true + }, + "mp-baidu": { + "usingComponents": true + }, + "mp-toutiao": { + "usingComponents": true + }, + "uniStatistics": { + "enable": false + }, + "vueVersion": "3", + "app": { + "distribute": { + "icons": { + "android": { + "hdpi": "", + "xhdpi": "", + "xxhdpi": "", + "xxxhdpi": "" + } + } + } + }, + "app-android": { + "distribute": { + "modules": {}, + "icons": { + "hdpi": "", + "xhdpi": "", + "xxhdpi": "", + "xxxhdpi": "" + }, + "splashScreens": { + "default": {} + } + } + }, + "app-ios": { + "distribute": { + "modules": {}, + "icons": {}, + "splashScreens": {} + } + } +} \ No newline at end of file diff --git a/src/pages.json b/src/pages.json new file mode 100644 index 0000000..9783d81 --- /dev/null +++ b/src/pages.json @@ -0,0 +1,23 @@ +{ + "pages": [ + { + "path": "pages/index/index", + "style": { + "navigationBarTitleText": "" + } + }, + { + "path": "pages/data/data", + "style": { + "navigationBarTitleText": "设备数据" + } + } + ], + "globalStyle": { + "navigationBarTextStyle": "black", + "navigationBarTitleText": "uni-app x", + "navigationBarBackgroundColor": "#F8F8F8", + "backgroundColor": "#F8F8F8" + }, + "uniIdRouter": {} +} diff --git a/src/pages/data/data.uvue b/src/pages/data/data.uvue new file mode 100644 index 0000000..0be921f --- /dev/null +++ b/src/pages/data/data.uvue @@ -0,0 +1,468 @@ + + + + + diff --git a/src/pages/index/index.uvue b/src/pages/index/index.uvue new file mode 100644 index 0000000..f03c974 --- /dev/null +++ b/src/pages/index/index.uvue @@ -0,0 +1,274 @@ + + + + + diff --git a/src/platformConfig.json b/src/platformConfig.json new file mode 100644 index 0000000..896f669 --- /dev/null +++ b/src/platformConfig.json @@ -0,0 +1,7 @@ + +// 参考链接 https://doc.dcloud.net.cn/uni-app-x/tutorial/ls-plugin.html#setting +{ + "targets": [ + "APP-ANDROID" + ] +} \ No newline at end of file diff --git a/src/static/logo.png b/src/static/logo.png new file mode 100644 index 0000000..b5771e2 Binary files /dev/null and b/src/static/logo.png differ diff --git a/src/uni.scss b/src/uni.scss new file mode 100644 index 0000000..b9249e9 --- /dev/null +++ b/src/uni.scss @@ -0,0 +1,76 @@ +/** + * 这里是uni-app内置的常用样式变量 + * + * uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量 + * 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App + * + */ + +/** + * 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能 + * + * 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件 + */ + +/* 颜色变量 */ + +/* 行为相关颜色 */ +$uni-color-primary: #007aff; +$uni-color-success: #4cd964; +$uni-color-warning: #f0ad4e; +$uni-color-error: #dd524d; + +/* 文字基本颜色 */ +$uni-text-color:#333;//基本色 +$uni-text-color-inverse:#fff;//反色 +$uni-text-color-grey:#999;//辅助灰色,如加载更多的提示信息 +$uni-text-color-placeholder: #808080; +$uni-text-color-disable:#c0c0c0; + +/* 背景颜色 */ +$uni-bg-color:#ffffff; +$uni-bg-color-grey:#f8f8f8; +$uni-bg-color-hover:#f1f1f1;//点击状态颜色 +$uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色 + +/* 边框颜色 */ +$uni-border-color:#c8c7cc; + +/* 尺寸变量 */ + +/* 文字尺寸 */ +$uni-font-size-sm:12px; +$uni-font-size-base:14px; +$uni-font-size-lg:16px; + +/* 图片尺寸 */ +$uni-img-size-sm:20px; +$uni-img-size-base:26px; +$uni-img-size-lg:40px; + +/* Border Radius */ +$uni-border-radius-sm: 2px; +$uni-border-radius-base: 3px; +$uni-border-radius-lg: 6px; +$uni-border-radius-circle: 50%; + +/* 水平间距 */ +$uni-spacing-row-sm: 5px; +$uni-spacing-row-base: 10px; +$uni-spacing-row-lg: 15px; + +/* 垂直间距 */ +$uni-spacing-col-sm: 4px; +$uni-spacing-col-base: 8px; +$uni-spacing-col-lg: 12px; + +/* 透明度 */ +$uni-opacity-disabled: 0.3; // 组件禁用态的透明度 + +/* 文章场景相关 */ +$uni-color-title: #2C405A; // 文章标题颜色 +$uni-font-size-title:20px; +$uni-color-subtitle: #555555; // 二级标题颜色 +$uni-font-size-subtitle:26px; +$uni-color-paragraph: #3F536E; // 文章段落颜色 +$uni-font-size-paragraph:15px; diff --git a/src/uni_modules/sp2-bluetooth/package.json b/src/uni_modules/sp2-bluetooth/package.json new file mode 100644 index 0000000..59259d5 --- /dev/null +++ b/src/uni_modules/sp2-bluetooth/package.json @@ -0,0 +1,50 @@ +{ + "id": "sp2-bluetooth", + "displayName": "sp2 蓝牙插件", + "version": "1.0.0", + "description": "用于 uni-app x Android BLE 扫描与连接的 UTS 插件", + "keywords": [ + "bluetooth", + "ble", + "uts" + ], + "engines": { + "HBuilderX": "^5.0.0", + "uni-app-x": "^5.0.0" + }, + "dcloudext": { + "type": "uts", + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "扫描附近蓝牙设备并建立 BLE 连接", + "permissions": "蓝牙与定位权限" + } + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "client": { + "uni-app-x": { + "app": { + "android": { + "extVersion": "1.0.0", + "minVersion": "21" + } + } + } + } + } + } +} diff --git a/src/uni_modules/sp2-bluetooth/utssdk/app-android/AndroidManifest.xml b/src/uni_modules/sp2-bluetooth/utssdk/app-android/AndroidManifest.xml new file mode 100644 index 0000000..fe1a7b0 --- /dev/null +++ b/src/uni_modules/sp2-bluetooth/utssdk/app-android/AndroidManifest.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + diff --git a/src/uni_modules/sp2-bluetooth/utssdk/app-android/hybrid.kt b/src/uni_modules/sp2-bluetooth/utssdk/app-android/hybrid.kt new file mode 100644 index 0000000..b0c4427 --- /dev/null +++ b/src/uni_modules/sp2-bluetooth/utssdk/app-android/hybrid.kt @@ -0,0 +1,972 @@ +package uts.sdk.modules.sp2Bluetooth + +import android.Manifest +import android.annotation.SuppressLint +import android.bluetooth.BluetoothAdapter +import android.bluetooth.BluetoothDevice +import android.bluetooth.BluetoothGatt +import android.bluetooth.BluetoothGattCallback +import android.bluetooth.BluetoothGattCharacteristic +import android.bluetooth.BluetoothGattDescriptor +import android.bluetooth.BluetoothGattService +import android.bluetooth.BluetoothManager +import android.bluetooth.BluetoothProfile +import android.bluetooth.le.BluetoothLeScanner +import android.bluetooth.le.ScanCallback +import android.bluetooth.le.ScanResult +import android.bluetooth.le.ScanSettings +import android.content.Context +import android.content.pm.PackageManager +import android.location.LocationManager +import android.os.Build +import android.os.Handler +import android.os.Looper +import android.util.Log +import io.dcloud.uts.UTSAndroid +import java.util.ArrayList +import java.util.Collections +import java.util.HashSet + +object BluetoothNative { + + private const val TAG = "sp2-bluetooth" + + private val mainHandler = Handler(Looper.getMainLooper()) + + private var scanCallback: ScanCallback? = null + private var bluetoothGatt: BluetoothGatt? = null + + private var onDeviceFoundCallback: ((String, String) -> Unit)? = null + private var onScanErrorCallback: ((String) -> Unit)? = null + private var onConnectSuccessCallback: (() -> Unit)? = null + private var onConnectErrorCallback: ((String) -> Unit)? = null + private var onDataReceivedCallback: ((String, String) -> Unit)? = null + private var onDebugMessageCallback: ((String) -> Unit)? = null + private var lastDebugMessage = "未产生蓝牙调试状态" + private var notifyEventCount = 0 + private var readEventCount = 0 + private var pendingNotifyDescriptorWrites = 0 + private var hasNotifyDescriptorSuccess = false + private var readCursor = 0 + private var mtuRequestDone = false + private val readableTargets = Collections.synchronizedList(ArrayList>()) + private val pendingDebugMessages = Collections.synchronizedList(ArrayList()) + private var selectedNotifyServiceUuid: String? = null + private var selectedNotifyCharacteristicUuid: String? = null + private var selectedWriteServiceUuid: String? = null + private var selectedWriteCharacteristicUuid: String? = null + private val pendingDataPackets = Collections.synchronizedList(ArrayList>()) + + private var hasConnectedOnce = false + private val discoveredDeviceIds = Collections.synchronizedSet(HashSet()) + + private val SERVICE_UUID = "0000FFF0-0000-1000-8000-00805F9B34FB" + private val READ_CHARACTERISTIC_UUID = "0000FFF1-0000-1000-8000-00805F9B34FB" + private val WRITE_CHARACTERISTIC_UUID = "0000FFF2-0000-1000-8000-00805F9B34FB" + private const val CLIENT_CHARACTERISTIC_CONFIG_UUID = "00002902-0000-1000-8000-00805f9b34fb" + private const val TARGET_MTU = 250 + fun startBluetoothScan( + onDeviceFound: (deviceId: String, name: String) -> Unit, + onError: (message: String) -> Unit + ) { + debug("开始扫描蓝牙设备") + Log.i(TAG, "startBluetoothScan: begin") + val context = UTSAndroid.getAppContext() + if (context == null) { + Log.e(TAG, "startBluetoothScan: context is null") + postScanError(onError, "无法获取 Android Context") + return + } + + val adapter = getBluetoothAdapter(context) + if (adapter == null) { + Log.e(TAG, "startBluetoothScan: bluetooth adapter unavailable") + postScanError(onError, "当前设备不支持蓝牙") + return + } + + if (!adapter.isEnabled) { + Log.e(TAG, "startBluetoothScan: bluetooth disabled") + postScanError(onError, "蓝牙未开启") + return + } + + val scanCheck = checkScanEnvironment(context) + if (scanCheck != null) { + Log.e(TAG, "startBluetoothScan: environment check failed -> $scanCheck") + postScanError(onError, scanCheck) + return + } + + stopBluetoothScan() + + val scanner = adapter.bluetoothLeScanner + if (scanner == null) { + Log.e(TAG, "startBluetoothScan: scanner is null") + postScanError(onError, "无法获取 BLE Scanner") + return + } + + discoveredDeviceIds.clear() + onDeviceFoundCallback = onDeviceFound + onScanErrorCallback = onError + + val settings = ScanSettings.Builder() + .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) + .build() + + scanCallback = object : ScanCallback() { + override fun onScanResult(callbackType: Int, result: ScanResult) { + val device = result.device ?: return + val deviceId = device.address ?: return + val isNewDevice = discoveredDeviceIds.add(deviceId) + if (!isNewDevice) { + return + } + + val name = resolveDeviceName(result) + Log.i(TAG, "onScanResult: deviceId=$deviceId, name=$name") + postDeviceFound(deviceId, name) + } + + override fun onBatchScanResults(results: MutableList) { + for (result in results) { + onScanResult(ScanSettings.CALLBACK_TYPE_ALL_MATCHES, result) + } + } + + override fun onScanFailed(errorCode: Int) { + Log.e(TAG, "onScanFailed: errorCode=$errorCode") + postScanError("BLE 扫描失败,errorCode=$errorCode") + } + } + + try { + scanner.startScan(null, settings, scanCallback) + Log.i(TAG, "startBluetoothScan: scanner started") + } catch (e: SecurityException) { + Log.e(TAG, "startBluetoothScan: permission denied", e) + postScanError(onError, "BLE 扫描权限不足: ${e.message ?: "unknown"}") + } catch (e: Throwable) { + Log.e(TAG, "startBluetoothScan: start failed", e) + postScanError(onError, "BLE 扫描启动失败: ${e.message ?: "unknown"}") + } + } + + fun stopBluetoothScan() { + Log.i(TAG, "stopBluetoothScan: stop requested") + val context = UTSAndroid.getAppContext() + val adapter = context?.let { getBluetoothAdapter(it) } + val scanner: BluetoothLeScanner? = adapter?.bluetoothLeScanner + + try { + if (scanner != null && scanCallback != null) { + scanner.stopScan(scanCallback) + } + } catch (_: Throwable) { + } finally { + scanCallback = null + onDeviceFoundCallback = null + onScanErrorCallback = null + discoveredDeviceIds.clear() + } + } + + fun connectBluetoothDevice( + deviceId: String, + onSuccess: () -> Unit, + onError: (message: String) -> Unit + ) { + debug("开始连接设备: $deviceId") + Log.i(TAG, "connectBluetoothDevice: deviceId=$deviceId") + val context = UTSAndroid.getAppContext() + if (context == null) { + Log.e(TAG, "connectBluetoothDevice: context is null") + postConnectError(onError, "无法获取 Android Context") + return + } + + val adapter = getBluetoothAdapter(context) + if (adapter == null) { + Log.e(TAG, "connectBluetoothDevice: bluetooth adapter unavailable") + postConnectError(onError, "当前设备不支持蓝牙") + return + } + + if (!adapter.isEnabled) { + Log.e(TAG, "connectBluetoothDevice: bluetooth disabled") + postConnectError(onError, "蓝牙未开启") + return + } + + val connectCheck = checkConnectEnvironment(context) + if (connectCheck != null) { + Log.e(TAG, "connectBluetoothDevice: environment check failed -> $connectCheck") + postConnectError(onError, connectCheck) + return + } + + val device = try { + adapter.getRemoteDevice(deviceId) + } catch (_: IllegalArgumentException) { + null + } + + if (device == null) { + Log.e(TAG, "connectBluetoothDevice: invalid deviceId=$deviceId") + postConnectError(onError, "无效的设备地址: $deviceId") + return + } + + disconnectBluetoothDevice() + + hasConnectedOnce = false + notifyEventCount = 0 + readEventCount = 0 + pendingNotifyDescriptorWrites = 0 + hasNotifyDescriptorSuccess = false + readCursor = 0 + mtuRequestDone = false + readableTargets.clear() + onConnectSuccessCallback = onSuccess + onConnectErrorCallback = onError + + val callback = object : BluetoothGattCallback() { + override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) { + debug("连接状态变化: status=$status, newState=$newState") + Log.i(TAG, "onConnectionStateChange: status=$status, newState=$newState, address=${gatt.device?.address}") + when { + status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothProfile.STATE_CONNECTED -> { + hasConnectedOnce = true + bluetoothGatt = gatt + requestPreferredMtu(gatt) + try { + Log.i(TAG, "onConnectionStateChange: discoverServices") + gatt.discoverServices() + } catch (e: Throwable) { + Log.e(TAG, "onConnectionStateChange: discoverServices failed", e) + postConnectError("服务发现启动失败: ${e.message ?: "unknown"}") + } + } + + newState == BluetoothProfile.STATE_DISCONNECTED -> { + val needCallback = !hasConnectedOnce + Log.w(TAG, "onConnectionStateChange: disconnected, needCallback=$needCallback") + safeCloseGatt(gatt) + bluetoothGatt = null + + if (needCallback) { + postConnectError("BLE 连接失败,status=$status") + } else { + clearConnectCallbacks() + } + } + + status != BluetoothGatt.GATT_SUCCESS -> { + Log.e(TAG, "onConnectionStateChange: gatt error status=$status") + safeCloseGatt(gatt) + bluetoothGatt = null + postConnectError("BLE 连接失败,status=$status") + } + } + } + + override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) { + debug("服务发现完成: status=$status, count=${gatt.services?.size ?: 0}") + Log.i(TAG, "onServicesDiscovered: status=$status, serviceCount=${gatt.services?.size ?: 0}") + if (status == BluetoothGatt.GATT_SUCCESS) { + gatt.services?.forEach { service -> + debug("服务: ${service.uuid}") + Log.i(TAG, "service: uuid=${service.uuid}, characteristicCount=${service.characteristics?.size ?: 0}") + service.characteristics?.forEach { characteristic -> + debug("特征: ${characteristic.uuid}, properties=${characteristic.properties}") + Log.i(TAG, "characteristic: uuid=${characteristic.uuid}, properties=${characteristic.properties}") + } + } + cacheReadableTargets(gatt) + enableCharacteristicNotification(gatt) + } else { + Log.e(TAG, "onServicesDiscovered: failed status=$status") + } + } + + override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) { + notifyEventCount += 1 + val data = characteristic.value + debug("notify回调#${notifyEventCount}: uuid=${characteristic.uuid}, size=${data?.size ?: 0}") + if (data != null && data.isNotEmpty()) { + val hexString = data.joinToString("") { "%02X".format(it) } + val stringData = String(data, Charsets.UTF_8) + debug("收到 notify#${notifyEventCount}: uuid=${characteristic.uuid}, hex=$hexString") + Log.i(TAG, "onCharacteristicChanged: uuid=${characteristic.uuid}, hex=$hexString, text=$stringData") + postDataReceived(stringData, hexString) + } else { + debug("收到 notify#${notifyEventCount} 但数据为空: ${characteristic.uuid}") + Log.w(TAG, "onCharacteristicChanged: empty payload, uuid=${characteristic.uuid}") + } + } + + override fun onDescriptorWrite( + gatt: BluetoothGatt, + descriptor: BluetoothGattDescriptor, + status: Int + ) { + debug("通知描述符写入: status=$status") + Log.i(TAG, "onDescriptorWrite: uuid=${descriptor.uuid}, status=$status") + if (pendingNotifyDescriptorWrites > 0) { + pendingNotifyDescriptorWrites -= 1 + } + if (status == BluetoothGatt.GATT_SUCCESS) { + hasNotifyDescriptorSuccess = true + } + + if (pendingNotifyDescriptorWrites == 0) { + if (hasNotifyDescriptorSuccess) { + postConnectSuccess() + } else { + postConnectError("启用蓝牙通知失败,status=$status") + } + } + } + + override fun onCharacteristicWrite( + gatt: BluetoothGatt, + characteristic: BluetoothGattCharacteristic, + status: Int + ) { + debug("写入特征完成: status=$status") + Log.i(TAG, "onCharacteristicWrite: uuid=${characteristic.uuid}, status=$status") + } + + override fun onMtuChanged(gatt: BluetoothGatt, mtu: Int, status: Int) { + debug("MTU 设置结果: status=$status, mtu=$mtu") + Log.i(TAG, "onMtuChanged: status=$status, mtu=$mtu") + } + + override fun onCharacteristicRead( + gatt: BluetoothGatt, + characteristic: BluetoothGattCharacteristic, + status: Int + ) { + readEventCount += 1 + debug("读取回调#${readEventCount}: status=$status, uuid=${characteristic.uuid}") + Log.i(TAG, "onCharacteristicRead: uuid=${characteristic.uuid}, status=$status") + if (status == BluetoothGatt.GATT_SUCCESS) { + val data = characteristic.value + if (data != null && data.isNotEmpty()) { + val hexString = data.joinToString("") { "%02X".format(it) } + val stringData = String(data, Charsets.UTF_8) + debug("读取到数据#${readEventCount}: uuid=${characteristic.uuid}, hex=$hexString") + postDataReceived(stringData, hexString) + } else { + debug("读取成功但数据为空#${readEventCount}: ${characteristic.uuid}") + } + } else { + debug("读取失败#${readEventCount}: status=$status, uuid=${characteristic.uuid}") + } + } + + } + + try { + bluetoothGatt = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + device.connectGatt(context, false, callback, BluetoothDevice.TRANSPORT_LE) + } else { + device.connectGatt(context, false, callback) + } + Log.i(TAG, "connectBluetoothDevice: connectGatt invoked") + } catch (e: SecurityException) { + Log.e(TAG, "connectBluetoothDevice: permission denied", e) + postConnectError(onError, "BLE 连接权限不足: ${e.message ?: "unknown"}") + } catch (e: Throwable) { + Log.e(TAG, "connectBluetoothDevice: start failed", e) + postConnectError(onError, "BLE 连接启动失败: ${e.message ?: "unknown"}") + } + } + + fun disconnectBluetoothDevice() { + Log.i(TAG, "disconnectBluetoothDevice: disconnect requested") + try { + bluetoothGatt?.disconnect() + } catch (_: Throwable) { + } + + try { + bluetoothGatt?.close() + } catch (_: Throwable) { + } + + bluetoothGatt = null + hasConnectedOnce = false + notifyEventCount = 0 + readEventCount = 0 + pendingNotifyDescriptorWrites = 0 + hasNotifyDescriptorSuccess = false + readCursor = 0 + mtuRequestDone = false + readableTargets.clear() + selectedNotifyServiceUuid = null + selectedNotifyCharacteristicUuid = null + selectedWriteServiceUuid = null + selectedWriteCharacteristicUuid = null + pendingDataPackets.clear() + pendingDebugMessages.clear() + clearConnectCallbacks() + } + + private fun getBluetoothAdapter(context: Context): BluetoothAdapter? { + val manager = context.getSystemService(Context.BLUETOOTH_SERVICE) as? BluetoothManager + return manager?.adapter + } + + private fun checkScanEnvironment(context: Context): String? { + val missingPermissions = getMissingScanPermissions(context) + if (missingPermissions.isNotEmpty()) { + return "缺少扫描权限: ${missingPermissions.joinToString(",")}" + } + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S && !isLocationEnabled(context)) { + return "Android 12 以下扫描 BLE 需要开启系统定位服务" + } + + return null + } + + private fun checkConnectEnvironment(context: Context): String? { + val missingPermissions = getMissingConnectPermissions(context) + if (missingPermissions.isNotEmpty()) { + return "缺少连接权限: ${missingPermissions.joinToString(",")}" + } + return null + } + + private fun getMissingScanPermissions(context: Context): List { + val permissions = mutableListOf() + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + permissions.add(Manifest.permission.BLUETOOTH_SCAN) + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + permissions.add(Manifest.permission.ACCESS_FINE_LOCATION) + } + + return permissions.filter { !hasPermission(context, it) } + } + + private fun getMissingConnectPermissions(context: Context): List { + val permissions = mutableListOf() + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + permissions.add(Manifest.permission.BLUETOOTH_CONNECT) + } + + return permissions.filter { !hasPermission(context, it) } + } + + private fun hasPermission(context: Context, permission: String): Boolean { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + context.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED + } else { + true + } + } + + private fun isLocationEnabled(context: Context): Boolean { + val locationManager = + context.getSystemService(Context.LOCATION_SERVICE) as? LocationManager ?: return false + + return try { + locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) || + locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER) + } catch (_: Throwable) { + false + } + } + + @SuppressLint("MissingPermission") + private fun resolveDeviceName(result: ScanResult): String { + return try { + result.device?.name + ?: result.scanRecord?.deviceName + ?: "" + } catch (_: SecurityException) { + result.scanRecord?.deviceName ?: "" + } catch (_: Throwable) { + "" + } + } + + private fun safeCloseGatt(gatt: BluetoothGatt?) { + Log.i(TAG, "safeCloseGatt: address=${gatt?.device?.address}") + try { + gatt?.disconnect() + } catch (_: Throwable) { + } + + try { + gatt?.close() + } catch (_: Throwable) { + } + } + + private fun clearConnectCallbacks() { + Log.i(TAG, "clearConnectCallbacks") + onConnectSuccessCallback = null + onConnectErrorCallback = null + } + + private fun postDeviceFound(deviceId: String, name: String) { + val callback = onDeviceFoundCallback ?: return + mainHandler.post { + callback(deviceId, name) + } + } + + private fun postScanError(message: String) { + val callback = onScanErrorCallback ?: return + mainHandler.post { + Log.e(TAG, "postScanError: $message") + callback(message) + } + } + + private fun postScanError(callback: (String) -> Unit, message: String) { + mainHandler.post { + Log.e(TAG, "postScanError: $message") + callback(message) + } + } + + private fun postConnectSuccess() { + val callback = onConnectSuccessCallback ?: return + mainHandler.post { + debug("设备连接完成,通知已开启") + Log.i(TAG, "postConnectSuccess") + callback() + } + } + + private fun postConnectError(message: String) { + val callback = onConnectErrorCallback ?: return + mainHandler.post { + Log.e(TAG, "postConnectError: $message") + callback(message) + clearConnectCallbacks() + } + } + + private fun postConnectError(callback: (String) -> Unit, message: String) { + mainHandler.post { + Log.e(TAG, "postConnectError: $message") + callback(message) + } + } + + private fun enableCharacteristicNotification(gatt: BluetoothGatt) { + try { + val notifyTargets = findNotifyTargets(gatt) + if (notifyTargets.isNotEmpty()) { + val firstTarget = notifyTargets[0] + selectedNotifyServiceUuid = firstTarget.first.uuid.toString() + selectedNotifyCharacteristicUuid = firstTarget.second.uuid.toString() + debug("已找到通知特征数量: ${notifyTargets.size}") + debug("当前订阅服务: ${selectedNotifyServiceUuid ?: ""}") + debug("当前订阅特征: ${selectedNotifyCharacteristicUuid ?: ""}") + + val writeTarget = findWriteTarget(gatt) + if (writeTarget != null) { + selectedWriteServiceUuid = writeTarget.first.uuid.toString() + selectedWriteCharacteristicUuid = writeTarget.second.uuid.toString() + debug("选择写入服务: ${writeTarget.first.uuid}") + debug("选择写入特征: ${writeTarget.second.uuid}") + Log.i(TAG, "enableCharacteristicNotification: selected write service=${writeTarget.first.uuid}, characteristic=${writeTarget.second.uuid}") + } else { + selectedWriteServiceUuid = selectedNotifyServiceUuid + selectedWriteCharacteristicUuid = selectedNotifyCharacteristicUuid + debug("未找到独立写入特征,回退为首个通知特征") + Log.w(TAG, "enableCharacteristicNotification: write characteristic not found, fallback to notify characteristic") + } + + pendingNotifyDescriptorWrites = 0 + hasNotifyDescriptorSuccess = false + + for (target in notifyTargets) { + val service = target.first + val characteristic = target.second + debug("订阅通知服务: ${service.uuid}") + debug("订阅通知特征: ${characteristic.uuid}") + Log.i(TAG, "enableCharacteristicNotification: subscribe service=${service.uuid}, characteristic=${characteristic.uuid}") + + val notifyResult = gatt.setCharacteristicNotification(characteristic, true) + debug("开启通知结果: $notifyResult") + Log.i(TAG, "enableCharacteristicNotification: characteristic=${characteristic.uuid}, notifyResult=$notifyResult") + if (!notifyResult) { + continue + } + + val descriptor = characteristic.getDescriptor(java.util.UUID.fromString(CLIENT_CHARACTERISTIC_CONFIG_UUID)) + if (descriptor == null) { + Log.e(TAG, "enableCharacteristicNotification: CCCD descriptor missing -> ${characteristic.uuid}") + continue + } + + val properties = characteristic.properties + val enableValue = if ((properties and BluetoothGattCharacteristic.PROPERTY_INDICATE) != 0) { + BluetoothGattDescriptor.ENABLE_INDICATION_VALUE + } else { + BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE + } + descriptor.value = enableValue + pendingNotifyDescriptorWrites += 1 + val writeResult = gatt.writeDescriptor(descriptor) + debug("写入通知描述符结果: $writeResult") + Log.i(TAG, "enableCharacteristicNotification: descriptor=${descriptor.uuid}, writeResult=$writeResult") + if (!writeResult) { + pendingNotifyDescriptorWrites -= 1 + } + } + + if (pendingNotifyDescriptorWrites == 0) { + postConnectError("没有成功开启任何通知特征") + } + } else { + Log.e(TAG, "enableCharacteristicNotification: no notify characteristic found") + postConnectError("未找到可接收数据的通知特征值") + } + } catch (e: Throwable) { + Log.e(TAG, "enableCharacteristicNotification: failed", e) + postConnectError("开启蓝牙通知失败: ${e.message ?: "unknown"}") + } + } + + fun setOnDataReceivedCallback(callback: (String, String) -> Unit) { + Log.i(TAG, "setOnDataReceivedCallback") + onDataReceivedCallback = callback + flushPendingDataPackets() + } + + fun setOnDebugMessageCallback(callback: (String) -> Unit) { + onDebugMessageCallback = callback + flushPendingDebugMessages() + } + + fun getDebugSnapshot(): String { + return lastDebugMessage + } + + fun readBluetoothDataOnce() { + val gatt = bluetoothGatt + if (gatt == null) { + debug("读取一次失败:设备未连接") + return + } + + if (readableTargets.isEmpty()) { + cacheReadableTargets(gatt) + } + + if (readableTargets.isEmpty()) { + debug("读取一次失败:未找到可读特征") + return + } + + val preferredTarget = findReadTarget(gatt) + if (preferredTarget != null) { + val characteristic = preferredTarget.second + try { + debug("开始手动读取 FFF1: ${characteristic.uuid}") + gatt.readCharacteristic(characteristic) + } catch (e: Throwable) { + Log.e(TAG, "readBluetoothDataOnce: readCharacteristic failed", e) + } + } + + if (readableTargets.isEmpty()) { + return + } + + if (readCursor >= readableTargets.size) { + readCursor = 0 + } + + val fallbackTarget = readableTargets[readCursor] + readCursor += 1 + debug("定位模式:额外读取候选特征 ${readCursor}/${readableTargets.size}") + val fallbackCharacteristic = fallbackTarget.second + try { + debug("开始手动读取候选特征: ${fallbackCharacteristic.uuid}") + gatt.readCharacteristic(fallbackCharacteristic) + } catch (e: Throwable) { + Log.e(TAG, "readBluetoothDataOnce: read fallback characteristic failed", e) + } + } + + fun refreshBluetoothNotification() { + val gatt = bluetoothGatt + if (gatt == null) { + debug("重订阅失败:设备未连接") + return + } + + debug("准备重新开启通知订阅") + enableCharacteristicNotification(gatt) + } + + fun writeBluetoothData(data: String) { + val gatt = bluetoothGatt ?: return + try { + val target = findWriteTarget(gatt) + if (target != null) { + val service = target.first + val characteristic = target.second + selectedWriteServiceUuid = service.uuid.toString() + selectedWriteCharacteristicUuid = characteristic.uuid.toString() + val normalizedData = data.replace("\r\n", "\n").replace("\n", "\r\n") + val payload = ByteArray(normalizedData.length) + for (index in normalizedData.indices) { + payload[index] = normalizedData[index].code.toByte() + } + characteristic.value = payload + if ((characteristic.properties and BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) != 0) { + characteristic.writeType = BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE + } else { + characteristic.writeType = BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT + } + val writeResult = gatt.writeCharacteristic(characteristic) + val payloadHex = payload.joinToString("") { "%02X".format(it) } + debug("写入命令: ${normalizedData.replace("\r", "\\r").replace("\n", "\\n")} [$payloadHex]") + Log.i(TAG, "writeBluetoothData: service=${service.uuid}, characteristic=${characteristic.uuid}, payload=$normalizedData, payloadHex=$payloadHex, writeResult=$writeResult") + } else { + Log.e(TAG, "writeBluetoothData: no writable characteristic found") + } + } catch (e: Throwable) { + Log.e(TAG, "writeBluetoothData: failed", e) + } + } + + private fun findNotifyTargets(gatt: BluetoothGatt): List> { + val services = gatt.services ?: return emptyList() + val preferredServiceUuid = SERVICE_UUID.lowercase() + val preferredCharacteristicUuid = READ_CHARACTERISTIC_UUID.lowercase() + val preferredTargets = ArrayList>() + val fallbackTargets = ArrayList>() + + for (service in services) { + val serviceUuid = service.uuid.toString().lowercase() + for (characteristic in service.characteristics ?: emptyList()) { + val characteristicUuid = characteristic.uuid.toString().lowercase() + val properties = characteristic.properties + val supportsNotify = (properties and BluetoothGattCharacteristic.PROPERTY_NOTIFY) != 0 + val supportsIndicate = (properties and BluetoothGattCharacteristic.PROPERTY_INDICATE) != 0 + if (!supportsNotify && !supportsIndicate) { + continue + } + + if (serviceUuid == preferredServiceUuid && characteristicUuid == preferredCharacteristicUuid) { + Log.i(TAG, "findNotifyTarget: matched preferred uuid") + preferredTargets.add(Pair(service, characteristic)) + } + + fallbackTargets.add(Pair(service, characteristic)) + } + } + + if (preferredTargets.isNotEmpty()) { + return preferredTargets + fallbackTargets.filter { fallback -> + preferredTargets.none { preferred -> + preferred.first.uuid == fallback.first.uuid && preferred.second.uuid == fallback.second.uuid + } + } + } + + return fallbackTargets + } + + private fun findWriteTarget(gatt: BluetoothGatt): Pair? { + val services = gatt.services ?: return null + val preferredServiceUuid = SERVICE_UUID.lowercase() + val preferredCharacteristicUuid = WRITE_CHARACTERISTIC_UUID.lowercase() + var fallbackTarget: Pair? = null + + for (service in services) { + val serviceUuid = service.uuid.toString().lowercase() + for (characteristic in service.characteristics ?: emptyList()) { + val characteristicUuid = characteristic.uuid.toString().lowercase() + val properties = characteristic.properties + val supportsWrite = (properties and BluetoothGattCharacteristic.PROPERTY_WRITE) != 0 || + (properties and BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) != 0 + if (!supportsWrite) { + continue + } + + if (serviceUuid == preferredServiceUuid && characteristicUuid == preferredCharacteristicUuid) { + Log.i(TAG, "findWriteTarget: matched preferred uuid") + return Pair(service, characteristic) + } + + if (fallbackTarget == null) { + fallbackTarget = Pair(service, characteristic) + } + } + } + + if (fallbackTarget != null) { + Log.w(TAG, "findWriteTarget: preferred uuid missing, use fallback service=${fallbackTarget.first.uuid}, characteristic=${fallbackTarget.second.uuid}") + } + return fallbackTarget + } + + private fun findReadTarget(gatt: BluetoothGatt): Pair? { + val services = gatt.services ?: return null + val preferredServiceUuid = SERVICE_UUID.lowercase() + val preferredCharacteristicUuid = READ_CHARACTERISTIC_UUID.lowercase() + var fallbackTarget: Pair? = null + + for (service in services) { + val serviceUuid = service.uuid.toString().lowercase() + for (characteristic in service.characteristics ?: emptyList()) { + val characteristicUuid = characteristic.uuid.toString().lowercase() + val properties = characteristic.properties + val supportsRead = (properties and BluetoothGattCharacteristic.PROPERTY_READ) != 0 + if (!supportsRead) { + continue + } + + if (serviceUuid == preferredServiceUuid && characteristicUuid == preferredCharacteristicUuid) { + Log.i(TAG, "findReadTarget: matched preferred uuid") + return Pair(service, characteristic) + } + + if (fallbackTarget == null) { + fallbackTarget = Pair(service, characteristic) + } + } + } + + if (fallbackTarget != null) { + Log.w(TAG, "findReadTarget: preferred uuid missing, use fallback service=${fallbackTarget.first.uuid}, characteristic=${fallbackTarget.second.uuid}") + } + return fallbackTarget + } + + private fun cacheReadableTargets(gatt: BluetoothGatt) { + readableTargets.clear() + + val services = gatt.services ?: return + val preferredServiceUuid = SERVICE_UUID.lowercase() + val preferredCharacteristicUuid = READ_CHARACTERISTIC_UUID.lowercase() + val fallbackTargets = ArrayList>() + for (service in services) { + val serviceUuid = service.uuid.toString().lowercase() + for (characteristic in service.characteristics ?: emptyList()) { + val characteristicUuid = characteristic.uuid.toString().lowercase() + val properties = characteristic.properties + val supportsRead = (properties and BluetoothGattCharacteristic.PROPERTY_READ) != 0 + if (!supportsRead) { + continue + } + + val target = Pair(service, characteristic) + if (serviceUuid == preferredServiceUuid && characteristicUuid == preferredCharacteristicUuid) { + readableTargets.add(target) + } else { + fallbackTargets.add(target) + } + } + } + + readableTargets.addAll(fallbackTargets) + readCursor = 0 + + debug("已找到可读特征数量: ${readableTargets.size}") + } + + private fun requestPreferredMtu(gatt: BluetoothGatt) { + if (mtuRequestDone) { + return + } + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + debug("当前系统不支持设置 MTU") + return + } + + mtuRequestDone = true + try { + val requested = gatt.requestMtu(TARGET_MTU) + debug("请求 MTU 250: $requested") + } catch (e: Throwable) { + Log.e(TAG, "requestPreferredMtu: failed", e) + debug("请求 MTU 250 失败") + } + } + + private fun flushPendingDataPackets() { + val callback = onDataReceivedCallback ?: return + val packets = ArrayList>() + synchronized(pendingDataPackets) { + if (pendingDataPackets.isEmpty()) { + return + } + packets.addAll(pendingDataPackets) + pendingDataPackets.clear() + } + + mainHandler.post { + for (packet in packets) { + Log.i(TAG, "flushPendingDataPackets: hex=${packet.second}, text=${packet.first}") + callback(packet.first, packet.second) + } + } + } + + private fun postDataReceived(data: String, hex: String) { + val callback = onDataReceivedCallback + if (callback == null) { + synchronized(pendingDataPackets) { + pendingDataPackets.add(Pair(data, hex)) + } + Log.w(TAG, "postDataReceived: callback missing, packet cached") + return + } + + mainHandler.post { + debug("收到数据: $hex") + Log.i(TAG, "postDataReceived: hex=$hex, text=$data") + callback(data, hex) + } + } + + private fun debug(message: String) { + lastDebugMessage = message + val callback = onDebugMessageCallback + if (callback == null) { + synchronized(pendingDebugMessages) { + pendingDebugMessages.add(message) + } + return + } + + mainHandler.post { + callback(message) + } + } + + private fun flushPendingDebugMessages() { + val callback = onDebugMessageCallback ?: return + val messages = ArrayList() + synchronized(pendingDebugMessages) { + if (pendingDebugMessages.isEmpty()) { + return + } + messages.addAll(pendingDebugMessages) + pendingDebugMessages.clear() + } + + mainHandler.post { + for (message in messages) { + callback(message) + } + } + } +} diff --git a/src/uni_modules/sp2-bluetooth/utssdk/app-android/index.uts b/src/uni_modules/sp2-bluetooth/utssdk/app-android/index.uts new file mode 100644 index 0000000..29b85f1 --- /dev/null +++ b/src/uni_modules/sp2-bluetooth/utssdk/app-android/index.uts @@ -0,0 +1,101 @@ +import Build from 'android.os.Build' +import { BluetoothNative } from 'uts.sdk.modules.sp2Bluetooth' +import { type BluetoothDeviceItem, type BluetoothDeviceFoundCallback, type BluetoothErrorCallback, type BluetoothSuccessCallback, type BluetoothDataCallback, type BluetoothDebugCallback } from '../interface.uts' + +function getBluetoothPermissions(): string[] { + const permissions: string[] = [] + if (Build.VERSION.SDK_INT >= 31) { + permissions.push('android.permission.BLUETOOTH_SCAN') + permissions.push('android.permission.BLUETOOTH_CONNECT') + } else if (Build.VERSION.SDK_INT >= 23) { + permissions.push('android.permission.ACCESS_FINE_LOCATION') + } + return permissions +} + +function ensureBluetoothPermissions(onSuccess: BluetoothSuccessCallback, onError: BluetoothErrorCallback): void { + const activity = UTSAndroid.getUniActivity() + if (activity == null) { + onError('无法获取当前 Activity') + return + } + + const permissions = getBluetoothPermissions() + if (permissions.length === 0) { + onSuccess() + return + } + + UTSAndroid.requestSystemPermission(activity, permissions, (allRight: boolean, grantedList: string[]) => { + if (allRight || grantedList.length === permissions.length) { + onSuccess() + return + } + onError('蓝牙权限未完全授予') + }, (doNotAskAgain: boolean, grantedList: string[]) => { + if (doNotAskAgain) { + onError('蓝牙权限被永久拒绝,请到系统设置中开启') + return + } + if (grantedList.length > 0) { + onError('蓝牙权限未完全授予') + return + } + onError('蓝牙权限申请失败') + }) +} + +export function startBluetoothScan(onDeviceFound: BluetoothDeviceFoundCallback, onError: BluetoothErrorCallback): void { + ensureBluetoothPermissions(() => { + BluetoothNative.startBluetoothScan((deviceId: string, name: string) => { + const device: BluetoothDeviceItem = { + deviceId: deviceId, + name: name + } + onDeviceFound(device) + }, onError) + }, onError) +} + +export function stopBluetoothScan(): void { + BluetoothNative.stopBluetoothScan() +} + +export function connectBluetoothDevice(deviceId: string, onSuccess: BluetoothSuccessCallback, onError: BluetoothErrorCallback): void { + stopBluetoothScan() + disconnectBluetoothDevice() + ensureBluetoothPermissions(() => { + // 对齐 ecBLE.js 的“先清理旧连接,再建立新连接”思路,避免复用脏的 GATT 状态。 + setTimeout(() => { + BluetoothNative.connectBluetoothDevice(deviceId, onSuccess, onError) + }, 300) + }, onError) +} + +export function disconnectBluetoothDevice(): void { + BluetoothNative.disconnectBluetoothDevice() +} + +export function onBluetoothDataReceived(callback: BluetoothDataCallback): void { + BluetoothNative.setOnDataReceivedCallback(callback) +} + +export function onBluetoothDebugMessage(callback: BluetoothDebugCallback): void { + BluetoothNative.setOnDebugMessageCallback(callback) +} + +export function getBluetoothDebugSnapshot(): string { + return BluetoothNative.getDebugSnapshot() +} + +export function readBluetoothDataOnce(): void { + BluetoothNative.readBluetoothDataOnce() +} + +export function refreshBluetoothNotification(): void { + BluetoothNative.refreshBluetoothNotification() +} + +export function writeBluetoothData(data: string): void { + BluetoothNative.writeBluetoothData(data) +} diff --git a/src/uni_modules/sp2-bluetooth/utssdk/interface.uts b/src/uni_modules/sp2-bluetooth/utssdk/interface.uts new file mode 100644 index 0000000..e6df17f --- /dev/null +++ b/src/uni_modules/sp2-bluetooth/utssdk/interface.uts @@ -0,0 +1,21 @@ +export type BluetoothDeviceItem = { + deviceId: string, + name: string +} + +export type BluetoothDeviceFoundCallback = (device: BluetoothDeviceItem) => void +export type BluetoothErrorCallback = (message: string) => void +export type BluetoothSuccessCallback = () => void +export type BluetoothDataCallback = (data: string, hex: string) => void +export type BluetoothDebugCallback = (message: string) => void + +export declare function startBluetoothScan(onDeviceFound: BluetoothDeviceFoundCallback, onError: BluetoothErrorCallback): void +export declare function stopBluetoothScan(): void +export declare function connectBluetoothDevice(deviceId: string, onSuccess: BluetoothSuccessCallback, onError: BluetoothErrorCallback): void +export declare function disconnectBluetoothDevice(): void +export declare function onBluetoothDataReceived(callback: BluetoothDataCallback): void +export declare function onBluetoothDebugMessage(callback: BluetoothDebugCallback): void +export declare function getBluetoothDebugSnapshot(): string +export declare function readBluetoothDataOnce(): void +export declare function refreshBluetoothNotification(): void +export declare function writeBluetoothData(data: string): void diff --git a/src/utils/ecBLE.js b/src/utils/ecBLE.js new file mode 100644 index 0000000..7e493fe --- /dev/null +++ b/src/utils/ecBLE.js @@ -0,0 +1,388 @@ +const regeneratorRuntime = require('./regenerator/runtime.js') + +const logEnable = true + +let deviceList = [] + +let ecDeviceId = "" +let ecServerId = '' +let ecWriteCharacteristicId = '' +let ecReadCharacteristicId = '' + +let ecServerIdOption1 = "0000FFF0-0000-1000-8000-00805F9B34FB" +let ecServerIdOption2 = "FFF0" +let ecWriteCharacteristicIdOption1 = "0000FFF2-0000-1000-8000-00805F9B34FB" +let ecWriteCharacteristicIdOption2 = "FFF2" +let ecReadCharacteristicIdOption1 = "0000FFF1-0000-1000-8000-00805F9B34FB" +let ecReadCharacteristicIdOption2 = "FFF1" + +const log = (data) => { + if (logEnable) { + console.log(data) + } +} +const wait = (i) => { + return new Promise(function (resolve, reject) { + setTimeout(() => { + resolve() + }, i); + }) +} +const openBluetoothAdapter = () => { + return new Promise(function (resolve, reject) { + wx.openBluetoothAdapter({ + success(res) { + resolve({ ok: true, errCode: 0, errMsg: "" }) + }, + fail(res) { + log(res) + resolve({ ok: false, errCode: res.errCode, errMsg: res.errMsg }) + } + }) + }) +} +const closeBluetoothAdapter = () => { + return new Promise(function (resolve, reject) { + wx.closeBluetoothAdapter({ + success(res) { + resolve({ ok: true, errCode: 0, errMsg: '' }) + }, + fail(res) { + resolve({ ok: false, errCode: res.errCode, errMsg: res.errMsg }) + } + }) + }) +} +const getBluetoothAdapterState = () => { + return new Promise(function (resolve, reject) { + wx.getBluetoothAdapterState({ + success(res) { + if (res.available) { + resolve({ ok: true, errCode: 0, errMsg: '' }) + } else { + //蓝牙适配器不可用,打印失败信息 + log(res) + resolve({ ok: false, errCode: 20000, errMsg: '蓝牙适配器关闭' }) + } + }, + fail(res) { + //打印失败信息 + log(res) + resolve({ ok: false, errCode: res.errCode, errMsg: res.errMsg }) + } + }) + }) +} + +const startBluetoothDevicesDiscovery = (cb) => { + deviceList = [] + wx.onBluetoothDeviceFound((res) => { + let name = res.devices[0].name ? res.devices[0].name : res.devices[0].localName + if (!name) { return } + // log(res) + for (const item of deviceList) { + if (item.name === name) { + item.rssi = res.devices[0].RSSI + cb(name, item.rssi) + return + } + } + deviceList.push({ name, rssi: res.devices[0].RSSI, deviceId: res.devices[0].deviceId }) + cb(name, res.devices[0].RSSI) + }) + //开始搜索 + wx.startBluetoothDevicesDiscovery({ + //services: [ecServerId], + allowDuplicatesKey: true, + success(res) { + }, + fail(res) { + } + }) +} + +//结束搜索 +const stopBluetoothDevicesDiscovery = () => { + return new Promise(function (resolve, reject) { + //停止扫描 + wx.stopBluetoothDevicesDiscovery({ + success(res) { + resolve({ ok: true, errCode: 0, errMsg: '' }) + }, + fail(res) { + resolve({ ok: false, errCode: res.errCode, errMsg: res.errMsg }) + } + }) + }) +} + +//和设备建立连接 +const createBLEConnection = (name) => { + return new Promise(function (resolve, reject) { + let isExist = false + for (const item of deviceList) { + if (item.name === name) { + isExist = true + ecDeviceId = item.deviceId + break + } + } + if (!isExist) { + resolve({ ok: false, errCode: -1, errMsg: "Name error,Device does not exist" }) + return + } + //开始连接 + wx.createBLEConnection({ + deviceId: ecDeviceId, + success(res) { + log(res) + resolve({ ok: true, errCode: 0, errMsg: '' }) + }, + fail(res) { + //连接失败 + log("connect fail") + log(res) + resolve({ ok: false, errCode: res.errCode, errMsg: res.errMsg }) + } + }) + }) +} + +//关闭当前连接 +const closeBLEConnection = () => { + return new Promise(function (resolve, reject) { + wx.closeBLEConnection({ + deviceId: ecDeviceId, + success(res) { + resolve({ ok: true, errCode: 0, errMsg: '' }) + }, + fail(res) { + resolve({ ok: false, errCode: res.errCode, errMsg: res.errMsg }) + } + }) + }) +} + +const onBLEConnectionStateChange = (cb) => { + wx.onBLEConnectionStateChange((res) => { + if (!res.connected) cb() + }) +} + +const getBLEDeviceServices = () => { + return new Promise(function (resolve, reject) { + wx.getBLEDeviceServices({ + deviceId: ecDeviceId, + success(res) { + log('device services:') + log(res.services) + for (let i = 0; i < res.services.length; i++) { + let uuid = '' + log(res.services[i].uuid) + uuid = res.services[i].uuid.toUpperCase() + if (uuid === ecServerIdOption1) { + ecServerId = ecServerIdOption1 + return resolve({ ok: true, errCode: 0, errMsg: '' }) + } + if (uuid === ecServerIdOption2) { + ecServerId = ecServerIdOption2 + return resolve({ ok: true, errCode: 0, errMsg: '' }) + } + } + resolve({ ok: false, errCode: 20000, errMsg: '服务未找到' }) + }, + fail(res) { + resolve({ ok: false, errCode: res.errCode, errMsg: res.errMsg }) + } + }) + }) +} + +//连接特性 +const getBLEDeviceCharacteristics = () => { + return new Promise(function (resolve, reject) { + wx.getBLEDeviceCharacteristics({ + deviceId: ecDeviceId, + serviceId: ecServerId, + success(res) { + log('device getBLEDeviceCharacteristics:') + log(res.characteristics) + if (res.characteristics.length < 2) { + resolve({ ok: false, errCode: 20000, errMsg: '特征值出错' }) + return + } + let uuid1 = '' + let uuid2 = '' + uuid1 = res.characteristics[0].uuid.toUpperCase() + uuid2 = res.characteristics[1].uuid.toUpperCase() + if ((uuid1 === ecWriteCharacteristicIdOption1) + && (uuid2 === ecReadCharacteristicIdOption1)) { + ecWriteCharacteristicId = ecWriteCharacteristicIdOption1 + ecReadCharacteristicId = ecReadCharacteristicIdOption1 + resolve({ ok: true, errCode: 0, errMsg: '' }) + } + else if ((uuid1 === ecReadCharacteristicIdOption1) + && (uuid2 === ecWriteCharacteristicIdOption1)) { + ecWriteCharacteristicId = ecWriteCharacteristicIdOption1 + ecReadCharacteristicId = ecReadCharacteristicIdOption1 + resolve({ ok: true, errCode: 0, errMsg: '' }) + } + else if ((uuid1 === ecWriteCharacteristicIdOption2) + && (uuid2 === ecReadCharacteristicIdOption2)) { + ecWriteCharacteristicId = ecWriteCharacteristicIdOption2 + ecReadCharacteristicId = ecReadCharacteristicIdOption2 + resolve({ ok: true, errCode: 0, errMsg: '' }) + } + else if ((uuid1 === ecReadCharacteristicIdOption2) + && (uuid2 === ecWriteCharacteristicIdOption2)) { + ecWriteCharacteristicId = ecWriteCharacteristicIdOption2 + ecReadCharacteristicId = ecReadCharacteristicIdOption2 + resolve({ ok: true, errCode: 0, errMsg: '' }) + } + else { + resolve({ ok: false, errCode: 20000, errMsg: '特征值出错' }) + } + }, + fail(res) { + resolve({ ok: false, errCode: res.errCode, errMsg: res.errMsg }) + } + }) + }) +} + +//订阅通知 接收key +const notifyBLECharacteristicValueChange = () => { + return new Promise(function (resolve, reject) { + //开始订阅 + wx.notifyBLECharacteristicValueChange({ + state: true, + deviceId: ecDeviceId, + serviceId: ecServerId, + characteristicId: ecReadCharacteristicId, + success(res) { + resolve({ ok: true, errCode: 0, errMsg: '' }) + }, + fail(res) { + resolve({ ok: false, errCode: res.errCode, errMsg: res.errMsg }) + } + }) + }) +} + +const setBLEMTU = (mtu) => { + return new Promise(function (resolve, reject) { + //开始订阅 + wx.setBLEMTU({ + deviceId: ecDeviceId, + mtu, + success(res) { + resolve({ ok: true, errCode: 0, errMsg: '' }) + }, + fail(res) { + resolve({ ok: false, errCode: res.errCode, errMsg: res.errMsg }) + } + }) + }) +} + +const easyConnect = async (name, cb) => { + let res = {} + await closeBluetoothAdapter() + await openBluetoothAdapter() + res = await createBLEConnection(name) + if (!res.ok) { + res = { ok: false, errMsg: '蓝牙连接失败|' + res.errCode + '|' + res.errMsg, errCode: 10001 } + cb(res) + return res + } + res = await getBLEDeviceServices() + if (!res.ok) { + closeBLEConnection() + res = { ok: false, errMsg: '获取服务失败|' + res.errCode + '|' + res.errMsg, errCode: 10002 } + cb(res) + return res + } + res = await getBLEDeviceCharacteristics() + if (!res.ok) { + closeBLEConnection() + res = { ok: false, errMsg: '获取特性失败|' + res.errCode + '|' + res.errMsg, errCode: 10003 } + cb(res) + return res + } + res = await notifyBLECharacteristicValueChange() + if (!res.ok) { + closeBLEConnection() + res = { ok: false, errMsg: '订阅失败|' + res.errCode + '|' + res.errMsg, errCode: 10004 } + cb(res) + return res + } + await setBLEMTU(250) + res = { ok: true, errMsg: '', errCode: 0 } + cb(res) + return res +} + +const onBLECharacteristicValueChange = (cb) => { + wx.onBLECharacteristicValueChange((res) => { + let x = new Uint8Array(res.value); + // log(x) + let strHex = "" + let str = "" + for (let i = 0; i < x.length; i++) { + strHex = strHex + x[i].toString(16).padStart(2, "0") + str = str + String.fromCharCode(x[i]) + } + // log(strHex) + // log(str) + cb(str, strHex) + }) +} + +const writeBLECharacteristicValue = (data) => { + return new Promise(function (resolve, reject) { + wx.writeBLECharacteristicValue({ + deviceId: ecDeviceId, + serviceId: ecServerId, + characteristicId: ecWriteCharacteristicId, + value: data, + success(res) { + resolve({ ok: true, errCode: 0, errMsg: '' }) + }, + fail(res) { + resolve({ ok: false, errCode: res.errCode, errMsg: res.errMsg }) + } + }) + }) +} + +const easySendData = async (str, isHex) => { + if (str.length === 0) return + if (isHex) { + const buffer = new ArrayBuffer(str.length / 2); + let x = new Uint8Array(buffer); + for (let i = 0; i < x.length; i++) { + x[i] = parseInt(str.substr(2 * i, 2), 16) + } + return await writeBLECharacteristicValue(buffer) + } else { + const buffer = new ArrayBuffer(str.length); + let x = new Uint8Array(buffer); + for (let i = 0; i < x.length; i++) { + x[i] = str.charCodeAt(i) + } + return await writeBLECharacteristicValue(buffer) + } +} + +module.exports = { + openBluetoothAdapter, + closeBluetoothAdapter, + getBluetoothAdapterState, + startBluetoothDevicesDiscovery, + stopBluetoothDevicesDiscovery, + easyConnect, + closeBLEConnection, + onBLEConnectionStateChange, + onBLECharacteristicValueChange, + easySendData, +} \ No newline at end of file