mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
feat: Traceroute map visualisation (#4002)
This commit is contained in:
parent
24f40b2005
commit
3dbc5108c2
18 changed files with 917 additions and 60 deletions
|
|
@ -17,6 +17,10 @@
|
|||
|
||||
package org.meshtastic.core.model
|
||||
|
||||
import org.jetbrains.compose.resources.StringResource
|
||||
import org.meshtastic.core.strings.Res
|
||||
import org.meshtastic.core.strings.traceroute_endpoint_missing
|
||||
import org.meshtastic.core.strings.traceroute_map_no_data
|
||||
import org.meshtastic.proto.MeshProtos
|
||||
import org.meshtastic.proto.MeshProtos.RouteDiscovery
|
||||
import org.meshtastic.proto.Portnums
|
||||
|
|
@ -28,11 +32,13 @@ val MeshProtos.MeshPacket.fullRouteDiscovery: RouteDiscovery?
|
|||
runCatching { RouteDiscovery.parseFrom(payload).toBuilder() }
|
||||
.getOrNull()
|
||||
?.apply {
|
||||
val fullRoute = listOf(to) + routeList + from
|
||||
val destinationId = dest.takeIf { it != 0 } ?: this@fullRouteDiscovery.to
|
||||
val sourceId = source.takeIf { it != 0 } ?: this@fullRouteDiscovery.from
|
||||
val fullRoute = listOf(destinationId) + routeList + sourceId
|
||||
clearRoute()
|
||||
addAllRoute(fullRoute)
|
||||
|
||||
val fullRouteBack = listOf(from) + routeBackList + to
|
||||
val fullRouteBack = listOf(sourceId) + routeBackList + destinationId
|
||||
clearRouteBack()
|
||||
if (hopStart > 0 && snrBackCount > 0) { // otherwise back route is invalid
|
||||
addAllRouteBack(fullRouteBack)
|
||||
|
|
@ -85,3 +91,35 @@ fun MeshProtos.MeshPacket.getTracerouteResponse(getUser: (nodeNum: Int) -> Strin
|
|||
fun MeshProtos.MeshPacket.getFullTracerouteResponse(getUser: (nodeNum: Int) -> String): String? = fullRouteDiscovery
|
||||
?.takeIf { it.routeList.isNotEmpty() && it.routeBackList.isNotEmpty() }
|
||||
?.getTracerouteResponse(getUser)
|
||||
|
||||
enum class TracerouteMapAvailability {
|
||||
Ok,
|
||||
MissingEndpoints,
|
||||
NoMappableNodes,
|
||||
}
|
||||
|
||||
fun evaluateTracerouteMapAvailability(
|
||||
forwardRoute: List<Int>,
|
||||
returnRoute: List<Int>,
|
||||
positionedNodeNums: Set<Int>,
|
||||
): TracerouteMapAvailability {
|
||||
val endpoints =
|
||||
listOfNotNull(
|
||||
forwardRoute.firstOrNull(),
|
||||
forwardRoute.lastOrNull(),
|
||||
returnRoute.firstOrNull(),
|
||||
returnRoute.lastOrNull(),
|
||||
)
|
||||
.distinct()
|
||||
val missingEndpoint = endpoints.any { !positionedNodeNums.contains(it) }
|
||||
if (missingEndpoint) return TracerouteMapAvailability.MissingEndpoints
|
||||
val relatedNodeNums = (forwardRoute + returnRoute).toSet()
|
||||
val hasAnyMappable = relatedNodeNums.any { positionedNodeNums.contains(it) }
|
||||
return if (hasAnyMappable) TracerouteMapAvailability.Ok else TracerouteMapAvailability.NoMappableNodes
|
||||
}
|
||||
|
||||
fun TracerouteMapAvailability.toMessageRes(): StringResource? = when (this) {
|
||||
TracerouteMapAvailability.Ok -> null
|
||||
TracerouteMapAvailability.MissingEndpoints -> Res.string.traceroute_endpoint_missing
|
||||
TracerouteMapAvailability.NoMappableNodes -> Res.string.traceroute_map_no_data
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,6 +78,8 @@ object NodeDetailRoutes {
|
|||
|
||||
@Serializable data class TracerouteLog(val destNum: Int) : Route
|
||||
|
||||
@Serializable data class TracerouteMap(val destNum: Int, val requestId: Int) : Route
|
||||
|
||||
@Serializable data class HostMetricsLog(val destNum: Int) : Route
|
||||
|
||||
@Serializable data class PaxMetrics(val destNum: Int) : Route
|
||||
|
|
|
|||
|
|
@ -29,6 +29,17 @@ import timber.log.Timber
|
|||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
data class TracerouteResponse(
|
||||
val message: String,
|
||||
val destinationNodeNum: Int,
|
||||
val requestId: Int,
|
||||
val forwardRoute: List<Int> = emptyList(),
|
||||
val returnRoute: List<Int> = emptyList(),
|
||||
) {
|
||||
val hasOverlay: Boolean
|
||||
get() = forwardRoute.isNotEmpty() || returnRoute.isNotEmpty()
|
||||
}
|
||||
|
||||
/** Repository class for managing the [IMeshService] instance and connection state */
|
||||
@Suppress("TooManyFunctions")
|
||||
@Singleton
|
||||
|
|
@ -94,11 +105,11 @@ class ServiceRepository @Inject constructor() {
|
|||
_meshPacketFlow.emit(packet)
|
||||
}
|
||||
|
||||
private val _tracerouteResponse = MutableStateFlow<String?>(null)
|
||||
val tracerouteResponse: StateFlow<String?>
|
||||
private val _tracerouteResponse = MutableStateFlow<TracerouteResponse?>(null)
|
||||
val tracerouteResponse: StateFlow<TracerouteResponse?>
|
||||
get() = _tracerouteResponse
|
||||
|
||||
fun setTracerouteResponse(value: String?) {
|
||||
fun setTracerouteResponse(value: TracerouteResponse?) {
|
||||
_tracerouteResponse.value = value
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -405,6 +405,12 @@
|
|||
<item quantity="other">%1$d hops</item>
|
||||
</plurals>
|
||||
<string name="traceroute_diff">Hops towards %1$d Hops back %2$d</string>
|
||||
<string name="traceroute_outgoing_route">Outgoing route</string>
|
||||
<string name="traceroute_return_route">Return route</string>
|
||||
<string name="traceroute_endpoint_missing">Cannot show traceroute map because the start or destination node has no position information.</string>
|
||||
<string name="view_on_map">View on map</string>
|
||||
<string name="traceroute_map_no_data">This traceroute does not have any mappable nodes yet.</string>
|
||||
<string name="traceroute_showing_nodes">Showing %1$d/%2$d nodes</string>
|
||||
<string name="twenty_four_hours">24H</string>
|
||||
<string name="forty_eight_hours">48H</string>
|
||||
<string name="one_week">1W</string>
|
||||
|
|
@ -1030,8 +1036,8 @@
|
|||
<item quantity="one">1 hour</item>
|
||||
<item quantity="other">%1$d hours</item>
|
||||
</plurals>
|
||||
|
||||
<!-- Compass -->
|
||||
|
||||
<!-- Compass -->
|
||||
<string name="compass_title">Compass</string>
|
||||
<string name="open_compass">Open Compass</string>
|
||||
<string name="compass_distance">Distance: %1$s</string>
|
||||
|
|
|
|||
|
|
@ -27,6 +27,13 @@ val MeshtasticAlt = Color(0xFF2C2D3C)
|
|||
val HyperlinkBlue = Color(0xFF43C3B0)
|
||||
val AnnotationColor = Color(0xFF039BE5)
|
||||
|
||||
object TracerouteColors {
|
||||
// High-contrast pair that stays legible on light/dark tiles and for most color-blind users.
|
||||
// Use partial alpha so polylines don’t overpower markers/tiles.
|
||||
val OutgoingRoute = Color(0xCCE86A00) // orange @ ~80% opacity
|
||||
val ReturnRoute = Color(0xCC0081C7) // cyan @ ~80% opacity
|
||||
}
|
||||
|
||||
object IAQColors {
|
||||
val IAQExcellent = Color(0xFF00E400)
|
||||
val IAQGood = Color(0xFF92D050)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue