STIMULATE COMMIT
This commit is contained in:
parent
bd8fd53597
commit
d6c870b243
|
|
@ -1,3 +1,4 @@
|
|||
{
|
||||
"java.configuration.updateBuildConfiguration": "interactive"
|
||||
"java.configuration.updateBuildConfiguration": "interactive",
|
||||
"idf.pythonInstallPath": "C:\\Espressif\\tools\\idf-python\\3.11.2\\python.exe"
|
||||
}
|
||||
|
|
@ -6,15 +6,22 @@
|
|||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
||||
|
||||
<!-- Android 12+ 新蓝牙权限 -->
|
||||
<!-- Android 12-14 新蓝牙权限 -->
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
|
||||
android:usesPermissionFlags="neverForLocation" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
||||
|
||||
<!-- Android 15+ 可能的额外权限 -->
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
|
||||
|
||||
<!-- 位置权限 (所有Android版本都需要用于蓝牙扫描) -->
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
|
||||
<!-- Android 8.1传统蓝牙扫描额外权限 -->
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BLUETOOTH_SCAN_RESULTS" />
|
||||
|
||||
<!-- 文件导出权限 -->
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
|
|
|
|||
|
|
@ -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,15 +282,46 @@ class BluetoothManager(private val context: Context) {
|
|||
|
||||
callback?.onStatusChanged("✅ 权限检查通过,开始扫描...")
|
||||
|
||||
// 优先使用BLE扫描
|
||||
// 改进的兼容性扫描策略
|
||||
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 {
|
||||
// 回退到传统蓝牙扫描
|
||||
callback?.onStatusChanged("📡 使用传统蓝牙扫描模式")
|
||||
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) {
|
||||
Log.e(TAG, "扫描失败: ${e.message}")
|
||||
|
|
@ -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("💡 尝试使用传统蓝牙扫描...")
|
||||
|
||||
// 延迟1秒后启动传统蓝牙扫描,避免同时启动太多扫描
|
||||
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
||||
try {
|
||||
startClassicScan()
|
||||
} catch (fallbackE: Exception) {
|
||||
callback?.onError("❌ 传统蓝牙扫描也失败: ${fallbackE.message}")
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -323,20 +397,153 @@ 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<String>()
|
||||
|
||||
// 启动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}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -345,9 +552,11 @@ class BluetoothManager(private val context: Context) {
|
|||
*/
|
||||
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("🔍 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扫描成功")
|
||||
}
|
||||
|
||||
// 仅在非混合模式下触发完成回调
|
||||
if (!isMixedScan) {
|
||||
callback?.onScanComplete(discoveredDevices.toList())
|
||||
} else {
|
||||
callback?.onStatusChanged("⏳ BLE扫描完成,传统蓝牙扫描继续进行...")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "停止BLE扫描失败: ${e.message}")
|
||||
// 如果是FragmentManager相关错误,记录但不抛出异常
|
||||
|
|
@ -871,28 +1091,182 @@ 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
|
||||
|
||||
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}")
|
||||
// 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) }}")
|
||||
|
||||
sendCommand(packet)
|
||||
|
||||
} catch (e: Exception) {
|
||||
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
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -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) {
|
||||
// 根据服务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 // 默认为轻迅设备
|
||||
}
|
||||
|
||||
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
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 查找RX特征(用于接收数据)
|
||||
for (notifyUuid in NOTIFY_CHARACTERISTIC_UUIDS) {
|
||||
// 查找对应的RX特征(用于接收数据)
|
||||
val notifyUuid = NOTIFY_CHARACTERISTIC_UUIDS[i]
|
||||
val notifyCharacteristic = service.getCharacteristic(notifyUuid)
|
||||
if (notifyCharacteristic != null) {
|
||||
Log.d(TAG, "找到RX特征: $notifyUuid")
|
||||
foundNotifyCharacteristic = notifyCharacteristic
|
||||
break
|
||||
}
|
||||
}
|
||||
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,13 +1799,34 @@ class BluetoothManager(private val context: Context) {
|
|||
}
|
||||
|
||||
callback?.onError("❌ BLE扫描失败: $errorMessage")
|
||||
|
||||
// 只在非混合模式下回退到传统蓝牙
|
||||
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>(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)
|
||||
}
|
||||
|
||||
// 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("💡 建议:长按'连接蓝牙'按钮直接连接")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,23 +41,28 @@ class MainActivity : AppCompatActivity(), NativeMethodCallback, DataManager.Real
|
|||
private const val PERMISSION_REQUEST_CODE = 1001
|
||||
|
||||
// 根据Android版本动态确定所需权限
|
||||
private val REQUIRED_PERMISSIONS: Array<String> = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
// Android 12+ 使用新的蓝牙权限,但仍然需要位置权限用于扫描
|
||||
private val REQUIRED_PERMISSIONS: Array<String> = 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
|
||||
)
|
||||
} else {
|
||||
// Android 11及以下需要位置权限
|
||||
}
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> {
|
||||
// Android 6-11 只需要位置权限
|
||||
arrayOf(
|
||||
Manifest.permission.BLUETOOTH_SCAN,
|
||||
Manifest.permission.BLUETOOTH_CONNECT,
|
||||
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.
|
||||
init {
|
||||
|
|
@ -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) {
|
||||
// 断开连接
|
||||
// 检查当前按钮状态
|
||||
val currentButtonText = binding.bluetoothButton.text.toString()
|
||||
|
||||
when {
|
||||
bluetoothConnected -> {
|
||||
// 已连接,断开连接
|
||||
bluetoothManager.disconnect()
|
||||
bluetoothConnected = false
|
||||
binding.bluetoothButton.text = "连接蓝牙"
|
||||
binding.bluetoothButton.setBackgroundColor(Color.parseColor("#4CAF50"))
|
||||
updateStatus("蓝牙已断开")
|
||||
} else {
|
||||
// 检查权限后再开始扫描
|
||||
if (checkAndRequestPermissions()) {
|
||||
startBluetoothScan()
|
||||
}
|
||||
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() {
|
||||
try {
|
||||
updateStatus("🚀 准备启动蓝牙扫描...")
|
||||
bluetoothManager.startScan()
|
||||
binding.bluetoothButton.text = "扫描中..."
|
||||
binding.bluetoothButton.setBackgroundColor(Color.parseColor("#FF9800"))
|
||||
updateStatus("正在扫描蓝牙设备...")
|
||||
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<String>()
|
||||
val grantedPermissions = mutableListOf<String>()
|
||||
|
||||
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("💡 正在请求权限...")
|
||||
|
||||
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<String>()
|
||||
val deniedPermissions = mutableListOf<String>()
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 显示调试状态对话框
|
||||
*/
|
||||
|
|
|
|||
Loading…
Reference in New Issue