feat(logging): Replace Timber with Kermit for multiplatform logging (#4083)

Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
James Rich 2025-12-28 08:30:15 -06:00 committed by GitHub
parent a927481e4d
commit 0776e029f3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
92 changed files with 727 additions and 957 deletions

View file

@ -40,6 +40,7 @@ import androidx.core.net.toUri
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.lifecycleScope
import co.touchlab.kermit.Logger
import com.geeksville.mesh.model.UIViewModel
import com.geeksville.mesh.ui.MainScreen
import dagger.hilt.android.AndroidEntryPoint
@ -53,7 +54,6 @@ import org.meshtastic.core.ui.theme.AppTheme
import org.meshtastic.core.ui.theme.MODE_DYNAMIC
import org.meshtastic.core.ui.util.showToast
import org.meshtastic.feature.intro.AppIntroductionScreen
import timber.log.Timber
import javax.inject.Inject
@AndroidEntryPoint
@ -118,27 +118,27 @@ class MainActivity : AppCompatActivity() {
when (appLinkAction) {
Intent.ACTION_VIEW -> {
appLinkData?.let {
Timber.d("App link data: $it")
Logger.d { "App link data: $it" }
if (it.path?.startsWith("/e/") == true || it.path?.startsWith("/E/") == true) {
Timber.d("App link data is a channel set")
Logger.d { "App link data is a channel set" }
model.requestChannelUrl(
url = it,
onFailure = { lifecycleScope.launch { showToast(Res.string.channel_invalid) } },
)
} else if (it.path?.startsWith("/v/") == true || it.path?.startsWith("/V/") == true) {
Timber.d("App link data is a shared contact")
Logger.d { "App link data is a shared contact" }
model.setSharedContactRequested(
url = it,
onFailure = { lifecycleScope.launch { showToast(Res.string.contact_invalid) } },
)
} else {
Timber.d("App link data is not a channel set")
Logger.d { "App link data is not a channel set" }
}
}
}
UsbManager.ACTION_USB_DEVICE_ATTACHED -> {
Timber.d("USB device attached")
Logger.d { "USB device attached" }
showSettingsPage()
}
@ -152,7 +152,7 @@ class MainActivity : AppCompatActivity() {
}
else -> {
Timber.w("Unexpected action $appLinkAction")
Logger.w { "Unexpected action $appLinkAction" }
}
}
}

View file

@ -23,6 +23,7 @@ import androidx.appcompat.app.AppCompatActivity.BIND_AUTO_CREATE
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import co.touchlab.kermit.Logger
import com.geeksville.mesh.android.BindFailedException
import com.geeksville.mesh.android.ServiceClient
import com.geeksville.mesh.concurrent.SequentialJob
@ -32,7 +33,6 @@ import dagger.hilt.android.qualifiers.ActivityContext
import dagger.hilt.android.scopes.ActivityScoped
import org.meshtastic.core.service.IMeshService
import org.meshtastic.core.service.ServiceRepository
import timber.log.Timber
import javax.inject.Inject
/** A Activity-lifecycle-aware [ServiceClient] that binds [MeshService] once the Activity is started. */
@ -49,7 +49,7 @@ constructor(
private val lifecycleOwner: LifecycleOwner = context as LifecycleOwner
init {
Timber.d("Adding self as LifecycleObserver for $lifecycleOwner")
Logger.d { "Adding self as LifecycleObserver for $lifecycleOwner" }
lifecycleOwner.lifecycle.addObserver(this)
}
@ -58,7 +58,7 @@ constructor(
override fun onConnected(service: IMeshService) {
serviceSetupJob.launch(lifecycleOwner.lifecycleScope) {
serviceRepository.setMeshService(service)
Timber.d("connected to mesh service, connectionState=${serviceRepository.connectionState.value}")
Logger.d { "connected to mesh service, connectionState=${serviceRepository.connectionState.value}" }
}
}
@ -73,32 +73,32 @@ constructor(
override fun onStart(owner: LifecycleOwner) {
super.onStart(owner)
Timber.d("Lifecycle: ON_START")
Logger.d { "Lifecycle: ON_START" }
try {
bindMeshService()
} catch (ex: BindFailedException) {
Timber.e("Bind of MeshService failed: ${ex.message}")
Logger.e { "Bind of MeshService failed: ${ex.message}" }
}
}
override fun onDestroy(owner: LifecycleOwner) {
super.onDestroy(owner)
Timber.d("Lifecycle: ON_DESTROY")
Logger.d { "Lifecycle: ON_DESTROY" }
owner.lifecycle.removeObserver(this)
Timber.d("Removed self as LifecycleObserver to $lifecycleOwner")
Logger.d { "Removed self as LifecycleObserver to $lifecycleOwner" }
}
// endregion
@Suppress("TooGenericExceptionCaught")
private fun bindMeshService() {
Timber.d("Binding to mesh service!")
Logger.d { "Binding to mesh service!" }
try {
MeshService.startService(context)
} catch (ex: Exception) {
Timber.e("Failed to start service from activity - but ignoring because bind will work: ${ex.message}")
Logger.e { "Failed to start service from activity - but ignoring because bind will work: ${ex.message}" }
}
connect(context, MeshService.createIntent(context), BIND_AUTO_CREATE + BIND_ABOVE_CLIENT)

View file

@ -18,6 +18,7 @@
package com.geeksville.mesh
import android.app.Application
import co.touchlab.kermit.Logger
import dagger.hilt.EntryPoint
import dagger.hilt.InstallIn
import dagger.hilt.android.EntryPointAccessors
@ -28,7 +29,6 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.meshtastic.core.database.DatabaseManager
import org.meshtastic.core.prefs.mesh.MeshPrefs
import timber.log.Timber
/**
* The main application class for Meshtastic.
@ -61,7 +61,7 @@ interface AppEntryPoint {
fun logAssert(executeReliableWrite: Boolean) {
if (!executeReliableWrite) {
val ex = AssertionError("Assertion failed")
Timber.e(ex)
Logger.e(ex) { "logAssert" }
throw ex
}
}

View file

@ -23,10 +23,9 @@ import android.content.Intent
import android.content.ServiceConnection
import android.os.IBinder
import android.os.IInterface
import co.touchlab.kermit.Logger
import com.geeksville.mesh.util.exceptionReporter
import timber.log.Timber
import java.io.Closeable
import java.lang.IllegalArgumentException
import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock
@ -73,14 +72,14 @@ open class ServiceClient<T : IInterface>(private val stubFactory: (IBinder) -> T
// Some phones seem to ahve a race where if you unbind and quickly rebind bindService returns false.
// Try
// a short sleep to see if that helps
Timber.e("Needed to use the second bind attempt hack")
Logger.e { "Needed to use the second bind attempt hack" }
Thread.sleep(500) // was 200ms, but received an autobug from a Galaxy Note4, android 6.0.1
if (!c.bindService(intent, connection, flags)) {
throw BindFailedException()
}
}
} else {
Timber.w("Ignoring rebind attempt for service")
Logger.w { "Ignoring rebind attempt for service" }
}
}
@ -90,7 +89,7 @@ open class ServiceClient<T : IInterface>(private val stubFactory: (IBinder) -> T
context?.unbindService(connection)
} catch (ex: IllegalArgumentException) {
// Autobugs show this can generate an illegal arg exception for "service not registered" during reinstall?
Timber.w("Ignoring error in ServiceClient.close, probably harmless")
Logger.w { "Ignoring error in ServiceClient.close, probably harmless" }
}
serviceP = null
context = null
@ -116,7 +115,7 @@ open class ServiceClient<T : IInterface>(private val stubFactory: (IBinder) -> T
// If we start to close a service, it seems that there is a possibility a onServiceConnected event
// is the queue
// for us. Be careful not to process that stale event
Timber.w("A service connected while we were closing it, ignoring")
Logger.w { "A service connected while we were closing it, ignoring" }
}
}

View file

@ -17,7 +17,7 @@
package com.geeksville.mesh.concurrent
import timber.log.Timber
import co.touchlab.kermit.Logger
/**
* Sometimes when starting services we face situations where messages come in that require computation but we can't do
@ -36,7 +36,7 @@ class DeferredExecution {
// / run all work in the queue and clear it to be ready to accept new work
fun run() {
Timber.d("Running deferred execution numjobs=${queue.size}")
Logger.d { "Running deferred execution numjobs=${queue.size}" }
queue.forEach { it() }
queue.clear()
}

View file

@ -27,7 +27,7 @@ interface Continuation<in T> {
fun resumeWithException(ex: Throwable) = try {
resume(Result.failure(ex))
} catch (ex: Throwable) {
// Timber.e("Ignoring $ex while resuming, because we are the ones who threw it")
// Logger.e { "Ignoring $ex while resuming because we are the ones who threw it" }
throw ex
}
}

View file

@ -24,6 +24,7 @@ import android.os.RemoteException
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import co.touchlab.kermit.Logger
import com.geeksville.mesh.repository.bluetooth.BluetoothRepository
import com.geeksville.mesh.repository.network.NetworkRepository
import com.geeksville.mesh.repository.network.NetworkRepository.Companion.toAddressString
@ -49,7 +50,6 @@ import org.meshtastic.core.service.ServiceRepository
import org.meshtastic.core.strings.Res
import org.meshtastic.core.strings.meshtastic
import org.meshtastic.core.ui.viewmodel.stateInWhileSubscribed
import timber.log.Timber
import javax.inject.Inject
// ... (DeviceListEntry sealed class remains the same) ...
@ -164,13 +164,13 @@ constructor(
init {
serviceRepository.statusMessage.onEach { errorText.value = it }.launchIn(viewModelScope)
Timber.d("BTScanModel created")
Logger.d { "BTScanModel created" }
}
override fun onCleared() {
super.onCleared()
bluetoothRepository.stopScan()
Timber.d("BTScanModel cleared")
Logger.d { "BTScanModel cleared" }
}
fun setErrorText(text: String) {
@ -178,7 +178,7 @@ constructor(
}
fun stopScan() {
Timber.d("stopping scan")
Logger.d { "stopping scan" }
bluetoothRepository.stopScan()
}
@ -187,7 +187,7 @@ constructor(
}
fun startScan() {
Timber.d("starting ble scan")
Logger.d { "starting ble scan" }
bluetoothRepository.startScan()
}
@ -195,24 +195,24 @@ constructor(
try {
serviceRepository.meshService?.let { service -> MeshService.changeDeviceAddress(context, service, address) }
} catch (ex: RemoteException) {
Timber.e(ex, "changeDeviceSelection failed, probably it is shutting down")
Logger.e(ex) { "changeDeviceSelection failed, probably it is shutting down" }
}
}
/** Initiates the bonding process and connects to the device upon success. */
private fun requestBonding(entry: DeviceListEntry.Ble) {
Timber.i("Starting bonding for ${entry.peripheral.address.anonymize}")
Logger.i { "Starting bonding for ${entry.peripheral.address.anonymize}" }
viewModelScope.launch {
@Suppress("TooGenericExceptionCaught")
try {
bluetoothRepository.bond(entry.peripheral)
Timber.i("Bonding complete for ${entry.peripheral.address.anonymize}, selecting device...")
Logger.i { "Bonding complete for ${entry.peripheral.address.anonymize}, selecting device..." }
changeDeviceAddress(entry.fullAddress)
} catch (ex: SecurityException) {
Timber.e(ex, "Bonding failed for ${entry.peripheral.address.anonymize} Permissions not granted")
Logger.e(ex) { "Bonding failed for ${entry.peripheral.address.anonymize} Permissions not granted" }
serviceRepository.setErrorMessage("Bonding failed: ${ex.message} Permissions not granted")
} catch (ex: Exception) {
Timber.e(ex, "Bonding failed for ${entry.peripheral.address.anonymize}")
Logger.e(ex) { "Bonding failed for ${entry.peripheral.address.anonymize}" }
serviceRepository.setErrorMessage("Bonding failed: ${ex.message}")
}
}
@ -223,10 +223,10 @@ constructor(
.requestPermission(it.driver.device)
.onEach { granted ->
if (granted) {
Timber.i("User approved USB access")
Logger.i { "User approved USB access" }
changeDeviceAddress(it.fullAddress)
} else {
Timber.e("USB permission denied for device ${it.address}")
Logger.e { "USB permission denied for device ${it.address}" }
}
}
.launchIn(viewModelScope)

View file

@ -25,6 +25,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope
import androidx.navigation.NavHostController
import co.touchlab.kermit.Logger
import com.geeksville.mesh.repository.radio.MeshActivity
import com.geeksville.mesh.repository.radio.RadioInterfaceService
import dagger.hilt.android.lifecycle.HiltViewModel
@ -67,7 +68,6 @@ import org.meshtastic.core.ui.viewmodel.stateInWhileSubscribed
import org.meshtastic.proto.AdminProtos
import org.meshtastic.proto.AppOnlyProtos
import org.meshtastic.proto.MeshProtos
import timber.log.Timber
import javax.inject.Inject
// Given a human name, strip out the first letter of the first three words and return that as the
@ -212,7 +212,7 @@ constructor(
}
.launchIn(viewModelScope)
Timber.d("ViewModel created")
Logger.d { "ViewModel created" }
}
private val _sharedContactRequested: MutableStateFlow<AdminProtos.SharedContact?> = MutableStateFlow(null)
@ -222,7 +222,7 @@ constructor(
fun setSharedContactRequested(url: Uri, onFailure: () -> Unit) {
runCatching { _sharedContactRequested.value = url.toSharedContact() }
.onFailure { ex ->
Timber.e(ex, "Shared contact error")
Logger.e(ex) { "Shared contact error" }
onFailure()
}
}
@ -243,7 +243,7 @@ constructor(
fun requestChannelUrl(url: Uri, onFailure: () -> Unit) =
runCatching { _requestChannelSet.value = url.toChannelSet() }
.onFailure { ex ->
Timber.e(ex, "Channel url error")
Logger.e(ex) { "Channel url error" }
onFailure()
}
@ -256,7 +256,7 @@ constructor(
override fun onCleared() {
super.onCleared()
Timber.d("ViewModel cleared")
Logger.d { "ViewModel cleared" }
}
val tracerouteResponse: LiveData<TracerouteResponse?>

View file

@ -22,6 +22,7 @@ import android.app.Application
import android.bluetooth.BluetoothAdapter
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.coroutineScope
import co.touchlab.kermit.Logger
import com.geeksville.mesh.repository.radio.BleConstants.BLE_NAME_PATTERN
import com.geeksville.mesh.repository.radio.BleConstants.BTM_SERVICE_UUID
import com.geeksville.mesh.util.registerReceiverCompat
@ -42,7 +43,6 @@ import no.nordicsemi.kotlin.ble.core.Manager
import org.meshtastic.core.common.hasBluetoothPermission
import org.meshtastic.core.di.CoroutineDispatchers
import org.meshtastic.core.di.ProcessLifecycle
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.time.Duration.Companion.seconds
@ -112,7 +112,7 @@ constructor(
.onStart { _isScanning.value = true }
.onCompletion { _isScanning.value = false }
.catch { ex ->
Timber.w(ex, "Bluetooth scan failed")
Logger.w(ex) { "Bluetooth scan failed" }
_isScanning.value = false
}
.collect { peripheral ->
@ -158,7 +158,7 @@ constructor(
)
_state.emit(newState)
Timber.d("Detected our bluetooth access=$newState")
Logger.d { "Detected our bluetooth access=$newState" }
}
@SuppressLint("MissingPermission")

View file

@ -17,6 +17,7 @@
package com.geeksville.mesh.repository.network
import co.touchlab.kermit.Logger
import com.geeksville.mesh.util.ignoreException
import com.google.protobuf.ByteString
import kotlinx.coroutines.channels.awaitClose
@ -36,7 +37,6 @@ import org.meshtastic.core.data.repository.RadioConfigRepository
import org.meshtastic.core.model.util.subscribeList
import org.meshtastic.proto.MeshProtos.MqttClientProxyMessage
import org.meshtastic.proto.mqttClientProxyMessage
import timber.log.Timber
import java.net.URI
import java.security.SecureRandom
import javax.inject.Inject
@ -70,7 +70,7 @@ constructor(
private var mqttClient: MqttAsyncClient? = null
fun disconnect() {
Timber.i("MQTT Disconnected")
Logger.i { "MQTT Disconnected" }
mqttClient?.apply {
ignoreException { disconnect() }
close(true)
@ -110,7 +110,7 @@ constructor(
val callback =
object : MqttCallbackExtended {
override fun connectComplete(reconnect: Boolean, serverURI: String) {
Timber.i("MQTT connectComplete: $serverURI reconnect: $reconnect")
Logger.i { "MQTT connectComplete: $serverURI reconnect: $reconnect" }
channelSet.subscribeList
.ifEmpty {
return
@ -123,7 +123,7 @@ constructor(
}
override fun connectionLost(cause: Throwable) {
Timber.i("MQTT connectionLost cause: $cause")
Logger.i { "MQTT connectionLost cause: $cause" }
if (cause is IllegalArgumentException) close(cause)
}
@ -138,7 +138,7 @@ constructor(
}
override fun deliveryComplete(token: IMqttDeliveryToken?) {
Timber.i("MQTT deliveryComplete messageId: ${token?.messageId}")
Logger.i { "MQTT deliveryComplete messageId: ${token?.messageId}" }
}
}
@ -161,15 +161,15 @@ constructor(
private fun subscribe(topic: String) {
mqttClient?.subscribe(topic, DEFAULT_QOS)
Timber.i("MQTT Subscribed to topic: $topic")
Logger.i { "MQTT Subscribed to topic: $topic" }
}
fun publish(topic: String, data: ByteArray, retained: Boolean) {
try {
val token = mqttClient?.publish(topic, data, DEFAULT_QOS, retained)
Timber.i("MQTT Publish messageId: ${token?.messageId}")
Logger.i { "MQTT Publish messageId: ${token?.messageId}" }
} catch (ex: Exception) {
Timber.e("MQTT Publish error: ${ex.message}")
Logger.e { "MQTT Publish error: ${ex.message}" }
}
}
}

View file

@ -21,6 +21,7 @@ import android.annotation.SuppressLint
import android.net.nsd.NsdManager
import android.net.nsd.NsdServiceInfo
import android.os.Build
import co.touchlab.kermit.Logger
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.asExecutor
@ -30,7 +31,6 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.suspendCancellableCoroutine
import timber.log.Timber
import java.util.concurrent.CopyOnWriteArrayList
import kotlin.coroutines.resume
@ -54,22 +54,22 @@ private fun NsdManager.discoverServices(
}
override fun onDiscoveryStarted(serviceType: String) {
Timber.d("NSD Service discovery started")
Logger.d { "NSD Service discovery started" }
}
override fun onDiscoveryStopped(serviceType: String) {
Timber.d("NSD Service discovery stopped")
Logger.d { "NSD Service discovery stopped" }
close()
}
override fun onServiceFound(serviceInfo: NsdServiceInfo) {
Timber.d("NSD Service found: $serviceInfo")
Logger.d { "NSD Service found: $serviceInfo" }
serviceList += serviceInfo
trySend(serviceList)
}
override fun onServiceLost(serviceInfo: NsdServiceInfo) {
Timber.d("NSD Service lost: $serviceInfo")
Logger.d { "NSD Service lost: $serviceInfo" }
serviceList.removeAll { it.serviceName == serviceInfo.serviceName }
trySend(serviceList)
}
@ -102,7 +102,7 @@ private suspend fun NsdManager.resolveService(serviceInfo: NsdServiceInfo): NsdS
try {
unregisterServiceInfoCallback(this)
} catch (e: IllegalArgumentException) {
Timber.w(e, "Already unregistered")
Logger.w(e) { "Already unregistered" }
}
}
}
@ -112,7 +112,7 @@ private suspend fun NsdManager.resolveService(serviceInfo: NsdServiceInfo): NsdS
try {
unregisterServiceInfoCallback(this)
} catch (e: IllegalArgumentException) {
Timber.w(e, "Already unregistered")
Logger.w(e) { "Already unregistered" }
}
}
@ -125,7 +125,7 @@ private suspend fun NsdManager.resolveService(serviceInfo: NsdServiceInfo): NsdS
try {
unregisterServiceInfoCallback(callback)
} catch (e: IllegalArgumentException) {
Timber.w(e, "Already unregistered")
Logger.w(e) { "Already unregistered" }
}
}
} else {

View file

@ -17,6 +17,7 @@
package com.geeksville.mesh.repository.radio
import co.touchlab.kermit.Logger
import com.geeksville.mesh.concurrent.handledLaunch
import com.geeksville.mesh.model.getInitials
import com.google.protobuf.ByteString
@ -38,7 +39,6 @@ import org.meshtastic.proto.config
import org.meshtastic.proto.deviceMetadata
import org.meshtastic.proto.fromRadio
import org.meshtastic.proto.queueStatus
import timber.log.Timber
import kotlin.random.Random
private val defaultLoRaConfig =
@ -71,7 +71,7 @@ constructor(
private val packetIdSequence = generateSequence { currentPacketId++ }.iterator()
init {
Timber.i("Starting the mock interface")
Logger.i { "Starting the mock interface" }
service.onConnect() // Tell clients they can use the API
}
@ -86,7 +86,7 @@ constructor(
data != null && data.portnum == Portnums.PortNum.ADMIN_APP ->
handleAdminPacket(pr, AdminProtos.AdminMessage.parseFrom(data.payload))
pr.hasPacket() && pr.packet.wantAck -> sendFakeAck(pr)
else -> Timber.i("Ignoring data sent to mock interface $pr")
else -> Logger.i { "Ignoring data sent to mock interface $pr" }
}
}
@ -108,12 +108,12 @@ constructor(
}
}
else -> Timber.i("Ignoring admin sent to mock interface $d")
else -> Logger.i { "Ignoring admin sent to mock interface $d" }
}
}
override fun close() {
Timber.i("Closing the mock interface")
Logger.i { "Closing the mock interface" }
}
// / Generate a fake text message from a node
@ -297,7 +297,7 @@ constructor(
}
private fun sendConfigResponse(configId: Int) {
Timber.d("Sending mock config response")
Logger.d { "Sending mock config response" }
// / Generate a fake node info entry
@Suppress("MagicNumber")

View file

@ -18,6 +18,7 @@
package com.geeksville.mesh.repository.radio
import android.annotation.SuppressLint
import co.touchlab.kermit.Logger
import com.geeksville.mesh.repository.radio.BleConstants.BTM_FROMNUM_CHARACTER
import com.geeksville.mesh.repository.radio.BleConstants.BTM_FROMRADIO_CHARACTER
import com.geeksville.mesh.repository.radio.BleConstants.BTM_LOGRADIO_CHARACTER
@ -52,7 +53,6 @@ import no.nordicsemi.kotlin.ble.client.android.Peripheral
import no.nordicsemi.kotlin.ble.core.CharacteristicProperty
import no.nordicsemi.kotlin.ble.core.ConnectionState
import no.nordicsemi.kotlin.ble.core.WriteType
import timber.log.Timber
import java.util.UUID
import kotlin.uuid.ExperimentalUuidApi
import kotlin.uuid.toKotlinUuid
@ -79,12 +79,12 @@ constructor(
) : IRadioInterface {
private val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
Timber.e(throwable, "[$address] Uncaught exception in connectionScope")
Logger.e(throwable) { "[$address] Uncaught exception in connectionScope" }
serviceScope.launch {
try {
peripheral?.disconnect()
} catch (e: Exception) {
Timber.e(e, "[$address] Failed to disconnect in exception handler")
Logger.e(e) { "[$address] Failed to disconnect in exception handler" }
}
}
service.onDisconnect(BleError.from(throwable))
@ -118,7 +118,7 @@ constructor(
val packet =
fromRadioCharacteristic?.read()?.takeIf { it.isNotEmpty() }
?: run {
Timber.d("[$address] fromRadio queue drain complete (read empty/null)")
Logger.d { "[$address] fromRadio queue drain complete (read empty/null)" }
break
}
send(packet)
@ -128,14 +128,14 @@ constructor(
private fun dispatchPacket(packet: ByteArray) {
packetsReceived++
bytesReceived += packet.size
Timber.d(
Logger.d {
"[$address] Dispatching packet to service.handleFromRadio() - " +
"Packet #$packetsReceived, ${packet.size} bytes (Total: $bytesReceived bytes)",
)
"Packet #$packetsReceived, ${packet.size} bytes (Total: $bytesReceived bytes)"
}
try {
service.handleFromRadio(p = packet)
} catch (t: Throwable) {
Timber.e(t, "[$address] Failed to execute service.handleFromRadio()")
Logger.e(t) { "[$address] Failed to execute service.handleFromRadio()" }
}
}
@ -145,13 +145,13 @@ constructor(
fromRadioPacketFlow()
.onEach { packet ->
drainedCount++
Timber.d("[$address] Read packet from queue (${packet.size} bytes)")
Logger.d { "[$address] Read packet from queue (${packet.size} bytes)" }
dispatchPacket(packet)
}
.catch { ex -> Timber.w(ex, "[$address] Exception while draining packet queue") }
.catch { ex -> Logger.w(ex) { "[$address] Exception while draining packet queue" } }
.onCompletion {
if (drainedCount > 0) {
Timber.d("[$address] Drained $drainedCount packets from packet queue")
Logger.d { "[$address] Drained $drainedCount packets from packet queue" }
}
}
.collect()
@ -168,19 +168,19 @@ constructor(
connectionScope.launch {
try {
connectionStartTime = System.currentTimeMillis()
Timber.i("[$address] BLE connection attempt started at $connectionStartTime")
Logger.i { "[$address] BLE connection attempt started at $connectionStartTime" }
peripheral = retryCall { findAndConnectPeripheral() }
peripheral?.let {
val connectionTime = System.currentTimeMillis() - connectionStartTime
Timber.i("[$address] BLE peripheral connected in ${connectionTime}ms")
Logger.i { "[$address] BLE peripheral connected in ${connectionTime}ms" }
onConnected()
observePeripheralChanges()
discoverServicesAndSetupCharacteristics(it)
}
} catch (e: Exception) {
val failureTime = System.currentTimeMillis() - connectionStartTime
Timber.e(e, "[$address] Failed to connect to peripheral after ${failureTime}ms")
Logger.e(e) { "[$address] Failed to connect to peripheral after ${failureTime}ms" }
service.onDisconnect(BleError.from(e))
}
}
@ -200,27 +200,27 @@ constructor(
try {
peripheral?.let { p ->
val rssi = retryCall { p.readRssi() }
Timber.d("[$address] Connection established. RSSI: $rssi dBm")
Logger.d { "[$address] Connection established. RSSI: $rssi dBm" }
val phyInUse = retryCall { p.readPhy() }
Timber.d("[$address] PHY in use: $phyInUse")
Logger.d { "[$address] PHY in use: $phyInUse" }
}
} catch (e: Exception) {
Timber.w(e, "[$address] Failed to read initial connection properties")
Logger.w(e) { "[$address] Failed to read initial connection properties" }
}
}
private fun observePeripheralChanges() {
peripheral?.let { p ->
p.phy.onEach { phy -> Timber.i("[$address] BLE PHY changed to $phy") }.launchIn(connectionScope)
p.phy.onEach { phy -> Logger.i { "[$address] BLE PHY changed to $phy" } }.launchIn(connectionScope)
p.connectionParameters
.onEach { params -> Timber.i("[$address] BLE connection parameters changed to $params") }
.onEach { params -> Logger.i { "[$address] BLE connection parameters changed to $params" } }
.launchIn(connectionScope)
p.state
.onEach { state ->
Timber.i("[$address] BLE connection state changed to $state")
Logger.i { "[$address] BLE connection state changed to $state" }
if (state is ConnectionState.Disconnected) {
val uptime =
if (connectionStartTime > 0) {
@ -228,19 +228,19 @@ constructor(
} else {
0
}
Timber.w(
Logger.w {
"[$address] BLE disconnected - Reason: ${state.reason}, " +
"Uptime: ${uptime}ms, " +
"Packets RX: $packetsReceived ($bytesReceived bytes), " +
"Packets TX: $packetsSent ($bytesSent bytes)",
)
"Packets TX: $packetsSent ($bytesSent bytes)"
}
service.onDisconnect(BleError.Disconnected(reason = state.reason))
}
}
.launchIn(connectionScope)
}
centralManager.state
.onEach { state -> Timber.i("[$address] CentralManager state changed to $state") }
.onEach { state -> Logger.i { "[$address] CentralManager state changed to $state" } }
.launchIn(connectionScope)
}
@ -268,35 +268,35 @@ constructor(
it != null
}
) {
Timber.d(
"[$address] Found toRadio: ${toRadioCharacteristic?.uuid}, ${toRadioCharacteristic?.instanceId}",
)
Timber.d(
"[$address] Found fromNum: ${fromNumCharacteristic?.uuid}, ${fromNumCharacteristic?.instanceId}",
)
Timber.d(
"[$address] Found fromRadio: ${fromRadioCharacteristic?.uuid}, ${fromRadioCharacteristic?.instanceId}",
)
Timber.d(
"[$address] Found logRadio: ${logRadioCharacteristic?.uuid}, ${logRadioCharacteristic?.instanceId}",
)
Logger.d {
"[$address] Found toRadio: ${toRadioCharacteristic?.uuid}, ${toRadioCharacteristic?.instanceId}"
}
Logger.d {
"[$address] Found fromNum: ${fromNumCharacteristic?.uuid}, ${fromNumCharacteristic?.instanceId}"
}
Logger.d {
"[$address] Found fromRadio: ${fromRadioCharacteristic?.uuid}, ${fromRadioCharacteristic?.instanceId}"
}
Logger.d {
"[$address] Found logRadio: ${logRadioCharacteristic?.uuid}, ${logRadioCharacteristic?.instanceId}"
}
setupNotifications()
service.onConnect()
} else {
Timber.w("[$address] Discovery failed: missing required characteristics")
Logger.w { "[$address] Discovery failed: missing required characteristics" }
service.onDisconnect(BleError.DiscoveryFailed("One or more characteristics not found"))
}
} else {
Timber.w("[$address] Discovery failed: Meshtastic service not found")
Logger.w { "[$address] Discovery failed: Meshtastic service not found" }
service.onDisconnect(BleError.DiscoveryFailed("Meshtastic service not found"))
}
}
.catch { e ->
Timber.e(e, "[$address] Service discovery failed")
Logger.e(e) { "[$address] Service discovery failed" }
try {
peripheral.disconnect()
} catch (e2: Exception) {
Timber.e(e2, "[$address] Failed to disconnect in discovery catch")
Logger.e(e2) { "[$address] Failed to disconnect in discovery catch" }
}
service.onDisconnect(BleError.from(e))
}
@ -309,29 +309,29 @@ constructor(
@OptIn(ExperimentalUuidApi::class)
private suspend fun setupNotifications() {
retryCall { fromNumCharacteristic?.subscribe() }
?.onStart { Timber.d("[$address] Subscribing to fromNumCharacteristic") }
?.onStart { Logger.d { "[$address] Subscribing to fromNumCharacteristic" } }
?.onEach { notifyBytes ->
Timber.d("[$address] FromNum Notification (${notifyBytes.size} bytes), draining queue")
Logger.d { "[$address] FromNum Notification (${notifyBytes.size} bytes), draining queue" }
connectionScope.launch { drainPacketQueueAndDispatch() }
}
?.catch { e ->
Timber.e(e, "[$address] Error subscribing to fromNumCharacteristic")
Logger.e(e) { "[$address] Error subscribing to fromNumCharacteristic" }
service.onDisconnect(BleError.from(e))
}
?.onCompletion { cause -> Timber.d("[$address] fromNum sub flow completed, cause=$cause") }
?.onCompletion { cause -> Logger.d { "[$address] fromNum sub flow completed, cause=$cause" } }
?.launchIn(scope = connectionScope)
retryCall { logRadioCharacteristic?.subscribe() }
?.onStart { Timber.d("[$address] Subscribing to logRadioCharacteristic") }
?.onStart { Logger.d { "[$address] Subscribing to logRadioCharacteristic" } }
?.onEach { notifyBytes ->
Timber.d("[$address] LogRadio Notification (${notifyBytes.size} bytes), dispatching packet")
Logger.d { "[$address] LogRadio Notification (${notifyBytes.size} bytes), dispatching packet" }
dispatchPacket(notifyBytes)
}
?.catch { e ->
Timber.e(e, "[$address] Error subscribing to logRadioCharacteristic")
Logger.e(e) { "[$address] Error subscribing to logRadioCharacteristic" }
service.onDisconnect(BleError.from(e))
}
?.onCompletion { cause -> Timber.d("[$address] logRadio sub flow completed, cause=$cause") }
?.onCompletion { cause -> Logger.d { "[$address] logRadio sub flow completed, cause=$cause" } }
?.launchIn(scope = connectionScope)
}
@ -345,14 +345,13 @@ constructor(
} catch (e: Exception) {
currentAttempt++
if (currentAttempt >= RETRY_COUNT) {
Timber.e(e, "[$address] BLE operation failed after $RETRY_COUNT attempts, giving up")
Logger.e(e) { "[$address] BLE operation failed after $RETRY_COUNT attempts, giving up" }
throw e
}
Timber.w(
e,
Logger.w(e) {
"[$address] BLE operation failed (attempt $currentAttempt/$RETRY_COUNT), " +
"retrying in ${RETRY_DELAY_MS}ms...",
)
"retrying in ${RETRY_DELAY_MS}ms..."
}
delay(RETRY_DELAY_MS)
}
}
@ -368,7 +367,7 @@ constructor(
override fun handleSendToRadio(p: ByteArray) {
toRadioCharacteristic?.let { characteristic ->
if (peripheral == null) {
Timber.w("[$address] BLE peripheral is null, cannot send packet")
Logger.w { "[$address] BLE peripheral is null, cannot send packet" }
return@let
}
connectionScope.launch {
@ -383,24 +382,23 @@ constructor(
retryCall {
packetsSent++
bytesSent += p.size
Timber.d(
Logger.d {
"[$address] Writing packet #$packetsSent to toRadioCharacteristic with $writeType - " +
"${p.size} bytes (Total TX: $bytesSent bytes)",
)
"${p.size} bytes (Total TX: $bytesSent bytes)"
}
characteristic.write(p, writeType = writeType)
}
drainPacketQueueAndDispatch()
} catch (e: Exception) {
Timber.e(
e,
Logger.e(e) {
"[$address] Failed to write packet to toRadioCharacteristic after " +
"$packetsSent successful writes",
)
"$packetsSent successful writes"
}
service.onDisconnect(BleError.from(e))
}
}
}
} ?: Timber.w("[$address] toRadio characteristic unavailable, can't send data")
} ?: Logger.w { "[$address] toRadio characteristic unavailable, can't send data" }
}
/** Closes the connection to the device. */
@ -412,12 +410,12 @@ constructor(
} else {
0
}
Timber.i(
Logger.i {
"[$address] BLE close() called - " +
"Uptime: ${uptime}ms, " +
"Packets RX: $packetsReceived ($bytesReceived bytes), " +
"Packets TX: $packetsSent ($bytesSent bytes)",
)
"Packets TX: $packetsSent ($bytesSent bytes)"
}
connectionScope.cancel()
peripheral?.disconnect()
service.onDisconnect(true)

View file

@ -17,9 +17,9 @@
package com.geeksville.mesh.repository.radio
import co.touchlab.kermit.Logger
import com.geeksville.mesh.repository.bluetooth.BluetoothRepository
import org.meshtastic.core.model.util.anonymize
import timber.log.Timber
import javax.inject.Inject
/** Bluetooth backend implementation. */
@ -35,7 +35,7 @@ constructor(
override fun addressValid(rest: String): Boolean {
val allPaired = bluetoothRepository.state.value.bondedDevices.map { it.address }.toSet()
return if (!allPaired.contains(rest)) {
Timber.w("Ignoring stale bond to ${rest.anonymize}")
Logger.w { "Ignoring stale bond to ${rest.anonymize}" }
false
} else {
true

View file

@ -21,6 +21,7 @@ import android.app.Application
import android.provider.Settings
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.coroutineScope
import co.touchlab.kermit.Logger
import com.geeksville.mesh.BuildConfig
import com.geeksville.mesh.android.BinaryLogFile
import com.geeksville.mesh.android.BuildUtils
@ -50,7 +51,6 @@ import org.meshtastic.core.model.util.anonymize
import org.meshtastic.core.prefs.radio.RadioPrefs
import org.meshtastic.core.service.ConnectionState
import org.meshtastic.proto.MeshProtos
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton
@ -141,7 +141,7 @@ constructor(
fun keepAlive(now: Long = System.currentTimeMillis()) {
if (now - lastHeartbeatMillis > HEARTBEAT_INTERVAL_MILLIS) {
if (radioIf is SerialInterface) {
Timber.i("Sending ToRadio heartbeat")
Logger.i { "Sending ToRadio heartbeat" }
val heartbeat =
MeshProtos.ToRadio.newBuilder().setHeartbeat(MeshProtos.Heartbeat.getDefaultInstance()).build()
handleSendToRadio(heartbeat.toByteArray())
@ -206,7 +206,7 @@ constructor(
}
private fun broadcastConnectionChanged(newState: ConnectionState) {
Timber.d("Broadcasting connection state change to $newState")
Logger.d { "Broadcasting connection state change to $newState" }
processLifecycle.coroutineScope.launch(dispatchers.default) { _connectionState.emit(newState) }
}
@ -223,7 +223,7 @@ constructor(
receivedPacketsLog.write(p)
receivedPacketsLog.flush()
} catch (t: Throwable) {
Timber.w(t, "Failed to write receive log in handleFromRadio")
Logger.w(t) { "Failed to write receive log in handleFromRadio" }
}
}
@ -231,13 +231,13 @@ constructor(
keepAlive(System.currentTimeMillis())
}
// ignoreException { Timber.d("FromRadio: ${MeshProtos.FromRadio.parseFrom(p)}") }
// ignoreException { Logger.d { "FromRadio: ${MeshProtos.FromRadio.parseFrom(p }}" } }
try {
processLifecycle.coroutineScope.launch(dispatchers.io) { _receivedData.emit(p) }
emitReceiveActivity()
} catch (t: Throwable) {
Timber.e(t, "RadioInterfaceService.handleFromRadio failed while emitting data")
Logger.e(t) { "RadioInterfaceService.handleFromRadio failed while emitting data" }
}
}
@ -262,13 +262,13 @@ constructor(
/** Start our configured interface (if it isn't already running) */
private fun startInterface() {
if (radioIf !is NopInterface) {
Timber.w("Can't start interface - $radioIf is already running")
Logger.w { "Can't start interface - $radioIf is already running" }
} else {
val address = getBondedDeviceAddress()
if (address == null) {
Timber.w("No bonded mesh radio, can't start interface")
Logger.w { "No bonded mesh radio, can't start interface" }
} else {
Timber.i("Starting radio ${address.anonymize}")
Logger.i { "Starting radio ${address.anonymize}" }
isStarted = true
if (logSends) {
@ -285,7 +285,7 @@ constructor(
private fun stopInterface() {
val r = radioIf
Timber.i("stopping interface $r")
Logger.i { "stopping interface $r" }
isStarted = false
radioIf = interfaceFactory.nopInterface
r.close()
@ -314,7 +314,7 @@ constructor(
*/
private fun setBondedDeviceAddress(address: String?): Boolean =
if (getBondedDeviceAddress() == address && isStarted && _connectionState.value == ConnectionState.Connected) {
Timber.w("Ignoring setBondedDevice ${address.anonymize}, because we are already using that device")
Logger.w { "Ignoring setBondedDevice ${address.anonymize}, because we are already using that device" }
false
} else {
// Record that this use has configured a new radio
@ -325,7 +325,7 @@ constructor(
// The device address "n" can be used to mean none
Timber.d("Setting bonded device to ${address.anonymize}")
Logger.d { "Setting bonded device to ${address.anonymize}" }
// Stores the address if non-null, otherwise removes the pref
radioPrefs.devAddr = address
@ -366,14 +366,14 @@ constructor(
// Use tryEmit for SharedFlow as it's non-blocking
val emitted = _meshActivity.tryEmit(MeshActivity.Send)
if (!emitted) {
Timber.d("MeshActivity.Send event was not emitted due to buffer overflow or no collectors")
Logger.d { "MeshActivity.Send event was not emitted due to buffer overflow or no collectors" }
}
}
private fun emitReceiveActivity() {
val emitted = _meshActivity.tryEmit(MeshActivity.Receive)
if (!emitted) {
Timber.d("MeshActivity.Receive event was not emitted due to buffer overflow or no collectors")
Logger.d { "MeshActivity.Receive event was not emitted due to buffer overflow or no collectors" }
}
}
}

View file

@ -17,12 +17,12 @@
package com.geeksville.mesh.repository.radio
import co.touchlab.kermit.Logger
import com.geeksville.mesh.repository.usb.SerialConnection
import com.geeksville.mesh.repository.usb.SerialConnectionListener
import com.geeksville.mesh.repository.usb.UsbRepository
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import timber.log.Timber
import java.util.concurrent.atomic.AtomicReference
/** An interface that assumes we are talking to a meshtastic device via USB serial */
@ -48,10 +48,10 @@ constructor(
override fun connect() {
val device = serialInterfaceSpec.findSerial(address)
if (device == null) {
Timber.e("[$address] Serial device not found at address")
Logger.e { "[$address] Serial device not found at address" }
} else {
val connectStart = System.currentTimeMillis()
Timber.i("[$address] Opening serial device: $device")
Logger.i { "[$address] Opening serial device: $device" }
var packetsReceived = 0
var bytesReceived = 0L
@ -60,7 +60,7 @@ constructor(
val onConnect: () -> Unit = {
connectionStartTime = System.currentTimeMillis()
val connectionTime = connectionStartTime - connectStart
Timber.i("[$address] Serial device connected in ${connectionTime}ms")
Logger.i { "[$address] Serial device connected in ${connectionTime}ms" }
super.connect()
}
@ -69,9 +69,9 @@ constructor(
device,
object : SerialConnectionListener {
override fun onMissingPermission() {
Timber.e(
"[$address] Serial connection failed - missing USB permissions for device: $device",
)
Logger.e {
"[$address] Serial connection failed - missing USB permissions for device: $device"
}
}
override fun onConnected() {
@ -81,10 +81,10 @@ constructor(
override fun onDataReceived(bytes: ByteArray) {
packetsReceived++
bytesReceived += bytes.size
Timber.d(
Logger.d {
"[$address] Serial received packet #$packetsReceived - " +
"${bytes.size} byte(s) (Total RX: $bytesReceived bytes)",
)
"${bytes.size} byte(s) (Total RX: $bytesReceived bytes)"
}
bytes.forEach(::readChar)
}
@ -95,13 +95,15 @@ constructor(
} else {
0
}
thrown?.let { e -> Timber.e(e, "[$address] Serial error after ${uptime}ms: ${e.message}") }
Timber.w(
thrown?.let { e ->
Logger.e(e) { "[$address] Serial error after ${uptime}ms: ${e.message}" }
}
Logger.w {
"[$address] Serial device disconnected - " +
"Device: $device, " +
"Uptime: ${uptime}ms, " +
"Packets RX: $packetsReceived ($bytesReceived bytes)",
)
"Packets RX: $packetsReceived ($bytesReceived bytes)"
}
onDeviceDisconnect(false)
}
},
@ -116,10 +118,10 @@ constructor(
override fun sendBytes(p: ByteArray) {
val conn = connRef.get()
if (conn != null) {
Timber.d("[$address] Serial sending ${p.size} bytes")
Logger.d { "[$address] Serial sending ${p.size} bytes" }
conn.sendBytes(p)
} else {
Timber.w("[$address] Serial connection not available, cannot send ${p.size} bytes")
Logger.w { "[$address] Serial connection not available, cannot send ${p.size} bytes" }
}
}
}

View file

@ -17,7 +17,7 @@
package com.geeksville.mesh.repository.radio
import timber.log.Timber
import co.touchlab.kermit.Logger
/**
* An interface that assumes we are talking to a meshtastic device over some sort of stream connection (serial or TCP
@ -41,7 +41,7 @@ abstract class StreamInterface(protected val service: RadioInterfaceService) : I
private var packetLen = 0
override fun close() {
Timber.d("Closing stream for good")
Logger.d { "Closing stream for good" }
onDeviceDisconnect(true)
}
@ -90,7 +90,7 @@ abstract class StreamInterface(protected val service: RadioInterfaceService) : I
when (val c = b.toInt().toChar()) {
'\r' -> {} // ignore
'\n' -> {
Timber.d("DeviceLog: $debugLineBuf")
Logger.d { "DeviceLog: $debugLineBuf" }
debugLineBuf.clear()
}
else -> debugLineBuf.append(c)
@ -104,7 +104,7 @@ abstract class StreamInterface(protected val service: RadioInterfaceService) : I
var nextPtr = ptr + 1
fun lostSync() {
Timber.e("Lost protocol sync")
Logger.e { "Lost protocol sync" }
nextPtr = 0
}

View file

@ -17,6 +17,7 @@
package com.geeksville.mesh.repository.radio
import co.touchlab.kermit.Logger
import com.geeksville.mesh.concurrent.handledLaunch
import com.geeksville.mesh.repository.network.NetworkRepository
import com.geeksville.mesh.util.Exceptions
@ -25,7 +26,6 @@ import dagger.assisted.AssistedInject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
import timber.log.Timber
import java.io.BufferedInputStream
import java.io.BufferedOutputStream
import java.io.IOException
@ -67,12 +67,12 @@ class TCPInterface @AssistedInject constructor(service: RadioInterfaceService, @
override fun sendBytes(p: ByteArray) {
packetsSent++
bytesSent += p.size
Timber.d("[$address] TCP sending packet #$packetsSent - ${p.size} bytes (Total TX: $bytesSent bytes)")
Logger.d { "[$address] TCP sending packet #$packetsSent - ${p.size} bytes (Total TX: $bytesSent bytes)" }
outStream.write(p)
}
override fun flushBytes() {
Timber.d("[$address] TCP flushing output stream")
Logger.d { "[$address] TCP flushing output stream" }
outStream.flush()
}
@ -85,13 +85,13 @@ class TCPInterface @AssistedInject constructor(service: RadioInterfaceService, @
} else {
0
}
Timber.w(
Logger.w {
"[$address] TCP disconnecting - " +
"Uptime: ${uptime}ms, " +
"Packets RX: $packetsReceived ($bytesReceived bytes), " +
"Packets TX: $packetsSent ($bytesSent bytes), " +
"Timeout events: $timeoutEvents",
)
"Timeout events: $timeoutEvents"
}
s.close()
socket = null
}
@ -110,7 +110,7 @@ class TCPInterface @AssistedInject constructor(service: RadioInterfaceService, @
} else {
0
}
Timber.e(ex, "[$address] TCP IOException after ${uptime}ms - ${ex.message}")
Logger.e(ex) { "[$address] TCP IOException after ${uptime}ms - ${ex.message}" }
onDeviceDisconnect(false)
} catch (ex: Throwable) {
val uptime =
@ -119,38 +119,38 @@ class TCPInterface @AssistedInject constructor(service: RadioInterfaceService, @
} else {
0
}
Timber.e(ex, "[$address] TCP exception after ${uptime}ms - ${ex.message}")
Logger.e(ex) { "[$address] TCP exception after ${uptime}ms - ${ex.message}" }
Exceptions.report(ex, "Exception in TCP reader")
onDeviceDisconnect(false)
}
if (retryCount > MAX_RETRIES_ALLOWED) {
Timber.e("[$address] TCP max retries ($MAX_RETRIES_ALLOWED) exceeded, giving up")
Logger.e { "[$address] TCP max retries ($MAX_RETRIES_ALLOWED) exceeded, giving up" }
break
}
Timber.i(
Logger.i {
"[$address] TCP reconnect attempt #$retryCount in ${backoffDelay / 1000}s " +
"(backoff: ${backoffDelay}ms)",
)
"(backoff: ${backoffDelay}ms)"
}
delay(backoffDelay)
retryCount++
backoffDelay = minOf(backoffDelay * 2, MAX_BACKOFF_MILLIS)
}
Timber.i("[$address] TCP reader exiting")
Logger.i { "[$address] TCP reader exiting" }
}
}
// Create a socket to make the connection with the server
private suspend fun startConnect() = withContext(Dispatchers.IO) {
val attemptStart = System.currentTimeMillis()
Timber.i("[$address] TCP connection attempt starting...")
Logger.i { "[$address] TCP connection attempt starting..." }
val (host, port) =
address.split(":", limit = 2).let { it[0] to (it.getOrNull(1)?.toIntOrNull() ?: SERVICE_PORT) }
Timber.d("[$address] Resolving host '$host' and connecting to port $port...")
Logger.d { "[$address] Resolving host '$host' and connecting to port $port..." }
Socket(InetAddress.getByName(host), port).use { socket ->
socket.tcpNoDelay = true
@ -159,10 +159,10 @@ class TCPInterface @AssistedInject constructor(service: RadioInterfaceService, @
val connectTime = System.currentTimeMillis() - attemptStart
connectionStartTime = System.currentTimeMillis()
Timber.i(
Logger.i {
"[$address] TCP socket connected in ${connectTime}ms - " +
"Local: ${socket.localSocketAddress}, Remote: ${socket.remoteSocketAddress}",
)
"Local: ${socket.localSocketAddress}, Remote: ${socket.remoteSocketAddress}"
}
BufferedOutputStream(socket.getOutputStream()).use { outputStream ->
outStream = outputStream
@ -178,7 +178,9 @@ class TCPInterface @AssistedInject constructor(service: RadioInterfaceService, @
try { // close after 90s of inactivity
val c = inputStream.read()
if (c == -1) {
Timber.w("[$address] TCP got EOF on stream after $packetsReceived packets received")
Logger.w {
"[$address] TCP got EOF on stream after $packetsReceived packets received"
}
break
} else {
timeoutCount = 0
@ -190,20 +192,20 @@ class TCPInterface @AssistedInject constructor(service: RadioInterfaceService, @
timeoutCount++
timeoutEvents++
if (timeoutCount % TIMEOUT_LOG_INTERVAL == 0) {
Timber.d(
Logger.d {
"[$address] TCP socket timeout count: $timeoutCount/$SOCKET_RETRIES " +
"(total timeouts: $timeoutEvents)",
)
"(total timeouts: $timeoutEvents)"
}
}
// Ignore and start another read
}
}
if (timeoutCount >= SOCKET_RETRIES) {
val inactivityMs = SOCKET_RETRIES * SOCKET_TIMEOUT
Timber.w(
Logger.w {
"[$address] TCP closing connection due to $SOCKET_RETRIES consecutive timeouts " +
"(${inactivityMs}ms of inactivity)",
)
"(${inactivityMs}ms of inactivity)"
}
}
}
}

View file

@ -18,11 +18,11 @@
package com.geeksville.mesh.repository.usb
import android.hardware.usb.UsbManager
import co.touchlab.kermit.Logger
import com.geeksville.mesh.util.ignoreException
import com.hoho.android.usbserial.driver.UsbSerialDriver
import com.hoho.android.usbserial.driver.UsbSerialPort
import com.hoho.android.usbserial.util.SerialInputOutputManager
import timber.log.Timber
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicBoolean
@ -40,7 +40,7 @@ internal class SerialConnectionImpl(
override fun sendBytes(bytes: ByteArray) {
ioRef.get()?.let {
Timber.d("writing ${bytes.size} byte(s)")
Logger.d { "writing ${bytes.size} byte(s }" }
it.writeAsync(bytes)
}
}
@ -54,7 +54,7 @@ internal class SerialConnectionImpl(
// Allow a short amount of time for the manager to quit (so the port can be cleanly closed)
if (waitForStopped) {
Timber.d("Waiting for USB manager to stop...")
Logger.d { "Waiting for USB manager to stop..." }
closedLatch.await(1, TimeUnit.SECONDS)
}
}
@ -80,7 +80,7 @@ internal class SerialConnectionImpl(
port.dtr = true
port.rts = true
Timber.d("Starting serial reader thread")
Logger.d { "Starting serial reader thread" }
val io =
SerialInputOutputManager(
port,

View file

@ -23,9 +23,9 @@ import android.content.Intent
import android.content.IntentFilter
import android.hardware.usb.UsbDevice
import android.hardware.usb.UsbManager
import co.touchlab.kermit.Logger
import com.geeksville.mesh.util.exceptionReporter
import com.geeksville.mesh.util.getParcelableExtraCompat
import timber.log.Timber
import javax.inject.Inject
/** A helper class to call onChanged when bluetooth is enabled or disabled or when permissions are changed. */
@ -44,15 +44,15 @@ class UsbBroadcastReceiver @Inject constructor(private val usbRepository: UsbRep
when (intent.action) {
UsbManager.ACTION_USB_DEVICE_DETACHED -> {
Timber.d("USB device '$deviceName' was detached")
Logger.d { "USB device '$deviceName' was detached" }
usbRepository.refreshState()
}
UsbManager.ACTION_USB_DEVICE_ATTACHED -> {
Timber.d("USB device '$deviceName' was attached")
Logger.d { "USB device '$deviceName' was attached" }
usbRepository.refreshState()
}
UsbManager.EXTRA_PERMISSION_GRANTED -> {
Timber.d("USB device '$deviceName' was granted permission")
Logger.d { "USB device '$deviceName' was granted permission" }
usbRepository.refreshState()
}
}

View file

@ -30,6 +30,7 @@ import android.util.Log
import androidx.annotation.VisibleForTesting
import androidx.core.app.ServiceCompat
import androidx.core.location.LocationCompat
import co.touchlab.kermit.Logger
import com.geeksville.mesh.BuildConfig
import com.geeksville.mesh.concurrent.handledLaunch
import com.geeksville.mesh.model.NO_DEVICE_SELECTED
@ -127,7 +128,6 @@ import org.meshtastic.proto.fromRadio
import org.meshtastic.proto.position
import org.meshtastic.proto.telemetry
import org.meshtastic.proto.user
import timber.log.Timber
import java.util.ArrayDeque
import java.util.Locale
import java.util.UUID
@ -280,12 +280,20 @@ class MeshService : Service() {
crossinline message: () -> String,
) {
if (!BuildConfig.DEBUG) return
val timber = Timber.tag(HISTORY_TAG)
val severity =
when (priority) {
Log.VERBOSE -> co.touchlab.kermit.Severity.Verbose
Log.DEBUG -> co.touchlab.kermit.Severity.Debug
Log.INFO -> co.touchlab.kermit.Severity.Info
Log.WARN -> co.touchlab.kermit.Severity.Warn
Log.ERROR -> co.touchlab.kermit.Severity.Error
else -> co.touchlab.kermit.Severity.Info
}
val msg = message()
if (throwable != null) {
timber.log(priority, throwable, msg)
Logger.log(severity, HISTORY_TAG, throwable, msg)
} else {
timber.log(priority, msg)
Logger.log(severity, HISTORY_TAG, null, msg)
}
}
@ -350,7 +358,7 @@ class MeshService : Service() {
private fun stopLocationRequests() {
if (locationFlow?.isActive == true) {
Timber.i("Stopping location requests")
Logger.i { "Stopping location requests" }
locationFlow?.cancel()
locationFlow = null
}
@ -391,7 +399,7 @@ class MeshService : Service() {
override fun onCreate() {
super.onCreate()
Timber.i("Creating mesh service")
Logger.i { "Creating mesh service" }
serviceNotifications.initChannels()
// Switch to the IO thread
serviceScope.handledLaunch { radioInterfaceService.connect() }
@ -408,7 +416,7 @@ class MeshService : Service() {
.onEach(::onReceiveFromRadio)
.launchIn(serviceScope)
radioInterfaceService.connectionError
.onEach { error -> Timber.e("BLE Connection Error: ${error.message}") }
.onEach { error -> Logger.e { "BLE Connection Error: ${error.message}" } }
.launchIn(serviceScope)
radioConfigRepository.localConfigFlow.onEach { localConfig = it }.launchIn(serviceScope)
radioConfigRepository.moduleConfigFlow.onEach { moduleConfig = it }.launchIn(serviceScope)
@ -461,7 +469,7 @@ class MeshService : Service() {
val a = radioInterfaceService.getBondedDeviceAddress()
val wantForeground = a != null && a != NO_DEVICE_SELECTED
Timber.i("Requesting foreground service=$wantForeground")
Logger.i { "Requesting foreground service=$wantForeground" }
val notification = updateServiceStatusNotification()
try {
@ -480,7 +488,7 @@ class MeshService : Service() {
},
)
} catch (ex: Exception) {
Timber.e(ex, "Error starting foreground service")
Logger.e(ex) { "Error starting foreground service" }
return START_NOT_STICKY
}
return if (!wantForeground) {
@ -492,7 +500,7 @@ class MeshService : Service() {
}
override fun onDestroy() {
Timber.i("Destroying mesh service")
Logger.i { "Destroying mesh service" }
// Make sure we aren't using the notification first
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE)
@ -510,7 +518,7 @@ class MeshService : Service() {
/** discard entire node db & message state - used when downloading a new db from the device */
private fun discardNodeDB() {
Timber.d("Discarding NodeDB")
Logger.d { "Discarding NodeDB" }
myNodeInfo = null
nodeDBbyNodeNum.clear()
isNodeDbReady = false
@ -836,7 +844,7 @@ class MeshService : Service() {
// We ignore most messages that we sent
val fromUs = myInfo.myNodeNum == packet.from
Timber.d("Received data from $fromId, portnum=${data.portnum} ${bytes.size} bytes")
Logger.d { "Received data from $fromId, portnum=${data.portnum} ${bytes.size} bytes" }
dataPacket.status = MessageStatus.RECEIVED
@ -851,24 +859,24 @@ class MeshService : Service() {
when (data.portnumValue) {
Portnums.PortNum.TEXT_MESSAGE_APP_VALUE -> {
if (data.replyId != 0 && data.emoji == 0) {
Timber.d("Received REPLY from $fromId")
Logger.d { "Received REPLY from $fromId" }
rememberDataPacket(dataPacket)
} else if (data.replyId != 0 && data.emoji != 0) {
Timber.d("Received EMOJI from $fromId")
Logger.d { "Received EMOJI from $fromId" }
rememberReaction(packet)
} else {
Timber.d("Received CLEAR_TEXT from $fromId")
Logger.d { "Received CLEAR_TEXT from $fromId" }
rememberDataPacket(dataPacket)
}
}
Portnums.PortNum.ALERT_APP_VALUE -> {
Timber.d("Received ALERT_APP from $fromId")
Logger.d { "Received ALERT_APP from $fromId" }
rememberDataPacket(dataPacket)
}
Portnums.PortNum.WAYPOINT_APP_VALUE -> {
Timber.d("Received WAYPOINT_APP from $fromId")
Logger.d { "Received WAYPOINT_APP from $fromId" }
val u = MeshProtos.Waypoint.parseFrom(data.payload)
// Validate locked Waypoints from the original sender
if (u.lockedTo != 0 && u.lockedTo != packet.from) return
@ -876,10 +884,10 @@ class MeshService : Service() {
}
Portnums.PortNum.POSITION_APP_VALUE -> {
Timber.d("Received POSITION_APP from $fromId")
Logger.d { "Received POSITION_APP from $fromId" }
val u = MeshProtos.Position.parseFrom(data.payload)
if (data.wantResponse && u.latitudeI == 0 && u.longitudeI == 0) {
Timber.d("Ignoring nop position update from position request")
Logger.d { "Ignoring nop position update from position request" }
} else {
handleReceivedPosition(packet.from, u, dataPacket.time)
}
@ -887,7 +895,7 @@ class MeshService : Service() {
Portnums.PortNum.NODEINFO_APP_VALUE ->
if (!fromUs) {
Timber.d("Received NODEINFO_APP from $fromId")
Logger.d { "Received NODEINFO_APP from $fromId" }
val u =
MeshProtos.User.parseFrom(data.payload).copy {
if (isLicensed) clearPublicKey()
@ -898,7 +906,7 @@ class MeshService : Service() {
// Handle new telemetry info
Portnums.PortNum.TELEMETRY_APP_VALUE -> {
Timber.d("Received TELEMETRY_APP from $fromId")
Logger.d { "Received TELEMETRY_APP from $fromId" }
val u =
TelemetryProtos.Telemetry.parseFrom(data.payload).copy {
if (time == 0) time = (dataPacket.time / 1000L).toInt()
@ -907,7 +915,7 @@ class MeshService : Service() {
}
Portnums.PortNum.ROUTING_APP_VALUE -> {
Timber.d("Received ROUTING_APP from $fromId")
Logger.d { "Received ROUTING_APP from $fromId" }
// We always send ACKs to other apps, because they might care about the
// messages they sent
shouldBroadcast = true
@ -922,41 +930,41 @@ class MeshService : Service() {
}
Portnums.PortNum.ADMIN_APP_VALUE -> {
Timber.d("Received ADMIN_APP from $fromId")
Logger.d { "Received ADMIN_APP from $fromId" }
val u = AdminProtos.AdminMessage.parseFrom(data.payload)
handleReceivedAdmin(packet.from, u)
shouldBroadcast = false
}
Portnums.PortNum.PAXCOUNTER_APP_VALUE -> {
Timber.d("Received PAXCOUNTER_APP from $fromId")
Logger.d { "Received PAXCOUNTER_APP from $fromId" }
val p = PaxcountProtos.Paxcount.parseFrom(data.payload)
handleReceivedPaxcounter(packet.from, p)
shouldBroadcast = false
}
Portnums.PortNum.STORE_FORWARD_APP_VALUE -> {
Timber.d("Received STORE_FORWARD_APP from $fromId")
Logger.d { "Received STORE_FORWARD_APP from $fromId" }
val u = StoreAndForwardProtos.StoreAndForward.parseFrom(data.payload)
handleReceivedStoreAndForward(dataPacket, u)
shouldBroadcast = false
}
Portnums.PortNum.RANGE_TEST_APP_VALUE -> {
Timber.d("Received RANGE_TEST_APP from $fromId")
Logger.d { "Received RANGE_TEST_APP from $fromId" }
if (!moduleConfig.rangeTest.enabled) return
val u = dataPacket.copy(dataType = Portnums.PortNum.TEXT_MESSAGE_APP_VALUE)
rememberDataPacket(u)
}
Portnums.PortNum.DETECTION_SENSOR_APP_VALUE -> {
Timber.d("Received DETECTION_SENSOR_APP from $fromId")
Logger.d { "Received DETECTION_SENSOR_APP from $fromId" }
val u = dataPacket.copy(dataType = Portnums.PortNum.TEXT_MESSAGE_APP_VALUE)
rememberDataPacket(u)
}
Portnums.PortNum.TRACEROUTE_APP_VALUE -> {
Timber.d("Received TRACEROUTE_APP from $fromId")
Logger.d { "Received TRACEROUTE_APP from $fromId" }
val routeDiscovery = packet.fullRouteDiscovery
val full = packet.getFullTracerouteResponse(::getUserName)
if (full != null) {
@ -988,7 +996,7 @@ class MeshService : Service() {
if (start != null) {
val elapsedMs = System.currentTimeMillis() - start
val seconds = elapsedMs / 1000.0
Timber.i("Traceroute $requestId complete in $seconds s")
Logger.i { "Traceroute $requestId complete in $seconds s" }
"$full\n\nDuration: ${"%.1f".format(seconds)} s"
} else {
full
@ -1012,9 +1020,9 @@ class MeshService : Service() {
Portnums.PortNum.NEIGHBORINFO_APP_VALUE -> {
val requestId = packet.decoded.requestId
Timber.d("Processing NEIGHBORINFO_APP packet with requestId: $requestId")
Logger.d { "Processing NEIGHBORINFO_APP packet with requestId: $requestId" }
val start = neighborInfoStartTimes.remove(requestId)
Timber.d("Found start time for requestId $requestId: $start")
Logger.d { "Found start time for requestId $requestId: $start" }
val info =
runCatching { MeshProtos.NeighborInfo.parseFrom(data.payload.toByteArray()) }.getOrNull()
@ -1022,7 +1030,7 @@ class MeshService : Service() {
// Store the last neighbor info from our connected radio
if (info != null && packet.from == myInfo.myNodeNum) {
lastNeighborInfo = info
Timber.d("Stored last neighbor info from connected radio")
Logger.d { "Stored last neighbor info from connected radio" }
}
// Only show response if packet is addressed to us and we sent a request in the last 3 minutes
@ -1060,27 +1068,25 @@ class MeshService : Service() {
if (start != null) {
val elapsedMs = System.currentTimeMillis() - start
val seconds = elapsedMs / 1000.0
Timber.i("Neighbor info $requestId complete in $seconds s")
Logger.i { "Neighbor info $requestId complete in $seconds s" }
"$formatted\n\nDuration: ${"%.1f".format(seconds)} s"
} else {
Timber.w("No start time found for neighbor info requestId: $requestId")
Logger.w { "No start time found for neighbor info requestId: $requestId" }
formatted
}
serviceRepository.setNeighborInfoResponse(response)
} else {
Timber.d(
"Neighbor info response filtered: ToUs=%s, isRecentRequest=%s",
isAddressedToUs,
isRecentRequest,
)
Logger.d {
"Neighbor info response filtered: ToUs=$isAddressedToUs, isRecentRequest=$isRecentRequest"
}
}
}
Portnums.PortNum.NEIGHBORINFO_APP_VALUE -> {
val requestId = packet.decoded.requestId
Timber.d("Processing NEIGHBORINFO_APP packet with requestId: $requestId")
Logger.d { "Processing NEIGHBORINFO_APP packet with requestId: $requestId" }
val start = neighborInfoStartTimes.remove(requestId)
Timber.d("Found start time for requestId $requestId: $start")
Logger.d { "Found start time for requestId $requestId: $start" }
val info =
runCatching { MeshProtos.NeighborInfo.parseFrom(data.payload.toByteArray()) }.getOrNull()
@ -1088,7 +1094,7 @@ class MeshService : Service() {
// Store the last neighbor info from our connected radio
if (info != null && packet.from == myInfo.myNodeNum) {
lastNeighborInfo = info
Timber.d("Stored last neighbor info from connected radio")
Logger.d { "Stored last neighbor info from connected radio" }
}
// Only show response if packet is addressed to us and we sent a request in the last 3 minutes
@ -1126,23 +1132,21 @@ class MeshService : Service() {
if (start != null) {
val elapsedMs = System.currentTimeMillis() - start
val seconds = elapsedMs / 1000.0
Timber.i("Neighbor info $requestId complete in $seconds s")
Logger.i { "Neighbor info $requestId complete in $seconds s" }
"$formatted\n\nDuration: ${"%.1f".format(seconds)} s"
} else {
Timber.w("No start time found for neighbor info requestId: $requestId")
Logger.w { "No start time found for neighbor info requestId: $requestId" }
formatted
}
serviceRepository.setNeighborInfoResponse(response)
} else {
Timber.d(
"Neighbor info response filtered: isToUs=%s, isRecent=%s",
isAddressedToUs,
isRecentRequest,
)
Logger.d {
"Neighbor info response filtered: isToUs=$isAddressedToUs, isRecent=$isRecentRequest"
}
}
}
else -> Timber.d("No custom processing needed for ${data.portnumValue} from $fromId")
else -> Logger.d { "No custom processing needed for ${data.portnumValue} from $fromId" }
}
// We always tell other apps when new data packets arrive
@ -1162,7 +1166,7 @@ class MeshService : Service() {
AdminProtos.AdminMessage.PayloadVariantCase.GET_CONFIG_RESPONSE -> {
if (fromNodeNum == myNodeNum) {
val response = a.getConfigResponse
Timber.d("Admin: received config ${response.payloadVariantCase}")
Logger.d { "Admin: received config ${response.payloadVariantCase}" }
setLocalConfig(response)
}
}
@ -1172,7 +1176,7 @@ class MeshService : Service() {
val mi = myNodeInfo
if (mi != null) {
val ch = a.getChannelResponse
Timber.d("Admin: Received channel ${ch.index}")
Logger.d { "Admin: Received channel ${ch.index}" }
if (ch.index + 1 < mi.maxChannels) {
handleChannel(ch)
@ -1182,15 +1186,15 @@ class MeshService : Service() {
}
AdminProtos.AdminMessage.PayloadVariantCase.GET_DEVICE_METADATA_RESPONSE -> {
Timber.d("Admin: received DeviceMetadata from $fromNodeNum")
Logger.d { "Admin: received DeviceMetadata from $fromNodeNum" }
serviceScope.handledLaunch {
nodeRepository.insertMetadata(MetadataEntity(fromNodeNum, a.getDeviceMetadataResponse))
}
}
else -> Timber.w("No special processing needed for ${a.payloadVariantCase}")
else -> Logger.w { "No special processing needed for ${a.payloadVariantCase}" }
}
Timber.d("Admin: Received session_passkey from $fromNodeNum")
Logger.d { "Admin: Received session_passkey from $fromNodeNum" }
sessionPasskey = a.sessionPasskey
}
@ -1224,11 +1228,11 @@ class MeshService : Service() {
if (shouldPreserve) {
// Firmware sent us a placeholder - keep all our existing user data
Timber.d(
Logger.d {
"Preserving existing user data for node $fromNum: " +
"kept='${it.user.longName}' (hwModel=${it.user.hwModel}), " +
"skipped default='${p.longName}' (hwModel=UNSET)",
)
"skipped default='${p.longName}' (hwModel=UNSET)"
}
// Ensure denormalized columns are updated from preserved user data
it.longName = it.user.longName
it.shortName = it.user.shortName
@ -1242,7 +1246,7 @@ class MeshService : Service() {
p
} else {
p.copy {
Timber.w("Public key mismatch from $longName ($shortName)")
Logger.w { "Public key mismatch from $longName ($shortName)" }
publicKey = NodeEntity.ERROR_BYTE_STRING
}
}
@ -1275,7 +1279,7 @@ class MeshService : Service() {
// (only)
// we don't record these nop position updates
if (myNodeNum == fromNum && p.latitudeI == 0 && p.longitudeI == 0) {
Timber.d("Ignoring nop position update for the local node")
Logger.d { "Ignoring nop position update for the local node" }
} else {
updateNodeInfo(fromNum) { it.setPosition(p, (defaultTime / 1000L).toInt()) }
}
@ -1407,7 +1411,7 @@ class MeshService : Service() {
}
private fun handleReceivedStoreAndForward(dataPacket: DataPacket, s: StoreAndForwardProtos.StoreAndForward) {
Timber.d("StoreAndForward: ${s.variantCase} ${s.rr} from ${dataPacket.from}")
Logger.d { "StoreAndForward: ${s.variantCase} ${s.rr} from ${dataPacket.from}" }
val transport = currentTransport()
val lastRequest =
if (s.variantCase == StoreAndForwardProtos.StoreAndForward.VariantCase.HISTORY) {
@ -1481,7 +1485,7 @@ class MeshService : Service() {
if (packet.rxTime == 0) setRxTime(currentSecond())
}
.build()
Timber.d("[packet]: ${packet.toOneLineString()}")
Logger.d { "[packet]: ${packet.toOneLineString()}" }
if (isNodeDbReady) {
processReceivedMeshPacket(preparedPacket)
return
@ -1524,7 +1528,7 @@ class MeshService : Service() {
private fun sendNow(p: DataPacket) {
val packet = toMeshPacket(p)
p.time = System.currentTimeMillis() // update time to the actual time we started sending
// Timber.d("Sending to radio: ${packet.toPIIString()}")
// Logger.d { "Sending to radio: ${packet.toPIIString()}" }
packetHandler.sendToRadio(packet)
}
@ -1535,7 +1539,7 @@ class MeshService : Service() {
sendNow(p)
sentPackets.add(p)
} catch (ex: Exception) {
Timber.e(ex, "Error sending queued message:")
Logger.e(ex) { "Error sending queued message:" }
}
}
offlineSentPackets.removeAll(sentPackets)
@ -1669,7 +1673,7 @@ class MeshService : Service() {
@Suppress("CyclomaticComplexMethod")
private fun onConnectionChanged(c: ConnectionState) {
if (connectionStateHolder.connectionState.value == c && c !is ConnectionState.Connected) return
Timber.d("onConnectionChanged: ${connectionStateHolder.connectionState.value} -> $c")
Logger.d { "onConnectionChanged: ${connectionStateHolder.connectionState.value} -> $c" }
// Cancel any existing timeouts
sleepTimeout?.cancel()
@ -1697,7 +1701,7 @@ class MeshService : Service() {
private fun handleDisconnected() {
connectionStateHolder.setState(ConnectionState.Disconnected)
Timber.d("Starting disconnect")
Logger.d { "Starting disconnect" }
packetHandler.stopPacketQueue()
stopLocationRequests()
stopMqttClientProxy()
@ -1730,12 +1734,12 @@ class MeshService : Service() {
// wait 30 seconds
val timeout = (localConfig.power?.lsSecs ?: 0) + 30
Timber.d("Waiting for sleeping device, timeout=$timeout secs")
Logger.d { "Waiting for sleeping device, timeout=$timeout secs" }
delay(timeout * 1000L)
Timber.w("Device timeout out, setting disconnected")
Logger.w { "Device timeout out, setting disconnected" }
onConnectionChanged(ConnectionState.Disconnected)
} catch (_: CancellationException) {
Timber.d("device sleep timeout cancelled")
Logger.d { "device sleep timeout cancelled" }
}
}
@ -1746,7 +1750,7 @@ class MeshService : Service() {
private fun handleConnected() {
connectionStateHolder.setState(ConnectionState.Connecting)
serviceBroadcasts.broadcastConnection()
Timber.d("Starting connect")
Logger.d { "Starting connect" }
historyLog {
val address = meshPrefs.deviceAddress ?: "null"
"onReconnect transport=${currentTransport()} node=$address"
@ -1755,9 +1759,9 @@ class MeshService : Service() {
connectTimeMsec = System.currentTimeMillis()
startConfigOnly()
} catch (ex: InvalidProtocolBufferException) {
Timber.e(ex, "Invalid protocol buffer sent by device - update device software and try again")
Logger.e(ex) { "Invalid protocol buffer sent by device - update device software and try again" }
} catch (ex: RadioNotConnectedException) {
Timber.e("Lost connection to radio during init - waiting for reconnect ${ex.message}")
Logger.e { "Lost connection to radio during init - waiting for reconnect ${ex.message}" }
} catch (ex: RemoteException) {
onConnectionChanged(ConnectionState.DeviceSleep)
throw ex
@ -1852,7 +1856,9 @@ class MeshService : Service() {
// Explicitly handle default/unwanted cases to satisfy the exhaustive `when`
PayloadVariantCase.PAYLOADVARIANT_NOT_SET -> { proto ->
Timber.d("Received variant PayloadVariantUnset: Full FromRadio proto: ${proto.toPIIString()}")
Logger.d {
"Received variant PayloadVariantUnset: Full FromRadio proto: ${proto.toPIIString()}"
}
}
}
}
@ -1872,9 +1878,9 @@ class MeshService : Service() {
runCatching { MeshProtos.FromRadio.parseFrom(bytes) }
.onSuccess { proto ->
if (proto.payloadVariantCase == PayloadVariantCase.PAYLOADVARIANT_NOT_SET) {
Timber.w(
"Received FromRadio with PAYLOADVARIANT_NOT_SET. rawBytes=${bytes.toHexString()} proto=$proto",
)
Logger.w {
"Received FromRadio with PAYLOADVARIANT_NOT_SET. rawBytes=${bytes.toHexString()} proto=$proto"
}
}
proto.route()
}
@ -1884,11 +1890,10 @@ class MeshService : Service() {
handleLogRecord(logRecord)
}
.onFailure { _ ->
Timber.e(
primaryException,
Logger.e(primaryException) {
"Failed to parse radio packet (len=${bytes.size} contents=${bytes.toHexString()}). " +
"Not a valid FromRadio or LogRecord.",
)
"Not a valid FromRadio or LogRecord."
}
}
}
}
@ -1908,7 +1913,7 @@ class MeshService : Service() {
private var nodeInfoNonce: Int = DEFAULT_NODE_INFO_NONCE
private fun handleDeviceConfig(config: ConfigProtos.Config) {
Timber.d("[deviceConfig] ${config.toPIIString()}")
Logger.d { "[deviceConfig] ${config.toPIIString()}" }
val packetToSave =
MeshLog(
uuid = UUID.randomUUID().toString(),
@ -1924,7 +1929,7 @@ class MeshService : Service() {
}
private fun handleModuleConfig(config: ModuleConfigProtos.ModuleConfig) {
Timber.d("[moduleConfig] ${config.toPIIString()}")
Logger.d { "[moduleConfig] ${config.toPIIString()}" }
val packetToSave =
MeshLog(
uuid = UUID.randomUUID().toString(),
@ -1940,7 +1945,7 @@ class MeshService : Service() {
}
private fun handleChannel(ch: ChannelProtos.Channel) {
Timber.d("[channel] ${ch.toPIIString()}")
Logger.d { "[channel] ${ch.toPIIString()}" }
val packetToSave =
MeshLog(
uuid = UUID.randomUUID().toString(),
@ -1965,11 +1970,11 @@ class MeshService : Service() {
if (shouldPreserve) {
// Firmware sent us a placeholder - keep all our existing user data
Timber.d(
Logger.d {
"Preserving existing user data for node ${info.num}: " +
"kept='${it.user.longName}' (hwModel=${it.user.hwModel}), " +
"skipped default='${info.user.longName}' (hwModel=UNSET)",
)
"skipped default='${info.user.longName}' (hwModel=UNSET)"
}
// Ensure denormalized columns are updated from preserved user data
it.longName = it.user.longName
it.shortName = it.user.shortName
@ -2013,7 +2018,7 @@ class MeshService : Service() {
}
private fun handleNodeInfo(info: MeshProtos.NodeInfo) {
Timber.d("[nodeInfo] ${info.toPIIString()}")
Logger.d { "[nodeInfo] ${info.toPIIString()}" }
val packetToSave =
MeshLog(
uuid = UUID.randomUUID().toString(),
@ -2029,7 +2034,7 @@ class MeshService : Service() {
}
private fun handleNodeInfoComplete() {
Timber.d("NodeInfo complete for nonce $nodeInfoNonce")
Logger.d { "NodeInfo complete for nonce $nodeInfoNonce" }
val packetToSave =
MeshLog(
uuid = UUID.randomUUID().toString(),
@ -2040,7 +2045,7 @@ class MeshService : Service() {
)
insertMeshLog(packetToSave)
if (newNodes.isEmpty()) {
Timber.e("Did not receive a valid node info")
Logger.e { "Did not receive a valid node info" }
} else {
// Batch update: Update in-memory models first without triggering individual DB writes
val entities =
@ -2074,13 +2079,13 @@ class MeshService : Service() {
private fun regenMyNodeInfo(metadata: MeshProtos.DeviceMetadata? = MeshProtos.DeviceMetadata.getDefaultInstance()) {
val myInfo = rawMyNodeInfo
val hasMetadata = metadata != null && metadata != MeshProtos.DeviceMetadata.getDefaultInstance()
Timber.i(
Logger.i {
"[MYNODE_REGEN] Called - " +
"rawMyNodeInfo: ${if (myInfo != null) "present" else "null"}, " +
"metadata: ${if (hasMetadata) "present" else "null/default"}, " +
"firmwareVersion: ${metadata?.firmwareVersion ?: "null"}, " +
"hasWifi: ${metadata?.hasWifi}",
)
"hasWifi: ${metadata?.hasWifi}"
}
if (myInfo != null) {
val mi =
@ -2107,21 +2112,21 @@ class MeshService : Service() {
)
}
Timber.i(
Logger.i {
"[MYNODE_REGEN] Created MyNodeEntity - " +
"nodeNum: ${mi.myNodeNum}, " +
"model: ${mi.model}, " +
"firmwareVersion: ${mi.firmwareVersion}, " +
"hasWifi: ${mi.hasWifi}",
)
"hasWifi: ${mi.hasWifi}"
}
if (metadata != null && metadata != MeshProtos.DeviceMetadata.getDefaultInstance()) {
serviceScope.handledLaunch { nodeRepository.insertMetadata(MetadataEntity(mi.myNodeNum, metadata)) }
}
newMyNodeInfo = mi
Timber.i("[MYNODE_REGEN] Set newMyNodeInfo (will be committed on configComplete)")
Logger.i { "[MYNODE_REGEN] Set newMyNodeInfo (will be committed on configComplete)" }
} else {
Timber.w("[MYNODE_REGEN] rawMyNodeInfo is null, cannot regenerate")
Logger.w { "[MYNODE_REGEN] rawMyNodeInfo is null, cannot regenerate" }
}
}
@ -2136,12 +2141,12 @@ class MeshService : Service() {
/** Update MyNodeInfo (called from either new API version or the old one) */
private fun handleMyInfo(myInfo: MeshProtos.MyNodeInfo) {
Timber.i(
Logger.i {
"[MYINFO_RECEIVED] MyNodeInfo received - " +
"nodeNum: ${myInfo.myNodeNum}, " +
"minAppVersion: ${myInfo.minAppVersion}, " +
"PII data: ${myInfo.toPIIString()}",
)
"PII data: ${myInfo.toPIIString()}"
}
val packetToSave =
MeshLog(
uuid = UUID.randomUUID().toString(),
@ -2153,7 +2158,7 @@ class MeshService : Service() {
insertMeshLog(packetToSave)
rawMyNodeInfo = myInfo
Timber.i("[MYINFO_RECEIVED] Set rawMyNodeInfo, calling regenMyNodeInfo()")
Logger.i { "[MYINFO_RECEIVED] Set rawMyNodeInfo, calling regenMyNodeInfo()" }
regenMyNodeInfo()
// We'll need to get a new set of channels and settings now
@ -2166,14 +2171,14 @@ class MeshService : Service() {
/** Update our DeviceMetadata */
private fun handleMetadata(metadata: MeshProtos.DeviceMetadata) {
Timber.i(
Logger.i {
"[METADATA_RECEIVED] DeviceMetadata received - " +
"firmwareVersion: ${metadata.firmwareVersion}, " +
"hwModel: ${metadata.hwModel}, " +
"hasWifi: ${metadata.hasWifi}, " +
"hasBluetooth: ${metadata.hasBluetooth}, " +
"PII data: ${metadata.toPIIString()}",
)
"PII data: ${metadata.toPIIString()}"
}
val packetToSave =
MeshLog(
uuid = UUID.randomUUID().toString(),
@ -2184,16 +2189,16 @@ class MeshService : Service() {
)
insertMeshLog(packetToSave)
Timber.i(
Logger.i {
"[METADATA_RECEIVED] Calling regenMyNodeInfo with metadata - " +
"This will update newMyNodeInfo with firmwareVersion: ${metadata.firmwareVersion}",
)
"This will update newMyNodeInfo with firmwareVersion: ${metadata.firmwareVersion}"
}
regenMyNodeInfo(metadata)
}
/** Publish MqttClientProxyMessage (fromRadio) */
private fun handleMqttProxyMessage(message: MeshProtos.MqttClientProxyMessage) {
Timber.d("[mqttClientProxyMessage] ${message.toPIIString()}")
Logger.d { "[mqttClientProxyMessage] ${message.toPIIString()}" }
with(message) {
when (payloadVariantCase) {
MeshProtos.MqttClientProxyMessage.PayloadVariantCase.TEXT -> {
@ -2210,7 +2215,7 @@ class MeshService : Service() {
}
private fun handleClientNotification(notification: MeshProtos.ClientNotification) {
Timber.d("[clientNotification] ${notification.toPIIString()}")
Logger.d { "[clientNotification] ${notification.toPIIString()}" }
serviceRepository.setClientNotification(notification)
serviceNotifications.showClientNotification(notification)
// if the future for the originating request is still in the queue, complete as unsuccessful
@ -2228,7 +2233,7 @@ class MeshService : Service() {
}
private fun handleFileInfo(fileInfo: MeshProtos.FileInfo) {
Timber.d("[fileInfo] ${fileInfo.toPIIString()}")
Logger.d { "[fileInfo] ${fileInfo.toPIIString()}" }
val packetToSave =
MeshLog(
uuid = UUID.randomUUID().toString(),
@ -2241,7 +2246,7 @@ class MeshService : Service() {
}
private fun handleLogRecord(logRecord: MeshProtos.LogRecord) {
Timber.d("[logRecord] ${logRecord.toPIIString()}")
Logger.d { "[logRecord] ${logRecord.toPIIString()}" }
val packetToSave =
MeshLog(
uuid = UUID.randomUUID().toString(),
@ -2254,7 +2259,7 @@ class MeshService : Service() {
}
private fun handleRebooted(rebooted: Boolean) {
Timber.d("[rebooted] $rebooted")
Logger.d { "[rebooted] $rebooted" }
val packetToSave =
MeshLog(
uuid = UUID.randomUUID().toString(),
@ -2267,7 +2272,7 @@ class MeshService : Service() {
}
private fun handleXmodemPacket(xmodemPacket: XmodemProtos.XModem) {
Timber.d("[xmodemPacket] ${xmodemPacket.toPIIString()}")
Logger.d { "[xmodemPacket] ${xmodemPacket.toPIIString()}" }
val packetToSave =
MeshLog(
uuid = UUID.randomUUID().toString(),
@ -2280,7 +2285,7 @@ class MeshService : Service() {
}
private fun handleDeviceUiConfig(deviceuiConfig: DeviceUIProtos.DeviceUIConfig) {
Timber.d("[deviceuiConfig] ${deviceuiConfig.toPIIString()}")
Logger.d { "[deviceuiConfig] ${deviceuiConfig.toPIIString()}" }
val packetToSave =
MeshLog(
uuid = UUID.randomUUID().toString(),
@ -2308,7 +2313,7 @@ class MeshService : Service() {
private fun stopMqttClientProxy() {
if (mqttMessageFlow?.isActive == true) {
Timber.i("Stopping MqttClientProxy")
Logger.i { "Stopping MqttClientProxy" }
mqttMessageFlow?.cancel()
mqttMessageFlow = null
}
@ -2329,19 +2334,19 @@ class MeshService : Service() {
}
private fun handleConfigComplete(configCompleteId: Int) {
Timber.d("[configCompleteId]: ${configCompleteId.toPIIString()}")
Logger.d { "[configCompleteId]: ${configCompleteId.toPIIString()}" }
when (configCompleteId) {
configOnlyNonce -> handleConfigOnlyComplete()
nodeInfoNonce -> handleNodeInfoComplete()
else ->
Timber.w(
"Config complete id mismatch: received=$configCompleteId expected one of [$configOnlyNonce,$nodeInfoNonce]",
)
Logger.w {
"Config complete id mismatch: received=$configCompleteId expected one of [$configOnlyNonce,$nodeInfoNonce]"
}
}
}
private fun handleConfigOnlyComplete() {
Timber.i("[CONFIG_COMPLETE] Config-only complete for nonce $configOnlyNonce")
Logger.i { "[CONFIG_COMPLETE] Config-only complete for nonce $configOnlyNonce" }
val packetToSave =
MeshLog(
uuid = UUID.randomUUID().toString(),
@ -2353,16 +2358,16 @@ class MeshService : Service() {
insertMeshLog(packetToSave)
if (newMyNodeInfo == null) {
Timber.e("[CONFIG_COMPLETE] Did not receive a valid config - newMyNodeInfo is null")
Logger.e { "[CONFIG_COMPLETE] Did not receive a valid config - newMyNodeInfo is null" }
} else {
Timber.i(
Logger.i {
"[CONFIG_COMPLETE] Committing newMyNodeInfo to myNodeInfo - " +
"firmwareVersion: ${newMyNodeInfo?.firmwareVersion}, " +
"hasWifi: ${newMyNodeInfo?.hasWifi}, " +
"model: ${newMyNodeInfo?.model}",
)
"model: ${newMyNodeInfo?.model}"
}
myNodeInfo = newMyNodeInfo
Timber.i("[CONFIG_COMPLETE] myNodeInfo committed successfully")
Logger.i { "[CONFIG_COMPLETE] myNodeInfo committed successfully" }
}
// Keep BLE awake and allow the firmware to settle before the node-info stage.
serviceScope.handledLaunch {
@ -2379,21 +2384,21 @@ class MeshService : Service() {
packetHandler.sendToRadio(
ToRadio.newBuilder().apply { heartbeat = MeshProtos.Heartbeat.getDefaultInstance() },
)
Timber.d("Heartbeat sent between nonce stages")
Logger.d { "Heartbeat sent between nonce stages" }
} catch (ex: Exception) {
Timber.w(ex, "Failed to send heartbeat; proceeding with node-info stage")
Logger.w(ex) { "Failed to send heartbeat; proceeding with node-info stage" }
}
}
private fun startConfigOnly() {
newMyNodeInfo = null
Timber.d("Starting config-only nonce=$configOnlyNonce")
Logger.d { "Starting config-only nonce=$configOnlyNonce" }
packetHandler.sendToRadio(ToRadio.newBuilder().apply { this.wantConfigId = configOnlyNonce })
}
private fun startNodeInfoOnly() {
newNodes.clear()
Timber.d("Starting node-info nonce=$nodeInfoNonce")
Logger.d { "Starting node-info nonce=$nodeInfoNonce" }
packetHandler.sendToRadio(ToRadio.newBuilder().apply { this.wantConfigId = nodeInfoNonce })
}
@ -2403,7 +2408,7 @@ class MeshService : Service() {
val mi = myNodeInfo
if (mi != null) {
val idNum = destNum ?: mi.myNodeNum // when null we just send to the local node
Timber.d("Sending our position/time to=$idNum ${Position(position)}")
Logger.d { "Sending our position/time to=$idNum ${Position(position)}" }
// Also update our own map for our nodeNum, by handling the packet just like packets from other users
if (!localConfig.position.fixedPosition) {
@ -2422,7 +2427,7 @@ class MeshService : Service() {
)
}
} catch (_: BLEException) {
Timber.w("Ignoring disconnected radio during gps location update")
Logger.w { "Ignoring disconnected radio during gps location update" }
}
}
@ -2433,11 +2438,11 @@ class MeshService : Service() {
@Suppress("ComplexCondition")
if (user == old) {
Timber.d("Ignoring nop owner change")
Logger.d { "Ignoring nop owner change" }
} else {
Timber.d(
"setOwner Id: $id longName: ${longName.anonymize} shortName: $shortName isLicensed: $isLicensed isUnmessagable: $isUnmessagable",
)
Logger.d {
"setOwner Id: $id longName: ${longName.anonymize} shortName: $shortName isLicensed: $isLicensed isUnmessagable: $isUnmessagable"
}
// Also update our own map for our nodeNum, by handling the packet just like packets from other users
handleReceivedUser(dest.num, user)
@ -2511,10 +2516,10 @@ class MeshService : Service() {
packetHandler.sendToRadio(
newMeshPacketTo(myNodeNum).buildAdminPacket {
if (node.isFavorite) {
Timber.d("removing node ${node.num} from favorite list")
Logger.d { "removing node ${node.num} from favorite list" }
removeFavoriteNode = node.num
} else {
Timber.d("adding node ${node.num} to favorite list")
Logger.d { "adding node ${node.num} to favorite list" }
setFavoriteNode = node.num
}
},
@ -2526,10 +2531,10 @@ class MeshService : Service() {
packetHandler.sendToRadio(
newMeshPacketTo(myNodeNum).buildAdminPacket {
if (node.isIgnored) {
Timber.d("removing node ${node.num} from ignore list")
Logger.d { "removing node ${node.num} from ignore list" }
removeIgnoredNode = node.num
} else {
Timber.d("adding node ${node.num} to ignore list")
Logger.d { "adding node ${node.num} to ignore list" }
setIgnoredNode = node.num
}
},
@ -2555,11 +2560,11 @@ class MeshService : Service() {
private fun updateLastAddress(deviceAddr: String?) {
val currentAddr = meshPrefs.deviceAddress
Timber.d("setDeviceAddress: received request to change to: ${deviceAddr.anonymize}")
Logger.d { "setDeviceAddress: received request to change to: ${deviceAddr.anonymize}" }
if (deviceAddr != currentAddr) {
Timber.d(
"SetDeviceAddress: Device address changed from ${currentAddr.anonymize} to ${deviceAddr.anonymize}",
)
Logger.d {
"SetDeviceAddress: Device address changed from ${currentAddr.anonymize} to ${deviceAddr.anonymize}"
}
val currentLabel = currentAddr ?: "null"
val nextLabel = deviceAddr ?: "null"
val nextTransport = currentTransport(deviceAddr)
@ -2584,7 +2589,7 @@ class MeshService : Service() {
loadCachedNodeDB()
}
} else {
Timber.d("SetDeviceAddress: Device address is unchanged, ignoring.")
Logger.d { "SetDeviceAddress: Device address is unchanged, ignoring." }
}
}
@ -2596,7 +2601,7 @@ class MeshService : Service() {
object : IMeshService.Stub() {
override fun setDeviceAddress(deviceAddr: String?) = toRemoteExceptions {
Timber.d("Passing through device change to radio service: ${deviceAddr.anonymize}")
Logger.d { "Passing through device change to radio service: ${deviceAddr.anonymize}" }
updateLastAddress(deviceAddr)
radioInterfaceService.setDeviceAddress(deviceAddr)
}
@ -2642,9 +2647,9 @@ class MeshService : Service() {
toRemoteExceptions {
if (p.id == 0) p.id = generatePacketId()
val bytes = p.bytes!!
Timber.i(
"sendData dest=${p.to}, id=${p.id} <- ${bytes.size} bytes (connectionState=${connectionStateHolder.connectionState.value})",
)
Logger.i {
"sendData dest=${p.to}, id=${p.id} <- ${bytes.size} bytes (connectionState=${connectionStateHolder.connectionState.value})"
}
if (p.dataType == 0) throw Exception("Port numbers must be non-zero!")
if (bytes.size >= MeshProtos.Constants.DATA_PAYLOAD_LEN.number) {
p.status = MessageStatus.ERROR
@ -2656,7 +2661,7 @@ class MeshService : Service() {
try {
sendNow(p)
} catch (ex: Exception) {
Timber.e(ex, "Error sending message, so enqueueing")
Logger.e(ex) { "Error sending message, so enqueueing" }
enqueueForSending(p)
}
} else {
@ -2677,7 +2682,7 @@ class MeshService : Service() {
}
override fun setRemoteConfig(id: Int, num: Int, payload: ByteArray) = toRemoteExceptions {
Timber.d("Setting new radio config!")
Logger.d { "Setting new radio config!" }
val config = ConfigProtos.Config.parseFrom(payload)
packetHandler.sendToRadio(newMeshPacketTo(num).buildAdminPacket(id = id) { setConfig = config })
if (num == myNodeNum) setLocalConfig(config)
@ -2696,7 +2701,7 @@ class MeshService : Service() {
}
override fun setModuleConfig(id: Int, num: Int, payload: ByteArray) = toRemoteExceptions {
Timber.d("Setting new module config!")
Logger.d { "Setting new module config!" }
val config = ModuleConfigProtos.ModuleConfig.parseFrom(payload)
packetHandler.sendToRadio(newMeshPacketTo(num).buildAdminPacket(id = id) { setModuleConfig = config })
if (num == myNodeNum) setLocalModuleConfig(config)
@ -2765,13 +2770,13 @@ class MeshService : Service() {
override fun getNodes(): MutableList<NodeInfo> = toRemoteExceptions {
val r = nodeDBbyNodeNum.values.map { it.toNodeInfo() }.toMutableList()
Timber.i("in getOnline, count=${r.size}")
Logger.i { "in getOnline, count=${r.size}" }
r
}
override fun connectionState(): String = toRemoteExceptions {
val r = connectionStateHolder.connectionState.value
Timber.i("in connectionState=$r")
Logger.i { "in connectionState=$r" }
r.toString()
}
@ -2804,7 +2809,7 @@ class MeshService : Service() {
lastNeighborInfo
?: run {
// If we don't have it, send dummy/interceptable data
Timber.d("No stored neighbor info from connected radio, sending dummy data")
Logger.d { "No stored neighbor info from connected radio, sending dummy data" }
MeshProtos.NeighborInfo.newBuilder()
.setNodeId(myNodeNum)
.setLastSentById(myNodeNum)
@ -2844,7 +2849,7 @@ class MeshService : Service() {
else -> nodeDBbyNodeNum[myNodeNum]?.position?.let { Position(it) }?.takeIf { it.isValid() }
}
if (currentPosition == null) {
Timber.d("Position request skipped - no valid position available")
Logger.d { "Position request skipped - no valid position available" }
return@toRemoteExceptions
}
val meshPosition = position {

View file

@ -20,13 +20,13 @@ package com.geeksville.mesh.service
import android.content.Context
import android.content.Intent
import android.os.Parcelable
import co.touchlab.kermit.Logger
import dagger.hilt.android.qualifiers.ApplicationContext
import org.meshtastic.core.model.DataPacket
import org.meshtastic.core.model.MessageStatus
import org.meshtastic.core.model.NodeInfo
import org.meshtastic.core.model.util.toPIIString
import org.meshtastic.core.service.ServiceRepository
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton
@ -51,7 +51,7 @@ constructor(
}
fun broadcastNodeChange(info: NodeInfo) {
Timber.d("Broadcasting node change ${info.user?.toPIIString()}")
Logger.d { "Broadcasting node change ${info.user?.toPIIString()}" }
val intent = Intent(MeshService.ACTION_NODE_CHANGE).putExtra(EXTRA_NODEINFO, info)
explicitBroadcast(intent)
}
@ -60,10 +60,10 @@ constructor(
fun broadcastMessageStatus(id: Int, status: MessageStatus?) {
if (id == 0) {
Timber.d("Ignoring anonymous packet status")
Logger.d { "Ignoring anonymous packet status" }
} else {
// Do not log, contains PII possibly
// MeshService.Timber.d("Broadcasting message status $p")
// MeshService.Logger.d { "Broadcasting message status $p" }
val intent =
Intent(MeshService.ACTION_MESSAGE_STATUS).apply {
putExtra(EXTRA_PACKET_ID, id)

View file

@ -20,8 +20,8 @@ package com.geeksville.mesh.service
import android.app.ForegroundServiceStartNotAllowedException
import android.content.Context
import android.os.Build
import co.touchlab.kermit.Logger
import com.geeksville.mesh.BuildConfig
import timber.log.Timber
// / Helper function to start running our service
fun MeshService.Companion.startService(context: Context) {
@ -33,14 +33,14 @@ fun MeshService.Companion.startService(context: Context) {
// Before binding we want to explicitly create - so the service stays alive forever (so it can keep
// listening for the bluetooth packets arriving from the radio. And when they arrive forward them
// to Signal or whatever.
Timber.i("Trying to start service debug=${BuildConfig.DEBUG}")
Logger.i { "Trying to start service debug=${BuildConfig.DEBUG}" }
val intent = createIntent(context)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
try {
context.startForegroundService(intent)
} catch (ex: ForegroundServiceStartNotAllowedException) {
Timber.e("Unable to start service: ${ex.message}")
Logger.e { "Unable to start service: ${ex.message}" }
}
} else {
context.startForegroundService(intent)

View file

@ -17,6 +17,7 @@
package com.geeksville.mesh.service
import co.touchlab.kermit.Logger
import com.geeksville.mesh.concurrent.handledLaunch
import com.geeksville.mesh.repository.radio.RadioInterfaceService
import dagger.Lazy
@ -38,7 +39,6 @@ import org.meshtastic.proto.MeshProtos
import org.meshtastic.proto.MeshProtos.MeshPacket
import org.meshtastic.proto.MeshProtos.ToRadio
import org.meshtastic.proto.fromRadio
import timber.log.Timber
import java.util.UUID
import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.TimeUnit
@ -73,7 +73,7 @@ constructor(
*/
fun sendToRadio(p: ToRadio.Builder) {
val built = p.build()
Timber.d("Sending to radio ${built.toPIIString()}")
Logger.d { "Sending to radio ${built.toPIIString()}" }
val b = built.toByteArray()
radioInterfaceService.sendToRadio(b)
@ -105,7 +105,7 @@ constructor(
fun stopPacketQueue() {
if (queueJob?.isActive == true) {
Timber.i("Stopping packet queueJob")
Logger.i { "Stopping packet queueJob" }
queueJob?.cancel()
queueJob = null
queuedPackets.clear()
@ -115,7 +115,7 @@ constructor(
}
fun handleQueueStatus(queueStatus: MeshProtos.QueueStatus) {
Timber.d("[queueStatus] ${queueStatus.toOneLineString()}")
Logger.d { "[queueStatus] ${queueStatus.toOneLineString()}" }
val (success, isFull, requestId) = with(queueStatus) { Triple(res == 0, free == 0, meshPacketId) }
if (success && isFull) return // Queue is full, wait for free != 0
if (requestId != 0) {
@ -134,20 +134,20 @@ constructor(
if (queueJob?.isActive == true) return
queueJob =
scope.handledLaunch {
Timber.d("packet queueJob started")
Logger.d { "packet queueJob started" }
while (connectionStateHolder.connectionState.value == ConnectionState.Connected) {
// take the first packet from the queue head
val packet = queuedPackets.poll() ?: break
try {
// send packet to the radio and wait for response
val response = sendPacket(packet)
Timber.d("queueJob packet id=${packet.id.toUInt()} waiting")
Logger.d { "queueJob packet id=${packet.id.toUInt()} waiting" }
val success = response.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
Timber.d("queueJob packet id=${packet.id.toUInt()} success $success")
Logger.d { "queueJob packet id=${packet.id.toUInt()} success $success" }
} catch (e: TimeoutException) {
Timber.d("queueJob packet id=${packet.id.toUInt()} timeout")
Logger.d { "queueJob packet id=${packet.id.toUInt()} timeout" }
} catch (e: Exception) {
Timber.d("queueJob packet id=${packet.id.toUInt()} failed")
Logger.d { "queueJob packet id=${packet.id.toUInt()} failed" }
}
}
}
@ -186,7 +186,7 @@ constructor(
}
sendToRadio(ToRadio.newBuilder().apply { this.packet = packet })
} catch (ex: Exception) {
Timber.e(ex, "sendToRadio error: ${ex.message}")
Logger.e(ex) { "sendToRadio error: ${ex.message}" }
future.complete(false)
}
return future

View file

@ -77,7 +77,6 @@ import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
@ -88,6 +87,7 @@ import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import co.touchlab.kermit.Logger
import com.geeksville.mesh.BuildConfig
import com.geeksville.mesh.model.BTScanModel
import com.geeksville.mesh.model.UIViewModel
@ -157,7 +157,6 @@ import org.meshtastic.core.ui.theme.StatusColors.StatusBlue
import org.meshtastic.core.ui.theme.StatusColors.StatusGreen
import org.meshtastic.feature.node.metrics.annotateTraceroute
import org.meshtastic.proto.MeshProtos
import timber.log.Timber
enum class TopLevelDestination(val label: StringResource, val icon: ImageVector, val route: Route) {
Conversations(Res.string.conversations, MeshtasticIcons.Conversations, ContactsRoutes.ContactsGraph),
@ -610,7 +609,7 @@ private fun VersionChecks(viewModel: UIViewModel) {
LaunchedEffect(connectionState, firmwareEdition) {
if (connectionState == ConnectionState.Connected) {
firmwareEdition?.let { edition ->
Timber.d("FirmwareEdition: ${edition.name}")
Logger.d { "FirmwareEdition: ${edition.name}" }
when (edition) {
MeshProtos.FirmwareEdition.VANILLA -> {
// Handle any specific logic for VANILLA firmware edition if needed
@ -627,21 +626,21 @@ private fun VersionChecks(viewModel: UIViewModel) {
// Check if the device is running an old app version or firmware version
LaunchedEffect(connectionState, myNodeInfo) {
if (connectionState == ConnectionState.Connected) {
Timber.i(
Logger.i {
"[FW_CHECK] Connection state: $connectionState, " +
"myNodeInfo: ${if (myNodeInfo != null) "present" else "null"}, " +
"firmwareVersion: ${myFirmwareVersion ?: "null"}",
)
"firmwareVersion: ${myFirmwareVersion ?: "null"}"
}
myNodeInfo?.let { info ->
val isOld = info.minAppVersion > BuildConfig.VERSION_CODE && BuildConfig.DEBUG.not()
Timber.d(
Logger.d {
"[FW_CHECK] App version check - minAppVersion: ${info.minAppVersion}, " +
"currentVersion: ${BuildConfig.VERSION_CODE}, isOld: $isOld",
)
"currentVersion: ${BuildConfig.VERSION_CODE}, isOld: $isOld"
}
if (isOld) {
Timber.w("[FW_CHECK] App too old - showing update prompt")
Logger.w { "[FW_CHECK] App too old - showing update prompt" }
viewModel.showAlert(
getString(Res.string.app_too_old),
getString(Res.string.must_update),
@ -654,18 +653,18 @@ private fun VersionChecks(viewModel: UIViewModel) {
} else {
myFirmwareVersion?.let { fwVersion ->
val curVer = DeviceVersion(fwVersion)
Timber.i(
Logger.i {
"[FW_CHECK] Firmware version comparison - " +
"device: $curVer (raw: $fwVersion), " +
"absoluteMin: ${MeshService.absoluteMinDeviceVersion}, " +
"min: ${MeshService.minDeviceVersion}",
)
"min: ${MeshService.minDeviceVersion}"
}
if (curVer < MeshService.absoluteMinDeviceVersion) {
Timber.w(
Logger.w {
"[FW_CHECK] Firmware too old - " +
"device: $curVer < absoluteMin: ${MeshService.absoluteMinDeviceVersion}",
)
"device: $curVer < absoluteMin: ${MeshService.absoluteMinDeviceVersion}"
}
val title = getString(Res.string.firmware_too_old)
val message = getString(Res.string.firmware_old)
viewModel.showAlert(
@ -678,21 +677,21 @@ private fun VersionChecks(viewModel: UIViewModel) {
},
)
} else if (curVer < MeshService.minDeviceVersion) {
Timber.w(
Logger.w {
"[FW_CHECK] Firmware should update - " +
"device: $curVer < min: ${MeshService.minDeviceVersion}",
)
"device: $curVer < min: ${MeshService.minDeviceVersion}"
}
val title = getString(Res.string.should_update_firmware)
val message = getString(Res.string.should_update, latestStableFirmwareRelease.asString)
viewModel.showAlert(title = title, message = message, dismissable = false, onConfirm = {})
} else {
Timber.i("[FW_CHECK] Firmware version OK - device: $curVer meets requirements")
Logger.i { "[FW_CHECK] Firmware version OK - device: $curVer meets requirements" }
}
} ?: run { Timber.w("[FW_CHECK] Firmware version is null despite myNodeInfo being present") }
} ?: run { Logger.w { "[FW_CHECK] Firmware version is null despite myNodeInfo being present" } }
}
} ?: run { Timber.d("[FW_CHECK] myNodeInfo is null, skipping firmware check") }
} ?: run { Logger.d { "[FW_CHECK] myNodeInfo is null, skipping firmware check" } }
} else {
Timber.d("[FW_CHECK] Not connected (state: $connectionState), skipping firmware check")
Logger.d { "[FW_CHECK] Not connected (state: $connectionState), skipping firmware check" }
}
}
}

View file

@ -40,6 +40,7 @@ import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.PreviewLightDark
import androidx.compose.ui.unit.dp
import co.touchlab.kermit.Logger
import com.geeksville.mesh.model.DeviceListEntry
import kotlinx.coroutines.delay
import kotlinx.coroutines.withTimeout
@ -58,7 +59,6 @@ import org.meshtastic.core.ui.theme.StatusColors.StatusRed
import org.meshtastic.proto.MeshProtos
import org.meshtastic.proto.PaxcountProtos
import org.meshtastic.proto.TelemetryProtos
import timber.log.Timber
import kotlin.time.Duration.Companion.seconds
private const val RSSI_DELAY = 10
@ -81,13 +81,13 @@ fun CurrentlyConnectedInfo(
rssi = withTimeout(RSSI_TIMEOUT.seconds) { bleDevice.peripheral.readRssi() }
delay(RSSI_DELAY.seconds)
} catch (e: PeripheralNotConnectedException) {
Timber.e(e, "Failed to read RSSI ${e.message}")
Logger.e(e) { "Failed to read RSSI ${e.message}" }
break
} catch (e: OperationFailedException) {
Timber.e(e, "Failed to read RSSI ${e.message}")
Logger.e(e) { "Failed to read RSSI ${e.message}" }
break
} catch (e: SecurityException) {
Timber.e(e, "Failed to read RSSI ${e.message}")
Logger.e(e) { "Failed to read RSSI ${e.message}" }
break
}
}

View file

@ -87,6 +87,7 @@ import androidx.compose.ui.unit.sp
import androidx.core.net.toUri
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import co.touchlab.kermit.Logger
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.isGranted
import com.google.accompanist.permissions.rememberPermissionState
@ -133,7 +134,6 @@ import org.meshtastic.proto.ChannelProtos
import org.meshtastic.proto.ConfigProtos
import org.meshtastic.proto.channelSet
import org.meshtastic.proto.copy
import timber.log.Timber
/**
* Composable screen for managing and sharing Meshtastic channels. Allows users to view, edit, and share channel
@ -209,7 +209,7 @@ fun ChannelScreen(
}
fun zxingScan() {
Timber.d("Starting zxing QR code scanner")
Logger.d { "Starting zxing QR code scanner" }
val zxingScan = ScanOptions()
zxingScan.setCameraId(0)
zxingScan.setPrompt("")
@ -236,7 +236,7 @@ fun ChannelScreen(
viewModel.setChannels(newChannelSet)
// Since we are writing to DeviceConfig, that will trigger the rest of the GUI update (QR code etc)
} catch (ex: RemoteException) {
Timber.e(ex, "ignoring channel problem")
Logger.e(ex) { "ignoring channel problem" }
channelSet = channels // Throw away user edits
@ -264,7 +264,7 @@ fun ChannelScreen(
confirmButton = {
TextButton(
onClick = {
Timber.d("Switching back to default channel")
Logger.d { "Switching back to default channel" }
installSettings(
Channel.default.settings,
Channel.default.loraConfig.copy {

View file

@ -21,6 +21,7 @@ import android.net.Uri
import android.os.RemoteException
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import co.touchlab.kermit.Logger
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@ -39,7 +40,6 @@ import org.meshtastic.proto.LocalOnlyProtos.LocalConfig
import org.meshtastic.proto.channelSet
import org.meshtastic.proto.config
import org.meshtastic.proto.copy
import timber.log.Timber
import javax.inject.Inject
@HiltViewModel
@ -80,7 +80,7 @@ constructor(
fun requestChannelUrl(url: Uri, onError: () -> Unit) = runCatching { _requestChannelSet.value = url.toChannelSet() }
.onFailure { ex ->
Timber.e(ex, "Channel url error")
Logger.e(ex) { "Channel url error" }
onError()
}
@ -101,7 +101,7 @@ constructor(
try {
serviceRepository.meshService?.setChannel(channel.toByteArray())
} catch (ex: RemoteException) {
Timber.e(ex, "Set channel error")
Logger.e(ex) { "Set channel error" }
}
}
@ -110,7 +110,7 @@ constructor(
try {
serviceRepository.meshService?.setConfig(config.toByteArray())
} catch (ex: RemoteException) {
Timber.e(ex, "Set config error")
Logger.e(ex) { "Set config error" }
}
}

View file

@ -19,7 +19,7 @@ package com.geeksville.mesh.util
import android.os.RemoteException
import android.util.Log
import timber.log.Timber
import co.touchlab.kermit.Logger
object Exceptions {
// / Set in Application.onCreate
@ -31,10 +31,9 @@ object Exceptions {
* After reporting return
*/
fun report(exception: Throwable, tag: String? = null, message: String? = null) {
Timber.e(
exception,
"Exceptions.report: $tag $message",
) // print the message to the log _before_ telling the crash reporter
Logger.e(exception) {
"Exceptions.report: $tag $message"
} // print the message to the log _before_ telling the crash reporter
reporter?.let { r -> r(exception, tag, message) }
}
}
@ -58,7 +57,7 @@ fun ignoreException(silent: Boolean = false, inner: () -> Unit) {
inner()
} catch (ex: Throwable) {
// DO NOT THROW users expect we have fully handled/discarded the exception
if (!silent) Timber.e("ignoring exception", ex)
if (!silent) Logger.e(ex) { "ignoring exception" }
}
}