feat: BLE stability fixes, NIRS streaming improvements, catheter count feature
- Fix BLE API deprecation warnings with API 33+ version checks - Move isServiceReady to onDescriptorWrite for reliable CCCD setup - Fix battery polling: immediate query on connect, pause during NIRS streaming - Add mcj retry mechanism for NIRS streaming reliability - Add debug logging to BLE, NIRS parsing, and processing pipeline - Add catheter count feature to Piezo monitoring (persistent count, add dialog, auto-decrement on catheterize) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@ package com.example.medilightv2android
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import com.example.medilightv2android.ble.BleManager
|
import com.example.medilightv2android.ble.BleManager
|
||||||
@@ -22,6 +23,7 @@ class AppState(private val context: Context) {
|
|||||||
var bladderLevel by mutableStateOf(BladderLevel())
|
var bladderLevel by mutableStateOf(BladderLevel())
|
||||||
var isDeviceConnected by mutableStateOf(false)
|
var isDeviceConnected by mutableStateOf(false)
|
||||||
var piezoSettings by mutableStateOf(PiezoSettings())
|
var piezoSettings by mutableStateOf(PiezoSettings())
|
||||||
|
var catheterCount = mutableIntStateOf(0)
|
||||||
|
|
||||||
private val storage = UserStorage.getInstance(context)
|
private val storage = UserStorage.getInstance(context)
|
||||||
private val historyStore = MeasurementHistoryStore.getInstance(context)
|
private val historyStore = MeasurementHistoryStore.getInstance(context)
|
||||||
@@ -40,6 +42,7 @@ class AppState(private val context: Context) {
|
|||||||
catheterizationThreshold = storage.catheterizationLevel
|
catheterizationThreshold = storage.catheterizationLevel
|
||||||
)
|
)
|
||||||
piezoSettings = storage.piezoSettings
|
piezoSettings = storage.piezoSettings
|
||||||
|
catheterCount.intValue = storage.catheterCount
|
||||||
|
|
||||||
currentScreen = when {
|
currentScreen = when {
|
||||||
!storage.hasCompletedOnboarding -> AppScreen.ONBOARDING
|
!storage.hasCompletedOnboarding -> AppScreen.ONBOARDING
|
||||||
@@ -147,6 +150,11 @@ class AppState(private val context: Context) {
|
|||||||
bladderLevel = newLevel
|
bladderLevel = newLevel
|
||||||
storage.bladderLevel = 0
|
storage.bladderLevel = 0
|
||||||
notificationService.cancelAll()
|
notificationService.cancelAll()
|
||||||
|
|
||||||
|
if (catheterCount.intValue > 0) {
|
||||||
|
catheterCount.intValue--
|
||||||
|
storage.catheterCount = catheterCount.intValue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun showHistory() {
|
fun showHistory() {
|
||||||
@@ -157,6 +165,11 @@ class AppState(private val context: Context) {
|
|||||||
currentScreen = if (sensorMode == SensorMode.NIRS) AppScreen.VIVAMYO_MONITORING else AppScreen.PIEZO_MONITORING
|
currentScreen = if (sensorMode == SensorMode.NIRS) AppScreen.VIVAMYO_MONITORING else AppScreen.PIEZO_MONITORING
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun addCatheterCount(amount: Int = 1) {
|
||||||
|
catheterCount.intValue += amount
|
||||||
|
storage.catheterCount = catheterCount.intValue
|
||||||
|
}
|
||||||
|
|
||||||
fun updateCatheterizationThreshold(threshold: Int) {
|
fun updateCatheterizationThreshold(threshold: Int) {
|
||||||
bladderLevel = bladderLevel.copy(catheterizationThreshold = threshold)
|
bladderLevel = bladderLevel.copy(catheterizationThreshold = threshold)
|
||||||
storage.catheterizationLevel = threshold
|
storage.catheterizationLevel = threshold
|
||||||
|
|||||||
@@ -244,16 +244,28 @@ class BleManager private constructor(private val context: Context) {
|
|||||||
|
|
||||||
// Send
|
// Send
|
||||||
fun sendRaw(data: ByteArray) {
|
fun sendRaw(data: ByteArray) {
|
||||||
val characteristic = txCharacteristic ?: return
|
val characteristic = txCharacteristic
|
||||||
val gatt = bluetoothGatt ?: return
|
val gatt = bluetoothGatt
|
||||||
|
if (characteristic == null) {
|
||||||
|
Log.e(TAG, "sendRaw: txCharacteristic is null!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (gatt == null) {
|
||||||
|
Log.e(TAG, "sendRaw: bluetoothGatt is null!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val cmdPreview = if (data.size >= 4) String(data, 0, 4, Charsets.US_ASCII) else "?"
|
||||||
|
Log.d(TAG, "sendRaw: $cmdPreview (${data.size} bytes)")
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
gatt.writeCharacteristic(characteristic, data, BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE)
|
val result = gatt.writeCharacteristic(characteristic, data, BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE)
|
||||||
|
Log.d(TAG, "sendRaw result: $result")
|
||||||
} else {
|
} else {
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
characteristic.value = data
|
characteristic.value = data
|
||||||
characteristic.writeType = BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE
|
characteristic.writeType = BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
gatt.writeCharacteristic(characteristic)
|
val ok = gatt.writeCharacteristic(characteristic)
|
||||||
|
Log.d(TAG, "sendRaw success: $ok")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,7 +305,8 @@ class BleManager private constructor(private val context: Context) {
|
|||||||
|
|
||||||
fun startBatteryPolling() {
|
fun startBatteryPolling() {
|
||||||
batteryTimer?.let { handler.removeCallbacks(it) }
|
batteryTimer?.let { handler.removeCallbacks(it) }
|
||||||
handler.postDelayed({ sendBatteryQuery() }, 1000)
|
// Query immediately, then every 30s
|
||||||
|
sendBatteryQuery()
|
||||||
val poll = object : Runnable {
|
val poll = object : Runnable {
|
||||||
override fun run() {
|
override fun run() {
|
||||||
sendBatteryQuery()
|
sendBatteryQuery()
|
||||||
@@ -301,10 +314,10 @@ class BleManager private constructor(private val context: Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
batteryTimer = poll
|
batteryTimer = poll
|
||||||
handler.postDelayed(poll, 31000)
|
handler.postDelayed(poll, 30000)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun stopBatteryPolling() {
|
fun stopBatteryPolling() {
|
||||||
batteryTimer?.let { handler.removeCallbacks(it) }
|
batteryTimer?.let { handler.removeCallbacks(it) }
|
||||||
batteryTimer = null
|
batteryTimer = null
|
||||||
}
|
}
|
||||||
@@ -353,7 +366,6 @@ class BleManager private constructor(private val context: Context) {
|
|||||||
} catch (_: Exception) {}
|
} catch (_: Exception) {}
|
||||||
|
|
||||||
gatt.discoverServices()
|
gatt.discoverServices()
|
||||||
startBatteryPolling()
|
|
||||||
onConnectionStateChanged?.invoke(true)
|
onConnectionStateChanged?.invoke(true)
|
||||||
}
|
}
|
||||||
BluetoothProfile.STATE_DISCONNECTED -> {
|
BluetoothProfile.STATE_DISCONNECTED -> {
|
||||||
@@ -439,6 +451,7 @@ class BleManager private constructor(private val context: Context) {
|
|||||||
if (status == BluetoothGatt.GATT_SUCCESS) {
|
if (status == BluetoothGatt.GATT_SUCCESS) {
|
||||||
if (txCharacteristic != null && rxCharacteristic != null) {
|
if (txCharacteristic != null && rxCharacteristic != null) {
|
||||||
isServiceReady.value = true
|
isServiceReady.value = true
|
||||||
|
startBatteryPolling()
|
||||||
Log.d(TAG, "Service ready! (CCCD written)")
|
Log.d(TAG, "Service ready! (CCCD written)")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -469,7 +482,15 @@ class BleManager private constructor(private val context: Context) {
|
|||||||
|
|
||||||
private fun processReceivedData(data: ByteArray) {
|
private fun processReceivedData(data: ByteArray) {
|
||||||
val prefix = String(data, 0, minOf(4, data.size), Charsets.US_ASCII)
|
val prefix = String(data, 0, minOf(4, data.size), Charsets.US_ASCII)
|
||||||
Log.d(TAG, "Received: $prefix (${data.size} bytes)")
|
Log.d(TAG, "Received: $prefix (${data.size} bytes), callback=${
|
||||||
|
when {
|
||||||
|
prefix.startsWith("rcj") -> "onNirsMcjReceived=${onNirsMcjReceived != null}"
|
||||||
|
prefix.startsWith("rta") -> "onNirsPowerOnReceived=${onNirsPowerOnReceived != null}"
|
||||||
|
prefix.startsWith("rqq") || prefix.startsWith("rsh") -> "onNirsSensorActivated=${onNirsSensorActivated != null}"
|
||||||
|
prefix.startsWith("rag") -> "onNirsMagReceived=${onNirsMagReceived != null}"
|
||||||
|
else -> "n/a"
|
||||||
|
}
|
||||||
|
}")
|
||||||
when (prefix) {
|
when (prefix) {
|
||||||
"rta:", "rta!" -> onNirsPowerOnReceived?.invoke()
|
"rta:", "rta!" -> onNirsPowerOnReceived?.invoke()
|
||||||
"rsh:", "rsh!", "rqq:", "rqq!" -> onNirsSensorActivated?.invoke()
|
"rsh:", "rsh!", "rqq:", "rqq!" -> onNirsSensorActivated?.invoke()
|
||||||
@@ -482,6 +503,7 @@ class BleManager private constructor(private val context: Context) {
|
|||||||
batteryLevel.value = ((millivolts - 3700) * 100 / 500).coerceIn(0, 100)
|
batteryLevel.value = ((millivolts - 3700) * 100 / 500).coerceIn(0, 100)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else -> Log.w(TAG, "Unknown prefix: '$prefix' hex=${data.take(8).joinToString(" ") { "%02X".format(it) }}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,6 +42,14 @@ class NirsManager private constructor() {
|
|||||||
currentOxy.value = result.oxyFiltered
|
currentOxy.value = result.oxyFiltered
|
||||||
sampleCount.intValue++
|
sampleCount.intValue++
|
||||||
|
|
||||||
|
if (sampleCount.intValue <= 3 || sampleCount.intValue % 50 == 0) {
|
||||||
|
android.util.Log.d("NirsManager", "Sample #${sampleCount.intValue}: " +
|
||||||
|
"hasGain=${calcEngine.hasGain}, " +
|
||||||
|
"led[0..5]=${packet.ledValues.take(6)}, " +
|
||||||
|
"degain[0..2]=${result.degain.take(3).map { "%.2f".format(it) }}, " +
|
||||||
|
"oxy=StO2:${"%.1f".format(result.oxyFiltered.stO2)} HbO2:${"%.1f".format(result.oxyFiltered.hbO2)} HHb:${"%.1f".format(result.oxyFiltered.hb)}")
|
||||||
|
}
|
||||||
|
|
||||||
appendTrimming(oxyHbO2History, result.oxyFiltered.hbO2)
|
appendTrimming(oxyHbO2History, result.oxyFiltered.hbO2)
|
||||||
appendTrimming(oxyHbHistory, result.oxyFiltered.hb)
|
appendTrimming(oxyHbHistory, result.oxyFiltered.hb)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,8 +12,13 @@ data class NirsMcjPacket(
|
|||||||
val ledValues: IntArray
|
val ledValues: IntArray
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
|
private const val TAG = "NirsMcjPacket"
|
||||||
|
|
||||||
fun parse(data: ByteArray): NirsMcjPacket? {
|
fun parse(data: ByteArray): NirsMcjPacket? {
|
||||||
if (data.size < 72) return null
|
if (data.size < 72) {
|
||||||
|
android.util.Log.e(TAG, "parse: data too small: ${data.size} < 72")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
fun u16(offset: Int): Int =
|
fun u16(offset: Int): Int =
|
||||||
((data[offset].toInt() and 0xFF) shl 8) or (data[offset + 1].toInt() and 0xFF)
|
((data[offset].toInt() and 0xFF) shl 8) or (data[offset + 1].toInt() and 0xFF)
|
||||||
@@ -34,10 +39,15 @@ data class NirsMcjPacket(
|
|||||||
val ledValues = IntArray(24)
|
val ledValues = IntArray(24)
|
||||||
for (i in 0 until 24) {
|
for (i in 0 until 24) {
|
||||||
val offset = base + 20 + (i * 2)
|
val offset = base + 20 + (i * 2)
|
||||||
if (offset + 1 >= data.size) return null
|
if (offset + 1 >= data.size) {
|
||||||
|
android.util.Log.e(TAG, "parse: LED[$i] out of bounds: offset=${offset+1} >= size=${data.size}")
|
||||||
|
return null
|
||||||
|
}
|
||||||
ledValues[i] = u16(offset)
|
ledValues[i] = u16(offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
android.util.Log.d(TAG, "parse OK: battery=$battery led[0..5]=${ledValues.take(6)}")
|
||||||
|
|
||||||
return NirsMcjPacket(
|
return NirsMcjPacket(
|
||||||
battery, temperature,
|
battery, temperature,
|
||||||
accX, accY, accZ,
|
accX, accY, accZ,
|
||||||
|
|||||||
@@ -73,6 +73,10 @@ class UserStorage private constructor(context: Context) {
|
|||||||
get() = prefs.getInt("bladder_level", 0)
|
get() = prefs.getInt("bladder_level", 0)
|
||||||
set(value) { prefs.edit().putInt("bladder_level", value).apply() }
|
set(value) { prefs.edit().putInt("bladder_level", value).apply() }
|
||||||
|
|
||||||
|
var catheterCount: Int
|
||||||
|
get() = prefs.getInt("catheter_count", 0)
|
||||||
|
set(value) { prefs.edit().putInt("catheter_count", value).apply() }
|
||||||
|
|
||||||
var hasCompletedOnboarding: Boolean
|
var hasCompletedOnboarding: Boolean
|
||||||
get() = prefs.getBoolean("has_completed_onboarding", false)
|
get() = prefs.getBoolean("has_completed_onboarding", false)
|
||||||
set(value) { prefs.edit().putBoolean("has_completed_onboarding", value).apply() }
|
set(value) { prefs.edit().putBoolean("has_completed_onboarding", value).apply() }
|
||||||
|
|||||||
+47
@@ -43,6 +43,7 @@ fun PiezoMonitoringView(appState: AppState) {
|
|||||||
var showSettings by remember { mutableStateOf(false) }
|
var showSettings by remember { mutableStateOf(false) }
|
||||||
var isMeasuring by remember { mutableStateOf(false) }
|
var isMeasuring by remember { mutableStateOf(false) }
|
||||||
var showCatheterizeConfirm by remember { mutableStateOf(false) }
|
var showCatheterizeConfirm by remember { mutableStateOf(false) }
|
||||||
|
var showAddCatheterDialog by remember { mutableStateOf(false) }
|
||||||
var showDisconnectionBanner by remember { mutableStateOf(false) }
|
var showDisconnectionBanner by remember { mutableStateOf(false) }
|
||||||
var reconnectionState by remember { mutableStateOf(BleReconnectionState.DISCONNECTED) }
|
var reconnectionState by remember { mutableStateOf(BleReconnectionState.DISCONNECTED) }
|
||||||
var isAutoMeasuring by remember { mutableStateOf(false) }
|
var isAutoMeasuring by remember { mutableStateOf(false) }
|
||||||
@@ -50,6 +51,7 @@ fun PiezoMonitoringView(appState: AppState) {
|
|||||||
val urgency = appState.bladderLevel.urgency
|
val urgency = appState.bladderLevel.urgency
|
||||||
val batteryLvl by bleManager.batteryLevel
|
val batteryLvl by bleManager.batteryLevel
|
||||||
val isConnected by bleManager.isConnected
|
val isConnected by bleManager.isConnected
|
||||||
|
val catheterCount by appState.catheterCount
|
||||||
|
|
||||||
fun measure() {
|
fun measure() {
|
||||||
isMeasuring = true
|
isMeasuring = true
|
||||||
@@ -74,6 +76,36 @@ fun PiezoMonitoringView(appState: AppState) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add catheter dialog
|
||||||
|
if (showAddCatheterDialog) {
|
||||||
|
var addAmount by remember { mutableStateOf("1") }
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = { showAddCatheterDialog = false },
|
||||||
|
title = { Text("Add Catheters") },
|
||||||
|
text = {
|
||||||
|
Column {
|
||||||
|
Text("Enter the number of catheters to add:")
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
OutlinedTextField(
|
||||||
|
value = addAmount,
|
||||||
|
onValueChange = { addAmount = it.filter { c -> c.isDigit() } },
|
||||||
|
singleLine = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = {
|
||||||
|
val amount = addAmount.toIntOrNull() ?: 0
|
||||||
|
if (amount > 0) appState.addCatheterCount(amount)
|
||||||
|
showAddCatheterDialog = false
|
||||||
|
}) { Text("Add", color = MlPrimary) }
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = { showAddCatheterDialog = false }) { Text("Cancel") }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Catheterize confirmation dialog
|
// Catheterize confirmation dialog
|
||||||
if (showCatheterizeConfirm) {
|
if (showCatheterizeConfirm) {
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
@@ -124,6 +156,21 @@ fun PiezoMonitoringView(appState: AppState) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Catheter count
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(RoundedCornerShape(8.dp))
|
||||||
|
.background(MlPrimary.copy(alpha = 0.1f))
|
||||||
|
.clickable { showAddCatheterDialog = true }
|
||||||
|
.padding(horizontal = 8.dp, vertical = 4.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Icon(Icons.Default.Add, "Add catheter", tint = MlPrimary, modifier = Modifier.size(14.dp))
|
||||||
|
Spacer(modifier = Modifier.width(2.dp))
|
||||||
|
Text("$catheterCount", fontSize = 13.sp, fontWeight = FontWeight.Bold, color = MlPrimary)
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.width(4.dp))
|
||||||
|
|
||||||
IconButton(onClick = { appState.showHistory() }) {
|
IconButton(onClick = { appState.showHistory() }) {
|
||||||
Icon(Icons.Default.History, "History", tint = MlSecondaryText)
|
Icon(Icons.Default.History, "History", tint = MlSecondaryText)
|
||||||
}
|
}
|
||||||
|
|||||||
+44
-3
@@ -56,18 +56,56 @@ fun VivamyoMonitoringView(appState: AppState) {
|
|||||||
val sampleCount by nirsManager.sampleCount
|
val sampleCount by nirsManager.sampleCount
|
||||||
val isFilterEnabled by nirsManager.isFilterEnabled
|
val isFilterEnabled by nirsManager.isFilterEnabled
|
||||||
|
|
||||||
|
var mcjRetryCount = remember { mutableIntStateOf(0) }
|
||||||
|
var mcjRetryRunnable by remember { mutableStateOf<Runnable?>(null) }
|
||||||
|
|
||||||
|
fun cancelMcjRetry() {
|
||||||
|
mcjRetryRunnable?.let { handler.removeCallbacks(it) }
|
||||||
|
mcjRetryRunnable = null
|
||||||
|
mcjRetryCount.intValue = 0
|
||||||
|
}
|
||||||
|
|
||||||
fun startNirsStream() {
|
fun startNirsStream() {
|
||||||
|
cancelMcjRetry()
|
||||||
|
|
||||||
bleManager.onNirsMcjReceived = { data ->
|
bleManager.onNirsMcjReceived = { data ->
|
||||||
|
cancelMcjRetry() // Got response, cancel retry
|
||||||
if (nirsManager.isStreaming.value) {
|
if (nirsManager.isStreaming.value) {
|
||||||
NirsMcjPacket.parse(data)?.let { packet ->
|
val packet = NirsMcjPacket.parse(data)
|
||||||
|
if (packet != null) {
|
||||||
nirsManager.processMcjPacket(packet)
|
nirsManager.processMcjPacket(packet)
|
||||||
|
} else {
|
||||||
|
android.util.Log.e("VivamyoMonitor", "NirsMcjPacket.parse returned null! data.size=${data.size}")
|
||||||
}
|
}
|
||||||
handler.postDelayed({
|
handler.postDelayed({
|
||||||
if (nirsManager.isStreaming.value) bleManager.sendMcjQuery()
|
if (nirsManager.isStreaming.value) {
|
||||||
|
bleManager.sendMcjQuery()
|
||||||
|
// Retry if no response within 2s
|
||||||
|
val retry = Runnable {
|
||||||
|
if (nirsManager.isStreaming.value && mcjRetryCount.intValue < 3) {
|
||||||
|
mcjRetryCount.intValue++
|
||||||
|
bleManager.sendMcjQuery()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mcjRetryRunnable = retry
|
||||||
|
handler.postDelayed(retry, 2000)
|
||||||
|
}
|
||||||
}, 100)
|
}, 100)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Start mcj chain directly (same as iOS)
|
||||||
bleManager.sendMcjQuery()
|
bleManager.sendMcjQuery()
|
||||||
|
|
||||||
|
// Retry if first mcj gets no response within 2s
|
||||||
|
val initialRetry = Runnable {
|
||||||
|
if (nirsManager.isStreaming.value && mcjRetryCount.intValue < 5) {
|
||||||
|
mcjRetryCount.intValue++
|
||||||
|
bleManager.sendMcjQuery()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mcjRetryRunnable = initialRetry
|
||||||
|
handler.postDelayed(initialRetry, 2000)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toggleStreaming() {
|
fun toggleStreaming() {
|
||||||
@@ -75,7 +113,10 @@ fun VivamyoMonitoringView(appState: AppState) {
|
|||||||
isToggling = true
|
isToggling = true
|
||||||
if (isStreaming) {
|
if (isStreaming) {
|
||||||
nirsManager.stopStreaming()
|
nirsManager.stopStreaming()
|
||||||
|
cancelMcjRetry()
|
||||||
|
bleManager.startBatteryPolling() // Resume battery polling
|
||||||
} else {
|
} else {
|
||||||
|
bleManager.stopBatteryPolling() // Pause battery polling during streaming
|
||||||
nirsManager.startStreaming()
|
nirsManager.startStreaming()
|
||||||
startNirsStream()
|
startNirsStream()
|
||||||
}
|
}
|
||||||
@@ -89,7 +130,7 @@ fun VivamyoMonitoringView(appState: AppState) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DisposableEffect(Unit) { onDispose { nirsManager.stopStreaming() } }
|
DisposableEffect(Unit) { onDispose { nirsManager.stopStreaming(); cancelMcjRetry() } }
|
||||||
|
|
||||||
// Clear confirm dialog
|
// Clear confirm dialog
|
||||||
if (showClearConfirm) {
|
if (showClearConfirm) {
|
||||||
|
|||||||
Reference in New Issue
Block a user