mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-03-28 18:52:42 -04:00
Add :core:service (#3253)
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
package com.geeksville.mesh.service
|
||||
|
||||
import org.meshtastic.core.service.ConnectionState
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
34
core/service/build.gradle.kts
Normal file
34
core/service/build.gradle.kts
Normal file
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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)
|
||||
}
|
||||
@@ -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;
|
||||
@@ -15,7 +15,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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. */
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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()
|
||||
}
|
||||
@@ -15,25 +15,24 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
<queries>
|
||||
<package android:name="com.geeksville.mesh" />
|
||||
</queries>
|
||||
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.<portnumm> - 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<NodeInfo> 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();
|
||||
}
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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 <reified T : Parcelable> 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<DataPacket> {
|
||||
// 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<DataPacket?> = arrayOfNulls(size)
|
||||
}
|
||||
}
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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"
|
||||
}
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<Int, Int>
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
@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>) = 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<String>) = "%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<String> {
|
||||
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<String> {
|
||||
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<String> {
|
||||
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,
|
||||
)
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ include(
|
||||
":core:network",
|
||||
":core:prefs",
|
||||
":core:proto",
|
||||
":core:service",
|
||||
":core:strings",
|
||||
":core:ui",
|
||||
":feature:map",
|
||||
|
||||
Reference in New Issue
Block a user