refactor(firmware): 임상앱에서 프로브 DFU 와 펌웨어 버전 판정을 뺀다 — 펌웨어 갱신은 사용자 앱이 맡는다
사용자 결정(2026-10-13). 임상 폰 두 대 모두 사용자 앱이 같이 깔려 있고 거기서 양산 펌웨어 VB0FW0100 으로 올리므로, 임상앱이 번들 zip 과 버전 판정을 따로 들고 있을 이유가 없다. 실제로 임상 번들은 1.0.0+5 로 VBTFW0207 보다 낡아 "재설치"가 다운그레이드였고, 최소빌드 118 규칙은 0100 부터 다시 세는 양산 펌웨어에 "구버전" 거짓 경고를 낼 참이었다. 뺀 것 - firmware/ 패키지 전부(DfuManager·DfuNus·DfuNusConnection·DfuBleScanner·FirmwarePackage· FirmwareUpdateView)와 assets/dfu_application_1.0.0+5.zip, mcumgr-ble 의존성. - AppScreen.FIRMWARE_UPDATE 라우트·뒤로가기 매핑, 홈 화면 "펌웨어 업데이트" 버튼(여백만 남김). - BleManager 의 MIN_FIRMWARE_BUILD · parseFirmwareBuild · parseFwSemver · isFirmwareOutdated · isFirmwareUpdateAvailable · currentBluetoothDevice(DFU 전용이었다). - 배너의 주황(구버전)·파랑(새 펌웨어) 분기와 그 문자열. 빨강 "기기 응답 없음"은 생존 신호라 남긴다. 남긴 것 - 펌웨어 버전 표시(설정)와 세션 JSON·CSV 의 firmware_version 기록 — 어떤 펌웨어로 잰 세션인지는 계속 추적된다. - ClinicalEvent.FIRMWARE_OUTDATED 카테고리는 스키마 호환을 위해 두되, 올리는 곳은 없다. 검증: assembleDemoDebug · testDemoDebugUnitTest 통과. 수신 CRC 가드(사용자 앱 a0a61773)는 별건으로 남아 있다. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -54,8 +54,6 @@ class BleManager private constructor(private val context: Context) {
|
||||
|
||||
companion object {
|
||||
private const val TAG = "BleManager"
|
||||
/** 정상 측정에 필요한 최소 펌웨어 빌드 번호 (mtb? throttle/MTU 가정과 일치). */
|
||||
const val MIN_FIRMWARE_BUILD = 118
|
||||
private inline fun logd(msg: () -> String) { if (BuildConfig.DEBUG) Log.d(TAG, msg()) }
|
||||
private inline fun logw(msg: () -> String) { if (BuildConfig.DEBUG) Log.w(TAG, msg()) }
|
||||
private inline fun loge(msg: () -> String) { if (BuildConfig.DEBUG) Log.e(TAG, msg()) }
|
||||
@@ -199,85 +197,13 @@ class BleManager private constructor(private val context: Context) {
|
||||
val hardwareVersion = mutableStateOf("") // "VBTHW0100"
|
||||
val serialNumber = mutableStateOf("") // "VBT26040001"
|
||||
/** 마지막으로 의미있는 BLE RX (reb 등) 가 도착한 시각 ms.
|
||||
* FirmwareWarningBanner false-positive 차단용. */
|
||||
* FirmwareWarningBanner("기기 응답 없음") false-positive 차단용. */
|
||||
val lastBleRxAt = mutableStateOf(0L)
|
||||
|
||||
/** "VBTFW0116" → 116, 형식 안 맞으면 null. */
|
||||
fun parseFirmwareBuild(raw: String): Int? {
|
||||
val m = Regex("(\\d+)$").find(raw) ?: return null
|
||||
return m.groupValues[1].toIntOrNull()
|
||||
}
|
||||
// 펌웨어 버전 판정(최소빌드 118 경고 · 번들 DFU 비교)은 2026-10-13 에 뺐다. 임상앱은 버전을
|
||||
// 세션 JSON·CSV 에 기록만 하고 판정하지 않는다 — 판정과 DFU 는 같은 폰의 사용자 앱이 맡는다.
|
||||
// (양산 펌웨어 VB0FW0100 은 0100 부터 다시 세므로 옛 규칙이 "구버전" 거짓 경고를 냈다.)
|
||||
|
||||
/**
|
||||
* 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
|
||||
get() {
|
||||
val fw = firmwareVersion.value
|
||||
if (fw.isEmpty()) return false
|
||||
if (fw.startsWith("1.") || fw.contains("+")) return false
|
||||
val n = parseFirmwareBuild(fw) ?: return false
|
||||
return n < MIN_FIRMWARE_BUILD
|
||||
}
|
||||
|
||||
/**
|
||||
* 앱 번들 DFU 가 기기 fw 보다 **엄격히 최신** 이면 true (2026-08-05).
|
||||
* → "새 펌웨어 사용 가능" 배너 노출.
|
||||
*
|
||||
* 매핑 규칙 (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
|
||||
get() {
|
||||
val fw = firmwareVersion.value
|
||||
if (fw.isEmpty()) return false
|
||||
val bundled = com.medithings.vesiscan.firmware.FirmwareLoader.BUNDLED_VERSION
|
||||
val devTuple = parseFwSemver(fw) ?: return false
|
||||
val bunTuple = parseFwSemver(bundled) ?: return false
|
||||
return compareSemver(bunTuple, devTuple) > 0
|
||||
}
|
||||
|
||||
val batteryLevel = mutableStateOf(0)
|
||||
/** 현재 연결된 디바이스 RSSI (dBm) · 미측정/미연결 시 null. dev-mode UI 오버레이용. */
|
||||
@@ -353,12 +279,6 @@ class BleManager private constructor(private val context: Context) {
|
||||
val bluetoothEnabled = mutableStateOf(bluetoothAdapter?.isEnabled == true)
|
||||
val isServiceReady = mutableStateOf(false)
|
||||
|
||||
/** 현재 GATT 연결된 BluetoothDevice · 없으면 null. Firmware DFU 진입 시
|
||||
* 스캔 생략하고 바로 이 기기로 재연결하는 경로에서 사용. */
|
||||
val currentBluetoothDevice: android.bluetooth.BluetoothDevice?
|
||||
@android.annotation.SuppressLint("MissingPermission")
|
||||
get() = bluetoothGatt?.device
|
||||
|
||||
val isBonded: Boolean
|
||||
@android.annotation.SuppressLint("MissingPermission")
|
||||
get() = bluetoothGatt?.device?.bondState == android.bluetooth.BluetoothDevice.BOND_BONDED
|
||||
|
||||
Reference in New Issue
Block a user