2024-08-31 04:05:42 -07:00
|
|
|
package com.geeksville.mesh.model
|
|
|
|
|
|
|
|
|
|
import androidx.lifecycle.ViewModel
|
|
|
|
|
import androidx.lifecycle.viewModelScope
|
2024-10-26 05:57:18 -03:00
|
|
|
import com.geeksville.mesh.CoroutineDispatchers
|
2024-10-23 13:31:31 -07:00
|
|
|
import com.geeksville.mesh.MeshProtos.MeshPacket
|
2024-10-25 08:14:32 -03:00
|
|
|
import com.geeksville.mesh.Portnums.PortNum
|
2024-08-31 04:05:42 -07:00
|
|
|
import com.geeksville.mesh.TelemetryProtos.Telemetry
|
2024-11-02 09:34:30 -03:00
|
|
|
import com.geeksville.mesh.android.Logging
|
2024-08-31 04:05:42 -07:00
|
|
|
import com.geeksville.mesh.database.MeshLogRepository
|
2024-10-25 08:14:32 -03:00
|
|
|
import com.geeksville.mesh.database.entity.MeshLog
|
2024-09-18 17:37:55 -05:00
|
|
|
import com.geeksville.mesh.repository.datastore.RadioConfigRepository
|
2024-08-31 04:05:42 -07:00
|
|
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
2024-10-18 19:27:15 -03:00
|
|
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
2024-08-31 04:05:42 -07:00
|
|
|
import kotlinx.coroutines.flow.MutableStateFlow
|
2024-11-02 09:34:30 -03:00
|
|
|
import kotlinx.coroutines.flow.StateFlow
|
2024-09-01 12:03:32 -03:00
|
|
|
import kotlinx.coroutines.flow.combine
|
2024-10-18 19:27:15 -03:00
|
|
|
import kotlinx.coroutines.flow.flatMapLatest
|
2024-11-02 09:34:30 -03:00
|
|
|
import kotlinx.coroutines.flow.launchIn
|
|
|
|
|
import kotlinx.coroutines.flow.onEach
|
|
|
|
|
import kotlinx.coroutines.flow.update
|
2024-10-26 05:57:18 -03:00
|
|
|
import kotlinx.coroutines.launch
|
2024-08-31 04:05:42 -07:00
|
|
|
import javax.inject.Inject
|
2024-09-01 12:03:32 -03:00
|
|
|
|
2024-09-08 03:36:44 -07:00
|
|
|
data class MetricsState(
|
2024-10-25 08:14:32 -03:00
|
|
|
val isManaged: Boolean = true,
|
2024-11-02 09:34:30 -03:00
|
|
|
val isFahrenheit: Boolean = false,
|
2024-09-01 12:03:32 -03:00
|
|
|
val deviceMetrics: List<Telemetry> = emptyList(),
|
|
|
|
|
val environmentMetrics: List<Telemetry> = emptyList(),
|
2024-10-23 13:31:31 -07:00
|
|
|
val signalMetrics: List<MeshPacket> = emptyList(),
|
2024-11-02 09:34:30 -03:00
|
|
|
val tracerouteRequests: List<MeshLog> = emptyList(),
|
|
|
|
|
val tracerouteResults: List<MeshPacket> = emptyList(),
|
2024-09-01 12:03:32 -03:00
|
|
|
) {
|
2024-10-18 19:27:15 -03:00
|
|
|
fun hasDeviceMetrics() = deviceMetrics.isNotEmpty()
|
|
|
|
|
fun hasEnvironmentMetrics() = environmentMetrics.isNotEmpty()
|
2024-10-23 13:31:31 -07:00
|
|
|
fun hasSignalMetrics() = signalMetrics.isNotEmpty()
|
2024-11-02 09:34:30 -03:00
|
|
|
fun hasTracerouteLogs() = tracerouteRequests.isNotEmpty()
|
2024-10-18 19:27:15 -03:00
|
|
|
|
2024-09-01 12:03:32 -03:00
|
|
|
companion object {
|
2024-09-08 03:36:44 -07:00
|
|
|
val Empty = MetricsState()
|
2024-09-01 12:03:32 -03:00
|
|
|
}
|
|
|
|
|
}
|
2024-08-31 04:05:42 -07:00
|
|
|
|
|
|
|
|
@HiltViewModel
|
2024-09-08 03:36:44 -07:00
|
|
|
class MetricsViewModel @Inject constructor(
|
2024-10-26 05:57:18 -03:00
|
|
|
private val dispatchers: CoroutineDispatchers,
|
|
|
|
|
private val meshLogRepository: MeshLogRepository,
|
2024-10-25 08:14:32 -03:00
|
|
|
private val radioConfigRepository: RadioConfigRepository,
|
2024-11-02 09:34:30 -03:00
|
|
|
) : ViewModel(), Logging {
|
2024-10-18 19:27:15 -03:00
|
|
|
private val destNum = MutableStateFlow(0)
|
|
|
|
|
|
2024-10-23 17:49:47 -03:00
|
|
|
private fun MeshPacket.hasValidSignal(): Boolean =
|
|
|
|
|
rxTime > 0 && (rxSnr != 0f && rxRssi != 0) && (hopStart > 0 && hopStart - hopLimit == 0)
|
|
|
|
|
|
2024-10-25 08:14:32 -03:00
|
|
|
private fun MeshLog.hasValidTraceroute(): Boolean = with(fromRadio.packet) {
|
|
|
|
|
hasDecoded() && decoded.wantResponse && from == 0 && to == destNum.value
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun getUser(nodeNum: Int) = radioConfigRepository.getUser(nodeNum)
|
|
|
|
|
|
2024-10-26 05:57:18 -03:00
|
|
|
fun deleteLog(uuid: String) = viewModelScope.launch(dispatchers.io) {
|
|
|
|
|
meshLogRepository.deleteLog(uuid)
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-02 09:34:30 -03:00
|
|
|
private val _state = MutableStateFlow(MetricsState.Empty)
|
|
|
|
|
val state: StateFlow<MetricsState> = _state
|
|
|
|
|
|
|
|
|
|
init {
|
|
|
|
|
radioConfigRepository.deviceProfileFlow.onEach { profile ->
|
2024-10-25 08:14:32 -03:00
|
|
|
val moduleConfig = profile.moduleConfig
|
2024-11-02 09:34:30 -03:00
|
|
|
_state.update { state ->
|
|
|
|
|
state.copy(
|
|
|
|
|
isManaged = profile.config.security.isManaged,
|
|
|
|
|
isFahrenheit = moduleConfig.telemetry.environmentDisplayFahrenheit,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}.launchIn(viewModelScope)
|
|
|
|
|
|
|
|
|
|
@OptIn(ExperimentalCoroutinesApi::class)
|
|
|
|
|
destNum.flatMapLatest { destNum ->
|
|
|
|
|
meshLogRepository.getTelemetryFrom(destNum).onEach { telemetry ->
|
|
|
|
|
_state.update { state ->
|
|
|
|
|
state.copy(
|
|
|
|
|
deviceMetrics = telemetry.filter { it.hasDeviceMetrics() },
|
|
|
|
|
environmentMetrics = telemetry.filter {
|
|
|
|
|
it.hasEnvironmentMetrics() && it.environmentMetrics.relativeHumidity >= 0f
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}.launchIn(viewModelScope)
|
|
|
|
|
|
|
|
|
|
@OptIn(ExperimentalCoroutinesApi::class)
|
|
|
|
|
destNum.flatMapLatest { destNum ->
|
|
|
|
|
meshLogRepository.getMeshPacketsFrom(destNum).onEach { meshPackets ->
|
|
|
|
|
_state.update { state ->
|
|
|
|
|
state.copy(signalMetrics = meshPackets.filter { it.hasValidSignal() })
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}.launchIn(viewModelScope)
|
|
|
|
|
|
|
|
|
|
@OptIn(ExperimentalCoroutinesApi::class)
|
|
|
|
|
destNum.flatMapLatest { destNum ->
|
|
|
|
|
combine(
|
|
|
|
|
meshLogRepository.getLogsFrom(nodeNum = 0, PortNum.TRACEROUTE_APP_VALUE),
|
|
|
|
|
meshLogRepository.getMeshPacketsFrom(destNum, PortNum.TRACEROUTE_APP_VALUE),
|
|
|
|
|
) { request, response ->
|
|
|
|
|
_state.update { state ->
|
|
|
|
|
state.copy(
|
|
|
|
|
tracerouteRequests = request.filter { it.hasValidTraceroute() },
|
|
|
|
|
tracerouteResults = response,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}.launchIn(viewModelScope)
|
|
|
|
|
|
|
|
|
|
debug("MetricsViewModel created")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun onCleared() {
|
|
|
|
|
super.onCleared()
|
|
|
|
|
debug("MetricsViewModel cleared")
|
|
|
|
|
}
|
2024-08-31 04:05:42 -07:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Used to set the Node for which the user will see charts for.
|
|
|
|
|
*/
|
|
|
|
|
fun setSelectedNode(nodeNum: Int) {
|
2024-10-18 19:27:15 -03:00
|
|
|
destNum.value = nodeNum
|
2024-08-31 04:05:42 -07:00
|
|
|
}
|
|
|
|
|
}
|