From 3b2571d150ae300022a6352060e3a4a10888c59a Mon Sep 17 00:00:00 2001
From: yuntang <123@qq.com>
Date: Tue, 23 Jun 2026 11:27:35 +0800
Subject: [PATCH] 2026-06-23T11:27:35
---
push.sh | 22 +
src/.gitignore | 4 +
src/App.uvue | 51 +
src/android-logcat.log | 109 ++
src/index.html | 20 +
src/launch-android.log | 119 +++
src/main.uts | 9 +
src/manifest.json | 64 ++
src/pages.json | 23 +
src/pages/data/data.uvue | 468 +++++++++
src/pages/index/index.uvue | 274 +++++
src/platformConfig.json | 7 +
src/static/logo.png | Bin 0 -> 4023 bytes
src/uni.scss | 76 ++
src/uni_modules/sp2-bluetooth/package.json | 50 +
.../utssdk/app-android/AndroidManifest.xml | 24 +
.../utssdk/app-android/hybrid.kt | 972 ++++++++++++++++++
.../utssdk/app-android/index.uts | 101 ++
.../sp2-bluetooth/utssdk/interface.uts | 21 +
src/utils/ecBLE.js | 388 +++++++
20 files changed, 2802 insertions(+)
create mode 100644 push.sh
create mode 100644 src/.gitignore
create mode 100644 src/App.uvue
create mode 100644 src/android-logcat.log
create mode 100644 src/index.html
create mode 100644 src/launch-android.log
create mode 100644 src/main.uts
create mode 100644 src/manifest.json
create mode 100644 src/pages.json
create mode 100644 src/pages/data/data.uvue
create mode 100644 src/pages/index/index.uvue
create mode 100644 src/platformConfig.json
create mode 100644 src/static/logo.png
create mode 100644 src/uni.scss
create mode 100644 src/uni_modules/sp2-bluetooth/package.json
create mode 100644 src/uni_modules/sp2-bluetooth/utssdk/app-android/AndroidManifest.xml
create mode 100644 src/uni_modules/sp2-bluetooth/utssdk/app-android/hybrid.kt
create mode 100644 src/uni_modules/sp2-bluetooth/utssdk/app-android/index.uts
create mode 100644 src/uni_modules/sp2-bluetooth/utssdk/interface.uts
create mode 100644 src/utils/ecBLE.js
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 [33m提示:uts插件[sp2-bluetooth]依赖的原生配置或三方SDK在运行至标准基座时不能生效,如需正常调用请使用自定义基座[39m
+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 [33m手机端调试基座版本号为5.06.14660,与本地版本相同,跳过更新[39m
+17:07:47.069 正在建立手机连接...
+17:07:47.069 正在同步手机端程序文件...
+17:07:47.071 同步手机端程序文件成功
+17:07:47.072 正在启动uni-app x调试基座...
+17:07:47.073 [0mApp Launch[0m at App.uvue:7
+17:07:47.074 [0mApp Show[0m at App.uvue:11
+17:07:47.075 [0m应用启动到触发onLaunch耗时: 2278ms[0m
+17:07:47.075 [0m进入页面:/pages/index/index 。[{"创建dom元素个数":"15个","耗时":"13ms"},{"排版":"1次","耗时":"11ms"},{"渲染":"1次","耗时":"18ms"},{"跳转页面到onReady总耗时":"130ms"}][0m
+17:07:47.078 [31m应用【app_sp2】已启动[39m
+17:07:47.079 [0mApp Hide[0m at App.uvue:15
+17:07:47.080 [0mApp Show[0m at App.uvue:11
+17:07:47.083 [0mApp Hide[0m at App.uvue:15
+17:07:47.084 [0mApp Show[0m at App.uvue:11
+17:07:47.085 [0mApp Hide[0m at App.uvue:15
+17:07:47.086 [0mApp Launch[0m at App.uvue:7
+17:07:47.086 [0mApp Show[0m at App.uvue:11
+17:07:47.088 [0m应用启动到触发onLaunch耗时: 245ms[0m
+17:07:47.089 [0m进入页面:/pages/index/index 。[{"创建dom元素个数":"15个","耗时":"15ms"},{"排版":"1次","耗时":"11ms"},{"渲染":"1次","耗时":"20ms"},{"跳转页面到onReady总耗时":"140ms"}][0m
+17:07:47.089 开始差量编译...
+17:07:47.091 正在同步手机端程序文件...
+17:07:47.093 [0mApp Launch[0m at App.uvue:7
+17:07:47.094 [0mApp Show[0m at App.uvue:11
+17:07:47.096 [0m应用启动到触发onLaunch耗时: 237ms[0m
+17:07:47.097 [0m进入页面:/pages/index/index 。[{"创建dom元素个数":"15个","耗时":"14ms"},{"排版":"1次","耗时":"11ms"},{"渲染":"1次","耗时":"18ms"},{"跳转页面到onReady总耗时":"110ms"}][0m
+17:07:47.098 项目 app_sp2 UTS编译完毕。
+17:07:47.099 正在同步手机端程序文件...
+17:07:47.100 [0mApp Launch[0m at App.uvue:7
+17:07:47.100 [0mApp Show[0m at App.uvue:11
+17:07:47.102 [0m应用启动到触发onLaunch耗时: 221ms[0m
+17:07:47.103 [0m进入页面:/pages/index/index 。[{"创建dom元素个数":"15个","耗时":"14ms"},{"排版":"1次","耗时":"11ms"},{"渲染":"1次","耗时":"17ms"},{"跳转页面到onReady总耗时":"117ms"}][0m
+17:07:47.105 [0mApp Hide[0m at App.uvue:15
+17:07:47.105 [0mApp Launch[0m at App.uvue:7
+17:07:47.106 [0mApp Show[0m at App.uvue:11
+17:07:47.108 [0m应用启动到触发onLaunch耗时: 304ms[0m
+17:07:47.109 [0m进入页面:/pages/index/index 。[{"创建dom元素个数":"15个","耗时":"14ms"},{"排版":"1次","耗时":"10ms"},{"渲染":"1次","耗时":"17ms"},{"跳转页面到onReady总耗时":"122ms"}][0m
+17:07:47.110 开始差量编译...
+17:07:47.112 正在同步手机端程序文件...
+17:07:47.113 [0mApp Launch[0m at App.uvue:7
+17:07:47.114 [0mApp Show[0m at App.uvue:11
+17:07:47.115 [0m应用启动到触发onLaunch耗时: 213ms[0m
+17:07:47.116 [0m进入页面:/pages/index/index 。[{"创建dom元素个数":"15个","耗时":"14ms"},{"排版":"1次","耗时":"10ms"},{"渲染":"1次","耗时":"17ms"},{"跳转页面到onReady总耗时":"120ms"}][0m
+17:07:47.117 [33mwarning: 'var value: ByteArray!' is deprecated. Deprecated in Java.[39m
+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 [33mwarning: 'var value: ByteArray!' is deprecated. Deprecated in Java.[39m
+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 [33mwarning: 'fun writeDescriptor(p0: BluetoothGattDescriptor!): Boolean' is deprecated. Deprecated in Java.[39m
+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 [33mwarning: 'var value: ByteArray!' is deprecated. Deprecated in Java.[39m
+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 [33mwarning: 'fun writeCharacteristic(p0: BluetoothGattCharacteristic!): Boolean' is deprecated. Deprecated in Java.[39m
+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 [33m提示:uts插件[sp2-bluetooth]依赖的原生配置或三方SDK在运行至标准基座时不能生效,如需正常调用请使用自定义基座[39m
+17:07:47.167 项目 app_sp2 UTS编译完毕。
+17:07:47.168 正在同步手机端程序文件...
+17:07:47.169 [0mApp Launch[0m at App.uvue:7
+17:07:47.169 [0mApp Show[0m at App.uvue:11
+17:07:47.172 [0m应用启动到触发onLaunch耗时: 211ms[0m
+17:07:47.173 [0m进入页面:/pages/index/index 。[{"创建dom元素个数":"15个","耗时":"14ms"},{"排版":"1次","耗时":"12ms"},{"渲染":"1次","耗时":"18ms"},{"跳转页面到onReady总耗时":"117ms"}][0m
+17:07:47.174 [0m进入页面:/pages/data/data?deviceId=C6:91:6A:18:74:6A&deviceName=NB-C6916A18746A 。[{"创建dom元素个数":"26个","耗时":"17ms"},{"排版":"1次","耗时":"11ms"},{"渲染":"1次","耗时":"18ms"},{"跳转页面到onReady总耗时":"152ms"}][0m
+17:07:47.175 [0mApp Hide[0m at App.uvue:15
+17:07:47.175 [0mApp Launch[0m at App.uvue:7
+17:07:47.177 [0mApp Show[0m at App.uvue:11
+17:07:47.178 [0m应用启动到触发onLaunch耗时: 217ms[0m
+17:07:47.179 [0m进入页面:/pages/index/index 。[{"创建dom元素个数":"15个","耗时":"14ms"},{"排版":"1次","耗时":"11ms"},{"渲染":"1次","耗时":"18ms"},{"跳转页面到onReady总耗时":"123ms"}][0m
+17:07:47.179 [0m进入页面:/pages/data/data?deviceId=C6:91:6A:18:74:6A&deviceName=NB-C6916A18746A 。[{"创建dom元素个数":"26个","耗时":"17ms"},{"排版":"1次","耗时":"13ms"},{"渲染":"1次","耗时":"18ms"},{"跳转页面到onReady总耗时":"166ms"}][0m
+
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 [33m提示:uts插件[sp2-bluetooth]依赖的原生配置或三方SDK在运行至标准基座时不能生效,如需正常调用请使用自定义基座[39m
+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 [33m手机端调试基座版本号为5.06.14660,与本地版本相同,跳过更新[39m
+16:55:11.948 正在建立手机连接...
+16:55:12.987 正在同步手机端程序文件...
+16:55:13.144 同步手机端程序文件成功
+16:55:14.213 正在启动uni-app x调试基座...
+16:55:14.413 [0mApp Launch[0m at App.uvue:7
+16:55:14.416 [0mApp Show[0m at App.uvue:11
+16:55:14.513 [0m应用启动到触发onLaunch耗时: 2278ms[0m
+16:55:14.515 [0m进入页面:/pages/index/index 。[{"创建dom元素个数":"15个","耗时":"13ms"},{"排版":"1次","耗时":"11ms"},{"渲染":"1次","耗时":"18ms"},{"跳转页面到onReady总耗时":"130ms"}][0m
+16:55:15.257 [31m应用【app_sp2】已启动[39m
+16:58:39.489 [0mApp Hide[0m at App.uvue:15
+16:59:01.747 [0mApp Show[0m at App.uvue:11
+16:59:08.193 [0mApp Hide[0m at App.uvue:15
+16:59:47.677 [0mApp Show[0m at App.uvue:11
+17:00:06.599 [0mApp Hide[0m at App.uvue:15
+17:00:29.099 [0mApp Launch[0m at App.uvue:7
+17:00:29.100 [0mApp Show[0m at App.uvue:11
+17:00:29.126 [0m应用启动到触发onLaunch耗时: 245ms[0m
+17:00:29.128 [0m进入页面:/pages/index/index 。[{"创建dom元素个数":"15个","耗时":"15ms"},{"排版":"1次","耗时":"11ms"},{"渲染":"1次","耗时":"20ms"},{"跳转页面到onReady总耗时":"140ms"}][0m
+17:02:09.012 开始差量编译...
+17:02:11.404 正在同步手机端程序文件...
+17:02:12.134 [0mApp Launch[0m at App.uvue:7
+17:02:12.137 [0mApp Show[0m at App.uvue:11
+17:02:12.141 [0m应用启动到触发onLaunch耗时: 237ms[0m
+17:02:12.154 [0m进入页面:/pages/index/index 。[{"创建dom元素个数":"15个","耗时":"14ms"},{"排版":"1次","耗时":"11ms"},{"渲染":"1次","耗时":"18ms"},{"跳转页面到onReady总耗时":"110ms"}][0m
+17:02:13.077 项目 app_sp2 UTS编译完毕。
+17:02:13.084 正在同步手机端程序文件...
+17:02:13.790 [0mApp Launch[0m at App.uvue:7
+17:02:13.793 [0mApp Show[0m at App.uvue:11
+17:02:13.807 [0m应用启动到触发onLaunch耗时: 221ms[0m
+17:02:13.809 [0m进入页面:/pages/index/index 。[{"创建dom元素个数":"15个","耗时":"14ms"},{"排版":"1次","耗时":"11ms"},{"渲染":"1次","耗时":"17ms"},{"跳转页面到onReady总耗时":"117ms"}][0m
+17:04:01.683 [0mApp Hide[0m at App.uvue:15
+17:04:03.924 [0mApp Launch[0m at App.uvue:7
+17:04:03.926 [0mApp Show[0m at App.uvue:11
+17:04:03.955 [0m应用启动到触发onLaunch耗时: 304ms[0m
+17:04:03.956 [0m进入页面:/pages/index/index 。[{"创建dom元素个数":"15个","耗时":"14ms"},{"排版":"1次","耗时":"10ms"},{"渲染":"1次","耗时":"17ms"},{"跳转页面到onReady总耗时":"122ms"}][0m
+17:05:54.863 开始差量编译...
+17:05:57.926 正在同步手机端程序文件...
+17:05:58.667 [0mApp Launch[0m at App.uvue:7
+17:05:58.670 [0mApp Show[0m at App.uvue:11
+17:05:58.682 [0m应用启动到触发onLaunch耗时: 213ms[0m
+17:05:58.684 [0m进入页面:/pages/index/index 。[{"创建dom元素个数":"15个","耗时":"14ms"},{"排版":"1次","耗时":"10ms"},{"渲染":"1次","耗时":"17ms"},{"跳转页面到onReady总耗时":"120ms"}][0m
+17:06:01.307 [33mwarning: 'var value: ByteArray!' is deprecated. Deprecated in Java.[39m
+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 [33mwarning: 'var value: ByteArray!' is deprecated. Deprecated in Java.[39m
+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 [33mwarning: 'fun writeDescriptor(p0: BluetoothGattDescriptor!): Boolean' is deprecated. Deprecated in Java.[39m
+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 [33mwarning: 'var value: ByteArray!' is deprecated. Deprecated in Java.[39m
+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 [33mwarning: 'fun writeCharacteristic(p0: BluetoothGattCharacteristic!): Boolean' is deprecated. Deprecated in Java.[39m
+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 [33m提示:uts插件[sp2-bluetooth]依赖的原生配置或三方SDK在运行至标准基座时不能生效,如需正常调用请使用自定义基座[39m
+17:06:03.302 项目 app_sp2 UTS编译完毕。
+17:06:03.309 正在同步手机端程序文件...
+17:06:03.973 [0mApp Launch[0m at App.uvue:7
+17:06:03.974 [0mApp Show[0m at App.uvue:11
+17:06:03.986 [0m应用启动到触发onLaunch耗时: 211ms[0m
+17:06:04.001 [0m进入页面:/pages/index/index 。[{"创建dom元素个数":"15个","耗时":"14ms"},{"排版":"1次","耗时":"12ms"},{"渲染":"1次","耗时":"18ms"},{"跳转页面到onReady总耗时":"117ms"}][0m
+17:06:23.305 [0m进入页面:/pages/data/data?deviceId=C6:91:6A:18:74:6A&deviceName=NB-C6916A18746A 。[{"创建dom元素个数":"26个","耗时":"17ms"},{"排版":"1次","耗时":"11ms"},{"渲染":"1次","耗时":"18ms"},{"跳转页面到onReady总耗时":"152ms"}][0m
+17:07:08.011 [0mApp Hide[0m at App.uvue:15
+17:07:10.200 [0mApp Launch[0m at App.uvue:7
+17:07:10.201 [0mApp Show[0m at App.uvue:11
+17:07:10.225 [0m应用启动到触发onLaunch耗时: 217ms[0m
+17:07:10.228 [0m进入页面:/pages/index/index 。[{"创建dom元素个数":"15个","耗时":"14ms"},{"排版":"1次","耗时":"11ms"},{"渲染":"1次","耗时":"18ms"},{"跳转页面到onReady总耗时":"123ms"}][0m
+17:07:14.858 [0m进入页面:/pages/data/data?deviceId=C6:91:6A:18:74:6A&deviceName=NB-C6916A18746A 。[{"创建dom元素个数":"26个","耗时":"17ms"},{"排版":"1次","耗时":"13ms"},{"渲染":"1次","耗时":"18ms"},{"跳转页面到onReady总耗时":"166ms"}][0m
+17:09:52.869 [0mApp Hide[0m at App.uvue:15
+17:10:10.104 [0mApp Launch[0m at App.uvue:7
+17:10:10.105 [0mApp Show[0m at App.uvue:11
+17:10:10.122 [0m应用启动到触发onLaunch耗时: 226ms[0m
+17:10:10.123 [0m进入页面:/pages/index/index 。[{"创建dom元素个数":"15个","耗时":"13ms"},{"排版":"1次","耗时":"11ms"},{"渲染":"1次","耗时":"18ms"},{"跳转页面到onReady总耗时":"119ms"}][0m
+17:10:13.312 [0m进入页面:/pages/data/data?deviceId=C6:91:6A:18:74:6A&deviceName=NB-C6916A18746A 。[{"创建dom元素个数":"26个","耗时":"17ms"},{"排版":"1次","耗时":"12ms"},{"渲染":"1次","耗时":"18ms"},{"跳转页面到onReady总耗时":"149ms"}][0m
+17:11:12.674 [0mApp Hide[0m at App.uvue:15
+17:15:57.583 [0mApp Show[0m at App.uvue:11
+17:17:41.191 [0mApp Hide[0m at App.uvue:15
+17:26:19.684 [31m已停止运行...[39m
+
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 @@
+
+
+
+
+
+
+
+
+
+ {{ deviceName }}
+ {{ deviceId }}
+
+
+
+
+ {{ isConnected ? '已连接' : '未连接' }}
+
+
+
+
+
+
+
+
+ 接收到的数据
+ {{ currentTime }}
+
+
+
+ 接收到的字符串:
+
+ {{ receivedText }}
+
+
+
+
+
+
+ 数据记录
+
+
+
+
+ {{ record.time }}
+ {{ record.data }}
+
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
+
+ 请将手机放置到设备附近,并确保蓝牙与必要权限已开启
+
+
+
+
+ BLE
+
+ {{ statusText }}
+
+
+
+ {{ deviceList.length > 0 ? `发现 ${deviceList.length} 台可用设备,点击连接` : emptyText }}
+
+
+
+
+ {{ device.name.length > 0 ? device.name : '未知设备' }}
+ {{ device.deviceId }}
+
+
+
+
+
+
+
+
+
+
+
+
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 0000000000000000000000000000000000000000..b5771e209bb677e2ebd5ff766ad5ee11790f305a
GIT binary patch
literal 4023
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
SYs}WS&%*SCNx89m_
+
+
+
+
+
+
+
+
+
+
+
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