refactor(radio): connection state tweaks (#3089)

Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
James Rich 2025-09-14 07:31:30 -05:00
parent 4dbbcd222d
commit 7905334f1b
3 changed files with 22 additions and 51 deletions

View file

@ -31,6 +31,7 @@ import com.geeksville.mesh.android.prefs.RadioPrefs
import com.geeksville.mesh.concurrent.handledLaunch
import com.geeksville.mesh.repository.bluetooth.BluetoothRepository
import com.geeksville.mesh.repository.network.NetworkRepository
import com.geeksville.mesh.service.ConnectionState
import com.geeksville.mesh.util.anonymize
import com.geeksville.mesh.util.ignoreException
import com.geeksville.mesh.util.toRemoteExceptions
@ -74,8 +75,8 @@ constructor(
private val interfaceFactory: InterfaceFactory,
) : Logging {
private val _connectionState = MutableStateFlow(RadioServiceConnectionState())
val connectionState = _connectionState.asStateFlow()
private val _connectionState = MutableStateFlow(ConnectionState.DISCONNECTED)
val connectionState: StateFlow<ConnectionState> = _connectionState.asStateFlow()
private val _receivedData = MutableSharedFlow<ByteArray>()
val receivedData: SharedFlow<ByteArray> = _receivedData
@ -86,7 +87,7 @@ constructor(
private val logSends = false
private val logReceives = false
private lateinit var sentPacketsLog: BinaryLogFile // inited in onCreate
private lateinit var sentPacketsLog: BinaryLogFile
private lateinit var receivedPacketsLog: BinaryLogFile
val mockInterfaceAddress: String by lazy { toInterfaceAddress(InterfaceId.MOCK, "") }
@ -103,9 +104,6 @@ constructor(
*/
private var isStarted = false
// true if our interface is currently connected to a device
private var isConnected = false
private fun initStateListeners() {
bluetoothRepository.state
.onEach { state ->
@ -195,11 +193,10 @@ constructor(
}
}
private fun broadcastConnectionChanged(isConnected: Boolean, isPermanent: Boolean) {
debug("Broadcasting connection=$isConnected")
private fun broadcastConnectionChanged(newState: ConnectionState) {
debug("Broadcasting connection state change to $newState")
processLifecycle.coroutineScope.launch(dispatchers.default) {
_connectionState.emit(RadioServiceConnectionState(isConnected, isPermanent))
_connectionState.emit(newState)
}
}
@ -227,16 +224,15 @@ constructor(
}
fun onConnect() {
if (!isConnected) {
isConnected = true
broadcastConnectionChanged(isConnected = true, isPermanent = false)
if (_connectionState.value != ConnectionState.CONNECTED) {
broadcastConnectionChanged(ConnectionState.CONNECTED)
}
}
fun onDisconnect(isPermanent: Boolean) {
if (isConnected) {
isConnected = false
broadcastConnectionChanged(isConnected = false, isPermanent = isPermanent)
val newTargetState = if (isPermanent) ConnectionState.DISCONNECTED else ConnectionState.DEVICE_SLEEP
if (_connectionState.value != newTargetState) {
broadcastConnectionChanged(newTargetState)
}
}

View file

@ -1,23 +0,0 @@
/*
* Copyright (c) 2025 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh.repository.radio
data class RadioServiceConnectionState(
val isConnected: Boolean = false,
val isPermanent: Boolean = false
)

View file

@ -79,7 +79,6 @@ import com.geeksville.mesh.repository.datastore.RadioConfigRepository
import com.geeksville.mesh.repository.location.LocationRepository
import com.geeksville.mesh.repository.network.MQTTRepository
import com.geeksville.mesh.repository.radio.RadioInterfaceService
import com.geeksville.mesh.repository.radio.RadioServiceConnectionState
import com.geeksville.mesh.telemetry
import com.geeksville.mesh.user
import com.geeksville.mesh.util.anonymize
@ -1373,19 +1372,18 @@ class MeshService :
}
}
private fun onRadioConnectionState(state: RadioServiceConnectionState) {
// sleep now disabled by default on ESP32, permanent is true unless light sleep enabled
private fun onRadioConnectionState(newState: ConnectionState) {
// Respect light sleep (lsEnabled) setting: if device reports sleep
// but lsEnabled is false, treat as disconnected.
val isRouter = localConfig.device.role == ConfigProtos.Config.DeviceConfig.Role.ROUTER
val lsEnabled = localConfig.power.isPowerSaving || isRouter
val connected = state.isConnected
val permanent = state.isPermanent || !lsEnabled
onConnectionChanged(
when {
connected -> ConnectionState.CONNECTED
permanent -> ConnectionState.DISCONNECTED
else -> ConnectionState.DEVICE_SLEEP
},
)
val effectiveState = when (newState) {
ConnectionState.CONNECTED -> ConnectionState.CONNECTED
ConnectionState.DEVICE_SLEEP -> if (lsEnabled) ConnectionState.DEVICE_SLEEP else ConnectionState.DISCONNECTED
ConnectionState.DISCONNECTED -> ConnectionState.DISCONNECTED
}
onConnectionChanged(effectiveState)
}
private val packetHandlers: Map<PayloadVariantCase, ((MeshProtos.FromRadio) -> Unit)> by lazy {