feat(clinical): 프로브 파라미터 직접 설정 (mcs 수동 주입)
주파수(1.8/2.3) · cycle(3/5/7) 을 골라 "기기에 주입"으로 프로브에 써 넣는다. 병원 임상 모드 화면 아래, mcs 영구저장 경고가 있는 자리에 둔다. ## 왜 필요한가 프로토콜은 6조합을 자동으로 돌지만 **그 밖의 측정은 프로브에 마지막으로 남은 설정으로 돈다.** 프로토콜을 돌리면 2.3MHz·cycle 7 이, 정렬을 하면 2.3MHz·cycle 3 이 남는다. 원하는 조건으로 되돌리거나 특정 조건만 시험하려면 손으로 넣을 길이 있어야 한다는 현장 요구. ## echo 를 보고 나서 성공이라고 말한다 설정이 거부돼도 프로브는 옛 설정으로 측정을 계속한다. 그래서 `rcs:` 응답 값이 요청과 같은지까지 확인하고, 결과를 네 갈래로 구분해 보여 준다: 적용됨 / 응답 없음 / 거부됨(펌웨어 사유) / 값 불일치(프로브에 남은 실제 값 표시) 프로토콜 루프가 조합마다 하는 검사와 같은 기준이다. avg 10 · delay 10µs · samples 100 은 고정이며 화면에 명시한다. 측정 중에는 비활성 — 루프가 조합을 바꿔 가며 쓰는 중에 끼어들면 라벨과 데이터가 어긋난다. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -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<String?>(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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user