From 04607241ac8d486fe4f99e69d89d94f5783980b8 Mon Sep 17 00:00:00 2001 From: jjangddu Date: Tue, 7 Jul 2026 10:50:34 +0900 Subject: [PATCH] =?UTF-8?q?fix(ble):=20=EC=A2=80=EB=B9=84=20=EC=84=B8?= =?UTF-8?q?=EC=85=98=20=EC=9E=AC=EC=97=B0=EA=B2=B0=20=EC=8B=A4=EC=B8=A1=20?= =?UTF-8?q?=EB=8C=80=EC=9D=91=20=E2=80=94=20=EC=BD=9C=EB=B0=B1=20duplicate?= =?UTF-8?q?=20=EC=A0=9C=EA=B1=B0=20+=20watchdog=2025=EC=B4=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 실측 로그 (2026-07-07 10:43~10:44 세션) 재분석 결과 두 이슈 발견. 1) Duplicate startBatteryPolling 이전 fix (f4f5407) 로 onConnectionStateChanged(true) 가 CCCD write 완료 시점 (BleManager onDescriptorWrite 안 line 1059 startBatteryPolling 직전) 으로 이동. 그런데 PiezoMonitoringView 콜백도 startBatteryPolling 을 호출해 첫 msn battery TX 가 2번 발생 (10:43:46.008 + 10:43:46.040). Idempotent 라 실제 부작용은 낮지만 노이즈. PiezoMonitoringView 콜백에서 startBatteryPolling 제거 — BleManager 가 이미 담당. 2) Watchdog 25초 (from 15초) 세션 1: 첫 연결 후 CCCD/MTU/CONN_PRIORITY/startBatteryPolling 모두 성공, 그런데 15초간 RX 0개 → forced reconnect. 세션 2: 재연결 후 14초 만에 첫 rim RX 도착 (10:44:00.475) → 스스로 회복. 즉 peripheral / OS BLE 스택의 좀비 회복 시간이 실측 14초. 기존 15초 timeout 은 회복 직전에 reconnect 발동 → 무한 재연결 루프. 25초로 연장하면 대부분 회복 케이스 살릴 수 있음. 세션 1 의 첫 handshake 응답 실종 (mid → rid 응답 없음) 은 앱 코드로 근본 해결 불가 (peripheral 또는 OS BLE 스택 문제). Watchdog 연장은 실질적 완화책. --- .../java/com/medithings/vesiscan/ble/BleManager.kt | 6 +++++- .../ui/views/monitoring/PiezoMonitoringView.kt | 12 +++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/medithings/vesiscan/ble/BleManager.kt b/app/src/main/java/com/medithings/vesiscan/ble/BleManager.kt index 43f57ec..90cbaca 100644 --- a/app/src/main/java/com/medithings/vesiscan/ble/BleManager.kt +++ b/app/src/main/java/com/medithings/vesiscan/ble/BleManager.kt @@ -75,7 +75,11 @@ class BleManager private constructor(private val context: Context) { private var reconnectTimer: Runnable? = null @Volatile private var lastRxTimestamp: Long = 0 private var watchdogTimer: Runnable? = null - private val watchdogTimeoutMs: Long = 15000 + // 2026-07-07: 15 → 25초. 실측 로그 (2026-07-07 10:43~10:44) 에서 재연결 후 첫 RX 가 + // 14초 지연 후 도착하는 케이스 확인. Peripheral / OS BLE 스택의 좀비 회복 시간을 + // 허용하기 위해 timeout 여유 확보. 15초로는 회복 직전에 forced reconnect 발생 → + // 무한 재연결 루프. 25초면 대부분 회복 케이스 커버 가능. + private val watchdogTimeoutMs: Long = 25000 // 2026-07-07 fix: disconnect 시 취소 위해 Runnable 참조 유지. // fwFallback: onDescriptorWrite 성공 후 4초 mfv? fallback. // cccdRetry: onDescriptorWrite 실패 후 500ms retry. diff --git a/app/src/main/java/com/medithings/vesiscan/ui/views/monitoring/PiezoMonitoringView.kt b/app/src/main/java/com/medithings/vesiscan/ui/views/monitoring/PiezoMonitoringView.kt index ed9d9fc..2c3fd03 100644 --- a/app/src/main/java/com/medithings/vesiscan/ui/views/monitoring/PiezoMonitoringView.kt +++ b/app/src/main/java/com/medithings/vesiscan/ui/views/monitoring/PiezoMonitoringView.kt @@ -738,14 +738,20 @@ fun PiezoMonitoringView(appState: AppState) { // 실패해도 UI 알림 없음 — BleManager가 다음 주기에 또 재시도하거나 idle. } bleManager.onConnectionStateChanged = { connected -> + // 2026-07-07 fix: startBatteryPolling 을 여기서 호출하지 않음. + // 이전 fix 로 onConnectionStateChanged(true) 가 CCCD 완료 시점 (=BleManager + // 자체가 line 1059 에서 startBatteryPolling 실행하는 시점) 으로 이동했음. + // 콜백에서 또 호출하면 첫 msn battery TX 가 2번 나가는 duplicate 발생. + // BleManager 가 항상 담당하므로 여기서는 아무것도 안 함. (또는 UI 관련 side effect + // 만 처리하도록 향후 확장.) if (connected) { - bleManager.startBatteryPolling() + // 필요 시 UI 관련 반응만. Battery polling / TX 는 BleManager 소관. } } // 진입 시 이미 연결되어 있으면 한 번 polling 재시작 (안전망). - // 이전 화면(Clinical Unpair → 새 연결 등)에서 batteryLevel=0으로 reset된 채로 // 도넛차트 진입 시 onDescriptorWrite/onConnectionStateChanged 둘 다 이미 끝나 있어 - // 콜백이 트리거 안 됨. 진입 시점에 강제 1회 polling으로 보강. + // 콜백이 트리거 안 됨. 진입 시점에 강제 1회 polling으로 보강 — 이건 duplicate 아님 + // (BleManager 는 이미 polling 중이지만 stopBatteryPolling→restart 로 idempotent). if (bleManager.isConnected.value) { bleManager.startBatteryPolling() }