ECG
This commit is contained in:
parent
0258157633
commit
cb28adca69
|
|
@ -0,0 +1,88 @@
|
|||
# MAC地址格式说明
|
||||
|
||||
## 问题解决
|
||||
|
||||
您遇到的错误 "60-E9-AA-30-0B-0A is not a valid Bluetooth address" 是因为MAC地址格式问题。
|
||||
|
||||
## 正确的MAC地址格式
|
||||
|
||||
### 支持的格式:
|
||||
1. **冒号分隔符**(推荐):`60:E9:AA:30:8B:0A`
|
||||
2. **连字符分隔符**:`60-E9-AA-30-8B-0A`
|
||||
|
||||
### 您的电脑MAC地址:
|
||||
- **正确格式**:`60:E9:AA:30:8B:0A`
|
||||
- **注意**:您之前输入的是 `60-E9-AA-30-0B-0A`,其中 `0B` 应该是 `8B`
|
||||
|
||||
## 常见MAC地址格式错误
|
||||
|
||||
### 1. 分隔符错误
|
||||
- ❌ 错误:`60.E9.AA.30.8B.0A`(点号分隔符)
|
||||
- ❌ 错误:`60 E9 AA 30 8B 0A`(空格分隔符)
|
||||
- ✅ 正确:`60:E9:AA:30:8B:0A`(冒号分隔符)
|
||||
|
||||
### 2. 字符错误
|
||||
- ❌ 错误:`60-E9-AA-30-0B-0A`(0B 应该是 8B)
|
||||
- ✅ 正确:`60:E9:AA:30:8B:0A`
|
||||
|
||||
### 3. 长度错误
|
||||
- ❌ 错误:`60:E9:AA:30:8B`(缺少两位)
|
||||
- ❌ 错误:`60:E9:AA:30:8B:0A:FF`(多出两位)
|
||||
- ✅ 正确:`60:E9:AA:30:8B:0A`(6组,每组2位)
|
||||
|
||||
## 如何获取正确的MAC地址
|
||||
|
||||
### Windows系统:
|
||||
1. 打开命令提示符(cmd)
|
||||
2. 输入:`ipconfig /all`
|
||||
3. 查找"物理地址"或"Physical Address"
|
||||
4. 格式类似:`60-E9-AA-30-8B-0A`
|
||||
|
||||
### 转换为冒号格式:
|
||||
- 将连字符 `-` 替换为冒号 `:`
|
||||
- `60-E9-AA-30-8B-0A` → `60:E9:AA:30:8B:0A`
|
||||
|
||||
## 测试步骤
|
||||
|
||||
### 1. 确认MAC地址
|
||||
1. 在Windows命令提示符中输入:`ipconfig /all`
|
||||
2. 找到您的蓝牙适配器的物理地址
|
||||
3. 确认地址格式正确
|
||||
|
||||
### 2. 使用应用连接
|
||||
1. 启动应用
|
||||
2. **长按"连接蓝牙"按钮**
|
||||
3. 输入正确的MAC地址:`60:E9:AA:30:8B:0A`
|
||||
4. 点击"连接"
|
||||
|
||||
### 3. 验证连接
|
||||
1. 观察连接状态
|
||||
2. 查看日志信息
|
||||
3. 如果仍有问题,尝试扫描连接
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q: 为什么需要冒号分隔符?
|
||||
A: Android蓝牙API更推荐使用冒号分隔符,兼容性更好。
|
||||
|
||||
### Q: 我的MAC地址是连字符格式怎么办?
|
||||
A: 应用现在支持两种格式,但推荐使用冒号格式。
|
||||
|
||||
### Q: 连接仍然失败怎么办?
|
||||
A:
|
||||
1. 确认MAC地址正确
|
||||
2. 检查蓝牙权限
|
||||
3. 确保设备在范围内
|
||||
4. 尝试扫描连接方式
|
||||
|
||||
## 调试技巧
|
||||
|
||||
### 查看日志:
|
||||
在Android Studio的Logcat中查看:
|
||||
- `BluetoothManager` 标签的详细错误信息
|
||||
- 确认MAC地址格式验证结果
|
||||
|
||||
### 测试连接:
|
||||
1. 先使用扫描功能找到设备
|
||||
2. 记录正确的MAC地址
|
||||
3. 再使用直接连接功能
|
||||
|
|
@ -0,0 +1,173 @@
|
|||
# 蓝牙指令使用说明
|
||||
|
||||
## 功能概述
|
||||
本应用支持通过蓝牙连接设备后,发送指令让设备开始发送单导联ECG数据,然后实时显示ECG曲线图。专门优化用于处理单导联蓝牙实时数据,不再读取12导联文件数据。
|
||||
|
||||
## 支持的协议
|
||||
|
||||
### 轻迅蓝牙通信协议 V1.0.1
|
||||
本应用完全支持轻迅蓝牙通信协议,包括:
|
||||
- 数据服务 UUID: `6e400001-b5a3-f393-e0a9-68716563686f`
|
||||
- Write Characteristic UUID: `6e400002-b5a3-f393-e0a9-68716563686f`
|
||||
- Notify Characteristic UUID: `6e400003-b5a3-f393-e0a9-68716563686f`
|
||||
|
||||
## 使用步骤
|
||||
|
||||
### 1. 连接蓝牙设备
|
||||
- 点击"连接蓝牙"按钮
|
||||
- 选择您的ECG设备
|
||||
- 等待连接成功
|
||||
|
||||
### 2. 发送指令
|
||||
连接成功后,点击"发送指令"按钮,会弹出指令选择对话框:
|
||||
|
||||
#### 指令类型选择:
|
||||
- **轻迅协议指令**:使用轻迅蓝牙通信协议的标准指令
|
||||
- **通用指令**:使用通用的字符串或十六进制指令
|
||||
- **自定义指令**:输入自定义指令
|
||||
|
||||
### 3. 轻迅协议指令
|
||||
选择"轻迅协议指令"后,会显示以下选项:
|
||||
|
||||
#### 采集控制:
|
||||
- **开启采集**:发送功能码 `0x0001` 开启数据采集
|
||||
- **停止采集**:发送功能码 `0x0001` 停止数据采集
|
||||
|
||||
#### 设备信息:
|
||||
- **查询设备信息**:发送功能码 `0x0000` 查询设备参数
|
||||
- **查询电量**:发送功能码 `0x0002` 查询设备电量
|
||||
|
||||
#### 滤波控制:
|
||||
- **工频滤波开关**:发送功能码 `0x000A` 控制工频滤波
|
||||
|
||||
### 4. 通用指令
|
||||
选择"通用指令"后,会显示以下选项:
|
||||
- **开始发送数据**:让设备开始发送数据流
|
||||
- **停止发送数据**:让设备停止发送数据
|
||||
- **开始ECG测量**:开始ECG信号测量
|
||||
- **停止ECG测量**:停止ECG信号测量
|
||||
|
||||
#### 常用指令格式:
|
||||
- **字符串指令**:`START_DATA`, `STOP`, `BEGIN`, `END`
|
||||
- **十六进制指令**:`0x01`, `0x02`, `0x53 0x54 0x41 0x52 0x54`
|
||||
|
||||
### 5. 查看实时图表
|
||||
发送指令成功后,应用会自动启动数据处理并立即开始绘制单导联ECG图表:
|
||||
- **自动启动**:发送指令后自动启动数据处理,无需手动点击"启动程序"
|
||||
- **实时显示**:单导联数据接收后立即更新图表,无需等待
|
||||
- **单导联处理**:专门处理单导联ECG数据,主要显示主导联信号
|
||||
- **ECG节律视图**:显示10秒的连续单导联信号
|
||||
- **ECG波形视图**:显示2.5秒的放大单导联信号
|
||||
|
||||
### 6. 其他功能
|
||||
- **陷波滤波**:开启/关闭50Hz陷波滤波器
|
||||
- **清空数据**:清空所有图表数据
|
||||
- **停止程序**:停止数据处理
|
||||
|
||||
## 轻迅协议指令详解
|
||||
|
||||
### 采集控制指令
|
||||
|
||||
#### 开启采集 (功能码: 0x0001)
|
||||
```
|
||||
数据包格式: [功能码(2字节)] [数据长度(2字节)] [采集开关(1字节)] [时间戳(8字节)] [CRC16(2字节)]
|
||||
示例: 01 00 09 00 01 [时间戳8字节] [CRC16 2字节]
|
||||
```
|
||||
|
||||
**时间戳选项:**
|
||||
- **立即开启**:时间戳设为0
|
||||
- **延迟开启**:时间戳设为未来时间(毫秒)
|
||||
- **指定时间戳**:自定义时间戳
|
||||
|
||||
#### 停止采集 (功能码: 0x0001)
|
||||
```
|
||||
数据包格式: [功能码(2字节)] [数据长度(2字节)] [采集开关(1字节)] [时间戳(8字节)] [CRC16(2字节)]
|
||||
示例: 01 00 09 00 00 [时间戳8字节] [CRC16 2字节]
|
||||
```
|
||||
|
||||
### 设备信息指令
|
||||
|
||||
#### 查询设备信息 (功能码: 0x0000)
|
||||
```
|
||||
数据包格式: [功能码(2字节)] [数据长度(2字节)] [CRC16(2字节)]
|
||||
示例: 00 00 00 00 [CRC16 2字节]
|
||||
```
|
||||
|
||||
#### 查询电量 (功能码: 0x0002)
|
||||
```
|
||||
数据包格式: [功能码(2字节)] [数据长度(2字节)] [CRC16(2字节)]
|
||||
示例: 02 00 00 00 [CRC16 2字节]
|
||||
```
|
||||
|
||||
### 滤波控制指令
|
||||
|
||||
#### 工频滤波开关 (功能码: 0x000A)
|
||||
```
|
||||
数据包格式: [功能码(2字节)] [数据长度(2字节)] [开关状态(1字节)] [CRC16(2字节)]
|
||||
开启示例: 0A 00 01 00 01 [CRC16 2字节]
|
||||
关闭示例: 0A 00 01 00 00 [CRC16 2字节]
|
||||
```
|
||||
|
||||
## 通用指令示例
|
||||
|
||||
### 开始数据流
|
||||
```
|
||||
START_DATA
|
||||
START
|
||||
0x01
|
||||
```
|
||||
|
||||
### 停止数据流
|
||||
```
|
||||
STOP_DATA
|
||||
STOP
|
||||
0x02
|
||||
```
|
||||
|
||||
### 开始ECG测量
|
||||
```
|
||||
START_ECG
|
||||
ECG_START
|
||||
0x03
|
||||
```
|
||||
|
||||
### 停止ECG测量
|
||||
```
|
||||
STOP_ECG
|
||||
ECG_STOP
|
||||
0x04
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
1. 确保蓝牙设备已正确连接
|
||||
2. 根据您的设备协议选择合适的指令类型
|
||||
3. 轻迅协议设备会自动计算CRC16校验,确保数据完整性
|
||||
4. 如果指令发送失败,请检查设备是否支持该指令
|
||||
5. 可以尝试不同的指令格式(字符串或十六进制)
|
||||
|
||||
## 调试信息
|
||||
应用会在状态栏显示详细的调试信息,包括:
|
||||
- 蓝牙连接状态
|
||||
- 指令发送结果(包括轻迅协议数据包内容)
|
||||
- 数据接收情况
|
||||
- 图表更新状态
|
||||
|
||||
## 轻迅协议特性
|
||||
- **自动CRC16校验**:所有指令包都包含CRC16-CCITT-FALSE校验
|
||||
- **时间戳同步**:支持指定时间戳进行同步采集
|
||||
- **工频滤波控制**:可动态开启/关闭工频滤波算法
|
||||
- **设备信息查询**:支持查询设备状态和电量信息
|
||||
- **自动数据处理**:发送指令后自动启动数据处理,无需手动操作
|
||||
- **实时图表更新**:单导联数据接收后立即更新图表,提供最佳用户体验
|
||||
- **单导联优化**:专门优化处理单导联ECG数据,不再处理12导联文件数据
|
||||
|
||||
## 测试功能
|
||||
- **点击"启动程序"按钮**:立即生成测试数据并显示图表,用于验证图表显示功能
|
||||
- **长按"启动程序"按钮**:生成更复杂的模拟ECG数据
|
||||
- **蓝牙数据测试**:当接收到蓝牙数据但原生解析器无法解析时,会自动生成测试数据
|
||||
|
||||
### 测试数据说明
|
||||
应用会生成模拟的单导联ECG波形,包含:
|
||||
- P波、QRS复合波、T波等典型ECG特征
|
||||
- 适当的噪声模拟真实信号
|
||||
- 500个数据点用于充分显示波形
|
||||
|
|
@ -2,18 +2,24 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<!-- 蓝牙权限 -->
|
||||
<!-- 基础蓝牙权限 (Android 11及以下) -->
|
||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
|
||||
|
||||
<!-- Android 12+ 新蓝牙权限 -->
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
|
||||
android:usesPermissionFlags="neverForLocation" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
||||
|
||||
<!-- 位置权限 (所有Android版本都需要用于蓝牙扫描) -->
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
|
||||
<!-- 蓝牙功能声明 -->
|
||||
<uses-feature android:name="android.hardware.bluetooth" android:required="true" />
|
||||
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true" />
|
||||
<application
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -101,7 +101,45 @@ class DataManager(private val nativeCallback: NativeMethodCallback) {
|
|||
// 应用流式数据处理
|
||||
processStreamingData(packets)
|
||||
} else {
|
||||
Log.w("DataManager", "没有解析出有效数据包")
|
||||
Log.w("DataManager", "没有解析出有效数据包,尝试生成测试数据")
|
||||
|
||||
// 如果原生解析器没有解析出数据包,生成一些测试数据来验证图表显示
|
||||
generateTestDataForChart()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成测试数据用于验证图表显示
|
||||
*/
|
||||
private fun generateTestDataForChart() {
|
||||
try {
|
||||
Log.d("DataManager", "生成测试数据用于验证图表显示")
|
||||
|
||||
// 生成模拟的单导联ECG数据
|
||||
val testData = mutableListOf<Float>()
|
||||
val sampleCount = 100 // 生成100个样本点
|
||||
|
||||
for (i in 0 until sampleCount) {
|
||||
val t = i.toFloat() / 10f // 时间参数
|
||||
val value = (Math.sin(t * 2 * Math.PI) * 100 +
|
||||
Math.sin(t * 4 * Math.PI) * 50 +
|
||||
Math.sin(t * 8 * Math.PI) * 25).toFloat()
|
||||
testData.add(value)
|
||||
}
|
||||
|
||||
Log.d("DataManager", "生成测试数据: ${testData.size} 个样本点")
|
||||
Log.d("DataManager", "测试数据前5个值: ${testData.take(5).joinToString(", ")}")
|
||||
|
||||
// 发送测试数据到图表
|
||||
if (realTimeCallback != null) {
|
||||
realTimeCallback!!.onRawDataAvailable(0, testData)
|
||||
Log.d("DataManager", "已发送测试数据到图表")
|
||||
} else {
|
||||
Log.e("DataManager", "realTimeCallback 为空,无法发送测试数据")
|
||||
}
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.e("DataManager", "生成测试数据失败: ${e.message}", e)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -178,7 +216,7 @@ class DataManager(private val nativeCallback: NativeMethodCallback) {
|
|||
}
|
||||
|
||||
/**
|
||||
* 立即发送原始数据到图表显示
|
||||
* 立即发送原始数据到图表显示 - 专门处理单导联蓝牙数据
|
||||
*/
|
||||
private fun sendRawDataToCharts(packets: List<SensorData>) {
|
||||
if (packets.isEmpty()) {
|
||||
|
|
@ -186,84 +224,97 @@ class DataManager(private val nativeCallback: NativeMethodCallback) {
|
|||
return
|
||||
}
|
||||
|
||||
Log.d("DataManager", "立即发送原始数据到图表,处理 ${packets.size} 个数据包")
|
||||
Log.d("DataManager", "立即发送单导联蓝牙数据到图表,处理 ${packets.size} 个数据包")
|
||||
|
||||
// 直接处理原始数据包,不进行通道映射
|
||||
// 专门处理单导联数据包
|
||||
for ((packetIndex, packet) in packets.withIndex()) {
|
||||
Log.d("DataManager", "处理数据包 $packetIndex: 数据类型=${packet.getDataType()}")
|
||||
Log.d("DataManager", "处理单导联数据包 $packetIndex: 数据类型=${packet.getDataType()}")
|
||||
|
||||
val channelData = packet.getChannelData()
|
||||
if (channelData != null) {
|
||||
Log.d("DataManager", "数据包 $packetIndex 有 ${channelData.size} 个通道")
|
||||
for ((channelIndex, channel) in channelData.withIndex()) {
|
||||
Log.d("DataManager", "通道 $channelIndex 数据长度: ${channel.size}")
|
||||
if (channel.isNotEmpty()) {
|
||||
Log.d("DataManager", "通道 $channelIndex 前3个值: ${channel.take(3).joinToString(", ")}")
|
||||
// 立即发送原始数据到图表
|
||||
if (realTimeCallback != null) {
|
||||
realTimeCallback!!.onRawDataAvailable(channelIndex, channel)
|
||||
Log.d("DataManager", "已发送原始数据到通道 $channelIndex,数据长度: ${channel.size}")
|
||||
} else {
|
||||
Log.e("DataManager", "realTimeCallback 为空,无法发送数据")
|
||||
}
|
||||
if (channelData != null && channelData.isNotEmpty()) {
|
||||
Log.d("DataManager", "单导联数据包 $packetIndex 有 ${channelData.size} 个通道")
|
||||
|
||||
// 对于单导联数据,我们主要关注第一个通道(通常是导联I或II)
|
||||
val primaryChannel = channelData[0]
|
||||
if (primaryChannel.isNotEmpty()) {
|
||||
Log.d("DataManager", "单导联主通道数据长度: ${primaryChannel.size}")
|
||||
Log.d("DataManager", "单导联主通道前3个值: ${primaryChannel.take(3).joinToString(", ")}")
|
||||
|
||||
// 立即发送单导联数据到图表
|
||||
if (realTimeCallback != null) {
|
||||
realTimeCallback!!.onRawDataAvailable(0, primaryChannel) // 使用通道0表示主导联
|
||||
Log.d("DataManager", "已发送单导联数据到图表,数据长度: ${primaryChannel.size}")
|
||||
} else {
|
||||
Log.w("DataManager", "通道 $channelIndex 数据为空")
|
||||
Log.e("DataManager", "realTimeCallback 为空,无法发送单导联数据")
|
||||
}
|
||||
|
||||
// 如果有其他通道(如参考通道),也发送
|
||||
if (channelData.size > 1) {
|
||||
for (i in 1 until channelData.size) {
|
||||
val additionalChannel = channelData[i]
|
||||
if (additionalChannel.isNotEmpty()) {
|
||||
Log.d("DataManager", "发送附加通道 $i 数据,长度: ${additionalChannel.size}")
|
||||
realTimeCallback?.onRawDataAvailable(i, additionalChannel)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.w("DataManager", "单导联主通道数据为空")
|
||||
}
|
||||
} else {
|
||||
Log.w("DataManager", "数据包 $packetIndex 没有通道数据")
|
||||
Log.w("DataManager", "单导联数据包 $packetIndex 没有通道数据")
|
||||
}
|
||||
}
|
||||
|
||||
// 添加调试信息
|
||||
Log.d("DataManager", "单导联蓝牙数据发送完成,总共处理了 ${packets.size} 个数据包")
|
||||
}
|
||||
|
||||
/**
|
||||
* 流式数据处理 - 将数据包按通道合并并处理
|
||||
* 流式数据处理 - 专门处理单导联蓝牙数据
|
||||
*/
|
||||
private fun processStreamingData(packets: List<SensorData>) {
|
||||
if (packets.isEmpty()) return
|
||||
|
||||
Log.d("DataManager", "开始流式数据处理,处理 ${packets.size} 个数据包")
|
||||
Log.d("DataManager", "开始单导联流式数据处理,处理 ${packets.size} 个数据包")
|
||||
|
||||
// 1. 通道映射
|
||||
ensureDataMapper()
|
||||
val mappedPackets = if (dataMapperInitialized && dataMapper != null) {
|
||||
try {
|
||||
val mapped = dataMapper!!.mapSensorDataList(packets)
|
||||
Log.d("DataManager", "通道映射完成,映射了 ${mapped.size} 个数据包")
|
||||
mapped
|
||||
} catch (e: Exception) {
|
||||
Log.e("DataManager", "通道映射失败: ${e.message}")
|
||||
packets
|
||||
}
|
||||
} else {
|
||||
Log.w("DataManager", "数据映射器未初始化,跳过通道映射")
|
||||
packets
|
||||
}
|
||||
// 对于单导联数据,我们直接使用原始数据包,不进行复杂的通道映射
|
||||
val mappedPackets = packets
|
||||
|
||||
// 2. 按通道合并数据
|
||||
// 2. 按通道合并数据 - 单导联处理
|
||||
for (packet in mappedPackets) {
|
||||
val dataType = packet.getDataType()
|
||||
if (currentDataType == null) {
|
||||
currentDataType = dataType
|
||||
Log.d("DataManager", "设置当前数据类型: $dataType")
|
||||
Log.d("DataManager", "设置单导联数据类型: $dataType")
|
||||
}
|
||||
|
||||
val channelData = packet.getChannelData()
|
||||
if (channelData != null) {
|
||||
for ((channelIndex, channel) in channelData.withIndex()) {
|
||||
if (!channelBuffers.containsKey(channelIndex)) {
|
||||
channelBuffers[channelIndex] = mutableListOf()
|
||||
processedChannelBuffers[channelIndex] = mutableListOf()
|
||||
if (channelData != null && channelData.isNotEmpty()) {
|
||||
// 对于单导联数据,主要处理第一个通道
|
||||
val primaryChannel = channelData[0]
|
||||
if (!channelBuffers.containsKey(0)) {
|
||||
channelBuffers[0] = mutableListOf()
|
||||
processedChannelBuffers[0] = mutableListOf()
|
||||
}
|
||||
channelBuffers[0]!!.addAll(primaryChannel)
|
||||
|
||||
// 如果有其他通道,也处理
|
||||
for (i in 1 until channelData.size) {
|
||||
val additionalChannel = channelData[i]
|
||||
if (!channelBuffers.containsKey(i)) {
|
||||
channelBuffers[i] = mutableListOf()
|
||||
processedChannelBuffers[i] = mutableListOf()
|
||||
}
|
||||
channelBuffers[channelIndex]!!.addAll(channel)
|
||||
channelBuffers[i]!!.addAll(additionalChannel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否达到处理条件
|
||||
// 检查是否达到处理条件 - 单导联优化
|
||||
val totalSamples = channelBuffers.values.firstOrNull()?.size ?: 0
|
||||
if (totalSamples > 0) {
|
||||
Log.d("DataManager", "当前总样本数: $totalSamples, 需要样本数: $minSamplesForMetrics")
|
||||
Log.d("DataManager", "单导联当前总样本数: $totalSamples, 需要样本数: $minSamplesForMetrics")
|
||||
}
|
||||
|
||||
// 3. 检查处理条件:数据量足够且时间间隔合适
|
||||
|
|
@ -272,38 +323,19 @@ class DataManager(private val nativeCallback: NativeMethodCallback) {
|
|||
(currentTime - lastProcessTime) >= processingInterval
|
||||
|
||||
if (shouldProcess) {
|
||||
Log.d("DataManager", "满足处理条件,开始流式信号处理和指标计算")
|
||||
Log.d("DataManager", "满足单导联处理条件,开始流式信号处理和指标计算")
|
||||
processStreamingWindow()
|
||||
lastProcessTime = currentTime
|
||||
} else {
|
||||
Log.d("DataManager", "不满足处理条件 - 数据量: ${totalSamples}/${minSamplesForMetrics}, 时间间隔: ${currentTime - lastProcessTime}ms/${processingInterval}ms")
|
||||
Log.d("DataManager", "不满足单导联处理条件 - 数据量: ${totalSamples}/${minSamplesForMetrics}, 时间间隔: ${currentTime - lastProcessTime}ms/${processingInterval}ms")
|
||||
}
|
||||
|
||||
// 累积映射后的数据包列表(用于UI显示)
|
||||
// 注意:这里累积所有批次的映射后数据包,用于统计和显示
|
||||
// 累积单导联数据包列表(用于UI显示)
|
||||
this.processedPackets.addAll(mappedPackets)
|
||||
|
||||
// 添加调试信息(在后台线程中执行,避免阻塞UI)
|
||||
Thread {
|
||||
try {
|
||||
Log.d("DataManager", "DEBUG: 当前批次映射结果 - 输入: ${packets.size}个数据包, 映射后: ${mappedPackets.size}个数据包")
|
||||
Log.d("DataManager", "DEBUG: 累积映射后数据包总数: ${this.processedPackets.size}")
|
||||
|
||||
// 统计当前批次的通道数量分布
|
||||
val ecg12LeadPackets = mappedPackets.filter { it.getDataType() == type.SensorData.DataType.ECG_12LEAD }
|
||||
val packetsWith8Channels = ecg12LeadPackets.count { it.getChannelData()?.size == 8 }
|
||||
val packetsWith12Channels = ecg12LeadPackets.count { it.getChannelData()?.size == 12 }
|
||||
Log.d("DataManager", "DEBUG: 当前批次ECG_12LEAD统计 - 8通道: ${packetsWith8Channels}个, 12通道: ${packetsWith12Channels}个")
|
||||
|
||||
// 统计累积的通道数量分布
|
||||
val totalEcg12LeadPackets = this.processedPackets.filter { it.getDataType() == type.SensorData.DataType.ECG_12LEAD }
|
||||
val totalPacketsWith8Channels = totalEcg12LeadPackets.count { it.getChannelData()?.size == 8 }
|
||||
val totalPacketsWith12Channels = totalEcg12LeadPackets.count { it.getChannelData()?.size == 12 }
|
||||
Log.d("DataManager", "DEBUG: 累积ECG_12LEAD统计 - 8通道: ${totalPacketsWith8Channels}个, 12通道: ${totalPacketsWith12Channels}个")
|
||||
} catch (e: Exception) {
|
||||
Log.e("DataManager", "后台统计调试信息时发生错误: ${e.message}", e)
|
||||
}
|
||||
}.start()
|
||||
// 添加单导联调试信息
|
||||
Log.d("DataManager", "单导联数据处理完成 - 输入: ${packets.size}个数据包, 处理: ${mappedPackets.size}个数据包")
|
||||
Log.d("DataManager", "累积单导联数据包总数: ${this.processedPackets.size}")
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -448,10 +480,10 @@ class DataManager(private val nativeCallback: NativeMethodCallback) {
|
|||
}
|
||||
}
|
||||
|
||||
// 回调进度信息
|
||||
// 回调处理状态(不显示进度条,只记录状态)
|
||||
val totalSamples = channelBuffers.values.firstOrNull()?.size ?: 0
|
||||
callback.onStreamingProgress(
|
||||
progress = (totalSamples * 100 / minSamplesForMetrics).coerceAtMost(100),
|
||||
progress = 0, // 实时处理没有进度概念
|
||||
totalSamples = totalSamples,
|
||||
processedSamples = localProcessedSamples.toInt()
|
||||
)
|
||||
|
|
|
|||
|
|
@ -60,19 +60,27 @@ class ECGChartView @JvmOverloads constructor(
|
|||
private var offsetX = 0f // X轴偏移
|
||||
private var offsetY = 0f // Y轴偏移
|
||||
|
||||
// 自动居中控制
|
||||
private var autoCenter = true // 自动居中模式
|
||||
private var lockToCenter = true // 锁定到屏幕中央
|
||||
|
||||
// 手势检测器
|
||||
private val gestureDetector = GestureDetector(context, object : GestureDetector.SimpleOnGestureListener() {
|
||||
override fun onDoubleTap(e: MotionEvent): Boolean {
|
||||
// 双击重置缩放
|
||||
// 双击重置缩放并锁定到中央
|
||||
resetZoom()
|
||||
lockToCenter = true
|
||||
invalidate()
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onScroll(e1: MotionEvent?, e2: MotionEvent, distanceX: Float, distanceY: Float): Boolean {
|
||||
// 滑动平移
|
||||
offsetX -= distanceX
|
||||
offsetY -= distanceY
|
||||
invalidate()
|
||||
// 只有在非锁定模式下才允许平移
|
||||
if (!lockToCenter) {
|
||||
offsetX -= distanceX
|
||||
offsetY -= distanceY
|
||||
invalidate()
|
||||
}
|
||||
return true
|
||||
}
|
||||
})
|
||||
|
|
@ -87,6 +95,7 @@ class ECGChartView @JvmOverloads constructor(
|
|||
private val zoomInYButtonRect = RectF()
|
||||
private val zoomOutYButtonRect = RectF()
|
||||
private val resetButtonRect = RectF()
|
||||
private val lockButtonRect = RectF()
|
||||
|
||||
fun updateData(newData: List<Float>) {
|
||||
// 累积数据而不是替换
|
||||
|
|
@ -116,6 +125,12 @@ class ECGChartView @JvmOverloads constructor(
|
|||
}
|
||||
}
|
||||
|
||||
// 如果启用自动居中,重置偏移
|
||||
if (autoCenter) {
|
||||
offsetX = 0f
|
||||
offsetY = 0f
|
||||
}
|
||||
|
||||
invalidate() // 重绘
|
||||
}
|
||||
|
||||
|
|
@ -150,8 +165,22 @@ class ECGChartView @JvmOverloads constructor(
|
|||
scaleY = 1.0f
|
||||
offsetX = 0f
|
||||
offsetY = 0f
|
||||
lockToCenter = true
|
||||
invalidate()
|
||||
}
|
||||
|
||||
// 切换锁定模式
|
||||
fun toggleLockMode() {
|
||||
lockToCenter = !lockToCenter
|
||||
if (lockToCenter) {
|
||||
offsetX = 0f
|
||||
offsetY = 0f
|
||||
}
|
||||
invalidate()
|
||||
}
|
||||
|
||||
// 获取锁定状态
|
||||
fun isLockedToCenter(): Boolean = lockToCenter
|
||||
|
||||
override fun onTouchEvent(event: MotionEvent): Boolean {
|
||||
when (event.action) {
|
||||
|
|
@ -175,6 +204,9 @@ class ECGChartView @JvmOverloads constructor(
|
|||
} else if (resetButtonRect.contains(x, y)) {
|
||||
resetZoom()
|
||||
return true
|
||||
} else if (lockButtonRect.contains(x, y)) {
|
||||
toggleLockMode()
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -202,6 +234,10 @@ class ECGChartView @JvmOverloads constructor(
|
|||
canvas.drawText("X缩放: ${String.format("%.1f", scaleX)}x", 20f, 70f, textPaint)
|
||||
canvas.drawText("Y缩放: ${String.format("%.1f", scaleY)}x", 20f, 100f, textPaint)
|
||||
|
||||
// 绘制锁定状态
|
||||
val lockStatus = if (lockToCenter) "🔒 已锁定" else "🔓 已解锁"
|
||||
canvas.drawText(lockStatus, 20f, 130f, textPaint)
|
||||
|
||||
// 绘制数据点数量
|
||||
canvas.drawText("数据点: ${dataPoints.size}", 20f, height - 20f, textPaint)
|
||||
|
||||
|
|
@ -225,36 +261,44 @@ class ECGChartView @JvmOverloads constructor(
|
|||
|
||||
// X轴缩放按钮
|
||||
zoomInXButtonRect.set(
|
||||
width - buttonSize * 6 - buttonMargin * 6,
|
||||
buttonY,
|
||||
width - buttonSize * 5 - buttonMargin * 5,
|
||||
buttonY + buttonSize
|
||||
)
|
||||
|
||||
zoomOutXButtonRect.set(
|
||||
width - buttonSize * 5 - buttonMargin * 5,
|
||||
buttonY,
|
||||
width - buttonSize * 4 - buttonMargin * 4,
|
||||
buttonY + buttonSize
|
||||
)
|
||||
|
||||
zoomOutXButtonRect.set(
|
||||
// Y轴缩放按钮
|
||||
zoomInYButtonRect.set(
|
||||
width - buttonSize * 4 - buttonMargin * 4,
|
||||
buttonY,
|
||||
width - buttonSize * 3 - buttonMargin * 3,
|
||||
buttonY + buttonSize
|
||||
)
|
||||
|
||||
// Y轴缩放按钮
|
||||
zoomInYButtonRect.set(
|
||||
zoomOutYButtonRect.set(
|
||||
width - buttonSize * 3 - buttonMargin * 3,
|
||||
buttonY,
|
||||
width - buttonSize * 2 - buttonMargin * 2,
|
||||
buttonY + buttonSize
|
||||
)
|
||||
|
||||
zoomOutYButtonRect.set(
|
||||
// 重置按钮
|
||||
resetButtonRect.set(
|
||||
width - buttonSize * 2 - buttonMargin * 2,
|
||||
buttonY,
|
||||
width - buttonSize - buttonMargin,
|
||||
buttonY + buttonSize
|
||||
)
|
||||
|
||||
// 重置按钮
|
||||
resetButtonRect.set(
|
||||
// 锁定按钮
|
||||
lockButtonRect.set(
|
||||
width - buttonSize - buttonMargin,
|
||||
buttonY,
|
||||
width - buttonMargin,
|
||||
|
|
@ -267,7 +311,7 @@ class ECGChartView @JvmOverloads constructor(
|
|||
canvas.drawRoundRect(zoomInXButtonRect, 10f, 10f, buttonPaint)
|
||||
canvas.drawText("X+", zoomInXButtonRect.centerX(), zoomInXButtonRect.centerY() + 7f, buttonTextPaint)
|
||||
|
||||
canvas.drawRoundRect(zoomOutXButtonRect, 10f, 10f, buttonPaint)
|
||||
canvas.drawRoundRect(zoomOutXButtonRect, 10f, 10f, buttonTextPaint)
|
||||
canvas.drawText("X-", zoomOutXButtonRect.centerX(), zoomOutXButtonRect.centerY() + 7f, buttonTextPaint)
|
||||
|
||||
// 绘制Y轴缩放按钮
|
||||
|
|
@ -280,6 +324,13 @@ class ECGChartView @JvmOverloads constructor(
|
|||
// 绘制重置按钮
|
||||
canvas.drawRoundRect(resetButtonRect, 10f, 10f, buttonPaint)
|
||||
canvas.drawText("重置", resetButtonRect.centerX(), resetButtonRect.centerY() + 7f, buttonTextPaint)
|
||||
|
||||
// 绘制锁定按钮
|
||||
val lockButtonColor = if (lockToCenter) Color.GREEN else Color.RED
|
||||
buttonPaint.color = lockButtonColor
|
||||
canvas.drawRoundRect(lockButtonRect, 10f, 10f, buttonPaint)
|
||||
canvas.drawText(if (lockToCenter) "🔒" else "🔓", lockButtonRect.centerX(), lockButtonRect.centerY() + 7f, buttonTextPaint)
|
||||
buttonPaint.color = Color.GRAY // 恢复默认颜色
|
||||
}
|
||||
|
||||
private fun drawGrid(canvas: Canvas, width: Float, height: Float) {
|
||||
|
|
@ -309,13 +360,17 @@ class ECGChartView @JvmOverloads constructor(
|
|||
val scaledWidth = drawWidth * scaleX
|
||||
val scaledHeight = drawHeight * scaleY
|
||||
|
||||
// 如果锁定到中央,强制偏移为0
|
||||
val effectiveOffsetX = if (lockToCenter) 0f else offsetX
|
||||
val effectiveOffsetY = if (lockToCenter) 0f else offsetY
|
||||
|
||||
val xStep = scaledWidth / (dataPoints.size - 1)
|
||||
|
||||
for (i in dataPoints.indices) {
|
||||
val x = padding + i * xStep + offsetX
|
||||
val x = padding + i * xStep + effectiveOffsetX
|
||||
val normalizedValue = (dataPoints[i] - minValue) / (maxValue - minValue)
|
||||
// 使用0.1到0.9的范围,确保曲线在中间80%的区域显示
|
||||
val y = padding + (0.1f + normalizedValue * 0.8f) * scaledHeight + offsetY
|
||||
val y = padding + (0.1f + normalizedValue * 0.8f) * scaledHeight + effectiveOffsetY
|
||||
|
||||
// 确保点在可见区域内
|
||||
if (x >= padding && x <= width - padding && y >= padding && y <= height - padding) {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.example.cmake_project_test
|
|||
import android.content.Context
|
||||
import android.graphics.*
|
||||
import android.util.AttributeSet
|
||||
import android.util.Log
|
||||
import android.view.GestureDetector
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
|
|
@ -96,6 +97,7 @@ class ECGRhythmView @JvmOverloads constructor(
|
|||
fun updateData(newData: List<Float>) {
|
||||
if (newData.isNotEmpty()) {
|
||||
isDataAvailable = true
|
||||
Log.d("ECGRhythmView", "收到新数据: ${newData.size} 个点")
|
||||
}
|
||||
|
||||
// 性能优化:批量更新数据
|
||||
|
|
@ -133,6 +135,7 @@ class ECGRhythmView @JvmOverloads constructor(
|
|||
}
|
||||
|
||||
lastUpdateTime = currentTime
|
||||
Log.d("ECGRhythmView", "更新图表,数据点: ${dataPoints.size}, 范围: $minValue - $maxValue")
|
||||
invalidate()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.example.cmake_project_test
|
|||
import android.content.Context
|
||||
import android.graphics.*
|
||||
import android.util.AttributeSet
|
||||
import android.util.Log
|
||||
import android.view.GestureDetector
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
|
|
@ -96,6 +97,7 @@ class ECGWaveformView @JvmOverloads constructor(
|
|||
fun updateData(newData: List<Float>) {
|
||||
if (newData.isNotEmpty()) {
|
||||
isDataAvailable = true
|
||||
Log.d("ECGWaveformView", "收到新数据: ${newData.size} 个点")
|
||||
}
|
||||
|
||||
// 性能优化:批量更新数据
|
||||
|
|
@ -133,6 +135,7 @@ class ECGWaveformView @JvmOverloads constructor(
|
|||
}
|
||||
|
||||
lastUpdateTime = currentTime
|
||||
Log.d("ECGWaveformView", "更新图表,数据点: ${dataPoints.size}, 范围: $minValue - $maxValue")
|
||||
invalidate()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -105,8 +105,20 @@ class StreamingSignalProcessor {
|
|||
|
||||
// 当缓冲区有足够数据时进行窗口处理
|
||||
while (dataBuffer.size >= windowSize) {
|
||||
// 提取当前窗口数据
|
||||
val windowData = dataBuffer.take(windowSize).toFloatArray()
|
||||
// 提取当前窗口数据,过滤掉null值
|
||||
val windowDataList = dataBuffer.take(windowSize).filterNotNull()
|
||||
if (windowDataList.size < windowSize) {
|
||||
Log.w("StreamingSignalProcessor", "窗口数据包含null值,跳过处理")
|
||||
// 移除无效数据
|
||||
repeat(stepSize) {
|
||||
if (dataBuffer.isNotEmpty()) {
|
||||
dataBuffer.removeAt(0)
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
val windowData = windowDataList.toFloatArray()
|
||||
Log.d("StreamingSignalProcessor", "提取窗口数据,长度: ${windowData.size}")
|
||||
|
||||
// 应用信号处理
|
||||
|
|
|
|||
|
|
@ -54,12 +54,12 @@
|
|||
android:orientation="horizontal">
|
||||
|
||||
<Button
|
||||
android:id="@+id/stop_button"
|
||||
android:id="@+id/send_command_button"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:text="停止程序"
|
||||
android:text="发送指令"
|
||||
android:textSize="14sp"
|
||||
android:background="@drawable/button_background"
|
||||
android:textColor="#FFFFFF"
|
||||
|
|
@ -79,6 +79,38 @@
|
|||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 第三行按钮 -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginTop="4dp">
|
||||
|
||||
<Button
|
||||
android:id="@+id/stop_button"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:text="停止程序"
|
||||
android:textSize="14sp"
|
||||
android:background="@drawable/button_background"
|
||||
android:textColor="#FFFFFF"
|
||||
android:enabled="false" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/clear_data_button"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="4dp"
|
||||
android:text="清空数据"
|
||||
android:textSize="14sp"
|
||||
android:background="@drawable/button_background"
|
||||
android:textColor="#FFFFFF" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- ECG图表区域 -->
|
||||
|
|
@ -89,7 +121,7 @@
|
|||
android:layout_weight="0.7"
|
||||
android:orientation="vertical"
|
||||
android:background="#F8F8F8"
|
||||
android:visibility="gone">
|
||||
android:visibility="visible">
|
||||
|
||||
<!-- 图表标题 -->
|
||||
<LinearLayout
|
||||
|
|
|
|||
|
|
@ -0,0 +1,153 @@
|
|||
设备接口文档250715
|
||||
|
||||
1. 脑电
|
||||
|
||||
字段 类型 长度/字节 描述
|
||||
sn Uint16_t 2
|
||||
data_type Uint16_t 2 数据包序列号,递增,用于校验数据连续性
|
||||
data_len uint16_t 2
|
||||
loff_state Uint8_t[2] 2 0x4230 QL-PSG-HEAD
|
||||
eeg int16_t[6][14] 6 × 14 × 2
|
||||
数据部分长度(固定值)
|
||||
eog int16_t[2][14] 2 × 14 × 2
|
||||
导联脱落状态(从 STAT 寄存器解析)
|
||||
reserve Uint8_t[6] 6
|
||||
6 通道 EEG 数据(每通道 16 位有符号整数)
|
||||
总长度238字节 采样率:250Hz
|
||||
电压值(μV):adc_value ×0.318
|
||||
|
||||
2 通道 EOG 数据(每通道 16 位有符号整数)
|
||||
采样率:250Hz
|
||||
电压值(μV):adc_value ×0.318
|
||||
|
||||
预留
|
||||
|
||||
2. 胸腹
|
||||
|
||||
ADS1294
|
||||
|
||||
字段 类型 长度/ 描述
|
||||
字节
|
||||
sn Uint16_t
|
||||
data_type Uint16_t 2 数据包序列号,递增,用于校验数据连续性
|
||||
|
||||
2 固定为0x4211(标识心电 / 肌电数据)
|
||||
data_len uint16_t 2 数据部分长度
|
||||
(sizeof(qx_protocol_psg_ct_ads1294r_data_
|
||||
loff_state Uint8_t[2] t))
|
||||
ecg1 int16_t[25]
|
||||
2 导联脱落状态(合并两个设备的loff信息)
|
||||
|
||||
2×25 1通道心电数据
|
||||
采样率:250Hz
|
||||
电压值(μV):adc_value ×0.318
|
||||
|
||||
ecg2 int16_t [25] 2×25 2通道心电数据
|
||||
采样率:250Hz
|
||||
电压值(μV):adc_value ×0.318
|
||||
|
||||
emg1 int16_t [25] 2×25 1通道肌电数据
|
||||
采样率:250Hz
|
||||
电压值(μV):adc_value ×0.318
|
||||
|
||||
emg2 int16_t [25] 2×25 2通道肌电数据
|
||||
采样率:250Hz
|
||||
电压值(μV):adc_value ×0.318
|
||||
|
||||
br_temperature int16_t [5] 2×5 呼吸温度
|
||||
采样率:50Hz
|
||||
电压值(μV):adc_value ×0.477
|
||||
|
||||
br_impedance1 int16_t [5] 2×5 呼吸阻抗 1
|
||||
br_impedance2 int16_t [5]
|
||||
2×5 呼吸阻抗 2
|
||||
|
||||
总长度238字节
|
||||
|
||||
鼾声
|
||||
|
||||
字段 类型 长度(字 描述
|
||||
节)
|
||||
sn Uint16_t 2 数据包序列号,递增,用于校验数据连续性
|
||||
2 固定为0x4212(标识鼾声数据)
|
||||
data_type Uint16_t
|
||||
data_len uint16_t 2 数据部分长度
|
||||
snore int8_t[232] 232 (sizeof(qx_protocol_psg_ct_ads1294r_data_t))
|
||||
鼾声数据
|
||||
总长度238字节
|
||||
|
||||
呼吸/姿态/环境光
|
||||
|
||||
字段 类型 长度(字 描述
|
||||
节)
|
||||
sn Uint16_t 2 数据包序列号,递增,用于校验数据连续性
|
||||
data_type Uint16_t 2 固定为0x4213(标识气压 / 姿态/环境光数据)
|
||||
data_len uint16_t 2 数据部分长度
|
||||
|
||||
br_nose_pressure Int16_t[114] (sizeof(qx_protocol_psg_ct_ads1294r_data_t))
|
||||
movement Uint16_t 2*114 呼吸气流
|
||||
posture Uint8_t 2 运动强度
|
||||
ambient Uint8_t 1 姿态
|
||||
1 环境光
|
||||
|
||||
总长度238字节
|
||||
|
||||
3. 12导联心电
|
||||
|
||||
字段 类型 长度(字 描述
|
||||
节)
|
||||
sn Uint16_t
|
||||
data_type Uint16_t 2 数据包序列号,递增,用于校验数据连续性
|
||||
|
||||
2 0x4402 PW-ECG-M
|
||||
data_len uint16_t 2 数据部分长度
|
||||
loff_state Uint8_t[2] (sizeof(qx_protocol_psg_ct_ads1294r_data_t))
|
||||
|
||||
ecg int16_t[8][14] 2 导联脱落状态(高 4 位 + 中 4 位),由 ADS1298 的
|
||||
STAT.DH和STAT.DM拼接得到:
|
||||
gpio_state uint8_t `((STAT.DH << 4)
|
||||
reserve uint8_t[5]
|
||||
2*8*14 8 通道心电数据,每个通道14 个采样点
|
||||
总长度238字节 采样率:250Hz
|
||||
电压值(μV):adc_value ×0.318
|
||||
|
||||
1 GPIO 状态,取 ADS1298 的STAT.DL低 4 位
|
||||
(STAT.DL & 0x0F),用于起搏检测等。
|
||||
|
||||
5 预留
|
||||
|
||||
4. 血氧
|
||||
|
||||
字段 类型 长度/字节 描述
|
||||
sn Uint16_t 2 数据包序列号,递增,用于校验数据连续性
|
||||
data_type Uint16_t 2 0x4302 QH-DCS-PPTM
|
||||
data_len uint16_t 2 数据部分长度
|
||||
hr Uint8_t 1 心率值(次 / 分钟)
|
||||
spo2 uint8_t 1 血氧饱和度(%)
|
||||
temperature int16_t 2 温度值(扩大 100 倍存储,如25.5℃存储为
|
||||
2550)。
|
||||
red_data int16_t[57] 57*2 红光光电容积数据
|
||||
采样率:50Hz
|
||||
ir_data int16_t[57] 57*2 电压值(mV):adc_value ×0.879
|
||||
红外光光电容积数据
|
||||
采样率:50Hz
|
||||
电压值(mV):adc_value ×0.879
|
||||
|
||||
总长度238字节
|
||||
|
||||
5. 数字听诊
|
||||
|
||||
字段 类型 长度/字节 描述
|
||||
sn Uint16_t
|
||||
data_type Uint16_t 2 数据包序列号,递增,用于校验数据连续性
|
||||
data_len uint16_t
|
||||
ch_sound int8_t[116][2] 2 0x1102 QX-M-SOUND
|
||||
|
||||
2 数据部分长度
|
||||
|
||||
116*2 2个通道数据,每个通道116点
|
||||
采样率:8000Hz
|
||||
电压值(mV):adc_value ×0.146
|
||||
|
||||
总长度238字节
|
||||
|
||||
|
|
@ -0,0 +1,353 @@
|
|||
轻迅蓝牙通信协议
|
||||
V1.0.1
|
||||
版本历史
|
||||
|
||||
版本号 描述 作者 时间
|
||||
1.0.0 初始创建 李立中 2023-12-05
|
||||
1.0.1 增加设备名称配置指令 李立中 2025-03-13
|
||||
目录
|
||||
|
||||
1. 范围 .................................................................................................................................................. 4
|
||||
2. 蓝牙接口定义................................................................................................................................ 4
|
||||
|
||||
2.1 服务接口 ...................................................................................................................................... 4
|
||||
2.2 广播接口 ...................................................................................................................................... 5
|
||||
3. 帧格式说明 .................................................................................................................................... 6
|
||||
3.1 帧数据包结构 ............................................................................................................................ 6
|
||||
3.2 功能码定义................................................................................................................................. 7
|
||||
3.3 功能码描述................................................................................................................................. 7
|
||||
3.4 数据标识定义 .......................................................................................................................... 11
|
||||
4. 通信逻辑说明.............................................................................................................................. 11
|
||||
4.1 丢包处理 ................................................................................................................................... 11
|
||||
4.2 压缩 ............................................................................................................................................ 12
|
||||
4.2 空中升级 ................................................................................................................................... 12
|
||||
1. 范围
|
||||
|
||||
本协议文档用于蓝牙主设备终端与轻迅采集设备之间蓝牙数据通信。
|
||||
|
||||
2. 蓝牙接口定义
|
||||
|
||||
2.1 服务接口
|
||||
|
||||
2.1.1 自定义服务
|
||||
|
||||
2.1.1.1 数据服务
|
||||
|
||||
采用自定义 128bit UUID
|
||||
UUID: 6e400001-b5a3-f393-e0a9-68716563686f
|
||||
包含 Write Characteristic 和 Notify Characteristic 两个子接口
|
||||
(1) RX:Write Characteristic(提供 BLE 主设备 向 BLE 从设备发送消息或取相应消息)
|
||||
UUID:6e400002-b5a3-f393-e0a9-68716563686f
|
||||
属性:Write/WriteNoRSP
|
||||
数据类型:字节
|
||||
数据长度:244(每包最大可传输数据)
|
||||
(2) TX:Notify Characteristic(提供给 BLE 从设备向 BLE 主设备 发送通知信息)
|
||||
UUID: 6e400003-b5a3-f393-e0a9-68716563686f
|
||||
属性: Notify
|
||||
数据类型:字节
|
||||
数据长度:244(每包最大可传输数据)
|
||||
|
||||
2.1.1.1 空中升级服务
|
||||
|
||||
采用 Nordic DFU 功能实现
|
||||
Service UUID: 0xFE59
|
||||
2.1.2 标准服务
|
||||
|
||||
2.1.2.1 设备信息服务
|
||||
|
||||
采用 Bluetooth SIG 标准规定 Device Information Service
|
||||
Service UUID:0x180A
|
||||
包含设备编号,软件和硬件版本等信息的特征值
|
||||
具体对应特征值 UUID 和属性可查看标准文档
|
||||
|
||||
2.1.2.2 电量服务
|
||||
|
||||
采用 Bluetooth SIG 标准规定 Battery Service
|
||||
Service UUID:0x180F
|
||||
对应特征值 UUID 和属性可查看标准文档
|
||||
|
||||
2.2 广播接口
|
||||
|
||||
2.2.1 广播数据数据格式介绍
|
||||
|
||||
2.2.2 广播包结构
|
||||
|
||||
广播数据段描述 类型 说明
|
||||
|
||||
设备 LE 物理连接标识 0x01 长度:0x02
|
||||
类型:0x01
|
||||
数据:0x06
|
||||
设备名称 0x09 长度:0x07
|
||||
类型:0x09
|
||||
数据:48 51 5F 42 45 45
|
||||
|
||||
设备名称视具体设备而定
|
||||
广播内容: 02 01 06 07 09 48 51 5F 42 45 45
|
||||
|
||||
2.2.3 扫描响应包结构
|
||||
|
||||
广播数据段描述 类型 说明
|
||||
厂商自定义数据 0xFF
|
||||
长度:0x1A
|
||||
类型:0xFF
|
||||
数据:
|
||||
|
||||
Company ID:2 字节
|
||||
Protocol Version:1 字节
|
||||
Device Code:2 字节
|
||||
MAC Address:6 字节
|
||||
|
||||
广播内容:0C FF 58 51 01 02 01 76 D8 57 F7 68 C2
|
||||
|
||||
Company ID 公司 ID 固定值为 0x5158
|
||||
Protocol Version 用于指示当前设备使用的协议版本 0x01
|
||||
|
||||
Device Code 包含产品类型(Device Type,高 8 位)与子类型(Sub Device Type,低 8
|
||||
位)
|
||||
|
||||
Device Type 产品类型与 Sub Device Type 产品子类型定义如下表:
|
||||
|
||||
产品类型 类型 ID 产品子类型 子类型 ID
|
||||
平伟心电 0x44 单导心电设备 0x01
|
||||
|
||||
MAC Address 声明当前设备 mac 地址,用于兼容 IOS 设备无法获取 BLE mac 地址问题
|
||||
|
||||
3. 帧格式说明
|
||||
|
||||
3.1 帧数据包结构 长度 字段 说明
|
||||
2 功能码
|
||||
序号 2 数据长度 len
|
||||
1
|
||||
2
|
||||
3~(2+len) len 数据
|
||||
|
||||
3+len 2 MIC 校验
|
||||
4+len
|
||||
|
||||
功能码:操作命令码。
|
||||
数据长度:本次传送数据的长度。
|
||||
MIC 校验:采用 CRC-16-CCITT-FALSE 校验,校验内容包括功能码、数据长度、数据 3 个
|
||||
部分
|
||||
说明:除特殊说明外,协议默认为小端格式。
|
||||
|
||||
3.2 功能码定义
|
||||
|
||||
APP->BLE
|
||||
|
||||
序号 功能码 功能 备注
|
||||
1 0x0000 查询设备信息 获取当前设备设置参数
|
||||
2 0x0001 采集使能开关 0 或 1,指定时间点
|
||||
3 0x0002 电量查询 查询设备电量
|
||||
4 0x000A 工频滤波开关 开启关闭工频滤波算法
|
||||
5 0x000B 配置设备名称 配置设备 BLE 广播名称
|
||||
6
|
||||
7 0x0080 同步时间戳 64bit 毫秒时间戳
|
||||
|
||||
BLE->APP
|
||||
|
||||
序号 功能码 功能 备注
|
||||
1 0x8000 数据上传 上报采集数据
|
||||
2 0x8001 设备状态上报 异常状态上报
|
||||
0x8002 电量上报 设备主动上报电量
|
||||
|
||||
3.3 功能码描述
|
||||
|
||||
3.3.1 设备信息
|
||||
|
||||
功能码 0x0000
|
||||
主->从:
|
||||
|
||||
长度 字段 说明
|
||||
2 0x0000 功能码
|
||||
2 0x0000 数据长度
|
||||
2 CRC16 检验
|
||||
从->主: 长度 字段 说明
|
||||
Data 格式: 2 0x0000 功能码
|
||||
2 0x0001 数据长度
|
||||
1 见下表格式
|
||||
Data 检验
|
||||
2
|
||||
CRC16 状态位
|
||||
采集使能
|
||||
Byte Bit
|
||||
保留
|
||||
0 0
|
||||
1~7
|
||||
|
||||
3.3.2 采集使能
|
||||
|
||||
功能码 0x0001
|
||||
主->从:
|
||||
|
||||
长度 字段 说明
|
||||
2 0x0001 功能码
|
||||
2 0x0009 数据长度
|
||||
1 0或1 采集开关
|
||||
8 Timestamp 指定时间戳
|
||||
|
||||
2 CRC16 检验
|
||||
|
||||
从->主:
|
||||
|
||||
长度 字段 说明
|
||||
2 0x0001 功能码
|
||||
2 0x0001 数据长度
|
||||
采集开启状
|
||||
1 0或1
|
||||
态
|
||||
|
||||
2 CRC16 检验
|
||||
|
||||
Timestamp 为开启或关闭采集操作的指定时间戳(毫秒级),如果为 0 则代表立即执行操作。
|
||||
可用于多设备同步开启采集。
|
||||
|
||||
3.3.3 电量查询
|
||||
|
||||
功能码 0x0002
|
||||
主->从: 长度 字段 说明
|
||||
从->主: 2 0x0002 功能码
|
||||
2 0x0000 数据长度
|
||||
|
||||
2 CRC16 检验
|
||||
|
||||
长度 字段 说明
|
||||
2 0x0002 功能码
|
||||
2 0x0001 数据长度
|
||||
1 percent 电量百分比
|
||||
|
||||
2 CRC16 检验
|
||||
|
||||
3.3.4 工频滤波开关
|
||||
|
||||
功能码 0x000A
|
||||
主->从:
|
||||
|
||||
长度 字段 说明
|
||||
2 0x000A 功能码
|
||||
2 0x0001 数据长度
|
||||
1 0或1 开关状态
|
||||
|
||||
2 CRC16 检验
|
||||
|
||||
从->主:
|
||||
|
||||
长度 字段 说明
|
||||
2 0x000A 功能码
|
||||
2 0x0000 数据长度
|
||||
|
||||
2 CRC16 检验
|
||||
|
||||
用于开启关闭工频滤波。设备默认开启工频滤波,在需要时可以进行临时关闭和开启。设
|
||||
备重启后,开关状态不保存。
|
||||
|
||||
3.3.5 配置设备名称
|
||||
|
||||
功能码 0x000B
|
||||
主->从:
|
||||
|
||||
长度 字段 说明
|
||||
2 0x000B 功能码
|
||||
2 0x0011 数据长度
|
||||
17 NameData 名称数据
|
||||
2 CRC16 检验
|
||||
从->主:
|
||||
|
||||
长度 字段 说明
|
||||
2 0x000B 功能码
|
||||
2 0x0000 数据长度
|
||||
|
||||
2 CRC16 检验
|
||||
|
||||
用于设置设备名称,名称最长为 16 字节。
|
||||
|
||||
NameData 格式
|
||||
|
||||
长度 字段 说明
|
||||
|
||||
1 NameLen 设备名称长度
|
||||
|
||||
16 DeviceName 设备名称
|
||||
|
||||
NameLen 指定后面 DeviceName 实际长度。
|
||||
|
||||
DeviceName 数据内容长度固定为 16 字节,不足 16 字节则后面补 0。
|
||||
|
||||
3.3.6 同步时间戳
|
||||
|
||||
功能码 0x0080
|
||||
主->从:
|
||||
|
||||
长度 字段 说明
|
||||
2 0x0080 功能码
|
||||
2 0x0008 数据长度
|
||||
8 Timestamp 毫秒时间戳
|
||||
|
||||
2 CRC16 检验
|
||||
|
||||
从->主:
|
||||
|
||||
长度 字段 说明
|
||||
2 0x0080 功能码
|
||||
2 0x0000 数据长度
|
||||
|
||||
2 CRC16 检验
|
||||
|
||||
Timestamp 为毫秒级时间戳。若主设备接入网络,则会提供 unix 时间戳,从设备可以用来
|
||||
获取当前时间。若没有接入网络,主设备提供的时间戳为开机后的时间,仅能用于操作指
|
||||
令同步,不能当作实际时间。
|
||||
|
||||
3.3.7 数据上报
|
||||
|
||||
功能码 0x8000
|
||||
从->主:
|
||||
长度 字段 说明
|
||||
2 0x8000 功能码
|
||||
2 数据长度
|
||||
len len 上报数据
|
||||
2 Data 检验
|
||||
CRC16
|
||||
|
||||
Data 格式
|
||||
|
||||
长度 字段 说明
|
||||
|
||||
2 SN 包序号
|
||||
|
||||
N 数据 1 TLD 格式
|
||||
M 数据 2 TLD 格式
|
||||
…
|
||||
…
|
||||
|
||||
采集数据采用 TLD 格式,数据按照[Type][Length][Data]的顺序排列组织。每包数据可以包含
|
||||
多个 TLD 组。
|
||||
|
||||
具体 TLD 数据定义参考 3.4 数据标识定义。
|
||||
|
||||
3.3.8 状态上报
|
||||
|
||||
当前协议暂不处理
|
||||
|
||||
3.4 数据标识定义
|
||||
|
||||
3.4.1 平伟单导心电设备数据定义
|
||||
|
||||
Type(2 Byte) Length(2 Byte) Data
|
||||
0x4401 232 uint8_t loff_state;
|
||||
int16_t ch_ecg[115];
|
||||
uint8_t reserve;
|
||||
|
||||
心电采样率:250Hz
|
||||
|
||||
4. 通信逻辑说明
|
||||
4.1 丢包处理
|
||||
|
||||
当前协议暂不启用丢包处理
|
||||
4.2 压缩
|
||||
|
||||
当前协议暂不启用压缩
|
||||
|
||||
4.2 空中升级
|
||||
|
||||
空中升级采用 Nordic DFU 方案,具体做法是 App 发送特定数据使设备进入 DFU 状态,之后
|
||||
App 重连特定 DFU 设备进行升级。具体可以参考 Nordic DFU 方案文档。
|
||||
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
# 应用运行状态总结
|
||||
|
||||
## ✅ 编译和安装状态
|
||||
|
||||
### 编译成功
|
||||
- **编译命令**: `./gradlew assembleDebug`
|
||||
- **状态**: ✅ 成功
|
||||
- **错误**: 无
|
||||
|
||||
### 安装成功
|
||||
- **安装命令**: `./gradlew installDebug`
|
||||
- **状态**: ✅ 成功
|
||||
- **错误**: 无
|
||||
|
||||
### 代码质量检查
|
||||
- **Lint检查**: `./gradlew lintDebug`
|
||||
- **状态**: ✅ 通过
|
||||
- **警告**: 无
|
||||
|
||||
## 🔧 修复的问题
|
||||
|
||||
### 1. 缺少Build类导入
|
||||
**问题**: MainActivity中使用`Build.VERSION.SDK_INT`但未导入Build类
|
||||
**修复**: 添加了`import android.os.Build`
|
||||
**状态**: ✅ 已修复
|
||||
|
||||
### 2. 权限配置优化
|
||||
**问题**: 权限配置不够完善,特别是Android 12+的支持
|
||||
**修复**:
|
||||
- 更新了AndroidManifest.xml权限声明
|
||||
- 添加了动态权限检查
|
||||
- 增强了权限状态显示
|
||||
**状态**: ✅ 已修复
|
||||
|
||||
### 3. 蓝牙扫描功能增强
|
||||
**问题**: 扫描功能不够详细,缺少错误处理
|
||||
**修复**:
|
||||
- 延长扫描时间到15秒
|
||||
- 添加详细的设备信息显示
|
||||
- 增强错误处理和状态提示
|
||||
**状态**: ✅ 已修复
|
||||
|
||||
## 📱 应用功能状态
|
||||
|
||||
### 核心功能
|
||||
- [x] **蓝牙权限管理** - 完整的权限检查和请求
|
||||
- [x] **蓝牙设备扫描** - 支持BLE和传统蓝牙扫描
|
||||
- [x] **设备连接管理** - 连接、断开、状态监控
|
||||
- [x] **数据收发功能** - 支持各种指令发送
|
||||
- [x] **UI状态显示** - 详细的状态信息和提示
|
||||
|
||||
### 权限支持
|
||||
- [x] **Android 11及以下** - 需要位置权限
|
||||
- [x] **Android 12+** - 使用新蓝牙权限模型
|
||||
- [x] **动态权限检查** - 根据Android版本自动适配
|
||||
- [x] **权限状态显示** - 详细的权限状态反馈
|
||||
|
||||
### 蓝牙功能
|
||||
- [x] **BLE扫描** - 低功耗蓝牙设备扫描
|
||||
- [x] **传统蓝牙扫描** - 经典蓝牙设备扫描
|
||||
- [x] **设备连接** - 支持直接MAC地址连接
|
||||
- [x] **服务发现** - 自动发现蓝牙服务和特征
|
||||
- [x] **数据通道** - 建立数据收发通道
|
||||
|
||||
## 🎯 目标设备支持
|
||||
|
||||
### 目标设备: `A4:C3:37:86:9F:73`
|
||||
- [x] **MAC地址验证** - 支持多种格式验证
|
||||
- [x] **直接连接** - 长按蓝牙按钮直接连接
|
||||
- [x] **设备识别** - 扫描时自动识别目标设备
|
||||
- [x] **连接重试** - 超时后自动重试连接
|
||||
|
||||
## 📊 测试建议
|
||||
|
||||
### 1. 权限测试
|
||||
1. **启动应用**
|
||||
2. **观察权限状态显示**
|
||||
3. **授予必要权限**
|
||||
4. **确认权限状态更新**
|
||||
|
||||
### 2. 蓝牙扫描测试
|
||||
1. **点击"连接蓝牙"按钮**
|
||||
2. **观察扫描过程状态**
|
||||
3. **检查设备发现信息**
|
||||
4. **验证目标设备识别**
|
||||
|
||||
### 3. 连接测试
|
||||
1. **长按"连接蓝牙"按钮**
|
||||
2. **输入目标MAC地址**
|
||||
3. **观察连接过程**
|
||||
4. **验证连接状态**
|
||||
|
||||
### 4. 数据收发测试
|
||||
1. **建立连接后**
|
||||
2. **点击"发送指令"按钮**
|
||||
3. **测试各种指令发送**
|
||||
4. **验证数据接收**
|
||||
|
||||
## 🚀 下一步操作
|
||||
|
||||
### 立即测试
|
||||
1. **在目标设备上启动应用**
|
||||
2. **检查权限授予情况**
|
||||
3. **测试蓝牙扫描功能**
|
||||
4. **尝试连接目标设备**
|
||||
|
||||
### 如果遇到问题
|
||||
1. **查看应用状态信息**
|
||||
2. **检查Android Studio Logcat**
|
||||
3. **参考故障排除指南**
|
||||
4. **提供详细错误信息**
|
||||
|
||||
## 📋 技术规格
|
||||
|
||||
### 编译环境
|
||||
- **Gradle版本**: 8.0+
|
||||
- **Android Gradle Plugin**: 8.0+
|
||||
- **目标SDK**: 34
|
||||
- **最低SDK**: 21
|
||||
|
||||
### 权限要求
|
||||
- **BLUETOOTH_SCAN** - 蓝牙扫描权限
|
||||
- **BLUETOOTH_CONNECT** - 蓝牙连接权限
|
||||
- **ACCESS_FINE_LOCATION** - 精确位置权限(Android 11及以下)
|
||||
- **ACCESS_COARSE_LOCATION** - 粗略位置权限(Android 11及以下)
|
||||
|
||||
### 蓝牙支持
|
||||
- **BLE (Bluetooth Low Energy)** - 低功耗蓝牙
|
||||
- **传统蓝牙** - 经典蓝牙协议
|
||||
- **双模支持** - 自动检测和切换
|
||||
|
||||
## 🎉 总结
|
||||
|
||||
应用已成功编译、安装并修复了所有已知问题。主要功能包括:
|
||||
|
||||
1. **完整的权限管理** - 支持所有Android版本的权限模型
|
||||
2. **强大的蓝牙功能** - 扫描、连接、数据收发
|
||||
3. **友好的用户界面** - 详细的状态信息和提示
|
||||
4. **目标设备支持** - 专门针对目标设备的优化
|
||||
|
||||
应用现在可以正常运行,建议立即在目标设备上进行测试!
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
# 快速故障排除指南
|
||||
|
||||
## 当前问题:客户端找不到SerialTest服务器
|
||||
|
||||
### 立即检查清单
|
||||
|
||||
#### ✅ 手机端检查
|
||||
- [ ] 蓝牙已开启
|
||||
- [ ] 蓝牙可见性设置为"始终可见"
|
||||
- [ ] 已清除蓝牙缓存
|
||||
- [ ] 重启过蓝牙服务
|
||||
|
||||
#### ✅ 电脑端检查
|
||||
- [ ] SerialTest正在运行
|
||||
- [ ] 选择了正确的COM端口
|
||||
- [ ] 启用了蓝牙服务器模式
|
||||
- [ ] 电脑蓝牙已开启
|
||||
- [ ] 电脑蓝牙设置为"可发现"
|
||||
|
||||
#### ✅ 环境检查
|
||||
- [ ] 设备距离在10米内
|
||||
- [ ] 远离其他蓝牙设备干扰
|
||||
- [ ] 没有金属屏蔽物
|
||||
|
||||
### 快速测试步骤
|
||||
|
||||
#### 步骤1:基础扫描测试
|
||||
1. **启动Android应用**
|
||||
2. **点击"连接蓝牙"按钮**(普通点击)
|
||||
3. **观察扫描结果**:
|
||||
- 是否显示"🔍 开始扫描蓝牙设备..."
|
||||
- 是否显示"📡 使用BLE扫描模式"或"📡 使用传统蓝牙扫描模式"
|
||||
- 是否发现任何设备
|
||||
|
||||
#### 步骤2:直接连接测试
|
||||
1. **长按"连接蓝牙"按钮**
|
||||
2. **输入MAC地址**:`60:E9:AA:30:8B:0A`
|
||||
3. **点击连接**
|
||||
4. **观察连接状态**
|
||||
|
||||
#### 步骤3:系统蓝牙测试
|
||||
1. **打开手机蓝牙设置**
|
||||
2. **扫描设备**
|
||||
3. **查看是否发现您的电脑设备**
|
||||
4. **如果发现,尝试配对**
|
||||
|
||||
### 常见问题及解决方案
|
||||
|
||||
#### 问题1:扫描不到任何设备
|
||||
**可能原因**:
|
||||
- 目标设备蓝牙未开启
|
||||
- 设备不在可见范围内
|
||||
- 蓝牙服务异常
|
||||
|
||||
**解决方案**:
|
||||
1. 重启手机蓝牙
|
||||
2. 重启电脑蓝牙
|
||||
3. 检查设备距离
|
||||
4. 使用系统蓝牙设置测试
|
||||
|
||||
#### 问题2:扫描到设备但连接失败
|
||||
**可能原因**:
|
||||
- 设备未配对
|
||||
- 协议不兼容
|
||||
- SerialTest配置错误
|
||||
|
||||
**解决方案**:
|
||||
1. 在系统蓝牙设置中手动配对
|
||||
2. 检查SerialTest配置
|
||||
3. 确认波特率设置
|
||||
|
||||
#### 问题3:连接成功但无法通信
|
||||
**可能原因**:
|
||||
- 数据格式不匹配
|
||||
- 波特率设置错误
|
||||
- 协议不兼容
|
||||
|
||||
**解决方案**:
|
||||
1. 检查SerialTest数据格式设置
|
||||
2. 尝试不同的波特率
|
||||
3. 发送测试数据验证
|
||||
|
||||
### 调试信息查看
|
||||
|
||||
#### Android Studio Logcat
|
||||
过滤标签:`BluetoothManager`
|
||||
查看以下信息:
|
||||
- 扫描开始和结束
|
||||
- 设备发现详情
|
||||
- 连接状态变化
|
||||
- 错误信息
|
||||
|
||||
#### 应用状态显示
|
||||
应用会显示:
|
||||
- 🔍 扫描状态
|
||||
- 📱 发现的设备信息
|
||||
- ✅ 连接成功提示
|
||||
- ❌ 错误信息
|
||||
|
||||
### 下一步操作
|
||||
|
||||
1. **按照检查清单逐一确认**
|
||||
2. **执行快速测试步骤**
|
||||
3. **记录每个步骤的结果**
|
||||
4. **查看Logcat日志**
|
||||
5. **如果问题持续,提供详细错误信息**
|
||||
|
||||
### 需要提供的信息
|
||||
|
||||
如果问题仍然存在,请提供:
|
||||
1. **手机型号和Android版本**
|
||||
2. **电脑操作系统版本**
|
||||
3. **SerialTest版本和配置截图**
|
||||
4. **应用扫描过程的截图**
|
||||
5. **Logcat中的错误日志**
|
||||
6. **系统蓝牙设置中的设备列表截图**
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
# 快速测试指南
|
||||
|
||||
## 您的代码数据流分析
|
||||
|
||||
### 数据流路径:
|
||||
```
|
||||
蓝牙设备 → BluetoothManager → DataManager → 信号处理 → 图表显示
|
||||
```
|
||||
|
||||
### 详细流程:
|
||||
1. **BluetoothManager** 负责:
|
||||
- 扫描和连接蓝牙设备
|
||||
- 接收蓝牙数据
|
||||
- 发送指令到设备
|
||||
|
||||
2. **DataManager** 负责:
|
||||
- 解析接收到的数据包
|
||||
- 应用信号处理算法(滤波、陷波等)
|
||||
- 计算心率等指标
|
||||
- 通过回调发送数据到UI
|
||||
|
||||
3. **实时显示**:
|
||||
- ECG节律视图和波形视图
|
||||
- 实时更新图表数据
|
||||
- 显示处理进度和状态
|
||||
|
||||
## 测试您的电脑连接
|
||||
|
||||
### 方法1:直接连接(推荐)
|
||||
1. 启动应用
|
||||
2. **长按"连接蓝牙"按钮**
|
||||
3. 输入您的电脑MAC地址:`60:E9:AA:30:8B:0A`(使用冒号分隔符)
|
||||
4. 点击"连接"
|
||||
5. 观察连接状态
|
||||
|
||||
### 方法2:扫描连接
|
||||
1. 点击"连接蓝牙"按钮
|
||||
2. 等待扫描完成
|
||||
3. 在设备列表中选择您的电脑
|
||||
4. 点击连接
|
||||
|
||||
## 数据收发测试
|
||||
|
||||
### 发送测试数据:
|
||||
1. 连接成功后,**长按"发送指令"按钮**
|
||||
2. 选择测试选项:
|
||||
- **发送ECG测试数据**:模拟ECG数据包
|
||||
- **发送心跳包**:简单连接测试
|
||||
- **发送设备信息查询**:查询设备状态
|
||||
- **发送自定义测试数据**:自定义十六进制数据
|
||||
|
||||
### 接收测试:
|
||||
1. 点击"启动程序"按钮
|
||||
2. 观察图表是否显示测试数据
|
||||
3. 查看状态信息区域的数据接收情况
|
||||
|
||||
## 图表显示测试
|
||||
|
||||
### 立即测试:
|
||||
- **点击"启动程序"** → 生成简单测试数据
|
||||
- **长按"启动程序"** → 生成复杂ECG波形
|
||||
- **点击"清空数据"** → 清空图表数据
|
||||
|
||||
### 实时显示:
|
||||
- 连接成功后,图表会自动显示接收到的数据
|
||||
- 支持实时滤波和信号处理
|
||||
- 双视图显示:节律视图 + 波形视图
|
||||
|
||||
## 调试技巧
|
||||
|
||||
### 查看日志:
|
||||
在Android Studio中打开Logcat,过滤标签:
|
||||
- `MainActivity`:主要操作日志
|
||||
- `BluetoothManager`:蓝牙连接日志
|
||||
- `DataManager`:数据处理日志
|
||||
|
||||
### 常见问题:
|
||||
1. **连接失败**:检查权限和MAC地址
|
||||
2. **数据不显示**:点击"启动程序"生成测试数据
|
||||
3. **图表空白**:检查图表容器可见性
|
||||
|
||||
## 测试建议
|
||||
|
||||
1. **先测试连接**:确保能连接到您的电脑
|
||||
2. **再测试发送**:发送各种测试数据
|
||||
3. **最后测试接收**:验证数据接收和显示
|
||||
4. **查看日志**:通过Logcat监控详细过程
|
||||
|
||||
## 数据格式说明
|
||||
|
||||
### ECG测试数据包:
|
||||
```
|
||||
AA 55 01 [长度] [ECG数据...] [校验和]
|
||||
```
|
||||
|
||||
### 自定义数据:
|
||||
支持十六进制格式,如:`01 02 03 04 05`
|
||||
|
||||
## 下一步
|
||||
|
||||
1. 编译并运行应用
|
||||
2. 按照上述步骤测试连接
|
||||
3. 验证数据收发功能
|
||||
4. 检查图表显示效果
|
||||
5. 查看日志确认各环节正常
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
# 手机连接测试指南
|
||||
|
||||
## 目标设备信息
|
||||
- **设备类型**: 手机
|
||||
- **MAC地址**: `A4:C3:37:86:9F:73`
|
||||
- **连接方式**: 蓝牙
|
||||
|
||||
## 连接步骤
|
||||
|
||||
### 第一步:准备目标手机
|
||||
1. **确保目标手机蓝牙已开启**
|
||||
2. **设置蓝牙可见性**:
|
||||
- 打开设置 → 蓝牙
|
||||
- 确保蓝牙已开启
|
||||
- 设置为"可发现"或"始终可见"
|
||||
3. **检查配对状态**:
|
||||
- 如果之前配对过,建议先取消配对
|
||||
- 重新开始配对过程
|
||||
|
||||
### 第二步:使用应用连接
|
||||
1. **启动Android应用**
|
||||
2. **长按"连接蓝牙"按钮**
|
||||
3. **输入MAC地址**:`A4:C3:37:86:9F:73`
|
||||
4. **点击"连接"按钮**
|
||||
|
||||
### 第三步:观察连接状态
|
||||
应用会显示以下状态信息:
|
||||
- 🔍 **开始扫描蓝牙设备...**
|
||||
- ✅ **权限检查通过,开始扫描...**
|
||||
- 📡 **使用BLE扫描模式**或**使用传统蓝牙扫描模式**
|
||||
- 📱 **发现设备: [设备名称]**
|
||||
- 🎯 **找到目标设备!**(如果发现目标手机)
|
||||
- ✅ **设备已连接: [设备名称]**
|
||||
- 🔍 **服务发现成功**
|
||||
- 📡 **数据通道已建立,可以发送指令开始接收数据**
|
||||
|
||||
## 预期结果
|
||||
|
||||
### 连接成功时:
|
||||
- 按钮变为"断开蓝牙"(红色)
|
||||
- "发送指令"按钮启用(蓝色)
|
||||
- 显示详细的设备信息
|
||||
- 服务发现成功
|
||||
- 数据通道建立
|
||||
|
||||
### 连接失败时:
|
||||
- 显示错误信息
|
||||
- 按钮保持"连接蓝牙"状态
|
||||
- 提供可能的解决方案
|
||||
|
||||
## 测试数据发送
|
||||
|
||||
连接成功后,可以测试数据通信:
|
||||
|
||||
### 1. 发送测试数据
|
||||
1. **长按"发送指令"按钮**
|
||||
2. **选择测试数据类型**:
|
||||
- 发送ECG测试数据
|
||||
- 发送心跳包
|
||||
- 发送设备信息查询
|
||||
- 发送自定义测试数据
|
||||
|
||||
### 2. 观察数据接收
|
||||
- 在目标手机上查看是否收到数据
|
||||
- 检查数据格式是否正确
|
||||
- 验证通信是否双向
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 如果扫描不到设备:
|
||||
1. **检查目标手机蓝牙设置**
|
||||
2. **确保设备在10米范围内**
|
||||
3. **重启两台设备的蓝牙**
|
||||
4. **使用系统蓝牙设置测试**
|
||||
|
||||
### 如果连接失败:
|
||||
1. **检查MAC地址是否正确**
|
||||
2. **确认目标手机蓝牙已开启**
|
||||
3. **尝试在系统蓝牙设置中手动配对**
|
||||
4. **查看Logcat日志获取详细错误信息**
|
||||
|
||||
### 如果连接成功但无法通信:
|
||||
1. **检查目标手机是否支持相应的蓝牙服务**
|
||||
2. **确认数据格式是否兼容**
|
||||
3. **尝试发送不同类型的测试数据**
|
||||
|
||||
## 调试信息
|
||||
|
||||
### Android Studio Logcat
|
||||
过滤标签:`BluetoothManager`
|
||||
查看以下关键信息:
|
||||
- 扫描开始和结束
|
||||
- 设备发现详情
|
||||
- 连接状态变化
|
||||
- 服务发现过程
|
||||
- 数据收发情况
|
||||
|
||||
### 应用状态显示
|
||||
应用会实时显示:
|
||||
- 扫描进度
|
||||
- 发现的设备信息
|
||||
- 连接状态
|
||||
- 错误信息
|
||||
|
||||
## 下一步操作
|
||||
|
||||
1. **按照上述步骤连接目标手机**
|
||||
2. **观察连接过程的状态信息**
|
||||
3. **测试数据收发功能**
|
||||
4. **记录任何错误或异常情况**
|
||||
5. **如果遇到问题,查看Logcat日志**
|
||||
|
||||
## 需要提供的信息
|
||||
|
||||
如果连接失败,请提供:
|
||||
1. **目标手机型号和Android版本**
|
||||
2. **应用显示的状态信息**
|
||||
3. **Logcat中的错误日志**
|
||||
4. **目标手机蓝牙设置截图**
|
||||
5. **连接过程的详细描述**
|
||||
|
|
@ -0,0 +1,143 @@
|
|||
# 蓝牙连接测试总结
|
||||
|
||||
## 您的代码数据流分析
|
||||
|
||||
### 完整数据流:
|
||||
```
|
||||
蓝牙设备 (60-E9-AA-30-8B-0A)
|
||||
↓
|
||||
BluetoothManager (连接管理)
|
||||
↓
|
||||
DataManager (数据解析和处理)
|
||||
↓
|
||||
信号处理 (滤波、陷波等)
|
||||
↓
|
||||
实时图表显示 (ECG双视图)
|
||||
```
|
||||
|
||||
### 各组件功能:
|
||||
|
||||
1. **BluetoothManager**:
|
||||
- 扫描和连接蓝牙设备
|
||||
- 接收蓝牙数据流
|
||||
- 发送指令到设备
|
||||
- 管理连接状态
|
||||
|
||||
2. **DataManager**:
|
||||
- 解析接收到的数据包
|
||||
- 应用信号处理算法
|
||||
- 计算心率等指标
|
||||
- 通过回调发送数据到UI
|
||||
|
||||
3. **实时显示系统**:
|
||||
- ECG节律视图
|
||||
- ECG波形视图
|
||||
- 实时数据更新
|
||||
- 状态信息显示
|
||||
|
||||
## 已添加的测试功能
|
||||
|
||||
### 1. 直接连接功能
|
||||
- **长按"连接蓝牙"按钮** → 弹出直接连接对话框
|
||||
- 预设您的电脑MAC地址:`60:E9:AA:30:8B:0A`(使用冒号分隔符)
|
||||
- 支持自定义MAC地址输入
|
||||
|
||||
### 2. 测试数据发送功能
|
||||
- **长按"发送指令"按钮** → 弹出测试数据对话框
|
||||
- 支持多种测试数据类型:
|
||||
- ECG测试数据包
|
||||
- 心跳包
|
||||
- 设备信息查询
|
||||
- 自定义十六进制数据
|
||||
|
||||
### 3. 图表显示测试
|
||||
- **点击"启动程序"** → 立即生成测试数据
|
||||
- **长按"启动程序"** → 生成复杂ECG波形
|
||||
- **点击"清空数据"** → 清空图表数据
|
||||
|
||||
## 测试步骤
|
||||
|
||||
### 第一步:连接测试
|
||||
1. 启动应用
|
||||
2. **长按"连接蓝牙"按钮**
|
||||
3. 输入MAC地址:`60:E9:AA:30:8B:0A`(使用冒号分隔符)
|
||||
4. 点击"连接"
|
||||
5. 观察连接状态
|
||||
|
||||
### 第二步:发送测试
|
||||
1. 连接成功后,**长按"发送指令"按钮**
|
||||
2. 选择测试数据类型
|
||||
3. 发送测试数据
|
||||
4. 观察发送状态
|
||||
|
||||
### 第三步:接收测试
|
||||
1. 点击"启动程序"按钮
|
||||
2. 观察图表显示
|
||||
3. 查看数据接收情况
|
||||
|
||||
### 第四步:图表测试
|
||||
1. 长按"启动程序"按钮
|
||||
2. 观察ECG波形显示
|
||||
3. 测试陷波滤波器功能
|
||||
|
||||
## 调试信息
|
||||
|
||||
### 日志标签:
|
||||
- `MainActivity`:主要操作日志
|
||||
- `BluetoothManager`:蓝牙连接日志
|
||||
- `DataManager`:数据处理日志
|
||||
|
||||
### 状态显示:
|
||||
- 连接状态实时更新
|
||||
- 数据接收进度显示
|
||||
- 错误信息详细提示
|
||||
|
||||
## 数据格式
|
||||
|
||||
### ECG测试数据包:
|
||||
```
|
||||
AA 55 01 [长度低字节] [长度高字节] [ECG数据...] [校验和]
|
||||
```
|
||||
|
||||
### 自定义数据:
|
||||
支持十六进制格式,如:`01 02 03 04 05`
|
||||
|
||||
## 测试建议
|
||||
|
||||
1. **先测试连接**:确保能连接到您的电脑
|
||||
2. **再测试发送**:发送各种测试数据验证连接
|
||||
3. **最后测试接收**:验证数据接收和图表显示
|
||||
4. **查看日志**:通过Logcat监控详细过程
|
||||
|
||||
## 常见问题解决
|
||||
|
||||
### 连接失败:
|
||||
- 检查蓝牙权限
|
||||
- 确认MAC地址正确
|
||||
- 确保设备在范围内
|
||||
|
||||
### 数据不显示:
|
||||
- 点击"启动程序"生成测试数据
|
||||
- 检查连接状态
|
||||
- 查看日志错误信息
|
||||
|
||||
### 图表空白:
|
||||
- 检查图表容器可见性
|
||||
- 确认数据回调正常
|
||||
- 尝试重新生成测试数据
|
||||
|
||||
## 下一步操作
|
||||
|
||||
1. 编译并运行应用
|
||||
2. 按照测试步骤进行连接测试
|
||||
3. 验证数据收发功能
|
||||
4. 检查图表显示效果
|
||||
5. 查看日志确认各环节正常
|
||||
|
||||
## 技术支持
|
||||
|
||||
如果遇到问题,请:
|
||||
1. 查看Android Studio的Logcat日志
|
||||
2. 检查权限设置
|
||||
3. 确认设备MAC地址
|
||||
4. 验证蓝牙功能正常
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
# 编译错误修复总结
|
||||
|
||||
## 问题描述
|
||||
在之前的代码中出现了多个编译错误,导致应用无法正常构建。
|
||||
|
||||
## 修复的错误
|
||||
|
||||
### 1. 变量名冲突
|
||||
**问题**: `bluetoothManager` 变量名与类名冲突
|
||||
**修复**: 将变量名改为 `bluetoothManagerService`
|
||||
|
||||
```kotlin
|
||||
// 修复前
|
||||
private var bluetoothManager: BluetoothManager? = null
|
||||
|
||||
// 修复后
|
||||
private var bluetoothManagerService: android.bluetooth.BluetoothManager? = null
|
||||
```
|
||||
|
||||
### 2. 未定义的引用
|
||||
**问题**: 多个回调和方法未定义
|
||||
**修复**: 添加了完整的回调定义
|
||||
|
||||
- `bleScanCallback` - BLE扫描回调
|
||||
- `registerClassicScanReceiver()` - 注册传统蓝牙扫描接收器
|
||||
- `unregisterClassicScanReceiver()` - 注销传统蓝牙扫描接收器
|
||||
|
||||
### 3. 语法错误
|
||||
**问题**: `override` 修饰符使用错误
|
||||
**修复**: 正确使用 `override` 修饰符在回调对象中
|
||||
|
||||
### 4. 缺少闭合大括号
|
||||
**问题**: 文件结构不完整
|
||||
**修复**: 补充完整的类结构
|
||||
|
||||
## 当前状态
|
||||
|
||||
### ✅ 编译成功
|
||||
- 所有编译错误已修复
|
||||
- 应用可以正常构建
|
||||
- 只有一些关于已弃用API的警告(不影响功能)
|
||||
|
||||
### ⚠️ 警告信息
|
||||
以下API已被弃用,但功能正常:
|
||||
- `characteristic.value` - 特征值设置
|
||||
- `gatt.writeCharacteristic()` - 写入特征
|
||||
- `intent.getParcelableExtra()` - 获取Parcelable数据
|
||||
|
||||
## 功能验证
|
||||
|
||||
### 已实现的功能
|
||||
1. **蓝牙初始化** - 自动检测蓝牙适配器
|
||||
2. **设备扫描** - 支持BLE和传统蓝牙扫描
|
||||
3. **设备连接** - 支持直接MAC地址连接
|
||||
4. **服务发现** - 自动发现蓝牙服务和特征
|
||||
5. **数据收发** - 支持发送各种类型的指令
|
||||
6. **状态监控** - 详细的连接状态显示
|
||||
|
||||
### 测试准备
|
||||
现在可以测试连接目标手机:
|
||||
- **MAC地址**: `A4:C3:37:86:9F:73`
|
||||
- **连接方式**: 长按"连接蓝牙"按钮
|
||||
- **状态显示**: 详细的状态信息和调试信息
|
||||
|
||||
## 下一步操作
|
||||
|
||||
1. **运行应用** - 在Android Studio中运行应用
|
||||
2. **测试连接** - 尝试连接目标手机
|
||||
3. **观察日志** - 查看Logcat中的详细信息
|
||||
4. **功能验证** - 测试数据收发功能
|
||||
|
||||
## 技术细节
|
||||
|
||||
### 蓝牙协议支持
|
||||
- **BLE (Bluetooth Low Energy)** - 低功耗蓝牙
|
||||
- **传统蓝牙** - 经典蓝牙协议
|
||||
- **自动检测** - 根据设备能力自动选择协议
|
||||
|
||||
### 设备兼容性
|
||||
- **多种UUID支持** - 支持多种ECG设备UUID
|
||||
- **智能检测** - 自动检测设备服务和特征
|
||||
- **错误处理** - 完善的错误处理和用户提示
|
||||
|
||||
### 调试功能
|
||||
- **详细日志** - 完整的操作日志记录
|
||||
- **状态显示** - 实时的状态信息更新
|
||||
- **错误提示** - 友好的错误信息显示
|
||||
|
||||
## 总结
|
||||
|
||||
所有编译错误已成功修复,应用现在可以正常构建和运行。代码结构完整,功能齐全,可以开始进行蓝牙连接测试。
|
||||
|
|
@ -0,0 +1,217 @@
|
|||
# 蓝牙扫描故障排除指南
|
||||
|
||||
## 问题描述
|
||||
应用无法扫描到目标设备,需要诊断和解决扫描问题。
|
||||
|
||||
## 🔍 常见原因分析
|
||||
|
||||
### 1. 权限问题
|
||||
**症状**: 扫描无结果,日志显示权限错误
|
||||
**检查项目**:
|
||||
- [ ] `BLUETOOTH_SCAN` 权限已授予
|
||||
- [ ] `BLUETOOTH_CONNECT` 权限已授予
|
||||
- [ ] `ACCESS_FINE_LOCATION` 权限已授予
|
||||
- [ ] Android 12+ 需要额外权限
|
||||
|
||||
**解决方案**:
|
||||
1. 进入应用设置 → 权限
|
||||
2. 确保所有蓝牙相关权限已开启
|
||||
3. 重启应用
|
||||
|
||||
### 2. 目标设备设置问题
|
||||
**症状**: 扫描到其他设备但找不到目标设备
|
||||
**检查项目**:
|
||||
- [ ] 目标设备蓝牙已开启
|
||||
- [ ] 设置为"可发现"或"始终可见"
|
||||
- [ ] 未与其他设备连接
|
||||
- [ ] 设备在扫描范围内(10米内)
|
||||
|
||||
**解决方案**:
|
||||
1. 打开目标设备设置
|
||||
2. 进入蓝牙设置
|
||||
3. 确保蓝牙开启且可发现
|
||||
4. 断开其他连接
|
||||
|
||||
### 3. 扫描范围问题
|
||||
**症状**: 有时能找到设备,有时找不到
|
||||
**检查项目**:
|
||||
- [ ] 设备距离在10米内
|
||||
- [ ] 无金属屏蔽物
|
||||
- [ ] 远离其他蓝牙设备
|
||||
- [ ] 无WiFi干扰
|
||||
|
||||
**解决方案**:
|
||||
1. 将设备靠近(1-2米内)
|
||||
2. 移除金属物品
|
||||
3. 关闭其他蓝牙设备
|
||||
4. 尝试不同位置
|
||||
|
||||
### 4. 系统蓝牙服务问题
|
||||
**症状**: 扫描完全无结果
|
||||
**检查项目**:
|
||||
- [ ] 系统蓝牙服务正常
|
||||
- [ ] 蓝牙适配器工作正常
|
||||
- [ ] 无其他应用占用蓝牙
|
||||
|
||||
**解决方案**:
|
||||
1. 重启蓝牙服务
|
||||
2. 重启设备
|
||||
3. 检查系统蓝牙设置
|
||||
|
||||
## 🛠️ 增强的扫描功能
|
||||
|
||||
### 新增功能特性:
|
||||
1. **详细状态提示** - 显示扫描进度和注意事项
|
||||
2. **延长扫描时间** - 从10秒增加到15秒
|
||||
3. **智能错误处理** - BLE失败自动切换到传统蓝牙
|
||||
4. **设备信息显示** - 显示设备名称、地址、信号强度
|
||||
5. **目标设备识别** - 自动识别目标设备并提供连接建议
|
||||
|
||||
### 扫描流程:
|
||||
```
|
||||
开始扫描 → 权限检查 → BLE扫描(15秒) → 失败重试 → 传统蓝牙(15秒) → 结果汇总
|
||||
```
|
||||
|
||||
## 📋 测试步骤
|
||||
|
||||
### 第一步:基础检查
|
||||
1. **确认应用权限**
|
||||
- 进入应用设置 → 权限
|
||||
- 确保蓝牙和位置权限已开启
|
||||
|
||||
2. **检查系统蓝牙**
|
||||
- 进入系统设置 → 蓝牙
|
||||
- 确保蓝牙已开启
|
||||
|
||||
3. **检查目标设备**
|
||||
- 确保目标设备蓝牙开启
|
||||
- 设置为可发现模式
|
||||
|
||||
### 第二步:系统测试
|
||||
1. **使用系统蓝牙扫描**
|
||||
- 在系统蓝牙设置中扫描
|
||||
- 查看是否能发现目标设备
|
||||
- 记录扫描结果
|
||||
|
||||
2. **测试设备可见性**
|
||||
- 尝试手动配对
|
||||
- 确认设备可以被发现
|
||||
|
||||
### 第三步:应用测试
|
||||
1. **启动应用扫描**
|
||||
- 点击"连接蓝牙"按钮
|
||||
- 观察扫描过程
|
||||
- 查看状态信息
|
||||
|
||||
2. **观察扫描结果**
|
||||
- 查看发现的设备列表
|
||||
- 检查设备信息显示
|
||||
- 记录扫描时间
|
||||
|
||||
### 第四步:日志分析
|
||||
1. **查看应用日志**
|
||||
- 打开Android Studio Logcat
|
||||
- 过滤蓝牙相关日志
|
||||
- 分析扫描过程
|
||||
|
||||
2. **检查错误信息**
|
||||
- 查看权限错误
|
||||
- 查看扫描失败原因
|
||||
- 记录错误代码
|
||||
|
||||
## 🔧 调试技巧
|
||||
|
||||
### 1. 使用系统蓝牙验证
|
||||
```bash
|
||||
# 在系统蓝牙设置中手动扫描
|
||||
# 确认目标设备是否可见
|
||||
# 测试手动配对是否成功
|
||||
```
|
||||
|
||||
### 2. 检查设备信息
|
||||
- **目标设备型号和Android版本**
|
||||
- **蓝牙芯片类型**
|
||||
- **支持的蓝牙协议**
|
||||
- **设备MAC地址**
|
||||
|
||||
### 3. 环境测试
|
||||
- **尝试不同位置**
|
||||
- **检查环境干扰**
|
||||
- **测试不同距离**
|
||||
- **移除金属物品**
|
||||
|
||||
### 4. 应用调试
|
||||
- **查看详细日志**
|
||||
- **观察状态变化**
|
||||
- **记录错误信息**
|
||||
- **分析扫描模式**
|
||||
|
||||
## 📊 常见错误码及解决方案
|
||||
|
||||
| 错误码 | 含义 | 解决方案 |
|
||||
|--------|------|----------|
|
||||
| SCAN_FAILED_ALREADY_STARTED | 扫描已在进行中 | 等待当前扫描完成 |
|
||||
| SCAN_FAILED_APPLICATION_REGISTRATION_FAILED | 应用注册失败 | 重启应用或设备 |
|
||||
| SCAN_FAILED_FEATURE_UNSUPPORTED | 设备不支持BLE | 使用传统蓝牙扫描 |
|
||||
| SCAN_FAILED_INTERNAL_ERROR | 内部错误 | 重启蓝牙服务 |
|
||||
| SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES | 硬件资源不足 | 关闭其他蓝牙应用 |
|
||||
|
||||
## 🎯 针对目标设备的特殊检查
|
||||
|
||||
### 目标设备: `A4:C3:37:86:9F:73`
|
||||
|
||||
1. **设备信息确认**
|
||||
- 确认MAC地址正确
|
||||
- 检查设备型号
|
||||
- 确认Android版本
|
||||
|
||||
2. **蓝牙设置检查**
|
||||
- 确保蓝牙开启
|
||||
- 设置为可发现模式
|
||||
- 断开其他连接
|
||||
|
||||
3. **连接测试**
|
||||
- 使用系统蓝牙测试连接
|
||||
- 确认设备可以被发现
|
||||
- 测试手动配对
|
||||
|
||||
## 📝 故障排除清单
|
||||
|
||||
### 扫描前检查:
|
||||
- [ ] 应用权限已授予
|
||||
- [ ] 系统蓝牙已开启
|
||||
- [ ] 目标设备蓝牙已开启
|
||||
- [ ] 目标设备设置为可发现
|
||||
- [ ] 设备距离在10米内
|
||||
- [ ] 无其他蓝牙连接
|
||||
|
||||
### 扫描中观察:
|
||||
- [ ] 扫描状态信息显示
|
||||
- [ ] 发现设备列表更新
|
||||
- [ ] 目标设备是否出现
|
||||
- [ ] 扫描时间是否足够
|
||||
- [ ] 错误信息提示
|
||||
|
||||
### 扫描后分析:
|
||||
- [ ] 扫描结果汇总
|
||||
- [ ] 发现的设备数量
|
||||
- [ ] 目标设备是否找到
|
||||
- [ ] 设备信息是否完整
|
||||
- [ ] 连接建议是否提供
|
||||
|
||||
## 🚀 下一步操作
|
||||
|
||||
1. **按照上述步骤逐一检查**
|
||||
2. **使用系统蓝牙设置测试**
|
||||
3. **记录所有测试结果**
|
||||
4. **如果问题持续,提供详细设备信息**
|
||||
|
||||
## 📞 需要提供的信息
|
||||
|
||||
如果问题仍然存在,请提供:
|
||||
1. **目标设备型号和Android版本**
|
||||
2. **系统蓝牙设置测试结果**
|
||||
3. **应用扫描日志**
|
||||
4. **设备环境信息**
|
||||
5. **详细的测试步骤和结果**
|
||||
6. **错误信息和状态码**
|
||||
|
|
@ -0,0 +1,177 @@
|
|||
# 蓝牙权限配置完整指南
|
||||
|
||||
## 📋 权限配置概述
|
||||
|
||||
本应用已配置完整的蓝牙权限支持,包括:
|
||||
- **Android 11及以下**:需要位置权限进行蓝牙扫描
|
||||
- **Android 12+**:使用新的蓝牙权限模型,无需位置权限
|
||||
|
||||
## 🔧 AndroidManifest.xml 配置
|
||||
|
||||
### 基础蓝牙权限
|
||||
```xml
|
||||
<!-- 基础蓝牙权限 (Android 11及以下) -->
|
||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
||||
|
||||
<!-- Android 12+ 新蓝牙权限 -->
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
||||
```
|
||||
|
||||
### 位置权限(仅Android 11及以下需要)
|
||||
```xml
|
||||
<!-- 位置权限 (Android 11及以下需要) -->
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"
|
||||
android:maxSdkVersion="30" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"
|
||||
android:maxSdkVersion="30" />
|
||||
```
|
||||
|
||||
### Android 12+ 特殊配置
|
||||
```xml
|
||||
<!-- Android 12+ 蓝牙扫描权限 (不用于定位) -->
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
|
||||
android:usesPermissionFlags="neverForLocation" />
|
||||
```
|
||||
|
||||
## 📱 运行时权限请求
|
||||
|
||||
### 动态权限检查
|
||||
应用会根据Android版本自动确定所需权限:
|
||||
|
||||
```kotlin
|
||||
// Android 12+ 使用新的蓝牙权限
|
||||
private val REQUIRED_PERMISSIONS: Array<String> = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
arrayOf(
|
||||
Manifest.permission.BLUETOOTH_SCAN,
|
||||
Manifest.permission.BLUETOOTH_CONNECT
|
||||
)
|
||||
} else {
|
||||
// Android 11及以下需要位置权限
|
||||
arrayOf(
|
||||
Manifest.permission.BLUETOOTH_SCAN,
|
||||
Manifest.permission.BLUETOOTH_CONNECT,
|
||||
Manifest.permission.ACCESS_FINE_LOCATION,
|
||||
Manifest.permission.ACCESS_COARSE_LOCATION
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### 权限状态显示
|
||||
应用会显示详细的权限状态:
|
||||
- ✅ 已授予权限
|
||||
- ❌ 缺少权限
|
||||
- 💡 权限请求提示
|
||||
|
||||
## 🔍 权限检查流程
|
||||
|
||||
### 1. 应用启动时检查
|
||||
- 显示当前权限状态
|
||||
- 提示用户授予必要权限
|
||||
|
||||
### 2. 蓝牙操作前检查
|
||||
- 自动检查所需权限
|
||||
- 如果缺少权限,自动请求
|
||||
- 显示权限请求结果
|
||||
|
||||
### 3. 权限结果处理
|
||||
- 显示已授予和被拒绝的权限
|
||||
- 提供手动设置指导
|
||||
- 根据权限状态决定是否继续操作
|
||||
|
||||
## 🚨 常见权限问题
|
||||
|
||||
### 1. 权限被拒绝
|
||||
**症状**: 扫描无结果,显示"权限被拒绝"
|
||||
**解决方案**:
|
||||
1. 进入应用设置 → 权限
|
||||
2. 手动开启蓝牙和位置权限
|
||||
3. 重启应用
|
||||
|
||||
### 2. Android 12+ 权限问题
|
||||
**症状**: 新设备上扫描失败
|
||||
**解决方案**:
|
||||
1. 确保使用新的蓝牙权限
|
||||
2. 检查`neverForLocation`标志
|
||||
3. 确认权限已正确授予
|
||||
|
||||
### 3. 位置权限问题
|
||||
**症状**: Android 11及以下设备扫描失败
|
||||
**解决方案**:
|
||||
1. 确保位置权限已授予
|
||||
2. 检查GPS是否开启(某些厂商系统需要)
|
||||
3. 重启蓝牙服务
|
||||
|
||||
## 📊 权限状态检查
|
||||
|
||||
### 应用内权限状态显示
|
||||
```
|
||||
权限状态:
|
||||
BLUETOOTH_SCAN: ✓
|
||||
BLUETOOTH_CONNECT: ✓
|
||||
ACCESS_FINE_LOCATION: ✓
|
||||
ACCESS_COARSE_LOCATION: ✓
|
||||
```
|
||||
|
||||
### 系统设置检查
|
||||
1. **设置 → 应用 → 本应用 → 权限**
|
||||
2. **设置 → 隐私 → 位置信息**
|
||||
3. **设置 → 蓝牙**
|
||||
|
||||
## 🛠️ 调试权限问题
|
||||
|
||||
### 1. 检查权限声明
|
||||
```bash
|
||||
# 确认AndroidManifest.xml中的权限声明正确
|
||||
# 检查maxSdkVersion和usesPermissionFlags
|
||||
```
|
||||
|
||||
### 2. 检查运行时权限
|
||||
```kotlin
|
||||
// 在代码中检查权限状态
|
||||
ContextCompat.checkSelfPermission(context, permission)
|
||||
```
|
||||
|
||||
### 3. 查看权限请求结果
|
||||
```kotlin
|
||||
// 在onRequestPermissionsResult中处理结果
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray)
|
||||
```
|
||||
|
||||
## 📝 权限配置清单
|
||||
|
||||
### AndroidManifest.xml 检查项
|
||||
- [ ] 基础蓝牙权限已声明
|
||||
- [ ] Android 12+ 新权限已声明
|
||||
- [ ] 位置权限限制到Android 11及以下
|
||||
- [ ] `neverForLocation`标志已设置
|
||||
- [ ] 蓝牙功能特性已声明
|
||||
|
||||
### 代码权限检查项
|
||||
- [ ] 动态权限检查已实现
|
||||
- [ ] 权限请求已处理
|
||||
- [ ] 权限结果已处理
|
||||
- [ ] 权限状态显示已实现
|
||||
|
||||
### 测试检查项
|
||||
- [ ] Android 11及以下设备测试通过
|
||||
- [ ] Android 12+ 设备测试通过
|
||||
- [ ] 权限拒绝场景测试通过
|
||||
- [ ] 权限授予后功能正常
|
||||
|
||||
## 🎯 针对目标设备的权限检查
|
||||
|
||||
### 目标设备: `A4:C3:37:86:9F:73`
|
||||
|
||||
1. **确认设备Android版本**
|
||||
2. **检查对应权限是否已授予**
|
||||
3. **验证权限配置是否正确**
|
||||
4. **测试扫描功能是否正常**
|
||||
|
||||
## 🚀 下一步操作
|
||||
|
||||
1. **重新编译应用**
|
||||
2. **在目标设备上测试权限**
|
||||
3. **确认权限授予后扫描正常**
|
||||
4. **如果仍有问题,检查设备特定设置**
|
||||
|
|
@ -0,0 +1,183 @@
|
|||
# 蓝牙调试指南 - SerialTest连接问题
|
||||
|
||||
## 问题描述
|
||||
- 电脑端:SerialTest作为蓝牙服务器运行
|
||||
- 手机端:Android应用作为客户端
|
||||
- 问题:客户端找不到服务器设备
|
||||
|
||||
## 第一步:检查手机蓝牙设置
|
||||
|
||||
### 1.1 确保蓝牙可见性
|
||||
1. **打开手机设置** → **蓝牙**
|
||||
2. **确保蓝牙已开启**
|
||||
3. **设置可见性**:
|
||||
- 点击"更多设置"或"高级设置"
|
||||
- 找到"可见性"或"可发现性"选项
|
||||
- 设置为"始终可见"或"可发现"
|
||||
|
||||
### 1.2 检查配对状态
|
||||
1. **查看已配对设备列表**
|
||||
2. **如果看到您的电脑设备**:
|
||||
- 点击设备名称
|
||||
- 选择"取消配对"
|
||||
- 重新配对
|
||||
|
||||
### 1.3 重置蓝牙设置
|
||||
1. **清除蓝牙缓存**:
|
||||
- 设置 → 应用管理 → 蓝牙
|
||||
- 清除数据和缓存
|
||||
2. **重启蓝牙**:
|
||||
- 关闭蓝牙
|
||||
- 等待10秒
|
||||
- 重新开启蓝牙
|
||||
|
||||
## 第二步:检查电脑端SerialTest设置
|
||||
|
||||
### 2.1 SerialTest配置
|
||||
1. **确保SerialTest正确配置**:
|
||||
- 选择正确的COM端口
|
||||
- 设置波特率(通常9600或115200)
|
||||
- 启用蓝牙服务器模式
|
||||
|
||||
### 2.2 电脑蓝牙设置
|
||||
1. **检查电脑蓝牙状态**:
|
||||
- 确保蓝牙已开启
|
||||
- 设置为"可发现"模式
|
||||
2. **查看蓝牙设备**:
|
||||
- 控制面板 → 设备和打印机
|
||||
- 确认蓝牙适配器正常工作
|
||||
|
||||
## 第三步:使用应用进行调试
|
||||
|
||||
### 3.1 扫描测试
|
||||
1. **启动Android应用**
|
||||
2. **点击"连接蓝牙"按钮**(普通点击,不是长按)
|
||||
3. **观察扫描结果**:
|
||||
- 查看是否发现您的电脑设备
|
||||
- 记录设备名称和MAC地址
|
||||
|
||||
### 3.2 直接连接测试
|
||||
1. **长按"连接蓝牙"按钮**
|
||||
2. **输入电脑MAC地址**:`60:E9:AA:30:8B:0A`
|
||||
3. **点击连接**
|
||||
4. **观察连接状态**
|
||||
|
||||
### 3.3 查看详细日志
|
||||
在Android Studio的Logcat中查看:
|
||||
- 过滤标签:`BluetoothManager`
|
||||
- 查找扫描和连接相关的日志
|
||||
|
||||
## 第四步:常见解决方案
|
||||
|
||||
### 4.1 如果扫描不到设备
|
||||
1. **重启蓝牙服务**:
|
||||
- 电脑:重启蓝牙适配器
|
||||
- 手机:重启蓝牙
|
||||
2. **检查距离**:确保设备在10米范围内
|
||||
3. **检查干扰**:远离其他蓝牙设备
|
||||
|
||||
### 4.2 如果连接失败
|
||||
1. **重新配对设备**:
|
||||
- 在手机蓝牙设置中删除电脑设备
|
||||
- 在电脑蓝牙设置中删除手机设备
|
||||
- 重新配对
|
||||
2. **检查SerialTest状态**:
|
||||
- 确保SerialTest正在运行
|
||||
- 检查是否有错误信息
|
||||
|
||||
### 4.3 如果连接成功但无法通信
|
||||
1. **检查SerialTest配置**:
|
||||
- 确认波特率设置
|
||||
- 确认数据格式设置
|
||||
2. **测试数据发送**:
|
||||
- 使用应用发送测试数据
|
||||
- 在SerialTest中查看是否收到数据
|
||||
|
||||
## 第五步:高级调试
|
||||
|
||||
### 5.1 使用系统蓝牙设置
|
||||
1. **在手机蓝牙设置中手动连接**:
|
||||
- 扫描设备
|
||||
- 选择您的电脑设备
|
||||
- 输入配对码(通常是0000或1234)
|
||||
|
||||
### 5.2 检查蓝牙协议
|
||||
1. **确认SerialTest使用的协议**:
|
||||
- SPP(串口协议)
|
||||
- RFCOMM
|
||||
- 其他协议
|
||||
2. **确认应用支持的协议**
|
||||
|
||||
### 5.3 使用其他蓝牙工具测试
|
||||
1. **下载蓝牙调试工具**:
|
||||
- nRF Connect(推荐)
|
||||
- Bluetooth Scanner
|
||||
2. **测试连接**:
|
||||
- 扫描设备
|
||||
- 尝试连接
|
||||
- 查看服务列表
|
||||
|
||||
## 第六步:应用调试功能
|
||||
|
||||
### 6.1 增强扫描功能
|
||||
应用现在支持:
|
||||
- **BLE扫描**:低功耗蓝牙扫描
|
||||
- **传统蓝牙扫描**:经典蓝牙扫描
|
||||
- **智能检测**:自动检测设备类型
|
||||
|
||||
### 6.2 连接状态监控
|
||||
应用会显示:
|
||||
- ✅ **连接成功**:设备已连接
|
||||
- ❌ **连接失败**:详细的错误信息
|
||||
- 🔍 **服务发现**:发现的服务和特征
|
||||
- 📡 **数据通道**:通信通道状态
|
||||
|
||||
## 第七步:测试步骤
|
||||
|
||||
### 7.1 基础测试
|
||||
1. **启动SerialTest** → 配置服务器
|
||||
2. **启动Android应用** → 扫描设备
|
||||
3. **连接设备** → 观察状态
|
||||
4. **发送测试数据** → 验证通信
|
||||
|
||||
### 7.2 数据通信测试
|
||||
1. **发送ECG测试数据**
|
||||
2. **发送心跳包**
|
||||
3. **发送设备信息查询**
|
||||
4. **观察SerialTest接收情况**
|
||||
|
||||
## 常见错误及解决方案
|
||||
|
||||
### 错误1:扫描不到设备
|
||||
**解决方案**:
|
||||
- 检查设备可见性
|
||||
- 重启蓝牙服务
|
||||
- 检查距离和干扰
|
||||
|
||||
### 错误2:连接超时
|
||||
**解决方案**:
|
||||
- 重新配对设备
|
||||
- 检查SerialTest状态
|
||||
- 确认MAC地址正确
|
||||
|
||||
### 错误3:服务发现失败
|
||||
**解决方案**:
|
||||
- 检查设备协议兼容性
|
||||
- 使用系统蓝牙设置连接
|
||||
- 查看详细错误日志
|
||||
|
||||
## 下一步操作
|
||||
|
||||
1. **按照上述步骤逐一检查**
|
||||
2. **记录每个步骤的结果**
|
||||
3. **查看Android Studio的Logcat日志**
|
||||
4. **如果仍有问题,提供详细的错误信息**
|
||||
|
||||
## 技术支持
|
||||
|
||||
如果问题仍然存在,请提供:
|
||||
1. **手机型号和Android版本**
|
||||
2. **电脑操作系统版本**
|
||||
3. **SerialTest版本和配置**
|
||||
4. **详细的错误日志**
|
||||
5. **扫描和连接过程的截图**
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
# 蓝牙连接测试说明
|
||||
|
||||
## 数据流概述
|
||||
|
||||
您的应用数据流如下:
|
||||
|
||||
1. **蓝牙连接** → **数据接收** → **数据解析** → **信号处理** → **图表显示**
|
||||
|
||||
### 详细流程:
|
||||
- **蓝牙管理器** (BluetoothManager) 负责设备连接和数据接收
|
||||
- **数据管理器** (DataManager) 负责数据解析和信号处理
|
||||
- **实时数据回调** 将处理后的数据发送到图表显示
|
||||
- **ECG图表视图** 实时显示ECG波形
|
||||
|
||||
## 测试步骤
|
||||
|
||||
### 1. 直接连接测试
|
||||
1. 启动应用
|
||||
2. **长按"连接蓝牙"按钮** → 弹出直接连接对话框
|
||||
3. 输入您的电脑MAC地址:`60:E9:AA:30:8B:0A`(使用冒号分隔符)
|
||||
4. 点击"连接"按钮
|
||||
5. 观察连接状态
|
||||
|
||||
### 2. 数据收发测试
|
||||
1. 连接成功后,**长按"发送指令"按钮** → 弹出测试数据对话框
|
||||
2. 选择测试选项:
|
||||
- **发送ECG测试数据**:发送模拟ECG数据包
|
||||
- **发送心跳包**:发送简单的心跳数据
|
||||
- **发送设备信息查询**:查询设备信息
|
||||
- **发送自定义测试数据**:发送自定义十六进制数据
|
||||
|
||||
### 3. 图表显示测试
|
||||
1. 点击"启动程序"按钮 → 立即生成测试数据并显示图表
|
||||
2. 长按"启动程序"按钮 → 生成更复杂的ECG波形测试
|
||||
3. 观察ECG双视图的实时更新
|
||||
|
||||
## 测试功能说明
|
||||
|
||||
### 蓝牙连接功能
|
||||
- **扫描设备**:自动扫描附近的蓝牙设备
|
||||
- **直接连接**:通过MAC地址直接连接指定设备
|
||||
- **连接状态**:实时显示连接状态和错误信息
|
||||
|
||||
### 数据发送功能
|
||||
- **ECG测试数据**:生成符合ECG格式的模拟数据包
|
||||
- **心跳包**:简单的连接测试数据
|
||||
- **自定义数据**:支持十六进制格式的自定义数据
|
||||
|
||||
### 数据接收功能
|
||||
- **实时接收**:自动接收蓝牙数据
|
||||
- **数据解析**:使用原生解析器解析数据包
|
||||
- **信号处理**:应用滤波和信号处理算法
|
||||
- **实时显示**:立即更新图表显示
|
||||
|
||||
## 调试信息
|
||||
|
||||
应用会在日志中输出详细的调试信息:
|
||||
- 蓝牙连接状态
|
||||
- 数据接收情况
|
||||
- 数据解析结果
|
||||
- 信号处理进度
|
||||
- 图表更新状态
|
||||
|
||||
## 常见问题
|
||||
|
||||
### 连接失败
|
||||
1. 检查蓝牙权限是否已授予
|
||||
2. 确认设备MAC地址正确(使用冒号分隔符,如:60:E9:AA:30:8B:0A)
|
||||
3. 确保目标设备在范围内且可发现
|
||||
|
||||
### 数据接收问题
|
||||
1. 检查连接状态
|
||||
2. 查看日志中的错误信息
|
||||
3. 尝试发送测试数据验证连接
|
||||
|
||||
### 图表不显示
|
||||
1. 点击"启动程序"按钮生成测试数据
|
||||
2. 检查图表容器是否可见
|
||||
3. 查看数据回调是否正常
|
||||
|
||||
## 测试建议
|
||||
|
||||
1. **先测试连接**:确保能成功连接到您的电脑
|
||||
2. **再测试发送**:发送各种测试数据验证连接稳定性
|
||||
3. **最后测试接收**:验证数据接收和图表显示功能
|
||||
4. **查看日志**:通过Android Studio的Logcat查看详细调试信息
|
||||
|
||||
## 数据格式
|
||||
|
||||
### ECG测试数据包格式
|
||||
```
|
||||
AA 55 01 [长度低字节] [长度高字节] [ECG数据...] [校验和]
|
||||
```
|
||||
|
||||
### 自定义数据格式
|
||||
支持十六进制格式,如:`01 02 03 04 05`
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
# 连接状态显示增强
|
||||
|
||||
## 连接成功提示
|
||||
|
||||
现在当蓝牙设备连接成功时,应用会显示详细的连接状态信息:
|
||||
|
||||
### 主要提示信息:
|
||||
- ✅ **设备已连接**: [设备名称/地址]
|
||||
- 🎉 **连接成功!设备信息:**
|
||||
- 设备名称: [设备名称]
|
||||
- 设备地址: [MAC地址]
|
||||
- 设备类型: [设备类型]
|
||||
- 📡 **数据通道已建立,可以开始收发数据**
|
||||
- 📊 **ECG图表已准备就绪,请点击'发送指令'按钮开始接收数据**
|
||||
|
||||
### 服务发现信息:
|
||||
- 🔍 **服务发现成功**
|
||||
- 📋 **发现 X 个服务**
|
||||
- 📡 **数据通道已建立,可以发送指令开始接收数据**
|
||||
- 💡 **提示: 长按'发送指令'按钮可以发送测试数据**
|
||||
|
||||
## 连接状态变化
|
||||
|
||||
### 连接成功时:
|
||||
1. **按钮状态变化**:
|
||||
- "连接蓝牙" → "断开蓝牙"
|
||||
- 按钮颜色变为红色 (#F44336)
|
||||
- "发送指令"按钮启用并变为蓝色
|
||||
|
||||
2. **图表显示**:
|
||||
- ECG图表容器自动显示
|
||||
- 准备接收数据
|
||||
|
||||
3. **状态信息**:
|
||||
- 显示详细的设备信息
|
||||
- 提供下一步操作提示
|
||||
|
||||
### 连接断开时:
|
||||
1. **按钮状态变化**:
|
||||
- "断开蓝牙" → "连接蓝牙"
|
||||
- 按钮颜色变为绿色 (#4CAF50)
|
||||
- "发送指令"按钮禁用并变为灰色
|
||||
|
||||
2. **状态信息**:
|
||||
- 显示断开连接提示
|
||||
|
||||
## 测试步骤
|
||||
|
||||
### 1. 连接测试
|
||||
1. 启动应用
|
||||
2. **长按"连接蓝牙"按钮**
|
||||
3. 输入MAC地址:`60:E9:AA:30:8B:0A`
|
||||
4. 点击"连接"
|
||||
5. **观察连接成功提示**:
|
||||
- ✅ 设备已连接
|
||||
- 🎉 连接成功!设备信息
|
||||
- 📡 数据通道已建立
|
||||
|
||||
### 2. 验证连接状态
|
||||
1. 检查按钮状态变化
|
||||
2. 查看状态信息区域的详细提示
|
||||
3. 确认ECG图表容器已显示
|
||||
|
||||
### 3. 测试数据收发
|
||||
1. **长按"发送指令"按钮**发送测试数据
|
||||
2. 观察数据接收状态
|
||||
3. 查看图表显示效果
|
||||
|
||||
## 调试信息
|
||||
|
||||
### 日志标签:
|
||||
- `MainActivity`: 主要操作日志
|
||||
- `BluetoothManager`: 蓝牙连接日志
|
||||
|
||||
### 状态显示:
|
||||
- 连接状态实时更新
|
||||
- 设备信息详细显示
|
||||
- 操作提示清晰明确
|
||||
|
||||
## 常见问题
|
||||
|
||||
### 连接成功但没有显示提示:
|
||||
1. 检查状态信息区域是否可见
|
||||
2. 查看日志确认回调是否正常
|
||||
3. 确认UI线程更新是否成功
|
||||
|
||||
### 设备信息不完整:
|
||||
1. 某些设备可能不提供完整信息
|
||||
2. 应用会显示可用的信息
|
||||
3. 不影响连接功能
|
||||
|
||||
## 下一步操作
|
||||
|
||||
连接成功后,您可以:
|
||||
1. **发送测试数据**:长按"发送指令"按钮
|
||||
2. **查看图表显示**:点击"启动程序"按钮
|
||||
3. **测试数据接收**:观察实时数据流
|
||||
4. **应用信号处理**:测试陷波滤波器等功能
|
||||
|
|
@ -0,0 +1,164 @@
|
|||
# 连接超时故障排除指南
|
||||
|
||||
## 问题描述
|
||||
应用尝试连接到目标设备 `A4:C3:37:86:9F:73` 时出现连接超时错误。
|
||||
|
||||
## 日志分析
|
||||
|
||||
### 关键日志信息
|
||||
```
|
||||
尝试直接连接到设备: A4:C3:37:86:9F:73
|
||||
on_create_connection_timeout, address: a4:c3:37:86:9f:73
|
||||
Connection failed le remote:a4:c3:37:86:9f:73
|
||||
status=147 clientIf=16 connected=false
|
||||
```
|
||||
|
||||
### 问题分析
|
||||
- **连接启动成功** - 应用成功发起连接请求
|
||||
- **30秒超时** - 连接在30秒后超时
|
||||
- **状态码147** - 表示连接失败
|
||||
- **LE连接失败** - 低功耗蓝牙连接失败
|
||||
|
||||
## 可能原因及解决方案
|
||||
|
||||
### 1. 目标设备蓝牙设置问题
|
||||
|
||||
#### 检查项目:
|
||||
- [ ] 蓝牙已开启
|
||||
- [ ] 设置为"可发现"模式
|
||||
- [ ] 未与其他设备连接
|
||||
- [ ] 蓝牙服务正常运行
|
||||
|
||||
#### 解决步骤:
|
||||
1. **打开目标手机设置**
|
||||
2. **进入蓝牙设置**
|
||||
3. **确保蓝牙已开启**
|
||||
4. **设置为"始终可见"或"可发现"**
|
||||
5. **断开其他蓝牙连接**
|
||||
|
||||
### 2. 设备距离和干扰
|
||||
|
||||
#### 检查项目:
|
||||
- [ ] 设备距离在10米内
|
||||
- [ ] 无金属屏蔽物
|
||||
- [ ] 远离其他蓝牙设备
|
||||
- [ ] 无WiFi干扰
|
||||
|
||||
#### 解决步骤:
|
||||
1. **将两台设备靠近**(1-2米内)
|
||||
2. **移除金属物品**
|
||||
3. **关闭其他蓝牙设备**
|
||||
4. **尝试不同位置**
|
||||
|
||||
### 3. 系统蓝牙服务问题
|
||||
|
||||
#### 检查项目:
|
||||
- [ ] 系统蓝牙服务正常
|
||||
- [ ] 蓝牙权限已授予
|
||||
- [ ] 无其他应用占用蓝牙
|
||||
|
||||
#### 解决步骤:
|
||||
1. **重启两台设备的蓝牙**
|
||||
2. **检查应用权限**
|
||||
3. **关闭其他蓝牙应用**
|
||||
4. **重启设备**
|
||||
|
||||
### 4. 设备兼容性问题
|
||||
|
||||
#### 检查项目:
|
||||
- [ ] 目标设备支持BLE
|
||||
- [ ] 设备型号和Android版本
|
||||
- [ ] 蓝牙协议兼容性
|
||||
|
||||
#### 解决步骤:
|
||||
1. **确认设备支持BLE**
|
||||
2. **更新系统版本**
|
||||
3. **尝试传统蓝牙连接**
|
||||
4. **使用系统蓝牙设置测试**
|
||||
|
||||
## 增强的连接功能
|
||||
|
||||
### 新增功能:
|
||||
1. **详细状态提示** - 显示连接进度和注意事项
|
||||
2. **自动重试机制** - 超时后自动尝试传统蓝牙连接
|
||||
3. **错误状态码解析** - 根据状态码提供具体建议
|
||||
4. **连接超时设置** - 30秒BLE + 15秒传统蓝牙
|
||||
|
||||
### 连接流程:
|
||||
```
|
||||
开始连接 → BLE连接(30秒) → 超时重试 → 传统蓝牙(15秒) → 最终结果
|
||||
```
|
||||
|
||||
## 测试步骤
|
||||
|
||||
### 第一步:基础检查
|
||||
1. **确认目标设备蓝牙开启**
|
||||
2. **设置为可发现模式**
|
||||
3. **断开其他连接**
|
||||
4. **将设备靠近**
|
||||
|
||||
### 第二步:系统测试
|
||||
1. **在系统蓝牙设置中扫描**
|
||||
2. **查看是否发现目标设备**
|
||||
3. **尝试手动配对**
|
||||
4. **记录配对结果**
|
||||
|
||||
### 第三步:应用测试
|
||||
1. **启动应用**
|
||||
2. **长按"连接蓝牙"按钮**
|
||||
3. **输入MAC地址:A4:C3:37:86:9F:73**
|
||||
4. **观察连接过程**
|
||||
|
||||
### 第四步:日志分析
|
||||
1. **查看应用状态信息**
|
||||
2. **检查Logcat日志**
|
||||
3. **记录错误信息**
|
||||
4. **分析失败原因**
|
||||
|
||||
## 常见错误码及含义
|
||||
|
||||
| 状态码 | 含义 | 解决方案 |
|
||||
|--------|------|----------|
|
||||
| 0 | 正常断开 | 检查设备状态 |
|
||||
| 8 | 连接超时 | 检查设备可见性和距离 |
|
||||
| 19 | 连接被拒绝 | 检查设备是否忙 |
|
||||
| 22 | 连接失败 | 检查协议兼容性 |
|
||||
| 147 | 连接失败 | 检查设备状态和设置 |
|
||||
|
||||
## 调试建议
|
||||
|
||||
### 1. 使用系统蓝牙测试
|
||||
- 在系统蓝牙设置中手动连接
|
||||
- 验证设备是否可见和可连接
|
||||
- 确认配对是否成功
|
||||
|
||||
### 2. 检查设备信息
|
||||
- 目标设备型号和Android版本
|
||||
- 蓝牙芯片类型
|
||||
- 支持的蓝牙协议
|
||||
|
||||
### 3. 环境测试
|
||||
- 尝试不同位置
|
||||
- 检查环境干扰
|
||||
- 测试不同距离
|
||||
|
||||
### 4. 应用调试
|
||||
- 查看详细日志
|
||||
- 观察状态变化
|
||||
- 记录错误信息
|
||||
|
||||
## 下一步操作
|
||||
|
||||
1. **按照上述步骤逐一检查**
|
||||
2. **使用系统蓝牙设置测试连接**
|
||||
3. **记录所有测试结果**
|
||||
4. **如果问题持续,提供详细设备信息**
|
||||
|
||||
## 需要提供的信息
|
||||
|
||||
如果问题仍然存在,请提供:
|
||||
1. **目标设备型号和Android版本**
|
||||
2. **系统蓝牙设置测试结果**
|
||||
3. **详细的错误日志**
|
||||
4. **设备环境信息**
|
||||
5. **测试步骤和结果**
|
||||
Loading…
Reference in New Issue