diff --git a/app/src/main/java/com/example/medilightv2android/managers/GreenZoneConstants.kt b/app/src/main/java/com/example/medilightv2android/managers/GreenZoneConstants.kt index db6ad9e..2ac64d6 100644 --- a/app/src/main/java/com/example/medilightv2android/managers/GreenZoneConstants.kt +++ b/app/src/main/java/com/example/medilightv2android/managers/GreenZoneConstants.kt @@ -31,6 +31,11 @@ object GreenZoneConstants { /** Posture pitch 임계값 (deg). |pitch| > 이 값 → LYING, ≤ 이 값 → NON_LYING. dev 모드에서 조정 가능. */ @Volatile var pitchLyingThreshold: Float = 25f + /** Posture acc magnitude gate min (g). 이 값 미만이면 classifier UNKNOWN. */ + @Volatile var postureAccMagMin: Float = 0.8f + /** Posture acc magnitude gate max (g). 이 값 초과면 classifier UNKNOWN. */ + @Volatile var postureAccMagMax: Float = 1.2f + /** Posture polling 간격 (ms). PiezoMonitoring 에서 mtb 보내는 주기. dev 모드에서 조정 가능. */ @Volatile var posturePollIntervalMs: Long = 1000L diff --git a/app/src/main/java/com/example/medilightv2android/managers/ImuPostureClassifier.kt b/app/src/main/java/com/example/medilightv2android/managers/ImuPostureClassifier.kt index e35b9f4..cbee3a4 100644 --- a/app/src/main/java/com/example/medilightv2android/managers/ImuPostureClassifier.kt +++ b/app/src/main/java/com/example/medilightv2android/managers/ImuPostureClassifier.kt @@ -24,12 +24,10 @@ enum class Posture { UNKNOWN, LYING, NON_LYING, WALKING } object ImuPostureClassifier { - // 게이트 임계값 - private const val ACC_MAG_MIN = 0.8f - private const val ACC_MAG_MAX = 1.2f + // Acc gate 임계값은 GreenZoneConstants.postureAccMagMin/Max 사용 (dev 조절 가능). // Gyro gate 비활성화 — 일부 디바이스의 gyro calibration bias (-25 dps 등) 가 // flowchart 임계(9.16) 보다 커서 정지 상태에서도 fail → 분류 영구 불가. - // accel magnitude gate (0.8-1.2g) 가 이미 큰 모션 거름. gyro 게이트 제거. + // accel magnitude gate 가 이미 큰 모션 거름. gyro 게이트 제거. // 단 dev 모드 디버그 위해 magnitude 는 계속 계산해 로그/Result 에 노출. /** 자세 + 진단 정보 (UI/log용). */ @@ -62,7 +60,7 @@ object ImuPostureClassifier { // Pitch 는 acc gate 통과 여부와 무관하게 항상 계산 (walking detector 가 사용). val pitchPre = (atan(ax / sqrt(ay * ay + az * az)) * 180.0 / Math.PI).toFloat() - if (accMag < ACC_MAG_MIN || accMag > ACC_MAG_MAX) { + if (accMag < GreenZoneConstants.postureAccMagMin || accMag > GreenZoneConstants.postureAccMagMax) { return Result(Posture.UNKNOWN, pitchPre, accMag, gyroMag, "acc mag out of range (%.2f)".format(accMag)) } diff --git a/app/src/main/java/com/example/medilightv2android/ui/views/monitoring/PiezoMonitoringView.kt b/app/src/main/java/com/example/medilightv2android/ui/views/monitoring/PiezoMonitoringView.kt index 7e25a64..6c8a5cc 100644 --- a/app/src/main/java/com/example/medilightv2android/ui/views/monitoring/PiezoMonitoringView.kt +++ b/app/src/main/java/com/example/medilightv2android/ui/views/monitoring/PiezoMonitoringView.kt @@ -1998,7 +1998,63 @@ fun PiezoMonitoringView(appState: AppState) { } Slider(value = walkKeepWmean, onValueChange = { walkKeepWmean = it; GreenZoneConstants.walkingKeepWmean = it + }, valueRange = 0.5f..30f, colors = SliderDefaults.colors(thumbColor = MlPrimary, activeTrackColor = MlPrimary)) + + // Walking 시간 변수 (sec) + var walkStartHold by remember { mutableStateOf(GreenZoneConstants.walkingStartHoldMs / 1000f) } + Row(verticalAlignment = Alignment.CenterVertically) { + Text(" Start Hold", fontSize = 12.sp) + Spacer(Modifier.weight(1f)) + Text("%.1f초".format(walkStartHold), fontSize = 12.sp, fontWeight = FontWeight.Bold, color = MlPrimary) + } + Slider(value = walkStartHold, onValueChange = { + walkStartHold = it; GreenZoneConstants.walkingStartHoldMs = (it * 1000).toLong() + }, valueRange = 0.2f..5f, colors = SliderDefaults.colors(thumbColor = MlPrimary, activeTrackColor = MlPrimary)) + + var walkKeepHold by remember { mutableStateOf(GreenZoneConstants.walkingKeepHoldMs / 1000f) } + Row(verticalAlignment = Alignment.CenterVertically) { + Text(" Keep Grace", fontSize = 12.sp) + Spacer(Modifier.weight(1f)) + Text("%.1f초".format(walkKeepHold), fontSize = 12.sp, fontWeight = FontWeight.Bold, color = MlPrimary) + } + Slider(value = walkKeepHold, onValueChange = { + walkKeepHold = it; GreenZoneConstants.walkingKeepHoldMs = (it * 1000).toLong() }, valueRange = 0.5f..10f, colors = SliderDefaults.colors(thumbColor = MlPrimary, activeTrackColor = MlPrimary)) + + var walkWindow by remember { mutableStateOf(GreenZoneConstants.walkingWindowMs / 1000f) } + Row(verticalAlignment = Alignment.CenterVertically) { + Text(" Window", fontSize = 12.sp) + Spacer(Modifier.weight(1f)) + Text("%.1f초".format(walkWindow), fontSize = 12.sp, fontWeight = FontWeight.Bold, color = MlPrimary) + } + Slider(value = walkWindow, onValueChange = { + walkWindow = it; GreenZoneConstants.walkingWindowMs = (it * 1000).toLong() + }, valueRange = 0.5f..5f, colors = SliderDefaults.colors(thumbColor = MlPrimary, activeTrackColor = MlPrimary)) + + HorizontalDivider(modifier = Modifier.padding(vertical = dividerPadding)) + + // Acc magnitude gate (classifier UNKNOWN 결정) + Text("Acc Magnitude Gate", fontSize = labelSize, fontWeight = FontWeight.SemiBold, color = MlPrimary) + + var accMagMin by remember { mutableStateOf(GreenZoneConstants.postureAccMagMin) } + Row(verticalAlignment = Alignment.CenterVertically) { + Text(" Min", fontSize = 12.sp) + Spacer(Modifier.weight(1f)) + Text("%.2fg".format(accMagMin), fontSize = 12.sp, fontWeight = FontWeight.Bold, color = MlPrimary) + } + Slider(value = accMagMin, onValueChange = { + accMagMin = it; GreenZoneConstants.postureAccMagMin = it + }, valueRange = 0.3f..1.0f, colors = SliderDefaults.colors(thumbColor = MlPrimary, activeTrackColor = MlPrimary)) + + var accMagMax by remember { mutableStateOf(GreenZoneConstants.postureAccMagMax) } + Row(verticalAlignment = Alignment.CenterVertically) { + Text(" Max", fontSize = 12.sp) + Spacer(Modifier.weight(1f)) + Text("%.2fg".format(accMagMax), fontSize = 12.sp, fontWeight = FontWeight.Bold, color = MlPrimary) + } + Slider(value = accMagMax, onValueChange = { + accMagMax = it; GreenZoneConstants.postureAccMagMax = it + }, valueRange = 1.0f..3.0f, colors = SliderDefaults.colors(thumbColor = MlPrimary, activeTrackColor = MlPrimary)) } HorizontalDivider(modifier = Modifier.padding(vertical = dividerPadding))