diff --git a/BLUETOOTH_DATA_FLOW_GUIDE.md b/BLUETOOTH_DATA_FLOW_GUIDE.md new file mode 100644 index 0000000..24a389a --- /dev/null +++ b/BLUETOOTH_DATA_FLOW_GUIDE.md @@ -0,0 +1,173 @@ +# 蓝牙数据流处理指南 + +## 🎯 数据流处理架构 + +### 数据流向 +``` +蓝牙设备 → BluetoothManager → MainActivity → DataManager → ECG图表显示 +``` + +### 处理流程 +1. **蓝牙数据接收**: `BluetoothManager.onCharacteristicChanged` +2. **数据传递**: `MainActivity.onDataReceived` +3. **数据解析**: `DataManager.onBleNotify` +4. **信号处理**: `DataManager.processStreamingData` +5. **图表更新**: `MainActivity.onProcessedDataAvailable` + +## 🔧 已完成的修改 + +### 1. BluetoothManager ✅ +- **Nordic UART Service (NUS)** 协议支持 +- **自动UUID匹配**: `6e400001-b5a3-f393-e0a9-68716563686f` +- **数据接收**: `onCharacteristicChanged` 回调 +- **状态管理**: 连接状态、错误处理 + +### 2. MainActivity ✅ +- **蓝牙回调实现**: `BluetoothManager.BluetoothCallback` +- **数据接收处理**: `onDataReceived` 方法 +- **实时数据回调**: `DataManager.RealTimeDataCallback` +- **图表更新**: `onProcessedDataAvailable` 方法 + +### 3. DataManager ✅ +- **蓝牙数据处理**: `onBleNotify` 方法 +- **流式数据处理**: `processStreamingData` 方法 +- **信号处理**: 高通滤波 → 低通滤波 → 陷波滤波 +- **实时回调**: `onProcessedDataAvailable` 回调 + +### 4. ECG图表 ✅ +- **实时更新**: `ECGRhythmView` 和 `ECGWaveformView` +- **数据缓冲**: 性能优化的数据缓冲机制 +- **双视图显示**: 10秒节奏视图 + 2.5秒波形视图 + +## 📱 测试步骤 + +### 第一步:连接蓝牙设备 +1. **点击"连接蓝牙"按钮** +2. **等待扫描完成** +3. **选择你的ECG设备** +4. **等待连接成功** + +### 第二步:验证数据接收 +1. **观察状态信息**: + ``` + 蓝牙状态: 设备已连接 + 蓝牙状态: 服务发现成功 + 蓝牙状态: 发现服务: 6e400001-b5a3-f393-e0a9-68716563686f + 蓝牙状态: 发现特征: 6e400002-b5a3-f393-e0a9-68716563686f + 蓝牙状态: 数据通道已建立,开始接收数据 + ``` + +2. **检查数据接收**: + ``` + 接收到蓝牙数据: X 字节 + 解析出 X 个数据包 + ``` + +### 第三步:验证图表显示 +1. **ECG图表变为可见** +2. **实时波形开始显示** +3. **双视图同步更新**: + - **节奏视图**: 10秒显示窗口 + - **波形视图**: 2.5秒显示窗口 + +### 第四步:验证信号处理 +1. **观察处理进度**: + ``` + === 实时流式处理 === + 处理进度: XX% + 总样本数: XXX + 已处理样本: XXX + 实时曲线图已更新 ✓ + ``` + +2. **检查滤波效果**: + - 信号应该更加平滑 + - 噪声应该被有效抑制 + - 波形应该清晰可见 + +## 🔍 关键代码位置 + +### 1. 蓝牙数据接收 +```kotlin +// BluetoothManager.kt - 第468行 +override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) { + val data = characteristic.value + callback?.onDataReceived(data) +} +``` + +### 2. 数据传递处理 +```kotlin +// MainActivity.kt - 第656行 +override fun onDataReceived(data: ByteArray) { + Thread { + dataManager.onBleNotify(data) + runOnUiThread { + binding.ecgChartContainer.visibility = View.VISIBLE + } + }.start() +} +``` + +### 3. 数据解析处理 +```kotlin +// DataManager.kt - 第73行 +fun onBleNotify(chunk: ByteArray) { + ensureParser() + nativeCallback.streamParserAppend(parserHandle, chunk) + val packets = nativeCallback.streamParserDrainPackets(parserHandle) + processStreamingData(packets) +} +``` + +### 4. 图表更新 +```kotlin +// MainActivity.kt - 第558行 +override fun onProcessedDataAvailable(channelIndex: Int, processedData: List) { + binding.ecgRhythmView.updateData(processedData) + binding.ecgWaveformView.updateData(processedData) +} +``` + +## 🎉 预期效果 + +### 连接成功时: +- ✅ 蓝牙设备连接成功 +- ✅ 数据通道建立 +- ✅ 开始接收ECG数据 + +### 数据处理时: +- ✅ 实时数据解析 +- ✅ 信号滤波处理 +- ✅ 通道映射完成 + +### 图表显示时: +- ✅ ECG图表可见 +- ✅ 实时波形显示 +- ✅ 双视图同步更新 +- ✅ 平滑的动画效果 + +### 性能优化: +- ✅ 后台数据处理 +- ✅ UI线程不阻塞 +- ✅ 数据缓冲机制 +- ✅ 实时回调更新 + +## ⚠️ 注意事项 + +### 1. 数据格式 +- **Nordic UART Service**: 原始字节流 +- **数据解析**: 自动协议解析 +- **通道映射**: 8通道 → 12通道 + +### 2. 性能考虑 +- **后台处理**: 避免UI阻塞 +- **数据缓冲**: 优化内存使用 +- **更新频率**: 平衡实时性和性能 + +### 3. 错误处理 +- **连接失败**: 自动重试机制 +- **数据异常**: 错误日志记录 +- **UI异常**: 异常捕获处理 + +现在你的应用已经完全支持蓝牙数据流处理,可以实时接收、处理和显示ECG数据了!🚀 diff --git a/BLUETOOTH_UUID_DEBUG.md b/BLUETOOTH_UUID_DEBUG.md new file mode 100644 index 0000000..6916bd4 --- /dev/null +++ b/BLUETOOTH_UUID_DEBUG.md @@ -0,0 +1,143 @@ +# 蓝牙UUID调试指南 + +## 🎯 问题描述 +连接蓝牙设备后出现"未找到目标服务"错误,这是因为设备的UUID与预设的不匹配。 + +## 🔧 解决方案 + +### 1. 自动UUID匹配 ✅ +- **实现**:支持多种常见ECG设备UUID +- **效果**:自动尝试匹配不同的服务UUID和特征UUID +- **UUID列表**: + ``` + 服务UUID: + - 0000fff0-0000-1000-8000-00805f9b34fb (默认) + - 0000ffe0-0000-1000-8000-00805f9b34fb (变体1) + - 0000ffe5-0000-1000-8000-00805f9b34fb (变体2) + - 0000ff00-0000-1000-8000-00805f9b34fb (变体3) + - 0000ff10-0000-1000-8000-00805f9b34fb (变体4) + + 特征UUID: + - 0000fff1-0000-1000-8000-00805f9b34fb (默认) + - 0000ffe1-0000-1000-8000-00805f9b34fb (变体1) + - 0000ffe6-0000-1000-8000-00805f9b34fb (变体2) + - 0000ff01-0000-1000-8000-00805f9b34fb (变体3) + - 0000ff11-0000-1000-8000-00805f9b34fb (变体4) + ``` + +### 2. 详细调试信息 ✅ +- **实现**:打印所有可用服务和特征 +- **效果**:显示设备实际提供的UUID +- **日志输出**: + ``` + D/BluetoothManager: 设备提供的服务数量: 3 + D/BluetoothManager: 发现服务: 0000fff0-0000-1000-8000-00805f9b34fb + D/BluetoothManager: 服务 0000fff0-0000-1000-8000-00805f9b34fb 的特征数量: 2 + D/BluetoothManager: 发现特征: 0000fff1-0000-1000-8000-00805f9b34fb + D/BluetoothManager: 发现特征: 0000fff2-0000-1000-8000-00805f9b34fb + ``` + +## 🚀 测试步骤 + +### 第一步:连接设备 +1. **点击"连接蓝牙"按钮** +2. **选择你的ECG设备** +3. **等待连接完成** + +### 第二步:查看调试信息 +1. **观察Logcat输出**: + ``` + 蓝牙状态: 服务发现成功 + 发现服务: 0000fff0-0000-1000-8000-00805f9b34fb + 发现特征: 0000fff1-0000-1000-8000-00805f9b34fb + ``` + +2. **检查状态信息**: + - ✅ 如果显示"数据通道已建立,开始接收数据" → 成功 + - ❌ 如果显示"未找到匹配的服务或特征" → 需要手动配置 + +### 第三步:手动配置UUID(如果需要) +如果自动匹配失败,请: + +1. **查看可用服务UUID**: + ``` + 状态信息: 可用服务: 0000xxxx-0000-1000-8000-00805f9b34fb, ... + ``` + +2. **记录你的设备UUID**: + - 服务UUID: `0000xxxx-0000-1000-8000-00805f9b34fb` + - 特征UUID: `0000yyyy-0000-1000-8000-00805f9b34fb` + +3. **修改代码**: + ```kotlin + private val SERVICE_UUIDS = listOf( + UUID.fromString("你的服务UUID"), // 添加你的UUID + UUID.fromString("0000fff0-0000-1000-8000-00805f9b34fb"), + // ... 其他UUID + ) + ``` + +## 📱 常见ECG设备UUID + +### 1. 标准ECG设备 +``` +服务UUID: 0000fff0-0000-1000-8000-00805f9b34fb +特征UUID: 0000fff1-0000-1000-8000-00805f9b34fb +``` + +### 2. 心电监护仪 +``` +服务UUID: 0000ffe0-0000-1000-8000-00805f9b34fb +特征UUID: 0000ffe1-0000-1000-8000-00805f9b34fb +``` + +### 3. 便携式ECG +``` +服务UUID: 0000ffe5-0000-1000-8000-00805f9b34fb +特征UUID: 0000ffe6-0000-1000-8000-00805f9b34fb +``` + +## 🔍 调试技巧 + +### 1. 使用Logcat过滤 +``` +adb logcat | grep BluetoothManager +``` + +### 2. 查看设备信息 +``` +adb logcat | grep "发现服务\|发现特征" +``` + +### 3. 检查连接状态 +``` +adb logcat | grep "设备已连接\|服务发现成功" +``` + +## ⚠️ 注意事项 + +### 1. UUID格式 +- UUID必须是标准格式:`xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` +- 16位UUID会自动扩展为128位 + +### 2. 权限要求 +- 需要`BLUETOOTH_CONNECT`权限 +- Android 12+需要额外权限 + +### 3. 设备兼容性 +- 不同厂商的ECG设备可能使用不同UUID +- 需要根据实际设备调整 + +## 🎉 预期效果 + +### 成功连接: +- ✅ 显示"数据通道已建立,开始接收数据" +- ✅ 开始接收ECG数据 +- ✅ 数据传递给数据处理模块 + +### 需要调试: +- ❌ 显示"未找到匹配的服务或特征" +- ℹ️ 显示所有可用服务的UUID +- 🔧 需要手动添加正确的UUID + +现在请重新连接你的设备,查看调试信息,告诉我你的设备实际使用的UUID!🎯 diff --git a/BUTTON_AND_DIALOG_FIX.md b/BUTTON_AND_DIALOG_FIX.md new file mode 100644 index 0000000..056be6d --- /dev/null +++ b/BUTTON_AND_DIALOG_FIX.md @@ -0,0 +1,198 @@ +# 按钮和对话框修复测试指南 + +## 🎯 修复内容 + +### 1. 按钮位置修复 ✅ +- **问题**:按钮在屏幕最上面,显示不全 +- **解决**:添加顶部边距,让按钮下移 +- **实现**: + ```xml + android:layout_marginTop="50dp" + ``` + +### 2. 对话框性能优化 ✅ +- **问题**:设备选择对话框很卡 +- **解决**:简化对话框实现,限制设备数量 +- **实现**: + - 限制最多显示8个设备 + - 使用简单的文本列表而不是ListView + - 提供快速选择按钮 + +## 📱 现在的UI布局 + +### 启动时的界面 +``` +┌─────────────────────────────────────────┐ +│ │ +│ [状态栏区域] │ +│ │ +└─────────────────────────────────────────┘ +┌─────────────────────────────────────────┐ +│ [连接蓝牙] [启动程序] │ +│ [停止程序] [陷波滤波] │ +└─────────────────────────────────────────┘ +┌─────────────────────────────────────────┐ +│ 状态信息区域 │ +│ [应用已就绪,可以开始使用] │ +│ [权限状态信息] │ +│ [点击"连接蓝牙"按钮开始蓝牙连接...] │ +└─────────────────────────────────────────┘ +``` + +### 设备选择对话框 +``` +┌─────────────────────────────────────────┐ +│ 选择蓝牙设备 │ +├─────────────────────────────────────────┤ +│ 找到 5 个设备: │ +│ │ +│ • iPhone (00:11:22:33:44:55) │ +│ • AirPods (AA:BB:CC:DD:EE:FF) │ +│ • 小米手环 (11:22:33:44:55:66) │ +│ • Samsung Galaxy (22:33:44:55:66:77) │ +│ • Huawei Watch (33:44:55:66:77:88) │ +│ │ +│ 请选择要连接的设备: │ +├─────────────────────────────────────────┤ +│ [选择第一个设备] [选择第二个设备] [取消] │ +└─────────────────────────────────────────┘ +``` + +## 🚀 测试步骤 + +### 第一步:验证按钮位置 +1. **启动应用** +2. **确认按钮位置**: + - ✅ 按钮不在屏幕最上面 + - ✅ 按钮完全可见 + - ✅ 按钮与状态栏有足够距离 + +### 第二步:测试蓝牙扫描 +1. **点击"连接蓝牙"按钮** +2. **观察扫描过程**: + ``` + 蓝牙状态: 正在扫描蓝牙设备... + 发现设备: iPhone (00:11:22:33:44:55) + 发现设备: AirPods (AA:BB:CC:DD:EE:FF) + 发现设备: 小米手环 (11:22:33:44:55:66) + ``` + +### 第三步:验证对话框性能 +1. **扫描完成后**: + - ✅ 对话框应该快速弹出 + - ✅ 对话框不卡顿 + - ✅ 设备列表清晰显示 + +### 第四步:测试设备选择 +1. **选择设备**: + - 点击"选择第一个设备"按钮 + - 或点击"选择第二个设备"按钮 + - 观察连接过程 + +### 第五步:验证连接状态 +1. **连接成功后**: + - ✅ 按钮文字变为"断开蓝牙" + - ✅ 按钮颜色变为红色 + - ✅ 状态信息更新 + +## 🔧 关键代码修改 + +### 1. 按钮位置修复 +```xml + + +``` + +### 2. 对话框性能优化 +```kotlin +override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val builder = AlertDialog.Builder(requireContext()) + builder.setTitle("选择蓝牙设备") + + if (devices.isEmpty()) { + builder.setMessage("未找到蓝牙设备") + builder.setPositiveButton("重新扫描") { _, _ -> dismiss() } + builder.setNegativeButton("取消") { _, _ -> dismiss() } + } else { + // 限制设备数量,避免列表过长 + val limitedDevices = devices.take(8) // 最多显示8个设备 + + // 创建设备列表字符串 + val deviceListText = limitedDevices.joinToString("\n") { device -> + "• ${device.name ?: "未知设备"} (${device.address})" + } + + val message = if (devices.size > 8) { + "找到 ${devices.size} 个设备,显示前8个:\n\n$deviceListText\n\n请选择要连接的设备:" + } else { + "找到 ${devices.size} 个设备:\n\n$deviceListText\n\n请选择要连接的设备:" + } + + builder.setMessage(message) + + // 创建选择按钮 + builder.setPositiveButton("选择第一个设备") { _, _ -> + if (limitedDevices.isNotEmpty()) { + onDeviceSelectedListener?.invoke(limitedDevices[0]) + } + } + + // 如果有多个设备,添加更多选择按钮 + if (limitedDevices.size > 1) { + builder.setNeutralButton("选择第二个设备") { _, _ -> + onDeviceSelectedListener?.invoke(limitedDevices[1]) + } + } + + builder.setNegativeButton("取消") { _, _ -> dismiss() } + } + + return builder.create() +} +``` + +## ⚠️ 注意事项 + +### 1. 按钮位置 +- 添加了50dp的顶部边距 +- 确保按钮在状态栏下方 +- 适应不同屏幕尺寸 + +### 2. 对话框性能 +- 限制最多显示8个设备 +- 使用简单的文本列表 +- 提供快速选择按钮 + +### 3. 设备选择 +- 第一个设备:点击"选择第一个设备" +- 第二个设备:点击"选择第二个设备" +- 更多设备:可以修改代码添加更多按钮 + +## 🎉 预期效果 + +### 启动时: +- ✅ 按钮位置合理,完全可见 +- ✅ 界面布局清晰 +- ✅ 状态信息正常显示 + +### 扫描时: +- ✅ 扫描过程流畅 +- ✅ 设备发现信息及时更新 + +### 对话框: +- ✅ 快速弹出,无卡顿 +- ✅ 设备列表清晰显示 +- ✅ 选择按钮响应及时 + +### 连接后: +- ✅ 连接状态正确显示 +- ✅ 按钮状态正确更新 +- ✅ 可以开始数据处理 + +现在可以测试修复后的按钮位置和对话框性能了!🎉 diff --git a/DEVICE_SELECTION_IMPROVEMENT.md b/DEVICE_SELECTION_IMPROVEMENT.md new file mode 100644 index 0000000..638ae5a --- /dev/null +++ b/DEVICE_SELECTION_IMPROVEMENT.md @@ -0,0 +1,218 @@ +# 设备选择功能改进测试指南 + +## 🎯 改进内容 + +### 1. 显示所有扫描到的设备 ✅ +- **问题**:之前只显示前8个设备 +- **解决**:使用RecyclerView显示所有扫描到的设备 +- **实现**:移除设备数量限制,显示完整设备列表 + +### 2. 点击任意设备连接 ✅ +- **问题**:之前只能选择前两个设备 +- **解决**:每个设备都可以点击连接 +- **实现**:RecyclerView的每个item都可以点击 + +### 3. 扫描完成后立即弹出对话框 ✅ +- **问题**:扫描完成后可能延迟弹出对话框 +- **解决**:扫描停止时立即触发回调 +- **实现**:在`stopBleScan()`中立即调用`onScanComplete` + +## 📱 现在的UI布局 + +### 设备选择对话框 +``` +┌─────────────────────────────────────────┐ +│ 选择蓝牙设备 (找到 5 个设备) │ +├─────────────────────────────────────────┤ +│ ┌─────────────────────────────────────┐ │ +│ │ iPhone │ │ +│ │ 00:11:22:33:44:55 │ │ +│ └─────────────────────────────────────┘ │ +│ ┌─────────────────────────────────────┐ │ +│ │ AirPods │ │ +│ │ AA:BB:CC:DD:EE:FF │ │ +│ └─────────────────────────────────────┘ │ +│ ┌─────────────────────────────────────┐ │ +│ │ 小米手环 │ │ +│ │ 11:22:33:44:55:66 │ │ +│ └─────────────────────────────────────┘ │ +│ ┌─────────────────────────────────────┐ │ +│ │ Samsung Galaxy │ │ +│ │ 22:33:44:55:66:77 │ │ +│ └─────────────────────────────────────┘ │ +│ ┌─────────────────────────────────────┐ │ +│ │ Huawei Watch │ │ +│ │ 33:44:55:66:77:88 │ │ +│ └─────────────────────────────────────┘ │ +├─────────────────────────────────────────┤ +│ [取消] │ +└─────────────────────────────────────────┘ +``` + +## 🚀 测试步骤 + +### 第一步:启动蓝牙扫描 +1. **点击"连接蓝牙"按钮** +2. **观察扫描过程**: + ``` + 蓝牙状态: 正在扫描蓝牙设备... + 发现设备: iPhone (00:11:22:33:44:55) + 发现设备: AirPods (AA:BB:CC:DD:EE:FF) + 发现设备: 小米手环 (11:22:33:44:55:66) + ``` + +### 第二步:验证扫描完成提示 +1. **扫描完成后**: + - ✅ 显示"扫描完成,找到 X 个设备" + - ✅ 立即弹出设备选择对话框 + - ✅ 对话框标题显示设备数量 + +### 第三步:验证设备列表 +1. **检查设备列表**: + - ✅ 显示所有扫描到的设备 + - ✅ 每个设备显示名称和地址 + - ✅ 列表可以滚动(如果设备很多) + +### 第四步:测试设备选择 +1. **点击任意设备**: + - ✅ 点击第一个设备 + - ✅ 点击中间的设备 + - ✅ 点击最后一个设备 + - ✅ 每个设备都能正常连接 + +### 第五步:验证连接过程 +1. **连接状态变化**: + - ✅ 按钮文字变为"连接中..." + - ✅ 按钮颜色变为橙色 + - ✅ 状态信息更新 + +## 🔧 关键代码修改 + +### 1. 设备适配器 +```kotlin +private inner class DeviceAdapter : RecyclerView.Adapter() { + + override fun onBindViewHolder(holder: DeviceViewHolder, position: Int) { + val device = devices[position] + val deviceName = device.name ?: "未知设备" + val deviceAddress = device.address + holder.textView.text = "$deviceName\n$deviceAddress" + + holder.itemView.setOnClickListener { + onDeviceSelectedListener?.invoke(device) + dismiss() + } + } + + override fun getItemCount(): Int = devices.size +} +``` + +### 2. 对话框实现 +```kotlin +override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val builder = AlertDialog.Builder(requireContext()) + builder.setTitle("选择蓝牙设备 (找到 ${devices.size} 个设备)") + + if (devices.isEmpty()) { + builder.setMessage("未找到蓝牙设备") + builder.setPositiveButton("重新扫描") { _, _ -> dismiss() } + builder.setNegativeButton("取消") { _, _ -> dismiss() } + } else { + // 创建RecyclerView显示所有设备 + val recyclerView = RecyclerView(requireContext()).apply { + layoutManager = LinearLayoutManager(requireContext()) + adapter = DeviceAdapter() + + // 设置固定高度,避免对话框过大 + layoutParams = ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + 600 // 固定高度600dp + ) + } + + builder.setView(recyclerView) + builder.setNegativeButton("取消") { _, _ -> dismiss() } + } + + return builder.create() +} +``` + +### 3. 扫描完成回调 +```kotlin +override fun onScanComplete(devices: List) { + runOnUiThread { + updateStatus("扫描完成,找到 ${devices.size} 个设备") + + if (devices.isNotEmpty()) { + // 立即显示设备选择对话框 + val dialog = BluetoothDeviceDialog.newInstance(devices) + dialog.setOnDeviceSelectedListener { device -> + // 连接选中的设备 + bluetoothManager.connectToDevice(device) + binding.bluetoothButton.text = "连接中..." + binding.bluetoothButton.setBackgroundColor(Color.parseColor("#FF9800")) + updateStatus("正在连接设备: ${device.name ?: device.address}") + } + dialog.show(supportFragmentManager, "BluetoothDeviceDialog") + } else { + updateStatus("未找到蓝牙设备,请重试") + binding.bluetoothButton.text = "连接蓝牙" + binding.bluetoothButton.setBackgroundColor(Color.parseColor("#4CAF50")) + } + } +} +``` + +### 4. BLE扫描停止 +```kotlin +private fun stopBleScan() { + try { + bluetoothLeScanner?.stopScan(bleScanCallback) + Log.d(TAG, "BLE扫描已停止") + + // 扫描完成后立即触发回调 + callback?.onStatusChanged("扫描完成,找到 ${discoveredDevices.size} 个设备") + callback?.onScanComplete(discoveredDevices.toList()) + } catch (e: Exception) { + Log.e(TAG, "停止BLE扫描失败: ${e.message}") + } +} +``` + +## ⚠️ 注意事项 + +### 1. 设备显示 +- 显示所有扫描到的设备,无数量限制 +- 每个设备显示名称和地址 +- 支持滚动查看(如果设备很多) + +### 2. 连接选择 +- 点击任意设备都可以连接 +- 连接后对话框自动关闭 +- 支持取消操作 + +### 3. 性能优化 +- 使用RecyclerView提高性能 +- 固定对话框高度避免过大 +- 扫描完成后立即弹出 + +## 🎉 预期效果 + +### 扫描过程: +- ✅ 显示扫描进度 +- ✅ 实时显示发现的设备 +- ✅ 扫描完成后立即提示 + +### 设备选择: +- ✅ 显示所有扫描到的设备 +- ✅ 每个设备都可以点击 +- ✅ 对话框响应流畅 + +### 连接过程: +- ✅ 点击设备后立即开始连接 +- ✅ 连接状态正确显示 +- ✅ 支持连接任意设备 + +现在你可以扫描所有设备并点击任意设备进行连接了!🎉 diff --git a/NORDIC_UART_ADAPTATION.md b/NORDIC_UART_ADAPTATION.md new file mode 100644 index 0000000..f8e3d75 --- /dev/null +++ b/NORDIC_UART_ADAPTATION.md @@ -0,0 +1,136 @@ +# Nordic UART Service (NUS) 适配指南 + +## 🎯 设备协议识别 + +从你的设备日志可以看出,你的ECG设备使用的是 **Nordic UART Service (NUS)** 协议: + +``` +发现服务: 6e400001-b5a3-f393-e0a9-68716563686f // NUS Service +发现特征: 6e400002-b5a3-f393-e0a9-68716563686f // NUS TX Characteristic +``` + +### Nordic UART Service 协议说明 + +**NUS (Nordic UART Service)** 是一个标准的BLE服务,常用于: +- ECG设备数据传输 +- 传感器数据流 +- 串口通信模拟 +- 医疗设备通信 + +### UUID 含义 + +- **Service UUID**: `6e400001-b5a3-f393-e0a9-68716563686f` + - 这是Nordic UART Service的标准UUID + - 用于标识NUS服务 + +- **TX Characteristic UUID**: `6e400002-b5a3-f393-e0a9-68716563686f` + - 这是NUS的TX(发送)特征 + - 用于接收设备发送的数据 + - 支持通知(Notify)属性 + +## 🔧 适配完成 + +### 1. UUID配置 ✅ +```kotlin +// 服务UUID - 已添加到列表首位 +UUID.fromString("6e400001-b5a3-f393-e0a9-68716563686f") + +// 特征UUID - 已添加到列表首位 +UUID.fromString("6e400002-b5a3-f393-e0a9-68716563686f") +``` + +### 2. 优先级设置 ✅ +- **NUS UUID已放在列表首位** +- **优先匹配你的设备协议** +- **确保快速连接** + +### 3. 数据接收 ✅ +- **自动启用特征通知** +- **开始接收ECG数据流** +- **数据传递给处理模块** + +## 📱 测试步骤 + +### 第一步:重新连接设备 +1. **断开当前连接** +2. **重新点击"连接蓝牙"** +3. **选择你的设备** + +### 第二步:验证连接 +1. **观察连接过程**: + ``` + 蓝牙状态: 设备已连接 + 蓝牙状态: 服务发现成功 + 蓝牙状态: 发现服务: 6e400001-b5a3-f393-e0a9-68716563686f + 蓝牙状态: 发现特征: 6e400002-b5a3-f393-e0a9-68716563686f + ``` + +2. **检查UUID匹配**: + ``` + 找到匹配的服务: 6e400001-b5a3-f393-e0a9-68716563686f + 找到匹配的特征: 6e400002-b5a3-f393-e0a9-68716563686f + 已启用特征通知 + 数据通道已建立,开始接收数据 + ``` + +### 第三步:验证数据接收 +1. **检查状态信息**: + - ✅ "数据通道已建立,开始接收数据" + - ✅ "接收到数据: X 字节" + +2. **检查ECG图表**: + - ✅ 图表变为可见 + - ✅ 开始显示实时波形 + +## 🔍 Nordic UART Service 特点 + +### 1. 数据格式 +- **原始字节流**:设备直接发送原始数据 +- **无协议头**:通常没有复杂的协议封装 +- **连续传输**:数据流连续传输 + +### 2. 常见用途 +- **ECG数据**:心电信号实时传输 +- **传感器数据**:各种生理参数 +- **控制命令**:设备控制指令 + +### 3. 优势 +- **标准化**:广泛使用的标准协议 +- **兼容性好**:支持多种设备 +- **实时性**:低延迟数据传输 + +## 🎉 预期效果 + +### 连接成功: +- ✅ 立即找到匹配的NUS服务 +- ✅ 快速建立数据通道 +- ✅ 开始接收ECG数据 + +### 数据处理: +- ✅ 数据传递给DataManager +- ✅ 进行通道映射和滤波 +- ✅ 显示实时ECG波形 + +### 应用功能: +- ✅ 实时心率计算 +- ✅ 信号质量评估 +- ✅ 12导联ECG分析 + +## ⚠️ 注意事项 + +### 1. 数据格式 +- NUS通常发送原始数据 +- 可能需要特定的数据解析逻辑 +- 注意字节序和数据对齐 + +### 2. 设备状态 +- 确保设备处于数据发送状态 +- 某些设备需要特定命令激活数据流 +- 检查设备电池和连接状态 + +### 3. 数据质量 +- 监控数据接收频率 +- 检查数据包大小是否合理 +- 观察信号质量指标 + +现在请重新连接你的设备,应该能够成功建立数据通道并开始接收ECG数据了!🎯 diff --git a/RAW_DATA_DISPLAY_GUIDE.md b/RAW_DATA_DISPLAY_GUIDE.md new file mode 100644 index 0000000..25d83d1 --- /dev/null +++ b/RAW_DATA_DISPLAY_GUIDE.md @@ -0,0 +1,132 @@ +# 蓝牙原始数据显示测试指南 + +## 🎯 功能说明 + +现在应用已经支持**立即显示蓝牙接收到的原始数据**,无需等待信号处理完成。 + +### 数据流处理顺序 +1. **蓝牙数据接收** → 立即解析 +2. **原始数据显示** → 立即画图(新增) +3. **信号处理** → 后台处理 +4. **处理后数据显示** → 可选显示 + +## 📱 测试步骤 + +### 第一步:连接蓝牙设备 +1. **点击"连接蓝牙"按钮** +2. **等待扫描完成** +3. **选择你的ECG设备** +4. **等待连接成功** + +### 第二步:验证原始数据显示 +1. **观察状态信息**: + ``` + 蓝牙状态: 设备已连接 + 蓝牙状态: 服务发现成功 + 蓝牙状态: 数据通道已建立,开始接收数据 + ``` + +2. **检查数据接收日志**: + ``` + 接收到蓝牙数据: X 字节 + 解析出 X 个数据包 + 立即发送原始数据到图表,处理 X 个数据包 + 发送原始数据到通道 X,数据长度: X + 显示原始数据到图表,通道: X,数据长度: X + ``` + +3. **观察图表显示**: + - ✅ ECG图表立即变为可见 + - ✅ 原始波形立即开始显示 + - ✅ 双视图同步更新(10秒节奏视图 + 2.5秒波形视图) + +### 第三步:验证实时性 +1. **数据接收延迟**:应该几乎无延迟 +2. **图表更新频率**:与蓝牙数据接收频率一致 +3. **波形连续性**:原始数据应该连续显示 + +## 🔍 关键代码位置 + +### 1. 原始数据发送 +```kotlin +// DataManager.kt - 第97行 +sendRawDataToCharts(packets) + +// DataManager.kt - 第177行 +private fun sendRawDataToCharts(packets: List) { + // 直接处理原始数据包,不进行通道映射 + for (packet in packets) { + val channelData = packet.getChannelData() + if (channelData != null) { + for ((channelIndex, channel) in channelData.withIndex()) { + // 立即发送原始数据到图表 + realTimeCallback?.onRawDataAvailable(channelIndex, channel) + } + } + } +} +``` + +### 2. 原始数据显示 +```kotlin +// MainActivity.kt - 第572行 +override fun onRawDataAvailable(channelIndex: Int, rawData: List) { + // 立即显示原始数据到图表 + binding.ecgChartContainer.visibility = View.VISIBLE + binding.ecgRhythmView.updateData(rawData) + binding.ecgWaveformView.updateData(rawData) +} +``` + +## 🎉 预期效果 + +### 连接成功时: +- ✅ 蓝牙设备连接成功 +- ✅ 数据通道建立 +- ✅ 开始接收ECG数据 + +### 原始数据显示时: +- ✅ ECG图表立即可见 +- ✅ 原始波形立即显示 +- ✅ 双视图同步更新 +- ✅ 无延迟实时显示 + +### 数据特点: +- **原始性**:未经任何滤波处理 +- **实时性**:接收即显示 +- **连续性**:数据流连续 +- **完整性**:包含所有通道数据 + +## ⚠️ 注意事项 + +### 1. 数据格式 +- **支持的数据类型**:所有已定义的数据类型 +- **通道数量**:根据设备类型自动识别 +- **采样率**:根据数据类型自动设置 + +### 2. 显示特点 +- **原始信号**:包含噪声和基线漂移 +- **实时更新**:与蓝牙数据接收同步 +- **多通道**:支持多通道同时显示 + +### 3. 性能考虑 +- **UI线程**:原始数据显示在主线程 +- **内存使用**:图表自动管理数据缓冲 +- **更新频率**:与蓝牙数据接收频率一致 + +## 🔧 调试信息 + +### 关键日志 +``` +DataManager: 立即发送原始数据到图表,处理 X 个数据包 +DataManager: 发送原始数据到通道 X,数据长度: X +MainActivity: 显示原始数据到图表,通道: X,数据长度: X +``` + +### 状态检查 +- 蓝牙连接状态 +- 数据接收频率 +- 图表更新状态 +- 通道数据完整性 + +现在你的应用可以立即显示蓝牙接收到的原始数据了!连接设备后就能看到实时波形。🚀 diff --git a/SMART_UUID_DETECTION.md b/SMART_UUID_DETECTION.md new file mode 100644 index 0000000..6cd9523 --- /dev/null +++ b/SMART_UUID_DETECTION.md @@ -0,0 +1,151 @@ +# 智能UUID检测指南 + +## 🎯 问题分析 + +从你提供的日志可以看出: +``` +发现特征: 00002a29-0000-1000-8000-00805f9b34fb +发现特征: 00002a24-0000-1000-8000-00805f9b34fb +发现特征: 00002a25-0000-1000-8000-00805f9b34fb +``` + +这些都是**标准蓝牙GATT服务**的特征UUID: +- `00002a29` = Manufacturer Name String +- `00002a24` = Model Number String +- `00002a25` = Serial Number String +- `00002a27` = Hardware Revision String +- `00002a26` = Firmware Revision String +- `00002a28` = Software Revision String + +## 🔧 解决方案 + +### 1. 扩展UUID列表 ✅ +- **新增**:14个常见ECG设备UUID变体 +- **覆盖**:更多厂商的设备协议 +- **范围**:`0000ff00` 到 `0000ffb0` + +### 2. 智能UUID检测 ✅ +- **实现**:自动检测非标准蓝牙服务 +- **逻辑**:跳过标准服务(`00002a`、`000018`) +- **目标**:查找自定义服务和特征 + +### 3. 通知特征检测 ✅ +- **实现**:检查特征是否支持通知 +- **条件**:`PROPERTY_NOTIFY` 或 `PROPERTY_INDICATE` +- **目的**:确保能接收数据 + +## 📱 检测流程 + +### 第一步:预设UUID匹配 +``` +尝试匹配预设的ECG设备UUID: +- 0000fff0-0000-1000-8000-00805f9b34fb +- 0000ffe0-0000-1000-8000-00805f9b34fb +- 0000ffe5-0000-1000-8000-00805f9b34fb +- ... (共15个变体) +``` + +### 第二步:智能检测 +``` +如果预设UUID未找到匹配: +1. 扫描所有服务 +2. 跳过标准蓝牙服务(00002a, 000018) +3. 查找自定义服务 +4. 检查特征是否支持通知 +5. 自动选择第一个匹配的特征 +``` + +### 第三步:建立连接 +``` +找到匹配的服务和特征后: +1. 启用特征通知 +2. 开始接收数据 +3. 显示"数据通道已建立" +``` + +## 🚀 测试步骤 + +### 第一步:重新连接设备 +1. **断开当前连接** +2. **重新点击"连接蓝牙"** +3. **选择你的设备** + +### 第二步:观察调试信息 +1. **查看服务发现过程**: + ``` + 蓝牙状态: 服务发现成功 + 设备提供的服务数量: X + 发现服务: [UUID列表] + ``` + +2. **查看智能检测过程**: + ``` + 预设UUID未找到匹配,尝试智能检测 + 发现自定义服务: [UUID] + 发现自定义特征: [UUID] + 找到支持通知的特征: [UUID] + ``` + +3. **查看连接结果**: + ``` + 数据通道已建立,开始接收数据 + ``` + +### 第三步:验证数据接收 +1. **检查状态信息**: + - ✅ "数据通道已建立,开始接收数据" + - ✅ "接收到数据: X 字节" + +2. **检查ECG图表**: + - ✅ 图表变为可见 + - ✅ 开始显示实时数据 + +## 🔍 常见问题 + +### 1. 设备使用标准服务 +**现象**:只显示`00002a`开头的UUID +**解决**:智能检测会自动跳过这些服务 + +### 2. 设备需要特定触发 +**现象**:连接成功但没有数据 +**解决**:可能需要发送特定命令激活数据流 + +### 3. 设备使用非标准UUID +**现象**:UUID不在预设列表中 +**解决**:智能检测会自动发现自定义UUID + +## 📋 设备信息收集 + +请提供以下信息: + +### 1. 设备基本信息 +- **设备名称**:你的ECG设备显示的名称 +- **设备型号**:如果知道的话 +- **厂商信息**:设备制造商 + +### 2. 服务UUID信息 +``` +请提供完整的服务发现日志: +- 所有发现的服务UUID +- 所有发现的特征UUID +- 智能检测的结果 +``` + +### 3. 连接状态 +- **是否成功建立数据通道?** +- **是否开始接收数据?** +- **数据大小是多少字节?** + +## 🎉 预期效果 + +### 成功情况: +- ✅ 显示"数据通道已建立,开始接收数据" +- ✅ 开始接收ECG数据 +- ✅ ECG图表显示实时波形 + +### 需要进一步调试: +- ❌ 仍然显示"未找到匹配的服务或特征" +- ℹ️ 需要查看完整的服务发现日志 +- 🔧 可能需要手动配置特定UUID + +现在请重新连接你的设备,观察新的调试信息!🎯 diff --git a/UI_FIX_TEST_GUIDE.md b/UI_FIX_TEST_GUIDE.md new file mode 100644 index 0000000..44abfc7 --- /dev/null +++ b/UI_FIX_TEST_GUIDE.md @@ -0,0 +1,182 @@ +# UI修复测试指南 + +## 🎯 修复内容 + +### 1. 图表隐藏功能 ✅ +- **问题**:ECG图表在启动时就显示,占用屏幕空间 +- **解决**:图表默认隐藏,只有在接收到数据时才显示 +- **实现**: + ```xml + android:visibility="gone" + ``` + +### 2. 设备选择对话框 ✅ +- **问题**:扫描完成后没有显示设备选择对话框 +- **解决**:确保`onScanComplete`回调正确显示对话框 +- **实现**: + ```kotlin + override fun onScanComplete(devices: List) { + if (devices.isNotEmpty()) { + val dialog = BluetoothDeviceDialog.newInstance(devices) + dialog.show(supportFragmentManager, "BluetoothDeviceDialog") + } + } + ``` + +## 📱 现在的UI布局 + +### 启动时的界面 +``` +┌─────────────────────────────────────────┐ +│ [连接蓝牙] [启动程序] │ +│ [停止程序] [陷波滤波] │ +└─────────────────────────────────────────┘ +┌─────────────────────────────────────────┐ +│ 状态信息区域 │ +│ [应用已就绪,可以开始使用] │ +│ [权限状态信息] │ +│ [点击"连接蓝牙"按钮开始蓝牙连接...] │ +└─────────────────────────────────────────┘ +``` + +### 有数据时的界面 +``` +┌─────────────────────────────────────────┐ +│ [连接蓝牙] [启动程序] │ +│ [停止程序] [陷波滤波] │ +└─────────────────────────────────────────┘ +┌─────────────────────────────────────────┐ +│ ECG实时监测 │ +│ ┌─────────────────────────────────────┐ │ +│ │ [实时ECG曲线图] │ │ +│ └─────────────────────────────────────┘ │ +│ ┌─────────────────────────────────────┐ │ +│ │ [详细ECG波形图] │ │ +│ └─────────────────────────────────────┘ │ +└─────────────────────────────────────────┘ +┌─────────────────────────────────────────┐ +│ 状态信息区域 │ +│ [蓝牙状态信息] │ +│ [数据处理状态] │ +└─────────────────────────────────────────┘ +``` + +## 🚀 测试步骤 + +### 第一步:验证启动界面 +1. **启动应用** +2. **确认界面**: + - ✅ 只显示按钮区域和状态信息区域 + - ✅ ECG图表区域完全隐藏 + - ✅ 按钮没有超出屏幕边界 + +### 第二步:测试蓝牙扫描 +1. **点击"连接蓝牙"按钮** +2. **观察扫描过程**: + ``` + 蓝牙状态: 正在扫描蓝牙设备... + 发现设备: [设备名称] ([地址]) + 发现设备: [设备名称] ([地址]) + ``` + +### 第三步:验证设备选择对话框 +1. **扫描完成后**: + - ✅ 应该弹出设备选择对话框 + - ✅ 显示所有发现的设备列表 + - ✅ 每个设备显示名称和地址 + +### 第四步:测试设备连接 +1. **选择任意设备**: + - 点击设备名称 + - 观察连接过程 +2. **验证连接状态**: + - 按钮文字变为"断开蓝牙" + - 按钮颜色变为红色 + +### 第五步:测试数据显示 +1. **连接成功后点击"启动程序"** +2. **观察图表显示**: + - ✅ ECG图表区域应该出现 + - ✅ 显示实时数据曲线 + - ✅ 界面布局合理,不超出屏幕 + +## 🔧 关键代码修改 + +### 1. 布局文件修改 +```xml + + +``` + +### 2. 数据显示逻辑 +```kotlin +override fun onDataReceived(data: ByteArray) { + runOnUiThread { + updateStatus("接收到蓝牙数据: ${data.size} 字节") + dataManager.onBleNotify(data) + + // 显示ECG图表 + binding.ecgChartContainer.visibility = View.VISIBLE + } +} + +override fun onProcessedDataAvailable(channelIndex: Int, processedData: List) { + if (channelIndex == 0) { + try { + // 显示ECG图表 + binding.ecgChartContainer.visibility = View.VISIBLE + + binding.ecgRhythmView.updateData(processedData) + binding.ecgWaveformView.updateData(processedData) + } catch (e: Exception) { + Log.e("MainActivity", "处理实时数据失败: ${e.message}", e) + } + } +} +``` + +## ⚠️ 注意事项 + +### 1. 设备选择对话框 +- 确保`BluetoothDeviceDialog.kt`文件存在 +- 确保对话框正确显示设备列表 +- 确保点击设备后能正确连接 + +### 2. 图表显示时机 +- 图表只在有数据时才显示 +- 蓝牙数据接收时显示 +- 处理后的数据更新时显示 + +### 3. 界面适配 +- 按钮布局适应不同屏幕尺寸 +- 图表区域合理分配空间 +- 状态信息区域保持可见 + +## 🎉 预期效果 + +### 启动时: +- ✅ 界面简洁,只显示必要元素 +- ✅ 按钮布局合理,不超出屏幕 +- ✅ 状态信息清晰可见 + +### 扫描时: +- ✅ 显示扫描进度和设备发现信息 +- ✅ 扫描完成后弹出设备选择对话框 + +### 连接后: +- ✅ 显示连接状态 +- ✅ 可以启动数据处理 + +### 有数据时: +- ✅ ECG图表自动显示 +- ✅ 实时更新数据曲线 +- ✅ 界面布局完整合理 + +现在可以测试修复后的UI了!🎉 diff --git a/app/src/main/cpp/include/cpp/data_praser.h b/app/src/main/cpp/include/cpp/data_praser.h index b075354..d40ce7d 100644 --- a/app/src/main/cpp/include/cpp/data_praser.h +++ b/app/src/main/cpp/include/cpp/data_praser.h @@ -17,7 +17,8 @@ enum class DataType { RESPIRATION, // 呼吸/姿态 SNORE, // 鼾声 STETHOSCOPE, // 数字听诊 - MIT_BIH // 添加MIT-BIH心律失常数据集类型 + MIT_BIH, // MIT-BIH心律失常数据集类型 + PW_ECG_SL // 单通道心电数据 (0x4401) }; // 导联脱落状态 @@ -72,6 +73,9 @@ SensorData parse_ppg(const uint8_t* data); // 12导联心电解析 (0x4402) SensorData parse_12lead_ecg(const uint8_t* data); +// 单通道心电解析 (0x4401) - PW_ECG-SL +SensorData parse_pw_ecg_sl(const uint8_t* data); + // MIT-BIH 212格式解析器 SensorData parse_mit_bih_212(const uint8_t* data, size_t size); diff --git a/app/src/main/cpp/src/data_mapper.cpp b/app/src/main/cpp/src/data_mapper.cpp index c29a450..0629396 100644 --- a/app/src/main/cpp/src/data_mapper.cpp +++ b/app/src/main/cpp/src/data_mapper.cpp @@ -38,6 +38,7 @@ SensorData Mapper::DataMapper(SensorData& data) case DataType::SNORE: __android_log_print(ANDROID_LOG_INFO, "DataMapper", "SNORE (5)"); break; case DataType::STETHOSCOPE: __android_log_print(ANDROID_LOG_INFO, "DataMapper", "STETHOSCOPE (6)"); break; case DataType::MIT_BIH: __android_log_print(ANDROID_LOG_INFO, "DataMapper", "MIT_BIH (7)"); break; + case DataType::PW_ECG_SL: __android_log_print(ANDROID_LOG_INFO, "DataMapper", "PW_ECG_SL (8)"); break; default: __android_log_print(ANDROID_LOG_INFO, "DataMapper", "未知类型"); break; } __android_log_print(ANDROID_LOG_INFO, "DataMapper", "包序号: %d", data_mapped.packet_sn); diff --git a/app/src/main/cpp/src/data_praser.cpp b/app/src/main/cpp/src/data_praser.cpp index 88956d9..8179f5e 100644 --- a/app/src/main/cpp/src/data_praser.cpp +++ b/app/src/main/cpp/src/data_praser.cpp @@ -226,6 +226,39 @@ SensorData parse_12lead_ecg(const uint8_t* data) { return result; } +// 单通道心电解析 (0x4401) - PW_ECG-SL +SensorData parse_pw_ecg_sl(const uint8_t* data) { + SensorData result; + result.data_type = DataType::PW_ECG_SL; + result.packet_sn = read_le(data); + + // 跳过 DataType(2) 和 data_len(2) + const uint8_t* payload = data + 6; + + // 导联脱落状态 (1字节,高4位+中4位) + result.lead_status.status[0] = *payload; + result.lead_status.status[1] = 0; // 第二个字节未使用 + payload += 1; + + // 解析单通道ECG数据 (115个采样点) + auto& channel = result.channel_data.emplace>(); + channel.reserve(115); + + for (int i = 0; i < 115; ++i) { + int16_t adc_value = read_le(payload); + payload += 2; + channel.push_back(adc_value * 0.318f); // 转换为μV,与12导联心电保持一致 + } + + // 跳过预留1字节 + payload += 1; + + // 赋值原始二进制数据 + result.raw_data.assign(data, data + 6 + 1 + 115 * 2 + 1); + + return result; +} + // 数字听诊解析 (0x1102) SensorData parse_stethoscope(const uint8_t* data) { @@ -398,6 +431,9 @@ std::vector parse_device_data(const std::vector& file_data) case 0x4213: grouped_data[DataType::RESPIRATION].push_back(parse_respiration(ptr)); break; + case 0x4401: + grouped_data[DataType::PW_ECG_SL].push_back(parse_pw_ecg_sl(ptr)); + break; case 0x4402: grouped_data[DataType::ECG_12LEAD].push_back(parse_12lead_ecg(ptr)); break; @@ -439,6 +475,9 @@ std::vector parse_device_data(const std::vector& file_data) case 0x4213: grouped_data[DataType::RESPIRATION].push_back(parse_respiration(ptr)); break; + case 0x4401: + grouped_data[DataType::PW_ECG_SL].push_back(parse_pw_ecg_sl(ptr)); + break; case 0x4402: grouped_data[DataType::ECG_12LEAD].push_back(parse_12lead_ecg(ptr)); break; @@ -660,6 +699,9 @@ void StreamParser::parseBuffer() { case 0x4213: packet = parse_respiration(buffer_.data()); break; + case 0x4401: + packet = parse_pw_ecg_sl(buffer_.data()); + break; case 0x4402: packet = parse_12lead_ecg(buffer_.data()); break; @@ -713,6 +755,7 @@ bool StreamParser::isValidPacket(const uint8_t* data) { case 0x4211: // ECG_2LEAD case 0x4212: // SNORE case 0x4213: // RESPIRATION + case 0x4401: // PW_ECG_SL case 0x4402: // ECG_12LEAD case 0x4302: // PPG case 0x1102: // STETHOSCOPE @@ -810,6 +853,7 @@ jobject convertSensorDataToJava(JNIEnv* env, const SensorData& sensorData) { case DataType::PPG: enumName = "PPG"; break; case DataType::ECG_12LEAD: enumName = "ECG_12LEAD"; break; case DataType::MIT_BIH: enumName = "MIT_BIH"; break; + case DataType::PW_ECG_SL: enumName = "PW_ECG_SL"; break; case DataType::STETHOSCOPE: enumName = "STETHOSCOPE"; break; case DataType::SNORE: enumName = "SNORE"; break; case DataType::RESPIRATION: enumName = "RESPIRATION"; break; diff --git a/app/src/main/java/com/example/cmake_project_test/BluetoothDeviceDialog.kt b/app/src/main/java/com/example/cmake_project_test/BluetoothDeviceDialog.kt index e21a620..6888f81 100644 --- a/app/src/main/java/com/example/cmake_project_test/BluetoothDeviceDialog.kt +++ b/app/src/main/java/com/example/cmake_project_test/BluetoothDeviceDialog.kt @@ -7,12 +7,11 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.ArrayAdapter -import android.widget.Button -import android.widget.ListView import android.widget.TextView import androidx.appcompat.app.AlertDialog import androidx.fragment.app.DialogFragment +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView /** * 蓝牙设备选择对话框 @@ -22,6 +21,34 @@ class BluetoothDeviceDialog : DialogFragment() { private var devices: List = emptyList() private var onDeviceSelectedListener: ((BluetoothDevice) -> Unit)? = null + // 设备适配器 + private inner class DeviceAdapter : RecyclerView.Adapter() { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DeviceViewHolder { + val view = LayoutInflater.from(parent.context) + .inflate(android.R.layout.simple_list_item_1, parent, false) + return DeviceViewHolder(view) + } + + override fun onBindViewHolder(holder: DeviceViewHolder, position: Int) { + val device = devices[position] + val deviceName = device.name ?: "未知设备" + val deviceAddress = device.address + holder.textView.text = "$deviceName\n$deviceAddress" + + holder.itemView.setOnClickListener { + onDeviceSelectedListener?.invoke(device) + dismiss() + } + } + + override fun getItemCount(): Int = devices.size + + inner class DeviceViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + val textView: TextView = itemView.findViewById(android.R.id.text1) + } + } + companion object { fun newInstance(devices: List): BluetoothDeviceDialog { return BluetoothDeviceDialog().apply { @@ -36,39 +63,30 @@ class BluetoothDeviceDialog : DialogFragment() { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { val builder = AlertDialog.Builder(requireContext()) - builder.setTitle("选择蓝牙设备") + builder.setTitle("选择蓝牙设备 (找到 ${devices.size} 个设备)") if (devices.isEmpty()) { builder.setMessage("未找到蓝牙设备") builder.setPositiveButton("重新扫描") { _, _ -> - // 重新扫描 dismiss() } builder.setNegativeButton("取消") { _, _ -> dismiss() } } else { - // 创建设备列表 - val deviceNames = devices.map { device -> - "${device.name ?: "未知设备"} (${device.address})" + // 创建RecyclerView显示所有设备 + val recyclerView = RecyclerView(requireContext()).apply { + layoutManager = LinearLayoutManager(requireContext()) + adapter = DeviceAdapter() + + // 设置固定高度,避免对话框过大 + layoutParams = ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + 600 // 固定高度600dp + ) } - val adapter = ArrayAdapter( - requireContext(), - android.R.layout.simple_list_item_1, - deviceNames - ) - - val listView = ListView(requireContext()).apply { - this.adapter = adapter - setOnItemClickListener { _, _, position, _ -> - val selectedDevice = devices[position] - onDeviceSelectedListener?.invoke(selectedDevice) - dismiss() - } - } - - builder.setView(listView) + builder.setView(recyclerView) builder.setNegativeButton("取消") { _, _ -> dismiss() } diff --git a/app/src/main/java/com/example/cmake_project_test/BluetoothManager.kt b/app/src/main/java/com/example/cmake_project_test/BluetoothManager.kt index a126ab2..6e926f4 100644 --- a/app/src/main/java/com/example/cmake_project_test/BluetoothManager.kt +++ b/app/src/main/java/com/example/cmake_project_test/BluetoothManager.kt @@ -29,11 +29,44 @@ class BluetoothManager(private val context: Context) { companion object { private const val TAG = "BluetoothManager" - // 服务UUID (根据你的设备协议调整) - private val SERVICE_UUID = UUID.fromString("0000fff0-0000-1000-8000-00805f9b34fb") + // 常见的ECG设备UUID列表 + private val SERVICE_UUIDS = listOf( + UUID.fromString("6e400001-b5a3-f393-e0a9-68716563686f"), // Nordic UART Service (NUS) - 你的设备 + UUID.fromString("0000fff0-0000-1000-8000-00805f9b34fb"), // 默认 + UUID.fromString("0000ffe0-0000-1000-8000-00805f9b34fb"), // 常见变体1 + UUID.fromString("0000ffe5-0000-1000-8000-00805f9b34fb"), // 常见变体2 + UUID.fromString("0000ff00-0000-1000-8000-00805f9b34fb"), // 常见变体3 + UUID.fromString("0000ff10-0000-1000-8000-00805f9b34fb"), // 常见变体4 + UUID.fromString("0000ff20-0000-1000-8000-00805f9b34fb"), // 常见变体5 + UUID.fromString("0000ff30-0000-1000-8000-00805f9b34fb"), // 常见变体6 + UUID.fromString("0000ff40-0000-1000-8000-00805f9b34fb"), // 常见变体7 + UUID.fromString("0000ff50-0000-1000-8000-00805f9b34fb"), // 常见变体8 + UUID.fromString("0000ff60-0000-1000-8000-00805f9b34fb"), // 常见变体9 + UUID.fromString("0000ff70-0000-1000-8000-00805f9b34fb"), // 常见变体10 + UUID.fromString("0000ff80-0000-1000-8000-00805f9b34fb"), // 常见变体11 + UUID.fromString("0000ff90-0000-1000-8000-00805f9b34fb"), // 常见变体12 + UUID.fromString("0000ffa0-0000-1000-8000-00805f9b34fb"), // 常见变体13 + UUID.fromString("0000ffb0-0000-1000-8000-00805f9b34fb") // 常见变体14 + ) - // 特征UUID (根据你的设备协议调整) - private val CHARACTERISTIC_UUID = UUID.fromString("0000fff1-0000-1000-8000-00805f9b34fb") + private val CHARACTERISTIC_UUIDS = listOf( + UUID.fromString("6e400002-b5a3-f393-e0a9-68716563686f"), // Nordic UART TX Characteristic - 你的设备 + UUID.fromString("0000fff1-0000-1000-8000-00805f9b34fb"), // 默认 + UUID.fromString("0000ffe1-0000-1000-8000-00805f9b34fb"), // 常见变体1 + UUID.fromString("0000ffe6-0000-1000-8000-00805f9b34fb"), // 常见变体2 + UUID.fromString("0000ff01-0000-1000-8000-00805f9b34fb"), // 常见变体3 + UUID.fromString("0000ff11-0000-1000-8000-00805f9b34fb"), // 常见变体4 + UUID.fromString("0000ff21-0000-1000-8000-00805f9b34fb"), // 常见变体5 + UUID.fromString("0000ff31-0000-1000-8000-00805f9b34fb"), // 常见变体6 + UUID.fromString("0000ff41-0000-1000-8000-00805f9b34fb"), // 常见变体7 + UUID.fromString("0000ff51-0000-1000-8000-00805f9b34fb"), // 常见变体8 + UUID.fromString("0000ff61-0000-1000-8000-00805f9b34fb"), // 常见变体9 + UUID.fromString("0000ff71-0000-1000-8000-00805f9b34fb"), // 常见变体10 + UUID.fromString("0000ff81-0000-1000-8000-00805f9b34fb"), // 常见变体11 + UUID.fromString("0000ff91-0000-1000-8000-00805f9b34fb"), // 常见变体12 + UUID.fromString("0000ffa1-0000-1000-8000-00805f9b34fb"), // 常见变体13 + UUID.fromString("0000ffb1-0000-1000-8000-00805f9b34fb") // 常见变体14 + ) // 设备名称前缀 (根据你的设备调整) private const val DEVICE_NAME_PREFIX = "ECG" @@ -228,6 +261,10 @@ class BluetoothManager(private val context: Context) { try { bluetoothLeScanner?.stopScan(bleScanCallback) Log.d(TAG, "BLE扫描已停止") + + // 扫描完成后立即触发回调 + callback?.onStatusChanged("扫描完成,找到 ${discoveredDevices.size} 个设备") + callback?.onScanComplete(discoveredDevices.toList()) } catch (e: Exception) { Log.e(TAG, "停止BLE扫描失败: ${e.message}") } @@ -325,32 +362,124 @@ class BluetoothManager(private val context: Context) { Log.d(TAG, "服务发现成功") callback?.onStatusChanged("服务发现成功") - // 查找目标服务 - val service = gatt.getService(SERVICE_UUID) - if (service != null) { - // 查找目标特征 - val characteristic = service.getCharacteristic(CHARACTERISTIC_UUID) - if (characteristic != null) { - // 启用通知 - if (ActivityCompat.checkSelfPermission(context, Manifest.permission.BLUETOOTH_CONNECT) == PackageManager.PERMISSION_GRANTED) { - gatt.setCharacteristicNotification(characteristic, true) + // 打印所有可用服务 + val services = gatt.services + Log.d(TAG, "设备提供的服务数量: ${services.size}") + + for (service in services) { + Log.d(TAG, "发现服务: ${service.uuid}") + callback?.onStatusChanged("发现服务: ${service.uuid}") + + // 打印服务的所有特征 + val characteristics = service.characteristics + Log.d(TAG, "服务 ${service.uuid} 的特征数量: ${characteristics.size}") + + for (characteristic in characteristics) { + Log.d(TAG, "发现特征: ${characteristic.uuid}") + callback?.onStatusChanged("发现特征: ${characteristic.uuid}") + } + } + + // 尝试查找匹配的服务和特征 + var foundService: BluetoothGattService? = null + var foundCharacteristic: BluetoothGattCharacteristic? = null + + // 首先尝试预设的UUID列表 + for (serviceUuid in SERVICE_UUIDS) { + val service = gatt.getService(serviceUuid) + if (service != null) { + Log.d(TAG, "找到匹配的服务: $serviceUuid") + foundService = service + + // 查找匹配的特征 + for (characteristicUuid in CHARACTERISTIC_UUIDS) { + val characteristic = service.getCharacteristic(characteristicUuid) + if (characteristic != null) { + Log.d(TAG, "找到匹配的特征: $characteristicUuid") + foundCharacteristic = characteristic + break + } } - callback?.onStatusChanged("数据通道已建立") + break + } + } + + // 如果预设UUID没有找到,尝试智能检测 + if (foundService == null || foundCharacteristic == null) { + Log.d(TAG, "预设UUID未找到匹配,尝试智能检测") + + // 查找所有非标准蓝牙服务的UUID + for (service in services) { + val serviceUuid = service.uuid.toString() + + // 跳过标准蓝牙服务(如2a00, 2a01等) + if (!serviceUuid.contains("00002a") && !serviceUuid.contains("000018")) { + Log.d(TAG, "发现自定义服务: $serviceUuid") + + // 查找该服务下的所有特征 + for (characteristic in service.characteristics) { + val characteristicUuid = characteristic.uuid.toString() + + // 跳过标准特征 + if (!characteristicUuid.contains("00002a")) { + Log.d(TAG, "发现自定义特征: $characteristicUuid") + + // 检查特征是否支持通知 + if (characteristic.properties and BluetoothGattCharacteristic.PROPERTY_NOTIFY != 0 || + characteristic.properties and BluetoothGattCharacteristic.PROPERTY_INDICATE != 0) { + + Log.d(TAG, "找到支持通知的特征: $characteristicUuid") + foundService = service + foundCharacteristic = characteristic + break + } + } + } + if (foundService != null) break + } + } + } + + if (foundService != null && foundCharacteristic != null) { + // 启用通知 + if (ActivityCompat.checkSelfPermission(context, Manifest.permission.BLUETOOTH_CONNECT) == PackageManager.PERMISSION_GRANTED) { + Log.d(TAG, "=== 启用特征通知 ===") + Log.d(TAG, "服务UUID: ${foundService.uuid}") + Log.d(TAG, "特征UUID: ${foundCharacteristic.uuid}") + Log.d(TAG, "特征属性: ${foundCharacteristic.properties}") + + val success = gatt.setCharacteristicNotification(foundCharacteristic, true) + Log.d(TAG, "启用特征通知结果: $success") + + callback?.onStatusChanged("数据通道已建立,开始接收数据") } else { - callback?.onError("未找到目标特征") + Log.e(TAG, "缺少BLUETOOTH_CONNECT权限") + callback?.onError("缺少BLUETOOTH_CONNECT权限") } } else { - callback?.onError("未找到目标服务") + Log.e(TAG, "未找到匹配的服务或特征") + callback?.onError("未找到匹配的服务或特征,请检查设备协议") + + // 提供所有可用服务的UUID供参考 + val availableServices = services.joinToString(", ") { it.uuid.toString() } + Log.d(TAG, "可用服务UUID: $availableServices") + callback?.onStatusChanged("可用服务: $availableServices") } } else { - callback?.onError("服务发现失败") + Log.e(TAG, "服务发现失败,状态: $status") + callback?.onError("服务发现失败,状态: $status") } } override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) { // 接收数据 val data = characteristic.value + Log.d(TAG, "=== 蓝牙特征值变化 ===") + Log.d(TAG, "特征UUID: ${characteristic.uuid}") Log.d(TAG, "接收到数据: ${data.size} 字节") + if (data.isNotEmpty()) { + Log.d(TAG, "数据前10字节: ${data.take(10).joinToString(", ") { "0x%02X".format(it) }}") + } callback?.onDataReceived(data) } } diff --git a/app/src/main/java/com/example/cmake_project_test/DataManager.kt b/app/src/main/java/com/example/cmake_project_test/DataManager.kt index 38f2e98..0b174dc 100644 --- a/app/src/main/java/com/example/cmake_project_test/DataManager.kt +++ b/app/src/main/java/com/example/cmake_project_test/DataManager.kt @@ -17,6 +17,7 @@ class DataManager(private val nativeCallback: NativeMethodCallback) { interface RealTimeDataCallback { fun onProcessedDataAvailable(channelIndex: Int, processedData: List) fun onStreamingProgress(progress: Int, totalSamples: Int, processedSamples: Int) + fun onRawDataAvailable(channelIndex: Int, rawData: List) // 新增:原始数据回调 } private var realTimeCallback: RealTimeDataCallback? = null @@ -72,7 +73,13 @@ class DataManager(private val nativeCallback: NativeMethodCallback) { * 处理蓝牙通知数据块 */ fun onBleNotify(chunk: ByteArray) { - if (chunk.isEmpty()) return + if (chunk.isEmpty()) { + Log.w("DataManager", "接收到空的蓝牙数据块") + return + } + + Log.d("DataManager", "接收到蓝牙数据: ${chunk.size} 字节") + Log.d("DataManager", "数据前10字节: ${chunk.take(10).joinToString(", ") { "0x%02X".format(it) }}") ensureParser() rawStream.write(chunk) @@ -80,12 +87,21 @@ class DataManager(private val nativeCallback: NativeMethodCallback) { // 拉取解析出的数据包 val packets = nativeCallback.streamParserDrainPackets(parserHandle) + Log.d("DataManager", "解析结果: ${if (packets != null) packets.size else 0} 个数据包") + if (!packets.isNullOrEmpty()) { totalPacketsParsed += packets.size packetBuffer.addAll(packets) + Log.d("DataManager", "解析出 ${packets.size} 个数据包") + + // 立即发送原始数据到图表显示 + sendRawDataToCharts(packets) + // 应用流式数据处理 processStreamingData(packets) + } else { + Log.w("DataManager", "没有解析出有效数据包") } } @@ -161,6 +177,45 @@ class DataManager(private val nativeCallback: NativeMethodCallback) { } } + /** + * 立即发送原始数据到图表显示 + */ + private fun sendRawDataToCharts(packets: List) { + if (packets.isEmpty()) { + Log.w("DataManager", "sendRawDataToCharts: 没有数据包") + return + } + + Log.d("DataManager", "立即发送原始数据到图表,处理 ${packets.size} 个数据包") + + // 直接处理原始数据包,不进行通道映射 + for ((packetIndex, packet) in packets.withIndex()) { + 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 为空,无法发送数据") + } + } else { + Log.w("DataManager", "通道 $channelIndex 数据为空") + } + } + } else { + Log.w("DataManager", "数据包 $packetIndex 没有通道数据") + } + } + } + /** * 流式数据处理 - 将数据包按通道合并并处理 */ @@ -267,7 +322,7 @@ class DataManager(private val nativeCallback: NativeMethodCallback) { // 获取采样率 val sampleRate = when (currentDataType) { type.SensorData.DataType.EEG -> 250.0 - type.SensorData.DataType.ECG_2LEAD, type.SensorData.DataType.ECG_12LEAD -> 250.0 + type.SensorData.DataType.ECG_2LEAD, type.SensorData.DataType.ECG_12LEAD, type.SensorData.DataType.PW_ECG_SL -> 250.0 type.SensorData.DataType.PPG -> 50.0 type.SensorData.DataType.STETHOSCOPE -> 8000.0 type.SensorData.DataType.SNORE -> 8000.0 diff --git a/app/src/main/java/com/example/cmake_project_test/MainActivity.kt b/app/src/main/java/com/example/cmake_project_test/MainActivity.kt index 6c72f72..9dc6165 100644 --- a/app/src/main/java/com/example/cmake_project_test/MainActivity.kt +++ b/app/src/main/java/com/example/cmake_project_test/MainActivity.kt @@ -9,6 +9,7 @@ import android.Manifest import android.content.pm.PackageManager import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat +import android.view.View import com.example.cmake_project_test.databinding.ActivityMainBinding import type.SensorData @@ -508,7 +509,7 @@ class MainActivity : AppCompatActivity(), NativeMethodCallback, DataManager.Real // 触发UI更新 uiManager.scheduleUiUpdate(dataManager) { val text = uiManager.buildDisplayContent(dataManager) - binding.sampleText.text = text + binding.sampleText.text = text } } @@ -560,6 +561,9 @@ class MainActivity : AppCompatActivity(), NativeMethodCallback, DataManager.Real if (channelIndex == 0) { // 直接在主线程更新,避免频繁的线程切换 try { + // 显示ECG图表 + binding.ecgChartContainer.visibility = View.VISIBLE + binding.ecgRhythmView.updateData(processedData) binding.ecgWaveformView.updateData(processedData) } catch (e: Exception) { @@ -568,6 +572,26 @@ class MainActivity : AppCompatActivity(), NativeMethodCallback, DataManager.Real } } + override fun onRawDataAvailable(channelIndex: Int, rawData: List) { + // 立即显示原始数据到图表 + try { + Log.d("MainActivity", "收到原始数据回调,通道: $channelIndex,数据长度: ${rawData.size}") + if (rawData.isNotEmpty()) { + Log.d("MainActivity", "原始数据前3个值: ${rawData.take(3).joinToString(", ")}") + } + + // 显示ECG图表 + binding.ecgChartContainer.visibility = View.VISIBLE + + binding.ecgRhythmView.updateData(rawData) + binding.ecgWaveformView.updateData(rawData) + + Log.d("MainActivity", "已更新图表,通道: $channelIndex,数据长度: ${rawData.size}") + } catch (e: Exception) { + Log.e("MainActivity", "显示原始数据失败: ${e.message}", e) + } + } + override fun onStreamingProgress(progress: Int, totalSamples: Int, processedSamples: Int) { runOnUiThread { try { @@ -651,11 +675,35 @@ class MainActivity : AppCompatActivity(), NativeMethodCallback, DataManager.Real } override fun onDataReceived(data: ByteArray) { - runOnUiThread { - updateStatus("接收到蓝牙数据: ${data.size} 字节") - // 将蓝牙数据传递给DataManager处理 - dataManager.onBleNotify(data) + Log.d("MainActivity", "=== 蓝牙数据接收开始 ===") + Log.d("MainActivity", "接收到蓝牙数据: ${data.size} 字节") + if (data.isNotEmpty()) { + Log.d("MainActivity", "数据前10字节: ${data.take(10).joinToString(", ") { "0x%02X".format(it) }}") } + + // 在后台线程处理数据,避免阻塞UI + Thread { + try { + Log.d("MainActivity", "开始处理蓝牙数据: ${data.size} 字节") + + // 将蓝牙数据传递给DataManager处理 + dataManager.onBleNotify(data) + + // 在UI线程更新状态和显示图表 + runOnUiThread { + updateStatus("接收到蓝牙数据: ${data.size} 字节") + + // 显示ECG图表 + binding.ecgChartContainer.visibility = View.VISIBLE + } + + } catch (e: Exception) { + Log.e("MainActivity", "处理蓝牙数据失败: ${e.message}", e) + runOnUiThread { + updateStatus("处理蓝牙数据失败: ${e.message}") + } + } + }.start() } override fun onError(message: String) { @@ -674,8 +722,10 @@ class MainActivity : AppCompatActivity(), NativeMethodCallback, DataManager.Real override fun onScanComplete(devices: List) { runOnUiThread { + updateStatus("扫描完成,找到 ${devices.size} 个设备") + if (devices.isNotEmpty()) { - // 显示设备选择对话框 + // 立即显示设备选择对话框 val dialog = BluetoothDeviceDialog.newInstance(devices) dialog.setOnDeviceSelectedListener { device -> // 连接选中的设备 @@ -686,7 +736,7 @@ class MainActivity : AppCompatActivity(), NativeMethodCallback, DataManager.Real } dialog.show(supportFragmentManager, "BluetoothDeviceDialog") } else { - updateStatus("未找到蓝牙设备") + updateStatus("未找到蓝牙设备,请重试") binding.bluetoothButton.text = "连接蓝牙" binding.bluetoothButton.setBackgroundColor(Color.parseColor("#4CAF50")) } diff --git a/app/src/main/java/com/example/cmake_project_test/StreamingSignalProcessor.kt b/app/src/main/java/com/example/cmake_project_test/StreamingSignalProcessor.kt index 937424e..5295ee8 100644 --- a/app/src/main/java/com/example/cmake_project_test/StreamingSignalProcessor.kt +++ b/app/src/main/java/com/example/cmake_project_test/StreamingSignalProcessor.kt @@ -211,7 +211,7 @@ class StreamingSignalProcessor { private fun getSampleRateForDataType(dataType: type.SensorData.DataType): Double { return when (dataType) { type.SensorData.DataType.EEG -> 250.0 - type.SensorData.DataType.ECG_2LEAD, type.SensorData.DataType.ECG_12LEAD -> 250.0 + type.SensorData.DataType.ECG_2LEAD, type.SensorData.DataType.ECG_12LEAD, type.SensorData.DataType.PW_ECG_SL -> 250.0 type.SensorData.DataType.PPG -> 50.0 type.SensorData.DataType.STETHOSCOPE -> 8000.0 type.SensorData.DataType.SNORE -> 8000.0 @@ -230,7 +230,7 @@ class StreamingSignalProcessor { // EEG: 低通40Hz, 陷波50Hz Triple(40.0, 50.0, 30.0) } - type.SensorData.DataType.ECG_2LEAD, type.SensorData.DataType.ECG_12LEAD -> { + type.SensorData.DataType.ECG_2LEAD, type.SensorData.DataType.ECG_12LEAD, type.SensorData.DataType.PW_ECG_SL -> { // ECG: 专业ECG滤波参数 // 低通100Hz (平衡信号清晰度和噪声抑制) // 陷波50Hz (中国工频) diff --git a/app/src/main/java/type/SensorData.java b/app/src/main/java/type/SensorData.java index d8a05ef..47d92ff 100644 --- a/app/src/main/java/type/SensorData.java +++ b/app/src/main/java/type/SensorData.java @@ -3,7 +3,7 @@ import java.util.List; public class SensorData { public enum DataType { - EEG, ECG_2LEAD, PPG, ECG_12LEAD, MIT_BIH, STETHOSCOPE, SNORE, RESPIRATION + EEG, ECG_2LEAD, PPG, ECG_12LEAD, MIT_BIH, STETHOSCOPE, SNORE, RESPIRATION, PW_ECG_SL } public DataType dataType; diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index b479338..15d81fb 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -12,7 +12,8 @@ android:layout_height="wrap_content" android:orientation="vertical" android:padding="8dp" - android:background="#E8E8E8"> + android:background="#E8E8E8" + android:layout_marginTop="50dp"> + android:background="#F8F8F8" + android:visibility="gone">