refactor: 데모 브랜치 패키지 통일 com.example.medilightv2android → com.medithings.vesiscan

사용자앱 (feature/tab-navigation) 과 패키지 이름 일치. namespace + applicationId
둘 다 com.medithings.vesiscan 로 변경 (기존 데모 앱은 재설치 필요).

## 변경 범위
- Kotlin 148 파일: package + import 문 (714 occurrences)
- 디렉토리 이동: com/example/medilightv2android → com/medithings/vesiscan
  (main, test, androidTest 각각)
- app/build.gradle.kts: namespace, applicationId
- docs/FLAVOR_DEMO_STABLE.md: 참조 갱신
- V41DetectorCH4Test: BvDispatchResult.methodChosen → method (dto field name fix)

## 주의
- applicationId 가 바뀌므로 기존 데모 앱 (com.example.medilightv2android.demo) 은
  Android 관점에서 다른 앱으로 취급 — 재설치 시 PIN/설정 초기화됨.
- Fresh install 권장. 기존 앱 (com.example...) 은 별도로 uninstall 필요.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-07-02 14:30:43 +09:00
parent 55d55225ab
commit bcbc7b440c
159 changed files with 2865 additions and 717 deletions
@@ -1,14 +1,14 @@
package com.example.medilightv2android
package com.medithings.vesiscan
import android.content.Context
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import com.example.medilightv2android.ble.BleManager
import com.example.medilightv2android.managers.NirsManager
import com.example.medilightv2android.models.*
import com.example.medilightv2android.services.*
import com.medithings.vesiscan.ble.BleManager
import com.medithings.vesiscan.managers.NirsManager
import com.medithings.vesiscan.models.*
import com.medithings.vesiscan.services.*
enum class AppScreen {
ONBOARDING, CONSENT, REGISTRATION, HOME, SENSOR_SELECT, DEVICE_SCAN,
@@ -121,7 +121,7 @@ class AppState(private val context: Context) {
fun deviceConnected() {
isDeviceConnected = true
// 임상 모드에서 BLE 연결 시 ClinicalHome으로 복귀 (Personalization 우회)
if (com.example.medilightv2android.services.ClinicalSessionStore.inClinicalFlow) {
if (com.medithings.vesiscan.services.ClinicalSessionStore.inClinicalFlow) {
currentScreen = AppScreen.CLINICAL_HOME
return
}
@@ -1,4 +1,4 @@
package com.example.medilightv2android
package com.medithings.vesiscan
import android.os.Bundle
import androidx.activity.ComponentActivity
@@ -20,34 +20,34 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import app.rive.runtime.kotlin.core.Rive
import com.example.medilightv2android.services.KeychainService
import com.example.medilightv2android.services.NotificationService
import com.example.medilightv2android.R
import com.example.medilightv2android.ui.theme.Medilightv2androidTheme
import com.example.medilightv2android.ui.views.calibration.CalibrationView
import com.example.medilightv2android.ui.views.connection.DeviceScanView
import com.example.medilightv2android.ui.views.history.MeasurementHistoryView
import com.example.medilightv2android.ui.views.home.HomeView
import com.example.medilightv2android.ui.views.monitoring.PlacementGuideView
import com.example.medilightv2android.ui.views.monitoring.PiezoMonitoringView
import com.example.medilightv2android.ui.views.monitoring.VivamyoMonitoringView
import com.example.medilightv2android.ui.views.monitoring.UrineCameraScreen
import com.example.medilightv2android.ui.views.monitoring.VoidingDiaryView
import com.example.medilightv2android.ui.views.onboarding.OnboardingView
import com.example.medilightv2android.ui.views.personalization.PiezoPersonalizationView
import com.example.medilightv2android.ui.views.pin.PinMode
import com.example.medilightv2android.ui.views.pin.PinView
import com.example.medilightv2android.ui.views.registration.RegistrationView
import com.example.medilightv2android.ui.views.consent.ConsentView
import com.example.medilightv2android.ui.views.reminder.ReminderSettingsView
import com.example.medilightv2android.ui.views.sensorselect.SensorSelectView
import com.example.medilightv2android.ui.views.clinical.ClinicalHomeView
import com.example.medilightv2android.ui.views.clinical.ClinicalLiveView
import com.medithings.vesiscan.services.KeychainService
import com.medithings.vesiscan.services.NotificationService
import com.medithings.vesiscan.R
import com.medithings.vesiscan.ui.theme.Medilightv2androidTheme
import com.medithings.vesiscan.ui.views.calibration.CalibrationView
import com.medithings.vesiscan.ui.views.connection.DeviceScanView
import com.medithings.vesiscan.ui.views.history.MeasurementHistoryView
import com.medithings.vesiscan.ui.views.home.HomeView
import com.medithings.vesiscan.ui.views.monitoring.PlacementGuideView
import com.medithings.vesiscan.ui.views.monitoring.PiezoMonitoringView
import com.medithings.vesiscan.ui.views.monitoring.VivamyoMonitoringView
import com.medithings.vesiscan.ui.views.monitoring.UrineCameraScreen
import com.medithings.vesiscan.ui.views.monitoring.VoidingDiaryView
import com.medithings.vesiscan.ui.views.onboarding.OnboardingView
import com.medithings.vesiscan.ui.views.personalization.PiezoPersonalizationView
import com.medithings.vesiscan.ui.views.pin.PinMode
import com.medithings.vesiscan.ui.views.pin.PinView
import com.medithings.vesiscan.ui.views.registration.RegistrationView
import com.medithings.vesiscan.ui.views.consent.ConsentView
import com.medithings.vesiscan.ui.views.reminder.ReminderSettingsView
import com.medithings.vesiscan.ui.views.sensorselect.SensorSelectView
import com.medithings.vesiscan.ui.views.clinical.ClinicalHomeView
import com.medithings.vesiscan.ui.views.clinical.ClinicalLiveView
class MainActivity : ComponentActivity() {
/** 사용자가 선택한 앱 언어 적용 — Activity 생성 전에 Locale wrap. */
override fun attachBaseContext(newBase: android.content.Context) {
super.attachBaseContext(com.example.medilightv2android.services.LocalePref.wrap(newBase))
super.attachBaseContext(com.medithings.vesiscan.services.LocalePref.wrap(newBase))
}
override fun onCreate(savedInstanceState: Bundle?) {
@@ -106,28 +106,28 @@ fun MedilightApp(modifier: Modifier = Modifier) {
// DeviceScan Back: 임상 흐름이면 ClinicalHome으로, 아니면 HOME
AppScreen.DEVICE_SCAN ->
appState.currentScreen =
if (com.example.medilightv2android.services.ClinicalSessionStore.inClinicalFlow)
if (com.medithings.vesiscan.services.ClinicalSessionStore.inClinicalFlow)
AppScreen.CLINICAL_HOME
else AppScreen.HOME
AppScreen.PIEZO_PERSONALIZATION -> appState.currentScreen = AppScreen.DEVICE_SCAN
// 임상 모드 측정 중이면 endMeasurement → ClinicalHome 복귀
AppScreen.PLACEMENT_GUIDE ->
if (com.example.medilightv2android.services.ClinicalSessionStore.currentSession != null) {
com.example.medilightv2android.services.ClinicalSessionStore.endMeasurement(context)
if (com.medithings.vesiscan.services.ClinicalSessionStore.currentSession != null) {
com.medithings.vesiscan.services.ClinicalSessionStore.endMeasurement(context)
appState.currentScreen = AppScreen.CLINICAL_HOME
} else appState.backFromPlacementGuide()
AppScreen.PIEZO_MONITORING ->
if (com.example.medilightv2android.services.ClinicalSessionStore.currentSession != null) {
com.example.medilightv2android.services.ClinicalSessionStore.endMeasurement(context)
if (com.medithings.vesiscan.services.ClinicalSessionStore.currentSession != null) {
com.medithings.vesiscan.services.ClinicalSessionStore.endMeasurement(context)
appState.currentScreen = AppScreen.CLINICAL_HOME
} else appState.currentScreen = AppScreen.PLACEMENT_GUIDE
AppScreen.CLINICAL_HOME -> {
com.example.medilightv2android.services.ClinicalSessionStore.inClinicalFlow = false
com.medithings.vesiscan.services.ClinicalSessionStore.inClinicalFlow = false
appState.currentScreen = AppScreen.HOME
}
AppScreen.CLINICAL_LIVE -> {
// 측정 폴더 finalize 후 ClinicalHome 복귀
com.example.medilightv2android.services.ClinicalSessionStore.endMeasurement(context)
com.medithings.vesiscan.services.ClinicalSessionStore.endMeasurement(context)
appState.currentScreen = AppScreen.CLINICAL_HOME
}
// 모니터링 하위 화면 → 모니터링
@@ -1,4 +1,4 @@
package com.example.medilightv2android.ble
package com.medithings.vesiscan.ble
import android.annotation.SuppressLint
import android.bluetooth.*
@@ -8,7 +8,7 @@ import android.os.Build
import android.os.Handler
import android.os.Looper
import android.util.Log
import com.example.medilightv2android.BuildConfig
import com.medithings.vesiscan.BuildConfig
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import java.util.UUID
@@ -57,7 +57,7 @@ class BleManager private constructor(private val context: Context) {
@Volatile private var txCharacteristic: BluetoothGattCharacteristic? = null
@Volatile private var rxCharacteristic: BluetoothGattCharacteristic? = null
private val handler = Handler(Looper.getMainLooper())
val debugLogger = com.example.medilightv2android.services.BleDebugLogger.getInstance(context)
val debugLogger = com.medithings.vesiscan.services.BleDebugLogger.getInstance(context)
private var scanTimer: Runnable? = null
private var batteryTimer: Runnable? = null
private var batteryRetryTimer: Runnable? = null
@@ -299,7 +299,7 @@ class BleManager private constructor(private val context: Context) {
logw { "removeBond failed: ${e.message}" }
}
}
com.example.medilightv2android.services.KnownDeviceStore.remove(context, address)
com.medithings.vesiscan.services.KnownDeviceStore.remove(context, address)
debugLogger.info("DEVICE_UNREGISTERED $address")
}
@@ -326,7 +326,7 @@ class BleManager private constructor(private val context: Context) {
/** 저장된 마지막 기기에 자동 연결 시도 */
fun autoConnectLastDevice() {
if (isConnected.value) return
val recent = com.example.medilightv2android.services.KnownDeviceStore.mostRecent(context) ?: return
val recent = com.medithings.vesiscan.services.KnownDeviceStore.mostRecent(context) ?: return
debugLogger.info("AUTO_CONNECT attempting ${recent.name} (${recent.address})")
lastConnectedAddress = recent.address
scheduleAutoReconnect()
@@ -340,7 +340,7 @@ class BleManager private constructor(private val context: Context) {
connectionTimer?.let { handler.removeCallbacks(it) }
connectionTimer = null
bluetoothGatt?.disconnect()
com.example.medilightv2android.services.BleForegroundService.stop(context)
com.medithings.vesiscan.services.BleForegroundService.stop(context)
}
fun disconnectAndUnbond() {
@@ -348,7 +348,7 @@ class BleManager private constructor(private val context: Context) {
cancelAutoReconnect()
stopWatchdog()
stopBatteryPolling()
com.example.medilightv2android.services.BleForegroundService.stop(context)
com.medithings.vesiscan.services.BleForegroundService.stop(context)
connectionTimer?.let { handler.removeCallbacks(it) }
connectionTimer = null
@@ -386,7 +386,7 @@ class BleManager private constructor(private val context: Context) {
// 저장된 기기 정보 삭제 — known device list 에서도 제거
try {
device?.let { dev ->
com.example.medilightv2android.services.KnownDeviceStore.remove(context, dev.address)
com.medithings.vesiscan.services.KnownDeviceStore.remove(context, dev.address)
}
} catch (_: Exception) {}
@@ -900,9 +900,9 @@ class BleManager private constructor(private val context: Context) {
isConnected.value = true
connectedDeviceName.value = try { gatt.device.name ?: "Unknown" } catch (_: Exception) { "Unknown" }
// Auto-detect device preset + endian from name
com.example.medilightv2android.managers.PiezoHW.autoDetectPreset(connectedDeviceName.value)
com.medithings.vesiscan.managers.PiezoHW.autoDetectPreset(connectedDeviceName.value)
piezoCollector.forceBigEndian = connectedDeviceName.value.startsWith("VBT")
logd { "Device preset: ${com.example.medilightv2android.managers.PiezoHW.presetName}, endian: ${if (piezoCollector.forceBigEndian) "BE" else "LE"} (from '${connectedDeviceName.value}')" }
logd { "Device preset: ${com.medithings.vesiscan.managers.PiezoHW.presetName}, endian: ${if (piezoCollector.forceBigEndian) "BE" else "LE"} (from '${connectedDeviceName.value}')" }
lastConnectedAddress = gatt.device.address
reconnectAttempt = 0
@@ -911,9 +911,9 @@ class BleManager private constructor(private val context: Context) {
// Clinical 흐름에서만 KnownDeviceStore 영속화. 일반 사용자는
// 매 세션 fresh scan + pair — 저장 0개.
if (com.example.medilightv2android.services.ClinicalSessionStore.inClinicalFlow) {
if (com.medithings.vesiscan.services.ClinicalSessionStore.inClinicalFlow) {
try {
com.example.medilightv2android.services.KnownDeviceStore.add(
com.medithings.vesiscan.services.KnownDeviceStore.add(
context, gatt.device.address, connectedDeviceName.value
)
} catch (_: Exception) {}
@@ -1030,8 +1030,8 @@ class BleManager private constructor(private val context: Context) {
} catch (_: Exception) { false }
logd { "requestConnectionPriority(HIGH) → $priorityOk" }
debugLogger.info("CONN_PRIORITY HIGH requested (ok=$priorityOk)")
com.example.medilightv2android.services.AdcCsvLogger.newSession()
com.example.medilightv2android.services.BleForegroundService.start(context)
com.medithings.vesiscan.services.AdcCsvLogger.newSession()
com.medithings.vesiscan.services.BleForegroundService.start(context)
startBatteryPolling()
startWatchdog()
sendDeviceInfoQuery()
@@ -1105,7 +1105,7 @@ class BleManager private constructor(private val context: Context) {
private fun processReceivedData(data: ByteArray) {
lastRxTimestamp = System.currentTimeMillis()
// Raw BLE capture (디버그 — 사용자가 토글로 enable 했을 때만 writes)
com.example.medilightv2android.services.RawBleLogger.write(data)
com.medithings.vesiscan.services.RawBleLogger.write(data)
val prefix = String(data, 0, minOf(4, data.size), Charsets.US_ASCII)
logd { "Received: $prefix (${data.size} bytes)" }
when (prefix) {
@@ -1,4 +1,4 @@
package com.example.medilightv2android.ble
package com.medithings.vesiscan.ble
object CRC16 {
private const val POLYNOMIAL: Int = 0x1021
@@ -1,4 +1,4 @@
package com.example.medilightv2android.ble
package com.medithings.vesiscan.ble
/**
* IMU 6축 raw 샘플 (Accel xyz + Gyro xyz).
@@ -1,4 +1,4 @@
package com.example.medilightv2android.ble
package com.medithings.vesiscan.ble
/**
* Collects multi-packet Piezo echo responses: reb: → (red:) → raa:
@@ -16,12 +16,12 @@
*
* 기존 PlacementGuide computePlacementGuide(...) 와 병행 — V1/V2 토글로 분기.
*/
package com.example.medilightv2android.managers
package com.medithings.vesiscan.managers
import com.example.medilightv2android.R
import com.example.medilightv2android.walldetect.MethodDResult
import com.example.medilightv2android.walldetect.MethodDRunner
import com.example.medilightv2android.walldetect.algo.methodd.MethodDParams
import com.medithings.vesiscan.R
import com.medithings.vesiscan.walldetect.MethodDResult
import com.medithings.vesiscan.walldetect.MethodDRunner
import com.medithings.vesiscan.walldetect.algo.methodd.MethodDParams
import kotlin.math.abs
import kotlin.math.max
@@ -1,4 +1,4 @@
package com.example.medilightv2android.managers
package com.medithings.vesiscan.managers
import kotlin.math.abs
import kotlin.math.max
@@ -1,4 +1,4 @@
package com.example.medilightv2android.managers
package com.medithings.vesiscan.managers
import kotlin.math.abs
@@ -1,4 +1,4 @@
package com.example.medilightv2android.managers
package com.medithings.vesiscan.managers
import kotlin.math.cos
import kotlin.math.sin
@@ -1,4 +1,4 @@
package com.example.medilightv2android.managers
package com.medithings.vesiscan.managers
import kotlin.math.abs
import kotlin.math.sqrt
@@ -1,4 +1,4 @@
package com.example.medilightv2android.managers
package com.medithings.vesiscan.managers
/**
* Green Zone 전체 파이프라인의 공유 상수 — Single Source of Truth.
@@ -1,6 +1,6 @@
package com.example.medilightv2android.managers
package com.medithings.vesiscan.managers
import com.example.medilightv2android.ble.ImuSample
import com.medithings.vesiscan.ble.ImuSample
import kotlin.math.abs
import kotlin.math.atan2
import kotlin.math.sqrt
@@ -13,9 +13,9 @@
* ≤ threshold → NON_LYING (앉음/섬)
* threshold default = 25°, dev 모드 설정에서 변경 가능.
*/
package com.example.medilightv2android.managers
package com.medithings.vesiscan.managers
import com.example.medilightv2android.ble.ImuSample
import com.medithings.vesiscan.ble.ImuSample
import kotlin.math.abs
import kotlin.math.atan
import kotlin.math.sqrt
@@ -1,8 +1,8 @@
package com.example.medilightv2android.managers
package com.medithings.vesiscan.managers
import com.example.medilightv2android.models.NirsConstants
import com.example.medilightv2android.models.NirsMagResponse
import com.example.medilightv2android.models.NirsOxyResult
import com.medithings.vesiscan.models.NirsConstants
import com.medithings.vesiscan.models.NirsMagResponse
import com.medithings.vesiscan.models.NirsOxyResult
import kotlin.math.abs
import kotlin.math.ln
import kotlin.math.max
@@ -1,13 +1,13 @@
package com.example.medilightv2android.managers
package com.medithings.vesiscan.managers
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import com.example.medilightv2android.models.NirsMagResponse
import com.example.medilightv2android.models.NirsMcjPacket
import com.example.medilightv2android.models.NirsConstants
import com.example.medilightv2android.BuildConfig
import com.example.medilightv2android.models.NirsOxyResult
import com.medithings.vesiscan.models.NirsMagResponse
import com.medithings.vesiscan.models.NirsMcjPacket
import com.medithings.vesiscan.models.NirsConstants
import com.medithings.vesiscan.BuildConfig
import com.medithings.vesiscan.models.NirsOxyResult
class NirsManager private constructor() {
companion object {
@@ -1,6 +1,6 @@
package com.example.medilightv2android.managers
package com.medithings.vesiscan.managers
import com.example.medilightv2android.walldetect.core.WdConfig
import com.medithings.vesiscan.walldetect.core.WdConfig
import kotlin.math.abs
import kotlin.math.cos
import kotlin.math.hypot
@@ -484,7 +484,7 @@ fun estimateBladderVolume6ch(
val lateralWalls = PiezoHW.lateralCh.map { if (it < allWalls.size) allWalls[it] else null }
// 2026-07-01: algorithm 팀 최신 표준 (piezophantomtest 4830e7c) — lr_ratio_override=1.0
// Phantom 검증: computed lr 은 6채널 HW 한계로 -38% 오차. Dev panel 로 전환 가능.
val lrRatio = com.example.medilightv2android.managers.GreenZoneConstants.lrRatioOverride
val lrRatio = com.medithings.vesiscan.managers.GreenZoneConstants.lrRatioOverride
?: computeLrRatio(centerWalls, lateralWalls, sensorZMm = PiezoHW.sensorZMmAll)
return estimateBladderVolume(
walls = centerWalls,
@@ -1,6 +1,6 @@
package com.example.medilightv2android.managers
package com.medithings.vesiscan.managers
import com.example.medilightv2android.ble.PiezoChannelData
import com.medithings.vesiscan.ble.PiezoChannelData
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
@@ -1,4 +1,4 @@
package com.example.medilightv2android.managers
package com.medithings.vesiscan.managers
import kotlin.math.abs
import kotlin.math.max
@@ -1,4 +1,4 @@
package com.example.medilightv2android.managers
package com.medithings.vesiscan.managers
/**
* UrinAI (UrinGateKeeper) — 소변(액체) 존재 판정 엔진.
@@ -15,7 +15,7 @@
* // 매 posture 갱신 시:
* detector.update(currentPosture, System.currentTimeMillis())
*/
package com.example.medilightv2android.managers
package com.medithings.vesiscan.managers
class WakeupDetector(
/** 알람 발동을 위해 직전 LYING 상태로 유지되어야 하는 최소 시간 (초). */
@@ -18,9 +18,9 @@
* 입력: 매 mtb cycle (≈1초 주기) 마다 IMU sample 리스트 + 현재 pitch + 시각
* 출력: 현재 walking 상태 (Boolean) + 진단 정보
*/
package com.example.medilightv2android.managers
package com.medithings.vesiscan.managers
import com.example.medilightv2android.ble.ImuSample
import com.medithings.vesiscan.ble.ImuSample
import kotlin.math.abs
import kotlin.math.sqrt
@@ -1,4 +1,4 @@
package com.example.medilightv2android.measure
package com.medithings.vesiscan.measure
data class CCPosition(
val cc: Int,
@@ -1,4 +1,4 @@
package com.example.medilightv2android.measure
package com.medithings.vesiscan.measure
import android.graphics.Bitmap
import android.graphics.Color
@@ -6,7 +6,7 @@ import android.util.Log
import com.google.mlkit.vision.common.InputImage
import com.google.mlkit.vision.text.TextRecognition
import com.google.mlkit.vision.text.latin.TextRecognizerOptions
import com.example.medilightv2android.measure.CCPosition
import com.medithings.vesiscan.measure.CCPosition
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext
@@ -1,4 +1,4 @@
package com.example.medilightv2android.measure
package com.medithings.vesiscan.measure
import android.content.Context
import android.content.SharedPreferences
@@ -1,4 +1,4 @@
package com.example.medilightv2android.measure
package com.medithings.vesiscan.measure
import android.content.Context
import android.graphics.Bitmap
@@ -1,4 +1,4 @@
package com.example.medilightv2android.models
package com.medithings.vesiscan.models
data class BladderLevel(
var currentLevel: Int = 0,
@@ -1,4 +1,4 @@
package com.example.medilightv2android.models
package com.medithings.vesiscan.models
import java.text.SimpleDateFormat
import java.util.Date
@@ -1,4 +1,4 @@
package com.example.medilightv2android.models
package com.medithings.vesiscan.models
import java.util.Date
import java.util.UUID
@@ -1,4 +1,4 @@
package com.example.medilightv2android.models
package com.medithings.vesiscan.models
import androidx.compose.ui.graphics.Color
@@ -1,4 +1,4 @@
package com.example.medilightv2android.models
package com.medithings.vesiscan.models
data class NirsMcjPacket(
val battery: Int,
@@ -16,7 +16,7 @@ data class NirsMcjPacket(
fun parse(data: ByteArray): NirsMcjPacket? {
if (data.size < 72) {
if (com.example.medilightv2android.BuildConfig.DEBUG) {
if (com.medithings.vesiscan.BuildConfig.DEBUG) {
android.util.Log.e(TAG, "parse: data too small: ${data.size} < 72")
}
return null
@@ -42,7 +42,7 @@ data class NirsMcjPacket(
for (i in 0 until 24) {
val offset = base + 20 + (i * 2)
if (offset + 1 >= data.size) {
if (com.example.medilightv2android.BuildConfig.DEBUG) {
if (com.medithings.vesiscan.BuildConfig.DEBUG) {
android.util.Log.e(TAG, "parse: LED[$i] out of bounds: offset=${offset+1} >= size=${data.size}")
}
return null
@@ -50,7 +50,7 @@ data class NirsMcjPacket(
ledValues[i] = u16(offset)
}
if (com.example.medilightv2android.BuildConfig.DEBUG) {
if (com.medithings.vesiscan.BuildConfig.DEBUG) {
android.util.Log.d(TAG, "parse OK: battery=$battery led[0..5]=${ledValues.take(6)}")
}
@@ -1,4 +1,4 @@
package com.example.medilightv2android.models
package com.medithings.vesiscan.models
enum class Posture(val displayName: String) {
STANDING("Standing"),
@@ -1,4 +1,4 @@
package com.example.medilightv2android.models
package com.medithings.vesiscan.models
enum class SensorMode(val displayName: String) {
NIRS("NIRS"),
@@ -1,4 +1,4 @@
package com.example.medilightv2android.models
package com.medithings.vesiscan.models
import androidx.compose.ui.graphics.Color
@@ -1,4 +1,4 @@
package com.example.medilightv2android.models
package com.medithings.vesiscan.models
enum class Sex { MALE, FEMALE }
@@ -1,4 +1,4 @@
package com.example.medilightv2android.services
package com.medithings.vesiscan.services
import android.os.Environment
import java.io.File
@@ -63,15 +63,15 @@ object AdcCsvLogger {
val timestamp = timeFmt.format(Date())
val volStr = volumeMl?.let { "%.1f".format(it) } ?: ""
val lrStr = "%.2f".format(lrRatio)
val analyzer = com.example.medilightv2android.managers.PiezoEchoAnalyzer.shared
val analyzer = com.medithings.vesiscan.managers.PiezoEchoAnalyzer.shared
val thrStr = if (analyzer.useAdaptiveThreshold && analyzer.lastOtsuThreshold > 0)
"%.0f".format(analyzer.lastOtsuThreshold) else "%.0f".format(com.example.medilightv2android.managers.GreenZoneConstants.lowEchoAmp)
val dpsStr = "%.3f".format(com.example.medilightv2android.managers.PiezoHW.distancePerSample)
"%.0f".format(analyzer.lastOtsuThreshold) else "%.0f".format(com.medithings.vesiscan.managers.GreenZoneConstants.lowEchoAmp)
val dpsStr = "%.3f".format(com.medithings.vesiscan.managers.PiezoHW.distancePerSample)
val sgStr = if (analyzer.useSgFilter) "ON" else "OFF"
val clinicalLabel = ClinicalSessionStore.currentLabel() // 일반 모드면 ""
// BLE 펌웨어 버전 (연결 안 됐으면 "")
val fwVersion = com.example.medilightv2android.ble.BleManager.current?.firmwareVersion?.value ?: ""
val fwVersion = com.medithings.vesiscan.ble.BleManager.current?.firmwareVersion?.value ?: ""
java.io.FileWriter(file, !isNew).buffered().use { writer ->
if (isNew) {
@@ -1,4 +1,4 @@
package com.example.medilightv2android.services
package com.medithings.vesiscan.services
import android.content.Context
import android.content.Intent
@@ -1,4 +1,4 @@
package com.example.medilightv2android.services
package com.medithings.vesiscan.services
import android.app.Notification
import android.app.NotificationChannel
@@ -11,8 +11,8 @@ import android.os.Build
import android.os.IBinder
import android.os.PowerManager
import androidx.core.app.NotificationCompat
import com.example.medilightv2android.MainActivity
import com.example.medilightv2android.R
import com.medithings.vesiscan.MainActivity
import com.medithings.vesiscan.R
/**
* BLE 연결 유지를 위한 Foreground Service.
@@ -25,8 +25,8 @@ class BleForegroundService : Service() {
companion object {
const val CHANNEL_ID = "vesiscan_ble_channel"
const val NOTIFICATION_ID = 1001
const val ACTION_START = "com.example.medilightv2android.BLE_START"
const val ACTION_STOP = "com.example.medilightv2android.BLE_STOP"
const val ACTION_START = "com.medithings.vesiscan.BLE_START"
const val ACTION_STOP = "com.medithings.vesiscan.BLE_STOP"
fun start(context: Context) {
// 배터리 최적화 제외 요청 제거 — 다른 페이지로 이동되는 UX 문제
@@ -1,10 +1,10 @@
package com.example.medilightv2android.services
package com.medithings.vesiscan.services
import android.content.Context
import android.os.Environment
import com.example.medilightv2android.ble.ImuSample
import com.example.medilightv2android.ble.PiezoChannelData
import com.example.medilightv2android.models.ClinicalSession
import com.medithings.vesiscan.ble.ImuSample
import com.medithings.vesiscan.ble.PiezoChannelData
import com.medithings.vesiscan.models.ClinicalSession
import org.json.JSONArray
import org.json.JSONObject
import java.io.File
@@ -59,16 +59,16 @@ object ClinicalSessionStore {
/** 다음 측정 시 기본값으로 자동 채울 사용자 입력 — 같은 피험자/검사자 여러 측정 시 편의 */
var lastExaminer: String = ""
var lastNotes: String = ""
var lastSubjectType: com.example.medilightv2android.models.SubjectType =
com.example.medilightv2android.models.SubjectType.PHANTOM
var lastSubjectType: com.medithings.vesiscan.models.SubjectType =
com.medithings.vesiscan.models.SubjectType.PHANTOM
var lastSubject: String = "" // 카테고리 내 식별자 (예: "500mL", "S001")
var lastTrueVolumeMl: String = "" // 문자열로 보관 (입력 폼과 동일 형식)
var lastAbdomenThicknessMm: String = ""
// 회의 결정: Step/Posture 마지막 선택 유지 — 매번 0cm로 리셋되는 불편 제거
var lastPosture: com.example.medilightv2android.models.ClinicalPosture =
com.example.medilightv2android.models.ClinicalPosture.SUPINE
var lastStep: com.example.medilightv2android.models.ClinicalStep =
com.example.medilightv2android.models.ClinicalStep.ALIGN_0CM
var lastPosture: com.medithings.vesiscan.models.ClinicalPosture =
com.medithings.vesiscan.models.ClinicalPosture.SUPINE
var lastStep: com.medithings.vesiscan.models.ClinicalStep =
com.medithings.vesiscan.models.ClinicalStep.ALIGN_0CM
/** End 시점에 measurement.json 통합 생성용 — 측정 중 사이클 누적 */
private val cyclesBuffer = mutableListOf<MeasurementCycle>()
@@ -186,7 +186,7 @@ object ClinicalSessionStore {
put("android_release", android.os.Build.VERSION.RELEASE)
put("android_sdk", android.os.Build.VERSION.SDK_INT)
// VBT 디바이스 정보 (mid?/rid: 응답 파싱값) — 분석 시 펌웨어별 데이터 grouping용
val ble = com.example.medilightv2android.ble.BleManager.getInstance(context)
val ble = com.medithings.vesiscan.ble.BleManager.getInstance(context)
val fw = ble.firmwareVersion.value
val hw = ble.hardwareVersion.value
val sn = ble.serialNumber.value
@@ -224,7 +224,7 @@ object ClinicalSessionStore {
// meta: writeMeta()와 동일 내용 포함 (단독 파일로 자족 가능)
// alignment 모드 → dataType=002, 그 외 = 001 (기존 measurement)
val effectiveDataType = if (s.mode == com.example.medilightv2android.models.SessionMode.ALIGNMENT)
val effectiveDataType = if (s.mode == com.medithings.vesiscan.models.SessionMode.ALIGNMENT)
"002" else DATA_TYPE
val meta = JSONObject().apply {
put("data_type", effectiveDataType)
@@ -254,7 +254,7 @@ object ClinicalSessionStore {
put("app_version_name", try {
context.packageManager.getPackageInfo(context.packageName, 0).versionName ?: "unknown"
} catch (_: Exception) { "unknown" })
val ble = com.example.medilightv2android.ble.BleManager.current
val ble = com.medithings.vesiscan.ble.BleManager.current
ble?.firmwareVersion?.value?.takeIf { it.isNotEmpty() }?.let { put("firmware_version", it) }
ble?.hardwareVersion?.value?.takeIf { it.isNotEmpty() }?.let { put("hardware_version", it) }
ble?.serialNumber?.value?.takeIf { it.isNotEmpty() }?.let { put("serial_number", it) }
@@ -1,6 +1,6 @@
package com.example.medilightv2android.services
package com.medithings.vesiscan.services
import com.example.medilightv2android.ble.ImuSample
import com.medithings.vesiscan.ble.ImuSample
import java.io.File
import java.text.SimpleDateFormat
import java.util.Date
@@ -38,7 +38,7 @@ object ImuCsvLogger {
val scanId = AdcCsvLogger.lastScanId // piezo adc.csv와 매칭용
val timestamp = timeFmt.format(Date())
val clinicalLabel = ClinicalSessionStore.currentLabel()
val fwVersion = com.example.medilightv2android.ble.BleManager.current?.firmwareVersion?.value ?: ""
val fwVersion = com.medithings.vesiscan.ble.BleManager.current?.firmwareVersion?.value ?: ""
file.bufferedWriter().use { _ -> } // ensure exists
java.io.FileWriter(file, !isNew).buffered().use { writer ->
@@ -1,4 +1,4 @@
package com.example.medilightv2android.services
package com.medithings.vesiscan.services
import android.content.Context
import android.content.SharedPreferences
@@ -1,4 +1,4 @@
package com.example.medilightv2android.services
package com.medithings.vesiscan.services
import android.content.Context
import org.json.JSONArray
@@ -13,7 +13,7 @@
* Android 13+ 의 per-app locale API (AppCompatDelegate.setApplicationLocales) 와
* 무관하게 Configuration override 방식으로 모든 SDK 에서 동작.
*/
package com.example.medilightv2android.services
package com.medithings.vesiscan.services
import android.content.Context
import android.content.res.Configuration
@@ -1,8 +1,8 @@
package com.example.medilightv2android.services
package com.medithings.vesiscan.services
import android.content.Context
import android.content.SharedPreferences
import com.example.medilightv2android.models.MeasurementRecord
import com.medithings.vesiscan.models.MeasurementRecord
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
@@ -1,4 +1,4 @@
package com.example.medilightv2android.services
package com.medithings.vesiscan.services
import android.content.Context
import android.os.Build
@@ -70,7 +70,7 @@ class MeasurementLogService private constructor(private val context: Context) {
val clinical = ClinicalSessionStore.currentLabel()
if (clinical.isNotEmpty()) put("clinical_label", clinical)
// BLE 펌웨어 버전 (연결 시 mid?/rid: 응답에서 받음)
val fw = com.example.medilightv2android.ble.BleManager.current?.firmwareVersion?.value
val fw = com.medithings.vesiscan.ble.BleManager.current?.firmwareVersion?.value
if (!fw.isNullOrEmpty()) put("firmware_version", fw)
}
@@ -136,7 +136,7 @@ class MeasurementLogService private constructor(private val context: Context) {
val clinical = ClinicalSessionStore.currentLabel()
if (clinical.isNotEmpty()) put("clinical_label", clinical)
// BLE 펌웨어 버전
val fw = com.example.medilightv2android.ble.BleManager.current?.firmwareVersion?.value
val fw = com.medithings.vesiscan.ble.BleManager.current?.firmwareVersion?.value
if (!fw.isNullOrEmpty()) put("firmware_version", fw)
}
appendLog(entry, "placements.jsonl")
@@ -1,9 +1,9 @@
package com.example.medilightv2android.services
package com.medithings.vesiscan.services
import android.content.Context
import androidx.compose.runtime.mutableStateOf
import com.example.medilightv2android.ble.BleManager
import com.example.medilightv2android.models.SensorMode
import com.medithings.vesiscan.ble.BleManager
import com.medithings.vesiscan.models.SensorMode
class MeasurementService private constructor(context: Context) {
companion object {
@@ -1,4 +1,4 @@
package com.example.medilightv2android.services
package com.medithings.vesiscan.services
import android.app.NotificationChannel
import android.app.NotificationManager
@@ -6,8 +6,8 @@ import android.content.Context
import android.os.Build
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import com.example.medilightv2android.R
import com.example.medilightv2android.models.UrgencyLevel
import com.medithings.vesiscan.R
import com.medithings.vesiscan.models.UrgencyLevel
class NotificationService private constructor(private val context: Context) {
companion object {
@@ -23,7 +23,7 @@
*
* 분석: CSV → Excel / python pandas → ANNOTATION 과 CYCLE 의 walking 전이 대조.
*/
package com.example.medilightv2android.services
package com.medithings.vesiscan.services
import android.content.Context
import android.os.Environment
@@ -9,10 +9,10 @@
* saveAll(context) — slider 변경 시 호출 → 현재 GreenZoneConstants → SharedPreferences
* resetAll(context) — 사용자가 reset 누름 → defaults 로 복원
*/
package com.example.medilightv2android.services
package com.medithings.vesiscan.services
import android.content.Context
import com.example.medilightv2android.managers.GreenZoneConstants
import com.medithings.vesiscan.managers.GreenZoneConstants
object PostureTuningPrefs {
@@ -10,7 +10,7 @@
* 사용: startCapture() / stopCapture() 토글. 활성화 시 BleManager.processReceivedData
* 가 매 notification 마다 write() 호출.
*/
package com.example.medilightv2android.services
package com.medithings.vesiscan.services
import android.os.Environment
import java.io.File
@@ -1,4 +1,4 @@
package com.example.medilightv2android.services
package com.medithings.vesiscan.services
import android.app.AlarmManager
import android.app.NotificationChannel
@@ -15,7 +15,7 @@ import android.os.Vibrator
import android.os.VibratorManager
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import com.example.medilightv2android.R
import com.medithings.vesiscan.R
import java.util.Calendar
class ReminderService private constructor(private val context: Context) {
@@ -1,6 +1,6 @@
package com.example.medilightv2android.services
package com.medithings.vesiscan.services
import com.example.medilightv2android.ble.PiezoChannelData
import com.medithings.vesiscan.ble.PiezoChannelData
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -1,8 +1,8 @@
package com.example.medilightv2android.services
package com.medithings.vesiscan.services
import android.content.Context
import android.content.SharedPreferences
import com.example.medilightv2android.models.*
import com.medithings.vesiscan.models.*
import com.google.gson.Gson
class UserStorage private constructor(context: Context) {
@@ -4,7 +4,7 @@
* 사용자가 "설정 탭" 에서 toggle on/off → 일어남 감지 시 빨간 팝업.
* LYING 유지 시간 임계 (default 3 min) 도 함께.
*/
package com.example.medilightv2android.services
package com.medithings.vesiscan.services
import android.content.Context
import android.content.SharedPreferences
@@ -1,9 +1,9 @@
package com.example.medilightv2android.services.labdb
package com.medithings.vesiscan.services.labdb
import android.content.Context
import android.os.Build
import android.util.Log
import com.example.medilightv2android.ble.BleManager
import com.medithings.vesiscan.ble.BleManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.MediaType.Companion.toMediaType
@@ -1,6 +1,6 @@
package com.example.medilightv2android.services.labdb
package com.medithings.vesiscan.services.labdb
import com.example.medilightv2android.services.KeychainService
import com.medithings.vesiscan.services.KeychainService
/**
* labdb (https://labdb.medithings.net) 자격 증명 저장소.
@@ -1,4 +1,4 @@
package com.example.medilightv2android.services.labdb
package com.medithings.vesiscan.services.labdb
import android.content.Context
import android.util.Log
@@ -4,7 +4,7 @@
* MEDiThings Inc.
* All rights reserved.
*/
package com.example.medilightv2android.speech
package com.medithings.vesiscan.speech
import android.util.Log
import kotlinx.coroutines.Dispatchers
@@ -4,7 +4,7 @@
* MEDiThings Inc.
* All rights reserved.
*/
package com.example.medilightv2android.speech
package com.medithings.vesiscan.speech
import android.content.Context
import android.content.Intent
@@ -4,7 +4,7 @@
* MEDiThings Inc.
* All rights reserved.
*/
package com.example.medilightv2android.speech
package com.medithings.vesiscan.speech
/**
* Local (offline) parser that extracts a volume in milliliters from speech-recognition text.
@@ -1,4 +1,4 @@
package com.example.medilightv2android.ui.components
package com.medithings.vesiscan.ui.components
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
@@ -22,8 +22,8 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.medilightv2android.R
import com.example.medilightv2android.ble.BleManager
import com.medithings.vesiscan.R
import com.medithings.vesiscan.ble.BleManager
/**
* 배터리 잔량 경고 배너 — PlacementGuide / PiezoMonitoring 상단.
@@ -1,4 +1,4 @@
package com.example.medilightv2android.ui.components
package com.medithings.vesiscan.ui.components
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
@@ -13,8 +13,8 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import app.rive.runtime.kotlin.RiveAnimationView
import app.rive.runtime.kotlin.core.Fit
import com.example.medilightv2android.R
import com.example.medilightv2android.models.UrgencyLevel
import com.medithings.vesiscan.R
import com.medithings.vesiscan.models.UrgencyLevel
import kotlinx.coroutines.delay
import kotlin.random.Random
@@ -1,4 +1,4 @@
package com.example.medilightv2android.ui.components
package com.medithings.vesiscan.ui.components
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
@@ -13,7 +13,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.medilightv2android.ui.theme.MlPrimary
import com.medithings.vesiscan.ui.theme.MlPrimary
enum class BleReconnectionState {
DISCONNECTED, RECONNECTING, FAILED
@@ -1,4 +1,4 @@
package com.example.medilightv2android.ui.components
package com.medithings.vesiscan.ui.components
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
@@ -14,7 +14,7 @@ import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.example.medilightv2android.models.UrgencyLevel
import com.medithings.vesiscan.models.UrgencyLevel
@Composable
fun DonutChartView(
@@ -1,4 +1,4 @@
package com.example.medilightv2android.ui.components
package com.medithings.vesiscan.ui.components
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
@@ -26,8 +26,8 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.medilightv2android.R
import com.example.medilightv2android.ble.BleManager
import com.medithings.vesiscan.R
import com.medithings.vesiscan.ble.BleManager
import kotlinx.coroutines.delay
/**
@@ -15,7 +15,7 @@
* UX 톤:
* "1회 터치 후 조정" 패턴 — 의도된 조작만 허용.
*/
package com.example.medilightv2android.ui.components
package com.medithings.vesiscan.ui.components
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
@@ -39,7 +39,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.example.medilightv2android.R
import com.medithings.vesiscan.R
@Composable
fun LockableSlider(
@@ -5,7 +5,7 @@
* measurement, newest at the bottom, monospace, long-press to select spans
* across entries. Copy puts the full log on the system clipboard.
*/
package com.example.medilightv2android.ui.components
package com.medithings.vesiscan.ui.components
import android.content.ClipData
import android.content.ClipboardManager
@@ -1,4 +1,4 @@
package com.example.medilightv2android.ui.theme
package com.medithings.vesiscan.ui.theme
import androidx.compose.ui.graphics.Color
@@ -1,4 +1,4 @@
package com.example.medilightv2android.ui.theme
package com.medithings.vesiscan.ui.theme
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.gestures.detectTapGestures
@@ -1,4 +1,4 @@
package com.example.medilightv2android.ui.theme
package com.medithings.vesiscan.ui.theme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.lightColorScheme
@@ -1,4 +1,4 @@
package com.example.medilightv2android.ui.theme
package com.medithings.vesiscan.ui.theme
import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle
@@ -1,4 +1,4 @@
package com.example.medilightv2android.ui.views.calibration
package com.medithings.vesiscan.ui.views.calibration
import android.os.Handler
import android.os.Looper
@@ -22,13 +22,13 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.medilightv2android.AppState
import com.example.medilightv2android.R
import com.example.medilightv2android.ble.BleManager
import com.example.medilightv2android.managers.NirsManager
import com.example.medilightv2android.models.NirsMagResponse
import com.example.medilightv2android.ui.components.BladdyIdleRiveView
import com.example.medilightv2android.ui.theme.*
import com.medithings.vesiscan.AppState
import com.medithings.vesiscan.R
import com.medithings.vesiscan.ble.BleManager
import com.medithings.vesiscan.managers.NirsManager
import com.medithings.vesiscan.models.NirsMagResponse
import com.medithings.vesiscan.ui.components.BladdyIdleRiveView
import com.medithings.vesiscan.ui.theme.*
@Composable
fun CalibrationView(appState: AppState) {
@@ -1,4 +1,4 @@
package com.example.medilightv2android.ui.views.clinical
package com.medithings.vesiscan.ui.views.clinical
import android.widget.Toast
import androidx.compose.foundation.background
@@ -23,15 +23,15 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.medilightv2android.AppScreen
import com.example.medilightv2android.AppState
import com.example.medilightv2android.ble.BleManager
import com.example.medilightv2android.models.*
import com.example.medilightv2android.services.ClinicalSessionStore
import com.example.medilightv2android.services.labdb.LabdbClient
import com.example.medilightv2android.services.labdb.LabdbCredentials
import com.example.medilightv2android.services.labdb.LabdbUploader
import com.example.medilightv2android.ui.theme.*
import com.medithings.vesiscan.AppScreen
import com.medithings.vesiscan.AppState
import com.medithings.vesiscan.ble.BleManager
import com.medithings.vesiscan.models.*
import com.medithings.vesiscan.services.ClinicalSessionStore
import com.medithings.vesiscan.services.labdb.LabdbClient
import com.medithings.vesiscan.services.labdb.LabdbCredentials
import com.medithings.vesiscan.services.labdb.LabdbUploader
import com.medithings.vesiscan.ui.theme.*
import kotlinx.coroutines.launch
/**
@@ -397,7 +397,7 @@ fun ClinicalHomeView(appState: AppState) {
// 측정 대상 분류 — Phantom/Human/ETC 버튼 (회의 결정)
LabeledSegmented(
label = "Subject Type",
options = com.example.medilightv2android.models.SubjectType.values().toList(),
options = com.medithings.vesiscan.models.SubjectType.values().toList(),
selected = subjectType,
onSelect = { subjectType = it }
) { it.label }
@@ -492,7 +492,7 @@ fun ClinicalHomeView(appState: AppState) {
subjectType = subjectType, subject = subject.trim(),
trueVolumeMl = trueVolText.toDoubleOrNull(),
abdomenThicknessMm = abdomenText.toDoubleOrNull(),
mode = com.example.medilightv2android.models.SessionMode.MEASUREMENT,
mode = com.medithings.vesiscan.models.SessionMode.MEASUREMENT,
)
)
appState.currentScreen = AppScreen.CLINICAL_LIVE
@@ -517,9 +517,9 @@ fun ClinicalHomeView(appState: AppState) {
subjectType = subjectType, subject = subject.trim(),
trueVolumeMl = trueVolText.toDoubleOrNull(),
abdomenThicknessMm = abdomenText.toDoubleOrNull(),
mode = com.example.medilightv2android.models.SessionMode.ALIGNMENT,
mode = com.medithings.vesiscan.models.SessionMode.ALIGNMENT,
// 현재 dev 토글된 algo 그대로 — V1 (default) 또는 V2.
alignmentAlgo = com.example.medilightv2android.managers.GreenZoneConstants.alignmentAlgo.name,
alignmentAlgo = com.medithings.vesiscan.managers.GreenZoneConstants.alignmentAlgo.name,
)
)
appState.enterAlignmentSession()
@@ -1,4 +1,4 @@
package com.example.medilightv2android.ui.views.clinical
package com.medithings.vesiscan.ui.views.clinical
import android.widget.Toast
import androidx.compose.foundation.Canvas
@@ -25,17 +25,17 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.medilightv2android.AppScreen
import com.example.medilightv2android.AppState
import com.example.medilightv2android.ble.BleManager
import com.example.medilightv2android.ble.ImuSample
import com.example.medilightv2android.ble.PiezoChannelData
import com.example.medilightv2android.managers.ImuAnalysis
import com.example.medilightv2android.services.AdcCsvLogger
import com.example.medilightv2android.services.ClinicalSessionStore
import com.example.medilightv2android.services.ImuCsvLogger
import com.example.medilightv2android.services.MeasurementLogService
import com.example.medilightv2android.ui.theme.*
import com.medithings.vesiscan.AppScreen
import com.medithings.vesiscan.AppState
import com.medithings.vesiscan.ble.BleManager
import com.medithings.vesiscan.ble.ImuSample
import com.medithings.vesiscan.ble.PiezoChannelData
import com.medithings.vesiscan.managers.ImuAnalysis
import com.medithings.vesiscan.services.AdcCsvLogger
import com.medithings.vesiscan.services.ClinicalSessionStore
import com.medithings.vesiscan.services.ImuCsvLogger
import com.medithings.vesiscan.services.MeasurementLogService
import com.medithings.vesiscan.ui.theme.*
import kotlinx.coroutines.delay
import kotlin.math.max
import kotlin.math.min
@@ -79,7 +79,7 @@ fun ClinicalLiveView(appState: AppState) {
var pendingPiezo by remember { mutableStateOf<List<PiezoChannelData>?>(null) }
// V4.1 wall detection — 채널별 ant/post peak 표시용 (사내 테스트 가시화 요청)
val v41 = remember { com.example.medilightv2android.walldetect.V41Detector() }
val v41 = remember { com.medithings.vesiscan.walldetect.V41Detector() }
var channelWalls by remember { mutableStateOf<List<Pair<Int, Int>?>>(List(6) { null }) }
DisposableEffect(Unit) {
@@ -91,13 +91,13 @@ fun ClinicalLiveView(appState: AppState) {
val adcMatrix = (0 until 6).map { ch ->
channels.find { it.channel == ch }?.buffer?.map { it.toInt() } ?: List(100) { 0 }
}
val probe = com.example.medilightv2android.walldetect.dto.ProbeProfileDto(
name = com.example.medilightv2android.managers.PiezoHW.presetName,
val probe = com.medithings.vesiscan.walldetect.dto.ProbeProfileDto(
name = com.medithings.vesiscan.managers.PiezoHW.presetName,
rMm = 50.2, anteriorAnchorMm = 32.0,
fsHz = 537500, cMmPerUs = 1.54,
anglesDeg = com.example.medilightv2android.managers.PiezoHW.degreeAll.toList()
anglesDeg = com.medithings.vesiscan.managers.PiezoHW.degreeAll.toList()
)
val sweepInput = com.example.medilightv2android.walldetect.dto.SweepInput(
val sweepInput = com.medithings.vesiscan.walldetect.dto.SweepInput(
requestId = "clinical_${System.currentTimeMillis()}",
timestampMs = System.currentTimeMillis(),
deviceId = bleManager.connectedDeviceName.value,
@@ -1,4 +1,4 @@
package com.example.medilightv2android.ui.views.connection
package com.medithings.vesiscan.ui.views.connection
import android.Manifest
import android.content.pm.PackageManager
@@ -34,15 +34,15 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.content.ContextCompat
import com.example.medilightv2android.AppState
import com.example.medilightv2android.R
import com.example.medilightv2android.ble.BleDevice
import com.example.medilightv2android.ble.BleManager
import com.example.medilightv2android.models.SensorMode
import com.example.medilightv2android.services.KnownDevice
import com.example.medilightv2android.services.KnownDeviceStore
import com.example.medilightv2android.ui.components.BladdyIdleRiveView
import com.example.medilightv2android.ui.theme.*
import com.medithings.vesiscan.AppState
import com.medithings.vesiscan.R
import com.medithings.vesiscan.ble.BleDevice
import com.medithings.vesiscan.ble.BleManager
import com.medithings.vesiscan.models.SensorMode
import com.medithings.vesiscan.services.KnownDevice
import com.medithings.vesiscan.services.KnownDeviceStore
import com.medithings.vesiscan.ui.components.BladdyIdleRiveView
import com.medithings.vesiscan.ui.theme.*
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
@@ -55,7 +55,7 @@ fun DeviceScanView(appState: AppState) {
var permissionsGranted by remember { mutableStateOf(false) }
// Clinical 흐름에서만 다중 기기 저장/표시 — 일반 사용자는 fresh scan & pair.
val isClinicalFlow = com.example.medilightv2android.services.ClinicalSessionStore.inClinicalFlow
val isClinicalFlow = com.medithings.vesiscan.services.ClinicalSessionStore.inClinicalFlow
var knownDevices by remember {
mutableStateOf(if (isClinicalFlow) KnownDeviceStore.getAll(context) else emptyList())
}
@@ -287,7 +287,7 @@ fun DeviceScanView(appState: AppState) {
Button(
onClick = {
appState.isDeviceConnected = true
appState.currentScreen = com.example.medilightv2android.AppScreen.PIEZO_PERSONALIZATION
appState.currentScreen = com.medithings.vesiscan.AppScreen.PIEZO_PERSONALIZATION
},
modifier = Modifier.fillMaxWidth().height(48.dp),
shape = RoundedCornerShape(12.dp),
@@ -1,4 +1,4 @@
package com.example.medilightv2android.ui.views.consent
package com.medithings.vesiscan.ui.views.consent
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
@@ -15,13 +15,13 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import com.example.medilightv2android.R
import com.medithings.vesiscan.R
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.medilightv2android.AppState
import com.example.medilightv2android.services.UserStorage
import com.example.medilightv2android.ui.components.BladdyIdleRiveView
import com.example.medilightv2android.ui.theme.*
import com.medithings.vesiscan.AppState
import com.medithings.vesiscan.services.UserStorage
import com.medithings.vesiscan.ui.components.BladdyIdleRiveView
import com.medithings.vesiscan.ui.theme.*
@Composable
fun ConsentView(appState: AppState) {
@@ -1,4 +1,4 @@
package com.example.medilightv2android.ui.views.history
package com.medithings.vesiscan.ui.views.history
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
@@ -31,11 +31,11 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.medilightv2android.AppState
import com.example.medilightv2android.R
import com.example.medilightv2android.models.*
import com.example.medilightv2android.services.MeasurementHistoryStore
import com.example.medilightv2android.ui.theme.*
import com.medithings.vesiscan.AppState
import com.medithings.vesiscan.R
import com.medithings.vesiscan.models.*
import com.medithings.vesiscan.services.MeasurementHistoryStore
import com.medithings.vesiscan.ui.theme.*
import java.text.SimpleDateFormat
import java.util.*
@@ -1,4 +1,4 @@
package com.example.medilightv2android.ui.views.home
package com.medithings.vesiscan.ui.views.home
import android.widget.Toast
import androidx.compose.foundation.background
@@ -28,12 +28,12 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import com.example.medilightv2android.R
import com.medithings.vesiscan.R
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.medilightv2android.AppState
import com.example.medilightv2android.ui.components.BladdyIdleRiveView
import com.example.medilightv2android.ui.theme.*
import com.medithings.vesiscan.AppState
import com.medithings.vesiscan.ui.components.BladdyIdleRiveView
import com.medithings.vesiscan.ui.theme.*
@Composable
fun HomeView(appState: AppState) {
@@ -147,7 +147,7 @@ fun HomeView(appState: AppState) {
// Dev 모드 한정 — Clinical R&D Mode 진입
if (appState.isDevMode) {
Button(
onClick = { appState.currentScreen = com.example.medilightv2android.AppScreen.CLINICAL_HOME },
onClick = { appState.currentScreen = com.medithings.vesiscan.AppScreen.CLINICAL_HOME },
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 32.dp)
@@ -166,7 +166,7 @@ fun HomeView(appState: AppState) {
// 언어 선택 다이얼로그 — 시스템 / 한국어 / English. 변경 시 Activity recreate.
if (showLanguageDialog) {
val curMode = com.example.medilightv2android.services.LocalePref.getMode(context)
val curMode = com.medithings.vesiscan.services.LocalePref.getMode(context)
var selected by remember { mutableStateOf(curMode) }
AlertDialog(
onDismissRequest = { showLanguageDialog = false },
@@ -174,9 +174,9 @@ fun HomeView(appState: AppState) {
text = {
Column {
listOf(
com.example.medilightv2android.services.LocalePref.Mode.SYSTEM to stringResource(R.string.language_system_default),
com.example.medilightv2android.services.LocalePref.Mode.KO to stringResource(R.string.language_korean),
com.example.medilightv2android.services.LocalePref.Mode.EN to stringResource(R.string.language_english),
com.medithings.vesiscan.services.LocalePref.Mode.SYSTEM to stringResource(R.string.language_system_default),
com.medithings.vesiscan.services.LocalePref.Mode.KO to stringResource(R.string.language_korean),
com.medithings.vesiscan.services.LocalePref.Mode.EN to stringResource(R.string.language_english),
).forEach { (mode, label) ->
Row(
verticalAlignment = Alignment.CenterVertically,
@@ -197,7 +197,7 @@ fun HomeView(appState: AppState) {
},
confirmButton = {
TextButton(onClick = {
com.example.medilightv2android.services.LocalePref.setMode(context, selected)
com.medithings.vesiscan.services.LocalePref.setMode(context, selected)
showLanguageDialog = false
// Activity recreate → attachBaseContext 다시 호출 → 새 locale 적용
(context as? android.app.Activity)?.recreate()
@@ -1,4 +1,4 @@
package com.example.medilightv2android.ui.views.monitoring
package com.medithings.vesiscan.ui.views.monitoring
import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult
@@ -21,11 +21,11 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.content.ContextCompat
import com.example.medilightv2android.R
import com.example.medilightv2android.measure.VoidingRecord
import com.example.medilightv2android.measure.VoidingRecordStore
import com.example.medilightv2android.speech.SpeechRecognizerManager
import com.example.medilightv2android.speech.VolumeParser
import com.medithings.vesiscan.R
import com.medithings.vesiscan.measure.VoidingRecord
import com.medithings.vesiscan.measure.VoidingRecordStore
import com.medithings.vesiscan.speech.SpeechRecognizerManager
import com.medithings.vesiscan.speech.VolumeParser
/**
* Catheterize 바텀시트 — 수기/음성/카메라 3가지 입력
@@ -1,4 +1,4 @@
package com.example.medilightv2android.ui.views.monitoring
package com.medithings.vesiscan.ui.views.monitoring
import android.os.Handler
import android.os.Looper
@@ -6,7 +6,7 @@ import android.util.Log
import androidx.compose.runtime.rememberCoroutineScope
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import com.example.medilightv2android.AppScreen
import com.medithings.vesiscan.AppScreen
import androidx.compose.material3.OutlinedTextField
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
@@ -42,23 +42,23 @@ import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.medilightv2android.AppState
import com.example.medilightv2android.ble.BleManager
import com.example.medilightv2android.ble.PiezoChannelData
import com.example.medilightv2android.managers.PiezoEchoAnalyzer
import com.example.medilightv2android.managers.PiezoConstants
import com.example.medilightv2android.managers.GreenZoneConstants
import com.example.medilightv2android.managers.PiezoHW
import com.example.medilightv2android.managers.estimateBladderVolume6ch
import com.example.medilightv2android.models.BladderLevel
import com.example.medilightv2android.models.UrgencyLevel
import com.example.medilightv2android.services.ScanLogStore
import com.example.medilightv2android.ui.components.BladdyRiveView
import com.example.medilightv2android.ui.components.DonutChartView
import com.example.medilightv2android.ui.components.LockableSlider
import com.example.medilightv2android.ui.components.ScanLogDialog
import com.example.medilightv2android.R
import com.example.medilightv2android.ui.theme.*
import com.medithings.vesiscan.AppState
import com.medithings.vesiscan.ble.BleManager
import com.medithings.vesiscan.ble.PiezoChannelData
import com.medithings.vesiscan.managers.PiezoEchoAnalyzer
import com.medithings.vesiscan.managers.PiezoConstants
import com.medithings.vesiscan.managers.GreenZoneConstants
import com.medithings.vesiscan.managers.PiezoHW
import com.medithings.vesiscan.managers.estimateBladderVolume6ch
import com.medithings.vesiscan.models.BladderLevel
import com.medithings.vesiscan.models.UrgencyLevel
import com.medithings.vesiscan.services.ScanLogStore
import com.medithings.vesiscan.ui.components.BladdyRiveView
import com.medithings.vesiscan.ui.components.DonutChartView
import com.medithings.vesiscan.ui.components.LockableSlider
import com.medithings.vesiscan.ui.components.ScanLogDialog
import com.medithings.vesiscan.R
import com.medithings.vesiscan.ui.theme.*
import androidx.compose.ui.res.stringResource
import kotlin.math.roundToInt
@@ -132,21 +132,21 @@ fun PiezoMonitoringView(appState: AppState) {
val analyzer = remember { PiezoEchoAnalyzer.shared }
// Posture (flowchart-based, IMU polling — Phase 1) + Wakeup/Walking detector (Phase 2)
var currentPosture by remember { mutableStateOf(com.example.medilightv2android.managers.Posture.UNKNOWN) }
var currentPosture by remember { mutableStateOf(com.medithings.vesiscan.managers.Posture.UNKNOWN) }
var showWakeupAlarm by remember { mutableStateOf(false) } // 주황 — LYING → NON_LYING/WALKING
var wakeupAlarmElapsedSec by remember { mutableStateOf(0L) }
var showWalkingAlarm by remember { mutableStateOf(false) } // 빨강 — NON_LYING → WALKING
val walkingDetector = remember { com.example.medilightv2android.managers.WalkingDetector() }
val walkingDetector = remember { com.medithings.vesiscan.managers.WalkingDetector() }
val wakeupDetector = remember {
com.example.medilightv2android.managers.WakeupDetector(
lyingDurationSec = com.example.medilightv2android.services.WakeupAlarmPrefs.lyingDurationSec(context)
com.medithings.vesiscan.managers.WakeupDetector(
lyingDurationSec = com.medithings.vesiscan.services.WakeupAlarmPrefs.lyingDurationSec(context)
).apply {
onWakeup = { elapsed ->
if (com.example.medilightv2android.services.WakeupAlarmPrefs.isEnabled(context)) {
if (com.medithings.vesiscan.services.WakeupAlarmPrefs.isEnabled(context)) {
wakeupAlarmElapsedSec = elapsed
showWakeupAlarm = true
}
com.example.medilightv2android.services.PostureEventLogger.logAlarm(
com.medithings.vesiscan.services.PostureEventLogger.logAlarm(
System.currentTimeMillis(),
"wakeup_alarm_lying_${elapsed}s"
)
@@ -317,7 +317,7 @@ fun PiezoMonitoringView(appState: AppState) {
val fullChannels = channels.filter { it.buffer.size >= 20 }
if (fullChannels.size >= 6) {
val sortedBuffers = channels.sortedBy { it.channel }.map { it.buffer }
val detachResult = com.example.medilightv2android.managers.DetachmentDetection.detect(sortedBuffers)
val detachResult = com.medithings.vesiscan.managers.DetachmentDetection.detect(sortedBuffers)
if (detachResult.isDetached) {
if (!wasDetached) {
Log.w("PiezoMonitor", "DETACHED mean=${"%.1f".format(detachResult.meanAbs)} std=${"%.1f".format(detachResult.stdVal)}")
@@ -341,28 +341,28 @@ fun PiezoMonitoringView(appState: AppState) {
val validChannels = channels.filter { it.isValid }
val allChannelsReceived = channels.filter { it.buffer.size >= 20 }.size >= 6
if (validChannels.isNotEmpty() && allChannelsReceived) {
val method = com.example.medilightv2android.managers.GreenZoneConstants.detectionMethod
val analyzerA = com.example.medilightv2android.managers.PiezoEchoAnalyzerA.shared
val method = com.medithings.vesiscan.managers.GreenZoneConstants.detectionMethod
val analyzerA = com.medithings.vesiscan.managers.PiezoEchoAnalyzerA.shared
var methodCBvMl: Double? = null
val methodLabel: String
when (method) {
com.example.medilightv2android.managers.DetectionMethod.METHOD_C -> {
com.medithings.vesiscan.managers.DetectionMethod.METHOD_C -> {
methodLabel = "C"
// V4.1 detector
val v41 = com.example.medilightv2android.walldetect.V41Detector()
val v41 = com.medithings.vesiscan.walldetect.V41Detector()
val adcMatrix = (0 until 6).map { ch ->
val chData = channels.find { it.channel == ch }
chData?.buffer?.map { it.toInt() } ?: List(100) { 0 }
}
val probe = com.example.medilightv2android.walldetect.dto.ProbeProfileDto(
name = com.example.medilightv2android.managers.PiezoHW.presetName,
val probe = com.medithings.vesiscan.walldetect.dto.ProbeProfileDto(
name = com.medithings.vesiscan.managers.PiezoHW.presetName,
rMm = 50.2, anteriorAnchorMm = 32.0,
fsHz = 537500, cMmPerUs = 1.54,
anglesDeg = com.example.medilightv2android.managers.PiezoHW.degreeAll.toList()
anglesDeg = com.medithings.vesiscan.managers.PiezoHW.degreeAll.toList()
)
val sweepInput = com.example.medilightv2android.walldetect.dto.SweepInput(
val sweepInput = com.medithings.vesiscan.walldetect.dto.SweepInput(
requestId = "measure_${System.currentTimeMillis()}",
timestampMs = System.currentTimeMillis(),
deviceId = bleManager.connectedDeviceName.value,
@@ -395,7 +395,7 @@ fun PiezoMonitoringView(appState: AppState) {
Log.d("PiezoMonitor", " CH${ch.ch}[C]: ant=${ch.antIdx} post=${ch.postIdx} chord=${ch.chordMm?.let { "%.1f".format(it) }}mm")
}
}
com.example.medilightv2android.managers.DetectionMethod.METHOD_A -> {
com.medithings.vesiscan.managers.DetectionMethod.METHOD_A -> {
methodLabel = "A"
val channelResults = validChannels.map { analyzerA.analyzeChannel(it.buffer, it.channel) }
for (ch in channelResults) {
@@ -407,14 +407,14 @@ fun PiezoMonitoringView(appState: AppState) {
if (ch.isValid && ch.result != null) Log.d("PiezoMonitor", " CH${ch.channel}[A]: ant=${ch.result.ant} post=${ch.result.post} urine=${ch.result.urineLen}")
}
}
com.example.medilightv2android.managers.DetectionMethod.METHOD_D -> {
com.medithings.vesiscan.managers.DetectionMethod.METHOD_D -> {
// 2026-06-30: Detection + BV 통합. MethodDRunner 로 6채널 raw → walls.
methodLabel = "D"
val signals = (0..5).map { ch ->
val cd = channels.find { it.channel == ch }
cd?.buffer?.map { it.toDouble() }?.toDoubleArray() ?: DoubleArray(0)
}
val dets = com.example.medilightv2android.walldetect.MethodDRunner
val dets = com.medithings.vesiscan.walldetect.MethodDRunner
.detectMultichannel(signals)
for (i in 0..5) {
val r = dets.getOrNull(i)
@@ -443,10 +443,10 @@ fun PiezoMonitoringView(appState: AppState) {
totalWallCount = allWalls.count { it != null }
// ADC 로그 저장 — allWalls (선택된 Method) 기준으로 통일
val logService = com.example.medilightv2android.services.MeasurementLogService.getInstance(context)
val logService = com.medithings.vesiscan.services.MeasurementLogService.getInstance(context)
val channelLogs = (0..5).map { ch ->
val w = allWalls[ch]
com.example.medilightv2android.services.ChannelLogEntry(
com.medithings.vesiscan.services.ChannelLogEntry(
channel = ch,
ant = w?.first,
post = w?.second,
@@ -456,7 +456,7 @@ fun PiezoMonitoringView(appState: AppState) {
}
val rawADC = channels.sortedBy { it.channel }.map { it.buffer }
val bvMethodSetting = com.example.medilightv2android.managers.GreenZoneConstants.bvMethod
val bvMethodSetting = com.medithings.vesiscan.managers.GreenZoneConstants.bvMethod
// ── Ant-offset adjustment for BV calculation ───────────────────────────────
// The user-facing setting is a non-negative magnitude in mm. BV calculation
// subtracts it from each channel's anterior wall index (probe-ward shift).
@@ -467,13 +467,13 @@ fun PiezoMonitoringView(appState: AppState) {
val antOffsetSamples =
if (antOffsetMm != 0)
(antOffsetMm.toDouble() /
com.example.medilightv2android.managers.PiezoHW.distancePerSample).toInt()
com.medithings.vesiscan.managers.PiezoHW.distancePerSample).toInt()
else 0
// 2026-06-30: BvMethod.METHOD_D 시 Method D walls 사용. 단 detection 이
// 이미 METHOD_D 면 allWalls 가 곧 Method D → recompute 생략.
val useMethodDBv = bvMethodSetting == com.example.medilightv2android.managers.BvMethod.METHOD_D
val detectionIsMethodD = method == com.example.medilightv2android.managers.DetectionMethod.METHOD_D
val useMethodDBv = bvMethodSetting == com.medithings.vesiscan.managers.BvMethod.METHOD_D
val detectionIsMethodD = method == com.medithings.vesiscan.managers.DetectionMethod.METHOD_D
val sourceAllWalls: List<Pair<Int, Int>?> = if (useMethodDBv && !detectionIsMethodD) {
val signals = (0..5).map { ch ->
val cd = channels.find { it.channel == ch }
@@ -482,10 +482,10 @@ fun PiezoMonitoringView(appState: AppState) {
if (signals.all { it.isEmpty() }) allWalls
// 2026-07-02: NEW BV lib (piezophantomtest 4830e7c) lumen_inset_frac=0.15
// MethodDResult 의 (ls, le) 로 벽을 lumen 안쪽으로 15% 이동.
else com.example.medilightv2android.walldetect.MethodDRunner
else com.medithings.vesiscan.walldetect.MethodDRunner
.detectMultichannel(signals)
.map { r -> r?.let {
com.example.medilightv2android.managers.applyLumenInsetOne(
com.medithings.vesiscan.managers.applyLumenInsetOne(
it.ant, it.post, it.lowStart, it.lowEnd
)
} }
@@ -498,8 +498,8 @@ fun PiezoMonitoringView(appState: AppState) {
}
val effectiveCenterWalls = (0..3).count { effectiveAllWalls[it] != null }
val useV41Bv = method == com.example.medilightv2android.managers.DetectionMethod.METHOD_C
&& bvMethodSetting == com.example.medilightv2android.managers.BvMethod.V41
val useV41Bv = method == com.medithings.vesiscan.managers.DetectionMethod.METHOD_C
&& bvMethodSetting == com.medithings.vesiscan.managers.BvMethod.V41
&& methodCBvMl != null
&& antOffsetSamples == 0
&& effectiveCenterWalls >= 4 // center 4 모두 필요
@@ -510,7 +510,7 @@ fun PiezoMonitoringView(appState: AppState) {
bleManager.debugLogger.measurementResult(methodCBvMl, 1.0, totalWallCount,
(0..5).map { allWalls[it]?.let { w -> w.second - w.first } ?: 0 })
logService.logMeasurement(methodCBvMl, channelLogs, rawADC)
com.example.medilightv2android.services.AdcCsvLogger.log(rawADC, methodCBvMl)
com.medithings.vesiscan.services.AdcCsvLogger.log(rawADC, methodCBvMl)
} else if (effectiveCenterWalls >= 4) {
// 검증 로그 — ant offset 비활성(0)이 아닐 때 chord 변화 출력
if (antOffsetSamples != 0) {
@@ -534,18 +534,18 @@ fun PiezoMonitoringView(appState: AppState) {
(0..5).map { allWalls[it]?.let { w -> w.second - w.first } ?: 0 },
otsuThreshold = analyzer.lastOtsuThreshold)
logService.logMeasurement(bvResult.volumeMl, channelLogs, rawADC)
com.example.medilightv2android.services.AdcCsvLogger.log(rawADC, bvResult.volumeMl, bvResult.lrRatio)
com.medithings.vesiscan.services.AdcCsvLogger.log(rawADC, bvResult.volumeMl, bvResult.lrRatio)
}
} else if (totalWallCount > 0) {
Log.w("PiezoMonitor", "No center channels: center=0 total=$totalWallCount/6")
bleManager.debugLogger.warn("BV_SKIP scan=${com.example.medilightv2android.services.AdcCsvLogger.lastScanId} center=0 total=$totalWallCount/6")
com.example.medilightv2android.services.AdcCsvLogger.log(rawADC, null)
bleManager.debugLogger.warn("BV_SKIP scan=${com.medithings.vesiscan.services.AdcCsvLogger.lastScanId} center=0 total=$totalWallCount/6")
com.medithings.vesiscan.services.AdcCsvLogger.log(rawADC, null)
}
} else {
Log.d("PiezoMonitor", "No valid channels — volume unchanged")
bleManager.debugLogger.warn("BV_FAIL scan=${com.example.medilightv2android.services.AdcCsvLogger.lastScanId} no valid channels")
bleManager.debugLogger.warn("BV_FAIL scan=${com.medithings.vesiscan.services.AdcCsvLogger.lastScanId} no valid channels")
val failADC = channels.sortedBy { it.channel }.map { it.buffer }
com.example.medilightv2android.services.AdcCsvLogger.log(failADC, null)
com.medithings.vesiscan.services.AdcCsvLogger.log(failADC, null)
}
// allWalls를 UI state에 저장 (ChannelPanel에서 사용)
@@ -754,8 +754,8 @@ fun PiezoMonitoringView(appState: AppState) {
// Posture event 로깅 — 검증용 CSV (앱 진입 시 새 파일)
// + 사용자 튜닝 임계값 SharedPreferences 에서 로드
LaunchedEffect(Unit) {
com.example.medilightv2android.services.PostureTuningPrefs.loadAll(context)
com.example.medilightv2android.services.PostureEventLogger.startSession(context)
com.medithings.vesiscan.services.PostureTuningPrefs.loadAll(context)
com.medithings.vesiscan.services.PostureEventLogger.startSession(context)
}
// IMU polling + 자세 분류 — msp 명령 (rsp: 응답, IMU-only).
@@ -766,12 +766,12 @@ fun PiezoMonitoringView(appState: AppState) {
LaunchedEffect(isConnected, isDemoMode) {
Log.d("ImuDbg", "LaunchedEffect start isConnected=$isConnected isDemoMode=$isDemoMode")
if (isDemoMode) {
currentPosture = com.example.medilightv2android.managers.Posture.LYING
currentPosture = com.medithings.vesiscan.managers.Posture.LYING
return@LaunchedEffect
}
var unknownStreak = 0
bleManager.imuCollector.onComplete = { samples ->
val r = com.example.medilightv2android.managers.ImuPostureClassifier.classify(samples)
val r = com.medithings.vesiscan.managers.ImuPostureClassifier.classify(samples)
val nowMs = System.currentTimeMillis()
// Walking detector — pitch 가 valid 일 때만 의미. acc gate fail 시 그대로 update (wmean/wstd 계산 + 시간 grace 처리).
val walkSnap = walkingDetector.update(samples, r.pitchDeg, nowMs)
@@ -779,34 +779,34 @@ fun PiezoMonitoringView(appState: AppState) {
// Composite posture — WALKING 이 UNKNOWN/NON_LYING 보다 우선 (자체 게이트 신뢰)
val composite = when {
isWalkingNow -> com.example.medilightv2android.managers.Posture.WALKING
r.posture == com.example.medilightv2android.managers.Posture.UNKNOWN -> com.example.medilightv2android.managers.Posture.UNKNOWN
isWalkingNow -> com.medithings.vesiscan.managers.Posture.WALKING
r.posture == com.medithings.vesiscan.managers.Posture.UNKNOWN -> com.medithings.vesiscan.managers.Posture.UNKNOWN
else -> r.posture // LYING or NON_LYING
}
if (composite != com.example.medilightv2android.managers.Posture.UNKNOWN) {
if (composite != com.medithings.vesiscan.managers.Posture.UNKNOWN) {
currentPosture = composite
wakeupDetector.update(composite)
unknownStreak = 0
} else {
unknownStreak++
if (unknownStreak >= 3) currentPosture = com.example.medilightv2android.managers.Posture.UNKNOWN
if (unknownStreak >= 3) currentPosture = com.medithings.vesiscan.managers.Posture.UNKNOWN
}
// Walking 시작 전이 (NON_WALKING → WALKING) — critical 알람 (alarm enabled 시)
if (isWalkingNow && !prevIsWalking) {
if (com.example.medilightv2android.services.WakeupAlarmPrefs.isEnabled(context)) {
if (com.medithings.vesiscan.services.WakeupAlarmPrefs.isEnabled(context)) {
showWalkingAlarm = true
}
com.example.medilightv2android.services.PostureEventLogger.logAlarm(nowMs, "walking_start")
com.medithings.vesiscan.services.PostureEventLogger.logAlarm(nowMs, "walking_start")
}
if (!isWalkingNow && prevIsWalking) {
com.example.medilightv2android.services.PostureEventLogger.logAlarm(nowMs, "walking_end")
com.medithings.vesiscan.services.PostureEventLogger.logAlarm(nowMs, "walking_end")
}
prevIsWalking = isWalkingNow
// CSV logging — 검증용
com.example.medilightv2android.services.PostureEventLogger.logCycle(
com.medithings.vesiscan.services.PostureEventLogger.logCycle(
nowMs, composite.name, isWalkingNow,
r.pitchDeg, walkSnap.wmean, walkSnap.wstd, r.accMag, r.gyroMagDps,
)
@@ -961,8 +961,8 @@ fun PiezoMonitoringView(appState: AppState) {
val quickVol = if (lastVolumeMl > 0) lastVolumeMl.toInt() else appState.estimatedVolume
Surface(
onClick = {
com.example.medilightv2android.measure.VoidingRecordStore.add(
context, com.example.medilightv2android.measure.VoidingRecord(
com.medithings.vesiscan.measure.VoidingRecordStore.add(
context, com.medithings.vesiscan.measure.VoidingRecord(
volume = quickVol, method = "catheterize"
)
)
@@ -1021,8 +1021,8 @@ fun PiezoMonitoringView(appState: AppState) {
TextButton(onClick = {
val vol = volumeText.toIntOrNull()
if (vol != null && vol in 1..1000) {
com.example.medilightv2android.measure.VoidingRecordStore.add(
context, com.example.medilightv2android.measure.VoidingRecord(volume = vol, method = "manual")
com.medithings.vesiscan.measure.VoidingRecordStore.add(
context, com.medithings.vesiscan.measure.VoidingRecord(volume = vol, method = "manual")
)
appState.resetLevel()
android.widget.Toast.makeText(context, context.getString(R.string.registered_diary), android.widget.Toast.LENGTH_SHORT).show()
@@ -1038,7 +1038,7 @@ fun PiezoMonitoringView(appState: AppState) {
if (showVoiceInput) {
var voiceResult by remember { mutableStateOf<String?>(null) }
var isListening by remember { mutableStateOf(false) }
var speechMgr by remember { mutableStateOf<com.example.medilightv2android.speech.SpeechRecognizerManager?>(null) }
var speechMgr by remember { mutableStateOf<com.medithings.vesiscan.speech.SpeechRecognizerManager?>(null) }
DisposableEffect(Unit) { onDispose { speechMgr?.destroy() } }
@@ -1047,14 +1047,14 @@ fun PiezoMonitoringView(appState: AppState) {
if (androidx.core.content.ContextCompat.checkSelfPermission(context, android.Manifest.permission.RECORD_AUDIO)
== android.content.pm.PackageManager.PERMISSION_GRANTED) {
isListening = true
speechMgr = com.example.medilightv2android.speech.SpeechRecognizerManager(
speechMgr = com.medithings.vesiscan.speech.SpeechRecognizerManager(
context = context,
onResult = { candidates ->
isListening = false
val parsed = com.example.medilightv2android.speech.VolumeParser.parseBest(candidates)
val parsed = com.medithings.vesiscan.speech.VolumeParser.parseBest(candidates)
if (parsed != null) {
com.example.medilightv2android.measure.VoidingRecordStore.add(
context, com.example.medilightv2android.measure.VoidingRecord(volume = parsed.volumeMl, method = "voice")
com.medithings.vesiscan.measure.VoidingRecordStore.add(
context, com.medithings.vesiscan.measure.VoidingRecord(volume = parsed.volumeMl, method = "voice")
)
appState.resetLevel()
android.widget.Toast.makeText(context, context.getString(R.string.registered_ml, parsed.volumeMl), android.widget.Toast.LENGTH_SHORT).show()
@@ -1168,19 +1168,19 @@ fun PiezoMonitoringView(appState: AppState) {
// Dev mode 에서만 노출 (legacy 유지) — 일반 사용자에겐 숨김.
if (appState.isDevMode) {
var rawBleCapturing by remember {
mutableStateOf(com.example.medilightv2android.services.RawBleLogger.isCapturing)
mutableStateOf(com.medithings.vesiscan.services.RawBleLogger.isCapturing)
}
IconButton(
onClick = {
if (rawBleCapturing) {
val f = com.example.medilightv2android.services.RawBleLogger.stopCapture()
val n = com.example.medilightv2android.services.RawBleLogger.count
val f = com.medithings.vesiscan.services.RawBleLogger.stopCapture()
val n = com.medithings.vesiscan.services.RawBleLogger.count
rawBleCapturing = false
android.widget.Toast.makeText(context,
"Raw BLE: $n packets saved to ${f?.name}",
android.widget.Toast.LENGTH_LONG).show()
} else {
val f = com.example.medilightv2android.services.RawBleLogger.startCapture()
val f = com.medithings.vesiscan.services.RawBleLogger.startCapture()
rawBleCapturing = true
android.widget.Toast.makeText(context,
"Raw BLE capture started → ${f.name}",
@@ -1256,8 +1256,8 @@ fun PiezoMonitoringView(appState: AppState) {
// BLE 끊김 banner는 사내 테스트 피드백으로 제거됨 — 백그라운드 자동 재연결만 유지.
// 펌웨어 구버전 경고
com.example.medilightv2android.ui.components.FirmwareWarningBanner(bleManager)
com.example.medilightv2android.ui.components.BatteryWarningBanner(bleManager)
com.medithings.vesiscan.ui.components.FirmwareWarningBanner(bleManager)
com.medithings.vesiscan.ui.components.BatteryWarningBanner(bleManager)
val scrollState = rememberScrollState()
LaunchedEffect(showSettings) {
@@ -1306,22 +1306,22 @@ fun PiezoMonitoringView(appState: AppState) {
label = "posture-chip"
) { posture ->
val icon = when (posture) {
com.example.medilightv2android.managers.Posture.LYING -> Icons.Default.Hotel
com.example.medilightv2android.managers.Posture.NON_LYING -> Icons.Default.AccessibilityNew
com.example.medilightv2android.managers.Posture.WALKING -> Icons.Default.DirectionsWalk
com.example.medilightv2android.managers.Posture.UNKNOWN -> Icons.Default.HelpOutline
com.medithings.vesiscan.managers.Posture.LYING -> Icons.Default.Hotel
com.medithings.vesiscan.managers.Posture.NON_LYING -> Icons.Default.AccessibilityNew
com.medithings.vesiscan.managers.Posture.WALKING -> Icons.Default.DirectionsWalk
com.medithings.vesiscan.managers.Posture.UNKNOWN -> Icons.Default.HelpOutline
}
val tint = when (posture) {
com.example.medilightv2android.managers.Posture.LYING -> Color(0xFF2196F3)
com.example.medilightv2android.managers.Posture.NON_LYING -> Color(0xFF4CAF50)
com.example.medilightv2android.managers.Posture.WALKING -> Color(0xFFD32F2F)
com.example.medilightv2android.managers.Posture.UNKNOWN -> Color(0xFF616161) // 진한 회색 — 가시성 확보
com.medithings.vesiscan.managers.Posture.LYING -> Color(0xFF2196F3)
com.medithings.vesiscan.managers.Posture.NON_LYING -> Color(0xFF4CAF50)
com.medithings.vesiscan.managers.Posture.WALKING -> Color(0xFFD32F2F)
com.medithings.vesiscan.managers.Posture.UNKNOWN -> Color(0xFF616161) // 진한 회색 — 가시성 확보
}
val label = when (posture) {
com.example.medilightv2android.managers.Posture.LYING -> stringResource(R.string.piezo_posture_lying)
com.example.medilightv2android.managers.Posture.NON_LYING -> stringResource(R.string.piezo_posture_upright)
com.example.medilightv2android.managers.Posture.WALKING -> stringResource(R.string.piezo_posture_walking)
com.example.medilightv2android.managers.Posture.UNKNOWN -> stringResource(R.string.piezo_posture_detecting)
com.medithings.vesiscan.managers.Posture.LYING -> stringResource(R.string.piezo_posture_lying)
com.medithings.vesiscan.managers.Posture.NON_LYING -> stringResource(R.string.piezo_posture_upright)
com.medithings.vesiscan.managers.Posture.WALKING -> stringResource(R.string.piezo_posture_walking)
com.medithings.vesiscan.managers.Posture.UNKNOWN -> stringResource(R.string.piezo_posture_detecting)
}
Row(
verticalAlignment = Alignment.CenterVertically,
@@ -1458,8 +1458,8 @@ fun PiezoMonitoringView(appState: AppState) {
Button(
onClick = {
val quickVol = if (lastVolumeMl > 0) lastVolumeMl.toInt() else appState.estimatedVolume
com.example.medilightv2android.measure.VoidingRecordStore.add(
context, com.example.medilightv2android.measure.VoidingRecord(
com.medithings.vesiscan.measure.VoidingRecordStore.add(
context, com.medithings.vesiscan.measure.VoidingRecord(
volume = quickVol, method = "catheterize"
)
)
@@ -1847,22 +1847,22 @@ fun PiezoMonitoringView(appState: AppState) {
HorizontalDivider(modifier = Modifier.padding(vertical = dividerPadding))
// Detection Method A/B/C
var selectedMethod by remember { mutableStateOf(com.example.medilightv2android.managers.GreenZoneConstants.detectionMethod) }
var selectedMethod by remember { mutableStateOf(com.medithings.vesiscan.managers.GreenZoneConstants.detectionMethod) }
Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
Text("Detection", fontSize = labelSize, fontWeight = FontWeight.SemiBold)
Spacer(modifier = Modifier.weight(1f))
val methods = listOf(
com.example.medilightv2android.managers.DetectionMethod.METHOD_A to "A",
com.example.medilightv2android.managers.DetectionMethod.METHOD_B to "B",
com.example.medilightv2android.managers.DetectionMethod.METHOD_C to "C",
com.example.medilightv2android.managers.DetectionMethod.METHOD_D to "D"
com.medithings.vesiscan.managers.DetectionMethod.METHOD_A to "A",
com.medithings.vesiscan.managers.DetectionMethod.METHOD_B to "B",
com.medithings.vesiscan.managers.DetectionMethod.METHOD_C to "C",
com.medithings.vesiscan.managers.DetectionMethod.METHOD_D to "D"
)
val colors = listOf(Color(0xFF9C27B0), MlPrimary, Color(0xFFFF5722), Color(0xFF7C3AED))
Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) {
methods.forEachIndexed { idx, (m, label) ->
val sel = selectedMethod == m
Button(
onClick = { selectedMethod = m; com.example.medilightv2android.managers.GreenZoneConstants.detectionMethod = m },
onClick = { selectedMethod = m; com.medithings.vesiscan.managers.GreenZoneConstants.detectionMethod = m },
modifier = Modifier.height(32.dp),
shape = RoundedCornerShape(8.dp),
colors = ButtonDefaults.buttonColors(
@@ -1880,21 +1880,21 @@ fun PiezoMonitoringView(appState: AppState) {
HorizontalDivider(modifier = Modifier.padding(vertical = dividerPadding))
// BV Method (Frustum / V41)
var selectedBvMethod by remember { mutableStateOf(com.example.medilightv2android.managers.GreenZoneConstants.bvMethod) }
var selectedBvMethod by remember { mutableStateOf(com.medithings.vesiscan.managers.GreenZoneConstants.bvMethod) }
Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
Text("BV", fontSize = labelSize, fontWeight = FontWeight.SemiBold)
Spacer(modifier = Modifier.weight(1f))
val bvMethods = listOf(
com.example.medilightv2android.managers.BvMethod.FRUSTUM to "Frustum",
com.example.medilightv2android.managers.BvMethod.V41 to "V41",
com.example.medilightv2android.managers.BvMethod.METHOD_D to "Method D"
com.medithings.vesiscan.managers.BvMethod.FRUSTUM to "Frustum",
com.medithings.vesiscan.managers.BvMethod.V41 to "V41",
com.medithings.vesiscan.managers.BvMethod.METHOD_D to "Method D"
)
val bvColors = listOf(MlPrimary, Color(0xFFFF5722), Color(0xFF7C3AED))
Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) {
bvMethods.forEachIndexed { idx, (m, label) ->
val sel = selectedBvMethod == m
Button(
onClick = { selectedBvMethod = m; com.example.medilightv2android.managers.GreenZoneConstants.bvMethod = m },
onClick = { selectedBvMethod = m; com.medithings.vesiscan.managers.GreenZoneConstants.bvMethod = m },
modifier = Modifier.height(32.dp),
shape = RoundedCornerShape(8.dp),
colors = ButtonDefaults.buttonColors(
@@ -1913,7 +1913,7 @@ fun PiezoMonitoringView(appState: AppState) {
// 2026-07-01: lr_ratio override (algorithm 팀 4830e7c 표준)
var lrFixed by remember {
mutableStateOf(com.example.medilightv2android.managers.GreenZoneConstants.lrRatioOverride != null)
mutableStateOf(com.medithings.vesiscan.managers.GreenZoneConstants.lrRatioOverride != null)
}
Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
Text("lr=1.0 fixed", fontSize = labelSize, fontWeight = FontWeight.SemiBold)
@@ -1921,7 +1921,7 @@ fun PiezoMonitoringView(appState: AppState) {
Switch(checked = lrFixed,
onCheckedChange = {
lrFixed = it
com.example.medilightv2android.managers.GreenZoneConstants.lrRatioOverride =
com.medithings.vesiscan.managers.GreenZoneConstants.lrRatioOverride =
if (it) 1.0 else null
},
colors = SwitchDefaults.colors(checkedTrackColor = MlPrimary))
@@ -1970,7 +1970,7 @@ fun PiezoMonitoringView(appState: AppState) {
LockableSlider(
value = pitchThrVal,
onValueChange = { pitchThrVal = it; GreenZoneConstants.pitchLyingThreshold = it
com.example.medilightv2android.services.PostureTuningPrefs.saveAll(context) },
com.medithings.vesiscan.services.PostureTuningPrefs.saveAll(context) },
valueRange = 10f..70f,
steps = 11,
colors = SliderDefaults.colors(thumbColor = MlPrimary, activeTrackColor = MlPrimary)
@@ -1980,7 +1980,7 @@ fun PiezoMonitoringView(appState: AppState) {
// Wakeup Alarm toggle (Personalization 페이지 와 동일 prefs)
var wakeupEnabled by remember {
mutableStateOf(com.example.medilightv2android.services.WakeupAlarmPrefs.isEnabled(context))
mutableStateOf(com.medithings.vesiscan.services.WakeupAlarmPrefs.isEnabled(context))
}
Row(verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth()) {
@@ -1993,7 +1993,7 @@ fun PiezoMonitoringView(appState: AppState) {
checked = wakeupEnabled,
onCheckedChange = {
wakeupEnabled = it
com.example.medilightv2android.services.WakeupAlarmPrefs.setEnabled(context, it)
com.medithings.vesiscan.services.WakeupAlarmPrefs.setEnabled(context, it)
},
colors = SwitchDefaults.colors(checkedTrackColor = Color(0xFFD32F2F))
)
@@ -2002,7 +2002,7 @@ fun PiezoMonitoringView(appState: AppState) {
// Wakeup 알람 발동을 위한 LYING 유지 시간 (dev 조절 가능)
var lyingDurVal by remember {
mutableStateOf(
com.example.medilightv2android.services.WakeupAlarmPrefs.lyingDurationSec(context).toFloat()
com.medithings.vesiscan.services.WakeupAlarmPrefs.lyingDurationSec(context).toFloat()
)
}
Row(verticalAlignment = Alignment.CenterVertically) {
@@ -2021,7 +2021,7 @@ fun PiezoMonitoringView(appState: AppState) {
onValueChange = {
lyingDurVal = it
val sec = it.toInt()
com.example.medilightv2android.services.WakeupAlarmPrefs.setLyingDurationSec(context, sec)
com.medithings.vesiscan.services.WakeupAlarmPrefs.setLyingDurationSec(context, sec)
wakeupDetector.lyingDurationSec = sec
},
valueRange = 5f..600f, // 5초 ~ 10분
@@ -2046,7 +2046,7 @@ fun PiezoMonitoringView(appState: AppState) {
onValueChange = {
pollIntervalVal = it
GreenZoneConstants.posturePollIntervalMs = (it * 1000).toLong()
com.example.medilightv2android.services.PostureTuningPrefs.saveAll(context)
com.medithings.vesiscan.services.PostureTuningPrefs.saveAll(context)
},
valueRange = 1f..15f, // 1초 ~ 15초
steps = 0,
@@ -2066,7 +2066,7 @@ fun PiezoMonitoringView(appState: AppState) {
}
LockableSlider(value = walkPitchVal, onValueChange = {
walkPitchVal = it; GreenZoneConstants.walkingMinPitchDeg = it
com.example.medilightv2android.services.PostureTuningPrefs.saveAll(context)
com.medithings.vesiscan.services.PostureTuningPrefs.saveAll(context)
}, valueRange = 20f..70f, colors = SliderDefaults.colors(thumbColor = MlPrimary, activeTrackColor = MlPrimary))
var walkStartWmean by remember { mutableStateOf(GreenZoneConstants.walkingStartWmean) }
@@ -2077,7 +2077,7 @@ fun PiezoMonitoringView(appState: AppState) {
}
LockableSlider(value = walkStartWmean, onValueChange = {
walkStartWmean = it; GreenZoneConstants.walkingStartWmean = it
com.example.medilightv2android.services.PostureTuningPrefs.saveAll(context)
com.medithings.vesiscan.services.PostureTuningPrefs.saveAll(context)
}, valueRange = 1f..30f, colors = SliderDefaults.colors(thumbColor = MlPrimary, activeTrackColor = MlPrimary))
var walkStartWstd by remember { mutableStateOf(GreenZoneConstants.walkingStartWstd) }
@@ -2088,7 +2088,7 @@ fun PiezoMonitoringView(appState: AppState) {
}
LockableSlider(value = walkStartWstd, onValueChange = {
walkStartWstd = it; GreenZoneConstants.walkingStartWstd = it
com.example.medilightv2android.services.PostureTuningPrefs.saveAll(context)
com.medithings.vesiscan.services.PostureTuningPrefs.saveAll(context)
}, valueRange = 0.5f..10f, colors = SliderDefaults.colors(thumbColor = MlPrimary, activeTrackColor = MlPrimary))
var walkKeepWmean by remember { mutableStateOf(GreenZoneConstants.walkingKeepWmean) }
@@ -2099,7 +2099,7 @@ fun PiezoMonitoringView(appState: AppState) {
}
LockableSlider(value = walkKeepWmean, onValueChange = {
walkKeepWmean = it; GreenZoneConstants.walkingKeepWmean = it
com.example.medilightv2android.services.PostureTuningPrefs.saveAll(context)
com.medithings.vesiscan.services.PostureTuningPrefs.saveAll(context)
}, valueRange = 0.5f..30f, colors = SliderDefaults.colors(thumbColor = MlPrimary, activeTrackColor = MlPrimary))
// Walking 시간 변수 (sec)
@@ -2111,7 +2111,7 @@ fun PiezoMonitoringView(appState: AppState) {
}
LockableSlider(value = walkStartHold, onValueChange = {
walkStartHold = it; GreenZoneConstants.walkingStartHoldMs = (it * 1000).toLong()
com.example.medilightv2android.services.PostureTuningPrefs.saveAll(context)
com.medithings.vesiscan.services.PostureTuningPrefs.saveAll(context)
}, valueRange = 0.5f..10f, colors = SliderDefaults.colors(thumbColor = MlPrimary, activeTrackColor = MlPrimary))
var walkKeepHold by remember { mutableStateOf(GreenZoneConstants.walkingKeepHoldMs / 1000f) }
@@ -2122,7 +2122,7 @@ fun PiezoMonitoringView(appState: AppState) {
}
LockableSlider(value = walkKeepHold, onValueChange = {
walkKeepHold = it; GreenZoneConstants.walkingKeepHoldMs = (it * 1000).toLong()
com.example.medilightv2android.services.PostureTuningPrefs.saveAll(context)
com.medithings.vesiscan.services.PostureTuningPrefs.saveAll(context)
}, valueRange = 0.5f..10f, colors = SliderDefaults.colors(thumbColor = MlPrimary, activeTrackColor = MlPrimary))
var walkWindow by remember { mutableStateOf(GreenZoneConstants.walkingWindowMs / 1000f) }
@@ -2133,7 +2133,7 @@ fun PiezoMonitoringView(appState: AppState) {
}
LockableSlider(value = walkWindow, onValueChange = {
walkWindow = it; GreenZoneConstants.walkingWindowMs = (it * 1000).toLong()
com.example.medilightv2android.services.PostureTuningPrefs.saveAll(context)
com.medithings.vesiscan.services.PostureTuningPrefs.saveAll(context)
}, valueRange = 0.5f..5f, colors = SliderDefaults.colors(thumbColor = MlPrimary, activeTrackColor = MlPrimary))
HorizontalDivider(modifier = Modifier.padding(vertical = dividerPadding))
@@ -2149,7 +2149,7 @@ fun PiezoMonitoringView(appState: AppState) {
}
LockableSlider(value = accMagMin, onValueChange = {
accMagMin = it; GreenZoneConstants.postureAccMagMin = it
com.example.medilightv2android.services.PostureTuningPrefs.saveAll(context)
com.medithings.vesiscan.services.PostureTuningPrefs.saveAll(context)
}, valueRange = 0.3f..1.0f, colors = SliderDefaults.colors(thumbColor = MlPrimary, activeTrackColor = MlPrimary))
var accMagMax by remember { mutableStateOf(GreenZoneConstants.postureAccMagMax) }
@@ -2160,7 +2160,7 @@ fun PiezoMonitoringView(appState: AppState) {
}
LockableSlider(value = accMagMax, onValueChange = {
accMagMax = it; GreenZoneConstants.postureAccMagMax = it
com.example.medilightv2android.services.PostureTuningPrefs.saveAll(context)
com.medithings.vesiscan.services.PostureTuningPrefs.saveAll(context)
}, valueRange = 1.0f..3.0f, colors = SliderDefaults.colors(thumbColor = MlPrimary, activeTrackColor = MlPrimary))
HorizontalDivider(modifier = Modifier.padding(vertical = dividerPadding))
@@ -2168,7 +2168,7 @@ fun PiezoMonitoringView(appState: AppState) {
// Reset to defaults — 모든 threshold 를 default 로 복원 + prefs 저장.
OutlinedButton(
onClick = {
com.example.medilightv2android.services.PostureTuningPrefs.resetAll(context)
com.medithings.vesiscan.services.PostureTuningPrefs.resetAll(context)
android.widget.Toast.makeText(context,
"Defaults restored — close & reopen Settings to refresh sliders",
android.widget.Toast.LENGTH_LONG).show()
@@ -2201,7 +2201,7 @@ fun PiezoMonitoringView(appState: AppState) {
for ((tag, label) in annotateLabels) {
Button(
onClick = {
com.example.medilightv2android.services.PostureEventLogger.annotate(tag)
com.medithings.vesiscan.services.PostureEventLogger.annotate(tag)
android.widget.Toast.makeText(context,
"Marked: $label", android.widget.Toast.LENGTH_SHORT).show()
},
@@ -2281,7 +2281,7 @@ fun PiezoMonitoringView(appState: AppState) {
Text(stringResource(R.string.piezo_settings_app), fontSize = 13.sp, color = MlSecondaryText)
Spacer(modifier = Modifier.weight(1f))
Text(
com.example.medilightv2android.BuildConfig.VERSION_NAME,
com.medithings.vesiscan.BuildConfig.VERSION_NAME,
fontSize = 13.sp,
fontWeight = FontWeight.SemiBold,
color = MlPrimary,
@@ -2378,7 +2378,7 @@ fun PiezoMonitoringView(appState: AppState) {
Button(onClick = {
showRealignPrompt = false
autoFailCount = 0
appState.currentScreen = com.example.medilightv2android.AppScreen.PLACEMENT_GUIDE
appState.currentScreen = com.medithings.vesiscan.AppScreen.PLACEMENT_GUIDE
}) { Text(stringResource(R.string.piezo_realign_button)) }
},
dismissButton = {
@@ -2443,11 +2443,11 @@ private fun ChannelPanel(
}
} else {
// Zone indicator
val curMethod = com.example.medilightv2android.managers.GreenZoneConstants.detectionMethod
val analyzerAPanel = com.example.medilightv2android.managers.PiezoEchoAnalyzerA.shared
val curMethod = com.medithings.vesiscan.managers.GreenZoneConstants.detectionMethod
val analyzerAPanel = com.medithings.vesiscan.managers.PiezoEchoAnalyzerA.shared
val analyses = lastMultiChannelResult.map { chData ->
when (curMethod) {
com.example.medilightv2android.managers.DetectionMethod.METHOD_A -> analyzerAPanel.analyzeChannel(chData.buffer, chData.channel)
com.medithings.vesiscan.managers.DetectionMethod.METHOD_A -> analyzerAPanel.analyzeChannel(chData.buffer, chData.channel)
else -> analyzer.analyzeChannel(chData.buffer, chData.channel)
}
}
@@ -2509,7 +2509,7 @@ private fun ChannelPanel(
// Per-channel waveforms
for (chData in lastMultiChannelResult.filter { it.buffer.isNotEmpty() }) {
val chAnalysis = when (curMethod) {
com.example.medilightv2android.managers.DetectionMethod.METHOD_A -> analyzerAPanel.analyzeChannel(chData.buffer, chData.channel)
com.medithings.vesiscan.managers.DetectionMethod.METHOD_A -> analyzerAPanel.analyzeChannel(chData.buffer, chData.channel)
else -> analyzer.analyzeChannel(chData.buffer, chData.channel)
}
@@ -2537,8 +2537,8 @@ private fun ChannelPanel(
fontSize = 10.sp, fontWeight = FontWeight.SemiBold, color = Color.Cyan
)
Spacer(modifier = Modifier.width(4.dp))
val dps = com.example.medilightv2android.managers.PiezoHW.distancePerSample
val offset = com.example.medilightv2android.managers.PiezoHW.delayOffsetMm
val dps = com.medithings.vesiscan.managers.PiezoHW.distancePerSample
val offset = com.medithings.vesiscan.managers.PiezoHW.delayOffsetMm
Text(
"(${"%.0f".format(offset + antIdx * dps)}–${"%.0f".format(offset + postIdx * dps)}mm)",
fontSize = 9.sp, color = MlSecondaryText
@@ -2804,13 +2804,13 @@ private fun BleDebugPanel(bleManager: BleManager) {
) {
for (entry in entries) {
val color = when (entry.level) {
com.example.medilightv2android.services.BleDebugLogger.Level.TX -> Color(0xFF64B5F6)
com.example.medilightv2android.services.BleDebugLogger.Level.RX -> Color(0xFF81C784)
com.example.medilightv2android.services.BleDebugLogger.Level.ERROR -> Color.Red
com.example.medilightv2android.services.BleDebugLogger.Level.WARN -> Color(0xFFFFB74D)
com.example.medilightv2android.services.BleDebugLogger.Level.RSSI -> Color(0xFFFFD54F)
com.example.medilightv2android.services.BleDebugLogger.Level.CONN -> Color(0xFFCE93D8)
com.example.medilightv2android.services.BleDebugLogger.Level.INFO -> Color.White
com.medithings.vesiscan.services.BleDebugLogger.Level.TX -> Color(0xFF64B5F6)
com.medithings.vesiscan.services.BleDebugLogger.Level.RX -> Color(0xFF81C784)
com.medithings.vesiscan.services.BleDebugLogger.Level.ERROR -> Color.Red
com.medithings.vesiscan.services.BleDebugLogger.Level.WARN -> Color(0xFFFFB74D)
com.medithings.vesiscan.services.BleDebugLogger.Level.RSSI -> Color(0xFFFFD54F)
com.medithings.vesiscan.services.BleDebugLogger.Level.CONN -> Color(0xFFCE93D8)
com.medithings.vesiscan.services.BleDebugLogger.Level.INFO -> Color.White
}
Text(
entry.toLogLine(),
@@ -1,4 +1,4 @@
package com.example.medilightv2android.ui.views.monitoring
package com.medithings.vesiscan.ui.views.monitoring
import android.util.Log
import androidx.compose.animation.core.animateFloatAsState
@@ -44,17 +44,17 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.medilightv2android.AppScreen
import com.example.medilightv2android.AppState
import com.example.medilightv2android.R
import com.example.medilightv2android.ble.BleManager
import com.example.medilightv2android.ble.PiezoChannelData
import com.example.medilightv2android.managers.DetachmentDetection
import com.example.medilightv2android.managers.PiezoEchoAnalyzer
import com.example.medilightv2android.managers.PiezoHW
import com.example.medilightv2android.managers.UrinAI
import com.example.medilightv2android.services.MeasurementLogService
import com.example.medilightv2android.ui.theme.*
import com.medithings.vesiscan.AppScreen
import com.medithings.vesiscan.AppState
import com.medithings.vesiscan.R
import com.medithings.vesiscan.ble.BleManager
import com.medithings.vesiscan.ble.PiezoChannelData
import com.medithings.vesiscan.managers.DetachmentDetection
import com.medithings.vesiscan.managers.PiezoEchoAnalyzer
import com.medithings.vesiscan.managers.PiezoHW
import com.medithings.vesiscan.managers.UrinAI
import com.medithings.vesiscan.services.MeasurementLogService
import com.medithings.vesiscan.ui.theme.*
/**
* Sensor Alignment Guide -- shown after BLE connection, before measurement.
@@ -80,8 +80,8 @@ fun PlacementGuideView(appState: AppState) {
var phaseHoldHint by remember { mutableStateOf("") } // 강제 힌트
val channelDetected = remember { mutableStateListOf(false, false, false, false, false, false) }
val channelUrineLen = remember { mutableStateListOf(0, 0, 0, 0, 0, 0) }
val isSweepMode = com.example.medilightv2android.managers.GreenZoneConstants.placementGuideMode ==
com.example.medilightv2android.managers.PlacementGuideMode.SWEEP
val isSweepMode = com.medithings.vesiscan.managers.GreenZoneConstants.placementGuideMode ==
com.medithings.vesiscan.managers.PlacementGuideMode.SWEEP
var directionHint by remember { mutableStateOf(
if (isSweepMode) context.getString(R.string.placement_sweep_initial)
else context.getString(R.string.placement_initial_hint)
@@ -96,16 +96,16 @@ fun PlacementGuideView(appState: AppState) {
var greenFailCount by remember { mutableIntStateOf(0) } // GREEN 이탈 디바운스
var lastChannelData by remember { mutableStateOf<List<PiezoChannelData>>(emptyList()) }
// 최근 IMU 샘플 (15개, ODR 50Hz, ~300ms 분량). 자세 판정/흔들림 감지/임상 로그용.
var lastImuSamples by remember { mutableStateOf<List<com.example.medilightv2android.ble.ImuSample>>(emptyList()) }
var lastImuSamples by remember { mutableStateOf<List<com.medithings.vesiscan.ble.ImuSample>>(emptyList()) }
var lastWallResults by remember { mutableStateOf<List<Pair<Int, Int>?>>(List(6) { null }) }
var debounceCount by remember { mutableIntStateOf(0) }
var lastGuidePhase by remember { mutableStateOf(PlacementPhase.VERTICAL) }
var lastHintChangeMs by remember { mutableStateOf(0L) }
// V2 alignment (alignment.py 포팅) — dev 토글로 활성화.
val v2Aligner = remember { com.example.medilightv2android.managers.RollingAligner() }
val v2Aligner = remember { com.medithings.vesiscan.managers.RollingAligner() }
// SWEEP 모드용 sphere-fit state
val rTracker = remember { com.example.medilightv2android.managers.BladderSeekRTracker() }
val rTracker = remember { com.medithings.vesiscan.managers.BladderSeekRTracker() }
var currentRMm by remember { mutableStateOf<Double?>(null) }
var bestRMm by remember { mutableStateOf(0.0) }
var sagittalArgMaxCh by remember { mutableStateOf<Int?>(null) }
@@ -127,13 +127,13 @@ fun PlacementGuideView(appState: AppState) {
// V2 (4-stage Method D) 는 R&D/clinical alignment session 전용.
// 일반 사용자 flow (Home → Start → Personalization → PlacementGuide) 는 V1 사용.
LaunchedEffect(Unit) {
val sess = com.example.medilightv2android.services.ClinicalSessionStore.currentSession
val sess = com.medithings.vesiscan.services.ClinicalSessionStore.currentSession
val isClinicalAlign = !appState.placementFromMonitoring &&
sess != null &&
sess.mode == com.example.medilightv2android.models.SessionMode.ALIGNMENT
com.example.medilightv2android.managers.GreenZoneConstants.alignmentAlgo =
if (isClinicalAlign) com.example.medilightv2android.managers.AlignmentAlgo.V2
else com.example.medilightv2android.managers.AlignmentAlgo.V1
sess.mode == com.medithings.vesiscan.models.SessionMode.ALIGNMENT
com.medithings.vesiscan.managers.GreenZoneConstants.alignmentAlgo =
if (isClinicalAlign) com.medithings.vesiscan.managers.AlignmentAlgo.V2
else com.medithings.vesiscan.managers.AlignmentAlgo.V1
v2Aligner.reset()
// Clinical alignment session 진입 시 — 자동으로 scan 시작 (Step 0 baseline 캡쳐).
// waitingForStart 는 true 유지 — 사용자는 여전히 Start 눌러 알고리즘 시작 결정.
@@ -256,10 +256,10 @@ fun PlacementGuideView(appState: AppState) {
// Clinical alignment session 진입한 경우 — 저장 후 Clinical Home 으로.
// (PiezoMonitoring / Personalization 으로 떨어지지 않게)
// Back / Skip 도 endMeasurement → measurement.json + labdb 자동 업로드 (Complete 와 동일).
val curSess = com.example.medilightv2android.services.ClinicalSessionStore.currentSession
if (curSess != null && curSess.mode == com.example.medilightv2android.models.SessionMode.ALIGNMENT) {
com.example.medilightv2android.services.ClinicalSessionStore.endMeasurement(context)
com.example.medilightv2android.services.ClinicalSessionStore.inClinicalFlow = true
val curSess = com.medithings.vesiscan.services.ClinicalSessionStore.currentSession
if (curSess != null && curSess.mode == com.medithings.vesiscan.models.SessionMode.ALIGNMENT) {
com.medithings.vesiscan.services.ClinicalSessionStore.endMeasurement(context)
com.medithings.vesiscan.services.ClinicalSessionStore.inClinicalFlow = true
appState.currentScreen = AppScreen.CLINICAL_HOME
} else {
appState.backFromPlacementGuide()
@@ -279,10 +279,10 @@ fun PlacementGuideView(appState: AppState) {
// Skip — top bar 우측 (유일한 Skip 진입점)
TextButton(onClick = {
isContinuousMode = false
val curSess = com.example.medilightv2android.services.ClinicalSessionStore.currentSession
if (curSess != null && curSess.mode == com.example.medilightv2android.models.SessionMode.ALIGNMENT) {
com.example.medilightv2android.services.ClinicalSessionStore.endMeasurement(context)
com.example.medilightv2android.services.ClinicalSessionStore.inClinicalFlow = true
val curSess = com.medithings.vesiscan.services.ClinicalSessionStore.currentSession
if (curSess != null && curSess.mode == com.medithings.vesiscan.models.SessionMode.ALIGNMENT) {
com.medithings.vesiscan.services.ClinicalSessionStore.endMeasurement(context)
com.medithings.vesiscan.services.ClinicalSessionStore.inClinicalFlow = true
appState.currentScreen = AppScreen.CLINICAL_HOME
} else {
appState.placementComplete()
@@ -311,9 +311,9 @@ fun PlacementGuideView(appState: AppState) {
}
// 펌웨어 구버전 경고
com.example.medilightv2android.ui.components.FirmwareWarningBanner(bleManager)
com.medithings.vesiscan.ui.components.FirmwareWarningBanner(bleManager)
// 배터리 잔량 경고 (<15%)
com.example.medilightv2android.ui.components.BatteryWarningBanner(bleManager)
com.medithings.vesiscan.ui.components.BatteryWarningBanner(bleManager)
Box(modifier = Modifier.fillMaxSize()) {
Column(
@@ -435,7 +435,7 @@ fun PlacementGuideView(appState: AppState) {
) {
// 1) 하복부 일러스트 (배경 — 박스 가득)
Image(
painter = painterResource(id = com.example.medilightv2android.R.drawable.body_silhouette),
painter = painterResource(id = com.medithings.vesiscan.R.drawable.body_silhouette),
contentDescription = stringResource(R.string.placement_body_desc),
modifier = Modifier.matchParentSize(),
contentScale = ContentScale.FillBounds
@@ -444,14 +444,14 @@ fun PlacementGuideView(appState: AppState) {
val sensorBitmap = remember {
android.graphics.BitmapFactory.decodeResource(
context.resources,
com.example.medilightv2android.R.drawable.sensor_device,
com.medithings.vesiscan.R.drawable.sensor_device,
).asImageBitmap()
}
// 방향 화살표 PNG (132x112, 우향). 회전 + tint 로 4방향 + 색감 통일.
val arrowBitmap = remember {
android.graphics.BitmapFactory.decodeResource(
context.resources,
com.example.medilightv2android.R.drawable.arrow_transparent,
com.medithings.vesiscan.R.drawable.arrow_transparent,
).asImageBitmap()
}
Canvas(modifier = Modifier.fillMaxSize()) {
@@ -575,17 +575,17 @@ fun PlacementGuideView(appState: AppState) {
) {
// V2 활성 시 5-stage step indicator, 아니면 V1 의 3-stage.
// 2026-07-01: FINAL_CONFIRM phase 추가 (feature/tab-navigation 동기화).
val isV2Active = com.example.medilightv2android.managers.GreenZoneConstants.alignmentAlgo ==
com.example.medilightv2android.managers.AlignmentAlgo.V2
val isV2Active = com.medithings.vesiscan.managers.GreenZoneConstants.alignmentAlgo ==
com.medithings.vesiscan.managers.AlignmentAlgo.V2
// 2026-07-02: CENTER_OPTIMIZE 추가 (5-stage → 6-stage)
val (currentStepIdx, totalSteps) = if (isV2Active) {
val idx = when (v2Aligner.phase) {
com.example.medilightv2android.managers.AlignGuide4Stage.Phase4.INITIAL_ACCUM -> 0
com.example.medilightv2android.managers.AlignGuide4Stage.Phase4.VERTICAL_CLIMB -> 1
com.example.medilightv2android.managers.AlignGuide4Stage.Phase4.CH3_STABILIZE -> 2
com.example.medilightv2android.managers.AlignGuide4Stage.Phase4.CENTER_OPTIMIZE -> 3
com.example.medilightv2android.managers.AlignGuide4Stage.Phase4.LR_BALANCE -> 4
com.example.medilightv2android.managers.AlignGuide4Stage.Phase4.FINAL_CONFIRM -> 5
com.medithings.vesiscan.managers.AlignGuide4Stage.Phase4.INITIAL_ACCUM -> 0
com.medithings.vesiscan.managers.AlignGuide4Stage.Phase4.VERTICAL_CLIMB -> 1
com.medithings.vesiscan.managers.AlignGuide4Stage.Phase4.CH3_STABILIZE -> 2
com.medithings.vesiscan.managers.AlignGuide4Stage.Phase4.CENTER_OPTIMIZE -> 3
com.medithings.vesiscan.managers.AlignGuide4Stage.Phase4.LR_BALANCE -> 4
com.medithings.vesiscan.managers.AlignGuide4Stage.Phase4.FINAL_CONFIRM -> 5
}
idx to 6
} else {
@@ -634,13 +634,13 @@ fun PlacementGuideView(appState: AppState) {
}
if (appState.isDevMode && lastImuSamples.isNotEmpty()) {
val posture = remember(lastImuSamples) {
com.example.medilightv2android.managers.ImuAnalysis.estimatePosture(lastImuSamples)
com.medithings.vesiscan.managers.ImuAnalysis.estimatePosture(lastImuSamples)
}
val shake = remember(lastImuSamples) {
com.example.medilightv2android.managers.ImuAnalysis.shakeRmsDps(lastImuSamples)
com.medithings.vesiscan.managers.ImuAnalysis.shakeRmsDps(lastImuSamples)
}
val tilt = remember(lastImuSamples) {
com.example.medilightv2android.managers.ImuAnalysis.tiltDegrees(lastImuSamples)
com.medithings.vesiscan.managers.ImuAnalysis.tiltDegrees(lastImuSamples)
}
Text(
"IMU: $posture · tilt=${"%.0f".format(tilt)}° · shake=${"%.1f".format(shake)}dps",
@@ -720,7 +720,7 @@ fun PlacementGuideView(appState: AppState) {
val detected = if (ch < channelDetected.size) channelDetected[ch] else false
val wallPair = if (ch < lastWallResults.size) lastWallResults[ch] else null
val showWalls = if (wallPair != null) {
com.example.medilightv2android.managers.LowEchoResult(
com.medithings.vesiscan.managers.LowEchoResult(
ant = wallPair.first, post = wallPair.second,
lowStart = wallPair.first, lowEnd = wallPair.second,
lowMean = 0.0, urineLen = wallPair.second - wallPair.first,
@@ -746,8 +746,8 @@ fun PlacementGuideView(appState: AppState) {
if (chData != null && chData.buffer.isNotEmpty()) {
// V2 활성 시 RollingAligner 의 10-cycle 평균 신호로 표시 →
// 알고리즘 입력 신호와 그래프 일치 (실제 측정 시 보는 화면).
val isV2 = com.example.medilightv2android.managers.GreenZoneConstants.alignmentAlgo ==
com.example.medilightv2android.managers.AlignmentAlgo.V2
val isV2 = com.medithings.vesiscan.managers.GreenZoneConstants.alignmentAlgo ==
com.medithings.vesiscan.managers.AlignmentAlgo.V2
val avgRaw = if (isV2) v2Aligner.lastAvg?.getOrNull(ch) else null
PlacementWaveformChart(
buffer = chData.buffer,
@@ -806,26 +806,26 @@ fun PlacementGuideView(appState: AppState) {
val MIN_URINE_LEN_PLACEMENT = 12
val method = com.example.medilightv2android.managers.GreenZoneConstants.detectionMethod
val analyzerA = com.example.medilightv2android.managers.PiezoEchoAnalyzerA.shared
val method = com.medithings.vesiscan.managers.GreenZoneConstants.detectionMethod
val analyzerA = com.medithings.vesiscan.managers.PiezoEchoAnalyzerA.shared
for (ch in channels) {
if (ch.buffer.isNotEmpty() && ch.channel < 6) {
when (method) {
com.example.medilightv2android.managers.DetectionMethod.METHOD_C -> {
val v41 = com.example.medilightv2android.walldetect.V41Detector()
com.medithings.vesiscan.managers.DetectionMethod.METHOD_C -> {
val v41 = com.medithings.vesiscan.walldetect.V41Detector()
val adcSingle = listOf(ch.buffer.map { it.toInt() })
val probe = com.example.medilightv2android.walldetect.dto.ProbeProfileDto(
name = com.example.medilightv2android.managers.PiezoHW.presetName,
val probe = com.medithings.vesiscan.walldetect.dto.ProbeProfileDto(
name = com.medithings.vesiscan.managers.PiezoHW.presetName,
rMm = 50.2, anteriorAnchorMm = 32.0,
fsHz = 537500, cMmPerUs = 1.54,
anglesDeg = com.example.medilightv2android.managers.PiezoHW.degreeAll.toList()
anglesDeg = com.medithings.vesiscan.managers.PiezoHW.degreeAll.toList()
)
// V4.1은 6ch 입력 필요 — 현재 채널만 해당 위치에, 나머지 빈 값
val adcMatrix = (0 until 6).map { i ->
if (i == ch.channel) ch.buffer.map { it.toInt() } else List(100) { 0 }
}
val input = com.example.medilightv2android.walldetect.dto.SweepInput(
val input = com.medithings.vesiscan.walldetect.dto.SweepInput(
requestId = "placement", timestampMs = System.currentTimeMillis(),
deviceId = "", sweepSeq = 0, probe = probe, adc = adcMatrix
)
@@ -841,7 +841,7 @@ fun PlacementGuideView(appState: AppState) {
}
Log.d("PlacementGuide", "CH${ch.channel}[C] ant=${chResult?.antIdx} post=${chResult?.postIdx}")
}
com.example.medilightv2android.managers.DetectionMethod.METHOD_A -> {
com.medithings.vesiscan.managers.DetectionMethod.METHOD_A -> {
val analysis = analyzerA.analyzeChannel(ch.buffer, ch.channel)
Log.d("PlacementGuide", "CH${ch.channel}[A] valid=${analysis.isValid} urine=${analysis.result?.urineLen ?: 0}")
if (analysis.isValid && analysis.result != null && analysis.result.urineLen >= MIN_URINE_LEN_PLACEMENT) {
@@ -864,7 +864,7 @@ fun PlacementGuideView(appState: AppState) {
}
// postMaxIdx 필터: 후벽이 이 샘플 이상이면 바닥반사로 판단 → 제거
val postMax = com.example.medilightv2android.managers.GreenZoneConstants.postMaxIdx
val postMax = com.medithings.vesiscan.managers.GreenZoneConstants.postMaxIdx
for (ch in 0..5) {
val w = allWalls[ch]
if (w != null && w.second > postMax) {
@@ -903,8 +903,8 @@ fun PlacementGuideView(appState: AppState) {
bleManager.debugLogger.warn("DETACHED mean=${"%.1f".format(detachResult.meanAbs)} std=${"%.1f".format(detachResult.stdVal)}")
Log.w("PlacementGuide", "DETACHED mean=${
"%.1f".format(detachResult.meanAbs)} std=${"%.1f".format(detachResult.stdVal)}")
} else if (com.example.medilightv2android.managers.GreenZoneConstants.alignmentAlgo ==
com.example.medilightv2android.managers.AlignmentAlgo.V2) {
} else if (com.medithings.vesiscan.managers.GreenZoneConstants.alignmentAlgo ==
com.medithings.vesiscan.managers.AlignmentAlgo.V2) {
// ═══ V2 alignment (alignment.py 포팅, method_d 기반) ═══
// Clinical alignment session + waitingForStart 일 땐 알고리즘 skip —
// baseline cycles 만 누적 (Start 누르기 전 자동 phase 진행 차단).
@@ -941,20 +941,20 @@ fun PlacementGuideView(appState: AppState) {
directionHint = localizedMsg
// STOP 액션은 두 경우 — CH3_STABILIZE 의 "위치 유지" (still in progress) vs
// LR_BALANCE 의 "정렬 완료" (truly done). 후자만 finalStop 으로 GREEN lock.
val finalStop = st.action == com.example.medilightv2android.managers.AlignAction.STOP &&
v2Aligner.phase == com.example.medilightv2android.managers.AlignGuide4Stage.Phase4.LR_BALANCE
val finalStop = st.action == com.medithings.vesiscan.managers.AlignAction.STOP &&
v2Aligner.phase == com.medithings.vesiscan.managers.AlignGuide4Stage.Phase4.LR_BALANCE
directionIcon = when (st.action) {
com.example.medilightv2android.managers.AlignAction.MOVE_UP -> "arrow.up"
com.example.medilightv2android.managers.AlignAction.MOVE_DOWN -> "arrow.down"
com.example.medilightv2android.managers.AlignAction.MOVE_LEFT -> "arrow.left"
com.example.medilightv2android.managers.AlignAction.MOVE_RIGHT -> "arrow.right"
com.example.medilightv2android.managers.AlignAction.PROBE_LR -> "" // ↔ — 한쪽 화살표 안 어울림. msg 의 ↔ 만 표시
com.example.medilightv2android.managers.AlignAction.STOP -> if (finalStop) "checkmark.circle" else ""
com.medithings.vesiscan.managers.AlignAction.MOVE_UP -> "arrow.up"
com.medithings.vesiscan.managers.AlignAction.MOVE_DOWN -> "arrow.down"
com.medithings.vesiscan.managers.AlignAction.MOVE_LEFT -> "arrow.left"
com.medithings.vesiscan.managers.AlignAction.MOVE_RIGHT -> "arrow.right"
com.medithings.vesiscan.managers.AlignAction.PROBE_LR -> "" // ↔ — 한쪽 화살표 안 어울림. msg 의 ↔ 만 표시
com.medithings.vesiscan.managers.AlignAction.STOP -> if (finalStop) "checkmark.circle" else ""
null -> ""
}
placementScore = when {
finalStop -> 100
st.action == com.example.medilightv2android.managers.AlignAction.STOP -> 60 // CH3_STABILIZE 유지 중
st.action == com.medithings.vesiscan.managers.AlignAction.STOP -> 60 // CH3_STABILIZE 유지 중
else -> 40
}
if (finalStop) {
@@ -970,14 +970,14 @@ fun PlacementGuideView(appState: AppState) {
} // end of !waitingForStart V2 algorithm
} else {
// Sphere-fit 계산 (SWEEP 모드에서 사용, 다른 모드에서는 무시됨)
val fitResult = com.example.medilightv2android.managers.BladderSphereSeek
val fitResult = com.medithings.vesiscan.managers.BladderSphereSeek
.fitSagittalCircle(allWalls, detected)
val rStatus = rTracker.submit(fitResult?.radiusMm)
currentRMm = rStatus.currentR
bestRMm = rStatus.bestR
sagittalArgMaxCh = com.example.medilightv2android.managers.BladderSphereSeek
sagittalArgMaxCh = com.medithings.vesiscan.managers.BladderSphereSeek
.sagittalArgMaxCh(allWalls, detected)
val coronalResult = com.example.medilightv2android.managers.BladderSphereSeek
val coronalResult = com.medithings.vesiscan.managers.BladderSphereSeek
.computeCoronalLateral(allWalls, detected)
lateralOffsetMm = coronalResult?.lateralOffsetMm
@@ -1112,11 +1112,11 @@ fun PlacementGuideView(appState: AppState) {
// Phase 라벨 — V2 활성이면 V2 4-stage phase, 아니면 V1 placementPhase.
// Step 0 (Start 전 baseline) 은 alignment session 에서만 명시.
val isV2 = com.example.medilightv2android.managers.GreenZoneConstants.alignmentAlgo ==
com.example.medilightv2android.managers.AlignmentAlgo.V2
val clinSess = com.example.medilightv2android.services.ClinicalSessionStore.currentSession
val isV2 = com.medithings.vesiscan.managers.GreenZoneConstants.alignmentAlgo ==
com.medithings.vesiscan.managers.AlignmentAlgo.V2
val clinSess = com.medithings.vesiscan.services.ClinicalSessionStore.currentSession
val isClinAlign = clinSess != null &&
clinSess.mode == com.example.medilightv2android.models.SessionMode.ALIGNMENT
clinSess.mode == com.medithings.vesiscan.models.SessionMode.ALIGNMENT
val phaseLabel = when {
isClinAlign && waitingForStart -> "STEP_0_BASELINE"
isV2 -> v2Aligner.phase.name
@@ -1136,10 +1136,10 @@ fun PlacementGuideView(appState: AppState) {
// 임상 모드에서는 raw ADC도 adc.csv에 자동 저장.
if (clinSess != null) {
val rawADC = channels.sortedBy { it.channel }.map { it.buffer }
com.example.medilightv2android.services.AdcCsvLogger.log(rawADC, null)
com.medithings.vesiscan.services.AdcCsvLogger.log(rawADC, null)
// Alignment session 진행 중이면 measurement.json 용 frame 누적.
if (clinSess.mode == com.example.medilightv2android.models.SessionMode.ALIGNMENT) {
com.example.medilightv2android.services.ClinicalSessionStore.addAlignmentFrame(
if (clinSess.mode == com.medithings.vesiscan.models.SessionMode.ALIGNMENT) {
com.medithings.vesiscan.services.ClinicalSessionStore.addAlignmentFrame(
piezo = channels,
imu = lastImuSamples,
// Step 0 baseline / V2 phase / V1 phase 통일된 라벨 사용
@@ -1159,8 +1159,8 @@ fun PlacementGuideView(appState: AppState) {
bleManager.imuCollector.onComplete = { imuSamples ->
handler.post {
lastImuSamples = imuSamples
if (com.example.medilightv2android.services.ClinicalSessionStore.currentSession != null) {
com.example.medilightv2android.services.ImuCsvLogger.log(imuSamples)
if (com.medithings.vesiscan.services.ClinicalSessionStore.currentSession != null) {
com.medithings.vesiscan.services.ImuCsvLogger.log(imuSamples)
}
}
}
@@ -1241,16 +1241,16 @@ fun PlacementGuideView(appState: AppState) {
}
// Start Scanning / Complete Alignment 버튼 — clinical alignment 모드면 후자
val curClinSess = com.example.medilightv2android.services.ClinicalSessionStore.currentSession
val curClinSess = com.medithings.vesiscan.services.ClinicalSessionStore.currentSession
val isClinicalAlign = curClinSess != null &&
curClinSess.mode == com.example.medilightv2android.models.SessionMode.ALIGNMENT
curClinSess.mode == com.medithings.vesiscan.models.SessionMode.ALIGNMENT
if (!waitingForStart) Button(
onClick = {
isContinuousMode = false
if (isClinicalAlign) {
// endMeasurement → measurement.json finalize + labdb 자동 업로드
com.example.medilightv2android.services.ClinicalSessionStore.endMeasurement(context)
com.example.medilightv2android.services.ClinicalSessionStore.inClinicalFlow = true
com.medithings.vesiscan.services.ClinicalSessionStore.endMeasurement(context)
com.medithings.vesiscan.services.ClinicalSessionStore.inClinicalFlow = true
appState.currentScreen = AppScreen.CLINICAL_HOME
} else {
appState.placementComplete()
@@ -1300,21 +1300,21 @@ fun PlacementGuideView(appState: AppState) {
) {
Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
// Method A/B/C
var selectedMethod by remember { mutableStateOf(com.example.medilightv2android.managers.GreenZoneConstants.detectionMethod) }
var selectedMethod by remember { mutableStateOf(com.medithings.vesiscan.managers.GreenZoneConstants.detectionMethod) }
Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
Text("Detection", fontSize = 14.sp, fontWeight = FontWeight.SemiBold)
Spacer(modifier = Modifier.weight(1f))
val methods = listOf(
com.example.medilightv2android.managers.DetectionMethod.METHOD_A to "A",
com.example.medilightv2android.managers.DetectionMethod.METHOD_B to "B",
com.example.medilightv2android.managers.DetectionMethod.METHOD_C to "C"
com.medithings.vesiscan.managers.DetectionMethod.METHOD_A to "A",
com.medithings.vesiscan.managers.DetectionMethod.METHOD_B to "B",
com.medithings.vesiscan.managers.DetectionMethod.METHOD_C to "C"
)
val colors = listOf(Color(0xFF9C27B0), MlPrimary, Color(0xFFFF5722))
Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) {
methods.forEachIndexed { idx, (m, label) ->
val sel = selectedMethod == m
Button(
onClick = { selectedMethod = m; com.example.medilightv2android.managers.GreenZoneConstants.detectionMethod = m },
onClick = { selectedMethod = m; com.medithings.vesiscan.managers.GreenZoneConstants.detectionMethod = m },
modifier = Modifier.height(32.dp),
shape = RoundedCornerShape(8.dp),
colors = ButtonDefaults.buttonColors(
@@ -1332,14 +1332,14 @@ fun PlacementGuideView(appState: AppState) {
HorizontalDivider()
// Guide mode (Gradient / Boundary / SWEEP)
var selectedGuide by remember { mutableStateOf(com.example.medilightv2android.managers.GreenZoneConstants.placementGuideMode) }
var selectedGuide by remember { mutableStateOf(com.medithings.vesiscan.managers.GreenZoneConstants.placementGuideMode) }
Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
Text("Guide", fontSize = 14.sp, fontWeight = FontWeight.SemiBold)
Spacer(modifier = Modifier.weight(1f))
val guideModes = listOf(
com.example.medilightv2android.managers.PlacementGuideMode.SIMPLE to "Gradient",
com.example.medilightv2android.managers.PlacementGuideMode.BOUNDARY to "Boundary",
com.example.medilightv2android.managers.PlacementGuideMode.SWEEP to "SWEEP"
com.medithings.vesiscan.managers.PlacementGuideMode.SIMPLE to "Gradient",
com.medithings.vesiscan.managers.PlacementGuideMode.BOUNDARY to "Boundary",
com.medithings.vesiscan.managers.PlacementGuideMode.SWEEP to "SWEEP"
)
val guideColors = listOf(MlPrimary, Color(0xFFFF5722), Color(0xFF7C3AED))
Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) {
@@ -1348,7 +1348,7 @@ fun PlacementGuideView(appState: AppState) {
Button(
onClick = {
selectedGuide = mode
com.example.medilightv2android.managers.GreenZoneConstants.placementGuideMode = mode
com.medithings.vesiscan.managers.GreenZoneConstants.placementGuideMode = mode
},
modifier = Modifier.height(32.dp),
shape = RoundedCornerShape(8.dp),
@@ -1368,14 +1368,14 @@ fun PlacementGuideView(appState: AppState) {
// Alignment 알고리즘 V1/V2
var selectedAlgo by remember {
mutableStateOf(com.example.medilightv2android.managers.GreenZoneConstants.alignmentAlgo)
mutableStateOf(com.medithings.vesiscan.managers.GreenZoneConstants.alignmentAlgo)
}
Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
Text("Alignment", fontSize = 14.sp, fontWeight = FontWeight.SemiBold)
Spacer(modifier = Modifier.weight(1f))
val algoModes = listOf(
com.example.medilightv2android.managers.AlignmentAlgo.V1 to "V1 (legacy)",
com.example.medilightv2android.managers.AlignmentAlgo.V2 to "V2 (new)",
com.medithings.vesiscan.managers.AlignmentAlgo.V1 to "V1 (legacy)",
com.medithings.vesiscan.managers.AlignmentAlgo.V2 to "V2 (new)",
)
Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) {
algoModes.forEach { (algo, label) ->
@@ -1383,8 +1383,8 @@ fun PlacementGuideView(appState: AppState) {
Button(
onClick = {
selectedAlgo = algo
com.example.medilightv2android.managers.GreenZoneConstants.alignmentAlgo = algo
if (algo == com.example.medilightv2android.managers.AlignmentAlgo.V2) {
com.medithings.vesiscan.managers.GreenZoneConstants.alignmentAlgo = algo
if (algo == com.medithings.vesiscan.managers.AlignmentAlgo.V2) {
v2Aligner.reset()
}
},
@@ -1410,7 +1410,7 @@ fun PlacementGuideView(appState: AppState) {
Text("Threshold", fontSize = 14.sp, fontWeight = FontWeight.SemiBold)
Spacer(modifier = Modifier.width(8.dp))
Text(
if (otsuEnabled) "Otsu: ${"%.0f".format(analyzer.lastOtsuThreshold)}" else "${"%.0f".format(com.example.medilightv2android.managers.GreenZoneConstants.lowEchoAmp)}",
if (otsuEnabled) "Otsu: ${"%.0f".format(analyzer.lastOtsuThreshold)}" else "${"%.0f".format(com.medithings.vesiscan.managers.GreenZoneConstants.lowEchoAmp)}",
fontSize = 13.sp, fontWeight = FontWeight.Bold,
color = if (otsuEnabled) Color(0xFF4CAF50) else Color(0xFFFF9800)
)
@@ -1426,7 +1426,7 @@ fun PlacementGuideView(appState: AppState) {
HorizontalDivider()
// Post Max Index (바닥반사 무시 경계)
var postMaxVal by remember { mutableStateOf(com.example.medilightv2android.managers.GreenZoneConstants.postMaxIdx.toFloat()) }
var postMaxVal by remember { mutableStateOf(com.medithings.vesiscan.managers.GreenZoneConstants.postMaxIdx.toFloat()) }
Row(verticalAlignment = Alignment.CenterVertically) {
Text("Post Max", fontSize = 14.sp, fontWeight = FontWeight.SemiBold)
Spacer(modifier = Modifier.width(8.dp))
@@ -1434,7 +1434,7 @@ fun PlacementGuideView(appState: AppState) {
}
Slider(
value = postMaxVal,
onValueChange = { postMaxVal = it; com.example.medilightv2android.managers.GreenZoneConstants.postMaxIdx = it.toInt() },
onValueChange = { postMaxVal = it; com.medithings.vesiscan.managers.GreenZoneConstants.postMaxIdx = it.toInt() },
valueRange = 50f..95f,
steps = 8,
colors = SliderDefaults.colors(thumbColor = MlPrimary, activeTrackColor = MlPrimary)
@@ -1651,10 +1651,10 @@ data class PlacementGuideResult(
// CV threshold는 기기 preset에 따라 다름
private val DEFAULT_CV_THR: Double
get() = when (com.example.medilightv2android.managers.PiezoHW.activePreset) {
com.example.medilightv2android.managers.PiezoHW.DevicePreset.V0 -> 0.07
com.example.medilightv2android.managers.PiezoHW.DevicePreset.V1,
com.example.medilightv2android.managers.PiezoHW.DevicePreset.V2 -> 0.10
get() = when (com.medithings.vesiscan.managers.PiezoHW.activePreset) {
com.medithings.vesiscan.managers.PiezoHW.DevicePreset.V0 -> 0.07
com.medithings.vesiscan.managers.PiezoHW.DevicePreset.V1,
com.medithings.vesiscan.managers.PiezoHW.DevicePreset.V2 -> 0.10
else -> 0.08
}
private const val DEFAULT_LR_DEV_THR = 0.20
@@ -1727,10 +1727,10 @@ private fun computePlacementGuide(
// ═══ 1단계: 상하 탐색 ═══
if (currentPhase == PlacementPhase.VERTICAL) {
val guideMode = com.example.medilightv2android.managers.GreenZoneConstants.placementGuideMode
val guideMode = com.medithings.vesiscan.managers.GreenZoneConstants.placementGuideMode
// ═══ SWEEP 모드 (CKLaw): top-down sweep → Ch3 dropout → pull-back ═══
if (guideMode == com.example.medilightv2android.managers.PlacementGuideMode.SWEEP) {
if (guideMode == com.medithings.vesiscan.managers.PlacementGuideMode.SWEEP) {
val vs = verticalState ?: VerticalState()
val prev = vs.lastDetectedCount
vs.lastDetectedCount = centerDetected
@@ -2054,8 +2054,8 @@ private fun computePlacementGuide(
}
// R-peak gate (SWEEP 모드에서만 적용)
val isSweep = com.example.medilightv2android.managers.GreenZoneConstants.placementGuideMode ==
com.example.medilightv2android.managers.PlacementGuideMode.SWEEP
val isSweep = com.medithings.vesiscan.managers.GreenZoneConstants.placementGuideMode ==
com.medithings.vesiscan.managers.PlacementGuideMode.SWEEP
val rPeakOk = !isSweep || currentRMm == null || bestRMm <= 0.0 ||
(bestRMm - currentRMm) <= 1.5
val isPass = cv <= cvThr && lrPass && rPeakOk && edgePass
@@ -2159,7 +2159,7 @@ private fun PlacementWaveformChart(
val denoised = analyzer.denoise(raw)
val maxVal = 2500f
val thrVal = if (analyzer.useAdaptiveThreshold && analyzer.lastOtsuThreshold > 0)
analyzer.lastOtsuThreshold.toFloat() else com.example.medilightv2android.managers.GreenZoneConstants.lowEchoAmp
analyzer.lastOtsuThreshold.toFloat() else com.medithings.vesiscan.managers.GreenZoneConstants.lowEchoAmp
Canvas(modifier = modifier.clip(RoundedCornerShape(4.dp))) {
val w = size.width
@@ -1,4 +1,4 @@
package com.example.medilightv2android.ui.views.monitoring
package com.medithings.vesiscan.ui.views.monitoring
import android.Manifest
import android.content.pm.PackageManager
@@ -35,11 +35,11 @@ import androidx.compose.ui.unit.sp
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.content.ContextCompat
import com.example.medilightv2android.R
import com.example.medilightv2android.measure.SimpleMeasureService
import com.example.medilightv2android.measure.VoidingRecord
import com.example.medilightv2android.measure.VoidingRecordStore
import com.example.medilightv2android.measure.YoloDetector
import com.medithings.vesiscan.R
import com.medithings.vesiscan.measure.SimpleMeasureService
import com.medithings.vesiscan.measure.VoidingRecord
import com.medithings.vesiscan.measure.VoidingRecordStore
import com.medithings.vesiscan.measure.YoloDetector
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@@ -1,4 +1,4 @@
package com.example.medilightv2android.ui.views.monitoring
package com.medithings.vesiscan.ui.views.monitoring
import android.os.Handler
import android.os.Looper
@@ -29,14 +29,14 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.medilightv2android.AppState
import com.example.medilightv2android.R
import com.example.medilightv2android.ble.BleManager
import com.example.medilightv2android.managers.NirsManager
import com.example.medilightv2android.models.NirsMcjPacket
import com.example.medilightv2android.models.NirsOxyResult
import com.example.medilightv2android.ui.components.BladdyIdleRiveView
import com.example.medilightv2android.ui.theme.*
import com.medithings.vesiscan.AppState
import com.medithings.vesiscan.R
import com.medithings.vesiscan.ble.BleManager
import com.medithings.vesiscan.managers.NirsManager
import com.medithings.vesiscan.models.NirsMcjPacket
import com.medithings.vesiscan.models.NirsOxyResult
import com.medithings.vesiscan.ui.components.BladdyIdleRiveView
import com.medithings.vesiscan.ui.theme.*
import kotlin.random.Random
@Composable
@@ -84,7 +84,7 @@ fun VivamyoMonitoringView(appState: AppState) {
if (packet != null) {
nirsManager.processMcjPacket(packet)
} else {
if (com.example.medilightv2android.BuildConfig.DEBUG) {
if (com.medithings.vesiscan.BuildConfig.DEBUG) {
android.util.Log.e("VivamyoMonitor", "NirsMcjPacket.parse returned null! data.size=${data.size}")
}
}
@@ -1,4 +1,4 @@
package com.example.medilightv2android.ui.views.monitoring
package com.medithings.vesiscan.ui.views.monitoring
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
@@ -20,12 +20,12 @@ import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.medilightv2android.R
import com.example.medilightv2android.measure.VoidingRecord
import com.example.medilightv2android.measure.VoidingRecordStore
import com.example.medilightv2android.ui.theme.MlCardBackground
import com.example.medilightv2android.ui.theme.MlPrimary
import com.example.medilightv2android.ui.theme.MlSecondaryText
import com.medithings.vesiscan.R
import com.medithings.vesiscan.measure.VoidingRecord
import com.medithings.vesiscan.measure.VoidingRecordStore
import com.medithings.vesiscan.ui.theme.MlCardBackground
import com.medithings.vesiscan.ui.theme.MlPrimary
import com.medithings.vesiscan.ui.theme.MlSecondaryText
import java.text.SimpleDateFormat
import java.util.*
@@ -1,4 +1,4 @@
package com.example.medilightv2android.ui.views.onboarding
package com.medithings.vesiscan.ui.views.onboarding
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
@@ -16,10 +16,10 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.medilightv2android.AppState
import com.example.medilightv2android.R
import com.example.medilightv2android.ui.components.BladdyIdleRiveView
import com.example.medilightv2android.ui.theme.*
import com.medithings.vesiscan.AppState
import com.medithings.vesiscan.R
import com.medithings.vesiscan.ui.components.BladdyIdleRiveView
import com.medithings.vesiscan.ui.theme.*
@Composable
fun OnboardingView(appState: AppState) {
@@ -1,4 +1,4 @@
package com.example.medilightv2android.ui.views.personalization
package com.medithings.vesiscan.ui.views.personalization
import android.os.Handler
import android.os.Looper
@@ -25,11 +25,11 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.medilightv2android.AppState
import com.example.medilightv2android.R
import com.example.medilightv2android.ble.BleManager
import com.example.medilightv2android.ui.components.BladdyIdleRiveView
import com.example.medilightv2android.ui.theme.*
import com.medithings.vesiscan.AppState
import com.medithings.vesiscan.R
import com.medithings.vesiscan.ble.BleManager
import com.medithings.vesiscan.ui.components.BladdyIdleRiveView
import com.medithings.vesiscan.ui.theme.*
import kotlin.math.roundToInt
/**
@@ -338,7 +338,7 @@ private fun WakeupAlarmCard() {
val context = LocalContext.current
var enabled by androidx.compose.runtime.remember {
androidx.compose.runtime.mutableStateOf(
com.example.medilightv2android.services.WakeupAlarmPrefs.isEnabled(context)
com.medithings.vesiscan.services.WakeupAlarmPrefs.isEnabled(context)
)
}
Card(
@@ -370,7 +370,7 @@ private fun WakeupAlarmCard() {
checked = enabled,
onCheckedChange = {
enabled = it
com.example.medilightv2android.services.WakeupAlarmPrefs.setEnabled(context, it)
com.medithings.vesiscan.services.WakeupAlarmPrefs.setEnabled(context, it)
},
colors = androidx.compose.material3.SwitchDefaults.colors(
checkedTrackColor = Color(0xFFD32F2F)
@@ -1,4 +1,4 @@
package com.example.medilightv2android.ui.views.pin
package com.medithings.vesiscan.ui.views.pin
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
@@ -12,10 +12,10 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.medilightv2android.AppState
import com.example.medilightv2android.services.UserStorage
import com.example.medilightv2android.ui.components.BladdyIdleRiveView
import com.example.medilightv2android.ui.theme.*
import com.medithings.vesiscan.AppState
import com.medithings.vesiscan.services.UserStorage
import com.medithings.vesiscan.ui.components.BladdyIdleRiveView
import com.medithings.vesiscan.ui.theme.*
enum class PinMode { SETUP, ENTER }
@@ -1,4 +1,4 @@
package com.example.medilightv2android.ui.views.pin
package com.medithings.vesiscan.ui.views.pin
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
@@ -20,8 +20,8 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.medilightv2android.R
import com.example.medilightv2android.ui.theme.MlCardBackground
import com.medithings.vesiscan.R
import com.medithings.vesiscan.ui.theme.MlCardBackground
@Composable
fun ShuffledKeypad(
@@ -1,4 +1,4 @@
package com.example.medilightv2android.ui.views.registration
package com.medithings.vesiscan.ui.views.registration
import androidx.compose.foundation.background
import androidx.compose.foundation.border
@@ -18,11 +18,11 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.medilightv2android.AppState
import com.example.medilightv2android.R
import com.example.medilightv2android.models.Sex
import com.example.medilightv2android.ui.components.BladdyIdleRiveView
import com.example.medilightv2android.ui.theme.*
import com.medithings.vesiscan.AppState
import com.medithings.vesiscan.R
import com.medithings.vesiscan.models.Sex
import com.medithings.vesiscan.ui.components.BladdyIdleRiveView
import com.medithings.vesiscan.ui.theme.*
@Composable
fun RegistrationView(appState: AppState) {
@@ -1,4 +1,4 @@
package com.example.medilightv2android.ui.views.reminder
package com.medithings.vesiscan.ui.views.reminder
import android.content.Intent
import android.provider.Settings
@@ -20,10 +20,10 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.medilightv2android.R
import com.example.medilightv2android.services.ReminderService
import com.example.medilightv2android.ui.components.LockableSlider
import com.example.medilightv2android.ui.theme.*
import com.medithings.vesiscan.R
import com.medithings.vesiscan.services.ReminderService
import com.medithings.vesiscan.ui.components.LockableSlider
import com.medithings.vesiscan.ui.theme.*
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@@ -1,4 +1,4 @@
package com.example.medilightv2android.ui.views.sensorselect
package com.medithings.vesiscan.ui.views.sensorselect
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
@@ -22,11 +22,11 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.medilightv2android.AppState
import com.example.medilightv2android.R
import com.example.medilightv2android.models.SensorMode
import com.example.medilightv2android.ui.components.BladdyIdleRiveView
import com.example.medilightv2android.ui.theme.*
import com.medithings.vesiscan.AppState
import com.medithings.vesiscan.R
import com.medithings.vesiscan.models.SensorMode
import com.medithings.vesiscan.ui.components.BladdyIdleRiveView
import com.medithings.vesiscan.ui.theme.*
@Composable
fun SensorSelectView(appState: AppState) {
@@ -15,9 +15,9 @@
* own anatomical gate already drops genuinely-bad detections. This layer
* just surfaces "this channel's reading just jumped, double-check".
*/
package com.example.medilightv2android.walldetect
package com.medithings.vesiscan.walldetect
import com.example.medilightv2android.walldetect.dto.ChannelResult
import com.medithings.vesiscan.walldetect.dto.ChannelResult
import kotlin.math.abs
class DetectionSanity(
@@ -4,7 +4,7 @@
* Project: CharlesKWONsLaw — wall-detect live compare
* All rights reserved.
*/
package com.example.medilightv2android.walldetect
package com.medithings.vesiscan.walldetect
object DetectorIds {
const val V2 = "v2"
@@ -13,14 +13,14 @@
* 인터페이스는 WallDetector 와 무관 — alignment.py 가 channels[i].urine_len 만
* 필요로 하므로 가벼운 단일-함수 API 로 유지.
*/
package com.example.medilightv2android.walldetect
package com.medithings.vesiscan.walldetect
import com.example.medilightv2android.walldetect.algo.methodd.MethodDParams
import com.example.medilightv2android.walldetect.algo.methodd.MethodDPreprocessing
import com.example.medilightv2android.walldetect.algo.methodd.MethodDSpan
import com.example.medilightv2android.walldetect.algo.methodd.MethodDWallSelect
import com.example.medilightv2android.walldetect.algo.methodd.MethodDWallSelect.CType
import com.example.medilightv2android.walldetect.algo.methodd.MethodDWallSelect.Side
import com.medithings.vesiscan.walldetect.algo.methodd.MethodDParams
import com.medithings.vesiscan.walldetect.algo.methodd.MethodDPreprocessing
import com.medithings.vesiscan.walldetect.algo.methodd.MethodDSpan
import com.medithings.vesiscan.walldetect.algo.methodd.MethodDWallSelect
import com.medithings.vesiscan.walldetect.algo.methodd.MethodDWallSelect.CType
import com.medithings.vesiscan.walldetect.algo.methodd.MethodDWallSelect.Side
import kotlin.math.max
data class MethodDResult(
@@ -18,16 +18,16 @@
* 출력 컨트랙트: alignment.py 가 `dets[i].urine_len` 만 의존 →
* MethodDResult.urineLen 또는 null 의 List 로 충분.
*/
package com.example.medilightv2android.walldetect
package com.medithings.vesiscan.walldetect
import com.example.medilightv2android.managers.AlignmentConstants
import com.example.medilightv2android.managers.PiezoHW
import com.example.medilightv2android.walldetect.algo.methodd.MethodDParams
import com.example.medilightv2android.walldetect.algo.methodd.MethodDPreprocessing
import com.example.medilightv2android.walldetect.algo.methodd.MethodDTgc
import com.example.medilightv2android.walldetect.algo.methodd.MethodDWallSelect
import com.example.medilightv2android.walldetect.algo.methodd.MethodDWallSelect.CType
import com.example.medilightv2android.walldetect.algo.methodd.MethodDWallSelect.Side
import com.medithings.vesiscan.managers.AlignmentConstants
import com.medithings.vesiscan.managers.PiezoHW
import com.medithings.vesiscan.walldetect.algo.methodd.MethodDParams
import com.medithings.vesiscan.walldetect.algo.methodd.MethodDPreprocessing
import com.medithings.vesiscan.walldetect.algo.methodd.MethodDTgc
import com.medithings.vesiscan.walldetect.algo.methodd.MethodDWallSelect
import com.medithings.vesiscan.walldetect.algo.methodd.MethodDWallSelect.CType
import com.medithings.vesiscan.walldetect.algo.methodd.MethodDWallSelect.Side
import kotlin.math.abs
import kotlin.math.cos
import kotlin.math.min as kmin
@@ -32,7 +32,7 @@
* The contract: "I either pass the live sample through unchanged,
* or replace it with the temporal median of recent good values."
*/
package com.example.medilightv2android.walldetect
package com.medithings.vesiscan.walldetect
import kotlin.math.abs
@@ -15,15 +15,15 @@
* 6. post > POST_MAX_IDX → back-half retry
* 7. urine_len ≥ 3 required
*/
package com.example.medilightv2android.walldetect
package com.medithings.vesiscan.walldetect
import com.example.medilightv2android.walldetect.algo.DetectLumenFirst
import com.example.medilightv2android.walldetect.algo.Geometry
import com.example.medilightv2android.walldetect.algo.SubsampleRefine
import com.example.medilightv2android.walldetect.dto.ChannelResult
import com.example.medilightv2android.walldetect.dto.DetectionResult
import com.example.medilightv2android.walldetect.dto.DetectionSummary
import com.example.medilightv2android.walldetect.dto.SweepInput
import com.medithings.vesiscan.walldetect.algo.DetectLumenFirst
import com.medithings.vesiscan.walldetect.algo.Geometry
import com.medithings.vesiscan.walldetect.algo.SubsampleRefine
import com.medithings.vesiscan.walldetect.dto.ChannelResult
import com.medithings.vesiscan.walldetect.dto.DetectionResult
import com.medithings.vesiscan.walldetect.dto.DetectionSummary
import com.medithings.vesiscan.walldetect.dto.SweepInput
import kotlin.math.sqrt
class V2Detector : WallDetector {
@@ -98,34 +98,34 @@
* `docs/BV-CALCULATION-DESIGN.md`.
* ═════════════════════════════════════════════════════════════════════════
*/
package com.example.medilightv2android.walldetect
package com.medithings.vesiscan.walldetect
import com.example.medilightv2android.walldetect.algo.AnatomicalGate
import com.example.medilightv2android.walldetect.algo.BModeScore
import com.example.medilightv2android.walldetect.algo.BvEstimation
import com.example.medilightv2android.walldetect.algo.BvFromSphere
import com.example.medilightv2android.walldetect.algo.ChordConsensus
import com.example.medilightv2android.walldetect.algo.Clipping
import com.example.medilightv2android.walldetect.algo.ContrastAux
import com.example.medilightv2android.walldetect.algo.DetectLumenFirst
import com.example.medilightv2android.walldetect.algo.Geometry
import com.example.medilightv2android.walldetect.algo.ImpulseReject
import com.example.medilightv2android.walldetect.algo.PeakDetection
import com.example.medilightv2android.walldetect.algo.SphereFit2Step
import com.example.medilightv2android.walldetect.algo.SubsampleRefine
import com.example.medilightv2android.walldetect.algo.WaveletDenoise
import com.example.medilightv2android.walldetect.core.WdConfig
import com.example.medilightv2android.walldetect.dto.ChannelResult
import com.example.medilightv2android.walldetect.dto.DetectionResult
import com.example.medilightv2android.walldetect.dto.DetectionSummary
import com.example.medilightv2android.walldetect.dto.IntRange2
import com.example.medilightv2android.walldetect.dto.ScoreSubscores
import com.example.medilightv2android.walldetect.dto.SweepInput
import com.example.medilightv2android.walldetect.dto.V41Diagnostics
import com.example.medilightv2android.walldetect.dto.V41SphereFit
import com.example.medilightv2android.walldetect.dto.Vec3
import com.example.medilightv2android.walldetect.dto.WaveletCoefs
import com.example.medilightv2android.walldetect.dto.WaveletEnergyRatio
import com.medithings.vesiscan.walldetect.algo.AnatomicalGate
import com.medithings.vesiscan.walldetect.algo.BModeScore
import com.medithings.vesiscan.walldetect.algo.BvEstimation
import com.medithings.vesiscan.walldetect.algo.BvFromSphere
import com.medithings.vesiscan.walldetect.algo.ChordConsensus
import com.medithings.vesiscan.walldetect.algo.Clipping
import com.medithings.vesiscan.walldetect.algo.ContrastAux
import com.medithings.vesiscan.walldetect.algo.DetectLumenFirst
import com.medithings.vesiscan.walldetect.algo.Geometry
import com.medithings.vesiscan.walldetect.algo.ImpulseReject
import com.medithings.vesiscan.walldetect.algo.PeakDetection
import com.medithings.vesiscan.walldetect.algo.SphereFit2Step
import com.medithings.vesiscan.walldetect.algo.SubsampleRefine
import com.medithings.vesiscan.walldetect.algo.WaveletDenoise
import com.medithings.vesiscan.walldetect.core.WdConfig
import com.medithings.vesiscan.walldetect.dto.ChannelResult
import com.medithings.vesiscan.walldetect.dto.DetectionResult
import com.medithings.vesiscan.walldetect.dto.DetectionSummary
import com.medithings.vesiscan.walldetect.dto.IntRange2
import com.medithings.vesiscan.walldetect.dto.ScoreSubscores
import com.medithings.vesiscan.walldetect.dto.SweepInput
import com.medithings.vesiscan.walldetect.dto.V41Diagnostics
import com.medithings.vesiscan.walldetect.dto.V41SphereFit
import com.medithings.vesiscan.walldetect.dto.Vec3
import com.medithings.vesiscan.walldetect.dto.WaveletCoefs
import com.medithings.vesiscan.walldetect.dto.WaveletEnergyRatio
import kotlin.math.sqrt
class V41Detector(
@@ -23,10 +23,10 @@
* • Thread-safe: a single instance can be called from multiple coroutines
* • `gainDb`: V2 scales LOW_ECHO_AMP by 10^(-gainDb/20); V4.1 ignores it (Q4)
*/
package com.example.medilightv2android.walldetect
package com.medithings.vesiscan.walldetect
import com.example.medilightv2android.walldetect.dto.DetectionResult
import com.example.medilightv2android.walldetect.dto.SweepInput
import com.medithings.vesiscan.walldetect.dto.DetectionResult
import com.medithings.vesiscan.walldetect.dto.SweepInput
interface WallDetector {
@@ -17,10 +17,10 @@
* Beam-angle correction uses Snell-refracted angles (PROBE.DEGREE / DEGREE_LR),
* NOT the mechanical CAD angles (MECH_DEGREE / MECH_DEGREE_LR).
*/
package com.example.medilightv2android.walldetect.algo
package com.medithings.vesiscan.walldetect.algo
import com.example.medilightv2android.walldetect.core.WdConfig
import com.example.medilightv2android.walldetect.core.WdProbe
import com.medithings.vesiscan.walldetect.core.WdConfig
import com.medithings.vesiscan.walldetect.core.WdProbe
import kotlin.math.cos
object AnatomicalGate {

Some files were not shown because too many files have changed in this diff Show More