diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e6d175d84..282939bd3 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -155,7 +155,6 @@ android { } } bundle { language { enableSplit = false } } - buildFeatures { aidl = true } } secrets { @@ -189,6 +188,7 @@ dependencies { implementation(projects.core.network) implementation(projects.core.prefs) implementation(projects.core.proto) + implementation(projects.core.service) implementation(projects.core.strings) implementation(projects.core.ui) implementation(projects.feature.map) diff --git a/app/src/fdroid/java/com/geeksville/mesh/ui/map/MapViewModel.kt b/app/src/fdroid/java/com/geeksville/mesh/ui/map/MapViewModel.kt index 1d9bcfdc2..0120ddf54 100644 --- a/app/src/fdroid/java/com/geeksville/mesh/ui/map/MapViewModel.kt +++ b/app/src/fdroid/java/com/geeksville/mesh/ui/map/MapViewModel.kt @@ -19,7 +19,6 @@ package com.geeksville.mesh.ui.map import androidx.lifecycle.viewModelScope import com.geeksville.mesh.LocalOnlyProtos.LocalConfig -import com.geeksville.mesh.service.ServiceRepository import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.stateIn @@ -28,6 +27,7 @@ import org.meshtastic.core.data.repository.PacketRepository import org.meshtastic.core.data.repository.RadioConfigRepository import org.meshtastic.core.model.DataPacket import org.meshtastic.core.prefs.map.MapPrefs +import org.meshtastic.core.service.ServiceRepository import javax.inject.Inject @HiltViewModel diff --git a/app/src/google/java/com/geeksville/mesh/ui/map/MapViewModel.kt b/app/src/google/java/com/geeksville/mesh/ui/map/MapViewModel.kt index a77723b48..d9be78a2b 100644 --- a/app/src/google/java/com/geeksville/mesh/ui/map/MapViewModel.kt +++ b/app/src/google/java/com/geeksville/mesh/ui/map/MapViewModel.kt @@ -23,7 +23,6 @@ import androidx.core.net.toFile import androidx.lifecycle.viewModelScope import com.geeksville.mesh.ConfigProtos import com.geeksville.mesh.android.BuildUtils.debug -import com.geeksville.mesh.service.ServiceRepository import com.google.android.gms.maps.GoogleMap import com.google.android.gms.maps.model.TileProvider import com.google.android.gms.maps.model.UrlTileProvider @@ -55,6 +54,7 @@ import org.meshtastic.core.data.repository.RadioConfigRepository import org.meshtastic.core.datastore.UiPreferencesDataSource import org.meshtastic.core.prefs.map.GoogleMapsPrefs import org.meshtastic.core.prefs.map.MapPrefs +import org.meshtastic.core.service.ServiceRepository import timber.log.Timber import java.io.File import java.io.FileOutputStream diff --git a/app/src/main/java/com/geeksville/mesh/MeshServiceClient.kt b/app/src/main/java/com/geeksville/mesh/MeshServiceClient.kt index 88e68729c..e444b1c1a 100644 --- a/app/src/main/java/com/geeksville/mesh/MeshServiceClient.kt +++ b/app/src/main/java/com/geeksville/mesh/MeshServiceClient.kt @@ -27,10 +27,11 @@ import com.geeksville.mesh.android.BindFailedException import com.geeksville.mesh.android.ServiceClient import com.geeksville.mesh.concurrent.handledLaunch import com.geeksville.mesh.service.MeshService -import com.geeksville.mesh.service.ServiceRepository import com.geeksville.mesh.service.startService import dagger.hilt.android.scopes.ActivityScoped import kotlinx.coroutines.Job +import org.meshtastic.core.service.IMeshService +import org.meshtastic.core.service.ServiceRepository import javax.inject.Inject /** A Activity-lifecycle-aware [ServiceClient] that binds [MeshService] once the Activity is started. */ 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 6f0a3ef83..0c937263b 100644 --- a/app/src/main/java/com/geeksville/mesh/model/BTScanModel.kt +++ b/app/src/main/java/com/geeksville/mesh/model/BTScanModel.kt @@ -34,7 +34,6 @@ import com.geeksville.mesh.repository.radio.InterfaceId import com.geeksville.mesh.repository.radio.RadioInterfaceService import com.geeksville.mesh.repository.usb.UsbRepository import com.geeksville.mesh.service.MeshService -import com.geeksville.mesh.service.ServiceRepository import com.hoho.android.usbserial.driver.UsbSerialDriver import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Job @@ -52,6 +51,7 @@ import kotlinx.coroutines.launch import org.meshtastic.core.datastore.RecentAddressesDataSource import org.meshtastic.core.datastore.model.RecentAddress import org.meshtastic.core.model.util.anonymize +import org.meshtastic.core.service.ServiceRepository import org.meshtastic.core.strings.R import javax.inject.Inject diff --git a/app/src/main/java/com/geeksville/mesh/model/MetricsViewModel.kt b/app/src/main/java/com/geeksville/mesh/model/MetricsViewModel.kt index 21a39e21b..f31196933 100644 --- a/app/src/main/java/com/geeksville/mesh/model/MetricsViewModel.kt +++ b/app/src/main/java/com/geeksville/mesh/model/MetricsViewModel.kt @@ -35,8 +35,6 @@ import com.geeksville.mesh.Portnums import com.geeksville.mesh.Portnums.PortNum import com.geeksville.mesh.TelemetryProtos.Telemetry import com.geeksville.mesh.android.Logging -import com.geeksville.mesh.service.ServiceAction -import com.geeksville.mesh.service.ServiceRepository import com.geeksville.mesh.util.safeNumber import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow @@ -65,6 +63,8 @@ import org.meshtastic.core.model.DataPacket import org.meshtastic.core.model.DeviceHardware import org.meshtastic.core.navigation.NodesRoutes import org.meshtastic.core.prefs.map.MapPrefs +import org.meshtastic.core.service.ServiceAction +import org.meshtastic.core.service.ServiceRepository import org.meshtastic.core.strings.R import org.meshtastic.feature.map.model.CustomTileSource import java.io.BufferedWriter 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 b28a5af32..baec8f3ae 100644 --- a/app/src/main/java/com/geeksville/mesh/model/UIState.kt +++ b/app/src/main/java/com/geeksville/mesh/model/UIState.kt @@ -32,7 +32,6 @@ import com.geeksville.mesh.AppOnlyProtos import com.geeksville.mesh.ChannelProtos import com.geeksville.mesh.ChannelProtos.ChannelSettings import com.geeksville.mesh.ConfigProtos.Config -import com.geeksville.mesh.IMeshService import com.geeksville.mesh.LocalOnlyProtos.LocalConfig import com.geeksville.mesh.LocalOnlyProtos.LocalModuleConfig import com.geeksville.mesh.MeshProtos @@ -45,7 +44,6 @@ import com.geeksville.mesh.copy import com.geeksville.mesh.repository.radio.MeshActivity import com.geeksville.mesh.repository.radio.RadioInterfaceService import com.geeksville.mesh.service.MeshServiceNotifications -import com.geeksville.mesh.service.ServiceRepository import com.geeksville.mesh.util.safeNumber import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers @@ -75,6 +73,8 @@ import org.meshtastic.core.database.model.Node import org.meshtastic.core.datastore.UiPreferencesDataSource import org.meshtastic.core.model.DeviceHardware import org.meshtastic.core.model.util.toChannelSet +import org.meshtastic.core.service.IMeshService +import org.meshtastic.core.service.ServiceRepository import org.meshtastic.core.strings.R import javax.inject.Inject 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 34b622cd1..428f2e05e 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 @@ -30,7 +30,6 @@ import com.geeksville.mesh.android.Logging import com.geeksville.mesh.concurrent.handledLaunch import com.geeksville.mesh.repository.bluetooth.BluetoothRepository import com.geeksville.mesh.repository.network.NetworkRepository -import com.geeksville.mesh.service.ConnectionState import com.geeksville.mesh.util.ignoreException import com.geeksville.mesh.util.toRemoteExceptions import kotlinx.coroutines.CoroutineScope @@ -49,6 +48,7 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import org.meshtastic.core.model.util.anonymize import org.meshtastic.core.prefs.radio.RadioPrefs +import org.meshtastic.core.service.ConnectionState import javax.inject.Inject import javax.inject.Singleton 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 046a38fe1..b94d4afa5 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt @@ -34,7 +34,6 @@ import com.geeksville.mesh.ChannelProtos import com.geeksville.mesh.ConfigProtos import com.geeksville.mesh.CoroutineDispatchers import com.geeksville.mesh.DeviceUIProtos -import com.geeksville.mesh.IMeshService import com.geeksville.mesh.LocalOnlyProtos.LocalConfig import com.geeksville.mesh.LocalOnlyProtos.LocalModuleConfig import com.geeksville.mesh.MeshProtos @@ -103,6 +102,10 @@ import org.meshtastic.core.model.util.toOneLineString import org.meshtastic.core.model.util.toPIIString import org.meshtastic.core.prefs.mesh.MeshPrefs import org.meshtastic.core.prefs.ui.UiPrefs +import org.meshtastic.core.service.ConnectionState +import org.meshtastic.core.service.IMeshService +import org.meshtastic.core.service.ServiceAction +import org.meshtastic.core.service.ServiceRepository import org.meshtastic.core.strings.R import timber.log.Timber import java.util.Random @@ -111,18 +114,6 @@ import java.util.concurrent.ConcurrentHashMap import javax.inject.Inject import kotlin.math.absoluteValue -sealed class ServiceAction { - data class GetDeviceMetadata(val destNum: Int) : ServiceAction() - - data class Favorite(val node: Node) : ServiceAction() - - data class Ignore(val node: Node) : ServiceAction() - - data class Reaction(val emoji: String, val replyId: Int, val contactKey: String) : ServiceAction() - - data class AddSharedContact(val contact: AdminProtos.SharedContact) : ServiceAction() -} - /** * Handles all the communication with android apps. Also keeps an internal model of the network state. * 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 d36a6f9ce..94fcca450 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshServiceBroadcasts.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshServiceBroadcasts.kt @@ -24,6 +24,7 @@ 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.service.ServiceRepository import javax.inject.Inject import javax.inject.Singleton diff --git a/app/src/main/java/com/geeksville/mesh/service/MeshServiceConnectionStateHolder.kt b/app/src/main/java/com/geeksville/mesh/service/MeshServiceConnectionStateHolder.kt index 40c6f99de..3be8f7594 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshServiceConnectionStateHolder.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshServiceConnectionStateHolder.kt @@ -17,6 +17,7 @@ package com.geeksville.mesh.service +import org.meshtastic.core.service.ConnectionState import javax.inject.Inject import javax.inject.Singleton 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 f29b674b5..7b3338601 100644 --- a/app/src/main/java/com/geeksville/mesh/service/PacketHandler.kt +++ b/app/src/main/java/com/geeksville/mesh/service/PacketHandler.kt @@ -40,6 +40,7 @@ import org.meshtastic.core.model.DataPacket import org.meshtastic.core.model.MessageStatus import org.meshtastic.core.model.util.toOneLineString import org.meshtastic.core.model.util.toPIIString +import org.meshtastic.core.service.ConnectionState import java.util.UUID import java.util.concurrent.ConcurrentLinkedQueue import java.util.concurrent.TimeUnit diff --git a/app/src/main/java/com/geeksville/mesh/service/ReplyReceiver.kt b/app/src/main/java/com/geeksville/mesh/service/ReplyReceiver.kt index 7b8d4f1d5..257405509 100644 --- a/app/src/main/java/com/geeksville/mesh/service/ReplyReceiver.kt +++ b/app/src/main/java/com/geeksville/mesh/service/ReplyReceiver.kt @@ -22,6 +22,7 @@ import androidx.core.app.RemoteInput import dagger.hilt.android.AndroidEntryPoint import jakarta.inject.Inject import org.meshtastic.core.model.DataPacket +import org.meshtastic.core.service.ServiceRepository /** * A [BroadcastReceiver] that handles inline replies from notifications. 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 eb466ea77..92ccbf6b8 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/Main.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/Main.kt @@ -88,7 +88,6 @@ import com.geeksville.mesh.navigation.mapGraph import com.geeksville.mesh.navigation.nodesGraph import com.geeksville.mesh.navigation.settingsGraph import com.geeksville.mesh.repository.radio.MeshActivity -import com.geeksville.mesh.service.ConnectionState import com.geeksville.mesh.service.MeshService import com.geeksville.mesh.ui.common.components.MainAppBar import com.geeksville.mesh.ui.common.components.ScannedQrCodeDialog @@ -108,6 +107,7 @@ import org.meshtastic.core.navigation.NodeDetailRoutes import org.meshtastic.core.navigation.NodesRoutes import org.meshtastic.core.navigation.Route import org.meshtastic.core.navigation.SettingsRoutes +import org.meshtastic.core.service.ConnectionState import org.meshtastic.core.strings.R import org.meshtastic.core.ui.component.MultipleChoiceAlertDialog import org.meshtastic.core.ui.component.SimpleAlertDialog diff --git a/app/src/main/java/com/geeksville/mesh/ui/connections/ConnectionsScreen.kt b/app/src/main/java/com/geeksville/mesh/ui/connections/ConnectionsScreen.kt index 6f011199a..6d14e3b2b 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/connections/ConnectionsScreen.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/connections/ConnectionsScreen.kt @@ -64,7 +64,6 @@ import com.geeksville.mesh.model.BTScanModel import com.geeksville.mesh.model.DeviceListEntry import com.geeksville.mesh.navigation.ConfigRoute import com.geeksville.mesh.navigation.getNavRouteFrom -import com.geeksville.mesh.service.ConnectionState import com.geeksville.mesh.ui.common.components.MainAppBar import com.geeksville.mesh.ui.connections.components.BLEDevices import com.geeksville.mesh.ui.connections.components.ConnectionsSegmentedBar @@ -78,6 +77,7 @@ import com.google.accompanist.permissions.ExperimentalPermissionsApi import kotlinx.coroutines.delay import org.meshtastic.core.navigation.Route import org.meshtastic.core.navigation.SettingsRoutes +import org.meshtastic.core.service.ConnectionState import org.meshtastic.core.strings.R import org.meshtastic.core.ui.component.TitledCard diff --git a/app/src/main/java/com/geeksville/mesh/ui/connections/ConnectionsViewModel.kt b/app/src/main/java/com/geeksville/mesh/ui/connections/ConnectionsViewModel.kt index b1866546d..c748a68ed 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/connections/ConnectionsViewModel.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/connections/ConnectionsViewModel.kt @@ -21,7 +21,6 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.geeksville.mesh.LocalOnlyProtos.LocalConfig import com.geeksville.mesh.repository.bluetooth.BluetoothRepository -import com.geeksville.mesh.service.ServiceRepository import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted @@ -33,6 +32,7 @@ import org.meshtastic.core.data.repository.RadioConfigRepository import org.meshtastic.core.database.entity.MyNodeEntity import org.meshtastic.core.database.model.Node import org.meshtastic.core.prefs.ui.UiPrefs +import org.meshtastic.core.service.ServiceRepository import javax.inject.Inject @HiltViewModel diff --git a/app/src/main/java/com/geeksville/mesh/ui/connections/components/BLEDevices.kt b/app/src/main/java/com/geeksville/mesh/ui/connections/components/BLEDevices.kt index a7a921b69..38092eed9 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/connections/components/BLEDevices.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/connections/components/BLEDevices.kt @@ -48,10 +48,10 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.geeksville.mesh.model.BTScanModel import com.geeksville.mesh.model.DeviceListEntry -import com.geeksville.mesh.service.ConnectionState import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.MultiplePermissionsState import com.google.accompanist.permissions.rememberMultiplePermissionsState +import org.meshtastic.core.service.ConnectionState import org.meshtastic.core.strings.R import org.meshtastic.core.ui.component.TitledCard diff --git a/app/src/main/java/com/geeksville/mesh/ui/connections/components/NetworkDevices.kt b/app/src/main/java/com/geeksville/mesh/ui/connections/components/NetworkDevices.kt index 394613e72..6575038c2 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/connections/components/NetworkDevices.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/connections/components/NetworkDevices.kt @@ -54,9 +54,9 @@ import androidx.compose.ui.unit.dp import com.geeksville.mesh.model.BTScanModel import com.geeksville.mesh.model.DeviceListEntry import com.geeksville.mesh.repository.network.NetworkRepository -import com.geeksville.mesh.service.ConnectionState import com.geeksville.mesh.ui.connections.isIPAddress import kotlinx.coroutines.launch +import org.meshtastic.core.service.ConnectionState import org.meshtastic.core.strings.R import org.meshtastic.core.ui.component.TitledCard import org.meshtastic.core.ui.theme.AppTheme diff --git a/app/src/main/java/com/geeksville/mesh/ui/connections/components/TopLevelNavIcon.kt b/app/src/main/java/com/geeksville/mesh/ui/connections/components/TopLevelNavIcon.kt index a7f17cf5e..3d298a991 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/connections/components/TopLevelNavIcon.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/connections/components/TopLevelNavIcon.kt @@ -36,9 +36,9 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import com.geeksville.mesh.service.ConnectionState import com.geeksville.mesh.ui.TopLevelDestination import com.geeksville.mesh.ui.connections.DeviceType +import org.meshtastic.core.service.ConnectionState import org.meshtastic.core.ui.icon.Device import org.meshtastic.core.ui.icon.MeshtasticIcons import org.meshtastic.core.ui.icon.NoDevice diff --git a/app/src/main/java/com/geeksville/mesh/ui/connections/components/UsbDevices.kt b/app/src/main/java/com/geeksville/mesh/ui/connections/components/UsbDevices.kt index 2a1ba8d24..d95768cf2 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/connections/components/UsbDevices.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/connections/components/UsbDevices.kt @@ -25,7 +25,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.PreviewLightDark import com.geeksville.mesh.model.BTScanModel import com.geeksville.mesh.model.DeviceListEntry -import com.geeksville.mesh.service.ConnectionState +import org.meshtastic.core.service.ConnectionState import org.meshtastic.core.strings.R import org.meshtastic.core.ui.component.TitledCard import org.meshtastic.core.ui.theme.AppTheme diff --git a/app/src/main/java/com/geeksville/mesh/ui/contact/ContactsViewModel.kt b/app/src/main/java/com/geeksville/mesh/ui/contact/ContactsViewModel.kt index abc01c5eb..04f0aa330 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/contact/ContactsViewModel.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/contact/ContactsViewModel.kt @@ -22,7 +22,6 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.geeksville.mesh.channelSet import com.geeksville.mesh.model.Contact -import com.geeksville.mesh.service.ServiceRepository import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.Dispatchers @@ -37,6 +36,7 @@ import org.meshtastic.core.database.entity.Packet import org.meshtastic.core.model.DataPacket import org.meshtastic.core.model.util.getChannel import org.meshtastic.core.model.util.getShortDate +import org.meshtastic.core.service.ServiceRepository import org.meshtastic.core.strings.R import javax.inject.Inject import kotlin.collections.map diff --git a/app/src/main/java/com/geeksville/mesh/ui/map/BaseMapViewModel.kt b/app/src/main/java/com/geeksville/mesh/ui/map/BaseMapViewModel.kt index 79884ecc2..c7a37721b 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/map/BaseMapViewModel.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/map/BaseMapViewModel.kt @@ -22,7 +22,6 @@ import androidx.annotation.StringRes import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.geeksville.mesh.MeshProtos -import com.geeksville.mesh.service.ServiceRepository import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted @@ -38,6 +37,7 @@ import org.meshtastic.core.database.entity.Packet import org.meshtastic.core.database.model.Node import org.meshtastic.core.model.DataPacket import org.meshtastic.core.prefs.map.MapPrefs +import org.meshtastic.core.service.ServiceRepository import org.meshtastic.core.strings.R import timber.log.Timber import java.util.concurrent.TimeUnit diff --git a/app/src/main/java/com/geeksville/mesh/ui/message/MessageViewModel.kt b/app/src/main/java/com/geeksville/mesh/ui/message/MessageViewModel.kt index a039b18e2..2da36a195 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/message/MessageViewModel.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/message/MessageViewModel.kt @@ -22,8 +22,6 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.geeksville.mesh.channelSet import com.geeksville.mesh.service.MeshServiceNotifications -import com.geeksville.mesh.service.ServiceAction -import com.geeksville.mesh.service.ServiceRepository import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow @@ -43,6 +41,8 @@ import org.meshtastic.core.database.model.Message import org.meshtastic.core.database.model.Node import org.meshtastic.core.model.DataPacket import org.meshtastic.core.prefs.ui.UiPrefs +import org.meshtastic.core.service.ServiceAction +import org.meshtastic.core.service.ServiceRepository import timber.log.Timber import javax.inject.Inject diff --git a/app/src/main/java/com/geeksville/mesh/ui/node/NodeDetail.kt b/app/src/main/java/com/geeksville/mesh/ui/node/NodeDetail.kt index 864822a69..39e10bc54 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/node/NodeDetail.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/node/NodeDetail.kt @@ -133,7 +133,6 @@ import com.geeksville.mesh.ConfigProtos import com.geeksville.mesh.MeshProtos import com.geeksville.mesh.model.MetricsState import com.geeksville.mesh.model.MetricsViewModel -import com.geeksville.mesh.service.ServiceAction import com.geeksville.mesh.ui.common.components.MainAppBar import com.geeksville.mesh.ui.common.preview.NodePreviewParameterProvider import com.geeksville.mesh.ui.node.components.NodeActionDialogs @@ -162,6 +161,7 @@ import org.meshtastic.core.model.util.toSpeedString import org.meshtastic.core.navigation.NodeDetailRoutes import org.meshtastic.core.navigation.Route import org.meshtastic.core.navigation.SettingsRoutes +import org.meshtastic.core.service.ServiceAction import org.meshtastic.core.strings.R import org.meshtastic.core.ui.component.TitledCard import org.meshtastic.core.ui.theme.AppTheme diff --git a/app/src/main/java/com/geeksville/mesh/ui/node/NodeDetailViewModel.kt b/app/src/main/java/com/geeksville/mesh/ui/node/NodeDetailViewModel.kt index 420530427..26354439d 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/node/NodeDetailViewModel.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/node/NodeDetailViewModel.kt @@ -20,8 +20,6 @@ package com.geeksville.mesh.ui.node import android.os.RemoteException import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.geeksville.mesh.service.ServiceAction -import com.geeksville.mesh.service.ServiceRepository import com.geeksville.mesh.ui.node.components.NodeMenuAction import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers @@ -32,6 +30,8 @@ import kotlinx.coroutines.launch import org.meshtastic.core.data.repository.NodeRepository import org.meshtastic.core.database.model.Node import org.meshtastic.core.model.Position +import org.meshtastic.core.service.ServiceAction +import org.meshtastic.core.service.ServiceRepository import timber.log.Timber import javax.inject.Inject diff --git a/app/src/main/java/com/geeksville/mesh/ui/node/NodeScreen.kt b/app/src/main/java/com/geeksville/mesh/ui/node/NodeScreen.kt index 31278f486..a31b73de3 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/node/NodeScreen.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/node/NodeScreen.kt @@ -60,7 +60,6 @@ import androidx.compose.ui.unit.dp import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.geeksville.mesh.AdminProtos -import com.geeksville.mesh.service.ConnectionState import com.geeksville.mesh.ui.common.components.MainAppBar import com.geeksville.mesh.ui.node.components.NodeActionDialogs import com.geeksville.mesh.ui.node.components.NodeFilterTextField @@ -69,6 +68,7 @@ import com.geeksville.mesh.ui.sharing.AddContactFAB import com.geeksville.mesh.ui.sharing.supportsQrCodeSharing import org.meshtastic.core.database.model.Node import org.meshtastic.core.model.DeviceVersion +import org.meshtastic.core.service.ConnectionState import org.meshtastic.core.strings.R import org.meshtastic.core.ui.component.rememberTimeTickWithLifecycle import org.meshtastic.core.ui.theme.StatusColors.StatusRed diff --git a/app/src/main/java/com/geeksville/mesh/ui/node/NodesViewModel.kt b/app/src/main/java/com/geeksville/mesh/ui/node/NodesViewModel.kt index ff0b77fee..5e63de80a 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/node/NodesViewModel.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/node/NodesViewModel.kt @@ -21,8 +21,6 @@ import android.os.RemoteException import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.geeksville.mesh.AdminProtos -import com.geeksville.mesh.service.ServiceAction -import com.geeksville.mesh.service.ServiceRepository import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow @@ -40,6 +38,8 @@ import org.meshtastic.core.data.repository.RadioConfigRepository import org.meshtastic.core.database.model.Node import org.meshtastic.core.database.model.NodeSortOption import org.meshtastic.core.datastore.UiPreferencesDataSource +import org.meshtastic.core.service.ServiceAction +import org.meshtastic.core.service.ServiceRepository import timber.log.Timber import javax.inject.Inject diff --git a/app/src/main/java/com/geeksville/mesh/ui/settings/SettingsViewModel.kt b/app/src/main/java/com/geeksville/mesh/ui/settings/SettingsViewModel.kt index 5a79ba2e1..46cb9dcf0 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/settings/SettingsViewModel.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/settings/SettingsViewModel.kt @@ -21,12 +21,10 @@ import android.app.Application import android.net.Uri import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.geeksville.mesh.IMeshService import com.geeksville.mesh.LocalOnlyProtos.LocalConfig import com.geeksville.mesh.MeshProtos import com.geeksville.mesh.Portnums import com.geeksville.mesh.android.Logging -import com.geeksville.mesh.service.ServiceRepository import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow @@ -50,6 +48,8 @@ import org.meshtastic.core.datastore.UiPreferencesDataSource import org.meshtastic.core.model.Position import org.meshtastic.core.model.util.positionToMeter import org.meshtastic.core.prefs.ui.UiPrefs +import org.meshtastic.core.service.IMeshService +import org.meshtastic.core.service.ServiceRepository import java.io.BufferedWriter import java.io.FileNotFoundException import java.io.FileWriter diff --git a/app/src/main/java/com/geeksville/mesh/ui/settings/radio/CleanNodeDatabaseViewModel.kt b/app/src/main/java/com/geeksville/mesh/ui/settings/radio/CleanNodeDatabaseViewModel.kt index 36ea257a1..54adbef68 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/settings/radio/CleanNodeDatabaseViewModel.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/settings/radio/CleanNodeDatabaseViewModel.kt @@ -19,13 +19,13 @@ package com.geeksville.mesh.ui.settings.radio import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.geeksville.mesh.service.ServiceRepository import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch import org.meshtastic.core.data.repository.NodeRepository import org.meshtastic.core.database.entity.NodeEntity +import org.meshtastic.core.service.ServiceRepository import javax.inject.Inject import kotlin.time.Duration.Companion.days import kotlin.time.Duration.Companion.milliseconds diff --git a/app/src/main/java/com/geeksville/mesh/ui/settings/radio/RadioConfigViewModel.kt b/app/src/main/java/com/geeksville/mesh/ui/settings/radio/RadioConfigViewModel.kt index 243c56e35..f60fff56c 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/settings/radio/RadioConfigViewModel.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/settings/radio/RadioConfigViewModel.kt @@ -37,7 +37,6 @@ import com.geeksville.mesh.ChannelProtos import com.geeksville.mesh.ClientOnlyProtos.DeviceProfile import com.geeksville.mesh.ConfigProtos import com.geeksville.mesh.ConfigProtos.Config.SecurityConfig -import com.geeksville.mesh.IMeshService import com.geeksville.mesh.MeshProtos import com.geeksville.mesh.ModuleConfigProtos import com.geeksville.mesh.Portnums @@ -51,8 +50,6 @@ import com.geeksville.mesh.moduleConfig import com.geeksville.mesh.navigation.ConfigRoute import com.geeksville.mesh.navigation.ModuleRoute import com.geeksville.mesh.repository.location.LocationRepository -import com.geeksville.mesh.service.ConnectionState -import com.geeksville.mesh.service.ServiceRepository import com.geeksville.mesh.util.UiText import com.google.protobuf.MessageLite import dagger.hilt.android.lifecycle.HiltViewModel @@ -79,6 +76,9 @@ import org.meshtastic.core.model.util.toChannelSet import org.meshtastic.core.navigation.SettingsRoutes import org.meshtastic.core.prefs.analytics.AnalyticsPrefs import org.meshtastic.core.prefs.map.MapConsentPrefs +import org.meshtastic.core.service.ConnectionState +import org.meshtastic.core.service.IMeshService +import org.meshtastic.core.service.ServiceRepository import org.meshtastic.core.strings.R import java.io.FileOutputStream import javax.inject.Inject 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 28709d62e..29e3f553b 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 @@ -98,7 +98,6 @@ import com.geeksville.mesh.copy import com.geeksville.mesh.model.UIViewModel import com.geeksville.mesh.navigation.ConfigRoute import com.geeksville.mesh.navigation.getNavRouteFrom -import com.geeksville.mesh.service.ConnectionState import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel import com.geeksville.mesh.ui.settings.radio.components.ChannelSelection import com.geeksville.mesh.ui.settings.radio.components.PacketResponseStateDialog @@ -113,6 +112,7 @@ import org.meshtastic.core.model.util.getChannelUrl import org.meshtastic.core.model.util.qrCode import org.meshtastic.core.model.util.toChannelSet import org.meshtastic.core.navigation.Route +import org.meshtastic.core.service.ConnectionState import org.meshtastic.core.strings.R import org.meshtastic.core.ui.component.AdaptiveTwoPane import org.meshtastic.core.ui.component.PreferenceFooter diff --git a/core/service/build.gradle.kts b/core/service/build.gradle.kts new file mode 100644 index 000000000..df5d77452 --- /dev/null +++ b/core/service/build.gradle.kts @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2025 Meshtastic LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +plugins { + alias(libs.plugins.meshtastic.android.library) + alias(libs.plugins.meshtastic.hilt) +} + +android { + buildFeatures { aidl = true } + namespace = "org.meshtastic.core.service" +} + +dependencies { + implementation(projects.core.database) + implementation(projects.core.model) + implementation(projects.core.proto) + implementation(libs.kotlinx.coroutines.android) + implementation(libs.timber) +} diff --git a/app/src/main/aidl/com/geeksville/mesh/IMeshService.aidl b/core/service/src/main/aidl/org/meshtastic/core/service/IMeshService.aidl similarity index 99% rename from app/src/main/aidl/com/geeksville/mesh/IMeshService.aidl rename to core/service/src/main/aidl/org/meshtastic/core/service/IMeshService.aidl index bf118a33c..13ab9cef7 100644 --- a/app/src/main/aidl/com/geeksville/mesh/IMeshService.aidl +++ b/core/service/src/main/aidl/org/meshtastic/core/service/IMeshService.aidl @@ -1,5 +1,4 @@ -// com.geeksville.mesh.IMeshService.aidl -package com.geeksville.mesh; +package org.meshtastic.core.service; // Declare any non-default types here with import statements import org.meshtastic.core.model.DataPacket; diff --git a/app/src/main/java/com/geeksville/mesh/service/ConnectionState.kt b/core/service/src/main/kotlin/org/meshtastic/core/service/ConnectionState.kt similarity index 96% rename from app/src/main/java/com/geeksville/mesh/service/ConnectionState.kt rename to core/service/src/main/kotlin/org/meshtastic/core/service/ConnectionState.kt index 003f83806..394c760da 100644 --- a/app/src/main/java/com/geeksville/mesh/service/ConnectionState.kt +++ b/core/service/src/main/kotlin/org/meshtastic/core/service/ConnectionState.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.geeksville.mesh.service +package org.meshtastic.core.service enum class ConnectionState { /** We are disconnected from the device, and we should be trying to reconnect. */ diff --git a/core/service/src/main/kotlin/org/meshtastic/core/service/ServiceAction.kt b/core/service/src/main/kotlin/org/meshtastic/core/service/ServiceAction.kt new file mode 100644 index 000000000..07031a7f5 --- /dev/null +++ b/core/service/src/main/kotlin/org/meshtastic/core/service/ServiceAction.kt @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2025 Meshtastic LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.meshtastic.core.service + +import com.geeksville.mesh.AdminProtos +import org.meshtastic.core.database.model.Node + +sealed class ServiceAction { + data class GetDeviceMetadata(val destNum: Int) : ServiceAction() + + data class Favorite(val node: Node) : ServiceAction() + + data class Ignore(val node: Node) : ServiceAction() + + data class Reaction(val emoji: String, val replyId: Int, val contactKey: String) : ServiceAction() + + data class AddSharedContact(val contact: AdminProtos.SharedContact) : ServiceAction() +} diff --git a/app/src/main/java/com/geeksville/mesh/service/ServiceRepository.kt b/core/service/src/main/kotlin/org/meshtastic/core/service/ServiceRepository.kt similarity index 93% rename from app/src/main/java/com/geeksville/mesh/service/ServiceRepository.kt rename to core/service/src/main/kotlin/org/meshtastic/core/service/ServiceRepository.kt index 85a72e486..6c4ce6c36 100644 --- a/app/src/main/java/com/geeksville/mesh/service/ServiceRepository.kt +++ b/core/service/src/main/kotlin/org/meshtastic/core/service/ServiceRepository.kt @@ -15,25 +15,24 @@ * along with this program. If not, see . */ -package com.geeksville.mesh.service +package org.meshtastic.core.service -import com.geeksville.mesh.IMeshService import com.geeksville.mesh.MeshProtos import com.geeksville.mesh.MeshProtos.MeshPacket -import com.geeksville.mesh.android.Logging import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.receiveAsFlow +import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton /** Repository class for managing the [IMeshService] instance and connection state */ @Suppress("TooManyFunctions") @Singleton -class ServiceRepository @Inject constructor() : Logging { +class ServiceRepository @Inject constructor() { var meshService: IMeshService? = null private set @@ -64,7 +63,7 @@ class ServiceRepository @Inject constructor() : Logging { get() = _clientNotification fun setClientNotification(notification: MeshProtos.ClientNotification?) { - errormsg(notification?.message.orEmpty()) + Timber.e(notification?.message.orEmpty()) _clientNotification.value = notification } @@ -78,7 +77,7 @@ class ServiceRepository @Inject constructor() : Logging { get() = _errorMessage fun setErrorMessage(text: String) { - errormsg(text) + Timber.e(text) _errorMessage.value = text } diff --git a/mesh_service_example/README.md b/mesh_service_example/README.md index 58da40984..3276c769c 100644 --- a/mesh_service_example/README.md +++ b/mesh_service_example/README.md @@ -4,7 +4,7 @@ This module provides an example implementation of an app that uses the [AIDL](ht ## Overview -The [AIDL](../app/src/main/aidl/com/geeksville/mesh/IMeshService.aidl) is defined in the main app module and is used to interact with the mesh network. +The [AIDL](../core/service/src/main/aidl/org/meshtastic/core/service/IMeshService.aidl) is defined in the main app module and is used to interact with the mesh network. `mesh_service_example` demonstrates how to build and integrate a custom mesh service within the Meshtastic ecosystem. It is intended as a reference for developers who want to extend or customize mesh-related functionality. diff --git a/mesh_service_example/build.gradle.kts b/mesh_service_example/build.gradle.kts index 1642db333..11179b1dc 100644 --- a/mesh_service_example/build.gradle.kts +++ b/mesh_service_example/build.gradle.kts @@ -44,7 +44,6 @@ plugins { android { namespace = "com.meshtastic.android.meshserviceexample" - buildFeatures { aidl = true } defaultConfig { // Force this app to use the Google variant of any modules it's using that apply AndroidLibraryConventionPlugin missingDimensionStrategy(FlavorDimension.marketplace.name, MeshtasticFlavor.google.name) @@ -52,7 +51,9 @@ android { } dependencies { + implementation(projects.core.model) implementation(projects.core.proto) + implementation(projects.core.service) implementation(libs.appcompat) implementation(libs.material) diff --git a/mesh_service_example/src/main/aidl/com/geeksville/mesh/IMeshService.aidl b/mesh_service_example/src/main/aidl/com/geeksville/mesh/IMeshService.aidl deleted file mode 100644 index a73a9a14c..000000000 --- a/mesh_service_example/src/main/aidl/com/geeksville/mesh/IMeshService.aidl +++ /dev/null @@ -1,171 +0,0 @@ -// com.geeksville.mesh.IMeshService.aidl -package com.geeksville.mesh; - -// Declare any non-default types here with import statements -parcelable DataPacket; -parcelable NodeInfo; -parcelable MeshUser; -parcelable Position; -parcelable MyNodeInfo; - -/** -This is the public android API for talking to meshtastic radios. - -To connect to meshtastic you should bind to it per https://developer.android.com/guide/components/bound-services - -The intent you use to reach the service should look like this: - - val intent = Intent().apply { - setClassName( - "com.geeksville.mesh", - "com.geeksville.mesh.service.MeshService" - ) - } - -In Android 11+ you *may* need to add the following to the client app's manifest to allow binding of the mesh service: - - - -For additional information, see https://developer.android.com/guide/topics/manifest/queries-element - - -Once you have bound to the service you should register your broadcast receivers per https://developer.android.com/guide/components/broadcasts#context-registered-receivers - - // com.geeksville.mesh.x broadcast intents, where x is: - - // RECEIVED. - will **only** deliver packets for the specified port number. If a wellknown portnums.proto name for portnum is known it will be used - // (i.e. com.geeksville.mesh.RECEIVED.TEXT_MESSAGE_APP) else the numeric portnum will be included as a base 10 integer (com.geeksville.mesh.RECEIVED.4403 etc...) - - // NODE_CHANGE for new IDs appearing or disappearing - // CONNECTION_CHANGED for losing/gaining connection to the packet radio - // MESSAGE_STATUS_CHANGED for any message status changes (for sent messages only, payload will contain a message ID and a MessageStatus) - -Note - these calls might throw RemoteException to indicate mesh error states -*/ -interface IMeshService { - /// Tell the service where to send its broadcasts of received packets - /// This call is only required for manifest declared receivers. If your receiver is context-registered - /// you don't need this. - void subscribeReceiver(String packageName, String receiverName); - - /** - * Set the user info for this node - */ - void setOwner(in MeshUser user); - - void setRemoteOwner(in int requestId, in byte []payload); - void getRemoteOwner(in int requestId, in int destNum); - - /// Return my unique user ID string - String getMyId(); - - /// Return a unique packet ID - int getPacketId(); - - /* - Send a packet to a specified node name - - typ is defined in mesh.proto Data.Type. For now juse use 0 to mean opaque bytes. - - destId can be null to indicate "broadcast message" - - messageStatus and id of the provided message will be updated by this routine to indicate - message send status and the ID that can be used to locate the message in the future - */ - void send(inout DataPacket packet); - - /** - Get the IDs of everyone on the mesh. You should also subscribe for NODE_CHANGE broadcasts. - */ - List getNodes(); - - /// This method is only intended for use in our GUI, so the user can set radio options - /// It returns a DeviceConfig protobuf. - byte []getConfig(); - /// It sets a Config protobuf via admin packet - void setConfig(in byte []payload); - - /// Set and get a Config protobuf via admin packet - void setRemoteConfig(in int requestId, in int destNum, in byte []payload); - void getRemoteConfig(in int requestId, in int destNum, in int configTypeValue); - - /// Set and get a ModuleConfig protobuf via admin packet - void setModuleConfig(in int requestId, in int destNum, in byte []payload); - void getModuleConfig(in int requestId, in int destNum, in int moduleConfigTypeValue); - - /// Set and get the Ext Notification Ringtone string via admin packet - void setRingtone(in int destNum, in String ringtone); - void getRingtone(in int requestId, in int destNum); - - /// Set and get the Canned Message Messages string via admin packet - void setCannedMessages(in int destNum, in String messages); - void getCannedMessages(in int requestId, in int destNum); - - /// This method is only intended for use in our GUI, so the user can set radio options - /// It sets a Channel protobuf via admin packet - void setChannel(in byte []payload); - - /// Set and get a Channel protobuf via admin packet - void setRemoteChannel(in int requestId, in int destNum, in byte []payload); - void getRemoteChannel(in int requestId, in int destNum, in int channelIndex); - - /// Send beginEditSettings admin packet to nodeNum - void beginEditSettings(); - - /// Send commitEditSettings admin packet to nodeNum - void commitEditSettings(); - - /// delete a specific nodeNum from nodeDB - void removeByNodenum(in int requestID, in int nodeNum); - - /// Send position packet with wantResponse to nodeNum - void requestPosition(in int destNum, in Position position); - - /// Send setFixedPosition admin packet (or removeFixedPosition if Position is empty) - void setFixedPosition(in int destNum, in Position position); - - /// Send traceroute packet with wantResponse to nodeNum - void requestTraceroute(in int requestId, in int destNum); - - /// Send Shutdown admin packet to nodeNum - void requestShutdown(in int requestId, in int destNum); - - /// Send Reboot admin packet to nodeNum - void requestReboot(in int requestId, in int destNum); - - /// Send FactoryReset admin packet to nodeNum - void requestFactoryReset(in int requestId, in int destNum); - - /// Send NodedbReset admin packet to nodeNum - void requestNodedbReset(in int requestId, in int destNum); - - /// Returns a ChannelSet protobuf - byte []getChannelSet(); - - /** - Is the packet radio currently connected to the phone? Returns a ConnectionState string. - */ - String connectionState(); - - /// If a macaddress we will try to talk to our device, if null we will be idle. - /// Any current connection will be dropped (even if the device address is the same) before reconnecting. - /// Users should not call this directly, only used internally by the MeshUtil activity - /// Returns true if the device address actually changed, or false if no change was needed - boolean setDeviceAddress(String deviceAddr); - - /// Get basic device hardware info about our connected radio. Will never return NULL. Will return NULL - /// if no my node info is available (i.e. it will not throw an exception) - MyNodeInfo getMyNodeInfo(); - - /// Start updating the radios firmware - void startFirmwareUpdate(); - - /// Return a number 0-100 for firmware update progress. -1 for completed and success, -2 for failure - int getUpdateStatus(); - - /// Start providing location (from phone GPS) to mesh - void startProvideLocation(); - - /// Stop providing location (from phone GPS) to mesh - void stopProvideLocation(); -} diff --git a/mesh_service_example/src/main/kotlin/com/geeksville/mesh/DataPacket.kt b/mesh_service_example/src/main/kotlin/com/geeksville/mesh/DataPacket.kt deleted file mode 100644 index 9a470cc17..000000000 --- a/mesh_service_example/src/main/kotlin/com/geeksville/mesh/DataPacket.kt +++ /dev/null @@ -1,209 +0,0 @@ -/* - * Copyright (c) 2025 Meshtastic LLC - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.geeksville.mesh - -import android.os.Parcel -import android.os.Parcelable -import kotlinx.parcelize.Parcelize -import kotlinx.serialization.Serializable - -/** Generic [Parcel.readParcelable] Android 13 compatibility extension. */ -private inline fun Parcel.readParcelableCompat(loader: ClassLoader?): T? = - if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.TIRAMISU) { - @Suppress("DEPRECATION") - readParcelable(loader) - } else { - readParcelable(loader, T::class.java) - } - -@Parcelize -enum class MessageStatus : Parcelable { - UNKNOWN, // Not set for this message - RECEIVED, // Came in from the mesh - QUEUED, // Waiting to send to the mesh as soon as we connect to the device - ENROUTE, // Delivered to the radio, but no ACK or NAK received - DELIVERED, // We received an ack - ERROR, // We received back a nak, message not delivered -} - -/** A parcelable version of the protobuf MeshPacket + Data subpacket. */ -@Serializable -data class DataPacket( - var to: String? = ID_BROADCAST, // a nodeID string, or ID_BROADCAST for broadcast - val bytes: ByteArray?, - // A port number for this packet (formerly called DataType, see portnums.proto for new usage instructions) - val dataType: Int, - var from: String? = ID_LOCAL, // a nodeID string, or ID_LOCAL for localhost - var time: Long = System.currentTimeMillis(), // msecs since 1970 - var id: Int = 0, // 0 means unassigned - var status: MessageStatus? = MessageStatus.UNKNOWN, - var hopLimit: Int = 0, - var channel: Int = 0, // channel index - var wantAck: Boolean = true, // If true, the receiver should send an ack back -) : Parcelable { - - /** If there was an error with this message, this string describes what was wrong. */ - var errorMessage: String? = null - - /** Syntactic sugar to make it easy to create text messages */ - constructor( - to: String?, - channel: Int, - text: String, - ) : this( - to = to, - bytes = text.encodeToByteArray(), - dataType = Portnums.PortNum.TEXT_MESSAGE_APP_VALUE, - channel = channel, - ) - - /** If this is a text message, return the string, otherwise null */ - val text: String? - get() = - if (dataType == Portnums.PortNum.TEXT_MESSAGE_APP_VALUE) { - bytes?.decodeToString() - } else { - null - } - - val alert: String? - get() = - if (dataType == Portnums.PortNum.ALERT_APP_VALUE) { - bytes?.decodeToString() - } else { - null - } - - constructor( - to: String?, - channel: Int, - waypoint: MeshProtos.Waypoint, - ) : this(to = to, bytes = waypoint.toByteArray(), dataType = Portnums.PortNum.WAYPOINT_APP_VALUE, channel = channel) - - val waypoint: MeshProtos.Waypoint? - get() = - if (dataType == Portnums.PortNum.WAYPOINT_APP_VALUE) { - MeshProtos.Waypoint.parseFrom(bytes) - } else { - null - } - - // Autogenerated comparision, because we have a byte array - - constructor( - parcel: Parcel, - ) : this( - parcel.readString(), - parcel.createByteArray(), - parcel.readInt(), - parcel.readString(), - parcel.readLong(), - parcel.readInt(), - parcel.readParcelableCompat(MessageStatus::class.java.classLoader), - parcel.readInt(), - parcel.readInt(), - parcel.readInt() == 1, - ) - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as DataPacket - - if (from != other.from) return false - if (to != other.to) return false - if (channel != other.channel) return false - if (time != other.time) return false - if (id != other.id) return false - if (dataType != other.dataType) return false - if (!bytes!!.contentEquals(other.bytes!!)) return false - if (status != other.status) return false - if (hopLimit != other.hopLimit) return false - if (wantAck != other.wantAck) return false - - return true - } - - override fun hashCode(): Int { - var result = from.hashCode() - result = 31 * result + to.hashCode() - result = 31 * result + time.hashCode() - result = 31 * result + id - result = 31 * result + dataType - result = 31 * result + bytes!!.contentHashCode() - result = 31 * result + status.hashCode() - result = 31 * result + hopLimit - result = 31 * result + channel - result = 31 * result + wantAck.hashCode() - return result - } - - override fun writeToParcel(parcel: Parcel, flags: Int) { - parcel.writeString(to) - parcel.writeByteArray(bytes) - parcel.writeInt(dataType) - parcel.writeString(from) - parcel.writeLong(time) - parcel.writeInt(id) - parcel.writeParcelable(status, flags) - parcel.writeInt(hopLimit) - parcel.writeInt(channel) - parcel.writeInt(if (wantAck) 1 else 0) - } - - override fun describeContents(): Int = 0 - - // Update our object from our parcel (used for inout parameters - fun readFromParcel(parcel: Parcel) { - to = parcel.readString() - parcel.createByteArray() - parcel.readInt() - from = parcel.readString() - time = parcel.readLong() - id = parcel.readInt() - status = parcel.readParcelableCompat(MessageStatus::class.java.classLoader) - hopLimit = parcel.readInt() - channel = parcel.readInt() - wantAck = parcel.readInt() == 1 - } - - companion object CREATOR : Parcelable.Creator { - // Special node IDs that can be used for sending messages - - /** the Node ID for broadcast destinations */ - const val ID_BROADCAST = "^all" - - /** The Node ID for the local node - used for from when sender doesn't know our local node ID */ - const val ID_LOCAL = "^local" - - // special broadcast address - const val NODENUM_BROADCAST = (0xffffffff).toInt() - - // Public-key cryptography (PKC) channel index - const val PKC_CHANNEL_INDEX = 8 - - fun nodeNumToDefaultId(n: Int): String = "!%08x".format(n) - - fun idToDefaultNodeNum(id: String?): Int? = runCatching { id?.toLong(16)?.toInt() }.getOrNull() - - override fun createFromParcel(parcel: Parcel): DataPacket = DataPacket(parcel) - - override fun newArray(size: Int): Array = arrayOfNulls(size) - } -} diff --git a/mesh_service_example/src/main/kotlin/com/geeksville/mesh/MyNodeInfo.kt b/mesh_service_example/src/main/kotlin/com/geeksville/mesh/MyNodeInfo.kt deleted file mode 100644 index fa94cb21a..000000000 --- a/mesh_service_example/src/main/kotlin/com/geeksville/mesh/MyNodeInfo.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2025 Meshtastic LLC - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.geeksville.mesh - -import android.os.Parcelable -import kotlinx.parcelize.Parcelize - -// MyNodeInfo sent via special protobuf from radio -@Parcelize -data class MyNodeInfo( - val myNodeNum: Int, - val hasGPS: Boolean, - val model: String?, - val firmwareVersion: String?, - val couldUpdate: Boolean, // this application contains a software load we _could_ install if you want - val shouldUpdate: Boolean, // this device has old firmware - val currentPacketId: Long, - val messageTimeoutMsec: Int, - val minAppVersion: Int, - val maxChannels: Int, - val hasWifi: Boolean, - val channelUtilization: Float, - val airUtilTx: Float, - val deviceId: String?, -) : Parcelable { - /** A human readable description of the software/hardware version */ - val firmwareString: String - get() = "$model $firmwareVersion" -} diff --git a/mesh_service_example/src/main/kotlin/com/geeksville/mesh/NodeInfo.kt b/mesh_service_example/src/main/kotlin/com/geeksville/mesh/NodeInfo.kt deleted file mode 100644 index d339a7cfa..000000000 --- a/mesh_service_example/src/main/kotlin/com/geeksville/mesh/NodeInfo.kt +++ /dev/null @@ -1,232 +0,0 @@ -/* - * Copyright (c) 2025 Meshtastic LLC - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.geeksville.mesh - -import android.graphics.Color -import android.os.Parcelable -import com.geeksville.mesh.util.anonymize -import com.geeksville.mesh.util.bearing -import com.geeksville.mesh.util.latLongToMeter -import kotlinx.parcelize.Parcelize - -// -// model objects that directly map to the corresponding protobufs -// - -@Parcelize -data class MeshUser( - val id: String, - val longName: String, - val shortName: String, - val hwModel: MeshProtos.HardwareModel, - val isLicensed: Boolean = false, - val role: Int = 0, -) : Parcelable { - - override fun toString(): String = "MeshUser(id=${id.anonymize}, " + - "longName=${longName.anonymize}, " + - "shortName=${shortName.anonymize}, " + - "hwModel=$hwModelString, " + - "isLicensed=$isLicensed, " + - "role=$role)" - - /** Create our model object from a protobuf. */ - constructor(p: MeshProtos.User) : this(p.id, p.longName, p.shortName, p.hwModel, p.isLicensed, p.roleValue) - - /** - * a string version of the hardware model, converted into pretty lowercase and changing _ to -, and p to dot or null - * if unset - */ - val hwModelString: String? - get() = - if (hwModel == MeshProtos.HardwareModel.UNSET) { - null - } else { - hwModel.name.replace('_', '-').replace('p', '.').lowercase() - } -} - -@Parcelize -data class Position( - val latitude: Double, - val longitude: Double, - val altitude: Int, - val time: Int = currentTime(), // default to current time in secs (NOT MILLISECONDS!) - val satellitesInView: Int = 0, - val groundSpeed: Int = 0, - val groundTrack: Int = 0, // "heading" - val precisionBits: Int = 0, -) : Parcelable { - - companion object { - // / Convert to a double representation of degrees - fun degD(i: Int) = i * 1e-7 - - fun degI(d: Double) = (d * 1e7).toInt() - - fun currentTime() = (System.currentTimeMillis() / 1000).toInt() - } - - /** - * Create our model object from a protobuf. If time is unspecified in the protobuf, the provided default time will - * be used. - */ - constructor( - position: MeshProtos.Position, - defaultTime: Int = currentTime(), - ) : this( - // We prefer the int version of lat/lon but if not available use the depreciated legacy version - degD(position.latitudeI), - degD(position.longitudeI), - position.altitude, - if (position.time != 0) position.time else defaultTime, - position.satsInView, - position.groundSpeed, - position.groundTrack, - position.precisionBits, - ) - - // / @return distance in meters to some other node (or null if unknown) - fun distance(o: Position) = latLongToMeter(latitude, longitude, o.latitude, o.longitude) - - // / @return bearing to the other position in degrees - fun bearing(o: Position) = bearing(latitude, longitude, o.latitude, o.longitude) - - // If GPS gives a crap position don't crash our app - fun isValid(): Boolean = latitude != 0.0 && - longitude != 0.0 && - (latitude >= -90 && latitude <= 90.0) && - (longitude >= -180 && longitude <= 180) - - override fun toString(): String = - "Position(lat=${latitude.anonymize}, lon=${longitude.anonymize}, alt=${altitude.anonymize}, time=$time)" -} - -@Parcelize -data class DeviceMetrics( - val time: Int = currentTime(), // default to current time in secs (NOT MILLISECONDS!) - val batteryLevel: Int = 0, - val voltage: Float, - val channelUtilization: Float, - val airUtilTx: Float, - val uptimeSeconds: Int, -) : Parcelable { - companion object { - fun currentTime() = (System.currentTimeMillis() / 1000).toInt() - } - - /** Create our model object from a protobuf. */ - constructor( - p: TelemetryProtos.DeviceMetrics, - telemetryTime: Int = currentTime(), - ) : this(telemetryTime, p.batteryLevel, p.voltage, p.channelUtilization, p.airUtilTx, p.uptimeSeconds) -} - -@Parcelize -data class EnvironmentMetrics( - val time: Int = currentTime(), // default to current time in secs (NOT MILLISECONDS!) - val temperature: Float, - val relativeHumidity: Float, - val barometricPressure: Float, - val gasResistance: Float, - val voltage: Float, - val current: Float, - val iaq: Int, -) : Parcelable { - companion object { - fun currentTime() = (System.currentTimeMillis() / 1000).toInt() - } -} - -@Parcelize -data class NodeInfo( - val num: Int, // This is immutable, and used as a key - var user: MeshUser? = null, - var position: Position? = null, - var snr: Float = Float.MAX_VALUE, - var rssi: Int = Int.MAX_VALUE, - var lastHeard: Int = 0, // the last time we've seen this node in secs since 1970 - var deviceMetrics: DeviceMetrics? = null, - var channel: Int = 0, - var environmentMetrics: EnvironmentMetrics? = null, - var hopsAway: Int = 0, -) : Parcelable { - - val colors: Pair - get() { // returns foreground and background @ColorInt for each 'num' - val r = (num and 0xFF0000) shr 16 - val g = (num and 0x00FF00) shr 8 - val b = num and 0x0000FF - val brightness = ((r * 0.299) + (g * 0.587) + (b * 0.114)) / 255 - return (if (brightness > 0.5) Color.BLACK else Color.WHITE) to Color.rgb(r, g, b) - } - - val batteryLevel - get() = deviceMetrics?.batteryLevel - - val voltage - get() = deviceMetrics?.voltage - - val batteryStr - get() = if (batteryLevel in 1..100) String.format("%d%%", batteryLevel) else "" - - /** true if the device was heard from recently */ - @Suppress("MagicNumber") - val isOnline: Boolean - get() { - val now = System.currentTimeMillis() / 1000 - val timeout = 15 * 60 - return (now - lastHeard <= timeout) - } - - // / return the position if it is valid, else null - val validPosition: Position? - get() { - return position?.takeIf { it.isValid() } - } - - // / @return distance in meters to some other node (or null if unknown) - fun distance(o: NodeInfo?): Int? { - val p = validPosition - val op = o?.validPosition - return if (p != null && op != null) p.distance(op).toInt() else null - } - - // / @return bearing to the other position in degrees - fun bearing(o: NodeInfo?): Int? { - val p = validPosition - val op = o?.validPosition - return if (p != null && op != null) p.bearing(op).toInt() else null - } - - // / @return a nice human readable string for the distance, or null for unknown - fun distanceStr(o: NodeInfo?, prefUnits: Int = 0) = distance(o)?.let { dist -> - when { - dist == 0 -> null // same point - prefUnits == ConfigProtos.Config.DisplayConfig.DisplayUnits.METRIC_VALUE && dist < 1000 -> - "%.0f m".format(dist.toDouble()) - prefUnits == ConfigProtos.Config.DisplayConfig.DisplayUnits.METRIC_VALUE && dist >= 1000 -> - "%.1f km".format(dist / 1000.0) - prefUnits == ConfigProtos.Config.DisplayConfig.DisplayUnits.IMPERIAL_VALUE && dist < 1609 -> - "%.0f ft".format(dist.toDouble() * 3.281) - prefUnits == ConfigProtos.Config.DisplayConfig.DisplayUnits.IMPERIAL_VALUE && dist >= 1609 -> - "%.1f mi".format(dist / 1609.34) - else -> null - } - } -} diff --git a/mesh_service_example/src/main/kotlin/com/geeksville/mesh/util/Extensions.kt b/mesh_service_example/src/main/kotlin/com/geeksville/mesh/util/Extensions.kt deleted file mode 100644 index 03fe16512..000000000 --- a/mesh_service_example/src/main/kotlin/com/geeksville/mesh/util/Extensions.kt +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (c) 2025 Meshtastic LLC - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.geeksville.mesh.util - -import android.widget.EditText -import com.geeksville.mesh.ConfigProtos - -/** - * When printing strings to logs sometimes we want to print useful debugging information about users or positions. But - * we don't want to leak things like usernames or locations. So this function if given a string, will return a string - * which is a maximum of three characters long, taken from the tail of the string. Which should effectively hide real - * usernames and locations, but still let us see if values were zero, empty or different. - */ -val Any?.anonymize: String - get() = this.anonymize() - -/** A version of anonymize that allows passing in a custom minimum length */ -fun Any?.anonymize(maxLen: Int = 3) = if (this != null) ("..." + this.toString().takeLast(maxLen)) else "null" - -// A toString that makes sure all newlines are removed (for nice logging). -fun Any.toOneLineString() = this.toString().replace('\n', ' ') - -fun ConfigProtos.Config.toOneLineString(): String { - val redactedFields = """(wifi_psk:|public_key:|private_key:|admin_key:)\s*".*""" - return this.toString() - .replace(redactedFields.toRegex()) { "${it.groupValues[1]} \"[REDACTED]\"" } - .replace('\n', ' ') -} - -// Return a one line string version of an object (but if a release build, just say 'might be PII) -fun Any.toPIIString() = this.toOneLineString() - -fun ByteArray.toHexString() = joinToString("") { "%02x".format(it) } - -fun formatAgo(lastSeenUnix: Int, currentTimeMillis: Long = System.currentTimeMillis()): String { - val currentTime = (currentTimeMillis / 1000).toInt() - val diffMin = (currentTime - lastSeenUnix) / 60 - return when { - diffMin < 1 -> "now" - diffMin < 60 -> diffMin.toString() + " min" - diffMin < 2880 -> (diffMin / 60).toString() + " h" - diffMin < 1440000 -> (diffMin / (60 * 24)).toString() + " d" - else -> "?" - } -} - -// Allows usage like email.onEditorAction(EditorInfo.IME_ACTION_NEXT, { confirm() }) -fun EditText.onEditorAction(actionId: Int, func: () -> Unit) { - setOnEditorActionListener { _, receivedActionId, _ -> - if (actionId == receivedActionId) { - func() - } - true - } -} diff --git a/mesh_service_example/src/main/kotlin/com/geeksville/mesh/util/LocationUtils.kt b/mesh_service_example/src/main/kotlin/com/geeksville/mesh/util/LocationUtils.kt deleted file mode 100644 index 3caf2dbb5..000000000 --- a/mesh_service_example/src/main/kotlin/com/geeksville/mesh/util/LocationUtils.kt +++ /dev/null @@ -1,289 +0,0 @@ -/* - * Copyright (c) 2025 Meshtastic LLC - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -@file:Suppress("TooManyFunctions") - -package com.geeksville.mesh.util - -import com.geeksville.mesh.MeshProtos -import com.geeksville.mesh.Position -import mil.nga.grid.features.Point -import mil.nga.mgrs.MGRS -import mil.nga.mgrs.utm.UTM -import org.osmdroid.util.BoundingBox -import org.osmdroid.util.GeoPoint -import java.util.Locale -import kotlin.math.PI -import kotlin.math.abs -import kotlin.math.acos -import kotlin.math.atan2 -import kotlin.math.cos -import kotlin.math.log2 -import kotlin.math.pow -import kotlin.math.sin - -/** - * **************************************************************************** - * Revive some of my old Gaggle source code... - * - * GNU Public License, version 2 All other distribution of Gaggle must conform to the terms of the GNU Public License, - * version 2. The full text of this license is included in the Gaggle source, see assets/manual/gpl-2.0.txt. - * **************************************************************************** - */ -object GPSFormat { - fun dec(p: Position): String = - String.format(Locale.getDefault(), "%.5f %.5f", p.latitude, p.longitude).replace(",", ".") - - @Suppress("MagicNumber") - fun dms(p: Position): String { - val lat = degreesToDMS(p.latitude, true) - val lon = degreesToDMS(p.longitude, false) - fun string(a: Array) = String.format(Locale.getDefault(), "%s°%s'%.5s\"%s", a[0], a[1], a[2], a[3]) - return string(lat) + " " + string(lon) - } - - fun utm(p: Position): String { - val utm = UTM.from(Point.point(p.longitude, p.latitude)) - return String.format( - Locale.getDefault(), - "%s%s %.6s %.7s", - utm.zone, - utm.toMGRS().band, - utm.easting, - utm.northing, - ) - } - - fun mgrs(p: Position): String { - val mgrs = MGRS.from(Point.point(p.longitude, p.latitude)) - return String.format( - Locale.getDefault(), - "%s%s %s%s %05d %05d", - mgrs.zone, - mgrs.band, - mgrs.column, - mgrs.row, - mgrs.easting, - mgrs.northing, - ) - } - - fun toDEC(latitude: Double, longitude: Double): String = "%.5f %.5f".format(latitude, longitude).replace(",", ".") - - @Suppress("MagicNumber") - fun toDMS(latitude: Double, longitude: Double): String { - val lat = degreesToDMS(latitude, true) - val lon = degreesToDMS(longitude, false) - fun string(a: Array) = "%s°%s'%.5s\"%s".format(Locale.getDefault(), a[0], a[1], a[2], a[3]) - return string(lat) + " " + string(lon) - } - - fun toUTM(latitude: Double, longitude: Double): String { - val utm = UTM.from(Point.point(longitude, latitude)) - return "%s%s %.6s %.7s".format(Locale.getDefault(), utm.zone, utm.toMGRS().band, utm.easting, utm.northing) - } - - fun toMGRS(latitude: Double, longitude: Double): String { - val mgrs = MGRS.from(Point.point(longitude, latitude)) - return "%s%s %s%s %05d %05d" - .format(Locale.getDefault(), mgrs.zone, mgrs.band, mgrs.column, mgrs.row, mgrs.easting, mgrs.northing) - } -} - -/** - * Format as degrees, minutes, secs - * - * @param degIn - * @param isLatitude - * @return a string like 120deg - */ -@Suppress("MagicNumber") -fun degreesToDMS(degIn: Double, isLatitude: Boolean): Array { - var degIn = degIn - val isPos = degIn >= 0 - val dirLetter = - if (isLatitude) if (isPos) 'N' else 'S' - else if (isPos) { - 'E' - } else { - 'W' - } - degIn = abs(degIn) - val degOut = degIn.toInt() - val minutes = 60 * (degIn - degOut) - val minwhole = minutes.toInt() - val seconds = (minutes - minwhole) * 60 - return arrayOf(degOut.toString(), minwhole.toString(), seconds.toString(), dirLetter.toString()) -} - -@Suppress("MagicNumber") -fun degreesToDM(degIn: Double, isLatitude: Boolean): Array { - var degIn = degIn - val isPos = degIn >= 0 - val dirLetter = - if (isLatitude) if (isPos) 'N' else 'S' - else if (isPos) { - 'E' - } else { - 'W' - } - degIn = abs(degIn) - val degOut = degIn.toInt() - val minutes = 60 * (degIn - degOut) - val seconds = 0 - return arrayOf(degOut.toString(), minutes.toString(), seconds.toString(), dirLetter.toString()) -} - -fun degreesToD(degIn: Double, isLatitude: Boolean): Array { - var degIn = degIn - val isPos = degIn >= 0 - val dirLetter = - if (isLatitude) if (isPos) 'N' else 'S' - else if (isPos) { - 'E' - } else { - 'W' - } - degIn = abs(degIn) - val degOut = degIn - val minutes = 0 - val seconds = 0 - return arrayOf(degOut.toString(), minutes.toString(), seconds.toString(), dirLetter.toString()) -} - -/** - * A not super efficent mapping from a starting lat/long + a distance at a certain direction - * - * @param lat - * @param longitude - * @param distMeters - * @param theta in radians, 0 == north - * @return an array with lat and long - */ -@Suppress("MagicNumber") -fun addDistance(lat: Double, longitude: Double, distMeters: Double, theta: Double): DoubleArray { - val dx = distMeters * sin(theta) // theta measured clockwise - // from due north - val dy = distMeters * cos(theta) // dx, dy same units as R - val dLong = dx / (111320 * cos(lat)) // dx, dy in meters - val dLat = dy / 110540 // result in degrees long/lat - return doubleArrayOf(lat + dLat, longitude + dLong) -} - -/** @return distance in meters along the surface of the earth (ish) */ -@Suppress("MagicNumber") -fun latLongToMeter(latA: Double, lngA: Double, latB: Double, lngB: Double): Double { - val pk = (180 / PI) - val a1 = latA / pk - val a2 = lngA / pk - val b1 = latB / pk - val b2 = lngB / pk - val t1 = cos(a1) * cos(a2) * cos(b1) * cos(b2) - val t2 = cos(a1) * sin(a2) * cos(b1) * sin(b2) - val t3 = sin(a1) * sin(b1) - var tt = acos(t1 + t2 + t3) - if (java.lang.Double.isNaN(tt)) tt = 0.0 // Must have been the same point? - return 6366000 * tt -} - -// Same as above, but takes Mesh Position proto. -fun positionToMeter(a: MeshProtos.Position, b: MeshProtos.Position): Double = - latLongToMeter(a.latitudeI * 1e-7, a.longitudeI * 1e-7, b.latitudeI * 1e-7, b.longitudeI * 1e-7) - -/** - * Convert degrees/mins/secs to a single double - * - * @param degrees - * @param minutes - * @param seconds - * @param isPostive - * @return - */ -@Suppress("MagicNumber") -fun dmsToDegrees(degrees: Int, minutes: Int, seconds: Float, isPostive: Boolean): Double = - (if (isPostive) 1 else -1) * (degrees + minutes / 60.0 + seconds / 3600.0) - -@Suppress("MagicNumber") -fun dmsToDegrees(degrees: Double, minutes: Double, seconds: Double, isPostive: Boolean): Double = - (if (isPostive) 1 else -1) * (degrees + minutes / 60.0 + seconds / 3600.0) - -/** - * Computes the bearing in degrees between two points on Earth. - * - * @param lat1 Latitude of the first point - * @param lon1 Longitude of the first point - * @param lat2 Latitude of the second point - * @param lon2 Longitude of the second point - * @return Bearing between the two points in degrees. A value of 0 means due north. - */ -fun bearing(lat1: Double, lon1: Double, lat2: Double, lon2: Double): Double { - val lat1Rad = Math.toRadians(lat1) - val lat2Rad = Math.toRadians(lat2) - val deltaLonRad = Math.toRadians(lon2 - lon1) - val y = sin(deltaLonRad) * cos(lat2Rad) - val x = cos(lat1Rad) * sin(lat2Rad) - (sin(lat1Rad) * cos(lat2Rad) * cos(deltaLonRad)) - return radToBearing(atan2(y, x)) -} - -/** Converts an angle in radians to degrees */ -@Suppress("MagicNumber") -fun radToBearing(rad: Double): Double = (Math.toDegrees(rad) + 360) % 360 - -/** - * Calculates the zoom level required to fit the entire [BoundingBox] inside the map view. - * - * @return The zoom level as a Double value. - */ -@Suppress("MagicNumber") -fun BoundingBox.requiredZoomLevel(): Double { - val topLeft = GeoPoint(this.latNorth, this.lonWest) - val bottomRight = GeoPoint(this.latSouth, this.lonEast) - val latLonWidth = topLeft.distanceToAsDouble(GeoPoint(topLeft.latitude, bottomRight.longitude)) - val latLonHeight = topLeft.distanceToAsDouble(GeoPoint(bottomRight.latitude, topLeft.longitude)) - val requiredLatZoom = log2(360.0 / (latLonHeight / 111320)) - val requiredLonZoom = log2(360.0 / (latLonWidth / 111320)) - return maxOf(requiredLatZoom, requiredLonZoom) * 0.8 -} - -/** - * Creates a new bounding box with adjusted dimensions based on the provided [zoomFactor]. - * - * @return A new [BoundingBox] with added [zoomFactor]. Example: - * ``` - * // Setting the zoom level directly using setZoom() - * map.setZoom(14.0) - * val boundingBoxZoom14 = map.boundingBox - * - * // Using zoomIn() results the equivalent BoundingBox with setZoom(15.0) - * val boundingBoxZoom15 = boundingBoxZoom14.zoomIn(1.0) - * ``` - */ -fun BoundingBox.zoomIn(zoomFactor: Double): BoundingBox { - val center = GeoPoint((latNorth + latSouth) / 2, (lonWest + lonEast) / 2) - val latDiff = latNorth - latSouth - val lonDiff = lonEast - lonWest - - val newLatDiff = latDiff / (2.0.pow(zoomFactor)) - val newLonDiff = lonDiff / (2.0.pow(zoomFactor)) - - return BoundingBox( - center.latitude + newLatDiff / 2, - center.longitude + newLonDiff / 2, - center.latitude - newLatDiff / 2, - center.longitude - newLonDiff / 2, - ) -} diff --git a/mesh_service_example/src/main/kotlin/com/meshtastic/android/meshserviceexample/MainActivity.kt b/mesh_service_example/src/main/kotlin/com/meshtastic/android/meshserviceexample/MainActivity.kt index 119b74f48..8ef54274f 100644 --- a/mesh_service_example/src/main/kotlin/com/meshtastic/android/meshserviceexample/MainActivity.kt +++ b/mesh_service_example/src/main/kotlin/com/meshtastic/android/meshserviceexample/MainActivity.kt @@ -36,11 +36,11 @@ import androidx.annotation.RequiresApi import androidx.appcompat.app.AppCompatActivity import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat -import com.geeksville.mesh.DataPacket -import com.geeksville.mesh.IMeshService -import com.geeksville.mesh.MessageStatus -import com.geeksville.mesh.NodeInfo import com.geeksville.mesh.Portnums +import org.meshtastic.core.model.DataPacket +import org.meshtastic.core.model.MessageStatus +import org.meshtastic.core.model.NodeInfo +import org.meshtastic.core.service.IMeshService private const val TAG: String = "MeshServiceExample" diff --git a/settings.gradle.kts b/settings.gradle.kts index b091b15e2..0ca6613e7 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -28,6 +28,7 @@ include( ":core:network", ":core:prefs", ":core:proto", + ":core:service", ":core:strings", ":core:ui", ":feature:map",