diff --git a/app/src/main/java/com/example/medilightv2android/ble/BleManager.kt b/app/src/main/java/com/example/medilightv2android/ble/BleManager.kt index fd90aef..473649b 100644 --- a/app/src/main/java/com/example/medilightv2android/ble/BleManager.kt +++ b/app/src/main/java/com/example/medilightv2android/ble/BleManager.kt @@ -536,6 +536,11 @@ class BleManager private constructor(private val context: Context) { sendRaw(CRC16.buildCommandASCII("mid", " ")) } + /** mfv? — FW Version only. mid? 응답 안 오는 펌웨어용 fallback. */ + fun sendFirmwareVersionQuery() { + sendRaw(CRC16.buildCommandASCII("mfv", " ")) + } + fun sendLedMode(state: Int) { sendRaw(CRC16.buildCommandBE("mls", intArrayOf(state))) } @@ -995,6 +1000,13 @@ class BleManager private constructor(private val context: Context) { startBatteryPolling() startWatchdog() sendDeviceInfoQuery() + // 4초 후에도 펌웨어 미수신이면 mfv? fallback (구 펌웨어는 mid? 미지원 가능성) + handler.postDelayed({ + if (firmwareVersion.value.isEmpty() && isConnected.value) { + debugLogger.info("FW fallback: sending mfv?") + sendFirmwareVersionQuery() + } + }, 4_000) logd { "Service ready! (CCCD written)" } } } else { @@ -1115,27 +1127,38 @@ class BleManager private constructor(private val context: Context) { } } "rid:" -> { - // BLE 응답 끝에 NULL byte padding이 붙는 경우가 있음 — PostgreSQL JSONB가 - // 0x00을 거부하므로 trim()으로 안 빠지는 NULL/제어문자를 명시적으로 제거. - val info = if (data.size > 4) - String(data, 4, data.size - 4, Charsets.US_ASCII) - .replace(Regex("[\\x00-\\x1F\\x7F]"), " ").trim() - else "?" + // 명세: [tag 4B] [HW 12B] [Serial 12B] [FW 12B] [crc 2B] = 42B. + // 각 12B 필드는 ASCII 가변 길이 + NULL/공백/제어문자 padding. + // (구) whitespace split 방식은 padding 이 space 가 아닐 때 깨졌음. + fun field(offset: Int): String { + val end = (offset + 12).coerceAtMost(data.size - 2) // CRC 2B 앞까지 + if (end <= offset) return "" + return String(data, offset, end - offset, Charsets.US_ASCII) + .replace(Regex("[\\x00-\\x1F\\x7F]"), "").trim() + } + val hw = field(4) + val sn = field(16) + val fw = field(28) + val info = "$hw $sn $fw".trim() debugLogger.rx("rid", data.size, "device_info=$info") - debugLogger.info("DEVICE_INFO $info") - // 형식: "VBTHW0100 VBT26040001 VBTFW0111" (HW, Serial, FW) - val parts = info.split(Regex("\\s+")).filter { it.isNotBlank() } - val hw = parts.firstOrNull { it.startsWith("VBTHW") } ?: "" - val fw = parts.firstOrNull { it.startsWith("VBTFW") } ?: "" - val sn = parts.firstOrNull { it.startsWith("VBT") && !it.startsWith("VBTHW") && !it.startsWith("VBTFW") } ?: "" + debugLogger.info("DEVICE_INFO hw=$hw sn=$sn fw=$fw") handler.post { if (hw.isNotEmpty()) hardwareVersion.value = hw if (fw.isNotEmpty()) firmwareVersion.value = fw if (sn.isNotEmpty()) serialNumber.value = sn } - // ble.log에 헤더 블록 형태로 (텍스트 에디터로 열었을 때 즉시 보임) debugLogger.deviceInfo(fw, hw, sn) } + "rfv:" -> { + // 명세: [tag 4B] [FW ASCII N B] [crc 2B] — FW 만 별도 쿼리(mfv?) 응답. + // mid? 가 응답 안 하는 펌웨어 fallback 용. + if (data.size > 6) { + val fw = String(data, 4, data.size - 6, Charsets.US_ASCII) + .replace(Regex("[\\x00-\\x1F\\x7F]"), "").trim() + debugLogger.rx("rfv", data.size, "fw=$fw") + if (fw.isNotEmpty()) handler.post { firmwareVersion.value = fw } + } + } "rls:" -> { val state = if (data.size >= 5) data[4].toInt() and 0xFF else -1 debugLogger.rx("rls", data.size, "led_state=$state")