mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-10-02 16:44:33 -04:00
fix(service): correct phone position units and omit missing readings (#7437)
This commit is contained in:
1 parent
df33cad51c
commit
c7d7bc766d
3 files changed
+154
-19
No files matched your search
+14
-19
@@ -26,10 +26,8 @@ import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.koin.core.annotation.Single
|
||||
import org.meshtastic.core.common.hasLocationPermission
|
||||
import org.meshtastic.core.model.Position
|
||||
import org.meshtastic.core.repository.LocationRepository
|
||||
import org.meshtastic.core.repository.MeshLocationManager
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
import org.meshtastic.proto.Position as ProtoPosition
|
||||
|
||||
@Single
|
||||
@@ -51,23 +49,20 @@ class AndroidMeshLocationManager(private val context: Application, private val l
|
||||
.getLocations()
|
||||
.onEach { location ->
|
||||
sendPositionFn(
|
||||
ProtoPosition.Builder()
|
||||
.also { wb ->
|
||||
wb.latitude_i = Position.degI(location.latitude)
|
||||
wb.longitude_i = Position.degI(location.longitude)
|
||||
wb.altitude =
|
||||
if (LocationCompat.hasMslAltitude(location)) {
|
||||
LocationCompat.getMslAltitudeMeters(location).toInt()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
wb.altitude_hae = location.altitude.toInt()
|
||||
wb.time = (location.time.milliseconds.inWholeSeconds).toInt()
|
||||
wb.ground_speed = location.speed.toInt()
|
||||
wb.ground_track = location.bearing.toInt()
|
||||
wb.location_source = ProtoPosition.LocSource.LOC_EXTERNAL
|
||||
}
|
||||
.build(),
|
||||
phonePosition(
|
||||
latitude = location.latitude,
|
||||
longitude = location.longitude,
|
||||
timeMillis = location.time,
|
||||
mslAltitudeMeters =
|
||||
if (LocationCompat.hasMslAltitude(location)) {
|
||||
LocationCompat.getMslAltitudeMeters(location)
|
||||
} else {
|
||||
null
|
||||
},
|
||||
haeAltitudeMeters = location.altitude.takeIf { location.hasAltitude() },
|
||||
speedMetersPerSecond = location.speed.takeIf { location.hasSpeed() },
|
||||
bearingDegrees = location.bearing.takeIf { location.hasBearing() },
|
||||
),
|
||||
)
|
||||
}
|
||||
.launchIn(scope)
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (c) 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.service
|
||||
|
||||
import org.meshtastic.core.model.Position
|
||||
import org.meshtastic.core.model.util.GeoConstants
|
||||
import org.meshtastic.core.model.util.UnitConversions
|
||||
import kotlin.math.roundToInt
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
import org.meshtastic.proto.Position as ProtoPosition
|
||||
|
||||
/**
|
||||
* The position the phone reports for its node. A reading the fix lacks stays unset rather than going out as 0, and
|
||||
* speed and track use the units firmware's own GPS sends: km/h and 1e-5 degrees.
|
||||
*/
|
||||
internal fun phonePosition(
|
||||
latitude: Double,
|
||||
longitude: Double,
|
||||
timeMillis: Long,
|
||||
mslAltitudeMeters: Double?,
|
||||
haeAltitudeMeters: Double?,
|
||||
speedMetersPerSecond: Float?,
|
||||
bearingDegrees: Float?,
|
||||
): ProtoPosition = ProtoPosition.Builder()
|
||||
.also { wb ->
|
||||
wb.latitude_i = Position.degI(latitude)
|
||||
wb.longitude_i = Position.degI(longitude)
|
||||
wb.altitude = mslAltitudeMeters?.toInt()
|
||||
wb.altitude_hae = haeAltitudeMeters?.toInt()
|
||||
wb.time = timeMillis.milliseconds.inWholeSeconds.toInt()
|
||||
wb.ground_speed = speedMetersPerSecond?.let { UnitConversions.metersPerSecondToKph(it).roundToInt() }
|
||||
wb.ground_track = bearingDegrees?.let { (it / GeoConstants.HEADING_DEG).roundToInt() }
|
||||
wb.location_source = ProtoPosition.LocSource.LOC_EXTERNAL
|
||||
}
|
||||
.build()
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright (c) 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.service
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNull
|
||||
import org.meshtastic.proto.Position as ProtoPosition
|
||||
|
||||
class PhonePositionTest {
|
||||
|
||||
private fun fix(
|
||||
mslAltitudeMeters: Double? = null,
|
||||
haeAltitudeMeters: Double? = null,
|
||||
speedMetersPerSecond: Float? = null,
|
||||
bearingDegrees: Float? = null,
|
||||
) = phonePosition(
|
||||
latitude = 1.5,
|
||||
longitude = -2.25,
|
||||
timeMillis = 1_700_000_000_500,
|
||||
mslAltitudeMeters = mslAltitudeMeters,
|
||||
haeAltitudeMeters = haeAltitudeMeters,
|
||||
speedMetersPerSecond = speedMetersPerSecond,
|
||||
bearingDegrees = bearingDegrees,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `readings the fix lacks stay unset`() {
|
||||
val position = fix()
|
||||
|
||||
assertNull(position.altitude)
|
||||
assertNull(position.altitude_hae)
|
||||
assertNull(position.ground_speed)
|
||||
assertNull(position.ground_track)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `zero readings are sent as zero`() {
|
||||
val position =
|
||||
fix(mslAltitudeMeters = 0.0, haeAltitudeMeters = 0.0, speedMetersPerSecond = 0f, bearingDegrees = 0f)
|
||||
|
||||
assertEquals(0, position.altitude)
|
||||
assertEquals(0, position.altitude_hae)
|
||||
assertEquals(0, position.ground_speed)
|
||||
assertEquals(0, position.ground_track)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `speed goes out in km per hour`() {
|
||||
assertEquals(36, fix(speedMetersPerSecond = 10f).ground_speed)
|
||||
assertEquals(5, fix(speedMetersPerSecond = 1.4f).ground_speed)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `track goes out in hundred-thousandths of a degree`() {
|
||||
assertEquals(9_000_000, fix(bearingDegrees = 90f).ground_track)
|
||||
assertEquals(35_950_000, fix(bearingDegrees = 359.5f).ground_track)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `each altitude keeps its own datum`() {
|
||||
val position = fix(mslAltitudeMeters = 12.9, haeAltitudeMeters = -20.4)
|
||||
|
||||
assertEquals(12, position.altitude)
|
||||
assertEquals(-20, position.altitude_hae)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `coordinates and time use the wire scale`() {
|
||||
val position = fix()
|
||||
|
||||
assertEquals(15_000_000, position.latitude_i)
|
||||
assertEquals(-22_500_000, position.longitude_i)
|
||||
assertEquals(1_700_000_000, position.time)
|
||||
assertEquals(ProtoPosition.LocSource.LOC_EXTERNAL, position.location_source)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user