feat(firmware): 새 DFU zip (1.0.0+5) 번들 + 버전 불일치 시 업데이트 배너
DFU 파일:
- dfu_application_2+35.zip (구) 삭제
- dfu_application_1.0.0+5.zip (신규 238KB) 추가
FirmwarePackage:
- DEFAULT_ASSET = "dfu_application_1.0.0+5.zip"
- BUNDLED_VERSION = "1.0.0+5" 상수 신규 (manifest.json version_MCUBOOT 와 일치)
BleManager:
- isFirmwareOutdated: 신규 semver 포맷 ("1.0.0+5") 은 legacy VBTFW build-number
비교 제외 (false positive 방지 · parseFirmwareBuild 가 tail 숫자 5 를 118 미만
으로 오판정 안 하도록).
- isFirmwareUpdateAvailable (신규): 런타임 fw 문자열이 BUNDLED_VERSION 을 포함
하지 않으면 true. 사용자에게 "새 펌웨어 사용 가능" 배너 노출.
FirmwareWarningBanner:
- update-available 파랑 배너 신규 (정보성 · 우선순위 outdated > timedOut > update).
- onUpdateAvailableTap 콜백 · 파랑 배너 탭 시 FIRMWARE_UPDATE 화면 이동.
- PiezoMonitoringView · PlacementGuideView 호출부에 콜백 전달.
strings.xml (KR/EN):
- firmware_update_available_title/desc
동작 흐름:
1. 앱 연결 → mid?/rid: 로 fw 수신 (예: "VBTFW0200")
2. isFirmwareUpdateAvailable → true (fw !contains "1.0.0+5")
3. 상단 파랑 배너 "새 펌웨어가 있습니다 · 기기: VBTFW0200 · 신규: 1.0.0+5" 노출
4. 탭 → FIRMWARE_UPDATE 화면 → DFU 진행 → 성공 시 fw 갱신 후 배너 자동 사라짐
향후 FW 팀이 버전 스키마 확정 시 BUNDLED_VERSION 만 갱신하면 됨.
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -121,13 +121,32 @@ class BleManager private constructor(private val context: Context) {
|
||||
return m.groupValues[1].toIntOrNull()
|
||||
}
|
||||
|
||||
/** 펌웨어 버전이 MIN_FIRMWARE_BUILD 미만이면 true. 모르거나(아직 미수신) 형식 안 맞으면 false. */
|
||||
/** 펌웨어 버전이 MIN_FIRMWARE_BUILD 미만이면 true. 모르거나(아직 미수신) 형식 안 맞으면 false.
|
||||
* legacy "VBTFWxxxx" 포맷 대상. 신규 semver ("1.0.0+5") 는 무시. */
|
||||
val isFirmwareOutdated: Boolean
|
||||
get() {
|
||||
val n = parseFirmwareBuild(firmwareVersion.value) ?: return false
|
||||
val fw = firmwareVersion.value
|
||||
if (fw.isEmpty()) return false
|
||||
// 신규 semver 포맷 (1.0.0+5 등) 은 이 legacy build-number 비교 대상 아님.
|
||||
if (fw.startsWith("1.") || fw.contains("+")) return false
|
||||
val n = parseFirmwareBuild(fw) ?: return false
|
||||
return n < MIN_FIRMWARE_BUILD
|
||||
}
|
||||
|
||||
/**
|
||||
* 앱에 번들된 DFU 펌웨어와 기기 실행 중 펌웨어 버전이 다르면 true (2026-08-04).
|
||||
* → 사용자에게 "새 펌웨어 사용 가능 · 업데이트 하시겠습니까?" 배너 노출.
|
||||
* 정확한 match (contains) 로 비교 · 미수신 (empty) 시 false (모르는 것과 필요없는 것 구분).
|
||||
*/
|
||||
val isFirmwareUpdateAvailable: Boolean
|
||||
get() {
|
||||
val fw = firmwareVersion.value
|
||||
if (fw.isEmpty()) return false
|
||||
val bundled = com.medithings.vesiscan.firmware.FirmwareLoader.BUNDLED_VERSION
|
||||
// fw 가 bundled 버전 문자열을 포함하면 최신 (예: "VesiScan 1.0.0+5" · "1.0.0+5" 등)
|
||||
return !fw.contains(bundled)
|
||||
}
|
||||
|
||||
val batteryLevel = mutableStateOf(0)
|
||||
/** 현재 연결된 디바이스 RSSI (dBm) · 미측정/미연결 시 null. dev-mode UI 오버레이용. */
|
||||
val rssi = mutableStateOf<Int?>(null)
|
||||
|
||||
@@ -45,7 +45,13 @@ data class FirmwarePackage(
|
||||
*/
|
||||
object FirmwareLoader {
|
||||
|
||||
const val DEFAULT_ASSET = "dfu_application_2+35.zip"
|
||||
const val DEFAULT_ASSET = "dfu_application_1.0.0+5.zip"
|
||||
|
||||
/**
|
||||
* 번들된 DFU 버전 (manifest.json 의 version_MCUBOOT 필드와 일치).
|
||||
* 2026-08-04: BleManager 의 update-available 배너가 이 값 vs 런타임 FW 를 비교.
|
||||
*/
|
||||
const val BUNDLED_VERSION = "1.0.0+5"
|
||||
|
||||
private const val MANIFEST = "manifest.json"
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.medithings.vesiscan.ui.components
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
@@ -39,7 +40,11 @@ import kotlinx.coroutines.delay
|
||||
* (3) 정상 또는 미연결 — 안 보임
|
||||
*/
|
||||
@Composable
|
||||
fun FirmwareWarningBanner(bleManager: BleManager, modifier: Modifier = Modifier) {
|
||||
fun FirmwareWarningBanner(
|
||||
bleManager: BleManager,
|
||||
modifier: Modifier = Modifier,
|
||||
onUpdateAvailableTap: (() -> Unit)? = null, // 2026-08-04 · 파랑 배너 탭 시 DFU 이동
|
||||
) {
|
||||
val fw by bleManager.firmwareVersion
|
||||
val isConnected by bleManager.isConnected
|
||||
val lastRxAt by bleManager.lastBleRxAt
|
||||
@@ -76,25 +81,42 @@ fun FirmwareWarningBanner(bleManager: BleManager, modifier: Modifier = Modifier)
|
||||
val deviceAlive = lastRxAt > 0 && (System.currentTimeMillis() - lastRxAt) < 12_000
|
||||
|
||||
val outdated = bleManager.isFirmwareOutdated
|
||||
val updateAvailable = bleManager.isFirmwareUpdateAvailable
|
||||
val showTimedOut = timedOut && !deviceAlive
|
||||
if (!outdated && !showTimedOut) return
|
||||
if (!outdated && !showTimedOut && !updateAvailable) return
|
||||
|
||||
val bgColor = if (outdated) Color(0xFFFF9500) else Color(0xFFD32F2F)
|
||||
val title = if (outdated) stringResource(R.string.firmware_outdated_title) else stringResource(R.string.firmware_no_response_title)
|
||||
// timedOut(mid? 미수신) 케이스는 사용자에게 mid?/펌웨어 호환 같은 기술 용어가 무의미.
|
||||
// outdated 일 때만 상세 안내, timedOut 은 타이틀만 노출.
|
||||
val subtitle: String? = if (outdated) {
|
||||
stringResource(R.string.firmware_outdated_desc, fw, BleManager.MIN_FIRMWARE_BUILD)
|
||||
} else {
|
||||
null
|
||||
// 우선순위: outdated (빨강) > timedOut (빨강) > updateAvailable (파랑 · 정보성).
|
||||
val bgColor = when {
|
||||
outdated -> Color(0xFFFF9500) // 주황 · 강한 경고
|
||||
showTimedOut -> Color(0xFFD32F2F) // 빨강 · 응답 없음
|
||||
else -> Color(0xFF1976D2) // 파랑 · 업데이트 사용 가능
|
||||
}
|
||||
val title = when {
|
||||
outdated -> stringResource(R.string.firmware_outdated_title)
|
||||
showTimedOut -> stringResource(R.string.firmware_no_response_title)
|
||||
else -> stringResource(R.string.firmware_update_available_title)
|
||||
}
|
||||
val subtitle: String? = when {
|
||||
outdated -> stringResource(R.string.firmware_outdated_desc, fw, BleManager.MIN_FIRMWARE_BUILD)
|
||||
showTimedOut -> null
|
||||
else -> stringResource(
|
||||
R.string.firmware_update_available_desc,
|
||||
fw,
|
||||
com.medithings.vesiscan.firmware.FirmwareLoader.BUNDLED_VERSION,
|
||||
)
|
||||
}
|
||||
|
||||
// 파랑 배너 (update available) 는 탭 가능 · 나머지는 정보 표시만.
|
||||
val clickableModifier = if (updateAvailable && onUpdateAvailableTap != null) {
|
||||
Modifier.clickable { onUpdateAvailableTap() }
|
||||
} else Modifier
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 6.dp)
|
||||
.clip(RoundedCornerShape(10.dp))
|
||||
.background(bgColor)
|
||||
.then(clickableModifier)
|
||||
.padding(horizontal = 12.dp, vertical = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
|
||||
+4
-1
@@ -1323,7 +1323,10 @@ fun PiezoMonitoringView(appState: AppState) {
|
||||
// BLE 끊김 banner는 사내 테스트 피드백으로 제거됨 — 백그라운드 자동 재연결만 유지.
|
||||
|
||||
// 펌웨어 구버전 경고
|
||||
com.medithings.vesiscan.ui.components.FirmwareWarningBanner(bleManager)
|
||||
com.medithings.vesiscan.ui.components.FirmwareWarningBanner(
|
||||
bleManager,
|
||||
onUpdateAvailableTap = { appState.currentScreen = com.medithings.vesiscan.AppScreen.FIRMWARE_UPDATE },
|
||||
)
|
||||
com.medithings.vesiscan.ui.components.BatteryWarningBanner(bleManager)
|
||||
|
||||
val scrollState = rememberScrollState()
|
||||
|
||||
@@ -300,7 +300,10 @@ fun PlacementGuideView(appState: AppState) {
|
||||
}
|
||||
|
||||
// 펌웨어 구버전 경고
|
||||
com.medithings.vesiscan.ui.components.FirmwareWarningBanner(bleManager)
|
||||
com.medithings.vesiscan.ui.components.FirmwareWarningBanner(
|
||||
bleManager,
|
||||
onUpdateAvailableTap = { appState.currentScreen = com.medithings.vesiscan.AppScreen.FIRMWARE_UPDATE },
|
||||
)
|
||||
// 배터리 잔량 경고 (<15%)
|
||||
com.medithings.vesiscan.ui.components.BatteryWarningBanner(bleManager)
|
||||
|
||||
|
||||
@@ -177,6 +177,9 @@
|
||||
<string name="firmware_outdated_title">펌웨어 업데이트 필요</string>
|
||||
<string name="firmware_no_response_title">기기 응답 없음</string>
|
||||
<string name="firmware_outdated_desc">현재: %1$s · 필요: VBTFW%2$04d 이상. 측정이 정상 동작하지 않을 수 있습니다.</string>
|
||||
<!-- 2026-08-04 · 번들 FW 버전 불일치 시 업데이트 안내 (정보성 · 파랑 배너) -->
|
||||
<string name="firmware_update_available_title">새 펌웨어가 있습니다</string>
|
||||
<string name="firmware_update_available_desc">기기: %1$s · 신규: %2$s. 눌러서 DFU 업데이트를 진행하세요.</string>
|
||||
|
||||
<!-- Lockable Slider -->
|
||||
<string name="slider_unlocked_desc">잠금 해제됨 — 다시 탭하여 잠금</string>
|
||||
|
||||
@@ -177,6 +177,9 @@
|
||||
<string name="firmware_outdated_title">Firmware update required</string>
|
||||
<string name="firmware_no_response_title">No response from device</string>
|
||||
<string name="firmware_outdated_desc">Current: %1$s · Required: VBTFW%2$04d or higher. Measurement may not work properly.</string>
|
||||
<!-- 2026-08-04 · 번들 FW 버전 불일치 시 업데이트 안내 (정보성 · 파랑 배너) -->
|
||||
<string name="firmware_update_available_title">New firmware available</string>
|
||||
<string name="firmware_update_available_desc">Device: %1$s · New: %2$s. Tap to update via DFU.</string>
|
||||
|
||||
<!-- Lockable Slider -->
|
||||
<string name="slider_unlocked_desc">Unlocked — tap again to lock</string>
|
||||
|
||||
Reference in New Issue
Block a user