Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 34 additions & 9 deletions app/src/main/java/com/geeksville/mesh/service/MeshService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -1216,6 +1216,7 @@ class MeshService : Service() {
}

private var sleepTimeout: Job? = null
private var connectionTimeoutJob: Job? = null

// msecs since 1970 we started this connection
private var connectTimeMsec = 0L
Expand Down Expand Up @@ -1275,6 +1276,11 @@ class MeshService : Service() {
try {
connectTimeMsec = System.currentTimeMillis()
startConfig()
connectionTimeoutJob = serviceScope.handledLaunch {
delay(30000) // 30-second timeout
Timber.w("Connection timeout reached")
onConnectionChanged(ConnectionState.DISCONNECTED)
}
} catch (ex: InvalidProtocolBufferException) {
Timber.e(ex, "Invalid protocol buffer sent by device - update device software and try again")
} catch (ex: RadioNotConnectedException) {
Expand All @@ -1296,14 +1302,16 @@ class MeshService : Service() {
}

// Cancel any existing timeouts
sleepTimeout?.let {
it.cancel()
sleepTimeout = null
}
sleepTimeout?.cancel()
sleepTimeout = null

connectionTimeoutJob?.cancel()
connectionTimeoutJob = null

connectionStateHolder.setState(c)
when (c) {
ConnectionState.CONNECTED -> startConnect()
ConnectionState.CONNECTED -> onHasSettings()
ConnectionState.CONNECTING -> startConnect()
ConnectionState.DEVICE_SLEEP -> startDeviceSleep()
ConnectionState.DISCONNECTED -> startDisconnect()
}
Expand All @@ -1315,7 +1323,7 @@ class MeshService : Service() {
val notificationSummary =
when (connectionStateHolder.getState()) {
ConnectionState.CONNECTED -> getString(R.string.connected_count).format(numOnlineNodes)

ConnectionState.CONNECTING -> getString(R.string.connecting_to_device)
ConnectionState.DISCONNECTED -> getString(R.string.disconnected)
ConnectionState.DEVICE_SLEEP -> getString(R.string.device_sleeping)
}
Expand All @@ -1333,7 +1341,7 @@ class MeshService : Service() {

val effectiveState =
when (newState) {
ConnectionState.CONNECTED -> ConnectionState.CONNECTED
ConnectionState.CONNECTED -> if (connectionStateHolder.getState() == ConnectionState.DISCONNECTED) ConnectionState.CONNECTING else ConnectionState.CONNECTED
ConnectionState.DEVICE_SLEEP ->
if (lsEnabled) {
ConnectionState.DEVICE_SLEEP
Expand All @@ -1342,6 +1350,7 @@ class MeshService : Service() {
}

ConnectionState.DISCONNECTED -> ConnectionState.DISCONNECTED
else -> newState
}
onConnectionChanged(effectiveState)
}
Expand Down Expand Up @@ -1417,6 +1426,11 @@ class MeshService : Service() {
// provisional NodeInfos we will install if all goes well
private val newNodes = mutableListOf<MeshProtos.NodeInfo>()

private val progressWeights = floatArrayOf(0.1f, 0.1f, 0.2f, 0.6f)
private fun setProgress(step: Int, progress: Float) {
val totalProgress = progressWeights.take(step).sum() + progress * progressWeights[step]
serviceRepository.setConnectionProgress(totalProgress)
}
private fun handleDeviceConfig(config: ConfigProtos.Config) {
Timber.d("Received config ${config.toOneLineString()}")
val packetToSave =
Expand All @@ -1431,6 +1445,7 @@ class MeshService : Service() {
setLocalConfig(config)
val configCount = localConfig.allFields.size
serviceRepository.setStatusMessage("Device config ($configCount / $configTotal)")
setProgress(0, configCount.toFloat() / configTotal.toFloat())
}

private fun handleModuleConfig(config: ModuleConfigProtos.ModuleConfig) {
Expand All @@ -1447,6 +1462,7 @@ class MeshService : Service() {
setLocalModuleConfig(config)
val moduleCount = moduleConfig.allFields.size
serviceRepository.setStatusMessage("Module config ($moduleCount / $moduleTotal)")
setProgress(1, moduleCount.toFloat() / moduleTotal.toFloat())
}

private fun handleChannel(ch: ChannelProtos.Channel) {
Expand All @@ -1463,6 +1479,7 @@ class MeshService : Service() {
if (ch.role != ChannelProtos.Channel.Role.DISABLED) updateChannelSettings(ch)
val maxChannels = myNodeInfo?.maxChannels ?: 8
serviceRepository.setStatusMessage("Channels (${ch.index + 1} / $maxChannels)")
setProgress(2, (ch.index + 1).toFloat() / maxChannels.toFloat())
}

/** Convert a protobuf NodeInfo into our model objects and update our node DB */
Expand Down Expand Up @@ -1507,6 +1524,7 @@ class MeshService : Service() {
}
}

private var nodesTotal = 0
private fun handleNodeInfo(info: MeshProtos.NodeInfo) {
Timber.d(
"Received nodeinfo num=${info.num}," +
Expand All @@ -1526,7 +1544,10 @@ class MeshService : Service() {
insertMeshLog(packetToSave)

newNodes.add(info)
serviceRepository.setStatusMessage("Nodes (${newNodes.size})")
serviceRepository.setStatusMessage("Nodes (${newNodes.size} / $nodesTotal)")
if (nodesTotal > 0) {
setProgress(3, newNodes.size.toFloat() / nodesTotal.toFloat())
}
}

private var rawMyNodeInfo: MeshProtos.MyNodeInfo? = null
Expand Down Expand Up @@ -1591,6 +1612,10 @@ class MeshService : Service() {

rawMyNodeInfo = myInfo
regenMyNodeInfo()
nodesTotal = myInfo.nodedbCount
if (nodesTotal == 0) {
setProgress(3, 1f)
}

// We'll need to get a new set of channels and settings now
serviceScope.handledLaunch {
Expand Down Expand Up @@ -1773,7 +1798,7 @@ class MeshService : Service() {

haveNodeDB = true // we now have nodes from real hardware
sendAnalytics()
onHasSettings()
onConnectionChanged(ConnectionState.CONNECTED)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
Expand All @@ -34,6 +35,7 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Language
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
Expand Down Expand Up @@ -156,7 +158,7 @@ fun ConnectionsScreen(
ConnectionState.CONNECTED -> {
if (regionUnset) R.string.must_set_region else R.string.connected
}

ConnectionState.CONNECTING -> R.string.connecting_to_device
ConnectionState.DISCONNECTED -> R.string.not_connected
ConnectionState.DEVICE_SLEEP -> R.string.connected_sleeping
}.let { scanModel.setErrorText(context.getString(it)) }
Expand Down Expand Up @@ -185,6 +187,19 @@ fun ConnectionsScreen(
.padding(paddingValues)
.padding(16.dp),
) {
AnimatedVisibility(
visible = connectionState == ConnectionState.CONNECTING,
modifier = Modifier.padding(bottom = 16.dp),
) {
val connectionProgress by connectionsViewModel.connectionProgress.collectAsStateWithLifecycle()
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
Text(text = stringResource(id = R.string.connecting_to_device))
LinearProgressIndicator(
modifier = Modifier.fillMaxWidth(),
progress = { connectionProgress },
)
}
}
AnimatedVisibility(
visible = connectionState.isConnected(),
modifier = Modifier.padding(bottom = 16.dp),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,22 @@
package com.geeksville.mesh.ui.connections

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.geeksville.mesh.repository.bluetooth.BluetoothRepository
import com.geeksville.mesh.repository.radio.RadioInterfaceService
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import org.meshtastic.core.data.repository.NodeRepository
import org.meshtastic.core.data.repository.RadioConfigRepository
import org.meshtastic.core.database.entity.MyNodeEntity
import org.meshtastic.core.database.model.Node
import org.meshtastic.core.prefs.ui.UiPrefs
import org.meshtastic.core.service.ConnectionState
import org.meshtastic.core.service.ServiceRepository
import org.meshtastic.core.ui.viewmodel.stateInWhileSubscribed
import org.meshtastic.proto.LocalOnlyProtos.LocalConfig
Expand All @@ -45,6 +50,16 @@ constructor(
bluetoothRepository: BluetoothRepository,
private val uiPrefs: UiPrefs,
) : ViewModel() {

private val _connectionProgress = MutableStateFlow(0f)
val connectionProgress = _connectionProgress.asStateFlow()

init {
serviceRepository.connectionProgress.onEach {
_connectionProgress.value = it
}.launchIn(viewModelScope)
}

fun onStart() {
radioInterfaceService.setRssiPolling(true)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ enum class ConnectionState {
/** We are disconnected from the device, and we should be trying to reconnect. */
DISCONNECTED,

/** We are in the process of connecting to a device */
CONNECTING,

/** We are connected to the device and communicating normally. */
CONNECTED,

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,14 @@ class ServiceRepository @Inject constructor() {
}
}

private val _connectionProgress = MutableStateFlow(0f)
val connectionProgress: StateFlow<Float>
get() = _connectionProgress

fun setConnectionProgress(progress: Float) {
_connectionProgress.value = progress
}

private val _meshPacketFlow = MutableSharedFlow<MeshPacket>()
val meshPacketFlow: SharedFlow<MeshPacket>
get() = _meshPacketFlow
Expand Down