diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 7b95c1444..837a8458d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -232,7 +232,7 @@ dependencies { implementation(libs.usb.serial.android) implementation(libs.androidx.work.runtime.ktx) implementation(libs.accompanist.permissions) - implementation(libs.timber) + implementation(libs.kermit) implementation(libs.nordic) diff --git a/app/detekt-baseline.xml b/app/detekt-baseline.xml index b5b3189af..0227b52b2 100644 --- a/app/detekt-baseline.xml +++ b/app/detekt-baseline.xml @@ -5,11 +5,6 @@ CommentSpacing:BLEException.kt$BLEConnectionClosing$/// Our interface is being shut down CommentSpacing:Constants.kt$/// a bool true means we expect this condition to continue until, false means device might come back CommentSpacing:Coroutines.kt$/// Wrap launch with an exception handler, FIXME, move into a utility lib - ComposableParamOrder:Channel.kt$ChannelScreen - ComposableParamOrder:Channel.kt$EditChannelUrl - ComposableParamOrder:ConnectionsNavIcon.kt$ConnectionsNavIcon - ComposableParamOrder:EmptyStateContent.kt$EmptyStateContent - ComposableParamOrder:Share.kt$ShareScreen CyclomaticComplexMethod:BleError.kt$BleError.Companion$fun from(exception: Throwable): BleError CyclomaticComplexMethod:MeshService.kt$MeshService$private fun handleReceivedData(packet: MeshPacket) CyclomaticComplexMethod:SettingsNavigation.kt$@Suppress("LongMethod") fun NavGraphBuilder.settingsGraph(navController: NavHostController) @@ -32,10 +27,9 @@ FinalNewline:SerialInterfaceFactory.kt$com.geeksville.mesh.repository.radio.SerialInterfaceFactory.kt FinalNewline:TCPInterfaceFactory.kt$com.geeksville.mesh.repository.radio.TCPInterfaceFactory.kt FinalNewline:UsbRepositoryModule.kt$com.geeksville.mesh.repository.usb.UsbRepositoryModule.kt - LambdaParameterEventTrailing:Channel.kt$onConfirm - LambdaParameterInRestartableEffect:Channel.kt$onConfirm LargeClass:MeshService.kt$MeshService : Service LongMethod:MeshService.kt$MeshService$private fun handleReceivedData(packet: MeshPacket) + LongMethod:TCPInterface.kt$TCPInterface$private suspend fun startConnect() MagicNumber:Contacts.kt$7 MagicNumber:Contacts.kt$8 MagicNumber:MQTTRepository.kt$MQTTRepository$512 @@ -60,27 +54,15 @@ MagicNumber:StreamInterface.kt$StreamInterface$4 MagicNumber:StreamInterface.kt$StreamInterface$8 MagicNumber:TCPInterface.kt$TCPInterface$1000 - MagicNumber:TCPInterface.kt$TCPInterface$180 - MagicNumber:TCPInterface.kt$TCPInterface$500 MagicNumber:UIState.kt$4 MaxLineLength:MeshService.kt$MeshService$"Config complete id mismatch: received=$configCompleteId expected one of [$configOnlyNonce,$nodeInfoNonce]" + MaxLineLength:MeshService.kt$MeshService$"Neighbor info response filtered: ToUs=$isAddressedToUs, isRecentRequest=$isRecentRequest" MaxLineLength:MeshService.kt$MeshService$"setOwner Id: $id longName: ${longName.anonymize} shortName: $shortName isLicensed: $isLicensed isUnmessagable: $isUnmessagable" MaxLineLength:MeshService.kt$MeshService.<no name provided>$"sendData dest=${p.to}, id=${p.id} <- ${bytes.size} bytes (connectionState=${connectionStateHolder.connectionState.value})" MaxLineLength:NordicBleInterface.kt$NordicBleInterface$"[$address] Found fromNum: ${fromNumCharacteristic?.uuid}, ${fromNumCharacteristic?.instanceId}" MaxLineLength:NordicBleInterface.kt$NordicBleInterface$"[$address] Found fromRadio: ${fromRadioCharacteristic?.uuid}, ${fromRadioCharacteristic?.instanceId}" MaxLineLength:NordicBleInterface.kt$NordicBleInterface$"[$address] Found logRadio: ${logRadioCharacteristic?.uuid}, ${logRadioCharacteristic?.instanceId}" MaxLineLength:NordicBleInterface.kt$NordicBleInterface$"[$address] Found toRadio: ${toRadioCharacteristic?.uuid}, ${toRadioCharacteristic?.instanceId}" - ModifierClickableOrder:Channel.kt$clickable(onClick = onClick) - ModifierMissing:BLEDevices.kt$BLEDevices - ModifierMissing:Channel.kt$ChannelScreen - ModifierMissing:Contacts.kt$ContactListView - ModifierMissing:Contacts.kt$ContactsScreen - ModifierMissing:Contacts.kt$SelectionToolbar - ModifierMissing:EmptyStateContent.kt$EmptyStateContent - ModifierMissing:Main.kt$MainScreen - ModifierMissing:NetworkDevices.kt$NetworkDevices - ModifierMissing:Share.kt$ShareScreen - MutableStateAutoboxing:Contacts.kt$mutableStateOf(2) NestedBlockDepth:MeshService.kt$MeshService$private fun handleReceivedAdmin(fromNodeNum: Int, a: AdminProtos.AdminMessage) NestedBlockDepth:MeshService.kt$MeshService$private fun handleReceivedData(packet: MeshPacket) NewLineAtEndOfFile:BLEException.kt$com.geeksville.mesh.service.BLEException.kt @@ -106,11 +88,6 @@ NoEmptyClassBody:DebugLogFile.kt$BinaryLogFile${ } NoSemicolons:DateUtils.kt$DateUtils$; OptionalAbstractKeyword:SyncContinuation.kt$Continuation$abstract - ParameterNaming:Contacts.kt$onDeleteSelected - ParameterNaming:Contacts.kt$onMuteSelected - ParameterNaming:UsbDevices.kt$onDeviceSelected - PreviewPublic:Channel.kt$ModemPresetInfoPreview - PreviewPublic:EmptyStateContent.kt$EmptyStateContentPreview RethrowCaughtException:SyncContinuation.kt$Continuation$throw ex SwallowedException:Exceptions.kt$ex: Throwable SwallowedException:NsdManager.kt$ex: IllegalArgumentException @@ -138,6 +115,5 @@ TooManyFunctions:UIState.kt$UIViewModel : ViewModel TopLevelPropertyNaming:Constants.kt$const val prefix = "com.geeksville.mesh" UtilityClassWithPublicConstructor:NetworkRepositoryModule.kt$NetworkRepositoryModule - ViewModelForwarding:Main.kt$VersionChecks(uIViewModel) diff --git a/app/src/main/java/com/geeksville/mesh/MainActivity.kt b/app/src/main/java/com/geeksville/mesh/MainActivity.kt index daf26128e..914d69c79 100644 --- a/app/src/main/java/com/geeksville/mesh/MainActivity.kt +++ b/app/src/main/java/com/geeksville/mesh/MainActivity.kt @@ -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" } } } } diff --git a/app/src/main/java/com/geeksville/mesh/MeshServiceClient.kt b/app/src/main/java/com/geeksville/mesh/MeshServiceClient.kt index f57e487ce..94cd1a191 100644 --- a/app/src/main/java/com/geeksville/mesh/MeshServiceClient.kt +++ b/app/src/main/java/com/geeksville/mesh/MeshServiceClient.kt @@ -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) diff --git a/app/src/main/java/com/geeksville/mesh/MeshUtilApplication.kt b/app/src/main/java/com/geeksville/mesh/MeshUtilApplication.kt index 11feaa972..c2df57fa3 100644 --- a/app/src/main/java/com/geeksville/mesh/MeshUtilApplication.kt +++ b/app/src/main/java/com/geeksville/mesh/MeshUtilApplication.kt @@ -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 } } diff --git a/app/src/main/java/com/geeksville/mesh/android/ServiceClient.kt b/app/src/main/java/com/geeksville/mesh/android/ServiceClient.kt index f10baf5bf..3216f7daa 100644 --- a/app/src/main/java/com/geeksville/mesh/android/ServiceClient.kt +++ b/app/src/main/java/com/geeksville/mesh/android/ServiceClient.kt @@ -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(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(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(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" } } } diff --git a/app/src/main/java/com/geeksville/mesh/concurrent/DeferredExecution.kt b/app/src/main/java/com/geeksville/mesh/concurrent/DeferredExecution.kt index 934d8910b..4bfa231f6 100644 --- a/app/src/main/java/com/geeksville/mesh/concurrent/DeferredExecution.kt +++ b/app/src/main/java/com/geeksville/mesh/concurrent/DeferredExecution.kt @@ -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() } diff --git a/app/src/main/java/com/geeksville/mesh/concurrent/SyncContinuation.kt b/app/src/main/java/com/geeksville/mesh/concurrent/SyncContinuation.kt index 6e7d56ce8..ee81e75a4 100644 --- a/app/src/main/java/com/geeksville/mesh/concurrent/SyncContinuation.kt +++ b/app/src/main/java/com/geeksville/mesh/concurrent/SyncContinuation.kt @@ -27,7 +27,7 @@ interface Continuation { 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 } } diff --git a/app/src/main/java/com/geeksville/mesh/model/BTScanModel.kt b/app/src/main/java/com/geeksville/mesh/model/BTScanModel.kt index e09146993..a8cca8e9b 100644 --- a/app/src/main/java/com/geeksville/mesh/model/BTScanModel.kt +++ b/app/src/main/java/com/geeksville/mesh/model/BTScanModel.kt @@ -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) diff --git a/app/src/main/java/com/geeksville/mesh/model/UIState.kt b/app/src/main/java/com/geeksville/mesh/model/UIState.kt index 0d340c9cb..880810aec 100644 --- a/app/src/main/java/com/geeksville/mesh/model/UIState.kt +++ b/app/src/main/java/com/geeksville/mesh/model/UIState.kt @@ -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 = 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 diff --git a/app/src/main/java/com/geeksville/mesh/repository/bluetooth/BluetoothRepository.kt b/app/src/main/java/com/geeksville/mesh/repository/bluetooth/BluetoothRepository.kt index 0544556b2..f41869beb 100644 --- a/app/src/main/java/com/geeksville/mesh/repository/bluetooth/BluetoothRepository.kt +++ b/app/src/main/java/com/geeksville/mesh/repository/bluetooth/BluetoothRepository.kt @@ -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") diff --git a/app/src/main/java/com/geeksville/mesh/repository/network/MQTTRepository.kt b/app/src/main/java/com/geeksville/mesh/repository/network/MQTTRepository.kt index 074d1851f..d1b29cf80 100644 --- a/app/src/main/java/com/geeksville/mesh/repository/network/MQTTRepository.kt +++ b/app/src/main/java/com/geeksville/mesh/repository/network/MQTTRepository.kt @@ -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}" } } } } diff --git a/app/src/main/java/com/geeksville/mesh/repository/network/NsdManager.kt b/app/src/main/java/com/geeksville/mesh/repository/network/NsdManager.kt index 47a47f7b8..4646543b3 100644 --- a/app/src/main/java/com/geeksville/mesh/repository/network/NsdManager.kt +++ b/app/src/main/java/com/geeksville/mesh/repository/network/NsdManager.kt @@ -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 { diff --git a/app/src/main/java/com/geeksville/mesh/repository/radio/MockInterface.kt b/app/src/main/java/com/geeksville/mesh/repository/radio/MockInterface.kt index a2f80bae1..3dc1da8b8 100644 --- a/app/src/main/java/com/geeksville/mesh/repository/radio/MockInterface.kt +++ b/app/src/main/java/com/geeksville/mesh/repository/radio/MockInterface.kt @@ -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") diff --git a/app/src/main/java/com/geeksville/mesh/repository/radio/NordicBleInterface.kt b/app/src/main/java/com/geeksville/mesh/repository/radio/NordicBleInterface.kt index 9ceed32c7..e88508b6d 100644 --- a/app/src/main/java/com/geeksville/mesh/repository/radio/NordicBleInterface.kt +++ b/app/src/main/java/com/geeksville/mesh/repository/radio/NordicBleInterface.kt @@ -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) diff --git a/app/src/main/java/com/geeksville/mesh/repository/radio/NordicBleInterfaceSpec.kt b/app/src/main/java/com/geeksville/mesh/repository/radio/NordicBleInterfaceSpec.kt index 8316cea2b..98698206b 100644 --- a/app/src/main/java/com/geeksville/mesh/repository/radio/NordicBleInterfaceSpec.kt +++ b/app/src/main/java/com/geeksville/mesh/repository/radio/NordicBleInterfaceSpec.kt @@ -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 diff --git a/app/src/main/java/com/geeksville/mesh/repository/radio/RadioInterfaceService.kt b/app/src/main/java/com/geeksville/mesh/repository/radio/RadioInterfaceService.kt index 995db651b..b34756481 100644 --- a/app/src/main/java/com/geeksville/mesh/repository/radio/RadioInterfaceService.kt +++ b/app/src/main/java/com/geeksville/mesh/repository/radio/RadioInterfaceService.kt @@ -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" } } } } diff --git a/app/src/main/java/com/geeksville/mesh/repository/radio/SerialInterface.kt b/app/src/main/java/com/geeksville/mesh/repository/radio/SerialInterface.kt index 264d0cab3..d015621a5 100644 --- a/app/src/main/java/com/geeksville/mesh/repository/radio/SerialInterface.kt +++ b/app/src/main/java/com/geeksville/mesh/repository/radio/SerialInterface.kt @@ -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" } } } } diff --git a/app/src/main/java/com/geeksville/mesh/repository/radio/StreamInterface.kt b/app/src/main/java/com/geeksville/mesh/repository/radio/StreamInterface.kt index a7e712e63..3dd50cea0 100644 --- a/app/src/main/java/com/geeksville/mesh/repository/radio/StreamInterface.kt +++ b/app/src/main/java/com/geeksville/mesh/repository/radio/StreamInterface.kt @@ -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 } diff --git a/app/src/main/java/com/geeksville/mesh/repository/radio/TCPInterface.kt b/app/src/main/java/com/geeksville/mesh/repository/radio/TCPInterface.kt index 5bc3442b8..68333275e 100644 --- a/app/src/main/java/com/geeksville/mesh/repository/radio/TCPInterface.kt +++ b/app/src/main/java/com/geeksville/mesh/repository/radio/TCPInterface.kt @@ -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)" + } } } } diff --git a/app/src/main/java/com/geeksville/mesh/repository/usb/SerialConnectionImpl.kt b/app/src/main/java/com/geeksville/mesh/repository/usb/SerialConnectionImpl.kt index ad9c3bc47..508e9642e 100644 --- a/app/src/main/java/com/geeksville/mesh/repository/usb/SerialConnectionImpl.kt +++ b/app/src/main/java/com/geeksville/mesh/repository/usb/SerialConnectionImpl.kt @@ -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, diff --git a/app/src/main/java/com/geeksville/mesh/repository/usb/UsbBroadcastReceiver.kt b/app/src/main/java/com/geeksville/mesh/repository/usb/UsbBroadcastReceiver.kt index e7f60855a..3f324ec05 100644 --- a/app/src/main/java/com/geeksville/mesh/repository/usb/UsbBroadcastReceiver.kt +++ b/app/src/main/java/com/geeksville/mesh/repository/usb/UsbBroadcastReceiver.kt @@ -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() } } diff --git a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt index 0e14bf926..f99b05224 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt @@ -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 = 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 { diff --git a/app/src/main/java/com/geeksville/mesh/service/MeshServiceBroadcasts.kt b/app/src/main/java/com/geeksville/mesh/service/MeshServiceBroadcasts.kt index 5f148da72..1588394d3 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshServiceBroadcasts.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshServiceBroadcasts.kt @@ -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) diff --git a/app/src/main/java/com/geeksville/mesh/service/MeshServiceStarter.kt b/app/src/main/java/com/geeksville/mesh/service/MeshServiceStarter.kt index 67274eaa6..ed59ff5b2 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshServiceStarter.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshServiceStarter.kt @@ -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) diff --git a/app/src/main/java/com/geeksville/mesh/service/PacketHandler.kt b/app/src/main/java/com/geeksville/mesh/service/PacketHandler.kt index dd4886613..a46b835ba 100644 --- a/app/src/main/java/com/geeksville/mesh/service/PacketHandler.kt +++ b/app/src/main/java/com/geeksville/mesh/service/PacketHandler.kt @@ -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 diff --git a/app/src/main/java/com/geeksville/mesh/ui/Main.kt b/app/src/main/java/com/geeksville/mesh/ui/Main.kt index 1ea9d75a7..ea07f4f46 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/Main.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/Main.kt @@ -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" } } } } diff --git a/app/src/main/java/com/geeksville/mesh/ui/connections/components/CurrentlyConnectedInfo.kt b/app/src/main/java/com/geeksville/mesh/ui/connections/components/CurrentlyConnectedInfo.kt index 595d7994e..90b233d1c 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/connections/components/CurrentlyConnectedInfo.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/connections/components/CurrentlyConnectedInfo.kt @@ -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 } } diff --git a/app/src/main/java/com/geeksville/mesh/ui/sharing/Channel.kt b/app/src/main/java/com/geeksville/mesh/ui/sharing/Channel.kt index 79e070200..a36f8fc2e 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/sharing/Channel.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/sharing/Channel.kt @@ -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 { diff --git a/app/src/main/java/com/geeksville/mesh/ui/sharing/ChannelViewModel.kt b/app/src/main/java/com/geeksville/mesh/ui/sharing/ChannelViewModel.kt index a7a7a76c7..fb6c6d29f 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/sharing/ChannelViewModel.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/sharing/ChannelViewModel.kt @@ -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" } } } diff --git a/app/src/main/java/com/geeksville/mesh/util/Exceptions.kt b/app/src/main/java/com/geeksville/mesh/util/Exceptions.kt index 108a07cf9..4471efd4f 100644 --- a/app/src/main/java/com/geeksville/mesh/util/Exceptions.kt +++ b/app/src/main/java/com/geeksville/mesh/util/Exceptions.kt @@ -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" } } } diff --git a/core/analytics/build.gradle.kts b/core/analytics/build.gradle.kts index c19f95a56..7d23198df 100644 --- a/core/analytics/build.gradle.kts +++ b/core/analytics/build.gradle.kts @@ -46,7 +46,7 @@ dependencies { implementation(libs.androidx.compose.runtime) implementation(libs.androidx.lifecycle.process) implementation(libs.androidx.navigation.runtime) - implementation(libs.timber) + implementation(libs.kermit) googleApi(libs.dd.sdk.android.compose) googleApi(libs.dd.sdk.android.logs) diff --git a/core/analytics/src/fdroid/kotlin/org/meshtastic/core/analytics/platform/FdroidPlatformAnalytics.kt b/core/analytics/src/fdroid/kotlin/org/meshtastic/core/analytics/platform/FdroidPlatformAnalytics.kt index 84bd0ff07..a8b4532d1 100644 --- a/core/analytics/src/fdroid/kotlin/org/meshtastic/core/analytics/platform/FdroidPlatformAnalytics.kt +++ b/core/analytics/src/fdroid/kotlin/org/meshtastic/core/analytics/platform/FdroidPlatformAnalytics.kt @@ -19,9 +19,10 @@ package org.meshtastic.core.analytics.platform import androidx.compose.runtime.Composable import androidx.navigation.NavHostController +import co.touchlab.kermit.Logger +import co.touchlab.kermit.Severity import org.meshtastic.core.analytics.BuildConfig import org.meshtastic.core.analytics.DataPair -import timber.log.Timber import javax.inject.Inject /** @@ -34,16 +35,17 @@ class FdroidPlatformAnalytics @Inject constructor() : PlatformAnalytics { // In debug builds we attach a DebugTree for convenient local logging, but // release builds rely on system logging only. if (BuildConfig.DEBUG) { - Timber.plant(Timber.DebugTree()) - Timber.i("F-Droid platform no-op analytics initialized (DebugTree planted).") + Logger.setMinSeverity(Severity.Debug) + Logger.i { "F-Droid platform no-op analytics initialized (Debug mode }." } } else { - Timber.i("F-Droid platform no-op analytics initialized.") + Logger.setMinSeverity(Severity.Info) + Logger.i { "F-Droid platform no-op analytics initialized." } } } override fun setDeviceAttributes(firmwareVersion: String, model: String) { // No-op for F-Droid - Timber.d("Set device attributes called: firmwareVersion=$firmwareVersion, deviceHardware=$model") + Logger.d { "Set device attributes called: firmwareVersion=$firmwareVersion, deviceHardware=$model" } } @Composable @@ -51,7 +53,7 @@ class FdroidPlatformAnalytics @Inject constructor() : PlatformAnalytics { // No-op for F-Droid, but we can log navigation if needed for debugging if (BuildConfig.DEBUG) { navController.addOnDestinationChangedListener { _, destination, _ -> - Timber.d("Navigation changed to: ${destination.route}") + Logger.d { "Navigation changed to: ${destination.route}" } } } } @@ -60,6 +62,6 @@ class FdroidPlatformAnalytics @Inject constructor() : PlatformAnalytics { get() = false override fun track(event: String, vararg properties: DataPair) { - Timber.d("Track called: event=$event, properties=${properties.toList()}") + Logger.d { "Track called: event=$event, properties=${properties.toList()}" } } } diff --git a/core/analytics/src/google/kotlin/org/meshtastic/core/analytics/platform/GooglePlatformAnalytics.kt b/core/analytics/src/google/kotlin/org/meshtastic/core/analytics/platform/GooglePlatformAnalytics.kt index 412fed033..36d5db6ca 100644 --- a/core/analytics/src/google/kotlin/org/meshtastic/core/analytics/platform/GooglePlatformAnalytics.kt +++ b/core/analytics/src/google/kotlin/org/meshtastic/core/analytics/platform/GooglePlatformAnalytics.kt @@ -26,6 +26,8 @@ import androidx.compose.runtime.Composable import androidx.lifecycle.ProcessLifecycleOwner import androidx.lifecycle.lifecycleScope import androidx.navigation.NavHostController +import co.touchlab.kermit.LogWriter +import co.touchlab.kermit.Severity import com.datadog.android.Datadog import com.datadog.android.DatadogSite import com.datadog.android.compose.ExperimentalTrackingApi @@ -43,7 +45,6 @@ import com.datadog.android.rum.tracking.AcceptAllNavDestinations import com.datadog.android.sessionreplay.SessionReplay import com.datadog.android.sessionreplay.SessionReplayConfiguration import com.datadog.android.sessionreplay.compose.ComposeExtensionSupport -import com.datadog.android.timber.DatadogTree import com.datadog.android.trace.Trace import com.datadog.android.trace.TraceConfiguration import com.datadog.android.trace.opentelemetry.DatadogOpenTelemetry @@ -61,10 +62,8 @@ import kotlinx.coroutines.flow.onEach import org.meshtastic.core.analytics.BuildConfig import org.meshtastic.core.analytics.DataPair import org.meshtastic.core.prefs.analytics.AnalyticsPrefs -import timber.log.Timber -import timber.log.Timber.DebugTree -import timber.log.Timber.Tree import javax.inject.Inject +import co.touchlab.kermit.Logger as KermitLogger /** * Google Play Services specific implementation of [PlatformAnalytics]. This helper initializes and manages Firebase and @@ -102,14 +101,15 @@ constructor( .setBundleWithTraceEnabled(true) .setBundleWithRumEnabled(true) .build() - buildList { - add(DatadogTree(datadogLogger)) - add(CrashlyticsTree()) + val writers = buildList { + add(DatadogLogWriter(datadogLogger)) + add(CrashlyticsLogWriter()) if (BuildConfig.DEBUG) { - add(DebugTree()) + add(co.touchlab.kermit.LogcatWriter()) } } - .forEach(Timber::plant) + KermitLogger.setLogWriters(writers) + KermitLogger.setMinSeverity(if (BuildConfig.DEBUG) Severity.Debug else Severity.Info) // Initial consent state updateAnalyticsConsent(analyticsPrefs.analyticsAllowed) @@ -177,10 +177,10 @@ constructor( */ fun updateAnalyticsConsent(allowed: Boolean) { if (!isPlatformServicesAvailable || isInTestLab) { - Timber.i("Analytics not available or in test lab, consent update skipped.") + KermitLogger.i { "Analytics not available or in test lab, consent update skipped." } return } - Timber.i(if (allowed) "Analytics enabled" else "Analytics disabled") + KermitLogger.i { if (allowed) "Analytics enabled" else "Analytics disabled" } Datadog.setTrackingConsent(if (allowed) TrackingConsent.GRANTED else TrackingConsent.NOT_GRANTED) Firebase.crashlytics.isCrashlyticsCollectionEnabled = allowed @@ -221,30 +221,45 @@ constructor( override val isPlatformServicesAvailable: Boolean get() = isGooglePlayAvailable && isDatadogAvailable - private class CrashlyticsTree : Tree() { + private class CrashlyticsLogWriter : LogWriter() { companion object { private const val KEY_PRIORITY = "priority" private const val KEY_TAG = "tag" private const val KEY_MESSAGE = "message" } - override fun log(priority: Int, tag: String?, message: String, t: Throwable?) { + override fun log(severity: Severity, message: String, tag: String, throwable: Throwable?) { if (!Firebase.crashlytics.isCrashlyticsCollectionEnabled) return Firebase.crashlytics.setCustomKeys { - key(KEY_PRIORITY, priority) - key(KEY_TAG, tag ?: "No Tag") + key(KEY_PRIORITY, severity.ordinal) + key(KEY_TAG, tag) key(KEY_MESSAGE, message) } - if (t == null) { + if (throwable == null) { Firebase.crashlytics.recordException(Exception(message)) } else { - Firebase.crashlytics.recordException(t) + Firebase.crashlytics.recordException(throwable) } } } + private class DatadogLogWriter(private val datadogLogger: Logger) : LogWriter() { + override fun log(severity: Severity, message: String, tag: String, throwable: Throwable?) { + val datadogPriority = + when (severity) { + Severity.Verbose -> android.util.Log.VERBOSE + Severity.Debug -> android.util.Log.DEBUG + Severity.Info -> android.util.Log.INFO + Severity.Warn -> android.util.Log.WARN + Severity.Error -> android.util.Log.ERROR + Severity.Assert -> android.util.Log.ASSERT + } + datadogLogger.log(datadogPriority, message, throwable, mapOf("tag" to tag)) + } + } + private fun String.extractSemanticVersion(): String { val regex = "^(\\d+)(?:\\.(\\d+))?(?:\\.(\\d+))?".toRegex() val matchResult = regex.find(this) @@ -263,7 +278,7 @@ constructor( is String -> bundle.putString(it.name, it.value as String?) // Explicitly handle String else -> bundle.putString(it.name, it.value.toString()) // Fallback for other types } - Timber.tag(TAG).d("Analytics: track $event (${it.name} : ${it.value})") + KermitLogger.withTag(TAG).d { "Analytics: track $event (${it.name} : ${it.value})" } } Firebase.analytics.logEvent(event, bundle) } diff --git a/core/data/build.gradle.kts b/core/data/build.gradle.kts index 220af53c9..3f11c5cb9 100644 --- a/core/data/build.gradle.kts +++ b/core/data/build.gradle.kts @@ -61,5 +61,5 @@ dependencies { implementation(libs.androidx.core.location.altitude) implementation(libs.androidx.paging.common) implementation(libs.kotlinx.serialization.json) - implementation(libs.timber) + implementation(libs.kermit) } diff --git a/core/data/detekt-baseline.xml b/core/data/detekt-baseline.xml index b15fbf373..572c2ff08 100644 --- a/core/data/detekt-baseline.xml +++ b/core/data/detekt-baseline.xml @@ -6,6 +6,11 @@ MagicNumber:LocationRepository.kt$LocationRepository$30 MagicNumber:LocationRepository.kt$LocationRepository$31 MagicNumber:PacketRepository.kt$PacketRepository$500 + MaxLineLength:DeviceHardwareRepository.kt$DeviceHardwareRepository$"DeviceHardwareRepository: applying quirk: requiresBootloaderUpgradeForOta=${quirk.requiresBootloaderUpgradeForOta}, infoUrl=${quirk.infoUrl}" + MaxLineLength:DeviceHardwareRepository.kt$DeviceHardwareRepository$"DeviceHardwareRepository: cache ${if (staleEntity == null) "empty" else "incomplete"} for hwModel=$hwModel, falling back to bundled JSON asset" + MaxLineLength:DeviceHardwareRepository.kt$DeviceHardwareRepository$"DeviceHardwareRepository: failed to load device hardware from bundled JSON for hwModel=$hwModel" + MaxLineLength:DeviceHardwareRepository.kt$DeviceHardwareRepository$"DeviceHardwareRepository: lookup after JSON load for hwModel=$hwModel ${if (base != null) "succeeded" else "returned null"}" + MaxLineLength:DeviceHardwareRepository.kt$DeviceHardwareRepository$"DeviceHardwareRepository: lookup after remote fetch for hwModel=$hwModel ${if (fromDb != null) "succeeded" else "returned null"}" TooGenericExceptionCaught:LocationRepository.kt$LocationRepository$e: Exception TooManyFunctions:PacketRepository.kt$PacketRepository diff --git a/core/data/src/google/kotlin/org/meshtastic/core/data/repository/CustomTileProviderRepository.kt b/core/data/src/google/kotlin/org/meshtastic/core/data/repository/CustomTileProviderRepository.kt index 219a247ce..9ce615f53 100644 --- a/core/data/src/google/kotlin/org/meshtastic/core/data/repository/CustomTileProviderRepository.kt +++ b/core/data/src/google/kotlin/org/meshtastic/core/data/repository/CustomTileProviderRepository.kt @@ -17,6 +17,7 @@ package org.meshtastic.core.data.repository +import co.touchlab.kermit.Logger import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow @@ -26,10 +27,8 @@ import kotlinx.serialization.json.Json import org.meshtastic.core.data.model.CustomTileProviderConfig import org.meshtastic.core.di.CoroutineDispatchers import org.meshtastic.core.prefs.map.MapTileProviderPrefs -import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton -import kotlin.collections.plus interface CustomTileProviderRepository { fun getCustomTileProviders(): Flow> @@ -88,7 +87,7 @@ constructor( try { customTileProvidersStateFlow.value = json.decodeFromString>(jsonString) } catch (e: SerializationException) { - Timber.e(e, "Error deserializing tile providers") + Logger.e(e) { "Error deserializing tile providers" } customTileProvidersStateFlow.value = emptyList() } } else { @@ -102,7 +101,7 @@ constructor( val jsonString = json.encodeToString(providers) mapTileProviderPrefs.customTileProviders = jsonString } catch (e: SerializationException) { - Timber.e(e, "Error serializing tile providers") + Logger.e(e) { "Error serializing tile providers" } } } } diff --git a/core/data/src/main/kotlin/org/meshtastic/core/data/datasource/BootloaderOtaQuirksJsonDataSource.kt b/core/data/src/main/kotlin/org/meshtastic/core/data/datasource/BootloaderOtaQuirksJsonDataSource.kt index 5cc5081ba..29376cd16 100644 --- a/core/data/src/main/kotlin/org/meshtastic/core/data/datasource/BootloaderOtaQuirksJsonDataSource.kt +++ b/core/data/src/main/kotlin/org/meshtastic/core/data/datasource/BootloaderOtaQuirksJsonDataSource.kt @@ -18,12 +18,12 @@ package org.meshtastic.core.data.datasource import android.app.Application +import co.touchlab.kermit.Logger import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json import kotlinx.serialization.json.decodeFromStream import org.meshtastic.core.model.BootloaderOtaQuirk -import timber.log.Timber import javax.inject.Inject class BootloaderOtaQuirksJsonDataSource @Inject constructor(private val application: Application) { @@ -32,7 +32,7 @@ class BootloaderOtaQuirksJsonDataSource @Inject constructor(private val applicat val inputStream = application.assets.open("device_bootloader_ota_quirks.json") inputStream.use { Json.decodeFromStream(it).devices } } - .onFailure { e -> Timber.w(e, "Failed to load device_bootloader_ota_quirks.json") } + .onFailure { e -> Logger.w(e) { "Failed to load device_bootloader_ota_quirks.json" } } .getOrDefault(emptyList()) @Serializable private data class ListWrapper(val devices: List = emptyList()) diff --git a/core/data/src/main/kotlin/org/meshtastic/core/data/repository/DeviceHardwareRepository.kt b/core/data/src/main/kotlin/org/meshtastic/core/data/repository/DeviceHardwareRepository.kt index fcd0b30cf..f527ae6c8 100644 --- a/core/data/src/main/kotlin/org/meshtastic/core/data/repository/DeviceHardwareRepository.kt +++ b/core/data/src/main/kotlin/org/meshtastic/core/data/repository/DeviceHardwareRepository.kt @@ -17,6 +17,7 @@ package org.meshtastic.core.data.repository +import co.touchlab.kermit.Logger import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.meshtastic.core.data.datasource.BootloaderOtaQuirksJsonDataSource @@ -27,7 +28,6 @@ import org.meshtastic.core.database.entity.asExternalModel import org.meshtastic.core.model.BootloaderOtaQuirk import org.meshtastic.core.model.DeviceHardware import org.meshtastic.core.network.DeviceHardwareRemoteDataSource -import timber.log.Timber import java.util.concurrent.TimeUnit import javax.inject.Inject import javax.inject.Singleton @@ -59,45 +59,40 @@ constructor( @Suppress("LongMethod") suspend fun getDeviceHardwareByModel(hwModel: Int, forceRefresh: Boolean = false): Result = withContext(Dispatchers.IO) { - Timber.d( - "DeviceHardwareRepository: getDeviceHardwareByModel(hwModel=%d, forceRefresh=%b)", - hwModel, - forceRefresh, - ) + Logger.d { + "DeviceHardwareRepository: getDeviceHardwareByModel(hwModel=$hwModel, forceRefresh=$forceRefresh)" + } val quirks = loadQuirks() if (forceRefresh) { - Timber.d("DeviceHardwareRepository: forceRefresh=true, clearing local device hardware cache") + Logger.d { "DeviceHardwareRepository: forceRefresh=true, clearing local device hardware cache" } localDataSource.deleteAllDeviceHardware() } else { // 1. Attempt to retrieve from cache first val cachedEntity = localDataSource.getByHwModel(hwModel) if (cachedEntity != null && !cachedEntity.isStale()) { - Timber.d("DeviceHardwareRepository: using fresh cached device hardware for hwModel=%d", hwModel) + Logger.d { "DeviceHardwareRepository: using fresh cached device hardware for hwModel=$hwModel" } return@withContext Result.success( applyBootloaderQuirk(hwModel, cachedEntity.asExternalModel(), quirks), ) } - Timber.d("DeviceHardwareRepository: no fresh cache for hwModel=%d, attempting remote fetch", hwModel) + Logger.d { "DeviceHardwareRepository: no fresh cache for hwModel=$hwModel, attempting remote fetch" } } // 2. Fetch from remote API runCatching { - Timber.d("DeviceHardwareRepository: fetching device hardware from remote API") + Logger.d { "DeviceHardwareRepository: fetching device hardware from remote API" } val remoteHardware = remoteDataSource.getAllDeviceHardware() - Timber.d( - "DeviceHardwareRepository: remote API returned %d device hardware entries", - remoteHardware.size, - ) + Logger.d { + "DeviceHardwareRepository: remote API returned ${remoteHardware.size} device hardware entries" + } localDataSource.insertAllDeviceHardware(remoteHardware) val fromDb = localDataSource.getByHwModel(hwModel)?.asExternalModel() - Timber.d( - "DeviceHardwareRepository: lookup after remote fetch for hwModel=%d %s", - hwModel, - if (fromDb != null) "succeeded" else "returned null", - ) + Logger.d { + "DeviceHardwareRepository: lookup after remote fetch for hwModel=$hwModel ${if (fromDb != null) "succeeded" else "returned null"}" + } fromDb } .onSuccess { @@ -105,57 +100,48 @@ constructor( return@withContext Result.success(applyBootloaderQuirk(hwModel, it, quirks)) } .onFailure { e -> - Timber.w( - e, - "DeviceHardwareRepository: failed to fetch device hardware from server for hwModel=%d", - hwModel, - ) + Logger.w(e) { + "DeviceHardwareRepository: failed to fetch device hardware from server for hwModel=$hwModel" + } // 3. Attempt to use stale cache as a fallback, but only if it looks complete. val staleEntity = localDataSource.getByHwModel(hwModel) if (staleEntity != null && !staleEntity.isIncomplete()) { - Timber.d("DeviceHardwareRepository: using stale cached device hardware for hwModel=%d", hwModel) + Logger.d { "DeviceHardwareRepository: using stale cached device hardware for hwModel=$hwModel" } return@withContext Result.success( applyBootloaderQuirk(hwModel, staleEntity.asExternalModel(), quirks), ) } // 4. Fallback to bundled JSON if cache is empty or incomplete - Timber.d( - "DeviceHardwareRepository: cache %s for hwModel=%d, falling back to bundled JSON asset", - if (staleEntity == null) "empty" else "incomplete", - hwModel, - ) + Logger.d { + "DeviceHardwareRepository: cache ${if (staleEntity == null) "empty" else "incomplete"} for hwModel=$hwModel, falling back to bundled JSON asset" + } return@withContext loadFromBundledJson(hwModel, quirks) } } private suspend fun loadFromBundledJson(hwModel: Int, quirks: List): Result = runCatching { - Timber.d("DeviceHardwareRepository: loading device hardware from bundled JSON for hwModel=%d", hwModel) + Logger.d { "DeviceHardwareRepository: loading device hardware from bundled JSON for hwModel=$hwModel" } val jsonHardware = jsonDataSource.loadDeviceHardwareFromJsonAsset() - Timber.d( - "DeviceHardwareRepository: bundled JSON returned %d device hardware entries", - jsonHardware.size, - ) + Logger.d { + "DeviceHardwareRepository: bundled JSON returned ${jsonHardware.size} device hardware entries" + } localDataSource.insertAllDeviceHardware(jsonHardware) val base = localDataSource.getByHwModel(hwModel)?.asExternalModel() - Timber.d( - "DeviceHardwareRepository: lookup after JSON load for hwModel=%d %s", - hwModel, - if (base != null) "succeeded" else "returned null", - ) + Logger.d { + "DeviceHardwareRepository: lookup after JSON load for hwModel=$hwModel ${if (base != null) "succeeded" else "returned null"}" + } applyBootloaderQuirk(hwModel, base, quirks) } .also { result -> result.exceptionOrNull()?.let { e -> - Timber.e( - e, - "DeviceHardwareRepository: failed to load device hardware from bundled JSON for hwModel=%d", - hwModel, - ) + Logger.e(e) { + "DeviceHardwareRepository: failed to load device hardware from bundled JSON for hwModel=$hwModel" + } } } @@ -174,7 +160,7 @@ constructor( private fun loadQuirks(): List { val quirks = bootloaderOtaQuirksJsonDataSource.loadBootloaderOtaQuirksFromJsonAsset() - Timber.d("DeviceHardwareRepository: loaded %d bootloader quirks", quirks.size) + Logger.d { "DeviceHardwareRepository: loaded ${quirks.size} bootloader quirks" } return quirks } @@ -186,17 +172,11 @@ constructor( if (base == null) return null val quirk = quirks.firstOrNull { it.hwModel == hwModel } - Timber.d( - "DeviceHardwareRepository: applyBootloaderQuirk for hwModel=%d, quirk found=%b", - hwModel, - quirk != null, - ) + Logger.d { "DeviceHardwareRepository: applyBootloaderQuirk for hwModel=$hwModel, quirk found=${quirk != null}" } return if (quirk != null) { - Timber.d( - "DeviceHardwareRepository: applying quirk: requiresBootloaderUpgradeForOta=%b, infoUrl=%s", - quirk.requiresBootloaderUpgradeForOta, - quirk.infoUrl, - ) + Logger.d { + "DeviceHardwareRepository: applying quirk: requiresBootloaderUpgradeForOta=${quirk.requiresBootloaderUpgradeForOta}, infoUrl=${quirk.infoUrl}" + } base.copy( requiresBootloaderUpgradeForOta = quirk.requiresBootloaderUpgradeForOta, bootloaderInfoUrl = quirk.infoUrl, diff --git a/core/data/src/main/kotlin/org/meshtastic/core/data/repository/FirmwareReleaseRepository.kt b/core/data/src/main/kotlin/org/meshtastic/core/data/repository/FirmwareReleaseRepository.kt index ae0d1b389..c14ac47bf 100644 --- a/core/data/src/main/kotlin/org/meshtastic/core/data/repository/FirmwareReleaseRepository.kt +++ b/core/data/src/main/kotlin/org/meshtastic/core/data/repository/FirmwareReleaseRepository.kt @@ -17,6 +17,7 @@ package org.meshtastic.core.data.repository +import co.touchlab.kermit.Logger import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow import org.meshtastic.core.data.datasource.FirmwareReleaseJsonDataSource @@ -26,7 +27,6 @@ import org.meshtastic.core.database.entity.FirmwareReleaseEntity import org.meshtastic.core.database.entity.FirmwareReleaseType import org.meshtastic.core.database.entity.asExternalModel import org.meshtastic.core.network.FirmwareReleaseRemoteDataSource -import timber.log.Timber import java.util.concurrent.TimeUnit import javax.inject.Inject import javax.inject.Singleton @@ -68,7 +68,7 @@ constructor( // This gives the UI something to show immediately. val cachedRelease = localDataSource.getLatestRelease(releaseType) cachedRelease?.let { - Timber.d("Emitting cached firmware for $releaseType (isStale=${it.isStale()})") + Logger.d { "Emitting cached firmware for $releaseType (isStale=${it.isStale()})" } emit(it.asExternalModel()) } @@ -84,7 +84,7 @@ constructor( // The `distinctUntilChanged()` operator on the collector side will prevent // re-emitting the same data if the cache wasn't actually updated. val finalRelease = localDataSource.getLatestRelease(releaseType) - Timber.d("Emitting final firmware for $releaseType from cache.") + Logger.d { "Emitting final firmware for $releaseType from cache." } emit(finalRelease?.asExternalModel()) } @@ -98,7 +98,7 @@ constructor( private suspend fun updateCacheFromSources() { val remoteFetchSuccess = runCatching { - Timber.d("Fetching fresh firmware releases from remote API.") + Logger.d { "Fetching fresh firmware releases from remote API." } val networkReleases = remoteDataSource.getFirmwareReleases() // The API fetches all release types, so we cache them all at once. @@ -109,13 +109,13 @@ constructor( // If remote fetch failed, try the JSON fallback as a last resort. if (!remoteFetchSuccess) { - Timber.w("Remote fetch failed, attempting to cache from bundled JSON.") + Logger.w { "Remote fetch failed, attempting to cache from bundled JSON." } runCatching { val jsonReleases = jsonDataSource.loadFirmwareReleaseFromJsonAsset() localDataSource.insertFirmwareReleases(jsonReleases.releases.stable, FirmwareReleaseType.STABLE) localDataSource.insertFirmwareReleases(jsonReleases.releases.alpha, FirmwareReleaseType.ALPHA) } - .onFailure { Timber.w("Failed to cache from JSON: ${it.message}") } + .onFailure { Logger.w { "Failed to cache from JSON: ${it.message}" } } } } diff --git a/core/data/src/main/kotlin/org/meshtastic/core/data/repository/LocationRepository.kt b/core/data/src/main/kotlin/org/meshtastic/core/data/repository/LocationRepository.kt index 7849ea1df..35d087bf3 100644 --- a/core/data/src/main/kotlin/org/meshtastic/core/data/repository/LocationRepository.kt +++ b/core/data/src/main/kotlin/org/meshtastic/core/data/repository/LocationRepository.kt @@ -27,6 +27,7 @@ import androidx.core.location.LocationListenerCompat import androidx.core.location.LocationManagerCompat import androidx.core.location.LocationRequestCompat import androidx.core.location.altitude.AltitudeConverterCompat +import co.touchlab.kermit.Logger import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.asExecutor import kotlinx.coroutines.channels.awaitClose @@ -34,7 +35,6 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.callbackFlow import org.meshtastic.core.analytics.platform.PlatformAnalytics -import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton @@ -68,7 +68,7 @@ constructor( try { AltitudeConverterCompat.addMslAltitudeToLocation(context, location) } catch (e: Exception) { - Timber.e(e, "addMslAltitudeToLocation() failed") + Logger.e(e) { "addMslAltitudeToLocation() failed" } } } // info("New location: $location") @@ -85,9 +85,9 @@ constructor( } } - Timber.i( - "Starting location updates with $providerList intervalMs=${intervalMs}ms and minDistanceM=${minDistanceM}m", - ) + Logger.i { + "Starting location updates with $providerList intervalMs=${intervalMs}ms and minDistanceM=${minDistanceM}m" + } _receivingLocationUpdates.value = true analytics.track("location_start") // Figure out how many users needed to use the phone GPS @@ -106,7 +106,7 @@ constructor( } awaitClose { - Timber.i("Stopping location requests") + Logger.i { "Stopping location requests" } _receivingLocationUpdates.value = false analytics.track("location_stop") diff --git a/core/database/build.gradle.kts b/core/database/build.gradle.kts index 2b4dae22d..6de311544 100644 --- a/core/database/build.gradle.kts +++ b/core/database/build.gradle.kts @@ -55,7 +55,7 @@ dependencies { implementation(libs.androidx.room.paging) implementation(libs.kotlinx.serialization.json) - implementation(libs.timber) + implementation(libs.kermit) ksp(libs.androidx.room.compiler) diff --git a/core/database/src/main/kotlin/org/meshtastic/core/database/Converters.kt b/core/database/src/main/kotlin/org/meshtastic/core/database/Converters.kt index 4237db7d4..f9078a5ff 100644 --- a/core/database/src/main/kotlin/org/meshtastic/core/database/Converters.kt +++ b/core/database/src/main/kotlin/org/meshtastic/core/database/Converters.kt @@ -18,6 +18,7 @@ package org.meshtastic.core.database import androidx.room.TypeConverter +import co.touchlab.kermit.Logger import com.google.protobuf.ByteString import com.google.protobuf.InvalidProtocolBufferException import kotlinx.serialization.json.Json @@ -25,7 +26,6 @@ import org.meshtastic.core.model.DataPacket import org.meshtastic.proto.MeshProtos import org.meshtastic.proto.PaxcountProtos import org.meshtastic.proto.TelemetryProtos -import timber.log.Timber @Suppress("TooManyFunctions") class Converters { @@ -45,7 +45,7 @@ class Converters { fun bytesToFromRadio(bytes: ByteArray): MeshProtos.FromRadio = try { MeshProtos.FromRadio.parseFrom(bytes) } catch (ex: InvalidProtocolBufferException) { - Timber.e(ex, "bytesToFromRadio TypeConverter error") + Logger.e(ex) { "bytesToFromRadio TypeConverter error" } MeshProtos.FromRadio.getDefaultInstance() } @@ -55,7 +55,7 @@ class Converters { fun bytesToUser(bytes: ByteArray): MeshProtos.User = try { MeshProtos.User.parseFrom(bytes) } catch (ex: InvalidProtocolBufferException) { - Timber.e(ex, "bytesToUser TypeConverter error") + Logger.e(ex) { "bytesToUser TypeConverter error" } MeshProtos.User.getDefaultInstance() } @@ -65,7 +65,7 @@ class Converters { fun bytesToPosition(bytes: ByteArray): MeshProtos.Position = try { MeshProtos.Position.parseFrom(bytes) } catch (ex: InvalidProtocolBufferException) { - Timber.e(ex, "bytesToPosition TypeConverter error") + Logger.e(ex) { "bytesToPosition TypeConverter error" } MeshProtos.Position.getDefaultInstance() } @@ -75,7 +75,7 @@ class Converters { fun bytesToTelemetry(bytes: ByteArray): TelemetryProtos.Telemetry = try { TelemetryProtos.Telemetry.parseFrom(bytes) } catch (ex: InvalidProtocolBufferException) { - Timber.e(ex, "bytesToTelemetry TypeConverter error") + Logger.e(ex) { "bytesToTelemetry TypeConverter error" } TelemetryProtos.Telemetry.newBuilder().build() // Return an empty Telemetry object } @@ -85,7 +85,7 @@ class Converters { fun bytesToPaxcounter(bytes: ByteArray): PaxcountProtos.Paxcount = try { PaxcountProtos.Paxcount.parseFrom(bytes) } catch (ex: InvalidProtocolBufferException) { - Timber.e(ex, "bytesToPaxcounter TypeConverter error") + Logger.e(ex) { "bytesToPaxcounter TypeConverter error" } PaxcountProtos.Paxcount.getDefaultInstance() } @@ -95,7 +95,7 @@ class Converters { fun bytesToMetadata(bytes: ByteArray): MeshProtos.DeviceMetadata = try { MeshProtos.DeviceMetadata.parseFrom(bytes) } catch (ex: InvalidProtocolBufferException) { - Timber.e(ex, "bytesToMetadata TypeConverter error") + Logger.e(ex) { "bytesToMetadata TypeConverter error" } MeshProtos.DeviceMetadata.getDefaultInstance() } diff --git a/core/database/src/main/kotlin/org/meshtastic/core/database/DatabaseManager.kt b/core/database/src/main/kotlin/org/meshtastic/core/database/DatabaseManager.kt index 936e50e13..7f6250887 100644 --- a/core/database/src/main/kotlin/org/meshtastic/core/database/DatabaseManager.kt +++ b/core/database/src/main/kotlin/org/meshtastic/core/database/DatabaseManager.kt @@ -21,6 +21,7 @@ import android.app.Application import android.content.Context import android.content.SharedPreferences import androidx.room.Room +import co.touchlab.kermit.Logger import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob @@ -33,7 +34,6 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext -import timber.log.Timber import java.io.File import java.security.MessageDigest import javax.inject.Inject @@ -106,7 +106,7 @@ class DatabaseManager @Inject constructor(private val app: Application) { // One-time cleanup: remove legacy DB if present and not active managerScope.launch(Dispatchers.IO) { cleanupLegacyDbIfNeeded(activeDbName = dbName) } - Timber.i("Switched active DB to ${anonymizeDbName(dbName)} for address ${anonymizeAddress(address)}") + Logger.i { "Switched active DB to ${anonymizeDbName(dbName)} for address ${anonymizeAddress(address)}" } } /** Execute [block] with the current DB instance. */ @@ -138,26 +138,28 @@ class DatabaseManager @Inject constructor(private val app: Application) { // Only enforce the limit over device-specific DBs; exclude legacy and default DBs val deviceDbs = all.filterNot { it == DatabaseConstants.LEGACY_DB_NAME || it == DatabaseConstants.DEFAULT_DB_NAME } - Timber.d( - "LRU check: limit=%d, active=%s, deviceDbs=%s", - limit, - anonymizeDbName(activeDbName), - deviceDbs.joinToString(", ") { anonymizeDbName(it) }, - ) + Logger.d { + "LRU check: limit=$limit, active=${anonymizeDbName( + activeDbName, + )}, deviceDbs=${deviceDbs.joinToString(", ") { + anonymizeDbName(it) + }}" + } if (deviceDbs.size <= limit) return val usageSnapshot = deviceDbs.associateWith { lastUsed(it) } - Timber.d( - "LRU lastUsed(ms): %s", - usageSnapshot.entries.joinToString(", ") { (name, ts) -> "${anonymizeDbName(name)}=$ts" }, - ) + Logger.d { + "LRU lastUsed(ms): ${usageSnapshot.entries.joinToString(", ") { (name, ts) -> + "${anonymizeDbName(name)}=$ts" + }}" + } val victims = selectEvictionVictims(deviceDbs, activeDbName, limit, usageSnapshot) - Timber.i("LRU victims: %s", victims.joinToString(", ") { anonymizeDbName(it) }) + Logger.i { "LRU victims: ${victims.joinToString(", ") { anonymizeDbName(it) }}" } victims.forEach { name -> runCatching { dbCache.remove(name)?.close() } - .onFailure { Timber.w(it, "Failed to close database %s", name) } + .onFailure { Logger.w(it) { "Failed to close database $name" } } app.deleteDatabase(name) prefs.edit().remove(lastUsedKey(name)).apply() - Timber.i("Evicted cached DB ${anonymizeDbName(name)}") + Logger.i { "Evicted cached DB ${anonymizeDbName(name)}" } } } @@ -186,12 +188,12 @@ class DatabaseManager @Inject constructor(private val app: Application) { val legacyFile = getDbFile(app, legacy) if (legacyFile != null) { runCatching { dbCache.remove(legacy)?.close() } - .onFailure { Timber.w(it, "Failed to close legacy database %s before deletion", legacy) } + .onFailure { Logger.w(it) { "Failed to close legacy database $legacy before deletion" } } val deleted = app.deleteDatabase(legacy) if (deleted) { - Timber.i("Deleted legacy DB ${anonymizeDbName(legacy)}") + Logger.i { "Deleted legacy DB ${anonymizeDbName(legacy)}" } } else { - Timber.w("Attempted to delete legacy DB %s but deleteDatabase returned false", legacy) + Logger.w { "Attempted to delete legacy DB $legacy but deleteDatabase returned false" } } } prefs.edit().putBoolean(DatabaseConstants.LEGACY_DB_CLEANED_KEY, true).apply() diff --git a/core/datastore/build.gradle.kts b/core/datastore/build.gradle.kts index 03397aa88..ac23e9b57 100644 --- a/core/datastore/build.gradle.kts +++ b/core/datastore/build.gradle.kts @@ -48,5 +48,5 @@ dependencies { implementation(libs.androidx.datastore) implementation(libs.androidx.datastore.preferences) implementation(libs.kotlinx.serialization.json) - implementation(libs.timber) + implementation(libs.kermit) } diff --git a/core/datastore/src/main/kotlin/org/meshtastic/core/datastore/BootloaderWarningDataSource.kt b/core/datastore/src/main/kotlin/org/meshtastic/core/datastore/BootloaderWarningDataSource.kt index 7770ea05b..f90176671 100644 --- a/core/datastore/src/main/kotlin/org/meshtastic/core/datastore/BootloaderWarningDataSource.kt +++ b/core/datastore/src/main/kotlin/org/meshtastic/core/datastore/BootloaderWarningDataSource.kt @@ -21,11 +21,11 @@ import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.stringPreferencesKey +import co.touchlab.kermit.Logger import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map import kotlinx.serialization.SerializationException import kotlinx.serialization.json.Json -import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton @@ -43,9 +43,9 @@ class BootloaderWarningDataSource @Inject constructor(private val dataStore: Dat runCatching { Json.decodeFromString>(jsonString).toSet() } .onFailure { e -> if (e is IllegalArgumentException || e is SerializationException) { - Timber.w(e, "Failed to parse dismissed bootloader warning addresses, resetting preference") + Logger.w(e) { "Failed to parse dismissed bootloader warning addresses, resetting preference" } } else { - Timber.w(e, "Unexpected error while parsing dismissed bootloader warning addresses") + Logger.w(e) { "Unexpected error while parsing dismissed bootloader warning addresses" } } } .getOrDefault(emptySet()) diff --git a/core/datastore/src/main/kotlin/org/meshtastic/core/datastore/ChannelSetDataSource.kt b/core/datastore/src/main/kotlin/org/meshtastic/core/datastore/ChannelSetDataSource.kt index 5d726936e..fd64decdf 100644 --- a/core/datastore/src/main/kotlin/org/meshtastic/core/datastore/ChannelSetDataSource.kt +++ b/core/datastore/src/main/kotlin/org/meshtastic/core/datastore/ChannelSetDataSource.kt @@ -18,13 +18,13 @@ package org.meshtastic.core.datastore import androidx.datastore.core.DataStore +import co.touchlab.kermit.Logger import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.catch import org.meshtastic.proto.AppOnlyProtos.ChannelSet import org.meshtastic.proto.ChannelProtos.Channel import org.meshtastic.proto.ChannelProtos.ChannelSettings import org.meshtastic.proto.ConfigProtos -import timber.log.Timber import java.io.IOException import javax.inject.Inject import javax.inject.Singleton @@ -36,7 +36,7 @@ class ChannelSetDataSource @Inject constructor(private val channelSetStore: Data channelSetStore.data.catch { exception -> // dataStore.data throws an IOException when an error is encountered when reading data if (exception is IOException) { - Timber.e("Error reading DeviceConfig settings: ${exception.message}") + Logger.e { "Error reading DeviceConfig settings: ${exception.message}" } emit(ChannelSet.getDefaultInstance()) } else { throw exception diff --git a/core/datastore/src/main/kotlin/org/meshtastic/core/datastore/LocalConfigDataSource.kt b/core/datastore/src/main/kotlin/org/meshtastic/core/datastore/LocalConfigDataSource.kt index 95795f46f..06423c6b8 100644 --- a/core/datastore/src/main/kotlin/org/meshtastic/core/datastore/LocalConfigDataSource.kt +++ b/core/datastore/src/main/kotlin/org/meshtastic/core/datastore/LocalConfigDataSource.kt @@ -18,11 +18,11 @@ package org.meshtastic.core.datastore import androidx.datastore.core.DataStore +import co.touchlab.kermit.Logger import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.catch import org.meshtastic.proto.ConfigProtos.Config import org.meshtastic.proto.LocalOnlyProtos.LocalConfig -import timber.log.Timber import java.io.IOException import javax.inject.Inject import javax.inject.Singleton @@ -34,7 +34,7 @@ class LocalConfigDataSource @Inject constructor(private val localConfigStore: Da localConfigStore.data.catch { exception -> // dataStore.data throws an IOException when an error is encountered when reading data if (exception is IOException) { - Timber.e("Error reading LocalConfig settings: ${exception.message}") + Logger.e { "Error reading LocalConfig settings: ${exception.message}" } emit(LocalConfig.getDefaultInstance()) } else { throw exception @@ -53,7 +53,7 @@ class LocalConfigDataSource @Inject constructor(private val localConfigStore: Da if (localField != null) { builder.setField(localField, value) } else { - Timber.e("Error writing LocalConfig settings: ${config.payloadVariantCase}") + Logger.e { "Error writing LocalConfig settings: ${config.payloadVariantCase}" } } } builder.build() diff --git a/core/datastore/src/main/kotlin/org/meshtastic/core/datastore/ModuleConfigDataSource.kt b/core/datastore/src/main/kotlin/org/meshtastic/core/datastore/ModuleConfigDataSource.kt index 54dcf03e4..d96c3113e 100644 --- a/core/datastore/src/main/kotlin/org/meshtastic/core/datastore/ModuleConfigDataSource.kt +++ b/core/datastore/src/main/kotlin/org/meshtastic/core/datastore/ModuleConfigDataSource.kt @@ -18,11 +18,11 @@ package org.meshtastic.core.datastore import androidx.datastore.core.DataStore +import co.touchlab.kermit.Logger import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.catch import org.meshtastic.proto.LocalOnlyProtos.LocalModuleConfig import org.meshtastic.proto.ModuleConfigProtos.ModuleConfig -import timber.log.Timber import java.io.IOException import javax.inject.Inject import javax.inject.Singleton @@ -34,7 +34,7 @@ class ModuleConfigDataSource @Inject constructor(private val moduleConfigStore: moduleConfigStore.data.catch { exception -> // dataStore.data throws an IOException when an error is encountered when reading data if (exception is IOException) { - Timber.e("Error reading LocalModuleConfig settings: ${exception.message}") + Logger.e { "Error reading LocalModuleConfig settings: ${exception.message}" } emit(LocalModuleConfig.getDefaultInstance()) } else { throw exception @@ -53,7 +53,7 @@ class ModuleConfigDataSource @Inject constructor(private val moduleConfigStore: if (localField != null) { builder.setField(localField, value) } else { - Timber.e("Error writing LocalModuleConfig settings: ${config.payloadVariantCase}") + Logger.e { "Error writing LocalModuleConfig settings: ${config.payloadVariantCase}" } } } builder.build() diff --git a/core/datastore/src/main/kotlin/org/meshtastic/core/datastore/RecentAddressesDataSource.kt b/core/datastore/src/main/kotlin/org/meshtastic/core/datastore/RecentAddressesDataSource.kt index a8d6d0db1..63501dc91 100644 --- a/core/datastore/src/main/kotlin/org/meshtastic/core/datastore/RecentAddressesDataSource.kt +++ b/core/datastore/src/main/kotlin/org/meshtastic/core/datastore/RecentAddressesDataSource.kt @@ -21,6 +21,7 @@ import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.stringPreferencesKey +import co.touchlab.kermit.Logger import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map @@ -29,7 +30,6 @@ import kotlinx.serialization.json.Json import org.json.JSONArray import org.json.JSONObject import org.meshtastic.core.datastore.model.RecentAddress -import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton @@ -46,11 +46,11 @@ class RecentAddressesDataSource @Inject constructor(private val dataStore: DataS try { Json.decodeFromString>(jsonString) } catch (e: IllegalArgumentException) { - Timber.w("Could not parse recent addresses, falling back to legacy parsing: ${e.message}") + Logger.w { "Could not parse recent addresses, falling back to legacy parsing: ${e.message}" } // Fallback to legacy parsing parseLegacyRecentAddresses(jsonString) } catch (e: SerializationException) { - Timber.w("Could not parse recent addresses, falling back to legacy parsing: ${e.message}") + Logger.w { "Could not parse recent addresses, falling back to legacy parsing: ${e.message}" } // Fallback to legacy parsing parseLegacyRecentAddresses(jsonString) } @@ -73,7 +73,7 @@ class RecentAddressesDataSource @Inject constructor(private val dataStore: DataS } else -> { // Unknown format, log or handle as an error if necessary - Timber.w("Unknown item type in recent IP addresses: $item") + Logger.w { "Unknown item type in recent IP addresses: $item" } null } } diff --git a/core/model/build.gradle.kts b/core/model/build.gradle.kts index bb2de5bdb..2fbf17116 100644 --- a/core/model/build.gradle.kts +++ b/core/model/build.gradle.kts @@ -55,7 +55,7 @@ dependencies { implementation(libs.androidx.annotation) implementation(libs.kotlinx.serialization.json) - implementation(libs.timber) + implementation(libs.kermit) implementation(libs.zxing.android.embedded) { isTransitive = false } implementation(libs.zxing.core) diff --git a/core/model/detekt-baseline.xml b/core/model/detekt-baseline.xml index f459aeba8..49dc09531 100644 --- a/core/model/detekt-baseline.xml +++ b/core/model/detekt-baseline.xml @@ -2,30 +2,6 @@ - MagicNumber:Channel.kt$0xff - MagicNumber:ChannelOption.kt$.03125f - MagicNumber:ChannelOption.kt$.0625f - MagicNumber:ChannelOption.kt$.203125f - MagicNumber:ChannelOption.kt$.40625f - MagicNumber:ChannelOption.kt$.8125f - MagicNumber:ChannelOption.kt$1.6250f - MagicNumber:ChannelOption.kt$1000f - MagicNumber:ChannelOption.kt$1600 - MagicNumber:ChannelOption.kt$200 - MagicNumber:ChannelOption.kt$3.25f - MagicNumber:ChannelOption.kt$31 - MagicNumber:ChannelOption.kt$400 - MagicNumber:ChannelOption.kt$5 - MagicNumber:ChannelOption.kt$62 - MagicNumber:ChannelOption.kt$800 - MagicNumber:ChannelOption.kt$ChannelOption.LONG_FAST$.250f - MagicNumber:ChannelOption.kt$ChannelOption.LONG_MODERATE$.125f - MagicNumber:ChannelOption.kt$ChannelOption.LONG_SLOW$.125f - MagicNumber:ChannelOption.kt$ChannelOption.MEDIUM_FAST$.250f - MagicNumber:ChannelOption.kt$ChannelOption.MEDIUM_SLOW$.250f - MagicNumber:ChannelOption.kt$ChannelOption.SHORT_FAST$.250f - MagicNumber:ChannelOption.kt$ChannelOption.SHORT_SLOW$.250f - MagicNumber:ChannelOption.kt$ChannelOption.VERY_LONG_SLOW$.0625f MagicNumber:ChannelSet.kt$40 MagicNumber:ChannelSet.kt$960 SwallowedException:ChannelSet.kt$ex: Throwable diff --git a/core/model/src/main/kotlin/org/meshtastic/core/model/DeviceVersion.kt b/core/model/src/main/kotlin/org/meshtastic/core/model/DeviceVersion.kt index 5d07720a6..d9eda30cb 100644 --- a/core/model/src/main/kotlin/org/meshtastic/core/model/DeviceVersion.kt +++ b/core/model/src/main/kotlin/org/meshtastic/core/model/DeviceVersion.kt @@ -17,7 +17,7 @@ package org.meshtastic.core.model -import timber.log.Timber +import co.touchlab.kermit.Logger /** Provide structured access to parse and compare device version strings */ data class DeviceVersion(val asString: String) : Comparable { @@ -28,7 +28,7 @@ data class DeviceVersion(val asString: String) : Comparable { try { verStringToInt(asString) } catch (e: Exception) { - Timber.w("Exception while parsing version '$asString', assuming version 0") + Logger.w { "Exception while parsing version '$asString', assuming version 0" } 0 } diff --git a/core/model/src/main/kotlin/org/meshtastic/core/model/util/ChannelSet.kt b/core/model/src/main/kotlin/org/meshtastic/core/model/util/ChannelSet.kt index 757fefc3e..560bda0d8 100644 --- a/core/model/src/main/kotlin/org/meshtastic/core/model/util/ChannelSet.kt +++ b/core/model/src/main/kotlin/org/meshtastic/core/model/util/ChannelSet.kt @@ -20,14 +20,13 @@ package org.meshtastic.core.model.util import android.graphics.Bitmap import android.net.Uri import android.util.Base64 +import co.touchlab.kermit.Logger import com.google.zxing.BarcodeFormat import com.google.zxing.MultiFormatWriter import com.journeyapps.barcodescanner.BarcodeEncoder import org.meshtastic.core.model.Channel import org.meshtastic.proto.AppOnlyProtos.ChannelSet -import timber.log.Timber import java.net.MalformedURLException -import kotlin.jvm.Throws private const val MESHTASTIC_HOST = "meshtastic.org" private const val CHANNEL_PATH = "/e/" @@ -86,6 +85,6 @@ fun ChannelSet.qrCode(shouldAdd: Boolean): Bitmap? = try { val barcodeEncoder = BarcodeEncoder() barcodeEncoder.createBitmap(bitMatrix) } catch (ex: Throwable) { - Timber.e("URL was too complex to render as barcode") + Logger.e { "URL was too complex to render as barcode" } null } diff --git a/core/service/build.gradle.kts b/core/service/build.gradle.kts index 3e0453957..4d4235083 100644 --- a/core/service/build.gradle.kts +++ b/core/service/build.gradle.kts @@ -46,5 +46,5 @@ dependencies { implementation(projects.core.proto) implementation(libs.javax.inject) implementation(libs.kotlinx.coroutines.core) - implementation(libs.timber) + implementation(libs.kermit) } diff --git a/core/service/src/main/kotlin/org/meshtastic/core/service/ServiceRepository.kt b/core/service/src/main/kotlin/org/meshtastic/core/service/ServiceRepository.kt index f6c2eb274..a74df12e2 100644 --- a/core/service/src/main/kotlin/org/meshtastic/core/service/ServiceRepository.kt +++ b/core/service/src/main/kotlin/org/meshtastic/core/service/ServiceRepository.kt @@ -17,6 +17,7 @@ package org.meshtastic.core.service +import co.touchlab.kermit.Logger import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow @@ -25,7 +26,6 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.receiveAsFlow import org.meshtastic.proto.MeshProtos import org.meshtastic.proto.MeshProtos.MeshPacket -import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton @@ -66,7 +66,7 @@ class ServiceRepository @Inject constructor() { get() = _clientNotification fun setClientNotification(notification: MeshProtos.ClientNotification?) { - Timber.e(notification?.message.orEmpty()) + Logger.e { notification?.message.orEmpty() } _clientNotification.value = notification } @@ -80,7 +80,7 @@ class ServiceRepository @Inject constructor() { get() = _errorMessage fun setErrorMessage(text: String) { - Timber.e(text) + Logger.e { text } _errorMessage.value = text } diff --git a/core/ui/build.gradle.kts b/core/ui/build.gradle.kts index 772f8aba7..1960906c9 100644 --- a/core/ui/build.gradle.kts +++ b/core/ui/build.gradle.kts @@ -62,7 +62,7 @@ dependencies { implementation(libs.guava) implementation(libs.zxing.core) implementation(libs.zxing.android.embedded) - implementation(libs.timber) + implementation(libs.kermit) debugImplementation(libs.androidx.compose.ui.test.manifest) diff --git a/core/ui/detekt-baseline.xml b/core/ui/detekt-baseline.xml index aa0daf951..04634d26e 100644 --- a/core/ui/detekt-baseline.xml +++ b/core/ui/detekt-baseline.xml @@ -2,18 +2,6 @@ - ComposableParamOrder:AlertDialogs.kt$SimpleAlertDialog - ComposableParamOrder:EditBase64Preference.kt$EditBase64Preference - ComposableParamOrder:EditTextPreference.kt$EditTextPreference - ComposableParamOrder:MainAppBar.kt$MainAppBar - ComposableParamOrder:MaterialBatteryInfo.kt$MaterialBatteryInfo - ComposableParamOrder:NodeChip.kt$NodeChip - ComposableParamOrder:NodeKeyStatusIcon.kt$NodeKeyStatusIcon - ComposableParamOrder:SignalInfo.kt$SignalInfo - ComposableParamOrder:SwitchPreference.kt$SwitchPreference - ContentSlotReused:AdaptiveTwoPane.kt$second - LambdaParameterEventTrailing:ContactSharing.kt$onSharedContactRequested - LambdaParameterEventTrailing:MainAppBar.kt$onClickChip MagicNumber:EditIPv4Preference.kt$0xff MagicNumber:EditIPv4Preference.kt$16 MagicNumber:EditIPv4Preference.kt$24 @@ -22,47 +10,5 @@ MagicNumber:EditListPreference.kt$12345 MagicNumber:EditListPreference.kt$67890 MagicNumber:LazyColumnDragAndDropDemo.kt$50 - ModifierMissing:AdaptiveTwoPane.kt$AdaptiveTwoPane - ModifierMissing:ChannelItem.kt$ChannelItem - ModifierMissing:ChannelSelection.kt$ChannelSelection - ModifierMissing:ContactSharing.kt$SharedContactDialog - ModifierMissing:EmojiPicker.kt$EmojiPicker - ModifierMissing:EmojiPicker.kt$EmojiPickerDialog - ModifierMissing:IndoorAirQuality.kt$IndoorAirQuality - ModifierMissing:LoraSignalIndicator.kt$LoraSignalIndicator - ModifierMissing:LoraSignalIndicator.kt$Rssi - ModifierMissing:LoraSignalIndicator.kt$Snr - ModifierMissing:LoraSignalIndicator.kt$SnrAndRssi - ModifierMissing:PreferenceDivider.kt$PreferenceDivider - ModifierMissing:SecurityIcon.kt$SecurityIcon - ModifierMissing:SharedContactDialog.kt$SharedContactDialog - ModifierMissing:SimpleAlertDialog.kt$SimpleAlertDialog - ModifierMissing:SlidingSelector.kt$OptionLabel - ModifierNotUsedAtRoot:TextDividerPreference.kt$modifier = modifier.fillMaxWidth().padding(all = 16.dp) - ModifierNotUsedAtRoot:TextDividerPreference.kt$modifier = modifier.fillMaxWidth().wrapContentWidth(Alignment.End) - ModifierReused:PreferenceCategory.kt$Card(modifier = modifier.padding(bottom = 8.dp)) { Column( modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp, vertical = 16.dp), horizontalAlignment = Alignment.CenterHorizontally, ) { ProvideTextStyle(MaterialTheme.typography.bodyLarge) { content() } } } - ModifierReused:PreferenceCategory.kt$Text( text, modifier = modifier.padding(start = 16.dp, top = 24.dp, bottom = 8.dp, end = 16.dp), style = MaterialTheme.typography.titleLarge, ) - ModifierReused:TextDividerPreference.kt$Card(modifier = modifier.fillMaxWidth()) { Row(modifier = modifier.fillMaxWidth().padding(all = 16.dp), verticalAlignment = Alignment.CenterVertically) { Text( text = title, style = MaterialTheme.typography.bodyLarge, color = if (!enabled) { MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f) } else { Color.Unspecified }, ) if (trailingIcon != null) { Icon(trailingIcon, "trailingIcon", modifier = modifier.fillMaxWidth().wrapContentWidth(Alignment.End)) } } } - ModifierReused:TextDividerPreference.kt$Icon(trailingIcon, "trailingIcon", modifier = modifier.fillMaxWidth().wrapContentWidth(Alignment.End)) - ModifierReused:TextDividerPreference.kt$Row(modifier = modifier.fillMaxWidth().padding(all = 16.dp), verticalAlignment = Alignment.CenterVertically) { Text( text = title, style = MaterialTheme.typography.bodyLarge, color = if (!enabled) { MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f) } else { Color.Unspecified }, ) if (trailingIcon != null) { Icon(trailingIcon, "trailingIcon", modifier = modifier.fillMaxWidth().wrapContentWidth(Alignment.End)) } } - MultipleEmitters:PreferenceCategory.kt$PreferenceCategory - ParameterNaming:BitwisePreference.kt$onItemSelected - ParameterNaming:ChannelSelection.kt$onSelected - ParameterNaming:ContactSharing.kt$onSharedContactRequested - ParameterNaming:DropDownPreference.kt$onItemSelected - ParameterNaming:EditIPv4Preference.kt$onValueChanged - ParameterNaming:EditListPreference.kt$onValuesChanged - ParameterNaming:EditPasswordPreference.kt$onValueChanged - ParameterNaming:EditTextPreference.kt$onValueChanged - ParameterNaming:PositionPrecisionPreference.kt$onValueChanged - ParameterNaming:PreferenceFooter.kt$onNegativeClicked - ParameterNaming:PreferenceFooter.kt$onPositiveClicked - ParameterNaming:SlidingSelector.kt$onOptionSelected - PreviewPublic:IndoorAirQuality.kt$IAQScalePreview - PreviewPublic:LazyColumnDragAndDropDemo.kt$LazyColumnDragAndDropDemo - PreviewPublic:MaterialBatteryInfo.kt$MaterialBatteryInfoPreview - PreviewPublic:SignalInfo.kt$SignalInfoPreview - PreviewPublic:SignalInfo.kt$SignalInfoSelfPreview - PreviewPublic:SignalInfo.kt$SignalInfoSimplePreview diff --git a/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/ContactSharing.kt b/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/ContactSharing.kt index 4d8f00cb8..e8b8cd202 100644 --- a/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/ContactSharing.kt +++ b/core/ui/src/main/kotlin/org/meshtastic/core/ui/component/ContactSharing.kt @@ -43,6 +43,7 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.core.net.toUri +import co.touchlab.kermit.Logger import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.isGranted import com.google.accompanist.permissions.rememberPermissionState @@ -65,7 +66,6 @@ import org.meshtastic.core.ui.R import org.meshtastic.core.ui.share.SharedContactDialog import org.meshtastic.proto.AdminProtos import org.meshtastic.proto.MeshProtos -import timber.log.Timber import java.net.MalformedURLException /** @@ -90,7 +90,7 @@ fun AddContactFAB( try { uri.toSharedContact() } catch (ex: MalformedURLException) { - Timber.e("URL was malformed: ${ex.message}") + Logger.e { "URL was malformed: ${ex.message}" } null } if (sharedContact != null) { @@ -102,7 +102,7 @@ fun AddContactFAB( sharedContact?.let { SharedContactDialog(sharedContact = it, onDismiss = { onSharedContactRequested(null) }) } fun zxingScan() { - Timber.d("Starting zxing QR code scanner") + Logger.d { "Starting zxing QR code scanner" } val zxingScan = ScanOptions() zxingScan.setCameraId(CAMERA_ID) zxingScan.setPrompt("") @@ -115,9 +115,9 @@ fun AddContactFAB( LaunchedEffect(cameraPermissionState.status) { if (cameraPermissionState.status.isGranted) { - Timber.d("Camera permission granted") + Logger.d { "Camera permission granted" } } else { - Timber.d("Camera permission denied") + Logger.d { "Camera permission denied" } } } @@ -193,7 +193,7 @@ val Uri.qrCode: Bitmap? val barcodeEncoder = BarcodeEncoder() barcodeEncoder.createBitmap(bitMatrix) } catch (ex: WriterException) { - Timber.e("URL was too complex to render as barcode: ${ex.message}") + Logger.e { "URL was too complex to render as barcode: ${ex.message}" } null } diff --git a/core/ui/src/main/kotlin/org/meshtastic/core/ui/qr/ScannedQrCodeViewModel.kt b/core/ui/src/main/kotlin/org/meshtastic/core/ui/qr/ScannedQrCodeViewModel.kt index 6563700ce..bb4b90322 100644 --- a/core/ui/src/main/kotlin/org/meshtastic/core/ui/qr/ScannedQrCodeViewModel.kt +++ b/core/ui/src/main/kotlin/org/meshtastic/core/ui/qr/ScannedQrCodeViewModel.kt @@ -20,6 +20,7 @@ package org.meshtastic.core.ui.qr 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.launch import org.meshtastic.core.data.repository.RadioConfigRepository @@ -32,7 +33,6 @@ import org.meshtastic.proto.ConfigProtos.Config import org.meshtastic.proto.LocalOnlyProtos.LocalConfig import org.meshtastic.proto.channelSet import org.meshtastic.proto.config -import timber.log.Timber import javax.inject.Inject @HiltViewModel @@ -61,7 +61,7 @@ constructor( try { serviceRepository.meshService?.setChannel(channel.toByteArray()) } catch (ex: RemoteException) { - Timber.e(ex, "Set channel error") + Logger.e(ex) { "Set channel error" } } } @@ -70,7 +70,7 @@ constructor( try { serviceRepository.meshService?.setConfig(config.toByteArray()) } catch (ex: RemoteException) { - Timber.e(ex, "Set config error") + Logger.e(ex) { "Set config error" } } } } diff --git a/feature/firmware/build.gradle.kts b/feature/firmware/build.gradle.kts index 7a3ddcc96..e0ba936e6 100644 --- a/feature/firmware/build.gradle.kts +++ b/feature/firmware/build.gradle.kts @@ -64,7 +64,7 @@ dependencies { implementation(libs.androidx.hilt.lifecycle.viewmodel.compose) implementation(libs.androidx.navigation.compose) implementation(libs.kotlinx.collections.immutable) - implementation(libs.timber) + implementation(libs.kermit) implementation(libs.nordic) implementation(libs.nordic.dfu) diff --git a/feature/firmware/src/main/kotlin/org/meshtastic/feature/firmware/FirmwareFileHandler.kt b/feature/firmware/src/main/kotlin/org/meshtastic/feature/firmware/FirmwareFileHandler.kt index a4bf93c9e..b5b346949 100644 --- a/feature/firmware/src/main/kotlin/org/meshtastic/feature/firmware/FirmwareFileHandler.kt +++ b/feature/firmware/src/main/kotlin/org/meshtastic/feature/firmware/FirmwareFileHandler.kt @@ -19,6 +19,7 @@ package org.meshtastic.feature.firmware import android.content.Context import android.net.Uri +import co.touchlab.kermit.Logger import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Dispatchers @@ -27,7 +28,6 @@ import kotlinx.coroutines.withContext import okhttp3.OkHttpClient import okhttp3.Request import org.meshtastic.core.model.DeviceHardware -import timber.log.Timber import java.io.File import java.io.FileInputStream import java.io.FileOutputStream @@ -57,7 +57,7 @@ constructor( } tempDir.mkdirs() } - .onFailure { e -> Timber.w(e, "Failed to cleanup temp directory") } + .onFailure { e -> Logger.w(e) { "Failed to cleanup temp directory" } } } suspend fun checkUrlExists(url: String): Boolean = withContext(Dispatchers.IO) { @@ -65,7 +65,7 @@ constructor( try { client.newCall(request).execute().use { response -> response.isSuccessful } } catch (e: IOException) { - Timber.w(e, "Failed to check URL existence: $url") + Logger.w(e) { "Failed to check URL existence: $url" } false } } @@ -77,12 +77,12 @@ constructor( try { client.newCall(request).execute() } catch (e: IOException) { - Timber.w(e, "Download failed for $url") + Logger.w(e) { "Download failed for $url" } return@withContext null } if (!response.isSuccessful) { - Timber.w("Download failed: ${response.code} for $url") + Logger.w { "Download failed: ${response.code} for $url" } return@withContext null } @@ -167,7 +167,7 @@ constructor( } } } catch (e: IOException) { - Timber.w(e, "Failed to extract firmware from URI") + Logger.w(e) { "Failed to extract firmware from URI" } return@withContext null } matchingEntries.minByOrNull { it.first.name.length }?.second diff --git a/feature/firmware/src/main/kotlin/org/meshtastic/feature/firmware/FirmwareUpdateViewModel.kt b/feature/firmware/src/main/kotlin/org/meshtastic/feature/firmware/FirmwareUpdateViewModel.kt index 68a89af2e..a9f938b90 100644 --- a/feature/firmware/src/main/kotlin/org/meshtastic/feature/firmware/FirmwareUpdateViewModel.kt +++ b/feature/firmware/src/main/kotlin/org/meshtastic/feature/firmware/FirmwareUpdateViewModel.kt @@ -26,6 +26,7 @@ import android.net.Uri import android.os.Build import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import co.touchlab.kermit.Logger import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.CancellationException @@ -72,7 +73,6 @@ import org.meshtastic.core.strings.firmware_update_starting_service import org.meshtastic.core.strings.firmware_update_unknown_hardware import org.meshtastic.core.strings.firmware_update_updating import org.meshtastic.core.strings.unknown -import timber.log.Timber import java.io.File import javax.inject.Inject @@ -169,7 +169,7 @@ constructor( } .onFailure { e -> if (e is CancellationException) throw e - Timber.e(e) + Logger.e(e) { "Error checking for updates" } _state.value = FirmwareUpdateState.Error(e.message ?: "Unknown error") } } @@ -224,13 +224,13 @@ constructor( _state.value = FirmwareUpdateState.Processing(getString(Res.string.firmware_update_flashing)) withTimeoutOrNull(DEVICE_DETACH_TIMEOUT) { waitForDeviceDetach(context).first() } - ?: Timber.w("Timed out waiting for device to detach, assuming success") + ?: Logger.w { "Timed out waiting for device to detach, assuming success" } _state.value = FirmwareUpdateState.Success } catch (e: CancellationException) { throw e } catch (e: Exception) { - Timber.e(e) + Logger.e(e) { "Error saving DFU file" } _state.value = FirmwareUpdateState.Error(e.message ?: getString(Res.string.firmware_update_failed)) } finally { cleanupTemporaryFiles(fileHandler, tempFirmwareFile) @@ -279,7 +279,7 @@ constructor( } catch (e: CancellationException) { throw e } catch (e: Exception) { - Timber.e(e) + Logger.e(e) { "Error starting update from file" } _state.value = FirmwareUpdateState.Error(e.message ?: "Local update failed") } } @@ -345,7 +345,7 @@ private fun cleanupTemporaryFiles(fileHandler: FirmwareFileHandler, tempFirmware tempFirmwareFile?.takeIf { it.exists() }?.delete() fileHandler.cleanupAllTemporaryFiles() } - .onFailure { e -> Timber.w(e, "Failed to cleanup temp files") } + .onFailure { e -> Logger.w(e) { "Failed to cleanup temp files" } } return null } diff --git a/feature/firmware/src/main/kotlin/org/meshtastic/feature/firmware/UpdateHandler.kt b/feature/firmware/src/main/kotlin/org/meshtastic/feature/firmware/UpdateHandler.kt index ce21ea6d9..8f23d2270 100644 --- a/feature/firmware/src/main/kotlin/org/meshtastic/feature/firmware/UpdateHandler.kt +++ b/feature/firmware/src/main/kotlin/org/meshtastic/feature/firmware/UpdateHandler.kt @@ -19,6 +19,7 @@ package org.meshtastic.feature.firmware import android.content.Context import android.net.Uri +import co.touchlab.kermit.Logger import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.CancellationException import kotlinx.coroutines.delay @@ -26,7 +27,6 @@ import no.nordicsemi.android.dfu.DfuServiceInitiator import org.meshtastic.core.database.entity.FirmwareRelease import org.meshtastic.core.model.DeviceHardware import org.meshtastic.core.service.ServiceRepository -import timber.log.Timber import java.io.File import javax.inject.Inject @@ -81,7 +81,7 @@ class FirmwareRetriever @Inject constructor(private val fileHandler: FirmwareFil return it } } catch (e: Exception) { - Timber.w(e, "Direct download for $filename failed, falling back to release zip") + Logger.w(e) { "Direct download for $filename failed, falling back to release zip" } } } @@ -141,7 +141,7 @@ constructor( } catch (e: CancellationException) { throw e } catch (e: Exception) { - Timber.e(e) + Logger.e(e) { "OTA Update failed" } updateState(FirmwareUpdateState.Error(e.message ?: "OTA Update failed")) null } @@ -214,7 +214,7 @@ constructor( } catch (e: CancellationException) { throw e } catch (e: Exception) { - Timber.e(e) + Logger.e(e) { "USB Update failed" } updateState(FirmwareUpdateState.Error(e.message ?: "USB Update failed")) null } diff --git a/feature/intro/detekt-baseline.xml b/feature/intro/detekt-baseline.xml index 5c5d30217..ecf2e0cce 100644 --- a/feature/intro/detekt-baseline.xml +++ b/feature/intro/detekt-baseline.xml @@ -1,8 +1,5 @@ - - ComposableParamOrder:PermissionScreenLayout.kt$PermissionScreenLayout - ParameterNaming:WelcomeScreen.kt$onGetStarted - + diff --git a/feature/map/build.gradle.kts b/feature/map/build.gradle.kts index 4c175dab4..b69e19e25 100644 --- a/feature/map/build.gradle.kts +++ b/feature/map/build.gradle.kts @@ -67,7 +67,7 @@ dependencies { implementation(libs.androidx.lifecycle.viewmodel.compose) implementation(libs.androidx.navigation.common) implementation(libs.material) - implementation(libs.timber) + implementation(libs.kermit) fdroidImplementation(libs.osmbonuspack) fdroidImplementation(libs.osmdroid.android) diff --git a/feature/map/src/fdroid/kotlin/org/meshtastic/feature/map/MapView.kt b/feature/map/src/fdroid/kotlin/org/meshtastic/feature/map/MapView.kt index e4781766b..606004375 100644 --- a/feature/map/src/fdroid/kotlin/org/meshtastic/feature/map/MapView.kt +++ b/feature/map/src/fdroid/kotlin/org/meshtastic/feature/map/MapView.kt @@ -76,6 +76,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle +import co.touchlab.kermit.Logger import com.google.accompanist.permissions.ExperimentalPermissionsApi // Added for Accompanist import com.google.accompanist.permissions.rememberMultiplePermissionsState // Added for Accompanist import com.google.android.material.dialog.MaterialAlertDialogBuilder @@ -156,7 +157,6 @@ import org.osmdroid.views.overlay.Polygon import org.osmdroid.views.overlay.Polyline import org.osmdroid.views.overlay.infowindow.InfoWindow import org.osmdroid.views.overlay.mylocation.MyLocationNewOverlay -import timber.log.Timber import java.io.File import java.text.DateFormat import kotlin.math.abs @@ -170,7 +170,7 @@ private fun MapView.updateMarkers( waypointMarkers: List, nodeClusterer: RadiusMarkerClusterer, ) { - Timber.d("Showing on map: ${nodeMarkers.size} nodes ${waypointMarkers.size} waypoints") + Logger.d { "Showing on map: ${nodeMarkers.size} nodes ${waypointMarkers.size} waypoints" } overlays.removeAll { it is MarkerWithLabel } // overlays.addAll(nodeMarkers + waypointMarkers) overlays.addAll(waypointMarkers) @@ -271,7 +271,7 @@ fun MapView( fun loadOnlineTileSourceBase(): ITileSource { val id = mapViewModel.mapStyleId - Timber.d("mapStyleId from prefs: $id") + Logger.d { "mapStyleId from prefs: $id" } return CustomTileSource.getTileSource(id).also { zoomLevelMax = it.maximumZoomLevel.toDouble() showDownloadButton = if (it is OnlineTileSourceBase) it.tileSourcePolicy.acceptsBulkDownload() else false @@ -295,11 +295,11 @@ fun MapView( fun MapView.toggleMyLocation() { if (context.gpsDisabled()) { - Timber.d("Telling user we need location turned on for MyLocationNewOverlay") + Logger.d { "Telling user we need location turned on for MyLocationNewOverlay" } scope.launch { context.showToast(Res.string.location_disabled) } return } - Timber.d("user clicked MyLocationNewOverlay ${myLocationOverlay == null}") + Logger.d { "user clicked MyLocationNewOverlay ${myLocationOverlay == null}" } if (myLocationOverlay == null) { myLocationOverlay = MyLocationNewOverlay(this).apply { @@ -454,15 +454,15 @@ fun MapView( val builder = MaterialAlertDialogBuilder(context) builder.setTitle(com.meshtastic.core.strings.getString(Res.string.waypoint_delete)) builder.setNeutralButton(com.meshtastic.core.strings.getString(Res.string.cancel)) { _, _ -> - Timber.d("User canceled marker delete dialog") + Logger.d { "User canceled marker delete dialog" } } builder.setNegativeButton(com.meshtastic.core.strings.getString(Res.string.delete_for_me)) { _, _ -> - Timber.d("User deleted waypoint ${waypoint.id} for me") + Logger.d { "User deleted waypoint ${waypoint.id} for me" } mapViewModel.deleteWaypoint(waypoint.id) } if (waypoint.lockedTo in setOf(0, mapViewModel.myNodeNum ?: 0) && isConnected) { builder.setPositiveButton(com.meshtastic.core.strings.getString(Res.string.delete_for_everyone)) { _, _ -> - Timber.d("User deleted waypoint ${waypoint.id} for everyone") + Logger.d { "User deleted waypoint ${waypoint.id} for everyone" } mapViewModel.sendWaypoint(waypoint.copy { expire = 1 }) mapViewModel.deleteWaypoint(waypoint.id) } @@ -485,7 +485,7 @@ fun MapView( fun showMarkerLongPressDialog(id: Int) { performHapticFeedback() - Timber.d("marker long pressed id=$id") + Logger.d { "marker long pressed id=$id" } val waypoint = waypoints[id]?.data?.waypoint ?: return // edit only when unlocked or lockedTo myNodeNum if (waypoint.lockedTo in setOf(0, mapViewModel.myNodeNum ?: 0) && isConnected) { @@ -691,9 +691,9 @@ fun MapView( ), ) } catch (ex: TileSourcePolicyException) { - Timber.d("Tile source does not allow archiving: ${ex.message}") + Logger.d { "Tile source does not allow archiving: ${ex.message}" } } catch (ex: Exception) { - Timber.d("Tile source exception: ${ex.message}") + Logger.d { "Tile source exception: ${ex.message}" } } } @@ -897,7 +897,7 @@ fun MapView( EditWaypointDialog( waypoint = showEditWaypointDialog ?: return, // Safe call onSendClicked = { waypoint -> - Timber.d("User clicked send waypoint ${waypoint.id}") + Logger.d { "User clicked send waypoint ${waypoint.id}" } showEditWaypointDialog = null mapViewModel.sendWaypoint( waypoint.copy { @@ -910,12 +910,12 @@ fun MapView( ) }, onDeleteClicked = { waypoint -> - Timber.d("User clicked delete waypoint ${waypoint.id}") + Logger.d { "User clicked delete waypoint ${waypoint.id}" } showEditWaypointDialog = null showDeleteMarkerDialog(waypoint) }, onDismissRequest = { - Timber.d("User clicked cancel marker edit dialog") + Logger.d { "User clicked cancel marker edit dialog" } showEditWaypointDialog = null }, ) diff --git a/feature/map/src/fdroid/kotlin/org/meshtastic/feature/map/MapViewWithLifecycle.kt b/feature/map/src/fdroid/kotlin/org/meshtastic/feature/map/MapViewWithLifecycle.kt index 090ae1b98..a32e49a0a 100644 --- a/feature/map/src/fdroid/kotlin/org/meshtastic/feature/map/MapViewWithLifecycle.kt +++ b/feature/map/src/fdroid/kotlin/org/meshtastic/feature/map/MapViewWithLifecycle.kt @@ -33,6 +33,7 @@ import androidx.compose.ui.platform.LocalContext import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.compose.LocalLifecycleOwner +import co.touchlab.kermit.Logger import org.osmdroid.config.Configuration import org.osmdroid.tileprovider.tilesource.ITileSource import org.osmdroid.tileprovider.tilesource.TileSourceFactory @@ -40,7 +41,6 @@ import org.osmdroid.util.BoundingBox import org.osmdroid.util.GeoPoint import org.osmdroid.views.CustomZoomButtonsController import org.osmdroid.views.MapView -import timber.log.Timber @SuppressLint("WakelockTimeout") private fun PowerManager.WakeLock.safeAcquire() { @@ -48,9 +48,9 @@ private fun PowerManager.WakeLock.safeAcquire() { try { acquire() } catch (e: SecurityException) { - Timber.e("WakeLock permission exception: ${e.message}") + Logger.e { "WakeLock permission exception: ${e.message}" } } catch (e: IllegalStateException) { - Timber.e("WakeLock acquire() exception: ${e.message}") + Logger.e { "WakeLock acquire() exception: ${e.message}" } } } } @@ -60,7 +60,7 @@ private fun PowerManager.WakeLock.safeRelease() { try { release() } catch (e: IllegalStateException) { - Timber.e("WakeLock release() exception: ${e.message}") + Logger.e { "WakeLock release() exception: ${e.message}" } } } } diff --git a/feature/map/src/google/kotlin/org/meshtastic/feature/map/LocationHandler.kt b/feature/map/src/google/kotlin/org/meshtastic/feature/map/LocationHandler.kt index c7b5ffa15..ac4d632ed 100644 --- a/feature/map/src/google/kotlin/org/meshtastic/feature/map/LocationHandler.kt +++ b/feature/map/src/google/kotlin/org/meshtastic/feature/map/LocationHandler.kt @@ -32,12 +32,12 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.platform.LocalContext import androidx.core.content.ContextCompat +import co.touchlab.kermit.Logger import com.google.android.gms.common.api.ResolvableApiException import com.google.android.gms.location.LocationRequest import com.google.android.gms.location.LocationServices import com.google.android.gms.location.LocationSettingsRequest import com.google.android.gms.location.Priority -import timber.log.Timber private const val INTERVAL_MILLIS = 10000L @@ -66,11 +66,11 @@ fun LocationPermissionsHandler(onPermissionResult: (Boolean) -> Unit) { val locationSettingsLauncher = rememberLauncherForActivityResult(contract = ActivityResultContracts.StartIntentSenderForResult()) { result -> if (result.resultCode == Activity.RESULT_OK) { - Timber.d("Location settings changed by user.") + Logger.d { "Location settings changed by user." } // User has enabled location services or improved accuracy. onPermissionResult(true) // Settings are now adequate, and permission was already granted. } else { - Timber.d("Location settings change cancelled by user.") + Logger.d { "Location settings change cancelled by user." } // User chose not to change settings. The permission itself is still granted, // but the experience might be degraded. For the purpose of enabling map features, // we consider this as success if the core permission is there. @@ -111,7 +111,7 @@ fun LocationPermissionsHandler(onPermissionResult: (Boolean) -> Unit) { val task = client.checkLocationSettings(builder.build()) task.addOnSuccessListener { - Timber.d("Location settings are satisfied.") + Logger.d { "Location settings are satisfied." } onPermissionResult(true) // Permission granted and settings are good } @@ -122,11 +122,11 @@ fun LocationPermissionsHandler(onPermissionResult: (Boolean) -> Unit) { locationSettingsLauncher.launch(intentSenderRequest) // Result of this launch will be handled by locationSettingsLauncher's callback } catch (sendEx: ActivityNotFoundException) { - Timber.d("Error launching location settings resolution ${sendEx.message}.") + Logger.d { "Error launching location settings resolution ${sendEx.message}." } onPermissionResult(true) // Permission is granted, but settings dialog failed. Proceed. } } else { - Timber.d("Location settings are not satisfiable.${exception.message}") + Logger.d { "Location settings are not satisfiable.${exception.message}" } onPermissionResult(true) // Permission is granted, but settings not ideal. Proceed. } } diff --git a/feature/map/src/google/kotlin/org/meshtastic/feature/map/MapView.kt b/feature/map/src/google/kotlin/org/meshtastic/feature/map/MapView.kt index 1350b9aad..d19568b17 100644 --- a/feature/map/src/google/kotlin/org/meshtastic/feature/map/MapView.kt +++ b/feature/map/src/google/kotlin/org/meshtastic/feature/map/MapView.kt @@ -62,6 +62,7 @@ import androidx.compose.ui.unit.dp import androidx.core.graphics.createBitmap import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle +import co.touchlab.kermit.Logger import com.google.android.gms.location.LocationCallback import com.google.android.gms.location.LocationRequest import com.google.android.gms.location.LocationResult @@ -124,7 +125,6 @@ import org.meshtastic.proto.MeshProtos.Position import org.meshtastic.proto.MeshProtos.Waypoint import org.meshtastic.proto.copy import org.meshtastic.proto.waypoint -import timber.log.Timber import java.text.DateFormat import kotlin.math.abs import kotlin.math.max @@ -219,7 +219,7 @@ fun MapView( try { cameraPositionState.animate(cameraUpdate) } catch (e: IllegalStateException) { - Timber.d("Error animating camera to location: ${e.message}") + Logger.d { "Error animating camera to location: ${e.message}" } } } } @@ -239,14 +239,14 @@ fun MapView( try { @Suppress("MissingPermission") fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, null) - Timber.d("Started location tracking") + Logger.d { "Started location tracking" } } catch (e: SecurityException) { - Timber.d("Location permission not available: ${e.message}") + Logger.d { "Location permission not available: ${e.message}" } isLocationTrackingEnabled = false } } else { fusedLocationClient.removeLocationUpdates(locationCallback) - Timber.d("Stopped location tracking") + Logger.d { "Stopped location tracking" } } } @@ -412,7 +412,7 @@ fun MapView( cameraPositionState.animate(cameraUpdate) hasCenteredTraceroute = true } catch (e: IllegalStateException) { - Timber.d("Error centering traceroute overlay: ${e.message}") + Logger.d { "Error centering traceroute overlay: ${e.message}" } } } } @@ -548,7 +548,7 @@ fun MapView( CameraUpdateFactory.newLatLngBounds(bounds.build(), 100), ) } - Timber.d("Cluster clicked! $cluster") + Logger.d { "Cluster clicked! $cluster" } } true }, @@ -679,9 +679,9 @@ fun MapView( val currentPosition = cameraPositionState.position val newCameraPosition = CameraPosition.Builder(currentPosition).bearing(0f).build() cameraPositionState.animate(CameraUpdateFactory.newCameraPosition(newCameraPosition)) - Timber.d("Oriented map to north") + Logger.d { "Oriented map to north" } } catch (e: IllegalStateException) { - Timber.d("Error orienting map to north: ${e.message}") + Logger.d { "Error orienting map to north: ${e.message}" } } } } @@ -715,7 +715,7 @@ fun MapView( internal fun convertIntToEmoji(unicodeCodePoint: Int): String = try { String(Character.toChars(unicodeCodePoint)) } catch (e: IllegalArgumentException) { - Timber.w(e, "Invalid unicode code point: $unicodeCodePoint") + Logger.w(e) { "Invalid unicode code point: $unicodeCodePoint" } "\uD83D\uDCCD" } diff --git a/feature/map/src/google/kotlin/org/meshtastic/feature/map/MapViewModel.kt b/feature/map/src/google/kotlin/org/meshtastic/feature/map/MapViewModel.kt index f08a2c0d9..6aaaa4b55 100644 --- a/feature/map/src/google/kotlin/org/meshtastic/feature/map/MapViewModel.kt +++ b/feature/map/src/google/kotlin/org/meshtastic/feature/map/MapViewModel.kt @@ -21,6 +21,7 @@ import android.app.Application import android.net.Uri import androidx.core.net.toFile import androidx.lifecycle.viewModelScope +import co.touchlab.kermit.Logger import com.google.android.gms.maps.GoogleMap import com.google.android.gms.maps.model.CameraPosition import com.google.android.gms.maps.model.LatLng @@ -56,7 +57,6 @@ import org.meshtastic.core.prefs.map.MapPrefs import org.meshtastic.core.service.ServiceRepository import org.meshtastic.core.ui.viewmodel.stateInWhileSubscribed import org.meshtastic.proto.ConfigProtos -import timber.log.Timber import java.io.File import java.io.FileOutputStream import java.io.IOException @@ -200,7 +200,7 @@ constructor( fun selectCustomTileProvider(config: CustomTileProviderConfig?) { if (config != null) { if (!isValidTileUrlTemplate(config.urlTemplate)) { - Timber.tag("MapViewModel").w("Attempted to select invalid URL template: ${config.urlTemplate}") + Logger.withTag("MapViewModel").w("Attempted to select invalid URL template: ${config.urlTemplate}") _selectedCustomTileProviderUrl.value = null googleMapsPrefs.selectedCustomTileUrl = null return @@ -224,7 +224,8 @@ constructor( fun createUrlTileProvider(urlString: String): TileProvider? { if (!isValidTileUrlTemplate(urlString)) { - Timber.tag("MapViewModel").e("Tile URL does not contain valid {x}, {y}, and {z} placeholders: $urlString") + Logger.withTag("MapViewModel") + .e("Tile URL does not contain valid {x}, {y}, and {z} placeholders: $urlString") return null } return object : UrlTileProvider(TILE_SIZE, TILE_SIZE) { @@ -237,7 +238,7 @@ constructor( return try { URL(formattedUrl) } catch (e: MalformedURLException) { - Timber.tag("MapViewModel").e(e, "Malformed URL: $formattedUrl") + Logger.withTag("MapViewModel").e(e) { "Malformed URL: $formattedUrl" } null } } @@ -290,7 +291,7 @@ constructor( try { _selectedGoogleMapType.value = MapType.valueOf(savedGoogleMapTypeName ?: MapType.NORMAL.name) } catch (e: IllegalArgumentException) { - Timber.e(e, "Invalid saved Google Map type: $savedGoogleMapTypeName") + Logger.e(e) { "Invalid saved Google Map type: $savedGoogleMapTypeName" } _selectedGoogleMapType.value = MapType.NORMAL // Fallback in case of invalid stored name googleMapsPrefs.selectedGoogleMapType = null } @@ -335,14 +336,14 @@ constructor( } _mapLayers.value = loadedItems if (loadedItems.isNotEmpty()) { - Timber.tag("MapViewModel").i("Loaded ${loadedItems.size} persisted map layers.") + Logger.withTag("MapViewModel").i("Loaded ${loadedItems.size} persisted map layers.") } } } else { - Timber.tag("MapViewModel").i("Map layers directory does not exist. No layers loaded.") + Logger.withTag("MapViewModel").i("Map layers directory does not exist. No layers loaded.") } } catch (e: Exception) { - Timber.tag("MapViewModel").e(e, "Error loading persisted map layers") + Logger.withTag("MapViewModel").e(e) { "Error loading persisted map layers" } _mapLayers.value = emptyList() } } @@ -367,7 +368,7 @@ constructor( } if (layerType == null) { - Timber.tag("MapViewModel").e("Unsupported map layer file type: $extension") + Logger.withTag("MapViewModel").e("Unsupported map layer file type: $extension") return@launch } @@ -384,7 +385,7 @@ constructor( val newItem = MapLayerItem(name = layerName, uri = localFileUri, layerType = layerType) _mapLayers.value = _mapLayers.value + newItem } else { - Timber.tag("MapViewModel").e("Failed to copy file to internal storage.") + Logger.withTag("MapViewModel").e("Failed to copy file to internal storage.") } } } @@ -402,7 +403,7 @@ constructor( inputStream?.use { input -> outputStream.use { output -> input.copyTo(output) } } Uri.fromFile(outputFile) } catch (e: IOException) { - Timber.tag("MapViewModel").e(e, "Error copying file to internal storage") + Logger.withTag("MapViewModel").e(e) { "Error copying file to internal storage" } null } } @@ -453,7 +454,7 @@ constructor( file.delete() } } catch (e: Exception) { - Timber.tag("MapViewModel").e(e, "Error deleting file from internal storage") + Logger.withTag("MapViewModel").e(e) { "Error deleting file from internal storage" } } } } @@ -465,7 +466,7 @@ constructor( try { application.contentResolver.openInputStream(uriToLoad) } catch (_: Exception) { - Timber.d("MapViewModel: Error opening InputStream from URI: $uriToLoad") + Logger.d { "MapViewModel: Error opening InputStream from URI: $uriToLoad" } null } } @@ -480,7 +481,7 @@ constructor( LayerType.GEOJSON -> loadGeoJsonLayerIfNeeded(layerItem, map) } } catch (e: Exception) { - Timber.tag("MapViewModel").e(e, "Error loading map layer for ${layerItem.uri}") + Logger.withTag("MapViewModel").e(e) { "Error loading map layer for ${layerItem.uri}" } } } diff --git a/feature/map/src/main/kotlin/org/meshtastic/feature/map/BaseMapViewModel.kt b/feature/map/src/main/kotlin/org/meshtastic/feature/map/BaseMapViewModel.kt index ef6021799..8301daba2 100644 --- a/feature/map/src/main/kotlin/org/meshtastic/feature/map/BaseMapViewModel.kt +++ b/feature/map/src/main/kotlin/org/meshtastic/feature/map/BaseMapViewModel.kt @@ -20,6 +20,7 @@ package org.meshtastic.feature.map import android.os.RemoteException import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import co.touchlab.kermit.Logger import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -44,7 +45,6 @@ import org.meshtastic.core.strings.two_days import org.meshtastic.core.ui.viewmodel.stateInWhileSubscribed import org.meshtastic.feature.map.model.TracerouteOverlay import org.meshtastic.proto.MeshProtos -import timber.log.Timber import java.util.concurrent.TimeUnit @Suppress("MagicNumber") @@ -150,7 +150,7 @@ abstract class BaseMapViewModel( return try { serviceRepository.meshService?.packetId } catch (ex: RemoteException) { - Timber.e("RemoteException: ${ex.message}") + Logger.e { "RemoteException: ${ex.message}" } return null } } @@ -170,7 +170,7 @@ abstract class BaseMapViewModel( try { serviceRepository.meshService?.send(p) } catch (ex: RemoteException) { - Timber.e("Send DataPacket error: ${ex.message}") + Logger.e { "Send DataPacket error: ${ex.message}" } } } diff --git a/feature/messaging/build.gradle.kts b/feature/messaging/build.gradle.kts index 9f80e291d..804daf1f3 100644 --- a/feature/messaging/build.gradle.kts +++ b/feature/messaging/build.gradle.kts @@ -58,7 +58,7 @@ dependencies { implementation(libs.androidx.compose.ui.tooling.preview) implementation(libs.androidx.hilt.lifecycle.viewmodel.compose) implementation(libs.androidx.paging.compose) - implementation(libs.timber) + implementation(libs.kermit) debugImplementation(libs.androidx.compose.ui.test.manifest) diff --git a/feature/messaging/detekt-baseline.xml b/feature/messaging/detekt-baseline.xml index e117b9886..ecf2e0cce 100644 --- a/feature/messaging/detekt-baseline.xml +++ b/feature/messaging/detekt-baseline.xml @@ -1,26 +1,5 @@ - - ComposableParamOrder:Message.kt$MessageScreen - ComposableParamOrder:Message.kt$QuickChatRow - ComposableParamOrder:MessageActions.kt$MessageActions - ComposableParamOrder:MessageActions.kt$MessageStatusButton - ComposableParamOrder:MessageItem.kt$MessageItem - ComposableParamOrder:MessageList.kt$DeliveryInfo - ComposableParamOrder:MessageList.kt$MessageList - ComposableParamOrder:QuickChat.kt$OutlinedTextFieldWithCounter - LambdaParameterEventTrailing:Message.kt$onClick - LambdaParameterEventTrailing:Message.kt$onSendMessage - LambdaParameterEventTrailing:MessageList.kt$onReply - LambdaParameterEventTrailing:QuickChat.kt$onNavigateUp - LambdaParameterInRestartableEffect:MessageList.kt$onUnreadChanged - LongParameterList:MessageViewModel.kt$MessageViewModel$( private val nodeRepository: NodeRepository, radioConfigRepository: RadioConfigRepository, quickChatActionRepository: QuickChatActionRepository, private val serviceRepository: ServiceRepository, private val packetRepository: PacketRepository, private val uiPrefs: UiPrefs, private val meshServiceNotifications: MeshServiceNotifications, ) - ModifierMissing:Message.kt$MessageScreen - ModifierNotUsedAtRoot:QuickChat.kt$modifier = modifier.fillMaxSize().padding(innerPadding) - MutableStateParam:MessageList.kt$selectedIds - ParameterNaming:MessageList.kt$onUnreadChanged - TooManyFunctions:MessageViewModel.kt$MessageViewModel : ViewModel - Wrapping:Message.kt${ event -> when (event) { is MessageScreenEvent.SendMessage -> { viewModel.sendMessage(event.text, contactKey, event.replyingToPacketId) if (event.replyingToPacketId != null) replyingToPacketId = null messageInputState.clearText() } is MessageScreenEvent.SendReaction -> viewModel.sendReaction(event.emoji, event.messageId, contactKey) is MessageScreenEvent.DeleteMessages -> { viewModel.deleteMessages(event.ids) selectedMessageIds.value = emptySet() showDeleteDialog = false } is MessageScreenEvent.ClearUnreadCount -> viewModel.clearUnreadCount(contactKey, event.lastReadMessageId) is MessageScreenEvent.NodeDetails -> navigateToNodeDetails(event.node.num) is MessageScreenEvent.SetTitle -> viewModel.setTitle(event.title) is MessageScreenEvent.NavigateToMessages -> navigateToMessages(event.contactKey) is MessageScreenEvent.NavigateToNodeDetails -> navigateToNodeDetails(event.nodeNum) MessageScreenEvent.NavigateBack -> onNavigateBack() is MessageScreenEvent.CopyToClipboard -> { clipboardManager.nativeClipboard.setPrimaryClip(ClipData.newPlainText(event.text, event.text)) selectedMessageIds.value = emptySet() } } } - + diff --git a/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/MessageViewModel.kt b/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/MessageViewModel.kt index 0ea758b4c..750fa17cd 100644 --- a/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/MessageViewModel.kt +++ b/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/MessageViewModel.kt @@ -23,6 +23,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.paging.PagingData import androidx.paging.cachedIn +import co.touchlab.kermit.Logger import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow @@ -50,7 +51,6 @@ import org.meshtastic.core.ui.viewmodel.stateInWhileSubscribed import org.meshtastic.proto.ConfigProtos.Config.DeviceConfig.Role import org.meshtastic.proto.channelSet import org.meshtastic.proto.sharedContact -import timber.log.Timber import javax.inject.Inject private const val VERIFIED_CONTACT_FIRMWARE_CUTOFF = "2.7.12" @@ -198,7 +198,7 @@ constructor( try { serviceRepository.onServiceAction(ServiceAction.Favorite(node)) } catch (ex: RemoteException) { - Timber.e(ex, "Favorite node error") + Logger.e(ex) { "Favorite node error" } } } @@ -211,7 +211,7 @@ constructor( } serviceRepository.onServiceAction(ServiceAction.SendContact(contact = contact)) } catch (ex: RemoteException) { - Timber.e(ex, "Send shared contact error") + Logger.e(ex) { "Send shared contact error" } } } @@ -219,7 +219,7 @@ constructor( try { serviceRepository.meshService?.send(p) } catch (ex: RemoteException) { - Timber.e("Send DataPacket error: ${ex.message}") + Logger.e { "Send DataPacket error: ${ex.message}" } } } } diff --git a/feature/node/build.gradle.kts b/feature/node/build.gradle.kts index a7f89e78b..be1a651c0 100644 --- a/feature/node/build.gradle.kts +++ b/feature/node/build.gradle.kts @@ -62,7 +62,7 @@ dependencies { implementation(libs.androidx.compose.ui.tooling.preview) implementation(libs.androidx.hilt.lifecycle.viewmodel.compose) implementation(libs.androidx.navigation.common) - implementation(libs.timber) + implementation(libs.kermit) implementation(libs.coil) implementation(libs.markdown.renderer.android) implementation(libs.markdown.renderer.m3) diff --git a/feature/node/detekt-baseline.xml b/feature/node/detekt-baseline.xml index d3fc2282a..3f154d934 100644 --- a/feature/node/detekt-baseline.xml +++ b/feature/node/detekt-baseline.xml @@ -3,86 +3,9 @@ CommentWrapping:SignalMetrics.kt$Metric.SNR$/* Selected 12 as the max to get 4 equal vertical sections. */ - ComposableParamOrder:DeviceMetrics.kt$DeviceMetricsChart - ComposableParamOrder:ElevationInfo.kt$ElevationInfo - ComposableParamOrder:EnvironmentCharts.kt$ChartContent - ComposableParamOrder:EnvironmentCharts.kt$EnvironmentMetricsChart - ComposableParamOrder:EnvironmentCharts.kt$MetricPlottingCanvas - ComposableParamOrder:HostMetricsLog.kt$HostMetricsItem - ComposableParamOrder:HostMetricsLog.kt$LogLine - ComposableParamOrder:LastHeardInfo.kt$LastHeardInfo - ComposableParamOrder:NodeFilterTextField.kt$NodeFilterTextField - ComposableParamOrder:NodeItem.kt$NodeItem - ComposableParamOrder:PaxMetrics.kt$PaxMetricsChart - ComposableParamOrder:PowerMetrics.kt$PowerMetricsChart - ComposableParamOrder:SatelliteCountInfo.kt$SatelliteCountInfo - ComposableParamOrder:SignalMetrics.kt$SignalMetricsChart - ComposableParamOrder:TracerouteButton.kt$TracerouteButton - LambdaParameterEventTrailing:TracerouteLog.kt$onNavigateUp LongMethod:EnvironmentMetrics.kt$@Composable fun EnvironmentMetricsScreen(viewModel: MetricsViewModel = hiltViewModel(), onNavigateUp: () -> Unit) - LongMethod:NodeDetailsSection.kt$@Composable private fun MainNodeDetails(node: Node) MagicNumber:MetricsViewModel.kt$MetricsViewModel$1000L MagicNumber:MetricsViewModel.kt$MetricsViewModel$1e-5 MagicNumber:MetricsViewModel.kt$MetricsViewModel$1e-7 - ModifierMissing:CommonCharts.kt$ChartHeader - ModifierMissing:CommonCharts.kt$Legend - ModifierMissing:CommonCharts.kt$TimeLabels - ModifierMissing:DeviceMetrics.kt$DeviceMetricsScreen - ModifierMissing:EnvironmentMetrics.kt$EnvironmentMetricsScreen - ModifierMissing:HostMetricsLog.kt$HostMetricsLogScreen - ModifierMissing:NodeListScreen.kt$NodeListScreen - ModifierMissing:NodeStatusIcons.kt$NodeStatusIcons - ModifierMissing:PaxMetrics.kt$PaxMetricsItem - ModifierMissing:PaxMetrics.kt$PaxMetricsScreen - ModifierMissing:PositionLog.kt$PositionItem - ModifierMissing:PositionLog.kt$PositionLogScreen - ModifierMissing:PowerMetrics.kt$PowerMetricsScreen - ModifierMissing:SignalMetrics.kt$SignalMetricsScreen - ModifierNotUsedAtRoot:DeviceMetrics.kt$modifier = modifier.weight(weight = Y_AXIS_WEIGHT) - ModifierNotUsedAtRoot:DeviceMetrics.kt$modifier = modifier.width(dp) - ModifierNotUsedAtRoot:DeviceMetrics.kt$modifier.width(dp) - ModifierNotUsedAtRoot:EnvironmentCharts.kt$modifier = modifier.width(dp) - ModifierNotUsedAtRoot:EnvironmentCharts.kt$modifier.width(dp) - ModifierNotUsedAtRoot:PaxMetrics.kt$modifier.width(dp) - ModifierNotUsedAtRoot:PowerMetrics.kt$modifier = modifier.weight(weight = Y_AXIS_WEIGHT) - ModifierNotUsedAtRoot:PowerMetrics.kt$modifier = modifier.width(dp) - ModifierNotUsedAtRoot:PowerMetrics.kt$modifier.width(dp) - ModifierNotUsedAtRoot:SignalMetrics.kt$modifier = modifier.weight(weight = Y_AXIS_WEIGHT) - ModifierNotUsedAtRoot:SignalMetrics.kt$modifier = modifier.width(dp) - ModifierNotUsedAtRoot:SignalMetrics.kt$modifier.width(dp) - ModifierNotUsedAtRoot:TracerouteLog.kt$modifier = modifier.fillMaxSize().padding(innerPadding) - ModifierReused:DeviceMetrics.kt$Canvas(modifier = modifier.width(dp)) { val height = size.height val width = size.width for (i in telemetries.indices) { val telemetry = telemetries[i] /* x-value time */ val xRatio = (telemetry.time - oldest.time).toFloat() / timeDiff val x = xRatio * width /* Channel Utilization */ plotPoint( drawContext = drawContext, color = Device.CH_UTIL.color, x = x, value = telemetry.deviceMetrics.channelUtilization, divisor = MAX_PERCENT_VALUE, ) /* Air Utilization Transmit */ plotPoint( drawContext = drawContext, color = Device.AIR_UTIL.color, x = x, value = telemetry.deviceMetrics.airUtilTx, divisor = MAX_PERCENT_VALUE, ) } /* Battery Line */ var index = 0 while (index < telemetries.size) { val path = Path() index = createPath( telemetries = telemetries, index = index, path = path, oldestTime = oldest.time, timeRange = timeDiff, width = width, timeThreshold = selectedTime.timeThreshold(), ) { i -> val telemetry = telemetries.getOrNull(i) ?: telemetries.last() val ratio = telemetry.deviceMetrics.batteryLevel / MAX_PERCENT_VALUE val y = height - (ratio * height) return@createPath y } drawPath( path = path, color = Device.BATTERY.color, style = Stroke(width = GraphUtil.RADIUS, cap = StrokeCap.Round), ) } } - ModifierReused:DeviceMetrics.kt$HorizontalLinesOverlay( modifier.width(dp), lineColors = listOf(graphColor, Color.Yellow, Color.Red, graphColor, graphColor), ) - ModifierReused:DeviceMetrics.kt$TimeAxisOverlay(modifier.width(dp), oldest = oldest.time, newest = newest.time, selectedTime.lineInterval()) - ModifierReused:EnvironmentCharts.kt$Box( contentAlignment = Alignment.TopStart, modifier = modifier.horizontalScroll(state = scrollState, reverseScrolling = true), ) { HorizontalLinesOverlay(modifier.width(dp), lineColors = List(size = 5) { graphColor }) TimeAxisOverlay(modifier = modifier.width(dp), oldest = oldest, newest = newest, selectedTime.lineInterval()) MetricPlottingCanvas( modifier = modifier.width(dp), telemetries = telemetries, graphData = graphData, selectedTime = selectedTime, oldest = oldest, timeDiff = timeDiff, rightMin = rightMin, rightMax = rightMax, ) } - ModifierReused:EnvironmentCharts.kt$HorizontalLinesOverlay(modifier.width(dp), lineColors = List(size = 5) { graphColor }) - ModifierReused:EnvironmentCharts.kt$MetricPlottingCanvas( modifier = modifier.width(dp), telemetries = telemetries, graphData = graphData, selectedTime = selectedTime, oldest = oldest, timeDiff = timeDiff, rightMin = rightMin, rightMax = rightMax, ) - ModifierReused:EnvironmentCharts.kt$TimeAxisOverlay(modifier = modifier.width(dp), oldest = oldest, newest = newest, selectedTime.lineInterval()) - ModifierReused:PaxMetrics.kt$HorizontalLinesOverlay(modifier.width(dp), lineColors = List(size = 5) { Color.LightGray }) - ModifierReused:PaxMetrics.kt$Row(modifier = modifier.fillMaxWidth().fillMaxHeight(fraction = 0.33f)) { YAxisLabels( modifier = Modifier.weight(Y_AXIS_WEIGHT).fillMaxHeight().padding(start = 8.dp), labelColor = MaterialTheme.colorScheme.onSurface, minValue = minValue, maxValue = maxValue, ) Box( contentAlignment = Alignment.TopStart, modifier = Modifier.horizontalScroll(state = scrollState, reverseScrolling = true).weight(CHART_WEIGHT), ) { HorizontalLinesOverlay(modifier.width(dp), lineColors = List(size = 5) { Color.LightGray }) TimeAxisOverlay(modifier.width(dp), oldest = minTime, newest = maxTime, timeFrame.lineInterval()) Canvas(modifier = Modifier.width(dp).fillMaxHeight()) { val width = size.width val height = size.height fun xForTime(t: Int): Float = if (maxTime == minTime) width / 2 else (t - minTime).toFloat() / (maxTime - minTime) * width fun yForValue(v: Int): Float = height - (v - minValue) / (maxValue - minValue) * height fun drawLine(series: List<Pair<Int, Int>>, color: Color) { for (i in 1 until series.size) { drawLine( color = color, start = Offset(xForTime(series[i - 1].first), yForValue(series[i - 1].second)), end = Offset(xForTime(series[i].first), yForValue(series[i].second)), strokeWidth = 2.dp.toPx(), ) } } drawLine(bleSeries, PaxSeries.BLE.color) drawLine(wifiSeries, PaxSeries.WIFI.color) drawLine(totalSeries, PaxSeries.PAX.color) } } YAxisLabels( modifier = Modifier.weight(Y_AXIS_WEIGHT).fillMaxHeight().padding(end = 8.dp), labelColor = MaterialTheme.colorScheme.onSurface, minValue = minValue, maxValue = maxValue, ) } - ModifierReused:PaxMetrics.kt$TimeAxisOverlay(modifier.width(dp), oldest = minTime, newest = maxTime, timeFrame.lineInterval()) - ModifierReused:PowerMetrics.kt$Canvas(modifier = modifier.width(dp)) { val width = size.width val height = size.height /* Voltage */ var index = 0 while (index < telemetries.size) { val path = Path() index = createPath( telemetries = telemetries, index = index, path = path, oldestTime = oldest.time, timeRange = timeDiff, width = width, timeThreshold = selectedTime.timeThreshold(), ) { i -> val telemetry = telemetries.getOrNull(i) ?: telemetries.last() val ratio = (retrieveVoltage(selectedChannel, telemetry) - voltageMin) / voltageDiff val y = height - (ratio * height) return@createPath y } drawPath( path = path, color = VOLTAGE_COLOR, style = Stroke(width = GraphUtil.RADIUS, cap = StrokeCap.Round), ) } /* Current */ index = 0 while (index < telemetries.size) { val path = Path() index = createPath( telemetries = telemetries, index = index, path = path, oldestTime = oldest.time, timeRange = timeDiff, width = width, timeThreshold = selectedTime.timeThreshold(), ) { i -> val telemetry = telemetries.getOrNull(i) ?: telemetries.last() val ratio = (retrieveCurrent(selectedChannel, telemetry) - Power.CURRENT.min) / currentDiff val y = height - (ratio * height) return@createPath y } drawPath( path = path, color = Power.CURRENT.color, style = Stroke(width = GraphUtil.RADIUS, cap = StrokeCap.Round), ) } } - ModifierReused:PowerMetrics.kt$HorizontalLinesOverlay(modifier.width(dp), lineColors = List(size = 5) { graphColor }) - ModifierReused:PowerMetrics.kt$TimeAxisOverlay(modifier.width(dp), oldest = oldest.time, newest = newest.time, selectedTime.lineInterval()) - ModifierReused:PowerMetrics.kt$YAxisLabels( modifier = modifier.weight(weight = Y_AXIS_WEIGHT), Power.CURRENT.color, minValue = Power.CURRENT.min, maxValue = Power.CURRENT.max, ) - ModifierReused:PowerMetrics.kt$YAxisLabels( modifier = modifier.weight(weight = Y_AXIS_WEIGHT), VOLTAGE_COLOR, minValue = voltageMin, maxValue = voltageMax, ) - ModifierReused:SignalMetrics.kt$Canvas(modifier = modifier.width(dp)) { val width = size.width /* Plot */ for (packet in meshPackets) { val xRatio = (packet.rxTime - oldest.rxTime).toFloat() / timeDiff val x = xRatio * width /* SNR */ plotPoint( drawContext = drawContext, color = Metric.SNR.color, x = x, value = packet.rxSnr - Metric.SNR.min, divisor = snrDiff, ) /* RSSI */ plotPoint( drawContext = drawContext, color = Metric.RSSI.color, x = x, value = packet.rxRssi - Metric.RSSI.min, divisor = rssiDiff, ) } } - ModifierReused:SignalMetrics.kt$HorizontalLinesOverlay(modifier.width(dp), lineColors = List(size = 5) { graphColor }) - ModifierReused:SignalMetrics.kt$TimeAxisOverlay( modifier.width(dp), oldest = oldest.rxTime, newest = newest.rxTime, selectedTime.lineInterval(), ) - ModifierReused:SignalMetrics.kt$YAxisLabels( modifier = modifier.weight(weight = Y_AXIS_WEIGHT), Metric.RSSI.color, minValue = Metric.RSSI.min, maxValue = Metric.RSSI.max, ) - ModifierReused:SignalMetrics.kt$YAxisLabels( modifier = modifier.weight(weight = Y_AXIS_WEIGHT), Metric.SNR.color, minValue = Metric.SNR.min, maxValue = Metric.SNR.max, ) - ModifierWithoutDefault:CommonCharts.kt$modifier - ModifierWithoutDefault:EnvironmentCharts.kt$modifier - MultipleEmitters:CommonCharts.kt$LegendLabel - MultipleEmitters:DeviceMetrics.kt$DeviceMetricsChart - MultipleEmitters:EnvironmentCharts.kt$EnvironmentMetricsChart - MultipleEmitters:NodeDetailsSection.kt$MainNodeDetails - MultipleEmitters:PaxMetrics.kt$PaxMetricsChart - MultipleEmitters:PowerMetrics.kt$PowerMetricsChart - MultipleEmitters:RemoteDeviceActions.kt$RemoteDeviceActions - MultipleEmitters:SignalMetrics.kt$SignalMetricsChart - ParameterNaming:NodeFilterTextField.kt$onToggleShowIgnored - PreviewPublic:NodeItem.kt$NodeInfoPreview - PreviewPublic:NodeItem.kt$NodeInfoSimplePreview diff --git a/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/FirmwareReleaseSheetContent.kt b/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/FirmwareReleaseSheetContent.kt index ca6feba6d..a909bd4bc 100644 --- a/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/FirmwareReleaseSheetContent.kt +++ b/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/FirmwareReleaseSheetContent.kt @@ -41,6 +41,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import androidx.core.net.toUri +import co.touchlab.kermit.Logger import com.mikepenz.markdown.m3.Markdown import kotlinx.coroutines.launch import org.jetbrains.compose.resources.stringResource @@ -50,7 +51,6 @@ import org.meshtastic.core.strings.download import org.meshtastic.core.strings.error_no_app_to_handle_link import org.meshtastic.core.strings.view_release import org.meshtastic.core.ui.util.showToast -import timber.log.Timber @Composable fun FirmwareReleaseSheetContent(firmwareRelease: FirmwareRelease, modifier: Modifier = Modifier) { @@ -72,7 +72,7 @@ fun FirmwareReleaseSheetContent(firmwareRelease: FirmwareRelease, modifier: Modi context.startActivity(intent) } catch (e: ActivityNotFoundException) { scope.launch { context.showToast(Res.string.error_no_app_to_handle_link) } - Timber.e(e) + Logger.e(e) { "Failed to handle release page URL" } } }, modifier = Modifier.weight(1f), @@ -88,7 +88,7 @@ fun FirmwareReleaseSheetContent(firmwareRelease: FirmwareRelease, modifier: Modi context.startActivity(intent) } catch (e: ActivityNotFoundException) { scope.launch { context.showToast(Res.string.error_no_app_to_handle_link) } - Timber.e(e) + Logger.e(e) { "Failed to handle release zip URL" } } }, modifier = Modifier.weight(1f), diff --git a/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/LinkedCoordinatesItem.kt b/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/LinkedCoordinatesItem.kt index f3640e868..edc0e5a0d 100644 --- a/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/LinkedCoordinatesItem.kt +++ b/feature/node/src/main/kotlin/org/meshtastic/feature/node/component/LinkedCoordinatesItem.kt @@ -32,6 +32,7 @@ import androidx.compose.ui.platform.LocalClipboard import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.core.net.toUri +import co.touchlab.kermit.Logger import kotlinx.coroutines.launch import org.jetbrains.compose.resources.stringResource import org.meshtastic.core.database.model.Node @@ -47,7 +48,6 @@ import org.meshtastic.core.ui.component.icon import org.meshtastic.core.ui.theme.AppTheme import org.meshtastic.core.ui.util.showToast import org.meshtastic.proto.ConfigProtos.Config.DisplayConfig.DisplayUnits -import timber.log.Timber import java.net.URLEncoder @OptIn(ExperimentalFoundationApi::class) @@ -82,7 +82,7 @@ fun LinkedCoordinatesItem(node: Node, displayUnits: DisplayUnits = DisplayUnits. coroutineScope.launch { context.showToast("No application available to open this location!") } } } catch (ex: ActivityNotFoundException) { - Timber.d("Failed to open geo intent: $ex") + Logger.d { "Failed to open geo intent: $ex" } } }, onLongClick = { diff --git a/feature/node/src/main/kotlin/org/meshtastic/feature/node/detail/NodeDetailViewModel.kt b/feature/node/src/main/kotlin/org/meshtastic/feature/node/detail/NodeDetailViewModel.kt index a9a28055b..f0816eea7 100644 --- a/feature/node/src/main/kotlin/org/meshtastic/feature/node/detail/NodeDetailViewModel.kt +++ b/feature/node/src/main/kotlin/org/meshtastic/feature/node/detail/NodeDetailViewModel.kt @@ -20,6 +20,7 @@ package org.meshtastic.feature.node.detail 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.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow @@ -33,7 +34,6 @@ import org.meshtastic.core.model.TelemetryType import org.meshtastic.core.service.ServiceAction import org.meshtastic.core.service.ServiceRepository import org.meshtastic.feature.node.component.NodeMenuAction -import timber.log.Timber import javax.inject.Inject @HiltViewModel @@ -77,20 +77,20 @@ constructor( try { nodeRepository.setNodeNotes(nodeNum, notes) } catch (ex: java.io.IOException) { - Timber.e("Set node notes IO error: ${ex.message}") + Logger.e { "Set node notes IO error: ${ex.message}" } } catch (ex: java.sql.SQLException) { - Timber.e("Set node notes SQL error: ${ex.message}") + Logger.e { "Set node notes SQL error: ${ex.message}" } } } private fun removeNode(nodeNum: Int) = viewModelScope.launch(Dispatchers.IO) { - Timber.i("Removing node '$nodeNum'") + Logger.i { "Removing node '$nodeNum'" } try { val packetId = serviceRepository.meshService?.packetId ?: return@launch serviceRepository.meshService?.removeByNodenum(packetId, nodeNum) nodeRepository.deleteNode(nodeNum) } catch (ex: RemoteException) { - Timber.e("Remove node error: ${ex.message}") + Logger.e { "Remove node error: ${ex.message}" } } } @@ -98,7 +98,7 @@ constructor( try { serviceRepository.onServiceAction(ServiceAction.Ignore(node)) } catch (ex: RemoteException) { - Timber.e(ex, "Ignore node error") + Logger.e(ex) { "Ignore node error" } } } @@ -106,55 +106,55 @@ constructor( try { serviceRepository.onServiceAction(ServiceAction.Favorite(node)) } catch (ex: RemoteException) { - Timber.e(ex, "Favorite node error") + Logger.e(ex) { "Favorite node error" } } } private fun requestUserInfo(destNum: Int) { - Timber.i("Requesting UserInfo for '$destNum'") + Logger.i { "Requesting UserInfo for '$destNum'" } try { serviceRepository.meshService?.requestUserInfo(destNum) } catch (ex: RemoteException) { - Timber.e("Request NodeInfo error: ${ex.message}") + Logger.e { "Request NodeInfo error: ${ex.message}" } } } private fun requestNeighborInfo(destNum: Int) { - Timber.i("Requesting NeighborInfo for '$destNum'") + Logger.i { "Requesting NeighborInfo for '$destNum'" } try { val packetId = serviceRepository.meshService?.packetId ?: return serviceRepository.meshService?.requestNeighborInfo(packetId, destNum) } catch (ex: RemoteException) { - Timber.e("Request NeighborInfo error: ${ex.message}") + Logger.e { "Request NeighborInfo error: ${ex.message}" } } } private fun requestPosition(destNum: Int, position: Position = Position(0.0, 0.0, 0)) { - Timber.i("Requesting position for '$destNum'") + Logger.i { "Requesting position for '$destNum'" } try { serviceRepository.meshService?.requestPosition(destNum, position) } catch (ex: RemoteException) { - Timber.e("Request position error: ${ex.message}") + Logger.e { "Request position error: ${ex.message}" } } } private fun requestTelemetry(destNum: Int, type: TelemetryType) { - Timber.i("Requesting telemetry for '$destNum'") + Logger.i { "Requesting telemetry for '$destNum'" } try { val packetId = serviceRepository.meshService?.packetId ?: return serviceRepository.meshService?.requestTelemetry(packetId, destNum, type.ordinal) } catch (ex: RemoteException) { - Timber.e("Request telemetry error: ${ex.message}") + Logger.e { "Request telemetry error: ${ex.message}" } } } private fun requestTraceroute(destNum: Int) { - Timber.i("Requesting traceroute for '$destNum'") + Logger.i { "Requesting traceroute for '$destNum'" } try { val packetId = serviceRepository.meshService?.packetId ?: return serviceRepository.meshService?.requestTraceroute(packetId, destNum) } catch (ex: RemoteException) { - Timber.e("Request traceroute error: ${ex.message}") + Logger.e { "Request traceroute error: ${ex.message}" } } } } diff --git a/feature/node/src/main/kotlin/org/meshtastic/feature/node/list/NodeActions.kt b/feature/node/src/main/kotlin/org/meshtastic/feature/node/list/NodeActions.kt index 85b7dfbd2..a5a91601e 100644 --- a/feature/node/src/main/kotlin/org/meshtastic/feature/node/list/NodeActions.kt +++ b/feature/node/src/main/kotlin/org/meshtastic/feature/node/list/NodeActions.kt @@ -18,13 +18,13 @@ package org.meshtastic.feature.node.list import android.os.RemoteException +import co.touchlab.kermit.Logger import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.meshtastic.core.data.repository.NodeRepository import org.meshtastic.core.database.model.Node import org.meshtastic.core.service.ServiceAction import org.meshtastic.core.service.ServiceRepository -import timber.log.Timber import javax.inject.Inject class NodeActions @@ -37,7 +37,7 @@ constructor( try { serviceRepository.onServiceAction(ServiceAction.Favorite(node)) } catch (ex: RemoteException) { - Timber.e(ex, "Favorite node error") + Logger.e(ex) { "Favorite node error" } } } @@ -45,18 +45,18 @@ constructor( try { serviceRepository.onServiceAction(ServiceAction.Ignore(node)) } catch (ex: RemoteException) { - Timber.e(ex, "Ignore node error") + Logger.e(ex) { "Ignore node error" } } } suspend fun removeNode(nodeNum: Int) = withContext(Dispatchers.IO) { - Timber.i("Removing node '$nodeNum'") + Logger.i { "Removing node '$nodeNum'" } try { val packetId = serviceRepository.meshService?.packetId ?: return@withContext serviceRepository.meshService?.removeByNodenum(packetId, nodeNum) nodeRepository.deleteNode(nodeNum) } catch (ex: RemoteException) { - Timber.e("Remove node error: ${ex.message}") + Logger.e { "Remove node error: ${ex.message}" } } } } diff --git a/feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/HardwareModelExtensions.kt b/feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/HardwareModelExtensions.kt index 754191ffd..3dde4ef13 100644 --- a/feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/HardwareModelExtensions.kt +++ b/feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/HardwareModelExtensions.kt @@ -17,8 +17,8 @@ package org.meshtastic.feature.node.metrics +import co.touchlab.kermit.Logger import org.meshtastic.proto.MeshProtos -import timber.log.Timber /** * Safely extracts the hardware model number from a HardwareModel enum. @@ -34,6 +34,6 @@ import timber.log.Timber fun MeshProtos.HardwareModel.safeNumber(fallbackValue: Int = -1): Int = try { this.number } catch (e: IllegalArgumentException) { - Timber.w("Unknown hardware model enum value: $this, using fallback value: $fallbackValue") + Logger.w { "Unknown hardware model enum value: $this, using fallback value: $fallbackValue" } fallbackValue } diff --git a/feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/MetricsViewModel.kt b/feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/MetricsViewModel.kt index fc07fe74f..5e0713f00 100644 --- a/feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/MetricsViewModel.kt +++ b/feature/node/src/main/kotlin/org/meshtastic/feature/node/metrics/MetricsViewModel.kt @@ -23,6 +23,7 @@ import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.navigation.toRoute +import co.touchlab.kermit.Logger import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow @@ -63,7 +64,6 @@ import org.meshtastic.proto.MeshProtos import org.meshtastic.proto.MeshProtos.MeshPacket import org.meshtastic.proto.Portnums import org.meshtastic.proto.Portnums.PortNum -import timber.log.Timber import java.io.BufferedWriter import java.io.FileNotFoundException import java.io.FileWriter @@ -343,16 +343,16 @@ constructor( } } - Timber.d("MetricsViewModel created") + Logger.d { "MetricsViewModel created" } } else { - Timber.d("MetricsViewModel: destNum is null, skipping metrics flows initialization.") + Logger.d { "MetricsViewModel: destNum is null, skipping metrics flows initialization." } } } } override fun onCleared() { super.onCleared() - Timber.d("MetricsViewModel cleared") + Logger.d { "MetricsViewModel cleared" } } fun setTimeFrame(timeFrame: TimeFrame) { @@ -395,7 +395,7 @@ constructor( } } } catch (ex: FileNotFoundException) { - Timber.e(ex, "Can't write file error") + Logger.e(ex) { "Can't write file error" } } } } diff --git a/feature/settings/build.gradle.kts b/feature/settings/build.gradle.kts index 7246ee0d4..06d56edd0 100644 --- a/feature/settings/build.gradle.kts +++ b/feature/settings/build.gradle.kts @@ -65,7 +65,7 @@ dependencies { implementation(libs.androidx.hilt.lifecycle.viewmodel.compose) implementation(libs.androidx.navigation.compose) implementation(libs.kotlinx.collections.immutable) - implementation(libs.timber) + implementation(libs.kermit) implementation(libs.zxing.android.embedded) androidTestImplementation(libs.androidx.compose.ui.test.junit4) diff --git a/feature/settings/detekt-baseline.xml b/feature/settings/detekt-baseline.xml index 809b837cf..59df3d54a 100644 --- a/feature/settings/detekt-baseline.xml +++ b/feature/settings/detekt-baseline.xml @@ -2,28 +2,18 @@ - ComposableParamOrder:ChannelConfigScreen.kt$ChannelConfigScreen - ComposableParamOrder:Debug.kt$DecodedPayloadBlock - ComposableParamOrder:DebugSearch.kt$DebugSearchState - ComposableParamOrder:DebugSearch.kt$DebugSearchStateviewModelDefaults - ComposableParamOrder:MapReportingPreference.kt$MapReportingPreference - ComposableParamOrder:NodeActionButton.kt$NodeActionButton - ComposableParamOrder:WarningDialog.kt$WarningDialog CyclomaticComplexMethod:NetworkConfigItemList.kt$@Composable fun NetworkConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -> Unit) CyclomaticComplexMethod:PositionConfigItemList.kt$@OptIn(ExperimentalPermissionsApi::class) @Composable fun PositionConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -> Unit) CyclomaticComplexMethod:RadioConfigViewModel.kt$RadioConfigViewModel$private fun processPacketResponse(packet: MeshProtos.MeshPacket) - LambdaParameterEventTrailing:NodeActionButton.kt$onClick LongMethod:AudioConfigItemList.kt$@Composable fun AudioConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -> Unit) LongMethod:CannedMessageConfigItemList.kt$@Composable fun CannedMessageConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -> Unit) LongMethod:DetectionSensorConfigItemList.kt$@Composable fun DetectionSensorConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -> Unit) LongMethod:DeviceConfigItemList.kt$@OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable fun DeviceConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -> Unit) LongMethod:DisplayConfigItemList.kt$@Composable fun DisplayConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -> Unit) - LongMethod:ExternalNotificationConfigItemList.kt$@Composable fun ExternalNotificationConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -> Unit) LongMethod:LoRaConfigItemList.kt$@Composable fun LoRaConfigScreen(viewModel: RadioConfigViewModel, onBack: () -> Unit) LongMethod:NetworkConfigItemList.kt$@Composable fun NetworkConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -> Unit) LongMethod:PositionConfigItemList.kt$@OptIn(ExperimentalPermissionsApi::class) @Composable fun PositionConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -> Unit) LongMethod:PowerConfigItemList.kt$@Composable fun PowerConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -> Unit) - LongMethod:RadioConfigScreenList.kt$@Composable fun <T : MessageLite> RadioConfigScreenList( title: String, onBack: () -> Unit, responseState: ResponseState<Any>, onDismissPacketResponse: () -> Unit, configState: ConfigState<T>, enabled: Boolean, onSave: (T) -> Unit, content: LazyListScope.() -> Unit, ) LongMethod:RadioConfigViewModel.kt$RadioConfigViewModel$private fun processPacketResponse(packet: MeshProtos.MeshPacket) LongMethod:SecurityConfigItemList.kt$@OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable fun SecurityConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -> Unit) LongMethod:SerialConfigItemList.kt$@Composable fun SerialConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -> Unit) @@ -34,35 +24,10 @@ MagicNumber:EditChannelDialog.kt$16 MagicNumber:EditChannelDialog.kt$32 MagicNumber:PacketResponseStateDialog.kt$100 - ModifierMissing:CleanNodeDatabaseScreen.kt$CleanNodeDatabaseScreen - ModifierMissing:Debug.kt$DebugScreen - ModifierMissing:DeviceConfigItemList.kt$DeviceConfigScreen - ModifierMissing:MapReportingPreference.kt$MapReportingPreference - ModifierMissing:NetworkConfigItemList.kt$NetworkConfigScreen - ModifierMissing:PositionConfigItemList.kt$PositionConfigScreen - ModifierMissing:RadioConfig.kt$RadioConfigItemList - ModifierMissing:RadioConfigScreenList.kt$RadioConfigScreenList - ModifierMissing:SecurityConfigItemList.kt$SecurityConfigScreen - ModifierMissing:SettingsScreen.kt$SettingsScreen - ModifierNotUsedAtRoot:EditChannelDialog.kt$modifier = modifier.weight(1f) - ModifierNotUsedAtRoot:EditDeviceProfileDialog.kt$modifier = modifier.weight(1f) - MultipleEmitters:CleanNodeDatabaseScreen.kt$NodesDeletionPreview - MultipleEmitters:RadioConfig.kt$RadioConfigItemList NestedBlockDepth:RadioConfigViewModel.kt$RadioConfigViewModel$private fun processPacketResponse(packet: MeshProtos.MeshPacket) - ParameterNaming:ChannelConfigScreen.kt$onPositiveClicked - ParameterNaming:CleanNodeDatabaseScreen.kt$onCheckedChanged - ParameterNaming:CleanNodeDatabaseScreen.kt$onDaysChanged - ParameterNaming:MapReportingPreference.kt$onMapReportingEnabledChanged - ParameterNaming:MapReportingPreference.kt$onPositionPrecisionChanged - ParameterNaming:MapReportingPreference.kt$onPublishIntervalSecsChanged - ParameterNaming:MapReportingPreference.kt$onShouldReportLocationChanged - PreviewPublic:MapReportingPreference.kt$MapReportingPreview ReturnCount:RadioConfigViewModel.kt$RadioConfigViewModel$private fun processPacketResponse(packet: MeshProtos.MeshPacket) TooGenericExceptionCaught:LanguageUtils.kt$LanguageUtils$e: Exception TooGenericExceptionCaught:RadioConfigViewModel.kt$RadioConfigViewModel$ex: Exception TooManyFunctions:RadioConfigViewModel.kt$RadioConfigViewModel : ViewModel - UnusedParameter:ChannelConfigScreen.kt$onBack: () -> Unit - UnusedParameter:ChannelConfigScreen.kt$title: String - ViewModelInjection:DebugSearch.kt$viewModel diff --git a/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/SettingsViewModel.kt b/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/SettingsViewModel.kt index 51fdc435e..c03d9afb7 100644 --- a/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/SettingsViewModel.kt +++ b/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/SettingsViewModel.kt @@ -21,6 +21,7 @@ import android.app.Application import android.net.Uri import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import co.touchlab.kermit.Logger import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow @@ -57,7 +58,6 @@ import org.meshtastic.core.ui.viewmodel.stateInWhileSubscribed import org.meshtastic.proto.LocalOnlyProtos.LocalConfig import org.meshtastic.proto.MeshProtos import org.meshtastic.proto.Portnums -import timber.log.Timber import java.io.BufferedWriter import java.io.FileNotFoundException import java.io.FileWriter @@ -287,7 +287,7 @@ constructor( } } } catch (ex: FileNotFoundException) { - Timber.e("Can't write file error: ${ex.message}") + Logger.e { "Can't write file error: ${ex.message}" } } } } diff --git a/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/debugging/Debug.kt b/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/debugging/Debug.kt index ac0126f43..509041c9d 100644 --- a/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/debugging/Debug.kt +++ b/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/debugging/Debug.kt @@ -75,6 +75,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle +import co.touchlab.kermit.Logger import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -96,7 +97,6 @@ import org.meshtastic.core.ui.theme.AnnotationColor import org.meshtastic.core.ui.theme.AppTheme import org.meshtastic.core.ui.util.showToast import org.meshtastic.feature.settings.debugging.DebugViewModel.UiMeshLog -import timber.log.Timber import java.io.IOException import java.io.OutputStreamWriter import java.nio.charset.StandardCharsets @@ -408,7 +408,7 @@ private suspend fun exportAllLogsToUri(context: Context, targetUri: Uri, logs: L withContext(Dispatchers.Main) { context.showToast(Res.string.debug_export_success, logs.size) } } catch (e: IOException) { withContext(Dispatchers.Main) { context.showToast(Res.string.debug_export_failed, e.message ?: "") } - Timber.w(e, "Error:IOException ") + Logger.w(e) { "Error:IOException" } } } diff --git a/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/debugging/DebugViewModel.kt b/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/debugging/DebugViewModel.kt index b7a93c690..48050b229 100644 --- a/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/debugging/DebugViewModel.kt +++ b/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/debugging/DebugViewModel.kt @@ -20,6 +20,7 @@ package org.meshtastic.feature.settings.debugging import androidx.compose.runtime.Immutable import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import co.touchlab.kermit.Logger import com.google.protobuf.InvalidProtocolBufferException import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.collections.immutable.ImmutableList @@ -44,7 +45,6 @@ import org.meshtastic.proto.PaxcountProtos import org.meshtastic.proto.Portnums.PortNum import org.meshtastic.proto.StoreAndForwardProtos import org.meshtastic.proto.TelemetryProtos -import timber.log.Timber import java.text.DateFormat import java.util.Date import java.util.Locale @@ -237,7 +237,7 @@ constructor( } init { - Timber.d("DebugViewModel created") + Logger.d { "DebugViewModel created" } viewModelScope.launch { combine(searchManager.searchText, filterManager.filteredLogs) { searchText, logs -> searchManager.findSearchMatches(searchText, logs) @@ -250,7 +250,7 @@ constructor( override fun onCleared() { super.onCleared() - Timber.d("DebugViewModel cleared") + Logger.d { "DebugViewModel cleared" } } private fun toUiState(databaseLogs: List) = databaseLogs diff --git a/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/radio/RadioConfigViewModel.kt b/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/radio/RadioConfigViewModel.kt index 3b2bfeb4c..a1c520bfc 100644 --- a/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/radio/RadioConfigViewModel.kt +++ b/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/radio/RadioConfigViewModel.kt @@ -31,6 +31,7 @@ import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.navigation.toRoute +import co.touchlab.kermit.Logger import com.google.protobuf.MessageLite import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers @@ -80,7 +81,6 @@ import org.meshtastic.proto.Portnums import org.meshtastic.proto.config import org.meshtastic.proto.deviceProfile import org.meshtastic.proto.moduleConfig -import timber.log.Timber import java.io.FileOutputStream import javax.inject.Inject @@ -179,7 +179,7 @@ constructor( } .launchIn(viewModelScope) - Timber.d("RadioConfigViewModel created") + Logger.d { "RadioConfigViewModel created" } } private val myNodeInfo: StateFlow @@ -205,7 +205,7 @@ constructor( override fun onCleared() { super.onCleared() - Timber.d("RadioConfigViewModel cleared") + Logger.d { "RadioConfigViewModel cleared" } } private fun request(destNum: Int, requestAction: suspend (IMeshService, Int, Int) -> Unit, errorMessage: String) = @@ -227,7 +227,7 @@ constructor( } } } catch (ex: RemoteException) { - Timber.e("$errorMessage: ${ex.message}") + Logger.e { "$errorMessage: ${ex.message}" } } } } @@ -422,7 +422,7 @@ constructor( try { meshService?.setFixedPosition(destNum, position) } catch (ex: RemoteException) { - Timber.e("Set fixed position error: ${ex.message}") + Logger.e { "Set fixed position error: ${ex.message}" } } } @@ -436,7 +436,7 @@ constructor( onResult(protobuf) } } catch (ex: Exception) { - Timber.e("Import DeviceProfile error: ${ex.message}") + Logger.e { "Import DeviceProfile error: ${ex.message}" } sendError(ex.customMessage) } } @@ -452,7 +452,7 @@ constructor( } setResponseStateSuccess() } catch (ex: Exception) { - Timber.e("Can't write file error: ${ex.message}") + Logger.e { "Can't write file error: ${ex.message}" } sendError(ex.customMessage) } } @@ -491,7 +491,7 @@ constructor( setResponseStateSuccess() } catch (ex: Exception) { val errorMessage = "Can't write security keys JSON error: ${ex.message}" - Timber.e(errorMessage) + Logger.e { errorMessage } sendError(ex.customMessage) } } @@ -514,7 +514,7 @@ constructor( try { setChannels(channelUrl) } catch (ex: Exception) { - Timber.e(ex, "DeviceProfile channel import error") + Logger.e(ex) { "DeviceProfile channel import error" } sendError(ex.customMessage) } } @@ -656,7 +656,7 @@ constructor( if (data?.portnumValue == Portnums.PortNum.ROUTING_APP_VALUE) { val parsed = MeshProtos.Routing.parseFrom(data.payload) - Timber.d(debugMsg.format(parsed.errorReason.name)) + Logger.d { debugMsg.format(parsed.errorReason.name) } if (parsed.errorReason != MeshProtos.Routing.Error.NONE) { sendError(getStringResFrom(parsed.errorReasonValue)) } else if (packet.from == destNum && route.isEmpty()) { @@ -670,7 +670,7 @@ constructor( } if (data?.portnumValue == Portnums.PortNum.ADMIN_APP_VALUE) { val parsed = AdminProtos.AdminMessage.parseFrom(data.payload) - Timber.d(debugMsg.format(parsed.payloadVariantCase.name)) + Logger.d { debugMsg.format(parsed.payloadVariantCase.name) } if (destNum != packet.from) { sendError("Unexpected sender: ${packet.from.toUInt()} instead of ${destNum.toUInt()}.") return @@ -744,7 +744,7 @@ constructor( incrementCompleted() } - else -> Timber.d("No custom processing needed for ${parsed.payloadVariantCase}") + else -> Logger.d { "No custom processing needed for ${parsed.payloadVariantCase}" } } if (AdminRoute.entries.any { it.name == route }) { diff --git a/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/radio/component/ExternalNotificationConfigItemList.kt b/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/radio/component/ExternalNotificationConfigItemList.kt index e02fa7b4c..39b736aa0 100644 --- a/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/radio/component/ExternalNotificationConfigItemList.kt +++ b/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/radio/component/ExternalNotificationConfigItemList.kt @@ -44,6 +44,7 @@ import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle +import co.touchlab.kermit.Logger import org.jetbrains.compose.resources.stringResource import org.meshtastic.core.strings.Res import org.meshtastic.core.strings.advanced @@ -79,7 +80,6 @@ import org.meshtastic.feature.settings.util.gpioPins import org.meshtastic.feature.settings.util.toDisplayString import org.meshtastic.proto.copy import org.meshtastic.proto.moduleConfig -import timber.log.Timber import java.io.File private const val MAX_RINGTONE_SIZE = 230 @@ -116,7 +116,7 @@ fun ExternalNotificationConfigScreen( } } } catch (e: Exception) { - Timber.e(e, "Error importing ringtone") + Logger.e(e) { "Error importing ringtone" } Toast.makeText(context, "Error importing: ${e.message}", Toast.LENGTH_SHORT).show() } } @@ -308,7 +308,7 @@ fun ExternalNotificationConfigScreen( tempFile.delete() } } catch (e: Exception) { - Timber.e(e, "Failed to play ringtone") + Logger.e(e) { "Failed to play ringtone" } } }, enabled = state.connected, diff --git a/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/util/LanguageUtils.kt b/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/util/LanguageUtils.kt index c8822ea88..445044ffc 100644 --- a/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/util/LanguageUtils.kt +++ b/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/util/LanguageUtils.kt @@ -22,6 +22,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.platform.LocalResources import androidx.core.os.LocaleListCompat +import co.touchlab.kermit.Logger import org.jetbrains.compose.resources.stringResource import org.meshtastic.core.strings.Res import org.meshtastic.core.strings.fr_HT @@ -30,7 +31,6 @@ import org.meshtastic.core.strings.pt_BR import org.meshtastic.core.strings.zh_CN import org.meshtastic.core.strings.zh_TW import org.xmlpull.v1.XmlPullParser -import timber.log.Timber import java.util.Locale object LanguageUtils { @@ -69,7 +69,7 @@ object LanguageUtils { } } } catch (e: Exception) { - Timber.e("Error parsing locale_config.xml: ${e.message}") + Logger.e { "Error parsing locale_config.xml: ${e.message}" } } } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ac0e4acbd..128b69366 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -164,7 +164,8 @@ osmbonuspack = { module = "com.github.MKergall:osmbonuspack", version = "6.9.0" osmdroid-android = { module = "org.osmdroid:osmdroid-android", version.ref = "osmdroid-android" } osmdroid-geopackage = { module = "org.osmdroid:osmdroid-geopackage", version.ref = "osmdroid-android" } streamsupport-minifuture = { module = "net.sourceforge.streamsupport:streamsupport-minifuture", version = "1.7.4" } -timber = { module = "com.jakewharton.timber:timber", version = "5.0.1" } + kermit = { module = "co.touchlab:kermit", version = "2.0.5" } + usb-serial-android = { module = "com.github.mik3y:usb-serial-for-android", version = "3.10.0" } zxing-android-embedded = { module = "com.journeyapps:zxing-android-embedded", version = "4.3.0" }