Modularize more models/utils (#3182)

This commit is contained in:
Phil Oliver
2025-09-24 11:43:46 -04:00
committed by GitHub
parent 5bb3f73e0d
commit 4eba3e9daf
80 changed files with 656 additions and 629 deletions

View File

@@ -26,7 +26,6 @@ import com.geeksville.mesh.database.entity.MyNodeEntity
import com.geeksville.mesh.database.entity.NodeEntity
import com.geeksville.mesh.model.Node
import com.geeksville.mesh.model.NodeSortOption
import com.geeksville.mesh.util.onlineTimeThreshold
import com.google.protobuf.ByteString
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
@@ -38,6 +37,7 @@ import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.meshtastic.core.model.util.onlineTimeThreshold
@RunWith(AndroidJUnit4::class)
class NodeInfoDaoTest {
@@ -48,129 +48,150 @@ class NodeInfoDaoTest {
private val offlineNodeLastHeard = onlineThreshold - 30
private val onlineNodeLastHeard = onlineThreshold + 20
private val unknownNode = NodeEntity(
num = 7,
user = user {
id = "!a1b2c3d4"
longName = "Meshtastic c3d4"
shortName = "c3d4"
hwModel = MeshProtos.HardwareModel.UNSET
},
longName = "Meshtastic c3d4",
shortName = null // Dao filter for includeUnknown
)
private val ourNode = NodeEntity(
num = 8,
user = user {
id = "+16508765308".format(8)
longName = "Kevin Mester"
shortName = "KLO"
hwModel = MeshProtos.HardwareModel.ANDROID_SIM
isLicensed = false
},
longName = "Kevin Mester", shortName = "KLO",
latitude = 30.267153, longitude = -97.743057, // Austin
hopsAway = 0,
)
private val onlineNode = NodeEntity(
num = 9,
user = user {
id = "!25060801"
longName = "Meshtastic 0801"
shortName = "0801"
hwModel = MeshProtos.HardwareModel.ANDROID_SIM
},
longName = "Meshtastic 0801",
shortName = "0801",
hopsAway = 0,
lastHeard = onlineNodeLastHeard
)
private val offlineNode = NodeEntity(
num = 10,
user = user {
id = "!25060802"
longName = "Meshtastic 0802"
shortName = "0802"
hwModel = MeshProtos.HardwareModel.ANDROID_SIM
},
longName = "Meshtastic 0802",
shortName = "0802",
hopsAway = 0,
lastHeard = offlineNodeLastHeard
)
private val directNode = NodeEntity(
num = 11,
user = user {
id = "!25060803"
longName = "Meshtastic 0803"
shortName = "0803"
hwModel = MeshProtos.HardwareModel.ANDROID_SIM
},
longName = "Meshtastic 0803",
shortName = "0803",
hopsAway = 0,
lastHeard = onlineNodeLastHeard
)
private val relayedNode = NodeEntity(
num = 12,
user = user {
id = "!25060804"
longName = "Meshtastic 0804"
shortName = "0804"
hwModel = MeshProtos.HardwareModel.ANDROID_SIM
},
longName = "Meshtastic 0804",
shortName = "0804",
hopsAway = 3,
lastHeard = onlineNodeLastHeard
)
private val myNodeInfo: MyNodeEntity = MyNodeEntity(
myNodeNum = ourNode.num,
model = null,
firmwareVersion = null,
couldUpdate = false,
shouldUpdate = false,
currentPacketId = 1L,
messageTimeoutMsec = 5 * 60 * 1000,
minAppVersion = 1,
maxChannels = 8,
hasWifi = false,
)
private val testPositions = arrayOf(
0.0 to 0.0,
32.776665 to -96.796989, // Dallas
32.960758 to -96.733521, // Richardson
32.912901 to -96.781776, // North Dallas
29.760427 to -95.369804, // Houston
33.748997 to -84.387985, // Atlanta
34.052235 to -118.243683, // Los Angeles
40.712776 to -74.005974, // New York City
41.878113 to -87.629799, // Chicago
39.952583 to -75.165222, // Philadelphia
)
private val testNodes = listOf(ourNode, unknownNode, onlineNode, offlineNode, directNode, relayedNode) + testPositions.mapIndexed { index, pos ->
private val unknownNode =
NodeEntity(
num = 1000 + index,
user = user {
id = "+165087653%02d".format(9 + index)
longName = "Kevin Mester$index"
shortName = "KM$index"
num = 7,
user =
user {
id = "!a1b2c3d4"
longName = "Meshtastic c3d4"
shortName = "c3d4"
hwModel = MeshProtos.HardwareModel.UNSET
},
longName = "Meshtastic c3d4",
shortName = null, // Dao filter for includeUnknown
)
private val ourNode =
NodeEntity(
num = 8,
user =
user {
id = "+16508765308".format(8)
longName = "Kevin Mester"
shortName = "KLO"
hwModel = MeshProtos.HardwareModel.ANDROID_SIM
isLicensed = false
publicKey = ByteString.copyFrom(ByteArray(32) { index.toByte() })
},
longName = "Kevin Mester$index", shortName = "KM$index",
latitude = pos.first, longitude = pos.second,
lastHeard = 9 + index,
longName = "Kevin Mester",
shortName = "KLO",
latitude = 30.267153,
longitude = -97.743057, // Austin
hopsAway = 0,
)
}
private val onlineNode =
NodeEntity(
num = 9,
user =
user {
id = "!25060801"
longName = "Meshtastic 0801"
shortName = "0801"
hwModel = MeshProtos.HardwareModel.ANDROID_SIM
},
longName = "Meshtastic 0801",
shortName = "0801",
hopsAway = 0,
lastHeard = onlineNodeLastHeard,
)
private val offlineNode =
NodeEntity(
num = 10,
user =
user {
id = "!25060802"
longName = "Meshtastic 0802"
shortName = "0802"
hwModel = MeshProtos.HardwareModel.ANDROID_SIM
},
longName = "Meshtastic 0802",
shortName = "0802",
hopsAway = 0,
lastHeard = offlineNodeLastHeard,
)
private val directNode =
NodeEntity(
num = 11,
user =
user {
id = "!25060803"
longName = "Meshtastic 0803"
shortName = "0803"
hwModel = MeshProtos.HardwareModel.ANDROID_SIM
},
longName = "Meshtastic 0803",
shortName = "0803",
hopsAway = 0,
lastHeard = onlineNodeLastHeard,
)
private val relayedNode =
NodeEntity(
num = 12,
user =
user {
id = "!25060804"
longName = "Meshtastic 0804"
shortName = "0804"
hwModel = MeshProtos.HardwareModel.ANDROID_SIM
},
longName = "Meshtastic 0804",
shortName = "0804",
hopsAway = 3,
lastHeard = onlineNodeLastHeard,
)
private val myNodeInfo: MyNodeEntity =
MyNodeEntity(
myNodeNum = ourNode.num,
model = null,
firmwareVersion = null,
couldUpdate = false,
shouldUpdate = false,
currentPacketId = 1L,
messageTimeoutMsec = 5 * 60 * 1000,
minAppVersion = 1,
maxChannels = 8,
hasWifi = false,
)
private val testPositions =
arrayOf(
0.0 to 0.0,
32.776665 to -96.796989, // Dallas
32.960758 to -96.733521, // Richardson
32.912901 to -96.781776, // North Dallas
29.760427 to -95.369804, // Houston
33.748997 to -84.387985, // Atlanta
34.052235 to -118.243683, // Los Angeles
40.712776 to -74.005974, // New York City
41.878113 to -87.629799, // Chicago
39.952583 to -75.165222, // Philadelphia
)
private val testNodes =
listOf(ourNode, unknownNode, onlineNode, offlineNode, directNode, relayedNode) +
testPositions.mapIndexed { index, pos ->
NodeEntity(
num = 1000 + index,
user =
user {
id = "+165087653%02d".format(9 + index)
longName = "Kevin Mester$index"
shortName = "KM$index"
hwModel = MeshProtos.HardwareModel.ANDROID_SIM
isLicensed = false
publicKey = ByteString.copyFrom(ByteArray(32) { index.toByte() })
},
longName = "Kevin Mester$index",
shortName = "KM$index",
latitude = pos.first,
longitude = pos.second,
lastHeard = 9 + index,
)
}
@Before
fun createDb(): Unit = runBlocking {
@@ -190,8 +211,8 @@ class NodeInfoDaoTest {
}
/**
* Retrieves a list of nodes based on [sort], [filter] and [includeUnknown] parameters.
* The list excludes [ourNode] to ensure consistency in the results.
* Retrieves a list of nodes based on [sort], [filter] and [includeUnknown] parameters. The list excludes [ourNode]
* to ensure consistency in the results.
*/
private suspend fun getNodes(
sort: NodeSortOption = NodeSortOption.LAST_HEARD,
@@ -199,13 +220,17 @@ class NodeInfoDaoTest {
includeUnknown: Boolean = true,
onlyOnline: Boolean = false,
onlyDirect: Boolean = false,
) = nodeInfoDao.getNodes(
sort = sort.sqlValue,
filter = filter,
includeUnknown = includeUnknown,
hopsAwayMax = if (onlyDirect) 0 else -1,
lastHeardMin = if (onlyOnline) onlineTimeThreshold() else -1,
).map { list -> list.map { it.toModel() } }.first().filter { it.num != ourNode.num }
) = nodeInfoDao
.getNodes(
sort = sort.sqlValue,
filter = filter,
includeUnknown = includeUnknown,
hopsAwayMax = if (onlyDirect) 0 else -1,
lastHeardMin = if (onlyOnline) onlineTimeThreshold() else -1,
)
.map { list -> list.map { it.toModel() } }
.first()
.filter { it.num != ourNode.num }
@Test // node list size
fun testNodeListSize() = runBlocking {
@@ -237,9 +262,10 @@ class NodeInfoDaoTest {
fun testSortByDistance() = runBlocking {
val nodes = getNodes(sort = NodeSortOption.DISTANCE)
fun NodeEntity.toNode() = Node(num = num, user = user, position = position)
val sortedNodes = nodes.sortedWith( // nodes with invalid (null) positions at the end
compareBy<Node> { it.validPosition == null }.thenBy { it.distance(ourNode.toNode()) }
)
val sortedNodes =
nodes.sortedWith( // nodes with invalid (null) positions at the end
compareBy<Node> { it.validPosition == null }.thenBy { it.distance(ourNode.toNode()) },
)
assertEquals(sortedNodes, nodes)
}
@@ -297,9 +323,8 @@ class NodeInfoDaoTest {
@Test
fun testPkcMismatch() = runBlocking {
val newNode = testNodes[1].copy(user = testNodes[1].user.copy {
publicKey = ByteString.copyFrom(ByteArray(32) { 99 })
})
val newNode =
testNodes[1].copy(user = testNodes[1].user.copy { publicKey = ByteString.copyFrom(ByteArray(32) { 99 }) })
nodeInfoDao.putAll(listOf(newNode))
val nodes = getNodes()
val containsMismatchNode = nodes.any { it.mismatchKey }

View File

@@ -33,6 +33,7 @@ import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.meshtastic.core.model.DataPacket
@RunWith(AndroidJUnit4::class)
class PacketDaoTest {
@@ -40,25 +41,24 @@ class PacketDaoTest {
private lateinit var nodeInfoDao: NodeInfoDao
private lateinit var packetDao: PacketDao
private val myNodeInfo: MyNodeEntity = MyNodeEntity(
myNodeNum = 42424242,
model = null,
firmwareVersion = null,
couldUpdate = false,
shouldUpdate = false,
currentPacketId = 1L,
messageTimeoutMsec = 5 * 60 * 1000,
minAppVersion = 1,
maxChannels = 8,
hasWifi = false,
)
private val myNodeInfo: MyNodeEntity =
MyNodeEntity(
myNodeNum = 42424242,
model = null,
firmwareVersion = null,
couldUpdate = false,
shouldUpdate = false,
currentPacketId = 1L,
messageTimeoutMsec = 5 * 60 * 1000,
minAppVersion = 1,
maxChannels = 8,
hasWifi = false,
)
private val myNodeNum: Int get() = myNodeInfo.myNodeNum
private val myNodeNum: Int
get() = myNodeInfo.myNodeNum
private val testContactKeys = listOf(
"0${DataPacket.ID_BROADCAST}",
"1!test1234",
)
private val testContactKeys = listOf("0${DataPacket.ID_BROADCAST}", "1!test1234")
private fun generateTestPackets(myNodeNum: Int) = testContactKeys.flatMap { contactKey ->
List(SAMPLE_SIZE) {
@@ -79,14 +79,13 @@ class PacketDaoTest {
val context = InstrumentationRegistry.getInstrumentation().targetContext
database = Room.inMemoryDatabaseBuilder(context, MeshtasticDatabase::class.java).build()
nodeInfoDao = database.nodeInfoDao().apply {
setMyNodeInfo(myNodeInfo)
}
nodeInfoDao = database.nodeInfoDao().apply { setMyNodeInfo(myNodeInfo) }
packetDao = database.packetDao().apply {
generateTestPackets(42424243).forEach { insert(it) }
generateTestPackets(myNodeNum).forEach { insert(it) }
}
packetDao =
database.packetDao().apply {
generateTestPackets(42424243).forEach { insert(it) }
generateTestPackets(myNodeNum).forEach { insert(it) }
}
}
@After

View File

@@ -21,13 +21,13 @@ import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.geeksville.mesh.MessageStatus
import com.geeksville.mesh.model.Message
import com.geeksville.mesh.ui.common.preview.NodePreviewParameterProvider
import com.geeksville.mesh.ui.message.components.MessageItem
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.meshtastic.core.model.MessageStatus
@RunWith(AndroidJUnit4::class)
class MessageItemTest {

View File

@@ -62,7 +62,6 @@ import androidx.compose.ui.viewinterop.AndroidView
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import com.geeksville.mesh.DataPacket
import com.geeksville.mesh.MeshProtos.Waypoint
import com.geeksville.mesh.android.BuildUtils.debug
import com.geeksville.mesh.android.gpsDisabled
@@ -79,11 +78,12 @@ import com.geeksville.mesh.util.SqlTileWriterExt
import com.geeksville.mesh.util.addCopyright
import com.geeksville.mesh.util.addScaleBarOverlay
import com.geeksville.mesh.util.createLatLongGrid
import com.geeksville.mesh.util.formatAgo
import com.geeksville.mesh.waypoint
import com.google.accompanist.permissions.ExperimentalPermissionsApi // Added for Accompanist
import com.google.accompanist.permissions.rememberMultiplePermissionsState // Added for Accompanist
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.meshtastic.core.model.DataPacket
import org.meshtastic.core.model.util.formatAgo
import org.meshtastic.core.strings.R
import org.meshtastic.feature.map.cluster.RadiusMarkerClusterer
import org.meshtastic.feature.map.model.CustomTileSource

View File

@@ -84,10 +84,7 @@ import com.geeksville.mesh.ui.metrics.HEADING_DEG
import com.geeksville.mesh.ui.metrics.formatPositionTime
import com.geeksville.mesh.ui.node.DEG_D
import com.geeksville.mesh.ui.node.components.NodeChip
import com.geeksville.mesh.util.formatAgo
import com.geeksville.mesh.util.metersIn
import com.geeksville.mesh.util.mpsToKmph
import com.geeksville.mesh.util.mpsToMph
import com.geeksville.mesh.util.toString
import com.geeksville.mesh.waypoint
import com.google.android.gms.location.LocationCallback
@@ -120,6 +117,9 @@ import com.google.maps.android.compose.rememberUpdatedMarkerState
import com.google.maps.android.compose.widgets.ScaleBar
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import org.meshtastic.core.model.util.formatAgo
import org.meshtastic.core.model.util.mpsToKmph
import org.meshtastic.core.model.util.mpsToMph
import org.meshtastic.core.strings.R
import timber.log.Timber
import java.text.DateFormat

View File

@@ -2,11 +2,11 @@
package com.geeksville.mesh;
// Declare any non-default types here with import statements
parcelable DataPacket;
parcelable NodeInfo;
parcelable MeshUser;
parcelable Position;
parcelable MyNodeInfo;
import org.meshtastic.core.model.DataPacket;
import org.meshtastic.core.model.NodeInfo;
import org.meshtastic.core.model.MeshUser;
import org.meshtastic.core.model.Position;
import org.meshtastic.core.model.MyNodeInfo;
/**
This is the public android API for talking to meshtastic radios.

View File

@@ -18,7 +18,6 @@
package com.geeksville.mesh.database
import androidx.room.TypeConverter
import com.geeksville.mesh.DataPacket
import com.geeksville.mesh.MeshProtos
import com.geeksville.mesh.PaxcountProtos
import com.geeksville.mesh.TelemetryProtos
@@ -26,6 +25,7 @@ import com.geeksville.mesh.android.Logging
import com.google.protobuf.ByteString
import com.google.protobuf.InvalidProtocolBufferException
import kotlinx.serialization.json.Json
import org.meshtastic.core.model.DataPacket
@Suppress("TooManyFunctions")
class Converters : Logging {

View File

@@ -20,7 +20,6 @@ package com.geeksville.mesh.database
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.coroutineScope
import com.geeksville.mesh.CoroutineDispatchers
import com.geeksville.mesh.DataPacket
import com.geeksville.mesh.MeshProtos
import com.geeksville.mesh.database.dao.NodeInfoDao
import com.geeksville.mesh.database.entity.MetadataEntity
@@ -28,7 +27,6 @@ import com.geeksville.mesh.database.entity.MyNodeEntity
import com.geeksville.mesh.database.entity.NodeEntity
import com.geeksville.mesh.model.Node
import com.geeksville.mesh.model.NodeSortOption
import com.geeksville.mesh.util.onlineTimeThreshold
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
@@ -40,6 +38,8 @@ import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext
import org.meshtastic.core.model.DataPacket
import org.meshtastic.core.model.util.onlineTimeThreshold
import javax.inject.Inject
import javax.inject.Singleton

View File

@@ -17,8 +17,6 @@
package com.geeksville.mesh.database
import com.geeksville.mesh.DataPacket
import com.geeksville.mesh.MessageStatus
import com.geeksville.mesh.Portnums.PortNum
import com.geeksville.mesh.database.dao.PacketDao
import com.geeksville.mesh.database.entity.ContactSettings
@@ -29,66 +27,52 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.withContext
import org.meshtastic.core.model.DataPacket
import org.meshtastic.core.model.MessageStatus
import javax.inject.Inject
class PacketRepository @Inject constructor(private val packetDaoLazy: dagger.Lazy<PacketDao>) {
private val packetDao by lazy {
packetDaoLazy.get()
}
private val packetDao by lazy { packetDaoLazy.get() }
fun getWaypoints(): Flow<List<Packet>> = packetDao.getAllPackets(PortNum.WAYPOINT_APP_VALUE)
fun getContacts(): Flow<Map<String, Packet>> = packetDao.getContactKeys()
suspend fun getMessageCount(contact: String): Int = withContext(Dispatchers.IO) {
packetDao.getMessageCount(contact)
}
suspend fun getMessageCount(contact: String): Int =
withContext(Dispatchers.IO) { packetDao.getMessageCount(contact) }
suspend fun getUnreadCount(contact: String): Int = withContext(Dispatchers.IO) {
packetDao.getUnreadCount(contact)
}
suspend fun getUnreadCount(contact: String): Int = withContext(Dispatchers.IO) { packetDao.getUnreadCount(contact) }
suspend fun clearUnreadCount(contact: String, timestamp: Long) = withContext(Dispatchers.IO) {
packetDao.clearUnreadCount(contact, timestamp)
}
suspend fun clearUnreadCount(contact: String, timestamp: Long) =
withContext(Dispatchers.IO) { packetDao.clearUnreadCount(contact, timestamp) }
suspend fun getQueuedPackets(): List<DataPacket>? = withContext(Dispatchers.IO) {
packetDao.getQueuedPackets()
}
suspend fun getQueuedPackets(): List<DataPacket>? = withContext(Dispatchers.IO) { packetDao.getQueuedPackets() }
suspend fun insert(packet: Packet) = withContext(Dispatchers.IO) {
packetDao.insert(packet)
}
suspend fun insert(packet: Packet) = withContext(Dispatchers.IO) { packetDao.insert(packet) }
suspend fun getMessagesFrom(contact: String, getNode: suspend (String?) -> Node) =
withContext(Dispatchers.IO) {
packetDao.getMessagesFrom(contact).mapLatest { packets ->
packets.map { packet ->
val message = packet.toMessage(getNode)
message.replyId.takeIf { it != null && it != 0 }
?.let { getPacketByPacketId(it) }
?.toMessage(getNode)
?.let { originalMessage -> message.copy(originalMessage = originalMessage) }
?: message
}
suspend fun getMessagesFrom(contact: String, getNode: suspend (String?) -> Node) = withContext(Dispatchers.IO) {
packetDao.getMessagesFrom(contact).mapLatest { packets ->
packets.map { packet ->
val message = packet.toMessage(getNode)
message.replyId
.takeIf { it != null && it != 0 }
?.let { getPacketByPacketId(it) }
?.toMessage(getNode)
?.let { originalMessage -> message.copy(originalMessage = originalMessage) } ?: message
}
}
suspend fun updateMessageStatus(d: DataPacket, m: MessageStatus) = withContext(Dispatchers.IO) {
packetDao.updateMessageStatus(d, m)
}
suspend fun updateMessageId(d: DataPacket, id: Int) = withContext(Dispatchers.IO) {
packetDao.updateMessageId(d, id)
}
suspend fun updateMessageStatus(d: DataPacket, m: MessageStatus) =
withContext(Dispatchers.IO) { packetDao.updateMessageStatus(d, m) }
suspend fun getPacketById(requestId: Int) = withContext(Dispatchers.IO) {
packetDao.getPacketById(requestId)
}
suspend fun updateMessageId(d: DataPacket, id: Int) =
withContext(Dispatchers.IO) { packetDao.updateMessageId(d, id) }
suspend fun getPacketByPacketId(packetId: Int) = withContext(Dispatchers.IO) {
packetDao.getPacketByPacketId(packetId)
}
suspend fun getPacketById(requestId: Int) = withContext(Dispatchers.IO) { packetDao.getPacketById(requestId) }
suspend fun getPacketByPacketId(packetId: Int) =
withContext(Dispatchers.IO) { packetDao.getPacketByPacketId(packetId) }
suspend fun deleteMessages(uuidList: List<Long>) = withContext(Dispatchers.IO) {
for (chunk in uuidList.chunked(500)) { // limit number of UUIDs per query
@@ -96,37 +80,24 @@ class PacketRepository @Inject constructor(private val packetDaoLazy: dagger.Laz
}
}
suspend fun deleteContacts(contactList: List<String>) = withContext(Dispatchers.IO) {
packetDao.deleteContacts(contactList)
}
suspend fun deleteContacts(contactList: List<String>) =
withContext(Dispatchers.IO) { packetDao.deleteContacts(contactList) }
suspend fun deleteWaypoint(id: Int) = withContext(Dispatchers.IO) {
packetDao.deleteWaypoint(id)
}
suspend fun deleteWaypoint(id: Int) = withContext(Dispatchers.IO) { packetDao.deleteWaypoint(id) }
suspend fun delete(packet: Packet) = withContext(Dispatchers.IO) {
packetDao.delete(packet)
}
suspend fun delete(packet: Packet) = withContext(Dispatchers.IO) { packetDao.delete(packet) }
suspend fun update(packet: Packet) = withContext(Dispatchers.IO) {
packetDao.update(packet)
}
suspend fun update(packet: Packet) = withContext(Dispatchers.IO) { packetDao.update(packet) }
fun getContactSettings(): Flow<Map<String, ContactSettings>> = packetDao.getContactSettings()
suspend fun getContactSettings(contact: String) = withContext(Dispatchers.IO) {
packetDao.getContactSettings(contact) ?: ContactSettings(contact)
}
suspend fun getContactSettings(contact: String) =
withContext(Dispatchers.IO) { packetDao.getContactSettings(contact) ?: ContactSettings(contact) }
suspend fun setMuteUntil(contacts: List<String>, until: Long) = withContext(Dispatchers.IO) {
packetDao.setMuteUntil(contacts, until)
}
suspend fun setMuteUntil(contacts: List<String>, until: Long) =
withContext(Dispatchers.IO) { packetDao.setMuteUntil(contacts, until) }
suspend fun insertReaction(reaction: ReactionEntity) = withContext(Dispatchers.IO) {
packetDao.insert(reaction)
}
suspend fun insertReaction(reaction: ReactionEntity) = withContext(Dispatchers.IO) { packetDao.insert(reaction) }
suspend fun clearPacketDB() = withContext(Dispatchers.IO) {
packetDao.deleteAll()
}
suspend fun clearPacketDB() = withContext(Dispatchers.IO) { packetDao.deleteAll() }
}

View File

@@ -23,13 +23,13 @@ import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Update
import androidx.room.Upsert
import com.geeksville.mesh.DataPacket
import com.geeksville.mesh.MessageStatus
import com.geeksville.mesh.database.entity.ContactSettings
import com.geeksville.mesh.database.entity.Packet
import com.geeksville.mesh.database.entity.PacketEntity
import com.geeksville.mesh.database.entity.ReactionEntity
import kotlinx.coroutines.flow.Flow
import org.meshtastic.core.model.DataPacket
import org.meshtastic.core.model.MessageStatus
@Dao
interface PacketDao {
@@ -40,7 +40,7 @@ interface PacketDao {
WHERE (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
AND port_num = :portNum
ORDER BY received_time ASC
"""
""",
)
fun getAllPackets(portNum: Int): Flow<List<Packet>>
@@ -50,16 +50,22 @@ interface PacketDao {
WHERE (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
AND port_num = 1
ORDER BY received_time DESC
"""
""",
)
fun getContactKeys(): Flow<Map<@MapColumn(columnName = "contact_key") String, Packet>>
fun getContactKeys(): Flow<
Map<
@MapColumn(columnName = "contact_key")
String,
Packet,
>,
>
@Query(
"""
SELECT COUNT(*) FROM packet
WHERE (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
AND port_num = 1 AND contact_key = :contact
"""
""",
)
suspend fun getMessageCount(contact: String): Int
@@ -68,7 +74,7 @@ interface PacketDao {
SELECT COUNT(*) FROM packet
WHERE (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
AND port_num = 1 AND contact_key = :contact AND read = 0
"""
""",
)
suspend fun getUnreadCount(contact: String): Int
@@ -78,12 +84,11 @@ interface PacketDao {
SET read = 1
WHERE (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
AND port_num = 1 AND contact_key = :contact AND read = 0 AND received_time <= :timestamp
"""
""",
)
suspend fun clearUnreadCount(contact: String, timestamp: Long)
@Upsert
suspend fun insert(packet: Packet)
@Upsert suspend fun insert(packet: Packet)
@Transaction
@Query(
@@ -92,7 +97,7 @@ interface PacketDao {
WHERE (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
AND port_num = 1 AND contact_key = :contact
ORDER BY received_time DESC
"""
""",
)
fun getMessagesFrom(contact: String): Flow<List<PacketEntity>>
@@ -101,7 +106,7 @@ interface PacketDao {
SELECT * FROM packet
WHERE (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
AND data = :data
"""
""",
)
suspend fun findDataPacket(data: DataPacket): Packet?
@@ -113,16 +118,16 @@ interface PacketDao {
DELETE FROM packet
WHERE (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
AND contact_key IN (:contactList)
"""
""",
)
suspend fun deleteContacts(contactList: List<String>)
@Query("DELETE FROM packet WHERE uuid=:uuid")
suspend fun _delete(uuid: Long)
suspend fun delete(uuid: Long)
@Transaction
suspend fun delete(packet: Packet) {
_delete(packet.uuid)
delete(packet.uuid)
}
@Query("SELECT packet_id FROM packet WHERE uuid IN (:uuidList)")
@@ -140,8 +145,7 @@ interface PacketDao {
deletePackets(uuidList)
}
@Update
suspend fun update(packet: Packet)
@Update suspend fun update(packet: Packet)
@Transaction
suspend fun updateMessageStatus(data: DataPacket, m: MessageStatus) {
@@ -160,7 +164,7 @@ interface PacketDao {
SELECT data FROM packet
WHERE (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
ORDER BY received_time ASC
"""
""",
)
suspend fun getDataPackets(): List<DataPacket>
@@ -171,7 +175,7 @@ interface PacketDao {
WHERE (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
AND packet_id = :requestId
ORDER BY received_time DESC
"""
""",
)
suspend fun getPacketById(requestId: Int): Packet?
@@ -180,8 +184,7 @@ interface PacketDao {
suspend fun getPacketByPacketId(packetId: Int): PacketEntity?
@Transaction
suspend fun getQueuedPackets(): List<DataPacket>? =
getDataPackets().filter { it.status == MessageStatus.QUEUED }
suspend fun getQueuedPackets(): List<DataPacket>? = getDataPackets().filter { it.status == MessageStatus.QUEUED }
@Query(
"""
@@ -189,7 +192,7 @@ interface PacketDao {
WHERE (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
AND port_num = 8
ORDER BY received_time ASC
"""
""",
)
suspend fun getAllWaypoints(): List<Packet>
@@ -200,25 +203,30 @@ interface PacketDao {
}
@Query("SELECT * FROM contact_settings")
fun getContactSettings(): Flow<Map<@MapColumn(columnName = "contact_key") String, ContactSettings>>
fun getContactSettings(): Flow<
Map<
@MapColumn(columnName = "contact_key")
String,
ContactSettings,
>,
>
@Query("SELECT * FROM contact_settings WHERE contact_key = :contact")
suspend fun getContactSettings(contact: String): ContactSettings?
@Upsert
suspend fun upsertContactSettings(contacts: List<ContactSettings>)
@Upsert suspend fun upsertContactSettings(contacts: List<ContactSettings>)
@Transaction
suspend fun setMuteUntil(contacts: List<String>, until: Long) {
val contactList = contacts.map { contact ->
getContactSettings(contact)?.copy(muteUntil = until)
?: ContactSettings(contact_key = contact, muteUntil = until)
}
val contactList =
contacts.map { contact ->
getContactSettings(contact)?.copy(muteUntil = until)
?: ContactSettings(contact_key = contact, muteUntil = until)
}
upsertContactSettings(contactList)
}
@Upsert
suspend fun insert(reaction: ReactionEntity)
@Upsert suspend fun insert(reaction: ReactionEntity)
@Query("DELETE FROM packet")
suspend fun deleteAll()

View File

@@ -22,7 +22,7 @@ import androidx.room.Entity
import androidx.room.PrimaryKey
import kotlinx.serialization.Serializable
import org.meshtastic.core.model.DeviceHardware
import org.meshtastic.core.network.model.NetworkDeviceHardware
import org.meshtastic.core.model.NetworkDeviceHardware
@Serializable
@Entity(tableName = "device_hardware")

View File

@@ -20,9 +20,9 @@ package com.geeksville.mesh.database.entity
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import com.geeksville.mesh.model.DeviceVersion
import kotlinx.serialization.Serializable
import org.meshtastic.core.network.model.NetworkFirmwareRelease
import org.meshtastic.core.model.DeviceVersion
import org.meshtastic.core.model.NetworkFirmwareRelease
@Serializable
@Entity(tableName = "firmware_release")

View File

@@ -19,12 +19,11 @@ package com.geeksville.mesh.database.entity
import androidx.room.Entity
import androidx.room.PrimaryKey
import com.geeksville.mesh.MyNodeInfo
import org.meshtastic.core.model.MyNodeInfo
@Entity(tableName = "my_node")
data class MyNodeEntity(
@PrimaryKey(autoGenerate = false)
val myNodeNum: Int,
@PrimaryKey(autoGenerate = false) val myNodeNum: Int,
val model: String?,
val firmwareVersion: String?,
val couldUpdate: Boolean, // this application contains a software load we _could_ install if you want
@@ -37,7 +36,8 @@ data class MyNodeEntity(
val deviceId: String? = "unknown",
) {
/** A human readable description of the software/hardware version */
val firmwareString: String get() = "$model $firmwareVersion"
val firmwareString: String
get() = "$model $firmwareVersion"
fun toMyNodeInfo() = MyNodeInfo(
myNodeNum = myNodeNum,

View File

@@ -23,19 +23,19 @@ import androidx.room.Entity
import androidx.room.Index
import androidx.room.PrimaryKey
import androidx.room.Relation
import com.geeksville.mesh.DeviceMetrics
import com.geeksville.mesh.EnvironmentMetrics
import com.geeksville.mesh.MeshProtos
import com.geeksville.mesh.MeshUser
import com.geeksville.mesh.NodeInfo
import com.geeksville.mesh.PaxcountProtos
import com.geeksville.mesh.Position
import com.geeksville.mesh.TelemetryProtos
import com.geeksville.mesh.copy
import com.geeksville.mesh.model.Node
import com.geeksville.mesh.util.onlineTimeThreshold
import com.google.protobuf.ByteString
import com.google.protobuf.kotlin.isNotEmpty
import org.meshtastic.core.model.DeviceMetrics
import org.meshtastic.core.model.EnvironmentMetrics
import org.meshtastic.core.model.MeshUser
import org.meshtastic.core.model.NodeInfo
import org.meshtastic.core.model.Position
import org.meshtastic.core.model.util.onlineTimeThreshold
data class NodeWithRelations(
@Embedded val node: NodeEntity,

View File

@@ -23,11 +23,11 @@ import androidx.room.Entity
import androidx.room.Index
import androidx.room.PrimaryKey
import androidx.room.Relation
import com.geeksville.mesh.DataPacket
import com.geeksville.mesh.MeshProtos.User
import com.geeksville.mesh.model.Message
import com.geeksville.mesh.model.Node
import com.geeksville.mesh.util.getShortDateTime
import org.meshtastic.core.model.DataPacket
import org.meshtastic.core.model.util.getShortDateTime
data class PacketEntity(
@Embedded val packet: Packet,
@@ -52,20 +52,15 @@ data class PacketEntity(
packetId = packetId,
emojis = reactions.toReaction(getNode),
replyId = data.replyId,
viaMqtt = node.viaMqtt
viaMqtt = node.viaMqtt,
)
}
}
@Entity(
tableName = "packet",
indices = [
Index(value = ["myNodeNum"]),
Index(value = ["port_num"]),
Index(value = ["contact_key"]),
]
indices = [Index(value = ["myNodeNum"]), Index(value = ["port_num"]), Index(value = ["contact_key"])],
)
data class Packet(
@PrimaryKey(autoGenerate = true) val uuid: Long,
@ColumnInfo(name = "myNodeNum", defaultValue = "0") val myNodeNum: Int,
@@ -83,26 +78,17 @@ data class Packet(
)
@Entity(tableName = "contact_settings")
data class ContactSettings(
@PrimaryKey val contact_key: String,
val muteUntil: Long = 0L,
) {
val isMuted get() = System.currentTimeMillis() <= muteUntil
data class ContactSettings(@PrimaryKey val contact_key: String, val muteUntil: Long = 0L) {
val isMuted
get() = System.currentTimeMillis() <= muteUntil
}
data class Reaction(
val replyId: Int,
val user: User,
val emoji: String,
val timestamp: Long,
)
data class Reaction(val replyId: Int, val user: User, val emoji: String, val timestamp: Long)
@Entity(
tableName = "reactions",
primaryKeys = ["reply_id", "user_id", "emoji"],
indices = [
Index(value = ["reply_id"]),
],
indices = [Index(value = ["reply_id"])],
)
data class ReactionEntity(
@ColumnInfo(name = "reply_id") val replyId: Int,
@@ -111,15 +97,8 @@ data class ReactionEntity(
val timestamp: Long,
)
private suspend fun ReactionEntity.toReaction(
getNode: suspend (userId: String?) -> Node
) = Reaction(
replyId = replyId,
user = getNode(userId).user,
emoji = emoji,
timestamp = timestamp,
)
private suspend fun ReactionEntity.toReaction(getNode: suspend (userId: String?) -> Node) =
Reaction(replyId = replyId, user = getNode(userId).user, emoji = emoji, timestamp = timestamp)
private suspend fun List<ReactionEntity>.toReaction(
getNode: suspend (userId: String?) -> Node
) = this.map { it.toReaction(getNode) }
private suspend fun List<ReactionEntity>.toReaction(getNode: suspend (userId: String?) -> Node) =
this.map { it.toReaction(getNode) }

View File

@@ -35,7 +35,6 @@ 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.geeksville.mesh.util.anonymize
import com.hoho.android.usbserial.driver.UsbSerialDriver
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Job
@@ -52,6 +51,7 @@ import kotlinx.coroutines.flow.stateIn
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.strings.R
import javax.inject.Inject

View File

@@ -28,7 +28,7 @@ import com.geeksville.mesh.ui.common.theme.GraphColors.Pink
import com.geeksville.mesh.ui.common.theme.GraphColors.Purple
import com.geeksville.mesh.ui.common.theme.GraphColors.Red
import com.geeksville.mesh.ui.common.theme.GraphColors.Yellow
import com.geeksville.mesh.util.UnitConversions
import org.meshtastic.core.model.util.UnitConversions
@Suppress("MagicNumber")
enum class Environment(val color: Color) {

View File

@@ -19,8 +19,8 @@ package com.geeksville.mesh.model
import androidx.annotation.StringRes
import com.geeksville.mesh.MeshProtos.Routing
import com.geeksville.mesh.MessageStatus
import com.geeksville.mesh.database.entity.Reaction
import org.meshtastic.core.model.MessageStatus
import org.meshtastic.core.strings.R
@Suppress("CyclomaticComplexMethod")

View File

@@ -28,7 +28,6 @@ import androidx.lifecycle.viewModelScope
import androidx.navigation.toRoute
import com.geeksville.mesh.ConfigProtos.Config.DisplayConfig.DisplayUnits
import com.geeksville.mesh.CoroutineDispatchers
import com.geeksville.mesh.DataPacket
import com.geeksville.mesh.MeshProtos
import com.geeksville.mesh.MeshProtos.MeshPacket
import com.geeksville.mesh.MeshProtos.Position
@@ -59,6 +58,7 @@ import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
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

View File

@@ -26,12 +26,12 @@ import com.geeksville.mesh.TelemetryProtos.DeviceMetrics
import com.geeksville.mesh.TelemetryProtos.EnvironmentMetrics
import com.geeksville.mesh.TelemetryProtos.PowerMetrics
import com.geeksville.mesh.database.entity.NodeEntity
import com.geeksville.mesh.util.GPSFormat
import com.geeksville.mesh.util.UnitConversions.celsiusToFahrenheit
import com.geeksville.mesh.util.latLongToMeter
import com.geeksville.mesh.util.toDistanceString
import com.google.protobuf.ByteString
import com.google.protobuf.kotlin.isNotEmpty
import org.meshtastic.core.model.util.GPSFormat
import org.meshtastic.core.model.util.UnitConversions.celsiusToFahrenheit
import org.meshtastic.core.model.util.latLongToMeter
@Suppress("MagicNumber")
data class Node(
@@ -114,7 +114,7 @@ data class Node(
// @return bearing to the other position in degrees
fun bearing(o: Node?): Int? = when {
validPosition == null || o?.validPosition == null -> null
else -> com.geeksville.mesh.util.bearing(latitude, longitude, o.latitude, o.longitude).toInt()
else -> org.meshtastic.core.model.util.bearing(latitude, longitude, o.latitude, o.longitude).toInt()
}
fun gpsString(): String = GPSFormat.toDec(latitude, longitude)

View File

@@ -32,12 +32,10 @@ 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.DataPacket
import com.geeksville.mesh.IMeshService
import com.geeksville.mesh.LocalOnlyProtos.LocalConfig
import com.geeksville.mesh.LocalOnlyProtos.LocalModuleConfig
import com.geeksville.mesh.MeshProtos
import com.geeksville.mesh.Position
import com.geeksville.mesh.android.Logging
import com.geeksville.mesh.channel
import com.geeksville.mesh.channelSet
@@ -60,7 +58,6 @@ import com.geeksville.mesh.repository.radio.RadioInterfaceService
import com.geeksville.mesh.service.MeshServiceNotifications
import com.geeksville.mesh.service.ServiceAction
import com.geeksville.mesh.ui.node.components.NodeMenuAction
import com.geeksville.mesh.util.getShortDate
import com.geeksville.mesh.util.safeNumber
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
@@ -82,7 +79,10 @@ import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import org.meshtastic.core.datastore.UiPreferencesDataSource
import org.meshtastic.core.model.DataPacket
import org.meshtastic.core.model.DeviceHardware
import org.meshtastic.core.model.Position
import org.meshtastic.core.model.util.getShortDate
import org.meshtastic.core.prefs.ui.UiPrefs
import org.meshtastic.core.strings.R
import javax.inject.Inject

View File

@@ -21,7 +21,7 @@ import android.app.Application
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream
import org.meshtastic.core.network.model.NetworkDeviceHardware
import org.meshtastic.core.model.NetworkDeviceHardware
import javax.inject.Inject
class DeviceHardwareJsonDataSource @Inject constructor(private val application: Application) {

View File

@@ -22,7 +22,7 @@ import com.geeksville.mesh.database.entity.DeviceHardwareEntity
import com.geeksville.mesh.database.entity.asEntity
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.meshtastic.core.network.model.NetworkDeviceHardware
import org.meshtastic.core.model.NetworkDeviceHardware
import javax.inject.Inject
class DeviceHardwareLocalDataSource

View File

@@ -21,7 +21,7 @@ import android.app.Application
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream
import org.meshtastic.core.network.model.NetworkFirmwareReleases
import org.meshtastic.core.model.NetworkFirmwareReleases
import javax.inject.Inject
class FirmwareReleaseJsonDataSource @Inject constructor(private val application: Application) {

View File

@@ -25,7 +25,7 @@ import com.geeksville.mesh.database.entity.asEntity
import dagger.Lazy
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.meshtastic.core.network.model.NetworkFirmwareRelease
import org.meshtastic.core.model.NetworkFirmwareRelease
import javax.inject.Inject
class FirmwareReleaseLocalDataSource @Inject constructor(private val firmwareReleaseDaoLazy: Lazy<FirmwareReleaseDao>) {

View File

@@ -18,19 +18,19 @@
package com.geeksville.mesh.repository.bluetooth
import android.bluetooth.BluetoothDevice
import com.geeksville.mesh.util.anonymize
import org.meshtastic.core.model.util.anonymize
/**
* A snapshot in time of the state of the bluetooth subsystem.
*/
/** A snapshot in time of the state of the bluetooth subsystem. */
data class BluetoothState(
/** Whether we have adequate permissions to query bluetooth state */
val hasPermissions: Boolean = false,
/** If we have adequate permissions and bluetooth is enabled */
val enabled: Boolean = false,
/** If enabled, a list of the currently bonded devices */
val bondedDevices: List<BluetoothDevice> = emptyList()
val bondedDevices: List<BluetoothDevice> = emptyList(),
) {
override fun toString(): String =
"BluetoothState(hasPermissions=$hasPermissions, enabled=$enabled, bondedDevices=${bondedDevices.map { it.anonymize }})"
"BluetoothState(hasPermissions=$hasPermissions, enabled=$enabled, bondedDevices=${bondedDevices.map {
it.anonymize
}})"
}

View File

@@ -29,7 +29,6 @@ import com.geeksville.mesh.service.BLEConnectionClosing
import com.geeksville.mesh.service.BLEException
import com.geeksville.mesh.service.RadioNotConnectedException
import com.geeksville.mesh.service.SafeBluetooth
import com.geeksville.mesh.util.anonymize
import com.geeksville.mesh.util.exceptionReporter
import com.geeksville.mesh.util.ignoreException
import dagger.assisted.Assisted
@@ -37,6 +36,7 @@ import dagger.assisted.AssistedInject
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import org.meshtastic.core.model.util.anonymize
import java.lang.reflect.Method
import java.util.UUID

View File

@@ -19,24 +19,22 @@ package com.geeksville.mesh.repository.radio
import com.geeksville.mesh.android.Logging
import com.geeksville.mesh.repository.bluetooth.BluetoothRepository
import com.geeksville.mesh.util.anonymize
import org.meshtastic.core.model.util.anonymize
import javax.inject.Inject
/**
* Bluetooth backend implementation.
*/
class BluetoothInterfaceSpec @Inject constructor(
/** Bluetooth backend implementation. */
class BluetoothInterfaceSpec
@Inject
constructor(
private val factory: BluetoothInterfaceFactory,
private val bluetoothRepository: BluetoothRepository,
) : InterfaceSpec<BluetoothInterface>, Logging {
override fun createInterface(rest: String): BluetoothInterface {
return factory.create(rest)
}
) : InterfaceSpec<BluetoothInterface>,
Logging {
override fun createInterface(rest: String): BluetoothInterface = factory.create(rest)
/** Return true if this address is still acceptable. For BLE that means, still bonded */
override fun addressValid(rest: String): Boolean {
val allPaired = bluetoothRepository.state.value.bondedDevices
.map { it.address }.toSet()
val allPaired = bluetoothRepository.state.value.bondedDevices.map { it.address }.toSet()
return if (!allPaired.contains(rest)) {
warn("Ignoring stale bond to ${rest.anonymize}")
false

View File

@@ -21,10 +21,8 @@ import com.geeksville.mesh.AdminProtos
import com.geeksville.mesh.ChannelProtos
import com.geeksville.mesh.ConfigKt
import com.geeksville.mesh.ConfigProtos
import com.geeksville.mesh.DataPacket
import com.geeksville.mesh.MeshProtos
import com.geeksville.mesh.Portnums
import com.geeksville.mesh.Position
import com.geeksville.mesh.TelemetryProtos
import com.geeksville.mesh.android.Logging
import com.geeksville.mesh.channel
@@ -39,6 +37,8 @@ import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import kotlinx.coroutines.delay
import org.meshtastic.core.model.Channel
import org.meshtastic.core.model.DataPacket
import org.meshtastic.core.model.Position
import kotlin.random.Random
private val defaultLoRaConfig =

View File

@@ -31,7 +31,6 @@ import com.geeksville.mesh.concurrent.handledLaunch
import com.geeksville.mesh.repository.bluetooth.BluetoothRepository
import com.geeksville.mesh.repository.network.NetworkRepository
import com.geeksville.mesh.service.ConnectionState
import com.geeksville.mesh.util.anonymize
import com.geeksville.mesh.util.ignoreException
import com.geeksville.mesh.util.toRemoteExceptions
import kotlinx.coroutines.CoroutineScope
@@ -48,6 +47,7 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import org.meshtastic.core.model.util.anonymize
import org.meshtastic.core.prefs.radio.RadioPrefs
import javax.inject.Inject
import javax.inject.Singleton

View File

@@ -33,7 +33,6 @@ import com.geeksville.mesh.BuildConfig
import com.geeksville.mesh.ChannelProtos
import com.geeksville.mesh.ConfigProtos
import com.geeksville.mesh.CoroutineDispatchers
import com.geeksville.mesh.DataPacket
import com.geeksville.mesh.DeviceUIProtos
import com.geeksville.mesh.IMeshService
import com.geeksville.mesh.LocalOnlyProtos.LocalConfig
@@ -42,14 +41,9 @@ import com.geeksville.mesh.MeshProtos
import com.geeksville.mesh.MeshProtos.FromRadio.PayloadVariantCase
import com.geeksville.mesh.MeshProtos.MeshPacket
import com.geeksville.mesh.MeshProtos.ToRadio
import com.geeksville.mesh.MeshUser
import com.geeksville.mesh.MessageStatus
import com.geeksville.mesh.ModuleConfigProtos
import com.geeksville.mesh.MyNodeInfo
import com.geeksville.mesh.NodeInfo
import com.geeksville.mesh.PaxcountProtos
import com.geeksville.mesh.Portnums
import com.geeksville.mesh.Position
import com.geeksville.mesh.StoreAndForwardProtos
import com.geeksville.mesh.TelemetryProtos
import com.geeksville.mesh.TelemetryProtos.LocalStats
@@ -68,7 +62,6 @@ import com.geeksville.mesh.database.entity.NodeEntity
import com.geeksville.mesh.database.entity.Packet
import com.geeksville.mesh.database.entity.ReactionEntity
import com.geeksville.mesh.fromRadio
import com.geeksville.mesh.model.DeviceVersion
import com.geeksville.mesh.model.NO_DEVICE_SELECTED
import com.geeksville.mesh.model.Node
import com.geeksville.mesh.position
@@ -78,10 +71,7 @@ import com.geeksville.mesh.repository.network.MQTTRepository
import com.geeksville.mesh.repository.radio.RadioInterfaceService
import com.geeksville.mesh.telemetry
import com.geeksville.mesh.user
import com.geeksville.mesh.util.anonymize
import com.geeksville.mesh.util.ignoreException
import com.geeksville.mesh.util.toOneLineString
import com.geeksville.mesh.util.toPIIString
import com.geeksville.mesh.util.toRemoteExceptions
import com.google.protobuf.ByteString
import com.google.protobuf.InvalidProtocolBufferException
@@ -100,7 +90,17 @@ import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import org.meshtastic.core.model.DataPacket
import org.meshtastic.core.model.DeviceVersion
import org.meshtastic.core.model.MeshUser
import org.meshtastic.core.model.MessageStatus
import org.meshtastic.core.model.MyNodeInfo
import org.meshtastic.core.model.NodeInfo
import org.meshtastic.core.model.Position
import org.meshtastic.core.model.getFullTracerouteResponse
import org.meshtastic.core.model.util.anonymize
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.strings.R
@@ -2155,8 +2155,9 @@ class MeshService :
toRemoteExceptions {
if (p.id == 0) p.id = generatePacketId()
val bytes = p.bytes!!
info(
"sendData dest=${p.to}, id=${p.id} <- ${p.bytes!!.size} bytes" +
"sendData dest=${p.to}, id=${p.id} <- ${bytes.size} bytes" +
" (connectionState=$connectionState)",
)
@@ -2164,7 +2165,7 @@ class MeshService :
throw Exception("Port numbers must be non-zero!") // we are now more strict
}
if (p.bytes.size >= MeshProtos.Constants.DATA_PAYLOAD_LEN.number) {
if (bytes.size >= MeshProtos.Constants.DATA_PAYLOAD_LEN.number) {
p.status = MessageStatus.ERROR
throw RemoteException("Message too long")
} else {
@@ -2188,7 +2189,7 @@ class MeshService :
GeeksvilleApplication.analytics.track(
"data_send",
DataPair("num_bytes", p.bytes.size),
DataPair("num_bytes", bytes.size),
DataPair("type", p.dataType),
)

View File

@@ -20,9 +20,9 @@ package com.geeksville.mesh.service
import android.content.Context
import android.content.Intent
import android.os.Parcelable
import com.geeksville.mesh.DataPacket
import com.geeksville.mesh.MessageStatus
import com.geeksville.mesh.NodeInfo
import org.meshtastic.core.model.DataPacket
import org.meshtastic.core.model.MessageStatus
import org.meshtastic.core.model.NodeInfo
class MeshServiceBroadcasts(
private val context: Context,

View File

@@ -40,7 +40,7 @@ import com.geeksville.mesh.R.raw
import com.geeksville.mesh.TelemetryProtos.LocalStats
import com.geeksville.mesh.database.entity.NodeEntity
import com.geeksville.mesh.service.ReplyReceiver.Companion.KEY_TEXT_REPLY
import com.geeksville.mesh.util.formatUptime
import org.meshtastic.core.model.util.formatUptime
import org.meshtastic.core.navigation.DEEP_LINK_BASE_URI
import org.meshtastic.core.strings.R

View File

@@ -17,11 +17,9 @@
package com.geeksville.mesh.service
import com.geeksville.mesh.DataPacket
import com.geeksville.mesh.MeshProtos
import com.geeksville.mesh.MeshProtos.MeshPacket
import com.geeksville.mesh.MeshProtos.ToRadio
import com.geeksville.mesh.MessageStatus
import com.geeksville.mesh.android.BuildUtils.debug
import com.geeksville.mesh.android.BuildUtils.errormsg
import com.geeksville.mesh.android.BuildUtils.info
@@ -31,8 +29,6 @@ import com.geeksville.mesh.database.PacketRepository
import com.geeksville.mesh.database.entity.MeshLog
import com.geeksville.mesh.fromRadio
import com.geeksville.mesh.repository.radio.RadioInterfaceService
import com.geeksville.mesh.util.toOneLineString
import com.geeksville.mesh.util.toPIIString
import dagger.Lazy
import java8.util.concurrent.CompletableFuture
import kotlinx.coroutines.CoroutineScope
@@ -40,6 +36,10 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.withTimeoutOrNull
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 java.util.UUID
import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.TimeUnit

View File

@@ -19,24 +19,22 @@ package com.geeksville.mesh.service
import android.content.BroadcastReceiver
import androidx.core.app.RemoteInput
import com.geeksville.mesh.DataPacket
import dagger.hilt.android.AndroidEntryPoint
import jakarta.inject.Inject
import org.meshtastic.core.model.DataPacket
/**
* A [BroadcastReceiver] that handles inline replies from notifications.
*
* This receiver is triggered when a user replies to a message directly from a notification.
* It extracts the reply text and the contact key from the intent, sends the message
* using the [ServiceRepository], and then cancels the original notification.
* This receiver is triggered when a user replies to a message directly from a notification. It extracts the reply text
* and the contact key from the intent, sends the message using the [ServiceRepository], and then cancels the original
* notification.
*/
@AndroidEntryPoint
class ReplyReceiver : BroadcastReceiver() {
@Inject
lateinit var serviceRepository: ServiceRepository
@Inject lateinit var serviceRepository: ServiceRepository
@Inject
lateinit var meshServiceNotifications: MeshServiceNotifications
@Inject lateinit var meshServiceNotifications: MeshServiceNotifications
companion object {
const val REPLY_ACTION = "com.geeksville.mesh.REPLY_ACTION"
@@ -57,9 +55,7 @@ class ReplyReceiver : BroadcastReceiver() {
if (remoteInput != null) {
val contactKey = intent.getStringExtra(CONTACT_KEY) ?: ""
val message = remoteInput.getCharSequence(
KEY_TEXT_REPLY
)?.toString() ?: ""
val message = remoteInput.getCharSequence(KEY_TEXT_REPLY)?.toString() ?: ""
sendMessage(message, contactKey)
MeshServiceNotifications(context).cancelMessageNotification(contactKey)
}

View File

@@ -80,7 +80,6 @@ import com.geeksville.mesh.android.AddNavigationTracking
import com.geeksville.mesh.android.BuildUtils.debug
import com.geeksville.mesh.android.setAttributes
import com.geeksville.mesh.model.BTScanModel
import com.geeksville.mesh.model.DeviceVersion
import com.geeksville.mesh.model.Node
import com.geeksville.mesh.model.UIViewModel
import com.geeksville.mesh.navigation.channelsGraph
@@ -112,6 +111,7 @@ import com.google.accompanist.permissions.isGranted
import com.google.accompanist.permissions.rememberPermissionState
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import org.meshtastic.core.model.DeviceVersion
import org.meshtastic.core.navigation.ConnectionsRoutes
import org.meshtastic.core.navigation.ContactsRoutes
import org.meshtastic.core.navigation.MapRoutes

View File

@@ -19,7 +19,6 @@ package com.geeksville.mesh.ui.common.preview
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import com.geeksville.mesh.ConfigProtos
import com.geeksville.mesh.DeviceMetrics.Companion.currentTime
import com.geeksville.mesh.MeshProtos
import com.geeksville.mesh.deviceMetrics
import com.geeksville.mesh.environmentMetrics
@@ -28,118 +27,132 @@ import com.geeksville.mesh.paxcount
import com.geeksville.mesh.position
import com.geeksville.mesh.user
import com.google.protobuf.ByteString
import org.meshtastic.core.model.DeviceMetrics.Companion.currentTime
import kotlin.random.Random
class NodePreviewParameterProvider : PreviewParameterProvider<Node> {
val mickeyMouse = Node(
num = 1955,
user = user {
id = "mickeyMouseId"
longName = "Mickey Mouse"
shortName = "MM"
hwModel = MeshProtos.HardwareModel.TBEAM
role = ConfigProtos.Config.DeviceConfig.Role.ROUTER
},
position = position {
latitudeI = 338125110
longitudeI = -1179189760
altitude = 138
satsInView = 4
},
lastHeard = currentTime(),
channel = 0,
snr = 12.5F,
rssi = -42,
deviceMetrics = deviceMetrics {
channelUtilization = 2.4F
airUtilTx = 3.5F
batteryLevel = 85
voltage = 3.7F
uptimeSeconds = 3600
},
isFavorite = true,
hopsAway = 0
)
val mickeyMouse =
Node(
num = 1955,
user =
user {
id = "mickeyMouseId"
longName = "Mickey Mouse"
shortName = "MM"
hwModel = MeshProtos.HardwareModel.TBEAM
role = ConfigProtos.Config.DeviceConfig.Role.ROUTER
},
position =
position {
latitudeI = 338125110
longitudeI = -1179189760
altitude = 138
satsInView = 4
},
lastHeard = currentTime(),
channel = 0,
snr = 12.5F,
rssi = -42,
deviceMetrics =
deviceMetrics {
channelUtilization = 2.4F
airUtilTx = 3.5F
batteryLevel = 85
voltage = 3.7F
uptimeSeconds = 3600
},
isFavorite = true,
hopsAway = 0,
)
val minnieMouse = mickeyMouse.copy(
num = Random.nextInt(),
user = user {
longName = "Minnie Mouse"
shortName = "MiMo"
id = "minnieMouseId"
hwModel = MeshProtos.HardwareModel.HELTEC_V3
},
snr = 12.5F,
rssi = -42,
position = position {},
hopsAway = 1
)
val minnieMouse =
mickeyMouse.copy(
num = Random.nextInt(),
user =
user {
longName = "Minnie Mouse"
shortName = "MiMo"
id = "minnieMouseId"
hwModel = MeshProtos.HardwareModel.HELTEC_V3
},
snr = 12.5F,
rssi = -42,
position = position {},
hopsAway = 1,
)
private val donaldDuck = Node(
num = Random.nextInt(),
position = position {
latitudeI = 338052347
longitudeI = -1179208460
altitude = 121
satsInView = 66
},
lastHeard = currentTime() - 300,
channel = 0,
snr = 12.5F,
rssi = -42,
deviceMetrics = deviceMetrics {
channelUtilization = 2.4F
airUtilTx = 3.5F
batteryLevel = 85
voltage = 3.7F
uptimeSeconds = 3600
},
user = user {
id = "donaldDuckId"
longName = "Donald Duck, the Grand Duck of the Ducks"
shortName = "DoDu"
hwModel = MeshProtos.HardwareModel.HELTEC_V3
publicKey = ByteString.copyFrom(ByteArray(32) { 1 })
},
environmentMetrics = environmentMetrics {
temperature = 28.0F
relativeHumidity = 50.0F
barometricPressure = 1013.25F
gasResistance = 0.0F
voltage = 3.7F
current = 0.0F
iaq = 100
},
paxcounter = paxcount {
wifi = 30
ble = 39
uptime = 420
},
isFavorite = true,
hopsAway = 2
)
private val donaldDuck =
Node(
num = Random.nextInt(),
position =
position {
latitudeI = 338052347
longitudeI = -1179208460
altitude = 121
satsInView = 66
},
lastHeard = currentTime() - 300,
channel = 0,
snr = 12.5F,
rssi = -42,
deviceMetrics =
deviceMetrics {
channelUtilization = 2.4F
airUtilTx = 3.5F
batteryLevel = 85
voltage = 3.7F
uptimeSeconds = 3600
},
user =
user {
id = "donaldDuckId"
longName = "Donald Duck, the Grand Duck of the Ducks"
shortName = "DoDu"
hwModel = MeshProtos.HardwareModel.HELTEC_V3
publicKey = ByteString.copyFrom(ByteArray(32) { 1 })
},
environmentMetrics =
environmentMetrics {
temperature = 28.0F
relativeHumidity = 50.0F
barometricPressure = 1013.25F
gasResistance = 0.0F
voltage = 3.7F
current = 0.0F
iaq = 100
},
paxcounter =
paxcount {
wifi = 30
ble = 39
uptime = 420
},
isFavorite = true,
hopsAway = 2,
)
private val unknown = donaldDuck.copy(
user = user {
id = "myId"
longName = "Meshtastic myId"
shortName = "myId"
hwModel = MeshProtos.HardwareModel.UNSET
},
environmentMetrics = environmentMetrics {},
paxcounter = paxcount {},
)
private val unknown =
donaldDuck.copy(
user =
user {
id = "myId"
longName = "Meshtastic myId"
shortName = "myId"
hwModel = MeshProtos.HardwareModel.UNSET
},
environmentMetrics = environmentMetrics {},
paxcounter = paxcount {},
)
private val almostNothing = Node(
num = Random.nextInt(),
)
private val almostNothing = Node(num = Random.nextInt())
override val values: Sequence<Node>
get() = sequenceOf(
mickeyMouse, // "this" node
unknown,
almostNothing,
minnieMouse,
donaldDuck
)
get() =
sequenceOf(
mickeyMouse, // "this" node
unknown,
almostNothing,
minnieMouse,
donaldDuck,
)
}

View File

@@ -95,7 +95,6 @@ import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.geeksville.mesh.AppOnlyProtos
import com.geeksville.mesh.DataPacket
import com.geeksville.mesh.database.entity.QuickChatAction
import com.geeksville.mesh.model.Message
import com.geeksville.mesh.model.Node
@@ -108,6 +107,7 @@ import com.geeksville.mesh.ui.node.components.NodeMenuAction
import com.geeksville.mesh.ui.sharing.SharedContactDialog
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import org.meshtastic.core.model.DataPacket
import org.meshtastic.core.strings.R
import java.nio.charset.StandardCharsets

View File

@@ -47,7 +47,6 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.geeksville.mesh.MessageStatus
import com.geeksville.mesh.database.entity.Reaction
import com.geeksville.mesh.model.Message
import com.geeksville.mesh.model.UIViewModel
@@ -58,6 +57,7 @@ import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.launch
import org.meshtastic.core.model.MessageStatus
import org.meshtastic.core.strings.R
@Composable

View File

@@ -39,8 +39,8 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import com.geeksville.mesh.MessageStatus
import com.geeksville.mesh.ui.common.components.EmojiPickerDialog
import org.meshtastic.core.model.MessageStatus
import org.meshtastic.core.strings.R
@Composable

View File

@@ -49,7 +49,6 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.PreviewLightDark
import androidx.compose.ui.unit.dp
import com.geeksville.mesh.MessageStatus
import com.geeksville.mesh.database.entity.Reaction
import com.geeksville.mesh.model.Message
import com.geeksville.mesh.model.Node
@@ -61,6 +60,7 @@ import com.geeksville.mesh.ui.common.theme.AppTheme
import com.geeksville.mesh.ui.common.theme.MessageItemColors
import com.geeksville.mesh.ui.node.components.NodeChip
import com.geeksville.mesh.ui.node.components.NodeMenuAction
import org.meshtastic.core.model.MessageStatus
import org.meshtastic.core.strings.R
@Suppress("LongMethod", "CyclomaticComplexMethod")

View File

@@ -48,7 +48,6 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.geeksville.mesh.EnvironmentMetrics
import com.geeksville.mesh.TelemetryProtos
import com.geeksville.mesh.TelemetryProtos.Telemetry
import com.geeksville.mesh.copy
@@ -60,7 +59,7 @@ import com.geeksville.mesh.ui.common.components.OptionLabel
import com.geeksville.mesh.ui.common.components.SlidingSelector
import com.geeksville.mesh.ui.metrics.CommonCharts.DATE_TIME_FORMAT
import com.geeksville.mesh.ui.metrics.CommonCharts.MS_PER_SEC
import com.geeksville.mesh.util.UnitConversions.celsiusToFahrenheit
import org.meshtastic.core.model.util.UnitConversions.celsiusToFahrenheit
import org.meshtastic.core.strings.R
@Composable

View File

@@ -57,7 +57,7 @@ import com.geeksville.mesh.TelemetryProtos
import com.geeksville.mesh.model.MetricsViewModel
import com.geeksville.mesh.ui.common.theme.AppTheme
import com.geeksville.mesh.ui.metrics.CommonCharts.DATE_TIME_FORMAT
import com.geeksville.mesh.util.formatUptime
import org.meshtastic.core.model.util.formatUptime
import org.meshtastic.core.strings.R
import java.text.DecimalFormat

View File

@@ -60,7 +60,7 @@ import com.geeksville.mesh.model.MetricsViewModel
import com.geeksville.mesh.model.TimeFrame
import com.geeksville.mesh.ui.common.components.OptionLabel
import com.geeksville.mesh.ui.common.components.SlidingSelector
import com.geeksville.mesh.util.formatUptime
import org.meshtastic.core.model.util.formatUptime
import org.meshtastic.core.strings.R
import java.text.DateFormat
import java.util.Date

View File

@@ -122,11 +122,9 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import coil3.compose.AsyncImage
import coil3.request.ImageRequest
import com.geeksville.mesh.ConfigProtos
import com.geeksville.mesh.DataPacket
import com.geeksville.mesh.MeshProtos
import com.geeksville.mesh.database.entity.FirmwareRelease
import com.geeksville.mesh.database.entity.asDeviceVersion
import com.geeksville.mesh.model.DeviceVersion
import com.geeksville.mesh.model.MetricsState
import com.geeksville.mesh.model.MetricsViewModel
import com.geeksville.mesh.model.Node
@@ -148,16 +146,18 @@ import com.geeksville.mesh.ui.settings.components.SettingsItem
import com.geeksville.mesh.ui.settings.components.SettingsItemDetail
import com.geeksville.mesh.ui.settings.components.SettingsItemSwitch
import com.geeksville.mesh.ui.sharing.SharedContactDialog
import com.geeksville.mesh.util.UnitConversions
import com.geeksville.mesh.util.UnitConversions.toTempString
import com.geeksville.mesh.util.formatAgo
import com.geeksville.mesh.util.formatUptime
import com.geeksville.mesh.util.thenIf
import com.geeksville.mesh.util.toDistanceString
import com.geeksville.mesh.util.toSmallDistanceString
import com.geeksville.mesh.util.toSpeedString
import com.mikepenz.markdown.m3.Markdown
import org.meshtastic.core.model.DataPacket
import org.meshtastic.core.model.DeviceHardware
import org.meshtastic.core.model.DeviceVersion
import org.meshtastic.core.model.util.UnitConversions
import org.meshtastic.core.model.util.UnitConversions.toTempString
import org.meshtastic.core.model.util.formatAgo
import org.meshtastic.core.model.util.formatUptime
import org.meshtastic.core.navigation.NodeDetailRoutes
import org.meshtastic.core.navigation.Route
import org.meshtastic.core.navigation.SettingsRoutes

View File

@@ -46,8 +46,6 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.geeksville.mesh.DataPacket
import com.geeksville.mesh.model.DeviceVersion
import com.geeksville.mesh.model.Node
import com.geeksville.mesh.model.UIViewModel
import com.geeksville.mesh.service.ConnectionState
@@ -59,6 +57,8 @@ import com.geeksville.mesh.ui.node.components.NodeMenuAction
import com.geeksville.mesh.ui.sharing.AddContactFAB
import com.geeksville.mesh.ui.sharing.SharedContactDialog
import com.geeksville.mesh.ui.sharing.supportsQrCodeSharing
import org.meshtastic.core.model.DataPacket
import org.meshtastic.core.model.DeviceVersion
import org.meshtastic.core.strings.R
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3ExpressiveApi::class)

View File

@@ -32,7 +32,7 @@ import androidx.compose.ui.tooling.preview.PreviewLightDark
import androidx.compose.ui.unit.dp
import com.geeksville.mesh.R
import com.geeksville.mesh.ui.common.theme.AppTheme
import com.geeksville.mesh.util.formatAgo
import org.meshtastic.core.model.util.formatAgo
@Composable
fun LastHeardInfo(modifier: Modifier = Modifier, lastHeard: Int, currentTimeMillis: Long) {

View File

@@ -43,8 +43,8 @@ import androidx.core.net.toUri
import com.geeksville.mesh.android.BuildUtils.debug
import com.geeksville.mesh.ui.common.theme.AppTheme
import com.geeksville.mesh.ui.common.theme.HyperlinkBlue
import com.geeksville.mesh.util.GPSFormat
import kotlinx.coroutines.launch
import org.meshtastic.core.model.util.GPSFormat
import java.net.URLEncoder
@OptIn(ExperimentalFoundationApi::class)

View File

@@ -25,14 +25,12 @@ 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.Position
import com.geeksville.mesh.android.Logging
import com.geeksville.mesh.database.MeshLogRepository
import com.geeksville.mesh.database.NodeRepository
import com.geeksville.mesh.database.entity.MyNodeEntity
import com.geeksville.mesh.model.Node
import com.geeksville.mesh.repository.datastore.RadioConfigRepository
import com.geeksville.mesh.util.positionToMeter
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
@@ -48,6 +46,8 @@ import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
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 java.io.BufferedWriter
import java.io.FileNotFoundException

View File

@@ -41,7 +41,6 @@ import com.geeksville.mesh.IMeshService
import com.geeksville.mesh.MeshProtos
import com.geeksville.mesh.ModuleConfigProtos
import com.geeksville.mesh.Portnums
import com.geeksville.mesh.Position
import com.geeksville.mesh.android.GeeksvilleApplication
import com.geeksville.mesh.android.Logging
import com.geeksville.mesh.android.isAnalyticsAvailable
@@ -74,6 +73,7 @@ import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.json.JSONObject
import org.meshtastic.core.model.Position
import org.meshtastic.core.navigation.SettingsRoutes
import org.meshtastic.core.prefs.analytics.AnalyticsPrefs
import org.meshtastic.core.prefs.map.MapConsentPrefs

View File

@@ -45,7 +45,7 @@ import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.geeksville.mesh.model.DeviceVersion
import org.meshtastic.core.model.DeviceVersion
import org.meshtastic.core.strings.R
/**

View File

@@ -73,7 +73,6 @@ import androidx.navigation.NavController
import com.geeksville.mesh.ChannelProtos.ChannelSettings
import com.geeksville.mesh.ConfigProtos.Config.LoRaConfig
import com.geeksville.mesh.channelSettings
import com.geeksville.mesh.model.DeviceVersion
import com.geeksville.mesh.ui.common.components.PreferenceCategory
import com.geeksville.mesh.ui.common.components.PreferenceFooter
import com.geeksville.mesh.ui.common.components.SecurityIcon
@@ -82,6 +81,7 @@ import com.geeksville.mesh.ui.common.components.dragDropItemsIndexed
import com.geeksville.mesh.ui.common.components.rememberDragDropState
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
import org.meshtastic.core.model.Channel
import org.meshtastic.core.model.DeviceVersion
import org.meshtastic.core.strings.R
@Composable

View File

@@ -41,7 +41,6 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavController
import com.geeksville.mesh.ConfigProtos
import com.geeksville.mesh.ConfigProtos.Config.PositionConfig
import com.geeksville.mesh.Position
import com.geeksville.mesh.config
import com.geeksville.mesh.copy
import com.geeksville.mesh.ui.common.components.BitwisePreference
@@ -53,6 +52,7 @@ import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.rememberPermissionState
import kotlinx.coroutines.launch
import org.meshtastic.core.model.Position
import org.meshtastic.core.strings.R
@OptIn(ExperimentalPermissionsApi::class)

View File

@@ -30,14 +30,13 @@ import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavController
import com.geeksville.mesh.copy
import com.geeksville.mesh.model.DeviceVersion
import com.geeksville.mesh.model.isUnmessageableRole
import com.geeksville.mesh.ui.common.components.EditTextPreference
import com.geeksville.mesh.ui.common.components.PreferenceCategory
import com.geeksville.mesh.ui.common.components.RegularPreference
import com.geeksville.mesh.ui.common.components.SwitchPreference
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
import com.geeksville.mesh.user
import org.meshtastic.core.model.DeviceVersion
import org.meshtastic.core.strings.R
@Composable

View File

@@ -54,7 +54,6 @@ import com.geeksville.mesh.AdminProtos
import com.geeksville.mesh.MeshProtos
import com.geeksville.mesh.android.BuildUtils.debug
import com.geeksville.mesh.android.BuildUtils.errormsg
import com.geeksville.mesh.model.DeviceVersion
import com.geeksville.mesh.model.Node
import com.geeksville.mesh.model.UIViewModel
import com.geeksville.mesh.ui.common.components.CopyIconButton
@@ -70,6 +69,7 @@ import com.google.zxing.WriterException
import com.journeyapps.barcodescanner.BarcodeEncoder
import com.journeyapps.barcodescanner.ScanContract
import com.journeyapps.barcodescanner.ScanOptions
import org.meshtastic.core.model.DeviceVersion
import org.meshtastic.core.strings.R
import timber.log.Timber
import java.net.MalformedURLException

View File

@@ -23,6 +23,9 @@ import org.junit.After
import org.junit.Assert
import org.junit.Before
import org.junit.Test
import org.meshtastic.core.model.MeshUser
import org.meshtastic.core.model.NodeInfo
import org.meshtastic.core.model.Position
import java.util.Locale
class NodeInfoTest {

View File

@@ -19,6 +19,7 @@ package com.geeksville.mesh
import org.junit.Assert
import org.junit.Test
import org.meshtastic.core.model.Position
class PositionTest {
@Test
@@ -35,5 +36,4 @@ class PositionTest {
val position = Position(37.1, 121.1, 35)
Assert.assertTrue(position.time != 0)
}
}

View File

@@ -17,17 +17,17 @@
package com.geeksville.mesh.model
import org.junit.Assert.*
import org.junit.Assert.assertEquals
import org.junit.Test
import org.meshtastic.core.model.DeviceVersion
class DeviceVersionTest {
/** make sure we match the python and device code behavior */
@Test
fun canParse() {
assertEquals(10000, DeviceVersion("1.0.0").asInt)
assertEquals(10101, DeviceVersion("1.1.1").asInt)
assertEquals(12357, DeviceVersion("1.23.57").asInt)
assertEquals(12357, DeviceVersion("1.23.57.abde123").asInt)
}
}
}

View File

@@ -19,9 +19,9 @@ package com.geeksville.mesh.ui.metrics
import com.geeksville.mesh.TelemetryProtos
import com.geeksville.mesh.copy
import com.geeksville.mesh.util.UnitConversions.celsiusToFahrenheit
import org.junit.Assert.assertEquals
import org.junit.Test
import org.meshtastic.core.model.util.UnitConversions.celsiusToFahrenheit
class EnvironmentMetricsTest {

View File

@@ -19,8 +19,19 @@ plugins {
alias(libs.plugins.kover)
alias(libs.plugins.meshtastic.android.library)
alias(libs.plugins.meshtastic.kotlinx.serialization)
alias(libs.plugins.kotlin.parcelize)
}
android { namespace = "org.meshtastic.core.model" }
android {
buildFeatures {
buildConfig = true
aidl = true
}
namespace = "org.meshtastic.core.model"
}
dependencies { implementation(projects.core.proto) }
dependencies {
implementation(projects.core.proto)
implementation(projects.core.strings)
implementation(libs.timber)
}

View File

@@ -0,0 +1,3 @@
package org.meshtastic.core.model;
parcelable DataPacket;

View File

@@ -0,0 +1,3 @@
package org.meshtastic.core.model;
parcelable MeshUser;

View File

@@ -0,0 +1,3 @@
package org.meshtastic.core.model;
parcelable MyNodeInfo;

View File

@@ -0,0 +1,3 @@
package org.meshtastic.core.model;
parcelable NodeInfo;

View File

@@ -0,0 +1,3 @@
package org.meshtastic.core.model;
parcelable Position;

View File

@@ -15,24 +15,23 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh
package org.meshtastic.core.model
import android.os.Parcel
import android.os.Parcelable
import com.geeksville.mesh.MeshProtos
import com.geeksville.mesh.Portnums
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? {
return if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.TIRAMISU) {
/** 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 {
@@ -41,17 +40,16 @@ enum class MessageStatus : Parcelable {
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
ERROR, // We received back a nak, message not delivered
}
/**
* A parcelable version of the protobuf MeshPacket + Data subpacket.
*/
/** 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?,
val dataType: Int, // A port number for this packet (formerly called DataType, see portnums.proto for new usage instructions)
// 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
@@ -62,62 +60,65 @@ data class DataPacket(
var hopStart: Int = 0,
var snr: Float = 0f,
var rssi: Int = 0,
var replyId: Int? = null // If this is a reply to a previous message, this is the ID of that message
var replyId: Int? = null, // If this is a reply to a previous message, this is the ID of that message
) : Parcelable {
/**
* If there was an error with this message, this string describes what was wrong.
*/
/** 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, replyId: Int? = null) : this(
/** Syntactic sugar to make it easy to create text messages */
constructor(
to: String?,
channel: Int,
text: String,
replyId: Int? = null,
) : this(
to = to,
bytes = text.encodeToByteArray(),
dataType = Portnums.PortNum.TEXT_MESSAGE_APP_VALUE,
channel = channel,
replyId = replyId ?: 0
replyId = replyId ?: 0,
)
/**
* If this is a text message, return the string, otherwise null
*/
/** 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
}
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
}
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,
)
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
}
get() =
if (dataType == Portnums.PortNum.WAYPOINT_APP_VALUE) {
MeshProtos.Waypoint.parseFrom(bytes)
} else {
null
}
val hopsAway: Int
get() = if (hopStart == 0 || hopLimit > hopStart) -1 else hopStart - hopLimit
// Autogenerated comparision, because we have a byte array
constructor(parcel: Parcel) : this(
constructor(
parcel: Parcel,
) : this(
parcel.readString(),
parcel.createByteArray(),
parcel.readInt(),
@@ -131,7 +132,7 @@ data class DataPacket(
parcel.readInt(),
parcel.readFloat(),
parcel.readInt(),
parcel.readInt().let { if (it == 0) null else it }
parcel.readInt().let { if (it == 0) null else it },
)
@Suppress("CyclomaticComplexMethod")
@@ -194,9 +195,7 @@ data class DataPacket(
parcel.writeInt(replyId ?: 0)
}
override fun describeContents(): Int {
return 0
}
override fun describeContents(): Int = 0
// Update our object from our parcel (used for inout parameters
fun readFromParcel(parcel: Parcel) {
@@ -232,15 +231,12 @@ data class DataPacket(
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 {
return DataPacket(parcel)
}
@Suppress("MagicNumber")
fun idToDefaultNodeNum(id: String?): Int? = runCatching { id?.toLong(16)?.toInt() }.getOrNull()
override fun newArray(size: Int): Array<DataPacket?> {
return arrayOfNulls(size)
}
override fun createFromParcel(parcel: Parcel): DataPacket = DataPacket(parcel)
override fun newArray(size: Int): Array<DataPacket?> = arrayOfNulls(size)
}
}

View File

@@ -15,37 +15,35 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh.model
package org.meshtastic.core.model
import com.geeksville.mesh.android.Logging
import timber.log.Timber
/**
* Provide structured access to parse and compare device version strings
*/
data class DeviceVersion(val asString: String) : Comparable<DeviceVersion>, Logging {
/** Provide structured access to parse and compare device version strings */
data class DeviceVersion(val asString: String) : Comparable<DeviceVersion> {
@Suppress("TooGenericExceptionCaught", "SwallowedException")
val asInt
get() = try {
verStringToInt(asString)
} catch (e: Exception) {
warn("Exception while parsing version '$asString', assuming version 0")
0
}
get() =
try {
verStringToInt(asString)
} catch (e: Exception) {
Timber.w("Exception while parsing version '$asString', assuming version 0")
0
}
/**
* Convert a version string of the form 1.23.57 to a comparable integer of
* the form 12357.
* Convert a version string of the form 1.23.57 to a comparable integer of the form 12357.
*
* Or throw an exception if the string can not be parsed
*/
@Suppress("TooGenericExceptionThrown", "MagicNumber")
private fun verStringToInt(s: String): Int {
// Allow 1 to two digits per match
val match =
Regex("(\\d{1,2}).(\\d{1,2}).(\\d{1,2})").find(s)
?: throw Exception("Can't parse version $s")
val match = Regex("(\\d{1,2}).(\\d{1,2}).(\\d{1,2})").find(s) ?: throw Exception("Can't parse version $s")
val (major, minor, build) = match.destructured
return major.toInt() * 10000 + minor.toInt() * 100 + build.toInt()
}
override fun compareTo(other: DeviceVersion): Int = asInt - other.asInt
}
}

View File

@@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh
package org.meshtastic.core.model
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
@@ -39,5 +39,6 @@ data class MyNodeInfo(
val deviceId: String?,
) : Parcelable {
/** A human readable description of the software/hardware version */
val firmwareString: String get() = "$model $firmwareVersion"
val firmwareString: String
get() = "$model $firmwareVersion"
}

View File

@@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.network.model
package org.meshtastic.core.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

View File

@@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.network.model
package org.meshtastic.core.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

View File

@@ -15,15 +15,18 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh
package org.meshtastic.core.model
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 com.geeksville.mesh.util.onlineTimeThreshold
import com.geeksville.mesh.ConfigProtos
import com.geeksville.mesh.MeshProtos
import com.geeksville.mesh.TelemetryProtos
import kotlinx.parcelize.Parcelize
import org.meshtastic.core.model.util.anonymize
import org.meshtastic.core.model.util.bearing
import org.meshtastic.core.model.util.latLongToMeter
import org.meshtastic.core.model.util.onlineTimeThreshold
//
// model objects that directly map to the corresponding protobufs
@@ -74,6 +77,7 @@ data class Position(
val precisionBits: Int = 0,
) : Parcelable {
@Suppress("MagicNumber")
companion object {
// / Convert to a double representation of degrees
fun degD(i: Int) = i * 1e-7
@@ -109,6 +113,7 @@ data class Position(
fun bearing(o: Position) = bearing(latitude, longitude, o.latitude, o.longitude)
// If GPS gives a crap position don't crash our app
@Suppress("MagicNumber")
fun isValid(): Boolean = latitude != 0.0 &&
longitude != 0.0 &&
(latitude >= -90 && latitude <= 90.0) &&
@@ -128,6 +133,7 @@ data class DeviceMetrics(
val uptimeSeconds: Int,
) : Parcelable {
companion object {
@Suppress("MagicNumber")
fun currentTime() = (System.currentTimeMillis() / 1000).toInt()
}
@@ -153,6 +159,7 @@ data class EnvironmentMetrics(
val lux: Float? = null,
val uvLux: Float? = null,
) : Parcelable {
@Suppress("MagicNumber")
companion object {
fun currentTime() = (System.currentTimeMillis() / 1000).toInt()
@@ -189,6 +196,7 @@ data class NodeInfo(
var hopsAway: Int = 0,
) : Parcelable {
@Suppress("MagicNumber")
val colors: Pair<Int, Int>
get() { // returns foreground and background @ColorInt for each 'num'
val r = (num and 0xFF0000) shr 16
@@ -204,6 +212,7 @@ data class NodeInfo(
val voltage
get() = deviceMetrics?.voltage
@Suppress("ImplicitDefaultLocale")
val batteryStr
get() = if (batteryLevel in 1..100) String.format("%d%%", batteryLevel) else ""
@@ -234,6 +243,7 @@ data class NodeInfo(
}
// / @return a nice human readable string for the distance, or null for unknown
@Suppress("MagicNumber")
fun distanceStr(o: NodeInfo?, prefUnits: Int = 0) = distance(o)?.let { dist ->
when {
dist == 0 -> null // same point

View File

@@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh.util
package org.meshtastic.core.model.util
import java.text.DateFormat
import java.util.Date
@@ -58,7 +58,8 @@ private fun formatUptime(seconds: Long): String {
"${hours}h".takeIf { hours > 0 },
"${minutes}m".takeIf { minutes > 0 },
"${secs}s".takeIf { secs > 0 },
).joinToString(" ")
)
.joinToString(" ")
}
@Suppress("MagicNumber")

View File

@@ -15,12 +15,12 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh.util
package org.meshtastic.core.model.util
import android.widget.EditText
import com.geeksville.mesh.BuildConfig
import com.geeksville.mesh.ConfigProtos
import com.geeksville.mesh.MeshProtos
import org.meshtastic.core.model.BuildConfig
/**
* When printing strings to logs sometimes we want to print useful debugging information about users or positions. But
@@ -60,6 +60,7 @@ fun Any.toPIIString() = if (!BuildConfig.DEBUG) {
fun ByteArray.toHexString() = joinToString("") { "%02x".format(it) }
@Suppress("MagicNumber")
fun formatAgo(lastSeenUnix: Int, currentTimeMillis: Long = System.currentTimeMillis()): String {
val currentTime = (currentTimeMillis / 1000).toInt()
val diffMin = (currentTime - lastSeenUnix) / 60

View File

@@ -15,10 +15,12 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh.util
@file:Suppress("MatchingDeclarationName")
package org.meshtastic.core.model.util
import android.annotation.SuppressLint
import com.geeksville.mesh.Position
import org.meshtastic.core.model.Position
import java.util.Locale
import kotlin.math.asin
import kotlin.math.atan2
@@ -52,6 +54,7 @@ fun latLongToMeter(latitudeA: Double, longitudeA: Double, latitudeB: Double, lon
}
// Same as above, but takes Mesh Position proto.
@Suppress("MagicNumber")
fun positionToMeter(a: Position, b: Position): Double =
latLongToMeter(a.latitude * 1e-7, a.longitude * 1e-7, b.latitude * 1e-7, b.longitude * 1e-7)
@@ -64,6 +67,7 @@ fun positionToMeter(a: Position, b: Position): Double =
* @param lon2 Longitude of the second point
* @return Bearing between the two points in degrees. A value of 0 means due north.
*/
@Suppress("MagicNumber")
fun bearing(lat1: Double, lon1: Double, lat2: Double, lon2: Double): Double {
val lat1Rad = Math.toRadians(lat1)
val lon1Rad = Math.toRadians(lon1)

View File

@@ -15,16 +15,14 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh.util
package org.meshtastic.core.model.util
import kotlin.math.ln
object UnitConversions {
@Suppress("MagicNumber")
fun celsiusToFahrenheit(celsius: Float): Float {
return (celsius * 1.8F) + 32
}
fun celsiusToFahrenheit(celsius: Float): Float = (celsius * 1.8F) + 32
fun Float.toTempString(isFahrenheit: Boolean) = if (isFahrenheit) {
val fahrenheit = celsiusToFahrenheit(this)
@@ -34,8 +32,8 @@ object UnitConversions {
}
/**
* Calculated the dew point based on the Magnus-Tetens approximation which is a widely used
* formula for calculating dew point temperature.
* Calculated the dew point based on the Magnus-Tetens approximation which is a widely used formula for calculating
* dew point temperature.
*/
@Suppress("MagicNumber")
fun calculateDewPoint(tempCelsius: Float, humidity: Float): Float {

View File

@@ -31,6 +31,7 @@ android {
}
dependencies {
implementation(projects.core.model)
implementation(libs.bundles.ktor)
implementation(libs.bundles.coil)
"googleImplementation"(libs.bundles.datadog)

View File

@@ -23,9 +23,9 @@ import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import org.meshtastic.core.model.NetworkDeviceHardware
import org.meshtastic.core.model.NetworkFirmwareReleases
import org.meshtastic.core.network.BuildConfig
import org.meshtastic.core.network.model.NetworkDeviceHardware
import org.meshtastic.core.network.model.NetworkFirmwareReleases
import org.meshtastic.core.network.service.ApiService
import javax.inject.Singleton

View File

@@ -19,7 +19,7 @@ package org.meshtastic.core.network
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.meshtastic.core.network.model.NetworkDeviceHardware
import org.meshtastic.core.model.NetworkDeviceHardware
import org.meshtastic.core.network.service.ApiService
import javax.inject.Inject

View File

@@ -19,7 +19,7 @@ package org.meshtastic.core.network
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.meshtastic.core.network.model.NetworkFirmwareReleases
import org.meshtastic.core.model.NetworkFirmwareReleases
import org.meshtastic.core.network.service.ApiService
import javax.inject.Inject

View File

@@ -18,8 +18,8 @@
package org.meshtastic.core.network.service
import de.jensklingenberg.ktorfit.http.GET
import org.meshtastic.core.network.model.NetworkDeviceHardware
import org.meshtastic.core.network.model.NetworkFirmwareReleases
import org.meshtastic.core.model.NetworkDeviceHardware
import org.meshtastic.core.model.NetworkFirmwareReleases
interface ApiService {
@GET("resource/deviceHardware")