Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update Live Activity to use new Local Stats Telemetry Message #897

Merged
merged 10 commits into from
Aug 27, 2024
39 changes: 31 additions & 8 deletions Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,16 @@
},
"%@%%" : {

},
"%@%% %@%%" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "%1$@%% %2$@%%"
}
}
}
},
"%@°F" : {

Expand Down Expand Up @@ -6325,7 +6335,7 @@
"Direct Message Help" : {

},
"Direct messages are using the new public key infrastructure for encryption. Reguires firmware version 2.5 or greater." : {
"Direct messages are using the new public key infrastructure for encryption. Requires firmware version 2.5 or greater." : {

},
"Direct messages are using the shared key for the channel." : {
Expand Down Expand Up @@ -6712,6 +6722,9 @@
},
"Drag & Drop is the recommended way to update firmware for NRF devices. If your iPhone or iPad is USB-C it will work with your regular USB-C charging cable, for lightning devices you need the Apple Lightning to USB camera adaptor." : {

},
"Dupe / Bad Packets: %d" : {

},
"echo" : {
"localizations" : {
Expand Down Expand Up @@ -7227,12 +7240,12 @@
},
"Favorites" : {

},
"Fetch the latest position of a cetain node" : {

},
"Favorites and nodes with recent messages show up at the top of the contact list." : {


},
"Fetch the latest position of a cetain node" : {

},
"Fifteen Minutes" : {

Expand Down Expand Up @@ -14565,9 +14578,10 @@

},
"Message content exceeds 228 bytes." : {

},
"Message Status Options" : {

},
"message.details" : {
"localizations" : {
Expand Down Expand Up @@ -16071,6 +16085,12 @@
},
"Override automatic OLED screen detection." : {

},
"Packets Received: %d" : {

},
"Packets Sent: %d" : {

},
"password" : {
"localizations" : {
Expand Down Expand Up @@ -17386,6 +17406,9 @@
},
"Requires that there be an accelerometer on your device." : {

},
"Reset App Settings" : {

},
"Reset NodeDB" : {

Expand Down Expand Up @@ -22241,7 +22264,7 @@
}
}
},
"Updated Device Metrics Data." : {
"Updated Node Stats Data." : {

},
"Updated: %@" : {
Expand Down Expand Up @@ -22678,7 +22701,7 @@
"Your Firmware is up to date" : {

},
"Your MQTT Server must support TLS." : {
"Your MQTT Server must support TLS. Not available via the public mqtt server." : {

},
"Your position has been sent with a request for a response with their position. You will receive a notification when a position is returned." : {
Expand Down
4 changes: 3 additions & 1 deletion Meshtastic.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,7 @@
DD77093C2AA1AFA3007A8BF0 /* ChannelTips.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelTips.swift; sourceTree = "<group>"; };
DD77093E2AA1B146007A8BF0 /* UIColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIColor.swift; sourceTree = "<group>"; };
DD798B062915928D005217CD /* ChannelMessageList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelMessageList.swift; sourceTree = "<group>"; };
DD7E235F2C7AA3E50078ACDF /* MeshtasticDataModelV 43.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 43.xcdatamodel"; sourceTree = "<group>"; };
DD8169F8271F1A6100F4AB02 /* MeshLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshLogger.swift; sourceTree = "<group>"; };
DD8169FA271F1F3A00F4AB02 /* MeshLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshLog.swift; sourceTree = "<group>"; };
DD8169FE272476C700F4AB02 /* LogDocument.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogDocument.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1881,6 +1882,7 @@
DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */ = {
isa = XCVersionGroup;
children = (
DD7E235F2C7AA3E50078ACDF /* MeshtasticDataModelV 43.xcdatamodel */,
DD1BD0F12C61D3AD008C0C70 /* MeshtasticDataModelV 42.xcdatamodel */,
DD2984A82C5AEF7500B1268D /* MeshtasticDataModelV 41.xcdatamodel */,
DD68BAE72C417A74004C01A0 /* MeshtasticDataModelV 40.xcdatamodel */,
Expand Down Expand Up @@ -1924,7 +1926,7 @@
DD5D0A9A2931AD6B00F7EA61 /* MeshtasticDataModelV2.xcdatamodel */,
DD3CC6BB28E366DF00FA9159 /* MeshtasticDataModel.xcdatamodel */,
);
currentVersion = DD1BD0F12C61D3AD008C0C70 /* MeshtasticDataModelV 42.xcdatamodel */;
currentVersion = DD7E235F2C7AA3E50078ACDF /* MeshtasticDataModelV 43.xcdatamodel */;
name = Meshtastic.xcdatamodeld;
path = Meshtastic/Meshtastic.xcdatamodeld;
sourceTree = "<group>";
Expand Down
8 changes: 6 additions & 2 deletions Meshtastic/Extensions/CoreData/NodeInfoEntityExtension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ extension NodeInfoEntity {
return self.telemetries?.filtered(using: NSPredicate(format: "metricsType == 1")).lastObject as? TelemetryEntity
}

// var latestLocalStats: TelemetryEntity? {
// return self.telemetries?.filtered(using: NSPredicate(format: "metricsType == 6")).lastObject as? TelemetryEntity
// }

var hasPositions: Bool {
return positions?.count ?? 0 > 0
}
Expand Down Expand Up @@ -52,8 +56,8 @@ extension NodeInfoEntity {
}

var isOnline: Bool {
let fifteenMinutesAgo = Calendar.current.date(byAdding: .minute, value: -15, to: Date())
if lastHeard?.compare(fifteenMinutesAgo!) == .orderedDescending {
let twoHoursAgo = Calendar.current.date(byAdding: .minute, value: -120, to: Date())
if lastHeard?.compare(twoHoursAgo!) == .orderedDescending {
return true
}
return false
Expand Down
77 changes: 48 additions & 29 deletions Meshtastic/Helpers/MeshPackets.swift
Original file line number Diff line number Diff line change
Expand Up @@ -681,14 +681,10 @@ func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManage

if let telemetryMessage = try? Telemetry(serializedData: packet.decoded.payload) {

// Only log telemetry from the mesh not the connected device
if connectedNode != Int64(packet.from) {
let logString = String.localizedStringWithFormat("mesh.log.telemetry.received %@".localized, String(packet.from))
MeshLogger.log("📈 \(logString)")
} else {
// If it is the connected node
}
if telemetryMessage.variant != Telemetry.OneOf_Variant.deviceMetrics(telemetryMessage.deviceMetrics) && telemetryMessage.variant != Telemetry.OneOf_Variant.environmentMetrics(telemetryMessage.environmentMetrics) {
let logString = String.localizedStringWithFormat("mesh.log.telemetry.received %@".localized, String(packet.from))
MeshLogger.log("📈 \(logString)")

if telemetryMessage.variant != Telemetry.OneOf_Variant.deviceMetrics(telemetryMessage.deviceMetrics) && telemetryMessage.variant != Telemetry.OneOf_Variant.environmentMetrics(telemetryMessage.environmentMetrics) && telemetryMessage.variant != Telemetry.OneOf_Variant.localStats(telemetryMessage.localStats) {
/// Other unhandled telemetry packets
return
}
Expand Down Expand Up @@ -727,6 +723,18 @@ func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManage
telemetry.windLull = telemetryMessage.environmentMetrics.windLull
telemetry.windDirection = Int32(truncatingIfNeeded: telemetryMessage.environmentMetrics.windDirection)
telemetry.metricsType = 1
} else if telemetryMessage.variant == Telemetry.OneOf_Variant.localStats(telemetryMessage.localStats) {
// Local Stats for Live activity
telemetry.uptimeSeconds = Int32(telemetryMessage.localStats.uptimeSeconds)
telemetry.channelUtilization = telemetryMessage.localStats.channelUtilization
telemetry.airUtilTx = telemetryMessage.localStats.airUtilTx
telemetry.numPacketsTx = Int32(truncatingIfNeeded: telemetryMessage.localStats.numPacketsTx)
telemetry.numPacketsRx = Int32(truncatingIfNeeded: telemetryMessage.localStats.numPacketsRx)
telemetry.numPacketsRxBad = Int32(truncatingIfNeeded: telemetryMessage.localStats.numPacketsRxBad)
telemetry.numOnlineNodes = Int32(truncatingIfNeeded: telemetryMessage.localStats.numOnlineNodes)
telemetry.numTotalNodes = Int32(truncatingIfNeeded: telemetryMessage.localStats.numTotalNodes)
telemetry.metricsType = 6
Logger.statistics.info("📈 [Mesh Statistics] Channel Utilization: \(telemetryMessage.localStats.channelUtilization, privacy: .public) Airtime: \(telemetryMessage.localStats.airUtilTx, privacy: .public) Packets Sent: \(telemetryMessage.localStats.numPacketsTx, privacy: .public) Packets Received: \(telemetryMessage.localStats.numPacketsRx, privacy: .public) Bad Packets Received: \(telemetryMessage.localStats.numPacketsRxBad, privacy: .public) Nodes Online: \(telemetryMessage.localStats.numOnlineNodes, privacy: .public) of \(telemetryMessage.localStats.numTotalNodes, privacy: .public) nodes for Node: \(packet.from.toHex(), privacy: .public)")
}
telemetry.snr = packet.rxSnr
telemetry.rssi = packet.rxRssi
Expand All @@ -743,34 +751,45 @@ func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManage
fetchedNode[0].telemetries = mutableTelemetries.copy() as? NSOrderedSet
}
try context.save()
// Only log telemetry from the mesh not the connected device
if connectedNode != Int64(packet.from) {
Logger.data.info("💾 [TelemetryEntity] Saved for Node: \(packet.from.toHex())")
} else if telemetry.metricsType == 0 {

Logger.data.info("💾 [TelemetryEntity] Saved for Node: \(packet.from.toHex())")
if telemetry.metricsType == 0 {
// Connected Device Metrics
// ------------------------
// Low Battery notification
if UserDefaults.lowBatteryNotifications && telemetry.batteryLevel > 0 && telemetry.batteryLevel < 4 {
let manager = LocalNotificationManager()
manager.notifications = [
Notification(
id: ("notification.id.\(UUID().uuidString)"),
title: "Critically Low Battery!",
subtitle: "AKA \(telemetry.nodeTelemetry?.user?.shortName ?? "UNK")",
content: "Time to charge your radio, there is \(telemetry.batteryLevel)% battery remaining.",
target: "nodes",
path: "meshtastic:///nodes?nodenum=\(telemetry.nodeTelemetry?.num ?? 0)"
)
]
manager.schedule()
if connectedNode != Int64(packet.from) {
if UserDefaults.lowBatteryNotifications && telemetry.batteryLevel > 0 && telemetry.batteryLevel < 4 {
let manager = LocalNotificationManager()
manager.notifications = [
Notification(
id: ("notification.id.\(UUID().uuidString)"),
title: "Critically Low Battery!",
subtitle: "AKA \(telemetry.nodeTelemetry?.user?.shortName ?? "UNK")",
content: "Time to charge your radio, there is \(telemetry.batteryLevel)% battery remaining.",
target: "nodes",
path: "meshtastic:///nodes?nodenum=\(telemetry.nodeTelemetry?.num ?? 0)"
)
]
manager.schedule()
}
}
} else if telemetry.metricsType == 6 {
// Update our live activity if there is one running, not available on mac iOS >= 16.2
#if !targetEnvironment(macCatalyst)

let oneMinuteLater = Calendar.current.date(byAdding: .minute, value: (Int(1) ), to: Date())!
let date = Date.now...oneMinuteLater
let updatedMeshStatus = MeshActivityAttributes.MeshActivityStatus(timerRange: date, connected: true, channelUtilization: telemetry.channelUtilization, airtime: telemetry.airUtilTx, batteryLevel: UInt32(telemetry.batteryLevel), nodes: 17, nodesOnline: 9)
let alertConfiguration = AlertConfiguration(title: "Mesh activity update", body: "Updated Device Metrics Data.", sound: .default)
let fifteenMinutesLater = Calendar.current.date(byAdding: .minute, value: (Int(15) ), to: Date())!
let date = Date.now...fifteenMinutesLater
let updatedMeshStatus = MeshActivityAttributes.MeshActivityStatus(uptimeSeconds: UInt32(telemetry.uptimeSeconds),
channelUtilization: telemetry.channelUtilization,
airtime: telemetry.airUtilTx,
sentPackets: UInt32(telemetry.numPacketsTx),
receivedPackets: UInt32(telemetry.numPacketsRx),
badReceivedPackets: UInt32(telemetry.numPacketsRxBad),
nodesOnline: UInt32(telemetry.numOnlineNodes),
totalNodes: UInt32(telemetry.numTotalNodes),
timerRange: date)

let alertConfiguration = AlertConfiguration(title: "Mesh activity update", body: "Updated Node Stats Data.", sound: .default)
let updatedContent = ActivityContent(state: updatedMeshStatus, staleDate: nil)

let meshActivity = Activity<MeshActivityAttributes>.activities.first(where: { $0.attributes.nodeNum == connectedNode })
Expand Down
2 changes: 1 addition & 1 deletion Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
<plist version="1.0">
<dict>
<key>_XCCurrentVersionName</key>
<string>MeshtasticDataModelV 42.xcdatamodel</string>
<string>MeshtasticDataModelV 43.xcdatamodel</string>
</dict>
</plist>
Loading