From d6c870b2432de01289ff174310fca36335a13921 Mon Sep 17 00:00:00 2001
From: ZhangJinLong <19357383190@163.com>
Date: Tue, 30 Sep 2025 14:10:23 +0800
Subject: [PATCH] STIMULATE COMMIT
---
.vscode/settings.json | 3 +-
app/src/main/AndroidManifest.xml | 9 +-
.../cmake_project_test/BluetoothManager.kt | 619 +++++++++++++++---
.../cmake_project_test/MainActivity.kt | 557 ++++++++++++++--
4 files changed, 1037 insertions(+), 151 deletions(-)
diff --git a/.vscode/settings.json b/.vscode/settings.json
index c5f3f6b..4ea45b0 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,3 +1,4 @@
{
- "java.configuration.updateBuildConfiguration": "interactive"
+ "java.configuration.updateBuildConfiguration": "interactive",
+ "idf.pythonInstallPath": "C:\\Espressif\\tools\\idf-python\\3.11.2\\python.exe"
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 37e9051..de35cd0 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -6,15 +6,22 @@
-
+
+
+
+
+
+
+
+
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 4a6ee41..85f856e 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
@@ -75,22 +75,37 @@ class BluetoothManager(private val context: Context) {
const val RESPONSE_TIMEOUT = 0x05 // 超时
}
- // 常见的ECG设备UUID列表
+ // 常见的ECG设备UUID列表(已更新包含ESP32S3 SPP服务)
private val SERVICE_UUIDS = listOf(
- UUID.fromString("6e400001-b5a3-f393-e0a9-68716563686f"), // Nordic UART Service (NUS) - 你的设备
+ UUID.fromString("0000abf0-0000-1000-8000-00805f9b34fb"), // // ESP32S3 SPP Service UUID - 0xABF0
+ UUID.fromString("6e400001-b5a3-f393-e0a9-68716563686f"), // Nordic UART Service (NUS) - 轻迅设备
+ UUID.fromString("1BD790EC-E8B9-7580-0A46-44D30102EDA6"), // 新设备服务UUID
)
private val CHARACTERISTIC_UUIDS = listOf(
- UUID.fromString("6e400002-b5a3-f393-e0a9-68716563686f"), // Nordic UART TX Characteristic - 发送数据
+ UUID.fromString("0000abf1-0000-1000-8000-00805f9b34fb"), // ESP32S3 SPP Data Receive - 0xABF1 (TX)
+ UUID.fromString("0000abf3-0000-1000-8000-00805f9b34fb"), // ESP32S3 SPP Command Receive - 0xABF3 (TX)
+ UUID.fromString("6e400002-b5a3-f393-e0a9-68716563686f"), // Nordic UART TX Characteristic - 轻迅设备发送
+ UUID.fromString("1BD790EC-E8B9-7580-0A46-44D30202EDA6"), // 新设备写入特征值
)
- // Notify特征UUID列表
+ // Notify特征UUID列表(已更新包含ESP32S3 SPP服务)
private val NOTIFY_CHARACTERISTIC_UUIDS = listOf(
- UUID.fromString("6e400003-b5a3-f393-e0a9-68716563686f"), // Nordic UART RX Characteristic - 你的设备
+ UUID.fromString("0000abf2-0000-1000-8000-00805f9b34fb"), // ESP32S3 SPP Data Notify - 0xABF2 (RX)
+ UUID.fromString("0000abf4-0000-1000-8000-00805f9b34fb"), // ESP32S3 SPP Command Notify - 0xABF4 (RX)
+ UUID.fromString("6e400003-b5a3-f393-e0a9-68716563686f"), // Nordic UART RX Characteristic - 轻迅设备接收
+ UUID.fromString("1BD790EC-E8B9-7580-0A46-44D30302EDA6"), // 新设备读取特征值
)
// 设备名称前缀 (根据你的设备调整)
- private const val DEVICE_NAME_PREFIX = "ECG"
+ private const val DEVICE_NAME_PREFIX = "ESP_SPP_SERVER"
+
+ // 设备类型枚举
+ enum class DeviceType {
+ ESP32S3_DEVICE, // ESP32S3 SPP设备
+ QINGXUN, // 轻迅设备
+ NEW_DEVICE // 新设备
+ }
}
private var bluetoothManagerService: android.bluetooth.BluetoothManager? = null
@@ -118,6 +133,9 @@ class BluetoothManager(private val context: Context) {
private var currentCharacteristic: BluetoothGattCharacteristic? = null
private var currentNotifyCharacteristic: BluetoothGattCharacteristic? = null
+ // 当前设备类型
+ private var currentDeviceType: DeviceType? = null
+
// 协议状态
private var deviceInfo: DeviceInfo? = null
private var isCollecting = false
@@ -207,6 +225,13 @@ class BluetoothManager(private val context: Context) {
this.callback = callback
}
+ /**
+ * 获取当前设备类型
+ */
+ fun getCurrentDeviceType(): DeviceType? {
+ return currentDeviceType
+ }
+
/**
* 扫描蓝牙设备
*/
@@ -257,14 +282,45 @@ class BluetoothManager(private val context: Context) {
callback?.onStatusChanged("✅ 权限检查通过,开始扫描...")
- // 优先使用BLE扫描
- if (bluetoothLeScanner != null) {
- callback?.onStatusChanged("📡 使用BLE扫描模式")
- startBleScan()
- } else {
- // 回退到传统蓝牙扫描
- callback?.onStatusChanged("📡 使用传统蓝牙扫描模式")
- startClassicScan()
+ // 改进的兼容性扫描策略
+ when {
+ // Android 8/8.1: 使用混合扫描模式以获得最佳兼容性
+ android.os.Build.VERSION.SDK_INT == android.os.Build.VERSION_CODES.O ||
+ android.os.Build.VERSION.SDK_INT == android.os.Build.VERSION_CODES.O_MR1 -> {
+ Log.d(TAG, "📱 Android 8/8.1检测:使用混合扫描模式(BLE + 传统蓝牙)")
+ callback?.onStatusChanged("📱 Android 8系统优化:混合扫描模式")
+ callback?.onStatusChanged("🔍 策略:BLE扫描 + 传统蓝牙扫描")
+ startEnhancedScanning()
+ }
+ // Android 6-11: 优先BLE,失败时回退到传统蓝牙
+ android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M &&
+ android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.S -> {
+ if (bluetoothLeScanner != null) {
+ Log.d(TAG, "📱 Android ${android.os.Build.VERSION.RELEASE}:优先BLE扫描")
+ callback?.onStatusChanged("📡 使用BLE扫描模式")
+ callback?.onStatusChanged("🔍 开始BLE扫描...")
+ startBleScan()
+ } else {
+ Log.w(TAG, "⚠️ BLE不可用,使用传统蓝牙扫描")
+ callback?.onStatusChanged("📡 BLE不可用,使用传统蓝牙扫描")
+ callback?.onStatusChanged("🔍 开始传统蓝牙扫描...")
+ startClassicScan()
+ }
+ }
+ // Android 12+: 完整的BLE扫描支持
+ else -> {
+ if (bluetoothLeScanner != null) {
+ Log.d(TAG, "✅ Android ${android.os.Build.VERSION.RELEASE}:完整的BLE支持")
+ callback?.onStatusChanged("📡 完整BLE扫描模式")
+ callback?.onStatusChanged("🔍 开始BLE扫描...")
+ startBleScan()
+ } else {
+ Log.w(TAG, "⚠️ 意外情况:BLE不可用,使用传统蓝牙")
+ callback?.onStatusChanged("📡 BLE不可用,使用传统蓝牙扫描")
+ callback?.onStatusChanged("🔍 开始传统蓝牙扫描...")
+ startClassicScan()
+ }
+ }
}
} catch (e: Exception) {
@@ -284,9 +340,19 @@ class BluetoothManager(private val context: Context) {
.setReportDelay(0) // 立即报告结果
.build()
+ // 不使用过滤器,扫描所有BLE设备,确保不会遗漏新设备
+ Log.d(TAG, "🚀 开始BLE扫描,无过滤器")
bluetoothLeScanner?.startScan(null, scanSettings, bleScanCallback)
- Log.d(TAG, "BLE扫描已开始")
- callback?.onStatusChanged("📡 BLE扫描进行中...")
+ Log.d(TAG, "✅ BLE扫描已启动成功")
+
+ callback?.onStatusChanged("📡 BLE扫描已启动")
+ callback?.onStatusChanged("🔍 扫描模式:所有BLE设备")
+ callback?.onStatusChanged("⏱️ 扫描时长:15秒")
+ callback?.onStatusChanged("💡 发现设备实时显示")
+ callback?.onStatusChanged("🎯 目标服务UUID:")
+ for (serviceUuid in SERVICE_UUIDS) {
+ callback?.onStatusChanged(" • $serviceUuid")
+ }
// 取消之前的延迟任务(如果存在)
bleScanStopRunnable?.let { mainHandler.removeCallbacks(it) }
@@ -314,7 +380,15 @@ class BluetoothManager(private val context: Context) {
Log.e(TAG, "BLE扫描失败: ${e.message}")
callback?.onError("❌ BLE扫描失败: ${e.message}")
callback?.onStatusChanged("💡 尝试使用传统蓝牙扫描...")
- startClassicScan()
+
+ // 延迟1秒后启动传统蓝牙扫描,避免同时启动太多扫描
+ android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
+ try {
+ startClassicScan()
+ } catch (fallbackE: Exception) {
+ callback?.onError("❌ 传统蓝牙扫描也失败: ${fallbackE.message}")
+ }
+ }, 1000)
}
}
@@ -323,31 +397,166 @@ class BluetoothManager(private val context: Context) {
*/
private fun startClassicScan() {
try {
- registerClassicScanReceiver()
- bluetoothAdapter?.startDiscovery()
- Log.d(TAG, "传统蓝牙扫描已开始")
- callback?.onStatusChanged("📡 传统蓝牙扫描进行中...")
+ Log.d(TAG, "🚀 开始传统蓝牙扫描")
+ callback?.onStatusChanged("📡 开始传统蓝牙扫描...")
- // 15秒后停止扫描
+ // Android 8.1特殊状态检查
+ if (android.os.Build.VERSION.SDK_INT == android.os.Build.VERSION_CODES.O_MR1) {
+ Log.d(TAG, "📱 Android 8.1状态检查:")
+ callback?.onStatusChanged("📱 Android 8.1系统检测...")
+
+ // 检查蓝牙适配器状态
+ val adapterState = bluetoothAdapter?.state ?: BluetoothAdapter.STATE_OFF
+ Log.d(TAG, "📱 蓝牙适配器状态: ${adapterState}")
+ callback?.onStatusChanged("📱 蓝牙适配器状态: ${when(adapterState) {
+ BluetoothAdapter.STATE_ON -> "已开启"
+ BluetoothAdapter.STATE_OFF -> "已关闭"
+ BluetoothAdapter.STATE_TURNING_ON -> "正在开启"
+ BluetoothAdapter.STATE_TURNING_OFF -> "正在关闭"
+ else -> "未知($adapterState)"
+ }}")
+
+ // 检查是否正在探索
+ val isDiscovering = bluetoothAdapter?.isDiscovering ?: false
+ Log.d(TAG, "📱 当前探索状态: $isDiscovering")
+ callback?.onStatusChanged("📱 当前探索状态: ${if(isDiscovering) "正在探索" else "未在探索"}")
+
+ if (isDiscovering) {
+ Log.d(TAG, "📱 停止当前探索并重新开始")
+ bluetoothAdapter?.cancelDiscovery()
+ Thread.sleep(500) // 等待500ms
+ }
+ }
+
+ registerClassicScanReceiver()
+ val discoveryStarted = bluetoothAdapter?.startDiscovery() ?: false
+
+ if (discoveryStarted) {
+ Log.d(TAG, "✅ 传统蓝牙扫描启动成功")
+ callback?.onStatusChanged("✅ 传统蓝牙扫描已启动")
+ callback?.onStatusChanged("🔍 扫描时长:25秒")
+ callback?.onStatusChanged("💡 请确保目标设备设置为可发现模式")
+ callback?.onStatusChanged("🎯 搜索经典蓝牙和音频设备")
+
+ // Android 8.1额外提示
+ if (android.os.Build.VERSION.SDK_INT == android.os.Build.VERSION_CODES.O_MR1) {
+ callback?.onStatusChanged("📱 Android 8.1提示:扫描传统蓝牙设备")
+ callback?.onStatusChanged("💡 如果找不到设备,请检查设备的配对模式")
+ }
+ } else {
+ Log.w(TAG, "⚠️ 传统蓝牙扫描启动失败")
+ callback?.onStatusChanged("❌ 传统蓝牙扫描启动失败")
+
+ if (android.os.Build.VERSION.SDK_INT == android.os.Build.VERSION_CODES.O_MR1) {
+ callback?.onStatusChanged("💡 Android 8.1排除步骤:")
+ callback?.onStatusChanged(" • 检查蓝牙是否已开启")
+ callback?.onStatusChanged(" • 尝试重启蓝牙")
+ callback?.onStatusChanged(" • 检查是否有其他应用在扫描")
+ }
+ }
+
+ // 25秒后停止扫描(增强扫描模式使用更长的时间)
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
+ Log.d(TAG, "⏰ 传统蓝牙扫描超时,自动停止")
stopClassicScan()
- }, 15000)
+ }, 25000)
} catch (e: Exception) {
- Log.e(TAG, "传统蓝牙扫描失败: ${e.message}")
+ Log.e(TAG, "传统蓝牙扫描异常: ${e.message}", e)
callback?.onError("❌ 传统蓝牙扫描失败: ${e.message}")
- callback?.onStatusChanged("💡 请检查蓝牙设置或重启设备")
+ callback?.onStatusChanged("💡 建议:")
+ callback?.onStatusChanged(" • 检查蓝牙是否开启")
+ callback?.onStatusChanged(" • 尝试重启蓝牙服务")
+ callback?.onStatusChanged(" • 重启设备")
}
}
+ /**
+ * 增强扫描模式:同时使用BLE和传统蓝牙扫描(适用于Android 8)
+ */
+ private fun startEnhancedScanning() {
+ try {
+ Log.d(TAG, "🚀 开始增强扫描模式")
+ callback?.onStatusChanged("🚀 启动混合扫描模式...")
+ callback?.onStatusChanged("💡 同时使用BLE和传统音频蓝牙扫描")
+
+ // 并行启动两种扫描
+ var startedCount = 0
+ val errors = mutableListOf()
+
+ // 启动BLE扫描
+ try {
+ bluetoothLeScanner?.let {
+ Log.d(TAG, "📡 启动BLE扫描...")
+ callback?.onStatusChanged("📡 启动BLE扫描...")
+ startedCount++
+ startBleScan()
+ } ?: run {
+ errors.add("BLE扫描器不可用")
+ }
+ } catch (e: Exception) {
+ errors.add("BLE扫描启动失败: ${e.message}")
+ Log.w(TAG, "BLE扫描启动失败,继续使用传统蓝牙: ${e.message}")
+ }
+
+ // 启动传统蓝牙扫描
+ try {
+ Log.d(TAG, "🔗 启动传统蓝牙扫描...")
+ callback?.onStatusChanged("🔗 启动传统蓝牙扫描...")
+ startedCount++
+ startClassicScan()
+ } catch (e: Exception) {
+ errors.add("传统蓝牙扫描启动失败: ${e.message}")
+ Log.w(TAG, "传统蓝牙扫描启动失败: ${e.message}")
+ }
+
+ // 报告结果
+ if (startedCount > 0) {
+ Log.d(TAG, "✅ 增强扫描模式启动成功:$startedCount 个扫描器")
+ callback?.onStatusChanged("✅ 混合扫描启动成功")
+ callback?.onStatusChanged("📊 活跃扫描器:$startedCount 个")
+ callback?.onStatusChanged("⏱️ 扫描时长:25秒")
+ callback?.onStatusChanged("💡 正在搜索所有类型的蓝牙设备...")
+
+ if (errors.isNotEmpty()) {
+ callback?.onStatusChanged("⚠️ 部分扫描器启动失败:")
+ errors.forEach { error ->
+ callback?.onStatusChanged(" • $error")
+ }
+ }
+ } else {
+ callback?.onStatusChanged("❌ 增强扫描模式启动失败")
+ errors.forEach { error ->
+ callback?.onStatusChanged(" • $error")
+ }
+ throw Exception("所有扫描器启动失败")
+ }
+
+ } catch (e: Exception) {
+ Log.e(TAG, "增强扫描模式失败: ${e.message}", e)
+ callback?.onError("❌ 混合扫描失败: ${e.message}")
+ callback?.onStatusChanged("💡 尝试单独启动扫描...")
+
+ // 回退到传统蓝牙扫描
+ try {
+ startClassicScan()
+ callback?.onStatusChanged("✅ 已回退到传统蓝牙扫描")
+ } catch (fallbackE: Exception) {
+ callback?.onError("❌ 回退扫描也失败了: ${fallbackE.message}")
+ }
+ }
+ }
+
/**
* 停止扫描
*/
fun stopScan() {
try {
+ Log.d(TAG, "🛑 开始停止所有扫描")
stopBleScan()
stopClassicScan()
callback?.onStatusChanged("扫描已停止")
+ Log.d(TAG, "✅ 所有扫描已停止")
} catch (e: Exception) {
Log.e(TAG, "停止扫描失败: ${e.message}")
}
@@ -373,21 +582,32 @@ class BluetoothManager(private val context: Context) {
bluetoothLeScanner?.stopScan(bleScanCallback)
Log.d(TAG, "BLE扫描已停止")
- // 扫描完成后立即触发回调
- callback?.onStatusChanged("🔍 BLE扫描完成")
- callback?.onStatusChanged("📊 扫描结果:找到 ${discoveredDevices.size} 个设备")
+ // 扫描完成后触发回调(仅在非混合模式)
+ val isMixedScan = android.os.Build.VERSION.SDK_INT == android.os.Build.VERSION_CODES.O ||
+ android.os.Build.VERSION.SDK_INT == android.os.Build.VERSION_CODES.O_MR1
- if (discoveredDevices.isEmpty()) {
- callback?.onStatusChanged("⚠️ 未发现任何设备")
- callback?.onStatusChanged("💡 建议:")
- callback?.onStatusChanged(" • 检查目标设备是否开启蓝牙")
- callback?.onStatusChanged(" • 确保设备在可见范围内")
- callback?.onStatusChanged(" • 尝试重启蓝牙服务")
+ callback?.onStatusChanged("🔍 BLE扫描完成")
+ val bleDeviceCount = discoveredDevices.count { it.type == BluetoothDevice.DEVICE_TYPE_LE }
+ callback?.onStatusChanged("📊 BLE扫描结果:找到 $bleDeviceCount 个BLE设备")
+
+ if (bleDeviceCount == 0) {
+ callback?.onStatusChanged("⚠️ 未发现BLE设备")
+ if (!isMixedScan) {
+ callback?.onStatusChanged("💡 建议:")
+ callback?.onStatusChanged(" • 检查目标设备是否开启蓝牙")
+ callback?.onStatusChanged(" • 确保设备在可见范围内")
+ callback?.onStatusChanged(" • 尝试重启蓝牙服务")
+ }
} else {
- callback?.onStatusChanged("✅ 扫描成功,请选择要连接的设备")
+ callback?.onStatusChanged("✅ BLE扫描成功")
}
- callback?.onScanComplete(discoveredDevices.toList())
+ // 仅在非混合模式下触发完成回调
+ if (!isMixedScan) {
+ callback?.onScanComplete(discoveredDevices.toList())
+ } else {
+ callback?.onStatusChanged("⏳ BLE扫描完成,传统蓝牙扫描继续进行...")
+ }
} catch (e: Exception) {
Log.e(TAG, "停止BLE扫描失败: ${e.message}")
// 如果是FragmentManager相关错误,记录但不抛出异常
@@ -871,30 +1091,184 @@ class BluetoothManager(private val context: Context) {
}
/**
- * 电刺激开关
+ * 高级电刺激控制
* 功能码: 0x0003
- * 数据格式: [开关及类型(1字节)]
- * 数据长度字段表示整个包的长度(7字节)
+ * 数据格式: [开关及类型(1字节)] + [强度(1字节)] + [频率(2字节)] + [总持续时间(2字节)] + [休息时间(2字节)] + [静默时间(2字节)] + [缓进时间(1字节)] + [保持时间(1字节)] + [缓出时间(1字节)]
+ * 数据长度:15字节
*/
- fun stimSwitch(stimType: Int, enable: Boolean) {
+ fun stimSwitch(
+ enable: Boolean, // 开关状态:true=开启,false=关闭
+ stimType: Int = 0, // 电刺激类型:0x00=关闭,0x10~0x1F=开启+类型
+ intensity: Int = 50, // 强度值:1-100
+ frequency: Int = 100, // 频率值:Hz
+ totalDuration: Int = 5000, // 总持续时间:毫秒
+ restTime: Int = 1000, // 休息时间:毫秒
+ silenceTime: Int = 500, // 静默时间:毫秒
+ fadeInTime: Int = 1, // 缓进时间:秒
+ holdTime: Int = 3, // 保持时间:秒
+ fadeOutTime: Int = 1 // 缓出时间:秒
+ ) {
try {
- val data = ByteArray(1)
- // 开关及类型控制
- // 高4位:开关控制(0000=关闭,0001=开启)
- // 低4位:刺激类型(0000~1111,即0-15)
- val switchBits = if (enable) 0x10 else 0x00 // 高4位:0001或0000
- val typeBits = stimType and 0x0F // 低4位:确保在0-15范围内
- data[0] = (switchBits or typeBits).toByte()
+ // 根据表格:总包19字节 = 功能码(2) + 数据长度(2) + 数据内容(13) + CRC16(2)
+ val packet = ByteArray(19)
+ var index = 0
+
+ // 1. 功能码 (2字节) - 小端格式
+ packet[index++] = (Protocol.FUNC_STIM_SWITCH and 0xFF).toByte()
+ packet[index++] = ((Protocol.FUNC_STIM_SWITCH shr 8) and 0xFF).toByte()
+
+ // 2. 数据长度 (2字节) - 小端格式,数据内容13字节
+ packet[index++] = 13.toByte() // 数据长度低字节
+ packet[index++] = 0.toByte() // 数据长度高字节
+
+ // 3. 开关状态以及电刺激类型 (1字节)
+ val switchAndType = if (enable) {
+ val typeBits = stimType and 0x0F // 低4位:类型
+ (0x10 or typeBits).toByte() // 0x10表示开启
+ } else {
+ 0x00.toByte() // 0x00表示关闭
+ }
+ packet[index++] = switchAndType
+
+ // 4. 强度 (1字节) - 限制在0-100范围内
+ packet[index++] = (intensity.coerceIn(0, 100)).toByte()
+
+ // 5. 频率 (2字节) - 小端格式
+ packet[index++] = (frequency and 0xFF).toByte()
+ packet[index++] = ((frequency shr 8) and 0xFF).toByte()
+
+ // 6. 总持续时间 (2字节) - 小端格式
+ packet[index++] = (totalDuration and 0xFF).toByte()
+ packet[index++] = ((totalDuration shr 8) and 0xFF).toByte()
+
+ // 7. 休息时间 (2字节) - 小端格式
+ packet[index++] = (restTime and 0xFF).toByte()
+ packet[index++] = ((restTime shr 8) and 0xFF).toByte()
+
+ // 8. 静默时间 (2字节) - 小端格式
+ packet[index++] = (silenceTime and 0xFF).toByte()
+ packet[index++] = ((silenceTime shr 8) and 0xFF).toByte()
+
+ // 9. 缓进时间 (1字节)
+ packet[index++] = (fadeInTime.coerceIn(0, 255)).toByte()
+
+ // 10. 保持时间 (1字节)
+ packet[index++] = (holdTime.coerceIn(0, 255)).toByte()
+
+ // 11. 缓出时间 (1字节)
+ packet[index++] = (fadeOutTime.coerceIn(0, 255)).toByte()
+
+ // 12. CRC16校验 (2字节) - 计算前面17字节的CRC
+ val crc = calculateCRC16(packet, 0, 17)
+ packet[index++] = (crc and 0xFF).toByte()
+ packet[index++] = ((crc shr 8) and 0xFF).toByte()
+
+ // 详细日志记录
+ Log.d(TAG, "发送高级电刺激控制指令 (19字节):")
+ Log.d(TAG, " 开关状态: ${if (enable) "开启" else "关闭"}")
+ Log.d(TAG, " 刺激类型: $stimType")
+ Log.d(TAG, " 强度: $intensity")
+ Log.d(TAG, " 频率: ${frequency}Hz")
+ Log.d(TAG, " 总持续时间: ${totalDuration}ms")
+ Log.d(TAG, " 休息时间: ${restTime}ms")
+ Log.d(TAG, " 静默时间: ${silenceTime}ms")
+ Log.d(TAG, " 缓进时间: ${fadeInTime}s")
+ Log.d(TAG, " 保持时间: ${holdTime}s")
+ Log.d(TAG, " 缓出时间: ${fadeOutTime}s")
+ Log.d(TAG, "数据包结构: 功能码(2) + 数据长度(2) + 数据内容(13) + CRC16(2) = 19字节")
+ Log.d(TAG, "数据包: ${packet.joinToString(", ") { "0x%02X".format(it) }}")
- val packet = buildProtocolPacket(Protocol.FUNC_STIM_SWITCH, data)
- Log.d(TAG, "发送电刺激开关指令: ${packet.joinToString(", ") { "0x%02X".format(it) }}")
sendCommand(packet)
+
} catch (e: Exception) {
- Log.e(TAG, "构建电刺激开关指令失败: ${e.message}")
- callback?.onCommandSent(false, "构建电刺激开关指令失败: ${e.message}")
+ Log.e(TAG, "构建高级电刺激控制指令失败: ${e.message}")
+ callback?.onCommandSent(false, "构建高级电刺激控制指令失败: ${e.message}")
}
}
+
+ /**
+ * 简化版电刺激开关(为了保持向后兼容)
+ */
+ fun stimSwitchSimple(stimType: Int, enable: Boolean) {
+ stimSwitch(
+ enable = enable,
+ stimType = stimType,
+ intensity = if (enable) 50 else 0,
+ frequency = if (enable) 100 else 0,
+ totalDuration = if (enable) 5000 else 0,
+ restTime = 1000,
+ silenceTime = 500,
+ fadeInTime = 1,
+ holdTime = 3,
+ fadeOutTime = 1
+ )
+ }
+ /**
+ * 电刺激预设:高强度短期刺激
+ */
+ fun stimHighIntensityShort(
+ stimType: Int = 1,
+ durationSeconds: Int = 30,
+ intensity: Int = 80
+ ) {
+ stimSwitch(
+ enable = true,
+ stimType = stimType,
+ intensity = intensity,
+ frequency = 150,
+ totalDuration = durationSeconds * 1000,
+ restTime = 200,
+ silenceTime = 100,
+ fadeInTime = 2,
+ holdTime = durationSeconds - 4,
+ fadeOutTime = 2
+ )
+ }
+
+ /**
+ * 电刺激预设:低强度长期刺激
+ */
+ fun stimLowIntensityLong(
+ stimType: Int = 1,
+ durationMinutes: Int = 15,
+ intensity: Int = 30
+ ) {
+ stimSwitch(
+ enable = true,
+ stimType = stimType,
+ intensity = intensity,
+ frequency = 80,
+ totalDuration = durationMinutes * 60 * 1000,
+ restTime = 1000,
+ silenceTime = 500,
+ fadeInTime = 5,
+ holdTime = (durationMinutes * 60) - 10,
+ fadeOutTime = 5
+ )
+ }
+
+ /**
+ * 电刺激预设:康复训练模式
+ */
+ fun stimRehabilitationMode(
+ stimType: Int = 2,
+ intensity: Int = 60
+ ) {
+ stimSwitch(
+ enable = true,
+ stimType = stimType,
+ intensity = intensity,
+ frequency = 120,
+ totalDuration = 300000, // 5分钟
+ restTime = 2000, // 2秒休息
+ silenceTime = 1000, // 1秒静默
+ fadeInTime = 3,
+ holdTime = 300, // 5分钟保持
+ fadeOutTime = 3
+ )
+ }
+
/**
* 同步时间戳
* 功能码: 0x0080
@@ -1174,30 +1548,38 @@ class BluetoothManager(private val context: Context) {
var foundNotifyCharacteristic: BluetoothGattCharacteristic? = null
// 首先尝试预设的UUID列表
- for (serviceUuid in SERVICE_UUIDS) {
+ for (i in SERVICE_UUIDS.indices) {
+ val serviceUuid = SERVICE_UUIDS[i]
val service = gatt.getService(serviceUuid)
if (service != null) {
Log.d(TAG, "找到匹配的服务: $serviceUuid")
foundService = service
- // 查找TX特征(用于发送数据)
- for (characteristicUuid in CHARACTERISTIC_UUIDS) {
- val characteristic = service.getCharacteristic(characteristicUuid)
- if (characteristic != null) {
- Log.d(TAG, "找到TX特征: $characteristicUuid")
- foundCharacteristic = characteristic
- break
- }
+ // 根据服务UUID确定设备类型
+ currentDeviceType = when (serviceUuid.toString().uppercase()) {
+ "0000ABF0-0000-1000-8000-00805F9B34FB" -> DeviceType.ESP32S3_DEVICE
+ "1BD790EC-E8B9-7580-0A46-44D30102EDA6" -> DeviceType.NEW_DEVICE
+ "6E400001-B5A3-F393-E0A9-68716563686F" -> DeviceType.QINGXUN
+ else -> DeviceType.QINGXUN // 默认为轻迅设备
}
- // 查找RX特征(用于接收数据)
- for (notifyUuid in NOTIFY_CHARACTERISTIC_UUIDS) {
- val notifyCharacteristic = service.getCharacteristic(notifyUuid)
- if (notifyCharacteristic != null) {
- Log.d(TAG, "找到RX特征: $notifyUuid")
- foundNotifyCharacteristic = notifyCharacteristic
- break
- }
+ Log.d(TAG, "检测到设备类型: $currentDeviceType")
+ callback?.onStatusChanged("🔍 检测到设备类型: $currentDeviceType")
+
+ // 查找对应的TX特征(用于发送数据)
+ val characteristicUuid = CHARACTERISTIC_UUIDS[i]
+ val characteristic = service.getCharacteristic(characteristicUuid)
+ if (characteristic != null) {
+ Log.d(TAG, "找到TX特征: $characteristicUuid")
+ foundCharacteristic = characteristic
+ }
+
+ // 查找对应的RX特征(用于接收数据)
+ val notifyUuid = NOTIFY_CHARACTERISTIC_UUIDS[i]
+ val notifyCharacteristic = service.getCharacteristic(notifyUuid)
+ if (notifyCharacteristic != null) {
+ Log.d(TAG, "找到RX特征: $notifyUuid")
+ foundNotifyCharacteristic = notifyCharacteristic
}
break
}
@@ -1381,12 +1763,15 @@ class BluetoothManager(private val context: Context) {
val deviceAddress = device.address
val rssi = result.rssi
- Log.d(TAG, "发现BLE设备: $deviceName ($deviceAddress) RSSI: $rssi")
+ Log.d(TAG, "🔍 发现BLE设备: $deviceName ($deviceAddress) RSSI: $rssi")
+ Log.d(TAG, "📊 设备详情: 名称=$deviceName, 地址=$deviceAddress, 信号=$rssi")
// 显示详细的设备信息
callback?.onStatusChanged("📱 发现BLE设备: $deviceName")
callback?.onStatusChanged(" 📍 地址: $deviceAddress")
callback?.onStatusChanged(" 📶 信号强度: $rssi dBm")
+ callback?.onStatusChanged(" 🔧 设备类型: ${device.type}")
+ Log.d(TAG, "📱 设备类型: ${device.type}")
// 检查是否是目标设备
if (deviceAddress.equals("A4:C3:37:86:9F:73", ignoreCase = true) ||
@@ -1414,11 +1799,32 @@ class BluetoothManager(private val context: Context) {
}
callback?.onError("❌ BLE扫描失败: $errorMessage")
- callback?.onStatusChanged("💡 尝试使用传统蓝牙扫描...")
- // 扫描失败时完成扫描
- callback?.onStatusChanged("扫描完成,找到 ${discoveredDevices.size} 个设备")
- callback?.onScanComplete(discoveredDevices.toList())
+ // 只在非混合模式下回退到传统蓝牙
+ val isMixedScan = android.os.Build.VERSION.SDK_INT == android.os.Build.VERSION_CODES.O ||
+ android.os.Build.VERSION.SDK_INT == android.os.Build.VERSION_CODES.O_MR1
+
+ if (!isMixedScan) {
+ callback?.onStatusChanged("💡 尝试使用传统蓝牙扫描...")
+
+ // 延迟1秒后启动传统蓝牙扫描,避免同时启动太多扫描
+ android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
+ try {
+ startClassicScan()
+ } catch (fallbackE: Exception) {
+ callback?.onError("❌ 传统蓝牙扫描也失败: ${fallbackE.message}")
+ }
+ }, 1000)
+ } else {
+ callback?.onStatusChanged("⚠️ BLE扫描失败,但混合扫描模式将继续使用传统蓝牙")
+ }
+
+ // 在混合扫描模式下,只记录错误但不结束扫描
+ if (!isMixedScan) {
+ // 扫描失败时完成扫描
+ callback?.onStatusChanged("扫描完成,找到 ${discoveredDevices.size} 个设备")
+ callback?.onScanComplete(discoveredDevices.toList())
+ }
}
}
@@ -1431,19 +1837,42 @@ class BluetoothManager(private val context: Context) {
BluetoothDevice.ACTION_FOUND -> {
val device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)
device?.let {
- Log.d(TAG, "发现传统蓝牙设备: ${it.name ?: "未知"} (${it.address})")
+ Log.d(TAG, "🔍 发现传统蓝牙设备: ${it.name ?: "未知"} (${it.address})")
+ Log.d(TAG, "📱 设备类型: ${it.type}")
+ Log.d(TAG, "📶 设备状态: ${it.bondState}")
+
+ // Android 8.1特殊处理
+ if (android.os.Build.VERSION.SDK_INT == android.os.Build.VERSION_CODES.O_MR1) {
+ Log.d(TAG, "📱 Android 8.1检测到传统蓝牙设备")
+ callback?.onStatusChanged("📱 Android 8.1发现设备: ${it.name ?: "未知设备"}")
+ }
// 添加所有发现的设备(不进行过滤)
addDiscoveredDevice(it)
callback?.onDeviceFound(it)
- }
+ } ?: Log.w(TAG, "⚠️ ACTION_FOUND广播中未找到设备信息")
}
BluetoothAdapter.ACTION_DISCOVERY_STARTED -> {
Log.d(TAG, "传统蓝牙扫描开始")
}
BluetoothAdapter.ACTION_DISCOVERY_FINISHED -> {
Log.d(TAG, "传统蓝牙扫描完成")
- callback?.onStatusChanged("扫描完成,找到 ${discoveredDevices.size} 个设备")
+ val classicDeviceCount = discoveredDevices.count { it.type != BluetoothDevice.DEVICE_TYPE_LE }
+ val totalDeviceCount = discoveredDevices.size
+
+ callback?.onStatusChanged("🔍 传统蓝牙扫描完成")
+ callback?.onStatusChanged("📊 传统蓝牙扫描结果:找到 $classicDeviceCount 个经典蓝牙设备")
+ callback?.onStatusChanged("📊 总共发现 $totalDeviceCount 个设备")
+
+ // Android 8.1混合扫描模式特殊处理
+ val isMixedScan = android.os.Build.VERSION.SDK_INT == android.os.Build.VERSION_CODES.O ||
+ android.os.Build.VERSION.SDK_INT == android.os.Build.VERSION_CODES.O_MR1
+
+ if (isMixedScan) {
+ callback?.onStatusChanged("✅ Android 8.1混合扫描完成")
+ callback?.onStatusChanged("📱 BLE + 传统蓝牙双重扫描完成")
+ }
+
callback?.onScanComplete(discoveredDevices.toList())
}
}
@@ -1460,15 +1889,34 @@ class BluetoothManager(private val context: Context) {
addAction(BluetoothDevice.ACTION_FOUND)
addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED)
addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)
+ // Android 8.1可能需要额外的广播动作
+ addAction(BluetoothAdapter.ACTION_STATE_CHANGED)
}
- context.registerReceiver(classicScanReceiver, filter)
+
+ // Android 8.1特殊处理:可能需要不同的注册方式
+ if (android.os.Build.VERSION.SDK_INT == android.os.Build.VERSION_CODES.O_MR1) {
+ Log.d(TAG, "📱 Android 8.1特殊注册广播接收器")
+ context.registerReceiver(classicScanReceiver, filter)
+ Log.d(TAG, "✅ Android 8.1广播接收器注册成功")
+ } else {
+ context.registerReceiver(classicScanReceiver, filter)
+ Log.d(TAG, "✅ 传统蓝牙扫描广播接收器已注册")
+ }
+
isClassicScanReceiverRegistered = true
Log.d(TAG, "传统蓝牙扫描广播接收器已注册")
+
+ // Android 8.1特殊状态
+ if (android.os.Build.VERSION.SDK_INT == android.os.Build.VERSION_CODES.O_MR1) {
+ Log.d(TAG, "📱 Android 8.1检测:扫描接收器已配置")
+ callback?.onStatusChanged("📱 Android 8.1检测:广播接收器已配置")
+ }
} else {
Log.d(TAG, "传统蓝牙扫描广播接收器已注册,跳过重复注册")
}
} catch (e: Exception) {
Log.e(TAG, "注册广播接收器失败: ${e.message}")
+ callback?.onStatusChanged("❌ 广播接收器注册失败: ${e.message}")
isClassicScanReceiverRegistered = false
}
}
@@ -1514,10 +1962,19 @@ class BluetoothManager(private val context: Context) {
callback?.onStatusChanged(" 📍 地址: $deviceAddress")
callback?.onStatusChanged(" 🔧 类型: $deviceType")
- // 检查是否是目标设备
- if (deviceAddress.equals("A4:C3:37:86:9F:73", ignoreCase = true) ||
- deviceAddress.equals("A4-C3-37-86-9F-73", ignoreCase = true)) {
- callback?.onStatusChanged("🎯 找到目标设备!")
+ // 检查是否是ESP32S3 SPP服务器或目标设备
+ val isESP32S3Device = deviceName.contains("ESP_SPP_SERVER", ignoreCase = true)
+ val isLegacyTargetDevice = deviceAddress.equals("A4:C3:37:86:9F:73", ignoreCase = true) ||
+ deviceAddress.equals("A4-C3-37-86-9F-73", ignoreCase = true)
+
+ if (isESP32S3Device) {
+ callback?.onStatusChanged("🔌 ESP32S3 SPP服务器!")
+ callback?.onStatusChanged(" 📡 SPP服务:0xABF0")
+ callback?.onStatusChanged(" 📤 数据发送:0xABF1/0xABF3")
+ callback?.onStatusChanged(" 📥 数据接收:0xABF2/0xABF4")
+ callback?.onStatusChanged("💡 优先级设备,建议立即连接")
+ } else if (isLegacyTargetDevice) {
+ callback?.onStatusChanged("🎯 找到传统目标设备!")
callback?.onStatusChanged("💡 建议:长按'连接蓝牙'按钮直接连接")
}
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 507f212..8171d17 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
@@ -27,6 +27,13 @@ import android.net.Uri
import android.widget.Toast
import android.os.Handler
import android.os.Looper
+import android.widget.TextView
+import android.widget.EditText
+import android.widget.Spinner
+import android.widget.ArrayAdapter
+import androidx.appcompat.widget.SwitchCompat
+import androidx.core.widget.NestedScrollView
+import android.widget.LinearLayout
class MainActivity : AppCompatActivity(), NativeMethodCallback, DataManager.RealTimeDataCallback, BluetoothManager.BluetoothCallback {
@@ -34,22 +41,27 @@ class MainActivity : AppCompatActivity(), NativeMethodCallback, DataManager.Real
private const val PERMISSION_REQUEST_CODE = 1001
// 根据Android版本动态确定所需权限
- private val REQUIRED_PERMISSIONS: Array = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
- // Android 12+ 使用新的蓝牙权限,但仍然需要位置权限用于扫描
- arrayOf(
- Manifest.permission.BLUETOOTH_SCAN,
- Manifest.permission.BLUETOOTH_CONNECT,
- Manifest.permission.ACCESS_FINE_LOCATION,
- Manifest.permission.ACCESS_COARSE_LOCATION
- )
- } else {
- // Android 11及以下需要位置权限
- arrayOf(
- Manifest.permission.BLUETOOTH_SCAN,
- Manifest.permission.BLUETOOTH_CONNECT,
- Manifest.permission.ACCESS_FINE_LOCATION,
- Manifest.permission.ACCESS_COARSE_LOCATION
- )
+ private val REQUIRED_PERMISSIONS: Array = when {
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
+ // Android 12+ 使用新的蓝牙权限,仍然需要位置权限用于扫描
+ arrayOf(
+ Manifest.permission.BLUETOOTH_SCAN,
+ Manifest.permission.BLUETOOTH_CONNECT,
+ Manifest.permission.ACCESS_FINE_LOCATION,
+ Manifest.permission.ACCESS_COARSE_LOCATION
+ )
+ }
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> {
+ // Android 6-11 只需要位置权限
+ arrayOf(
+ Manifest.permission.ACCESS_FINE_LOCATION,
+ Manifest.permission.ACCESS_COARSE_LOCATION
+ )
+ }
+ else -> {
+ // Android 5.1及以下不需要动态权限
+ emptyArray()
+ }
}
// Used to load the 'cmake_project_test' library on application startup.
@@ -117,7 +129,17 @@ class MainActivity : AppCompatActivity(), NativeMethodCallback, DataManager.Real
// 初始化UI
val permissionStatus = checkPermissionStatus()
- binding.sampleText.text = "应用已就绪,可以开始使用\n\n权限状态:\n$permissionStatus\n\n点击\"连接蓝牙\"按钮开始蓝牙连接..."
+ val androidVersion = android.os.Build.VERSION.RELEASE
+ val apiLevel = android.os.Build.VERSION.SDK_INT
+
+ updateStatus("📱 Android $androidVersion (API $apiLevel) - 就绪")
+ updateStatus("✅ 蓝牙管理器已初始化")
+ updateStatus("📋 权限状态:")
+ for (line in permissionStatus.split("\n")) {
+ updateStatus(" $line")
+ }
+ updateStatus("🎯 准备连接蓝牙设备")
+ updateStatus("💡 点击'连接蓝牙'按钮开始扫描")
// 确保图表容器可见
binding.dynamicChartContainer.visibility = View.VISIBLE
@@ -177,39 +199,141 @@ class MainActivity : AppCompatActivity(), NativeMethodCallback, DataManager.Real
private var notchFilterEnabled = false
private fun toggleBluetoothConnection() {
- if (bluetoothConnected) {
- // 断开连接
- bluetoothManager.disconnect()
- bluetoothConnected = false
- binding.bluetoothButton.text = "连接蓝牙"
- binding.bluetoothButton.setBackgroundColor(Color.parseColor("#4CAF50"))
- updateStatus("蓝牙已断开")
- } else {
- // 检查权限后再开始扫描
- if (checkAndRequestPermissions()) {
- startBluetoothScan()
+ // 检查当前按钮状态
+ val currentButtonText = binding.bluetoothButton.text.toString()
+
+ when {
+ bluetoothConnected -> {
+ // 已连接,断开连接
+ bluetoothManager.disconnect()
+ bluetoothConnected = false
+ binding.bluetoothButton.text = "连接蓝牙"
+ binding.bluetoothButton.setBackgroundColor(Color.parseColor("#4CAF50"))
+ updateStatus("蓝牙已断开")
+ }
+ currentButtonText == "扫描中..." -> {
+ // 正在扫描,停止扫描
+ stopBluetoothScan()
+ }
+ currentButtonText == "扫描失败" || currentButtonText == "权限检查中..." -> {
+ // 重试扫描
+ retryBluetoothScan()
+ }
+ else -> {
+ // 开始新扫描
+ startNewBluetoothScan()
}
}
}
+
+ private fun startNewBluetoothScan() {
+ updateStatus("🚀 开始蓝牙连接流程...")
+ updateStatus("📱 检测到Android ${Build.VERSION.RELEASE} (API ${Build.VERSION.SDK_INT})")
+
+ // Android 15特殊提示
+ if (Build.VERSION.SDK_INT >= 35) {
+ updateStatus("🔔 Android 15检测:确保已授予所有必要权限")
+ }
+
+ // 检查权限后再开始扫描
+ when {
+ Build.VERSION.SDK_INT < Build.VERSION_CODES.M -> {
+ // Android 6.0以下不需要动态权限
+ updateStatus("✅ Android ${Build.VERSION.RELEASE} 无需动态权限")
+ startBluetoothScan()
+ }
+ checkAndRequestPermissions() -> {
+ // 权限检查通过,开始扫描
+ updateStatus("🎯 权限检查通过,正在启动蓝牙扫描...")
+ startBluetoothScan()
+ }
+ else -> {
+ // 权限检查未通过或正在请求权限
+ updateStatus("⏳ 权限检查中,请稍候...")
+ updateStatus("💡 Android ${Build.VERSION.RELEASE} 权限指引:")
+ if (Build.VERSION.SDK_INT >= 35) {
+ updateStatus(" • 可能的权限:蓝牙扫描、蓝牙连接、位置")
+ updateStatus(" • 如果权限对话框没有出现,请手动进入应用设置→权限")
+ } else {
+ updateStatus(" • 需要的权限:蓝牙扫描、蓝牙连接、位置")
+ updateStatus(" • 请在弹出的权限对话框中点击 允许")
+ }
+ binding.bluetoothButton.text = "权限检查中..."
+ binding.bluetoothButton.setBackgroundColor(Color.parseColor("#FF9800"))
+ }
+ }
+ }
+
+ private fun stopBluetoothScan() {
+ try {
+ bluetoothManager.stopScan()
+ binding.bluetoothButton.text = "重新扫描"
+ binding.bluetoothButton.setBackgroundColor(Color.parseColor("#4CAF50"))
+ updateStatus("⏹️ 蓝牙扫描已停止")
+ updateStatus("💡 点击'重新扫描'按钮重新开始扫描")
+ } catch (e: Exception) {
+ Log.e("MainActivity", "停止蓝牙扫描失败: ${e.message}", e)
+ updateStatus("❌ 停止扫描失败: ${e.message}")
+ }
+ }
+
+ private fun retryBluetoothScan() {
+ binding.bluetoothButton.text = "重试扫描"
+ binding.bluetoothButton.setBackgroundColor(Color.parseColor("#FF9800"))
+ updateStatus("🔄 重试扫描...")
+ startNewBluetoothScan()
+ }
private fun startBluetoothScan() {
- bluetoothManager.startScan()
- binding.bluetoothButton.text = "扫描中..."
- binding.bluetoothButton.setBackgroundColor(Color.parseColor("#FF9800"))
- updateStatus("正在扫描蓝牙设备...")
+ try {
+ updateStatus("🚀 准备启动蓝牙扫描...")
+ bluetoothManager.startScan()
+ binding.bluetoothButton.text = "扫描中..."
+ binding.bluetoothButton.setBackgroundColor(Color.parseColor("#FF9800"))
+ updateStatus("✅ 蓝牙扫描已启动")
+ updateStatus("⏱️ 扫描时长15秒,请稍候...")
+
+ Log.d("MainActivity", "✅ 蓝牙扫描启动成功")
+
+ // 15秒后如果还在扫描,显示提示
+ android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
+ if (binding.bluetoothButton.text == "扫描中...") {
+ updateStatus("💡 扫描时间较长,可能附近没有BLE设备")
+ updateStatus("🔄 可以尝试停止后重新扫描")
+ }
+ }, 15000)
+
+ } catch (e: Exception) {
+ Log.e("MainActivity", "启动蓝牙扫描失败: ${e.message}", e)
+ updateStatus("❌ 蓝牙扫描启动失败: ${e.message}")
+ binding.bluetoothButton.text = "扫描失败"
+ binding.bluetoothButton.setBackgroundColor(Color.parseColor("#F44336"))
+ }
}
private fun checkAndRequestPermissions(): Boolean {
val missingPermissions = mutableListOf()
val grantedPermissions = mutableListOf()
+ Log.d("MainActivity", "=== 权限检查开始 ===")
+ Log.d("MainActivity", "Android版本: ${Build.VERSION.SDK_INT}")
+ Log.d("MainActivity", "需要检查的权限: ${REQUIRED_PERMISSIONS.contentToString()}")
+
updateStatus("🔍 检查蓝牙权限...")
+ updateStatus("📱 Android版本: ${Build.VERSION.RELEASE} (API ${Build.VERSION.SDK_INT})")
for (permission in REQUIRED_PERMISSIONS) {
- if (ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED) {
- grantedPermissions.add(permission.split(".").last())
+ val permissionStatus = ContextCompat.checkSelfPermission(this, permission)
+ val permissionName = permission.split(".").last()
+
+ Log.d("MainActivity", "权限 $permissionName 状态: $permissionStatus")
+
+ if (permissionStatus == PackageManager.PERMISSION_GRANTED) {
+ grantedPermissions.add(permissionName)
+ updateStatus("✅ 已授权: $permissionName")
} else {
missingPermissions.add(permission)
+ updateStatus("❌ 缺少权限: $permissionName")
}
}
@@ -219,19 +343,25 @@ class MainActivity : AppCompatActivity(), NativeMethodCallback, DataManager.Real
}
if (missingPermissions.isNotEmpty()) {
- val missingNames = missingPermissions.map { it.split(".").last() }
- updateStatus("❌ 缺少权限: ${missingNames.joinToString(", ")}")
updateStatus("💡 正在请求权限...")
- ActivityCompat.requestPermissions(
- this,
- missingPermissions.toTypedArray(),
- PERMISSION_REQUEST_CODE
- )
- return false
+ try {
+ ActivityCompat.requestPermissions(
+ this,
+ missingPermissions.toTypedArray(),
+ PERMISSION_REQUEST_CODE
+ )
+ updateStatus("📋 权限请求对话框已弹出")
+ return false
+ } catch (e: Exception) {
+ Log.e("MainActivity", "请求权限失败: ${e.message}", e)
+ updateStatus("❌ 权限请求失败: ${e.message}")
+ return false
+ }
}
updateStatus("✅ 所有必要权限已授予")
+ Log.d("MainActivity", "=== 权限检查完成:通过 ===")
return true
}
@@ -255,26 +385,38 @@ class MainActivity : AppCompatActivity(), NativeMethodCallback, DataManager.Real
when (requestCode) {
PERMISSION_REQUEST_CODE -> {
+ Log.d("MainActivity", "=== 权限请求结果回调 ===")
+ Log.d("MainActivity", "请求的权限数量: ${permissions.size}")
+ Log.d("MainActivity", "权限: ${permissions.contentToString()}")
+ Log.d("MainActivity", "结果: ${grantResults.contentToString()}")
+
val grantedPermissions = mutableListOf()
val deniedPermissions = mutableListOf()
+ updateStatus("📋 权限请求结果:")
+
for (i in permissions.indices) {
val permission = permissions[i]
val granted = grantResults[i] == PackageManager.PERMISSION_GRANTED
+ val permissionName = permission.split(".").last()
+
+ Log.d("MainActivity", "权限 $permissionName: ${if (granted) "已授予" else "被拒绝"}")
if (granted) {
- grantedPermissions.add(permission.split(".").last())
+ grantedPermissions.add(permissionName)
+ updateStatus("✅ $permissionName: 已授权")
} else {
- deniedPermissions.add(permission.split(".").last())
+ deniedPermissions.add(permissionName)
+ updateStatus("❌ $permissionName: 被拒绝")
}
}
if (grantedPermissions.isNotEmpty()) {
- updateStatus("✅ 权限已授予: ${grantedPermissions.joinToString(", ")}")
+ updateStatus("✅ 已授予权限: ${grantedPermissions.joinToString(", ")}")
}
if (deniedPermissions.isNotEmpty()) {
- updateStatus("❌ 权限被拒绝: ${deniedPermissions.joinToString(", ")}")
+ updateStatus("❌ 被拒绝权限: ${deniedPermissions.joinToString(", ")}")
updateStatus("💡 请在设置中手动授予权限")
}
@@ -283,12 +425,14 @@ class MainActivity : AppCompatActivity(), NativeMethodCallback, DataManager.Real
if (allGranted) {
updateStatus("🎉 所有权限已授予,可以开始蓝牙扫描")
updateStatus("📡 正在启动蓝牙扫描...")
+ binding.bluetoothButton.text = "权限通过"
+ binding.bluetoothButton.setBackgroundColor(Color.parseColor("#4CAF50"))
startBluetoothScan()
} else {
updateStatus("⚠️ 部分权限被拒绝,蓝牙功能可能受限")
- updateStatus("💡 建议:")
- updateStatus(" • 进入应用设置 → 权限")
- updateStatus(" • 手动开启蓝牙和位置权限")
+ updateStatus("💡 Android ${Build.VERSION.RELEASE} 解决方案:")
+ updateStatus(" • 进入设置 → 应用 → ${getString(android.R.string.unknownName)} → 权限")
+ updateStatus(" • 手动开启位置权限")
updateStatus(" • 重启应用")
binding.bluetoothButton.text = "权限被拒绝"
@@ -351,11 +495,11 @@ class MainActivity : AppCompatActivity(), NativeMethodCallback, DataManager.Real
super.onResume()
Log.d("MainActivity", "MainActivity恢复")
- // 检查蓝牙连接状态
+ // 检查蓝牙连接状态(只在真正连接后检查)
if (bluetoothConnected) {
Log.d("MainActivity", "蓝牙连接状态正常")
} else {
- Log.w("MainActivity", "⚠️ 蓝牙连接已断开,需要重新连接")
+ Log.d("MainActivity", "📱 未连接蓝牙设备(正常状态)")
}
}
@@ -1087,30 +1231,26 @@ class MainActivity : AppCompatActivity(), NativeMethodCallback, DataManager.Real
}
/**
- * 显示电刺激开关对话框
+ * 显示电刺激控制对话框
*/
private fun showStimSwitchDialog() {
- val stimOptions = arrayOf(
- "开启刺激A",
- "开启刺激B",
+ val dialogOptions = arrayOf(
+ "高级电刺激控制",
+ "预设模式选择",
+ "简单开关控制",
"关闭电刺激"
)
androidx.appcompat.app.AlertDialog.Builder(this)
- .setTitle("电刺激开关")
- .setItems(stimOptions) { _, which ->
+ .setTitle("电刺激控制")
+ .setItems(dialogOptions) { _, which ->
when (which) {
- 0 -> {
- bluetoothManager.stimSwitch(0, true)
- updateStatus("发送电刺激开关指令: 刺激A 开启")
- }
- 1 -> {
- bluetoothManager.stimSwitch(1, true)
- updateStatus("发送电刺激开关指令: 刺激B 开启")
- }
- 2 -> {
- bluetoothManager.stimSwitch(0, false)
- updateStatus("发送电刺激开关指令: 关闭电刺激")
+ 0 -> showAdvancedStimDialog() // 高级电刺激控制
+ 1 -> showPresetStimDialog() // 预设模式选择
+ 2 -> showSimpleStimDialog() // 简单开关控制
+ 3 -> {
+ bluetoothManager.stimSwitch(enable = false)
+ updateStatus("发送电刺激关闭指令")
}
}
}
@@ -1118,6 +1258,287 @@ class MainActivity : AppCompatActivity(), NativeMethodCallback, DataManager.Real
.show()
}
+ /**
+ * 高级电刺激控制对话框 - 完整参数版本
+ */
+ private fun showAdvancedStimDialog() {
+ val builder = androidx.appcompat.app.AlertDialog.Builder(this)
+ builder.setTitle("高级电刺激控制")
+
+ // 创建滚动视图
+ val scrollView = androidx.core.widget.NestedScrollView(this)
+
+ // 创建输入字段的线性布局
+ val layout = android.widget.LinearLayout(this).apply {
+ orientation = android.widget.LinearLayout.VERTICAL
+ setPadding(40, 20, 40, 20)
+ }
+
+ // 启用刺激开关
+ val enableSwitch = androidx.appcompat.widget.SwitchCompat(this).apply {
+ isChecked = true
+ }
+ val enableLabel = TextView(this).apply {
+ text = "启用刺激"
+ textSize = 16f
+ setPadding(0, 0, 0, 16)
+ }
+ layout.addView(enableLabel)
+ layout.addView(enableSwitch)
+
+ // 基础参数
+ val stimTypeInput = createSimpleInputField("刺激类型 (0-15):", "1")
+ layout.addView(stimTypeInput)
+
+ val intensityInput = createSimpleInputField("强度 (%):", "50")
+ layout.addView(intensityInput)
+
+ val frequencyInput = createSimpleInputField("频率 (Hz):", "100")
+ layout.addView(frequencyInput)
+
+ val durationInput = createSimpleInputField("总时长 (秒):", "10")
+ layout.addView(durationInput)
+
+ // 时间参数
+ val restTimeInput = createSimpleInputField("休息时间 (毫秒):", "1000")
+ layout.addView(restTimeInput)
+
+ val silenceTimeInput = createSimpleInputField("静默时间 (毫秒):", "500")
+ layout.addView(silenceTimeInput)
+
+ val fadeInTimeInput = createSimpleInputField("缓进时间 (秒):", "2")
+ layout.addView(fadeInTimeInput)
+
+ val holdTimeInput = createSimpleInputField("保持时间 (秒):", "5")
+ layout.addView(holdTimeInput)
+
+ val fadeOutTimeInput = createSimpleInputField("缓出时间 (秒):", "2")
+ layout.addView(fadeOutTimeInput)
+
+ // 添加提示信息
+ val hintText = TextView(this).apply {
+ text = "💡 提示:保持时间建议 = 总时长 - 缓进时间 - 缓出时间"
+ textSize = 12f
+ setTextColor(ContextCompat.getColor(this@MainActivity, android.R.color.darker_gray))
+ setPadding(0, 16, 0, 0)
+ }
+ layout.addView(hintText)
+
+ scrollView.addView(layout)
+ builder.setView(scrollView)
+
+ builder.setPositiveButton("开始刺激") { dialog, _ ->
+ try {
+ val enable = enableSwitch.isChecked
+ val stimTypeChild = stimTypeInput.getChildAt(1) as EditText
+ val intensityChild = intensityInput.getChildAt(1) as EditText
+ val frequencyChild = frequencyInput.getChildAt(1) as EditText
+ val durationChild = durationInput.getChildAt(1) as EditText
+ val restTimeChild = restTimeInput.getChildAt(1) as EditText
+ val silenceTimeChild = silenceTimeInput.getChildAt(1) as EditText
+ val fadeInTimeChild = fadeInTimeInput.getChildAt(1) as EditText
+ val holdTimeChild = holdTimeInput.getChildAt(1) as EditText
+ val fadeOutTimeChild = fadeOutTimeInput.getChildAt(1) as EditText
+
+ val stimType = stimTypeChild.text.toString().toInt().coerceIn(0, 15)
+ val intensity = intensityChild.text.toString().toInt().coerceIn(1, 100)
+ val frequency = frequencyChild.text.toString().toInt().coerceIn(1, 200)
+ val totalDuration = durationChild.text.toString().toInt().coerceIn(1, 3600) * 1000
+ val restTime = restTimeChild.text.toString().toInt().coerceIn(100, 10000)
+ val silenceTime = silenceTimeChild.text.toString().toInt().coerceIn(100, 5000)
+ val fadeInTime = fadeInTimeChild.text.toString().toInt().coerceIn(1, 30)
+ val holdTime = holdTimeChild.text.toString().toInt().coerceIn(1, 300)
+ val fadeOutTime = fadeOutTimeChild.text.toString().toInt().coerceIn(1, 30)
+
+ bluetoothManager.stimSwitch(
+ enable = enable,
+ stimType = stimType,
+ intensity = intensity,
+ frequency = frequency,
+ totalDuration = totalDuration,
+ restTime = restTime,
+ silenceTime = silenceTime,
+ fadeInTime = fadeInTime,
+ holdTime = holdTime,
+ fadeOutTime = fadeOutTime
+ )
+
+ updateStatus("发送高级电刺激控制指令:")
+ updateStatus(" 类型: $stimType, 强度: $intensity%, 频率: ${frequency}Hz")
+ updateStatus(" 总时长: ${totalDuration/1000}s, 保持: ${holdTime}s")
+ updateStatus(" 休息: ${restTime}ms, 静默: ${silenceTime}ms")
+ updateStatus(" 缓进/出: ${fadeInTime}s/${fadeOutTime}s")
+
+ } catch (e: NumberFormatException) {
+ updateStatus("❌ 参数输入错误,请检查数值格式")
+ } catch (e: Exception) {
+ updateStatus("❌ 发送失败: ${e.message}")
+ }
+ dialog.dismiss()
+ }
+ builder.setNegativeButton("取消", null)
+ builder.show()
+ }
+
+ /**
+ * 创建简单的输入字段
+ */
+ private fun createSimpleInputField(label: String, defaultValue: String): LinearLayout {
+ val layout = LinearLayout(this).apply {
+ orientation = LinearLayout.VERTICAL
+ setPadding(0, 16, 0, 8)
+ }
+
+ val labelView = TextView(this).apply {
+ text = label
+ textSize = 14f
+ }
+
+ val inputView = EditText(this).apply {
+ setText(defaultValue)
+ hint = defaultValue
+ inputType = android.text.InputType.TYPE_CLASS_NUMBER
+ textSize = 16f
+ }
+
+ layout.addView(labelView)
+ layout.addView(inputView)
+
+ return layout
+ }
+
+ /**
+ * 预设模式选择对话框
+ */
+ private fun showPresetStimDialog() {
+ val presetOptions = arrayOf(
+ "高强度短期刺激 (30s)",
+ "低强度长期刺激 (15分钟)",
+ "康复训练模式 (5分钟)",
+ "自定义预设参数"
+ )
+
+ androidx.appcompat.app.AlertDialog.Builder(this)
+ .setTitle("预设刺激模式")
+ .setItems(presetOptions) { _, which ->
+ when (which) {
+ 0 -> {
+ bluetoothManager.stimHighIntensityShort(intensity = 80)
+ updateStatus("启动高强度短期刺激模式")
+ }
+ 1 -> {
+ bluetoothManager.stimLowIntensityLong(intensity = 30)
+ updateStatus("启动低强度长期刺激模式")
+ }
+ 2 -> {
+ bluetoothManager.stimRehabilitationMode(intensity = 60)
+ updateStatus("启动康复训练模式")
+ }
+ 3 -> showPresetParamDialog()
+ }
+ }
+ .setNegativeButton("取消", null)
+ .show()
+ }
+
+ /**
+ * 预设参数调整对话框 - 简化版本
+ */
+ private fun showPresetParamDialog() {
+ val builder = androidx.appcompat.app.AlertDialog.Builder(this)
+ builder.setTitle("调整预设参数")
+
+ val layout = LinearLayout(this).apply {
+ orientation = LinearLayout.VERTICAL
+ setPadding(50, 30, 50, 30)
+ }
+
+ // 预设类型选择
+ val presetTypeView = Spinner(this).apply {
+ adapter = ArrayAdapter(this@MainActivity, android.R.layout.simple_spinner_item, arrayOf("高强度短期", "低强度长期", "康复训练")).also { adapter ->
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
+ }
+ setSelection(0)
+ }
+ val presetLabel = TextView(this).apply {
+ text = "预设类型:"
+ }
+ layout.addView(presetLabel)
+ layout.addView(presetTypeView)
+
+ // 强度输入
+ val intensityInput = createSimpleInputField("强度 (%):", "60")
+ layout.addView(intensityInput)
+
+ // 持续时间输入
+ val durationInput = createSimpleInputField("持续时间:", "30")
+ layout.addView(durationInput)
+
+ builder.setView(layout)
+ builder.setPositiveButton("应用") { dialog, _ ->
+ try {
+ val presetType = presetTypeView.selectedItemPosition
+ val intensityChild = intensityInput.getChildAt(1) as EditText
+ val durationChild = durationInput.getChildAt(1) as EditText
+ val intensity = intensityChild.text.toString().toInt().coerceIn(1, 100)
+ val duration = durationChild.text.toString().toInt().coerceIn(1, 120)
+
+ when (presetType) {
+ 0 -> {
+ bluetoothManager.stimHighIntensityShort(durationSeconds = duration, intensity = intensity)
+ updateStatus("应用高强度短期模式: ${duration}s, 强度${intensity}%")
+ }
+ 1 -> {
+ bluetoothManager.stimLowIntensityLong(durationMinutes = duration, intensity = intensity)
+ updateStatus("应用低强度长期模式: ${duration}分钟, 强度${intensity}%")
+ }
+ 2 -> {
+ bluetoothManager.stimRehabilitationMode(intensity = intensity)
+ updateStatus("应用康复训练模式: 强度${intensity}%")
+ }
+ }
+ } catch (e: NumberFormatException) {
+ updateStatus("❌ 参数输入错误")
+ }
+ dialog.dismiss()
+ }
+ builder.setNegativeButton("取消", null)
+ builder.show()
+ }
+
+ /**
+ * 简单开关控制对话框
+ */
+ private fun showSimpleStimDialog() {
+ val simpleOptions = arrayOf(
+ "开启刺激A (默认参数)",
+ "开启刺激B (默认参数)",
+ "关闭电刺激"
+ )
+
+ androidx.appcompat.app.AlertDialog.Builder(this)
+ .setTitle("简单电刺激控制")
+ .setItems(simpleOptions) { _, which ->
+ when (which) {
+ 0 -> {
+ bluetoothManager.stimSwitchSimple(0, true)
+ updateStatus("发送电刺激开关指令: 刺激A 开启 (默认参数)")
+ }
+ 1 -> {
+ bluetoothManager.stimSwitchSimple(1, true)
+ updateStatus("发送电刺激开关指令: 刺激B 开启 (默认参数)")
+ }
+ 2 -> {
+ bluetoothManager.stimSwitch(enable = false)
+ updateStatus("发送电刺激关闭指令")
+ }
+ }
+ }
+ .setNegativeButton("取消", null)
+ .show()
+ }
+
+
/**
* 显示调试状态对话框
*/