diff --git a/app/src/main/java/com/medithings/vesiscan/ui/views/clinical/HospitalModeView.kt b/app/src/main/java/com/medithings/vesiscan/ui/views/clinical/HospitalModeView.kt index 46e4ac4..098026d 100644 --- a/app/src/main/java/com/medithings/vesiscan/ui/views/clinical/HospitalModeView.kt +++ b/app/src/main/java/com/medithings/vesiscan/ui/views/clinical/HospitalModeView.kt @@ -47,6 +47,7 @@ import com.medithings.vesiscan.ui.theme.* import androidx.compose.foundation.border import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay +import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import kotlinx.coroutines.withTimeoutOrNull import org.json.JSONArray @@ -376,6 +377,9 @@ fun HospitalModeView(appState: AppState) { } } + Spacer(Modifier.height(20.dp)) + ProbeParamCard(bleManager = bleManager, enabled = !running) + Spacer(Modifier.height(16.dp)) Text( "저장 위치: Downloads/VesiScan_Hospital/", @@ -551,3 +555,100 @@ private fun AnchorCard(anchorCm: Int?, enabled: Boolean, onAlign: () -> Unit) { ) } } + +/** + * 프로브 파라미터 수동 주입 — 주파수·cycle 을 골라 `mcs` 로 직접 써 넣는다. + * + * ## 왜 필요한가 + * 프로토콜은 6조합을 자동으로 돌지만, 그 밖의 측정(일반 측정·정렬 외 실험)은 프로브에 + * **마지막으로 남은 설정**으로 돈다. 프로토콜을 돌리고 나면 2.3MHz·cycle 7 이 남고, + * 정렬을 하면 2.3MHz·cycle 3 이 남는다. 원하는 조건으로 되돌리거나 특정 조건만 + * 시험하려면 손으로 넣을 길이 있어야 한다. + * + * ## echo 를 확인하고 나서 성공이라고 말한다 + * 설정이 거부돼도 프로브는 옛 설정으로 측정을 계속한다. 응답(`rcs:`)의 값이 요청과 + * 같은지까지 봐야 "들어갔다"고 할 수 있다 — 프로토콜 루프가 조합마다 하는 검사와 같다. + */ +@Composable +private fun ProbeParamCard(bleManager: BleManager, enabled: Boolean) { + val scope = rememberCoroutineScope() + var freq by remember { mutableStateOf(ProbeFrequency.F_2_3) } + var cycle by remember { mutableStateOf(ProbeCycle.C3) } + var busy by remember { mutableStateOf(false) } + var result by remember { mutableStateOf(null) } + var ok by remember { mutableStateOf(false) } + val connected by bleManager.isConnected + + Column( + Modifier.fillMaxWidth() + .background(Color.White, RoundedCornerShape(12.dp)) + .padding(12.dp) + ) { + Text("프로브 파라미터 직접 설정", fontSize = 15.sp, fontWeight = FontWeight.Bold) + Text( + "고른 값을 프로브에 써 넣습니다. avg ${HospitalFixedParams.AVG} · " + + "delay ${HospitalFixedParams.DELAY_US}µs · samples ${HospitalFixedParams.SAMPLES} 는 고정입니다.", + fontSize = 11.sp, color = MlSecondaryText, + modifier = Modifier.padding(top = 2.dp, bottom = 8.dp), + ) + + Label("주파수") + ChipRow(ProbeFrequency.entries, freq, { freq = it }, enabled && !busy) { + "${it.label}MHz" + } + Spacer(Modifier.height(10.dp)) + Label("cycle") + ChipRow(ProbeCycle.entries, cycle, { cycle = it }, enabled && !busy) { + "${it.cycles}" + } + Spacer(Modifier.height(12.dp)) + + Button( + onClick = { + busy = true; result = null + scope.launch { + bleManager.piezoConfigEcho.value = null + bleManager.sendPiezoConfig( + freq = freq.freqOption, + cycles = cycle.cycles, + avg = HospitalFixedParams.AVG, + delayUs = HospitalFixedParams.DELAY_US, + samples = HospitalFixedParams.SAMPLES, + ) + val echo = withTimeoutOrNull(CONFIG_TIMEOUT_MS) { + while (bleManager.piezoConfigEcho.value == null) delay(20) + bleManager.piezoConfigEcho.value + } + ok = echo?.matches( + freq.freqOption, cycle.cycles, HospitalFixedParams.AVG, + HospitalFixedParams.DELAY_US, HospitalFixedParams.SAMPLES, + ) == true + result = when { + ok -> "적용됨 — ${freq.label}MHz · cycle ${cycle.cycles} (프로브에 저장)" + echo == null -> "응답 없음 — 연결과 프로브 상태를 확인하세요." + echo.error != null -> "거부됨 — ${echo.error}" + else -> "값 불일치 — 프로브는 ${echo.freq}/${echo.cycles} 로 남았습니다." + } + busy = false + } + }, + enabled = enabled && !busy && connected, + modifier = Modifier.fillMaxWidth().height(46.dp), + shape = RoundedCornerShape(12.dp), + ) { + Text(if (busy) "주입 중…" else "기기에 주입", fontWeight = FontWeight.Bold) + } + + result?.let { + Text( + it, fontSize = 12.sp, + color = if (ok) MlSuccess else MlCritical, + modifier = Modifier.padding(top = 8.dp), + ) + } + if (!connected) { + Text("기기를 먼저 연결하세요.", fontSize = 11.sp, color = MlSecondaryText, + modifier = Modifier.padding(top = 6.dp)) + } + } +}