feat: Accurately display outgoing diagnostic packets (#4569)

Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
James Rich 2026-02-16 16:09:21 -06:00 committed by GitHub
parent 6a244316b2
commit c690ddc7ea
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 922 additions and 381 deletions

View file

@ -34,12 +34,12 @@ interface MeshLogDao {
/**
* Retrieves [MeshLog]s matching 'from_num' (nodeNum) and 'port_num' (PortNum).
*
* @param portNum If 0, returns all MeshPackets. Otherwise, filters by 'port_num'.
* @param portNum If -1, returns all logs regardless of port. If 0, returns logs with port 0.
*/
@Query(
"""
SELECT * FROM log
WHERE from_num = :fromNum AND (:portNum = 0 AND port_num != 0 OR port_num = :portNum)
WHERE from_num = :fromNum AND (:portNum = -1 OR port_num = :portNum)
ORDER BY received_date DESC LIMIT 0,:maxItem
""",
)

View file

@ -28,6 +28,19 @@ import org.meshtastic.proto.MyNodeInfo
import org.meshtastic.proto.NodeInfo
import org.meshtastic.proto.Position
/**
* Represents a log entry in the database.
*
* Logs are used for auditing radio traffic, telemetry history, and debugging.
*
* @property uuid Unique identifier for this log entry.
* @property message_type The type of message (e.g., "Packet", "Telemetry", "LogRecord").
* @property received_date Timestamp when the log was recorded.
* @property raw_message A string representation of the raw data.
* @property fromNum The node number that sent the packet.
* @property portNum The application port number associated with the data.
* @property fromRadio The decoded [FromRadio] protobuf object.
*/
@Suppress("EmptyCatchBlock", "SwallowedException", "ConstructorParameterNaming")
@Entity(tableName = "log", indices = [Index(value = ["from_num"]), Index(value = ["port_num"])])
data class MeshLog(
@ -59,4 +72,14 @@ data class MeshLog(
null
}
} ?: nodeInfo?.position
companion object {
/**
* The node number used to represent the local node in the logs.
*
* Using 0 instead of the actual node number ensures log continuity even if the radio hardware or local ID
* changes.
*/
const val NODE_NUM_LOCAL = 0
}
}

View file

@ -20,6 +20,7 @@ import android.graphics.Color
import okio.ByteString
import org.meshtastic.core.database.entity.NodeEntity
import org.meshtastic.core.model.Capabilities
import org.meshtastic.core.model.DataPacket
import org.meshtastic.core.model.util.GPSFormat
import org.meshtastic.core.model.util.UnitConversions.celsiusToFahrenheit
import org.meshtastic.core.model.util.latLongToMeter
@ -36,6 +37,11 @@ import org.meshtastic.proto.PowerMetrics
import org.meshtastic.proto.Telemetry
import org.meshtastic.proto.User
/**
* Domain model representing a node in the mesh network.
*
* This class aggregates user information, position data, and hardware metrics.
*/
@Suppress("MagicNumber")
data class Node(
val num: Int,
@ -205,6 +211,20 @@ data class Node(
nodeStatus = nodeStatus,
lastTransport = lastTransport,
)
companion object {
private const val DEFAULT_ID_SUFFIX_LENGTH = 4
/** Creates a fallback [Node] when the node is not found in the database. */
fun createFallback(nodeNum: Int, fallbackNamePrefix: String): Node {
val userId = DataPacket.nodeNumToDefaultId(nodeNum)
val safeUserId = userId.padStart(DEFAULT_ID_SUFFIX_LENGTH, '0').takeLast(DEFAULT_ID_SUFFIX_LENGTH)
val longName = "$fallbackNamePrefix $safeUserId"
val defaultUser =
User(id = userId, long_name = longName, short_name = safeUserId, hw_model = HardwareModel.UNSET)
return Node(num = nodeNum, user = defaultUser)
}
}
}
fun Config.DeviceConfig.Role?.isUnmessageableRole(): Boolean = this in

View file

@ -0,0 +1,49 @@
/*
* 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.database.model
import org.junit.Assert.assertEquals
import org.junit.Test
import org.meshtastic.proto.HardwareModel
class NodeTest {
@Test
fun `createFallback produces expected node data`() {
val nodeNum = 0x12345678
val prefix = "Node"
val node = Node.createFallback(nodeNum, prefix)
assertEquals(nodeNum, node.num)
assertEquals("!12345678", node.user.id)
assertEquals("Node 5678", node.user.long_name)
assertEquals("5678", node.user.short_name)
assertEquals(HardwareModel.UNSET, node.user.hw_model)
}
@Test
fun `createFallback pads short IDs with zeros`() {
val nodeNum = 0x1
val prefix = "Node"
val node = Node.createFallback(nodeNum, prefix)
assertEquals(nodeNum, node.num)
assertEquals("!00000001", node.user.id)
assertEquals("Node 0001", node.user.long_name)
assertEquals("0001", node.user.short_name)
}
}