package com.example.medilightv2android.managers import kotlin.math.abs import kotlin.math.sqrt /** * 센서 미부착 감지 — detachment_detection.py 1:1 포팅 * * ADC 신호의 [20:80] 구간에서 baseline([80:100] median)을 빼고, * mean(|y|)과 std(y)가 모두 threshold 미만이면 미부착으로 판정. * * 미부착 시 LED state 4 (DETACH_WARNING) 전송. */ object DetachmentDetection { const val WINDOW_START = 20 const val WINDOW_END = 80 const val BASELINE_WINDOW_START = 80 const val BASELINE_WINDOW_END = 100 const val THR_MEAN = 30.0 const val THR_STD = 30.0 const val N_CENTER_CH = 4 data class DetachmentResult( val isDetached: Boolean, val meanAbs: Double, val stdVal: Double, val meanAbsPerCh: DoubleArray, val stdPerCh: DoubleArray ) /** * 4채널(CH0~CH3) ADC 신호에서 미부착 여부 판정. * * @param signals 6채널 ADC 데이터 (List>) * @return DetachmentResult */ fun detect(signals: List>): DetachmentResult { if (signals.size < N_CENTER_CH) { return DetachmentResult(true, 0.0, 0.0, DoubleArray(N_CENTER_CH), DoubleArray(N_CENTER_CH)) } val nSample = signals[0].size val ws = WINDOW_START.coerceAtMost(nSample) val we = WINDOW_END.coerceAtMost(nSample) val bs = BASELINE_WINDOW_START.coerceAtMost(nSample) val be = BASELINE_WINDOW_END.coerceAtMost(nSample) if (we <= ws || be <= bs) { return DetachmentResult(true, 0.0, 0.0, DoubleArray(N_CENTER_CH), DoubleArray(N_CENTER_CH)) } val meanAbsPerCh = DoubleArray(N_CENTER_CH) val stdPerCh = DoubleArray(N_CENTER_CH) for (ch in 0 until N_CENTER_CH) { val raw = signals[ch] if (raw.size < be) { meanAbsPerCh[ch] = 0.0 stdPerCh[ch] = 0.0 continue } // Baseline = median of [bs:be] val baselineSlice = (bs until be).map { raw[it].toDouble() }.sorted() val baseline = baselineSlice[baselineSlice.size / 2] // y = signal[ws:we] - baseline val y = DoubleArray(we - ws) { raw[ws + it].toDouble() - baseline } // mean(|y|) meanAbsPerCh[ch] = y.map { abs(it) }.average() // std(y) val mean = y.average() stdPerCh[ch] = sqrt(y.map { (it - mean) * (it - mean) }.average()) } val meanAbs = meanAbsPerCh.average() val stdVal = stdPerCh.average() // "and" rule: 둘 다 threshold 미만이면 미부착 val isDetached = meanAbs < THR_MEAN && stdVal < THR_STD return DetachmentResult( isDetached = isDetached, meanAbs = meanAbs, stdVal = stdVal, meanAbsPerCh = meanAbsPerCh, stdPerCh = stdPerCh ) } }