fix+UX: BLE 중복수신 수정, peak detection 개선, UI 대폭 정리
BLE: - GATT 다중연결 race condition 수정 (reconnectConnecting 플래그) - onCharacteristicChanged API 레벨 분기 (dual callback 방지) - DeviceScan: isConnected → isServiceReady 전환 (서비스 준비 후 화면전환) - Placement: mpa/maa 500ms 딜레이 (power on 대기) - Disconnect & Unbond 기능 추가 (msr? + removeBond + 저장정보 삭제) - autoConnect 삭제 (HomeView) Algorithm: - 후위벽 피크 >= threshold 조건 추가 (false positive 차단) - low-echo 분석범위 80샘플 제한 (뒤쪽 노이즈 무시) - urineLen: post-ant-1 → post-ant 수정 - Placement: UrinAI fallback 제거 (PiezoEchoAnalyzer만 사용) - 기기 프리셋 autoDetect index 7→8 수정 (VBT26040Nxx) - dps 기본값 1.771 → 1.968 UI: - 도넛차트: Home버튼 삭제, Auto/Catheter count/History 삭제 - 3버튼 레이아웃 (Catheterize/Auto/Spot) - 배터리 아이콘 (60+초록/30+주황/빨강) + 설정 톱니바퀴 분리 - SensorSelect/Consent 페이지 스킵 - 온보딩: VesiScan-Basic, NIRS 페이지 삭제 - MaxVolume: Step 표시 삭제, mL 오른쪽 배치 - BackHandler 전체 재정리 (자연스러운 뒤로가기) - BLE 신호 기준 완화 (Good≥-80, Fair≥-90) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -166,6 +166,12 @@ class BleManager private constructor(private val context: Context) {
|
||||
// Connection
|
||||
fun connect(bleDevice: BleDevice) {
|
||||
stopScan()
|
||||
cancelAutoReconnect()
|
||||
reconnectScanCallback?.let { cb ->
|
||||
try { bluetoothAdapter?.bluetoothLeScanner?.stopScan(cb) } catch (_: Exception) {}
|
||||
reconnectScanCallback = null
|
||||
}
|
||||
reconnectConnecting = false
|
||||
logd { "Connecting to ${bleDevice.name} (${bleDevice.address})" }
|
||||
|
||||
// Clean up any existing GATT connection
|
||||
@@ -241,6 +247,55 @@ class BleManager private constructor(private val context: Context) {
|
||||
bluetoothGatt?.disconnect()
|
||||
}
|
||||
|
||||
fun disconnectAndUnbond() {
|
||||
isUserDisconnect = true
|
||||
cancelAutoReconnect()
|
||||
stopWatchdog()
|
||||
stopBatteryPolling()
|
||||
connectionTimer?.let { handler.removeCallbacks(it) }
|
||||
connectionTimer = null
|
||||
|
||||
// 기기에 본딩 삭제 + 재부팅 명령 (msr?)
|
||||
sendRaw(CRC16.buildCommandASCII("msr", " "))
|
||||
debugLogger.info("TX msr? unbond & reboot device")
|
||||
|
||||
val device = bluetoothGatt?.device
|
||||
|
||||
// 기기가 재부팅되므로 약간 대기 후 GATT 정리
|
||||
handler.postDelayed({
|
||||
bluetoothGatt?.let { gatt ->
|
||||
try { gatt.disconnect(); gatt.close() } catch (_: Exception) {}
|
||||
}
|
||||
bluetoothGatt = null
|
||||
txCharacteristic = null
|
||||
rxCharacteristic = null
|
||||
isConnected.value = false
|
||||
isServiceReady.value = false
|
||||
connectedDeviceName.value = ""
|
||||
batteryLevel.value = 0
|
||||
lastConnectedAddress = null
|
||||
|
||||
// Android 측 본딩 정보도 삭제
|
||||
device?.let { dev ->
|
||||
try {
|
||||
val method = dev.javaClass.getMethod("removeBond")
|
||||
method.invoke(dev)
|
||||
logd { "removeBond called for ${dev.address}" }
|
||||
} catch (e: Exception) {
|
||||
logw { "removeBond failed: ${e.message}" }
|
||||
}
|
||||
}
|
||||
|
||||
// 저장된 기기 정보 삭제
|
||||
try {
|
||||
val prefs = context.getSharedPreferences("medilight_prefs", Context.MODE_PRIVATE)
|
||||
prefs.edit().remove("last_device_address").remove("last_device_name").apply()
|
||||
} catch (_: Exception) {}
|
||||
|
||||
debugLogger.info("DEVICE_UNREGISTERED")
|
||||
}, 300)
|
||||
}
|
||||
|
||||
// Reconnection
|
||||
fun reconnectToLastDevice(lastDeviceAddress: String?) {
|
||||
if (lastDeviceAddress == null || bluetoothAdapter?.isEnabled != true) {
|
||||
@@ -248,6 +303,13 @@ class BleManager private constructor(private val context: Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Stop any active reconnect scan first
|
||||
reconnectScanCallback?.let { cb ->
|
||||
try { bluetoothAdapter.bluetoothLeScanner?.stopScan(cb) } catch (_: Exception) {}
|
||||
reconnectScanCallback = null
|
||||
}
|
||||
reconnectConnecting = false
|
||||
|
||||
// Clean up existing
|
||||
bluetoothGatt?.let { gatt ->
|
||||
try { gatt.disconnect(); gatt.close() } catch (_: Exception) {}
|
||||
@@ -481,6 +543,7 @@ class BleManager private constructor(private val context: Context) {
|
||||
}
|
||||
|
||||
private var reconnectScanCallback: ScanCallback? = null
|
||||
@Volatile private var reconnectConnecting = false
|
||||
|
||||
private fun attemptAutoReconnect() {
|
||||
val targetAddress = lastConnectedAddress ?: return
|
||||
@@ -492,11 +555,12 @@ class BleManager private constructor(private val context: Context) {
|
||||
}
|
||||
|
||||
reconnectAttempt++
|
||||
reconnectConnecting = false
|
||||
logd { "Auto-reconnect #$reconnectAttempt: scanning for $targetName ($targetAddress)" }
|
||||
debugLogger.info("RECONNECT_SCAN #$reconnectAttempt scanning for $targetName")
|
||||
|
||||
bluetoothGatt?.let { gatt ->
|
||||
try { gatt.close() } catch (_: Exception) {}
|
||||
try { gatt.disconnect(); gatt.close() } catch (_: Exception) {}
|
||||
bluetoothGatt = null
|
||||
}
|
||||
|
||||
@@ -514,17 +578,18 @@ class BleManager private constructor(private val context: Context) {
|
||||
|
||||
val callback = object : ScanCallback() {
|
||||
override fun onScanResult(callbackType: Int, result: ScanResult) {
|
||||
if (reconnectConnecting) return
|
||||
val name = try { result.device.name ?: "" } catch (_: Exception) { "" }
|
||||
val address = result.device.address
|
||||
|
||||
if (address == targetAddress || (targetName.isNotEmpty() && name == targetName)) {
|
||||
reconnectConnecting = true
|
||||
logd { "Reconnect scan found: $name ($address)" }
|
||||
debugLogger.info("RECONNECT_FOUND $name ($address) RSSI=${result.rssi}")
|
||||
|
||||
try { scanner.stopScan(this) } catch (_: Exception) {}
|
||||
reconnectScanCallback = null
|
||||
|
||||
// Connect directly (autoConnect=false for fast connection)
|
||||
bluetoothGatt = result.device.connectGatt(
|
||||
context, false, gattCallback, BluetoothDevice.TRANSPORT_LE
|
||||
)
|
||||
@@ -735,19 +800,23 @@ class BleManager private constructor(private val context: Context) {
|
||||
}
|
||||
|
||||
override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, value: ByteArray) {
|
||||
if (characteristic.uuid == RX_CHAR_UUID) {
|
||||
if (value.size < 4) return
|
||||
handler.post { processReceivedData(value) }
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
if (characteristic.uuid == RX_CHAR_UUID) {
|
||||
if (value.size < 4) return
|
||||
handler.post { processReceivedData(value) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("Deprecated in API 33", ReplaceWith("onCharacteristicChanged(gatt, characteristic, value)"))
|
||||
override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {
|
||||
if (characteristic.uuid == RX_CHAR_UUID) {
|
||||
@Suppress("DEPRECATION")
|
||||
val data = characteristic.value ?: return
|
||||
if (data.size < 4) return
|
||||
handler.post { processReceivedData(data) }
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
||||
if (characteristic.uuid == RX_CHAR_UUID) {
|
||||
@Suppress("DEPRECATION")
|
||||
val data = characteristic.value ?: return
|
||||
if (data.size < 4) return
|
||||
handler.post { processReceivedData(data) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user