Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 81 additions & 1 deletion Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -3793,6 +3793,86 @@
}
}
}
},
"%d dBm" : {

},
"A local stats request has been sent to %@. Responses can take some time." : {
"comment" : "An alert message explaining that a local stats request has been sent to a specific node. The placeholder is replaced with the node name.",
"isCommentAutoGenerated" : true
},
"Bad Rx" : {

},
"Canceled" : {

},
"Canceled: %d" : {

},
"Delete all local stats?" : {

},
"Dupes" : {

},
"Dupes: %d" : {

},
"Local Stats" : {

},
"Local Stats (in %llds)" : {

},
"Local Stats Log" : {

},
"Local Stats Requested" : {
"comment" : "The title of an alert that appears when a user successfully requests a local stats update.",
"isCommentAutoGenerated" : true
},
"No Local Stats" : {

},
"No Reading" : {

},
"Nodes Online" : {

},
"Noise Floor" : {

},
"Noise Floor %d dBm" : {

},
"Noise Floor No Reading" : {

},
"Noise floor is a directional diagnostic. Readings can vary quickly, and external filters can lower or skew the displayed value due to insertion loss or in-band interference." : {

},
"Packets Rx" : {

},
"Packets Tx" : {

},
"Relayed" : {

},
"Relayed: %d" : {

},
"Request Local Stats" : {

},
"Total Nodes" : {

},
"Threshold (-85 dBm)" : {

},
"%lld received" : {
"comment" : "A label that shows the number of packets that were received.",
Expand Down Expand Up @@ -101005,4 +101085,4 @@
}
},
"version" : "1.1"
}
}
40 changes: 24 additions & 16 deletions Meshtastic.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -2401,4 +2401,40 @@ extension AccessoryManager {

return Int64(meshPacket.id)
}

func sendLocalStatsRequest(destNum: Int64, wantResponse: Bool) async throws {
guard let fromNodeNum = self.activeConnection?.device.num else {
Logger.services.error("Error while sending local stats request. No active device.")
throw AccessoryError.ioFailed("No active device")
}

var telemetryPacket = Telemetry()
telemetryPacket.localStats = LocalStats()

var meshPacket = MeshPacket()
meshPacket.id = UInt32.random(in: UInt32(UInt8.max)..<UInt32.max)
meshPacket.to = UInt32(destNum)
meshPacket.from = UInt32(fromNodeNum)
meshPacket.wantAck = true
meshPacket.decoded.wantResponse = wantResponse

var dataMessage = DataMessage()
if let serializedData: Data = try? telemetryPacket.serializedData() {
dataMessage.payload = serializedData
dataMessage.portnum = PortNum.telemetryApp
dataMessage.wantResponse = wantResponse
meshPacket.decoded = dataMessage
} else {
throw AccessoryError.ioFailed("sendLocalStatsRequest: Unable to serialize telemetry packet")
}

var toRadio: ToRadio!
toRadio = ToRadio()
toRadio.packet = meshPacket

let logString = String.localizedStringWithFormat("📊 Sent Local Stats Request from: %@ to: %@".localized, String(fromNodeNum), String(destNum))
try await send(toRadio, debugDescription: logString)

Logger.mesh.info("📊 \(logString, privacy: .public)")
}
}
27 changes: 27 additions & 0 deletions Meshtastic/Export/WriteCsvFile.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,33 @@ func telemetryToCsvFile<S: Sequence>(telemetry: S, metricsType: Int) -> String w
csvString += ", "
csvString += dm.time?.formatted(date: .numeric, time: .shortened).replacing(",", with: "") ?? ""
}
} else if metricsType == 4 {
// Create Local Stats Header
csvString = "Noise Floor, Uptime, Relayed, Canceled, Dupes, Packets Tx, Packets Rx, Bad Rx, Nodes Online, Total Nodes, \("Timestamp".localized)"
for dm in telemetry where dm.metricsType == 4 {
csvString += "\n"
csvString += dm.noiseFloor?.formatted(.number.grouping(.never)) ?? ""
csvString += ", "
csvString += dm.uptimeSeconds?.formatted(.number.grouping(.never)) ?? ""
csvString += ", "
csvString += dm.numTxRelay.formatted(.number.grouping(.never))
csvString += ", "
csvString += dm.numTxRelayCanceled.formatted(.number.grouping(.never))
csvString += ", "
csvString += dm.numRxDupe.formatted(.number.grouping(.never))
csvString += ", "
csvString += dm.numPacketsTx.formatted(.number.grouping(.never))
csvString += ", "
csvString += dm.numPacketsRx.formatted(.number.grouping(.never))
csvString += ", "
csvString += dm.numPacketsRxBad.formatted(.number.grouping(.never))
csvString += ", "
csvString += dm.numOnlineNodes.formatted(.number.grouping(.never))
csvString += ", "
csvString += dm.numTotalNodes.formatted(.number.grouping(.never))
csvString += ", "
csvString += dm.time?.formatted(date: .numeric, time: .shortened).replacing(",", with: "") ?? ""
}
}
return csvString
}
Expand Down
22 changes: 22 additions & 0 deletions Meshtastic/Extensions/SwiftData/NodeInfoEntityExtension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,28 @@ extension NodeInfoEntity {
return (try? ctx.fetchCount(descriptor)) ?? 0 > 0
}

var latestLocalStats: TelemetryEntity? {
guard let ctx = modelContext else { return nil }
let nodeNum = self.num
let metricsType: Int32 = 4
var descriptor = FetchDescriptor<TelemetryEntity>(
predicate: #Predicate<TelemetryEntity> { $0.nodeTelemetry?.num == nodeNum && $0.metricsType == metricsType },
sortBy: [SortDescriptor(\TelemetryEntity.time, order: .reverse)]
)
descriptor.fetchLimit = 1
return try? ctx.fetch(descriptor).first
}

var hasLocalStats: Bool {
guard let ctx = modelContext else { return false }
let nodeNum = self.num
let metricsType: Int32 = 4
let descriptor = FetchDescriptor<TelemetryEntity>(
predicate: #Predicate<TelemetryEntity> { $0.nodeTelemetry?.num == nodeNum && $0.metricsType == metricsType }
)
return (try? ctx.fetchCount(descriptor)) ?? 0 > 0
}

var hasTraceRoutes: Bool {
guard let ctx = modelContext else { return false }
let nodeNum = self.num
Expand Down
Loading
Loading