fix: watchdog heartbeat, 배터리 계산, placement 디바운스+방향 수정

BLE:
- watchdog idle heartbeat: 10초 무통신 시 IMU query로 keepalive
- 배터리: 3500-3700mV→0-5%, 3700-4100mV→5-100%, 증가 무시
- 배터리 표시: 아이콘→퍼센트 숫자 (색상 유지)

Placement:
- 힌트 디바운스: 같은 phase 2회 연속일 때만 변경 (깜빡임 방지)
- 방향 힌트 반전: CH3만 잡힘→아래로↓, CH0만 잡힘→위로↑

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-28 17:27:32 +09:00
parent 5cdae8a86e
commit c6e84bd6c0
3 changed files with 41 additions and 25 deletions
@@ -477,11 +477,15 @@ class BleManager private constructor(private val context: Context) {
if (!isConnected.value) break
val silenceMs = System.currentTimeMillis() - lastRxTimestamp
// Idle 10초 이상이면 IMU query로 heartbeat
if (silenceMs > 10000 && silenceMs <= watchdogTimeoutMs) {
handler.post { sendImuQuery() }
}
if (silenceMs > watchdogTimeoutMs) {
// 직접 로그 기록 (handler 블로킹 무관)
debugLogger.error("WATCHDOG_TIMEOUT silence=${silenceMs}ms — forcing reconnect")
// GATT close는 어느 스레드에서든 호출 가능
watchdogRunning = false
try {
bluetoothGatt?.disconnect()
@@ -491,7 +495,6 @@ class BleManager private constructor(private val context: Context) {
txCharacteristic = null
rxCharacteristic = null
// UI 업데이트 + reconnect는 메인 스레드
handler.post {
stopBatteryPolling()
isConnected.value = false
@@ -872,8 +875,14 @@ class BleManager private constructor(private val context: Context) {
"rsn:" -> {
if (data.size >= 6) {
val millivolts = ((data[4].toInt() and 0xFF) shl 8) or (data[5].toInt() and 0xFF)
val pct = ((millivolts - 3700) * 100 / 500).coerceIn(0, 100)
batteryLevel.value = pct
val pct = when {
millivolts <= 3500 -> 0
millivolts <= 3700 -> ((millivolts - 3500) * 5 / 200).coerceIn(0, 5)
else -> (5 + (millivolts - 3700) * 95 / 400).coerceIn(5, 100)
}
if (pct <= batteryLevel.value || batteryLevel.value == 0) {
batteryLevel.value = pct
}
debugLogger.rx("rsn", data.size, "battery=${millivolts}mV (${pct}%)")
}
}