fix(firmware): VBTFW ↔ MCUboot semver 매핑 · update-available 정확 판정 (FW 팀 스펙 2026-08-05)
증상 (사용자 보고):
기기 fw = VBTFW0200 · 번들 DFU = 1.0.0+5 · 앱이 "새 펌웨어 사용 가능" 배너 노출.
실제로는 기기 (2.0.0) > 번들 (1.0.0) 이라 업데이트 불필요.
원인:
기존 isFirmwareUpdateAvailable 은 단순 String contains 비교 (`!fw.contains(bundled)`)
→ "VBTFW0200" 은 "1.0.0+5" 를 포함 안 하므로 무조건 true. 방향성 없음.
FW 팀 스펙 (2026-08-05 슬라이드 · Firmware 식별 코드):
A. 공식 펌웨어 버전: VBTFW MMNN → M.0.N (major=MM, patch=NN, minor 항상 0)
· VBTFW0100 = 1.0.0
· VBTFW0200 = 2.0.0
· VBTFW0201 = 2.0.1
B. MCUboot/DFU 이미지: major.minor.patch+build (build 는 major/minor/patch 비교 무관)
C. 두 포맷 1:1 매칭 관리 (VBTFW0100 = 1.0.0+0)
수정:
BleManager.parseFwSemver(raw): Triple<Int, Int, Int>?
- VBTFW / VB0FW / VBFW 정규식 (`VB\d?FW(\d{2})(\d{2})`) → (M, 0, N)
- MCUboot 정규식 (`(\d+)\.(\d+)\.(\d+)`) → (major, minor, patch)
BleManager.compareSemver(a, b): Int (튜플 3단계 비교)
isFirmwareUpdateAvailable: bundled 튜플 > device 튜플 일 때만 true
→ 기기가 이미 최신이거나 동일하면 배너 미노출
검증 케이스:
device VBTFW0200 · bundled 1.0.0+5 → (1,0,0) < (2,0,0) → false (no banner)
device VBTFW0100 · bundled 2.0.0+0 → (2,0,0) > (1,0,0) → true (banner)
device VBTFW0200 · bundled 2.0.1+0 → (2,0,1) > (2,0,0) → true (banner)
빌드 SUCCESSFUL 37s.
This commit is contained in:
@@ -121,30 +121,75 @@ class BleManager private constructor(private val context: Context) {
|
|||||||
return m.groupValues[1].toIntOrNull()
|
return m.groupValues[1].toIntOrNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 펌웨어 버전이 MIN_FIRMWARE_BUILD 미만이면 true. 모르거나(아직 미수신) 형식 안 맞으면 false.
|
/**
|
||||||
* legacy "VBTFWxxxx" 포맷 대상. 신규 semver ("1.0.0+5") 는 무시. */
|
* 2026-08-05: FW 팀 스펙 (VBTFW 코드표) 반영. 두 포맷 통일 매핑.
|
||||||
|
*
|
||||||
|
* A. VBTFW MMNN → (M, 0, N)
|
||||||
|
* 예: VBTFW0100 = 1.0.0, VBTFW0200 = 2.0.0, VBTFW0201 = 2.0.1
|
||||||
|
* B. MCUboot major.minor.patch[+build] → (major, minor, patch)
|
||||||
|
* 예: 1.0.0+5 = 1.0.0 (build 5 는 major/minor/patch 비교와 무관)
|
||||||
|
*
|
||||||
|
* Returns Triple(major, minor, patch) · 파싱 실패 시 null.
|
||||||
|
*/
|
||||||
|
private fun parseFwSemver(raw: String): Triple<Int, Int, Int>? {
|
||||||
|
// VBTFW / VB0FW / VBFW MMNN
|
||||||
|
val vbt = Regex("""VB\d?FW(\d{2})(\d{2})""").find(raw)
|
||||||
|
if (vbt != null) {
|
||||||
|
val major = vbt.groupValues[1].toIntOrNull() ?: return null
|
||||||
|
val patch = vbt.groupValues[2].toIntOrNull() ?: return null
|
||||||
|
return Triple(major, 0, patch)
|
||||||
|
}
|
||||||
|
// MCUboot semver
|
||||||
|
val mcu = Regex("""(\d+)\.(\d+)\.(\d+)""").find(raw)
|
||||||
|
if (mcu != null) {
|
||||||
|
return Triple(
|
||||||
|
mcu.groupValues[1].toInt(),
|
||||||
|
mcu.groupValues[2].toInt(),
|
||||||
|
mcu.groupValues[3].toInt(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 튜플 비교: a>b 면 양수, 같으면 0, 작으면 음수. */
|
||||||
|
private fun compareSemver(a: Triple<Int, Int, Int>, b: Triple<Int, Int, Int>): Int {
|
||||||
|
if (a.first != b.first) return a.first - b.first
|
||||||
|
if (a.second != b.second) return a.second - b.second
|
||||||
|
return a.third - b.third
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 펌웨어 버전이 MIN_FIRMWARE_BUILD 미만이면 true. legacy "VBTFWxxxx" 만 판정.
|
||||||
|
* 신규 semver 는 별도 update-available 로 처리. */
|
||||||
val isFirmwareOutdated: Boolean
|
val isFirmwareOutdated: Boolean
|
||||||
get() {
|
get() {
|
||||||
val fw = firmwareVersion.value
|
val fw = firmwareVersion.value
|
||||||
if (fw.isEmpty()) return false
|
if (fw.isEmpty()) return false
|
||||||
// 신규 semver 포맷 (1.0.0+5 등) 은 이 legacy build-number 비교 대상 아님.
|
|
||||||
if (fw.startsWith("1.") || fw.contains("+")) return false
|
if (fw.startsWith("1.") || fw.contains("+")) return false
|
||||||
val n = parseFirmwareBuild(fw) ?: return false
|
val n = parseFirmwareBuild(fw) ?: return false
|
||||||
return n < MIN_FIRMWARE_BUILD
|
return n < MIN_FIRMWARE_BUILD
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 앱에 번들된 DFU 펌웨어와 기기 실행 중 펌웨어 버전이 다르면 true (2026-08-04).
|
* 앱 번들 DFU 가 기기 fw 보다 **엄격히 최신** 이면 true (2026-08-05).
|
||||||
* → 사용자에게 "새 펌웨어 사용 가능 · 업데이트 하시겠습니까?" 배너 노출.
|
* → "새 펌웨어 사용 가능" 배너 노출.
|
||||||
* 정확한 match (contains) 로 비교 · 미수신 (empty) 시 false (모르는 것과 필요없는 것 구분).
|
*
|
||||||
|
* 매핑 규칙 (FW 팀 스펙 · VBTFW 코드표):
|
||||||
|
* VBTFW MMNN = M.0.N (major=MM, minor=0, patch=NN)
|
||||||
|
* MCUboot X.Y.Z+B = (X, Y, Z) · +B build 는 무시 (같은 major.minor.patch 는 동일 취급)
|
||||||
|
*
|
||||||
|
* 예:
|
||||||
|
* device VBTFW0200 (2.0.0) · bundled 1.0.0+5 (1.0.0) → bundled < device → **false (배너 X)**
|
||||||
|
* device VBTFW0100 (1.0.0) · bundled 2.0.0+0 (2.0.0) → bundled > device → **true (배너 O)**
|
||||||
|
* device VBTFW0200 (2.0.0) · bundled 2.0.1+0 (2.0.1) → bundled > device → true
|
||||||
*/
|
*/
|
||||||
val isFirmwareUpdateAvailable: Boolean
|
val isFirmwareUpdateAvailable: Boolean
|
||||||
get() {
|
get() {
|
||||||
val fw = firmwareVersion.value
|
val fw = firmwareVersion.value
|
||||||
if (fw.isEmpty()) return false
|
if (fw.isEmpty()) return false
|
||||||
val bundled = com.medithings.vesiscan.firmware.FirmwareLoader.BUNDLED_VERSION
|
val bundled = com.medithings.vesiscan.firmware.FirmwareLoader.BUNDLED_VERSION
|
||||||
// fw 가 bundled 버전 문자열을 포함하면 최신 (예: "VesiScan 1.0.0+5" · "1.0.0+5" 등)
|
val devTuple = parseFwSemver(fw) ?: return false
|
||||||
return !fw.contains(bundled)
|
val bunTuple = parseFwSemver(bundled) ?: return false
|
||||||
|
return compareSemver(bunTuple, devTuple) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
val batteryLevel = mutableStateOf(0)
|
val batteryLevel = mutableStateOf(0)
|
||||||
|
|||||||
Reference in New Issue
Block a user