From 16d2b2e5f34e2818f0e738dbed5d73f9dc2ee716 Mon Sep 17 00:00:00 2001 From: Mike Cumings Date: Fri, 25 Feb 2022 14:14:50 -0800 Subject: [PATCH] CSV export improvements to make it more reliable --- .../mesh/database/PacketRepository.kt | 4 +- .../geeksville/mesh/database/entity/Packet.kt | 20 ++- .../java/com/geeksville/mesh/model/UIState.kt | 115 ++++++++++-------- 3 files changed, 85 insertions(+), 54 deletions(-) diff --git a/app/src/main/java/com/geeksville/mesh/database/PacketRepository.kt b/app/src/main/java/com/geeksville/mesh/database/PacketRepository.kt index 06a7ff841..e8a7077c9 100644 --- a/app/src/main/java/com/geeksville/mesh/database/PacketRepository.kt +++ b/app/src/main/java/com/geeksville/mesh/database/PacketRepository.kt @@ -16,8 +16,8 @@ class PacketRepository @Inject constructor(private val packetDaoLazy: dagger.Laz packetDao.getAllPacket(MAX_ITEMS) } - suspend fun getAllPacketsInReceiveOrder(): Flow> = withContext(Dispatchers.IO) { - packetDao.getAllPacketsInReceiveOrder(MAX_ITEMS) + suspend fun getAllPacketsInReceiveOrder(maxItems: Int = MAX_ITEMS): Flow> = withContext(Dispatchers.IO) { + packetDao.getAllPacketsInReceiveOrder(maxItems) } suspend fun insert(packet: Packet) = withContext(Dispatchers.IO) { diff --git a/app/src/main/java/com/geeksville/mesh/database/entity/Packet.kt b/app/src/main/java/com/geeksville/mesh/database/entity/Packet.kt index ad516e1ec..bc775f205 100644 --- a/app/src/main/java/com/geeksville/mesh/database/entity/Packet.kt +++ b/app/src/main/java/com/geeksville/mesh/database/entity/Packet.kt @@ -16,7 +16,7 @@ data class Packet(@PrimaryKey val uuid: String, @ColumnInfo(name = "message") val raw_message: String ) { - val proto: MeshProtos.MeshPacket? + val meshPacket: MeshProtos.MeshPacket? get() { if (message_type == "packet") { val builder = MeshProtos.MeshPacket.newBuilder() @@ -28,13 +28,27 @@ data class Packet(@PrimaryKey val uuid: String, } return null } + + val nodeInfo: MeshProtos.NodeInfo? + get() { + if (message_type == "NodeInfo") { + val builder = MeshProtos.NodeInfo.newBuilder() + try { + TextFormat.getParser().merge(raw_message, builder) + return builder.build() + } catch (e: IOException) { + } + } + return null + } + val position: MeshProtos.Position? get() { - return proto?.run { + return meshPacket?.run { if (hasDecoded() && decoded.portnumValue == Portnums.PortNum.POSITION_APP_VALUE) { return MeshProtos.Position.parseFrom(decoded.payload) } return null - } + } ?: nodeInfo?.position } } \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/model/UIState.kt b/app/src/main/java/com/geeksville/mesh/model/UIState.kt index 6b0600e2e..e4b5dfe51 100644 --- a/app/src/main/java/com/geeksville/mesh/model/UIState.kt +++ b/app/src/main/java/com/geeksville/mesh/model/UIState.kt @@ -283,66 +283,83 @@ class UIViewModel @Inject constructor( // Capture the current node value while we're still on main thread val nodes = nodeDB.nodes.value ?: emptyMap() + val positionToPos: (MeshProtos.Position?) -> Position? = { meshPosition -> + meshPosition?.let { Position(it) }.takeIf { + it?.isValid() == true + } + } + writeToUri(file_uri) { writer -> // Create a map of nodes keyed by their ID - val nodesById = nodes.values.associateBy { it.num } + val nodesById = nodes.values.associateBy { it.num }.toMutableMap() + val nodePositions = mutableMapOf() writer.appendLine("date,time,from,sender name,sender lat,sender long,rx lat,rx long,rx elevation,rx snr,distance,hop limit,payload") // Packets are ordered by time, we keep most recent position of // our device in localNodePosition. - var localNodePosition: MeshProtos.Position? = null val dateFormat = SimpleDateFormat("yyyy-MM-dd,HH:mm:ss", Locale.getDefault()) - repository.getAllPacketsInReceiveOrder().first().forEach { packet -> - packet.proto?.let { proto -> + repository.getAllPacketsInReceiveOrder(Int.MAX_VALUE).first().forEach { packet -> + // If we get a NodeInfo packet, use it to update our position data (if valid) + packet.nodeInfo?.let { nodeInfo -> + positionToPos.invoke(nodeInfo.position)?.let { _ -> + nodePositions[nodeInfo.num] = nodeInfo.position + } + } + + packet.meshPacket?.let { proto -> + // If the packet contains position data then use it to update, if valid packet.position?.let { position -> - if (proto.from == myNodeNum) { - localNodePosition = position - } else { - val rxDateTime = dateFormat.format(packet.received_date) - val rxFrom = proto.from.toUInt() - val senderName = nodesById[proto.from]?.user?.longName ?: "" - - // sender lat & long - val senderPos = packet.position - ?.let { p -> Position(p) } - ?.takeIf { p -> p.isValid() } - val senderLat = senderPos?.latitude ?: "" - val senderLong = senderPos?.longitude ?: "" - - // rx lat, long, and elevation - val rxPos = localNodePosition - ?.let { p -> Position(p) } - ?.takeIf { p -> p.isValid() } - val rxLat = rxPos?.latitude ?: "" - val rxLong = rxPos?.longitude ?: "" - val rxAlt = rxPos?.altitude ?: "" - val rxSnr = "%f".format(proto.rxSnr) - - // Calculate the distance if both positions are valid - val dist = if (senderPos == null || rxPos == null) { - "" - } else { - positionToMeter( - localNodePosition!!, - position - ).roundToInt().toString() - } - - val hopLimit = proto.hopLimit - - val payload = when { - proto.decoded.portnumValue != Portnums.PortNum.TEXT_MESSAGE_APP_VALUE -> "<${proto.decoded.portnum}>" - proto.hasDecoded() -> "\"" + proto.decoded.payload.toStringUtf8() - .replace("\"", "\\\"") + "\"" - proto.hasEncrypted() -> "${proto.encrypted.size()} encrypted bytes" - else -> "" - } - - // date,time,from,sender name,sender lat,sender long,rx lat,rx long,rx elevation,rx snr,distance,hop limit,payload - writer.appendLine("$rxDateTime,$rxFrom,$senderName,$senderLat,$senderLong,$rxLat,$rxLong,$rxAlt,$rxSnr,$dist,$hopLimit,$payload") + positionToPos.invoke(position)?.let { _ -> + nodePositions[proto.from] = position } } + + // Filter out of our results any packet that doesn't report SNR. This + // is primarily ADMIN_APP. + if (proto.rxSnr > 0.0f) { + val rxDateTime = dateFormat.format(packet.received_date) + val rxFrom = proto.from.toUInt() + val senderName = nodesById[proto.from]?.user?.longName ?: "" + + // sender lat & long + val senderPosition = nodePositions[proto.from] + val senderPos = positionToPos.invoke(senderPosition) + val senderLat = senderPos?.latitude ?: "" + val senderLong = senderPos?.longitude ?: "" + + // rx lat, long, and elevation + val rxPosition = nodePositions[myNodeNum] + val rxPos = positionToPos.invoke(rxPosition) + val rxLat = rxPos?.latitude ?: "" + val rxLong = rxPos?.longitude ?: "" + val rxAlt = rxPos?.altitude ?: "" + val rxSnr = "%f".format(proto.rxSnr) + + // Calculate the distance if both positions are valid + + val dist = if (senderPos == null || rxPos == null) { + "" + } else { + positionToMeter( + rxPosition!!, // Use rxPosition but only if rxPos was valid + senderPosition!! // Use senderPosition but only if senderPos was valid + ).roundToInt().toString() + } + + val hopLimit = proto.hopLimit + + val payload = when { + proto.decoded.portnumValue != Portnums.PortNum.TEXT_MESSAGE_APP_VALUE -> "<${proto.decoded.portnum}>" + proto.hasDecoded() -> "\"" + proto.decoded.payload.toStringUtf8() + .replace("\"", "\\\"") + "\"" + proto.hasEncrypted() -> "${proto.encrypted.size()} encrypted bytes" + else -> "" + } + + // date,time,from,sender name,sender lat,sender long,rx lat,rx long,rx elevation,rx snr,distance,hop limit,payload + writer.appendLine("$rxDateTime,$rxFrom,$senderName,$senderLat,$senderLong,$rxLat,$rxLong,$rxAlt,$rxSnr,$dist,$hopLimit,$payload") + } } } }