mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
refactor: migrate :core:database to Room Kotlin Multiplatform (#4702)
This commit is contained in:
parent
744db2d5bd
commit
6a858acb4a
42 changed files with 406 additions and 264 deletions
|
|
@ -16,9 +16,8 @@
|
|||
*/
|
||||
package org.meshtastic.core.data.datasource
|
||||
|
||||
import dagger.Lazy
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.meshtastic.core.database.dao.DeviceHardwareDao
|
||||
import org.meshtastic.core.database.DatabaseManager
|
||||
import org.meshtastic.core.database.entity.DeviceHardwareEntity
|
||||
import org.meshtastic.core.database.entity.asEntity
|
||||
import org.meshtastic.core.di.CoroutineDispatchers
|
||||
|
|
@ -28,10 +27,11 @@ import javax.inject.Inject
|
|||
class DeviceHardwareLocalDataSource
|
||||
@Inject
|
||||
constructor(
|
||||
private val deviceHardwareDaoLazy: Lazy<DeviceHardwareDao>,
|
||||
private val dbManager: DatabaseManager,
|
||||
private val dispatchers: CoroutineDispatchers,
|
||||
) {
|
||||
private val deviceHardwareDao by lazy { deviceHardwareDaoLazy.get() }
|
||||
private val deviceHardwareDao
|
||||
get() = dbManager.currentDb.value.deviceHardwareDao()
|
||||
|
||||
suspend fun insertAllDeviceHardware(deviceHardware: List<NetworkDeviceHardware>) =
|
||||
withContext(dispatchers.io) { deviceHardwareDao.insertAll(deviceHardware.map { it.asEntity() }) }
|
||||
|
|
|
|||
|
|
@ -16,9 +16,8 @@
|
|||
*/
|
||||
package org.meshtastic.core.data.datasource
|
||||
|
||||
import dagger.Lazy
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.meshtastic.core.database.dao.FirmwareReleaseDao
|
||||
import org.meshtastic.core.database.DatabaseManager
|
||||
import org.meshtastic.core.database.entity.FirmwareReleaseEntity
|
||||
import org.meshtastic.core.database.entity.FirmwareReleaseType
|
||||
import org.meshtastic.core.database.entity.asDeviceVersion
|
||||
|
|
@ -30,10 +29,11 @@ import javax.inject.Inject
|
|||
class FirmwareReleaseLocalDataSource
|
||||
@Inject
|
||||
constructor(
|
||||
private val firmwareReleaseDaoLazy: Lazy<FirmwareReleaseDao>,
|
||||
private val dbManager: DatabaseManager,
|
||||
private val dispatchers: CoroutineDispatchers,
|
||||
) {
|
||||
private val firmwareReleaseDao by lazy { firmwareReleaseDaoLazy.get() }
|
||||
private val firmwareReleaseDao
|
||||
get() = dbManager.currentDb.value.firmwareReleaseDao()
|
||||
|
||||
suspend fun insertFirmwareReleases(
|
||||
firmwareReleases: List<NetworkFirmwareRelease>,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright (c) 2025-2026 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.data.di
|
||||
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import org.meshtastic.core.database.DatabaseManager
|
||||
import javax.inject.Singleton
|
||||
|
||||
@InstallIn(SingletonComponent::class)
|
||||
@Module
|
||||
interface DatabaseModule {
|
||||
|
||||
@Binds @Singleton
|
||||
fun bindDatabaseManager(impl: DatabaseManager): org.meshtastic.core.repository.DatabaseManager
|
||||
}
|
||||
|
|
@ -201,10 +201,12 @@ constructor(
|
|||
val currentPosition =
|
||||
when {
|
||||
provideLocation && position.isValid() -> position
|
||||
else ->
|
||||
provideLocation ->
|
||||
nodeManager.nodeDBbyNodeNum[myNodeNum]?.position?.let { Position(it) }?.takeIf { it.isValid() }
|
||||
?: Position(0.0, 0.0, 0)
|
||||
else -> Position(0.0, 0.0, 0)
|
||||
}
|
||||
currentPosition?.let { commandSender.requestPosition(destNum, it) }
|
||||
commandSender.requestPosition(destNum, currentPosition)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -440,11 +440,13 @@ constructor(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
environment != null -> nextNode = nextNode.copy(environmentMetrics = environment)
|
||||
power != null -> nextNode = nextNode.copy(powerMetrics = power)
|
||||
}
|
||||
nextNode
|
||||
|
||||
val telemetryTime = if (t.time != 0) t.time else nextNode.lastHeard
|
||||
val newLastHeard = maxOf(nextNode.lastHeard, telemetryTime)
|
||||
nextNode.copy(lastHeard = newLastHeard)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -192,23 +192,43 @@ constructor(
|
|||
}
|
||||
|
||||
override fun handleReceivedPosition(fromNum: Int, myNodeNum: Int, p: ProtoPosition, defaultTime: Long) {
|
||||
if (myNodeNum == fromNum && (p.latitude_i ?: 0) == 0 && (p.longitude_i ?: 0) == 0) {
|
||||
Logger.d { "Ignoring nop position update for the local node" }
|
||||
} else {
|
||||
updateNode(fromNum) { node ->
|
||||
node.copy(position = p.copy(time = if (p.time != 0) p.time else (defaultTime / TIME_MS_TO_S).toInt()))
|
||||
}
|
||||
val isZeroPos = (p.latitude_i ?: 0) == 0 && (p.longitude_i ?: 0) == 0
|
||||
@Suppress("ComplexCondition")
|
||||
if (myNodeNum == fromNum && isZeroPos && p.sats_in_view == 0 && p.time == 0) {
|
||||
Logger.d { "Ignoring empty position update for the local node" }
|
||||
return
|
||||
}
|
||||
|
||||
updateNode(fromNum) { node ->
|
||||
val posTime = if (p.time != 0) p.time else (defaultTime / TIME_MS_TO_S).toInt()
|
||||
val newLastHeard = maxOf(node.lastHeard, posTime)
|
||||
|
||||
val newPos =
|
||||
if (isZeroPos) {
|
||||
p.copy(
|
||||
time = posTime,
|
||||
latitude_i = node.position.latitude_i,
|
||||
longitude_i = node.position.longitude_i,
|
||||
altitude = p.altitude ?: node.position.altitude,
|
||||
sats_in_view = p.sats_in_view,
|
||||
)
|
||||
} else {
|
||||
p.copy(time = posTime)
|
||||
}
|
||||
|
||||
node.copy(position = newPos, lastHeard = newLastHeard)
|
||||
}
|
||||
}
|
||||
|
||||
override fun handleReceivedTelemetry(fromNum: Int, telemetry: Telemetry) {
|
||||
updateNode(fromNum) { node ->
|
||||
when {
|
||||
telemetry.device_metrics != null -> node.copy(deviceMetrics = telemetry.device_metrics!!)
|
||||
telemetry.environment_metrics != null -> node.copy(environmentMetrics = telemetry.environment_metrics!!)
|
||||
telemetry.power_metrics != null -> node.copy(powerMetrics = telemetry.power_metrics!!)
|
||||
else -> node
|
||||
}
|
||||
var nextNode = node
|
||||
telemetry.device_metrics?.let { nextNode = nextNode.copy(deviceMetrics = it) }
|
||||
telemetry.environment_metrics?.let { nextNode = nextNode.copy(environmentMetrics = it) }
|
||||
telemetry.power_metrics?.let { nextNode = nextNode.copy(powerMetrics = it) }
|
||||
val telemetryTime = if (telemetry.time != 0) telemetry.time else node.lastHeard
|
||||
val newLastHeard = maxOf(node.lastHeard, telemetryTime)
|
||||
nextNode.copy(lastHeard = newLastHeard)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -260,6 +260,8 @@ constructor(
|
|||
num = num,
|
||||
user = user,
|
||||
position = position,
|
||||
latitude = latitude,
|
||||
longitude = longitude,
|
||||
snr = snr,
|
||||
rssi = rssi,
|
||||
lastHeard = lastHeard,
|
||||
|
|
|
|||
|
|
@ -105,6 +105,85 @@ class NodeManagerImplTest {
|
|||
assertEquals(90.0, result.longitude, 0.0001)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `handleReceivedPosition with zero coordinates preserves last known location but updates satellites`() {
|
||||
val nodeNum = 1234
|
||||
val initialPosition = Position(latitude_i = 450000000, longitude_i = 900000000, sats_in_view = 10)
|
||||
nodeManager.handleReceivedPosition(nodeNum, 9999, initialPosition, 1000000L)
|
||||
|
||||
// Receive "zero" position with new satellite count
|
||||
val zeroPosition = Position(latitude_i = 0, longitude_i = 0, sats_in_view = 5, time = 1001)
|
||||
nodeManager.handleReceivedPosition(nodeNum, 9999, zeroPosition, 1001000L)
|
||||
|
||||
val result = nodeManager.nodeDBbyNodeNum[nodeNum]
|
||||
assertEquals(45.0, result!!.latitude, 0.0001)
|
||||
assertEquals(90.0, result.longitude, 0.0001)
|
||||
assertEquals(5, result.position.sats_in_view)
|
||||
assertEquals(1001, result.lastHeard)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `handleReceivedPosition for local node ignores purely empty packets`() {
|
||||
val myNum = 1111
|
||||
val emptyPos = Position(latitude_i = 0, longitude_i = 0, sats_in_view = 0, time = 0)
|
||||
|
||||
nodeManager.handleReceivedPosition(myNum, myNum, emptyPos, 0)
|
||||
|
||||
val result = nodeManager.nodeDBbyNodeNum[myNum]
|
||||
// Should still be a default/unset node if it didn't exist, or shouldn't have position
|
||||
assertTrue(result == null || result.position.latitude_i == null)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `handleReceivedTelemetry updates lastHeard`() {
|
||||
val nodeNum = 1234
|
||||
nodeManager.updateNode(nodeNum) { it.copy(lastHeard = 1000) }
|
||||
|
||||
val telemetry =
|
||||
org.meshtastic.proto.Telemetry(
|
||||
time = 2000,
|
||||
device_metrics = org.meshtastic.proto.DeviceMetrics(battery_level = 50),
|
||||
)
|
||||
|
||||
nodeManager.handleReceivedTelemetry(nodeNum, telemetry)
|
||||
|
||||
val result = nodeManager.nodeDBbyNodeNum[nodeNum]
|
||||
assertEquals(2000, result!!.lastHeard)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `handleReceivedTelemetry updates device metrics`() {
|
||||
val nodeNum = 1234
|
||||
val telemetry =
|
||||
org.meshtastic.proto.Telemetry(
|
||||
device_metrics = org.meshtastic.proto.DeviceMetrics(battery_level = 75, voltage = 3.8f),
|
||||
)
|
||||
|
||||
nodeManager.handleReceivedTelemetry(nodeNum, telemetry)
|
||||
|
||||
val result = nodeManager.nodeDBbyNodeNum[nodeNum]
|
||||
assertNotNull(result!!.deviceMetrics)
|
||||
assertEquals(75, result.deviceMetrics.battery_level)
|
||||
assertEquals(3.8f, result.deviceMetrics.voltage)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `handleReceivedTelemetry updates environment metrics`() {
|
||||
val nodeNum = 1234
|
||||
val telemetry =
|
||||
org.meshtastic.proto.Telemetry(
|
||||
environment_metrics =
|
||||
org.meshtastic.proto.EnvironmentMetrics(temperature = 22.5f, relative_humidity = 45.0f),
|
||||
)
|
||||
|
||||
nodeManager.handleReceivedTelemetry(nodeNum, telemetry)
|
||||
|
||||
val result = nodeManager.nodeDBbyNodeNum[nodeNum]
|
||||
assertNotNull(result!!.environmentMetrics)
|
||||
assertEquals(22.5f, result.environmentMetrics.temperature)
|
||||
assertEquals(45.0f, result.environmentMetrics.relative_humidity)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clear resets internal state`() {
|
||||
nodeManager.updateNode(1234) { it.copy(user = it.user.copy(long_name = "Test")) }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue