2026-06-23T11:27:35
This commit is contained in:
commit
3b2571d150
22
push.sh
Normal file
22
push.sh
Normal file
@ -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
|
||||
4
src/.gitignore
vendored
Normal file
4
src/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
node_modules/
|
||||
unpackage/
|
||||
.hbuilderx/
|
||||
.DS_Store
|
||||
51
src/App.uvue
Normal file
51
src/App.uvue
Normal file
@ -0,0 +1,51 @@
|
||||
<script setup lang="uts">
|
||||
// #ifdef APP-ANDROID || APP-HARMONY
|
||||
let firstBackTime = 0
|
||||
// #endif
|
||||
|
||||
onLaunch(() => {
|
||||
console.log('App Launch')
|
||||
})
|
||||
|
||||
onAppShow(() => {
|
||||
console.log('App Show')
|
||||
})
|
||||
|
||||
onAppHide(() => {
|
||||
console.log('App Hide')
|
||||
})
|
||||
|
||||
// #ifdef APP-ANDROID || APP-HARMONY
|
||||
onLastPageBackPress(() => {
|
||||
console.log('App LastPageBackPress')
|
||||
if (firstBackTime == 0) {
|
||||
uni.showToast({
|
||||
title: '再按一次退出应用',
|
||||
position: 'bottom',
|
||||
})
|
||||
firstBackTime = Date.now()
|
||||
setTimeout(() => {
|
||||
firstBackTime = 0
|
||||
}, 2000)
|
||||
} else if (Date.now() - firstBackTime < 2000) {
|
||||
firstBackTime = Date.now()
|
||||
uni.exit()
|
||||
}
|
||||
})
|
||||
|
||||
onExit(() => {
|
||||
console.log('App Exit')
|
||||
})
|
||||
// #endif
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/*每个页面公共css */
|
||||
.uni-row {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.uni-column {
|
||||
flex-direction: column;
|
||||
}
|
||||
</style>
|
||||
109
src/android-logcat.log
Normal file
109
src/android-logcat.log
Normal file
@ -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
|
||||
|
||||
20
src/index.html
Normal file
20
src/index.html
Normal file
@ -0,0 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<script>
|
||||
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
|
||||
CSS.supports('top: constant(a)'))
|
||||
document.write(
|
||||
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
|
||||
(coverSupport ? ', viewport-fit=cover' : '') + '" />')
|
||||
</script>
|
||||
<title></title>
|
||||
<!--preload-links-->
|
||||
<!--app-context-->
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"><!--app-html--></div>
|
||||
<script type="module" src="/main"></script>
|
||||
</body>
|
||||
</html>
|
||||
119
src/launch-android.log
Normal file
119
src/launch-android.log
Normal file
@ -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
|
||||
|
||||
9
src/main.uts
Normal file
9
src/main.uts
Normal file
@ -0,0 +1,9 @@
|
||||
import App from './App.uvue'
|
||||
|
||||
import { createSSRApp } from 'vue'
|
||||
export function createApp() {
|
||||
const app = createSSRApp(App)
|
||||
return {
|
||||
app
|
||||
}
|
||||
}
|
||||
64
src/manifest.json
Normal file
64
src/manifest.json
Normal file
@ -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": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
23
src/pages.json
Normal file
23
src/pages.json
Normal file
@ -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": {}
|
||||
}
|
||||
468
src/pages/data/data.uvue
Normal file
468
src/pages/data/data.uvue
Normal file
@ -0,0 +1,468 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<!-- #ifdef APP -->
|
||||
<scroll-view class="page-scroll" :scroll-y="true">
|
||||
<!-- #endif -->
|
||||
<view class="page-content">
|
||||
<view class="header">
|
||||
<text class="title">设备数据</text>
|
||||
<text class="back-btn" @click="goBack">返回</text>
|
||||
</view>
|
||||
|
||||
<view class="device-info">
|
||||
<text class="device-name">{{ deviceName }}</text>
|
||||
<text class="device-id">{{ deviceId }}</text>
|
||||
</view>
|
||||
|
||||
<view class="status-bar">
|
||||
<text class="status-indicator" :class="{ 'connected': isConnected }"></text>
|
||||
<text class="status-text">{{ isConnected ? '已连接' : '未连接' }}</text>
|
||||
</view>
|
||||
|
||||
|
||||
<!-- <view class="debug-panel">
|
||||
<text class="debug-title">蓝牙调试状态</text>
|
||||
<text class="debug-content">{{ debugMessage }}</text>
|
||||
<text class="debug-tip">观察是否出现“当前订阅特征: ...”与“收到 notify: ...”</text>
|
||||
<view class="debug-log-list">
|
||||
<text class="debug-log-item" v-for="(item, index) in debugLogList" :key="index">{{ item }}</text>
|
||||
</view>
|
||||
<view class="debug-actions">
|
||||
<text class="debug-action-btn secondary" @click="runMeasureCommand">执行测量</text>
|
||||
<text class="debug-action-btn secondary" @click="runCalibrationCommand">执行校准</text>
|
||||
<text class="debug-action-btn" @click="readDataOnce">读取一次</text>
|
||||
</view>
|
||||
</view> -->
|
||||
|
||||
|
||||
<view class="data-section">
|
||||
<view class="section-title">
|
||||
<text class="section-title-text">接收到的数据</text>
|
||||
<text class="update-time">{{ currentTime }}</text>
|
||||
</view>
|
||||
|
||||
<view class="received-data">
|
||||
<text class="data-label">接收到的字符串:</text>
|
||||
<view class="received-text-box">
|
||||
<text class="data-content">{{ receivedText }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="data-section">
|
||||
<view class="section-title">
|
||||
<text class="section-title-text">数据记录</text>
|
||||
</view>
|
||||
|
||||
<view class="record-list">
|
||||
<view class="record-item" v-for="(record, index) in recordList" :key="index">
|
||||
<text class="record-time">{{ record.time }}</text>
|
||||
<text class="record-data">{{ record.data }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- #ifdef APP -->
|
||||
</scroll-view>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { onBluetoothDataReceived, onBluetoothDebugMessage, getBluetoothDebugSnapshot, readBluetoothDataOnce, writeBluetoothData, disconnectBluetoothDevice } from '@/uni_modules/sp2-bluetooth'
|
||||
|
||||
type DeviceRecordItem = {
|
||||
time: string,
|
||||
data: string
|
||||
}
|
||||
|
||||
function formatTimePart(value: number): string {
|
||||
return value.toString().padStart(2, '0')
|
||||
}
|
||||
|
||||
const MEASURE_COMMAND = '#RunMeas\n'
|
||||
const CALIBRATION_COMMAND = '#RunCal\n'
|
||||
export default {
|
||||
data() {
|
||||
const recordList: DeviceRecordItem[] = []
|
||||
const debugLogList: string[] = []
|
||||
return {
|
||||
deviceId: '',
|
||||
deviceName: '',
|
||||
currentTime: '--:--:--',
|
||||
receivedText: '等待接收数据...',
|
||||
debugMessage: '等待蓝牙调试状态...',
|
||||
debugLogList: debugLogList,
|
||||
isConnected: false,
|
||||
recordList: recordList
|
||||
}
|
||||
},
|
||||
onLoad(options : UTSJSONObject) {
|
||||
const incomingDeviceId = options['deviceId'] as string | null
|
||||
if (incomingDeviceId != null && incomingDeviceId.length > 0) {
|
||||
this.deviceId = incomingDeviceId
|
||||
}
|
||||
|
||||
const incomingDeviceName = options['deviceName'] as string | null
|
||||
if (incomingDeviceName != null && incomingDeviceName.length > 0) {
|
||||
const decodedDeviceName = decodeURIComponent(incomingDeviceName)
|
||||
if (decodedDeviceName != null && decodedDeviceName.length > 0) {
|
||||
this.deviceName = decodedDeviceName
|
||||
} else {
|
||||
this.deviceName = incomingDeviceName
|
||||
}
|
||||
} else {
|
||||
this.deviceName = '未知设备'
|
||||
}
|
||||
|
||||
this.isConnected = true
|
||||
this.refreshCurrentTime()
|
||||
const debugSnapshot = getBluetoothDebugSnapshot()
|
||||
this.debugMessage = `${this.currentTime} ${debugSnapshot}`
|
||||
this.pushDebugLog(this.debugMessage)
|
||||
this.setupDataListener()
|
||||
},
|
||||
onUnload() {
|
||||
},
|
||||
methods: {
|
||||
goBack() {
|
||||
disconnectBluetoothDevice()
|
||||
uni.navigateBack()
|
||||
},
|
||||
|
||||
refreshCurrentTime() {
|
||||
const now = new Date()
|
||||
const hours = formatTimePart(now.getHours())
|
||||
const minutes = formatTimePart(now.getMinutes())
|
||||
const seconds = formatTimePart(now.getSeconds())
|
||||
this.currentTime = `${hours}:${minutes}:${seconds}`
|
||||
},
|
||||
|
||||
setupDataListener() {
|
||||
const currentDebugMessage = this.debugMessage
|
||||
if (currentDebugMessage.length === 0 || currentDebugMessage.includes('等待蓝牙调试状态')) {
|
||||
this.refreshCurrentTime()
|
||||
this.debugMessage = `${this.currentTime} 已注册蓝牙调试监听`
|
||||
this.pushDebugLog(this.debugMessage)
|
||||
}
|
||||
|
||||
onBluetoothDebugMessage((message: string) => {
|
||||
this.refreshCurrentTime()
|
||||
this.debugMessage = `${this.currentTime} ${message}`
|
||||
this.pushDebugLog(this.debugMessage)
|
||||
})
|
||||
|
||||
onBluetoothDataReceived((data: string, hex: string) => {
|
||||
this.refreshCurrentTime()
|
||||
const normalizedData = this.normalizeReceivedText(data)
|
||||
const timeStr = this.currentTime
|
||||
const dataStr = normalizedData
|
||||
|
||||
if (this.receivedText === '等待接收数据...') {
|
||||
this.receivedText = normalizedData
|
||||
} else {
|
||||
this.receivedText = `${this.receivedText}\n${normalizedData}`
|
||||
}
|
||||
|
||||
if (this.recordList.length >= 50) {
|
||||
this.recordList.pop()
|
||||
}
|
||||
const recordItem: DeviceRecordItem = { time: timeStr, data: dataStr }
|
||||
this.recordList.unshift(recordItem)
|
||||
})
|
||||
},
|
||||
|
||||
pushDebugLog(message: string) {
|
||||
if (this.debugLogList.length >= 12) {
|
||||
this.debugLogList.pop()
|
||||
}
|
||||
this.debugLogList.unshift(message)
|
||||
},
|
||||
|
||||
normalizeReceivedText(data: string): string {
|
||||
const trimmedData = data.replace(/\r/g, '').replace(/\n/g, '').trim()
|
||||
if (trimmedData.endsWith(';')) {
|
||||
return trimmedData.substring(0, trimmedData.length - 1)
|
||||
}
|
||||
return trimmedData
|
||||
},
|
||||
|
||||
readDataOnce() {
|
||||
this.refreshCurrentTime()
|
||||
this.debugMessage = `${this.currentTime} 正在手动读取一次`
|
||||
this.pushDebugLog(this.debugMessage)
|
||||
readBluetoothDataOnce()
|
||||
},
|
||||
|
||||
triggerCommand(command: string, label: string) {
|
||||
this.refreshCurrentTime()
|
||||
this.debugMessage = `${this.currentTime} 正在发送${label}命令`
|
||||
this.pushDebugLog(this.debugMessage)
|
||||
writeBluetoothData(command)
|
||||
this.refreshCurrentTime()
|
||||
this.debugMessage = `${this.currentTime} ${label}命令已发送,等待 FFF1 通知数据`
|
||||
this.pushDebugLog(this.debugMessage)
|
||||
},
|
||||
|
||||
runMeasureCommand() {
|
||||
this.triggerCommand(MEASURE_COMMAND, '测量')
|
||||
},
|
||||
|
||||
runCalibrationCommand() {
|
||||
this.triggerCommand(CALIBRATION_COMMAND, '校准')
|
||||
},
|
||||
|
||||
sendData() {
|
||||
const testData = "Hello BLE"
|
||||
writeBluetoothData(testData)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
background-color: #f5f5f5;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.page-scroll {
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.page-content {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.header {
|
||||
width: 100%;
|
||||
height: 44px;
|
||||
background: #007aff;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 15px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.title {
|
||||
color: #ffffff;
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
color: #ffffff;
|
||||
font-size: 14px;
|
||||
padding: 5px 10px;
|
||||
border: 1px solid #ffffff;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.device-info {
|
||||
width: 100%;
|
||||
padding: 15px 20px;
|
||||
background-color: #ffffff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
}
|
||||
|
||||
.device-name {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #333333;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.device-id {
|
||||
font-size: 12px;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.status-bar {
|
||||
width: 100%;
|
||||
padding: 10px 20px;
|
||||
background-color: #ffffff;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
}
|
||||
|
||||
.status-indicator {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
background-color: #999999;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.connected {
|
||||
background-color: #4cd964;
|
||||
}
|
||||
|
||||
.status-text {
|
||||
font-size: 14px;
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
.debug-panel {
|
||||
width: 100%;
|
||||
padding: 15px 20px;
|
||||
background-color: #fff8e8;
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.debug-title {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #8c5a00;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.debug-content {
|
||||
font-size: 12px;
|
||||
color: #8c5a00;
|
||||
}
|
||||
|
||||
.debug-tip {
|
||||
font-size: 11px;
|
||||
color: #a7771f;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.debug-log-list {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.debug-log-item {
|
||||
font-size: 11px;
|
||||
color: #8c5a00;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.debug-actions {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.debug-action-btn {
|
||||
padding: 6px 12px;
|
||||
border-radius: 999px;
|
||||
background-color: #8c5a00;
|
||||
color: #ffffff;
|
||||
font-size: 12px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.secondary {
|
||||
background-color: #007aff;
|
||||
}
|
||||
|
||||
.data-section {
|
||||
width: 100%;
|
||||
padding: 15px 20px;
|
||||
background-color: #ffffff;
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.section-title-text {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.update-time {
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.received-data {
|
||||
width: 100%;
|
||||
margin-bottom: 15px;
|
||||
padding: 12px;
|
||||
background-color: #f8f8f8;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.received-text-box {
|
||||
width: 100%;
|
||||
min-height: 96px;
|
||||
padding: 14px 16px;
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #d9e2ef;
|
||||
border-radius: 10px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.data-label {
|
||||
width: 100%;
|
||||
font-size: 12px;
|
||||
color: #999999;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.data-content {
|
||||
width: 100%;
|
||||
font-size: 14px;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.record-list {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: 300px;
|
||||
}
|
||||
|
||||
.record-item {
|
||||
width: 100%;
|
||||
padding: 10px 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.record-time {
|
||||
font-size: 12px;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.record-data {
|
||||
font-size: 12px;
|
||||
color: #666666;
|
||||
}
|
||||
</style>
|
||||
274
src/pages/index/index.uvue
Normal file
274
src/pages/index/index.uvue
Normal file
@ -0,0 +1,274 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<!-- #ifdef APP -->
|
||||
<scroll-view class="page-scroll" :scroll-y="true">
|
||||
<!-- #endif -->
|
||||
<view class="content">
|
||||
<view class="header">
|
||||
<text class="title">连接设备</text>
|
||||
<text class="right" @click="reloadDevices">重新扫描</text>
|
||||
</view>
|
||||
|
||||
<view class="tip-container">
|
||||
<text class="tip-text">请将手机放置到设备附近,并确保蓝牙与必要权限已开启</text>
|
||||
</view>
|
||||
|
||||
<view class="loading-area">
|
||||
<view class="bluetooth-icon">
|
||||
<text class="bluetooth-symbol">BLE</text>
|
||||
</view>
|
||||
<text class="loading-text">{{ statusText }}</text>
|
||||
</view>
|
||||
|
||||
<view class="found-tip">
|
||||
<text class="found-text">{{ deviceList.length > 0 ? `发现 ${deviceList.length} 台可用设备,点击连接` : emptyText }}</text>
|
||||
</view>
|
||||
|
||||
<view class="device-list">
|
||||
<view class="device-card" :class="{ 'device-card-connecting': connectingDeviceId.length > 0 }" v-for="device in deviceList" :key="device.deviceId" @click="connectDevice(device)">
|
||||
<text class="device-name">{{ device.name.length > 0 ? device.name : '未知设备' }}</text>
|
||||
<text class="device-mac">{{ device.deviceId }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- #ifdef APP -->
|
||||
</scroll-view>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { startBluetoothScan, stopBluetoothScan, connectBluetoothDevice, disconnectBluetoothDevice, type BluetoothDeviceItem } from '@/uni_modules/sp2-bluetooth'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
deviceList: [] as BluetoothDeviceItem[],
|
||||
statusText: '正在扫描附近 BLE 设备',
|
||||
emptyText: '暂未发现设备,请确认设备已开机并靠近手机',
|
||||
connectingDeviceId: ''
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
this.initBluetooth()
|
||||
},
|
||||
onShow() {
|
||||
this.connectingDeviceId = ''
|
||||
},
|
||||
onUnload() {
|
||||
stopBluetoothScan()
|
||||
disconnectBluetoothDevice()
|
||||
},
|
||||
methods: {
|
||||
initBluetooth() {
|
||||
this.connectingDeviceId = ''
|
||||
this.deviceList = []
|
||||
this.statusText = '正在扫描附近 BLE 设备'
|
||||
this.emptyText = '暂未发现设备,请确认设备已开机并靠近手机'
|
||||
disconnectBluetoothDevice()
|
||||
stopBluetoothScan()
|
||||
startBluetoothScan((device : BluetoothDeviceItem) => {
|
||||
if (!device.name.startsWith('NB-')) {
|
||||
return
|
||||
}
|
||||
|
||||
let exists = false
|
||||
for (let i = 0; i < this.deviceList.length; i++) {
|
||||
if (this.deviceList[i].deviceId === device.deviceId) {
|
||||
exists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!exists) {
|
||||
this.deviceList.push(device)
|
||||
this.statusText = '已发现 BLE 设备,继续扫描中'
|
||||
}
|
||||
}, (message : string) => {
|
||||
this.statusText = '扫描失败'
|
||||
this.emptyText = message
|
||||
uni.showToast({
|
||||
title: message,
|
||||
icon: 'none'
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
reloadDevices() {
|
||||
if (this.connectingDeviceId.length > 0) {
|
||||
return
|
||||
}
|
||||
|
||||
this.initBluetooth()
|
||||
},
|
||||
|
||||
connectDevice(device : BluetoothDeviceItem) {
|
||||
if (this.connectingDeviceId.length > 0) {
|
||||
return
|
||||
}
|
||||
|
||||
this.connectingDeviceId = device.deviceId
|
||||
this.statusText = '正在连接设备'
|
||||
stopBluetoothScan()
|
||||
|
||||
connectBluetoothDevice(device.deviceId, () => {
|
||||
this.statusText = '设备连接成功'
|
||||
setTimeout(() => {
|
||||
this.connectingDeviceId = ''
|
||||
uni.navigateTo({
|
||||
url: `/pages/data/data?deviceId=${device.deviceId}&deviceName=${encodeURIComponent(device.name)}`
|
||||
})
|
||||
}, 500)
|
||||
}, (message : string) => {
|
||||
this.statusText = '连接失败'
|
||||
this.connectingDeviceId = ''
|
||||
uni.showToast({
|
||||
title: message,
|
||||
icon: 'none'
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
background-color: #ffffff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.page-scroll {
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.content {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.header {
|
||||
width: 100%;
|
||||
height: 44px;
|
||||
background: #007aff;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 15px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.title {
|
||||
color: #ffffff;
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.right {
|
||||
color: #ffffff;
|
||||
font-size: 14px;
|
||||
padding: 5px 10px;
|
||||
border: 1px solid #ffffff;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.tip-container {
|
||||
width: 100%;
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.tip-text {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
font-size: 15px;
|
||||
color: #333333;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.loading-area {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.bluetooth-icon {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
border-radius: 60px;
|
||||
background-color: #e8f2ff;
|
||||
border: 2px solid #007aff;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.bluetooth-symbol {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 2px;
|
||||
color: #007aff;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
margin-top: 15px;
|
||||
font-size: 16px;
|
||||
color: #007aff;
|
||||
}
|
||||
|
||||
.found-tip {
|
||||
width: 100%;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.found-text {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
.device-list {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-bottom: 24px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.device-card {
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
background-color: #f0f0f0;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.device-card-connecting {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.device-name {
|
||||
font-size: 18px;
|
||||
color: #333333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.device-mac {
|
||||
margin-top: 8px;
|
||||
font-size: 14px;
|
||||
color: #999999;
|
||||
}
|
||||
</style>
|
||||
7
src/platformConfig.json
Normal file
7
src/platformConfig.json
Normal file
@ -0,0 +1,7 @@
|
||||
|
||||
// 参考链接 https://doc.dcloud.net.cn/uni-app-x/tutorial/ls-plugin.html#setting
|
||||
{
|
||||
"targets": [
|
||||
"APP-ANDROID"
|
||||
]
|
||||
}
|
||||
BIN
src/static/logo.png
Normal file
BIN
src/static/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.9 KiB |
76
src/uni.scss
Normal file
76
src/uni.scss
Normal file
@ -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;
|
||||
50
src/uni_modules/sp2-bluetooth/package.json
Normal file
50
src/uni_modules/sp2-bluetooth/package.json
Normal file
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="io.dcloud.uni_modules.sp2Bluetooth">
|
||||
|
||||
<uses-feature
|
||||
android:name="android.hardware.bluetooth_le"
|
||||
android:required="false" />
|
||||
|
||||
<uses-permission
|
||||
android:name="android.permission.BLUETOOTH"
|
||||
android:maxSdkVersion="30" />
|
||||
<uses-permission
|
||||
android:name="android.permission.BLUETOOTH_ADMIN"
|
||||
android:maxSdkVersion="30" />
|
||||
<uses-permission
|
||||
android:name="android.permission.ACCESS_FINE_LOCATION"
|
||||
android:maxSdkVersion="30" />
|
||||
<uses-permission
|
||||
android:name="android.permission.BLUETOOTH_SCAN"
|
||||
android:usesPermissionFlags="neverForLocation" />
|
||||
<uses-permission
|
||||
android:name="android.permission.BLUETOOTH_CONNECT" />
|
||||
|
||||
</manifest>
|
||||
972
src/uni_modules/sp2-bluetooth/utssdk/app-android/hybrid.kt
Normal file
972
src/uni_modules/sp2-bluetooth/utssdk/app-android/hybrid.kt
Normal file
@ -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<Pair<BluetoothGattService, BluetoothGattCharacteristic>>())
|
||||
private val pendingDebugMessages = Collections.synchronizedList(ArrayList<String>())
|
||||
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<Pair<String, String>>())
|
||||
|
||||
private var hasConnectedOnce = false
|
||||
private val discoveredDeviceIds = Collections.synchronizedSet(HashSet<String>())
|
||||
|
||||
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<ScanResult>) {
|
||||
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<String> {
|
||||
val permissions = mutableListOf<String>()
|
||||
|
||||
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<String> {
|
||||
val permissions = mutableListOf<String>()
|
||||
|
||||
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<Pair<BluetoothGattService, BluetoothGattCharacteristic>> {
|
||||
val services = gatt.services ?: return emptyList()
|
||||
val preferredServiceUuid = SERVICE_UUID.lowercase()
|
||||
val preferredCharacteristicUuid = READ_CHARACTERISTIC_UUID.lowercase()
|
||||
val preferredTargets = ArrayList<Pair<BluetoothGattService, BluetoothGattCharacteristic>>()
|
||||
val fallbackTargets = ArrayList<Pair<BluetoothGattService, BluetoothGattCharacteristic>>()
|
||||
|
||||
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<BluetoothGattService, BluetoothGattCharacteristic>? {
|
||||
val services = gatt.services ?: return null
|
||||
val preferredServiceUuid = SERVICE_UUID.lowercase()
|
||||
val preferredCharacteristicUuid = WRITE_CHARACTERISTIC_UUID.lowercase()
|
||||
var fallbackTarget: Pair<BluetoothGattService, BluetoothGattCharacteristic>? = 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<BluetoothGattService, BluetoothGattCharacteristic>? {
|
||||
val services = gatt.services ?: return null
|
||||
val preferredServiceUuid = SERVICE_UUID.lowercase()
|
||||
val preferredCharacteristicUuid = READ_CHARACTERISTIC_UUID.lowercase()
|
||||
var fallbackTarget: Pair<BluetoothGattService, BluetoothGattCharacteristic>? = 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<Pair<BluetoothGattService, BluetoothGattCharacteristic>>()
|
||||
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<Pair<String, String>>()
|
||||
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<String>()
|
||||
synchronized(pendingDebugMessages) {
|
||||
if (pendingDebugMessages.isEmpty()) {
|
||||
return
|
||||
}
|
||||
messages.addAll(pendingDebugMessages)
|
||||
pendingDebugMessages.clear()
|
||||
}
|
||||
|
||||
mainHandler.post {
|
||||
for (message in messages) {
|
||||
callback(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
101
src/uni_modules/sp2-bluetooth/utssdk/app-android/index.uts
Normal file
101
src/uni_modules/sp2-bluetooth/utssdk/app-android/index.uts
Normal file
@ -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)
|
||||
}
|
||||
21
src/uni_modules/sp2-bluetooth/utssdk/interface.uts
Normal file
21
src/uni_modules/sp2-bluetooth/utssdk/interface.uts
Normal file
@ -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
|
||||
388
src/utils/ecBLE.js
Normal file
388
src/utils/ecBLE.js
Normal file
@ -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,
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user