From 85f9cc8ad3eed0d43a14c391a17a42fbdf060f4f Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sat, 24 Aug 2024 18:44:08 -0700 Subject: [PATCH 01/42] Upcate live activity to use the new local stats protobuf --- Localizable.xcstrings | 15 +- Meshtastic.xcodeproj/project.pbxproj | 4 +- Meshtastic/Helpers/MeshPackets.swift | 77 +-- .../Meshtastic.xcdatamodeld/.xccurrentversion | 2 +- .../contents | 475 ++++++++++++++++++ Meshtastic/Views/Bluetooth/Connect.swift | 20 +- Widgets/MeshActivityAttributes.swift | 12 +- Widgets/WidgetsLiveActivity.swift | 118 ++--- 8 files changed, 601 insertions(+), 122 deletions(-) create mode 100644 Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 43.xcdatamodel/contents diff --git a/Localizable.xcstrings b/Localizable.xcstrings index f1806ddf..9989f2b7 100644 --- a/Localizable.xcstrings +++ b/Localizable.xcstrings @@ -6325,7 +6325,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." : { @@ -7227,12 +7227,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" : { @@ -14565,9 +14565,10 @@ }, "Message content exceeds 228 bytes." : { + }, "Message Status Options" : { - + }, "message.details" : { "localizations" : { @@ -22241,7 +22242,7 @@ } } }, - "Updated Device Metrics Data." : { + "Updated Node Stats Data." : { }, "Updated: %@" : { diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 6b02e8f1..dcb2a600 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -368,6 +368,7 @@ DD77093C2AA1AFA3007A8BF0 /* ChannelTips.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelTips.swift; sourceTree = ""; }; DD77093E2AA1B146007A8BF0 /* UIColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIColor.swift; sourceTree = ""; }; DD798B062915928D005217CD /* ChannelMessageList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelMessageList.swift; sourceTree = ""; }; + DD7E235F2C7AA3E50078ACDF /* MeshtasticDataModelV 43.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 43.xcdatamodel"; sourceTree = ""; }; DD8169F8271F1A6100F4AB02 /* MeshLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshLogger.swift; sourceTree = ""; }; DD8169FA271F1F3A00F4AB02 /* MeshLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshLog.swift; sourceTree = ""; }; DD8169FE272476C700F4AB02 /* LogDocument.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogDocument.swift; sourceTree = ""; }; @@ -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 */, @@ -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 = ""; diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index d3bef97a..12ed1f1b 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -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 } @@ -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 @@ -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.activities.first(where: { $0.attributes.nodeNum == connectedNode }) diff --git a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion index 3ddf90f8..04d3cc1a 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion +++ b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion @@ -3,6 +3,6 @@ _XCCurrentVersionName - MeshtasticDataModelV 42.xcdatamodel + MeshtasticDataModelV 43.xcdatamodel diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 43.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 43.xcdatamodel/contents new file mode 100644 index 00000000..544d44c1 --- /dev/null +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 43.xcdatamodel/contents @@ -0,0 +1,475 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Meshtastic/Views/Bluetooth/Connect.swift b/Meshtastic/Views/Bluetooth/Connect.swift index 0048e430..58d8757b 100644 --- a/Meshtastic/Views/Bluetooth/Connect.swift +++ b/Meshtastic/Views/Bluetooth/Connect.swift @@ -327,17 +327,25 @@ struct Connect: View { #if canImport(ActivityKit) func startNodeActivity() { liveActivityStarted = true - let timerSeconds = 60 - let deviceMetrics = node?.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0")) - let mostRecent = deviceMetrics?.lastObject as? TelemetryEntity + // 15 Minutes Local Stats Interval + let timerSeconds = 900 + let localStats = node?.telemetries?.filtered(using: NSPredicate(format: "metricsType == 6")) + let mostRecent = localStats?.lastObject as? TelemetryEntity let activityAttributes = MeshActivityAttributes(nodeNum: Int(node?.num ?? 0), name: node?.user?.longName ?? "unknown") let future = Date(timeIntervalSinceNow: Double(timerSeconds)) + let initialContentState = MeshActivityAttributes.ContentState(uptimeSeconds: UInt32(mostRecent?.uptimeSeconds ?? 0), + channelUtilization: mostRecent?.channelUtilization ?? 0.0, + airtime: mostRecent?.airUtilTx ?? 0.0, + sentPackets: UInt32(mostRecent?.numPacketsTx ?? 0), + receivedPackets: UInt32(mostRecent?.numPacketsRx ?? 0), + badReceivedPackets: UInt32(mostRecent?.numPacketsRxBad ?? 0), + nodesOnline: UInt32(mostRecent?.numOnlineNodes ?? 0), + totalNodes: UInt32(mostRecent?.numTotalNodes ?? 0), + timerRange: Date.now...future) - let initialContentState = MeshActivityAttributes.ContentState(timerRange: Date.now...future, connected: true, channelUtilization: mostRecent?.channelUtilization ?? 0.0, airtime: mostRecent?.airUtilTx ?? 0.0, batteryLevel: UInt32(mostRecent?.batteryLevel ?? 0), nodes: 17, nodesOnline: 9) - - let activityContent = ActivityContent(state: initialContentState, staleDate: Calendar.current.date(byAdding: .minute, value: 2, to: Date())!) + let activityContent = ActivityContent(state: initialContentState, staleDate: Calendar.current.date(byAdding: .minute, value: 15, to: Date())!) do { let myActivity = try Activity.request(attributes: activityAttributes, content: activityContent, diff --git a/Widgets/MeshActivityAttributes.swift b/Widgets/MeshActivityAttributes.swift index 916377c9..a2abdbba 100644 --- a/Widgets/MeshActivityAttributes.swift +++ b/Widgets/MeshActivityAttributes.swift @@ -15,13 +15,15 @@ struct MeshActivityAttributes: ActivityAttributes { public typealias MeshActivityStatus = ContentState public struct ContentState: Codable, Hashable { // Dynamic stateful properties about your activity go here! - var timerRange: ClosedRange - var connected: Bool + var uptimeSeconds: UInt32 var channelUtilization: Float var airtime: Float - var batteryLevel: UInt32 - var nodes: Int - var nodesOnline: Int + var sentPackets: UInt32 + var receivedPackets: UInt32 + var badReceivedPackets: UInt32 + var nodesOnline: UInt32 + var totalNodes: UInt32 + var timerRange: ClosedRange } // Fixed non-changing properties about your activity go here! diff --git a/Widgets/WidgetsLiveActivity.swift b/Widgets/WidgetsLiveActivity.swift index 396aaac9..690fc298 100644 --- a/Widgets/WidgetsLiveActivity.swift +++ b/Widgets/WidgetsLiveActivity.swift @@ -13,7 +13,16 @@ struct WidgetsLiveActivity: Widget { var body: some WidgetConfiguration { ActivityConfiguration(for: MeshActivityAttributes.self) { context in - LiveActivityView(nodeName: context.attributes.name, channelUtilization: context.state.channelUtilization, airtime: context.state.airtime, batteryLevel: context.state.batteryLevel, nodes: 17, nodesOnline: 7, timerRange: context.state.timerRange) + LiveActivityView(nodeName: context.attributes.name, + uptimeSeconds: 0, // context.attributes.uptimeSeconds, + channelUtilization: context.state.channelUtilization, + airtime: context.state.airtime, + sentPackets: context.state.sentPackets, + receivedPackets: context.state.receivedPackets, + badReceivedPackets: context.state.badReceivedPackets, + nodesOnline: context.state.nodesOnline, + totalNodes: context.state.totalNodes, + timerRange: context.state.timerRange) .widgetURL(URL(string: "meshtastic:///node/\(context.attributes.name)")) } dynamicIsland: { context in @@ -38,24 +47,7 @@ struct WidgetsLiveActivity: Widget { Spacer() } DynamicIslandExpandedRegion(.center) { - VStack(alignment: .center, spacing: 0) { - BatteryIcon(batteryLevel: Int32(context.state.batteryLevel), font: .title, color: .accentColor) - if context.state.batteryLevel == 0 { - Text("< 1%") - .font(.title3) - .foregroundColor(.gray) - .fixedSize() - } else if context.state.batteryLevel < 101 { - Text(String(context.state.batteryLevel) + "%") - .font(.title3) - .foregroundColor(.gray) - .fixedSize() - } else { - Text("PWD") - .font(.title3) - .foregroundColor(.gray) - } - } + // Used to be battery } DynamicIslandExpandedRegion(.trailing, priority: 1) { TimerView(timerRange: context.state.timerRange) @@ -100,38 +92,40 @@ struct WidgetsLiveActivity: Widget { } } -struct WidgetsLiveActivity_Previews: PreviewProvider { - static let attributes = MeshActivityAttributes(nodeNum: 123456789, name: "RAK Compact Rotary Handset Gray 8E6G") - static let state = MeshActivityAttributes.ContentState( - timerRange: Date.now...Date(timeIntervalSinceNow: 60), connected: true, channelUtilization: 25.84, airtime: 10.01, batteryLevel: 39, nodes: 17, nodesOnline: 9) - - static var previews: some View { - attributes - .previewContext(state, viewKind: .dynamicIsland(.compact)) - .previewDisplayName("Compact") - attributes - .previewContext(state, viewKind: .dynamicIsland(.minimal)) - .previewDisplayName("Minimal") - attributes - .previewContext(state, viewKind: .dynamicIsland(.expanded)) - .previewDisplayName("Expanded") - attributes - .previewContext(state, viewKind: .content) - .previewDisplayName("Notification") - } -} +//struct WidgetsLiveActivity_Previews: PreviewProvider { +// static let attributes = MeshActivityAttributes(nodeNum: 123456789, name: "RAK Compact Rotary Handset Gray 8E6G") +// static let state = MeshActivityAttributes.ContentState( +// timerRange: Date.now...Date(timeIntervalSinceNow: 60), connected: true, channelUtilization: 25.84, airtime: 10.01, batteryLevel: 39, nodes: 17, nodesOnline: 9) +// +// static var previews: some View { +// attributes +// .previewContext(state, viewKind: .dynamicIsland(.compact)) +// .previewDisplayName("Compact") +// attributes +// .previewContext(state, viewKind: .dynamicIsland(.minimal)) +// .previewDisplayName("Minimal") +// attributes +// .previewContext(state, viewKind: .dynamicIsland(.expanded)) +// .previewDisplayName("Expanded") +// attributes +// .previewContext(state, viewKind: .content) +// .previewDisplayName("Notification") +// } +//} struct LiveActivityView: View { @Environment(\.colorScheme) private var colorScheme @Environment(\.isLuminanceReduced) var isLuminanceReduced var nodeName: String - // var connected: Bool + var uptimeSeconds: UInt32 var channelUtilization: Float var airtime: Float - var batteryLevel: UInt32 - var nodes: Int - var nodesOnline: Int + var sentPackets: UInt32 + var receivedPackets: UInt32 + var badReceivedPackets: UInt32 + var nodesOnline: UInt32 + var totalNodes: UInt32 var timerRange: ClosedRange var body: some View { @@ -143,33 +137,8 @@ struct LiveActivityView: View { .aspectRatio(contentMode: .fit) .frame(width: 65) Spacer() - NodeInfoView(nodeName: nodeName, timerRange: timerRange, channelUtilization: channelUtilization, airtime: airtime, batteryLevel: batteryLevel, nodes: nodes, nodesOnline: nodesOnline) + NodeInfoView(isLuminanceReduced: _isLuminanceReduced, nodeName: nodeName, uptimeSeconds: uptimeSeconds, channelUtilization: channelUtilization, airtime: airtime, sentPackets: sentPackets, receivedPackets: receivedPackets, badReceivedPackets: badReceivedPackets, nodesOnline: nodesOnline, totalNodes: totalNodes, timerRange: timerRange) Spacer() - VStack { - BatteryIcon(batteryLevel: Int32(batteryLevel), font: .title, color: .secondary) - if batteryLevel == 0 { - Text("< 1%") - .font(.headline) - .fontWeight(.medium) - .foregroundStyle(.secondary) - .opacity(isLuminanceReduced ? 0.8 : 1.0) - .fixedSize() - } else if batteryLevel < 101 { - Text(String(batteryLevel) + "%") - .font(.headline) - .fontWeight(.medium) - .foregroundStyle(.secondary) - .opacity(isLuminanceReduced ? 0.8 : 1.0) - .fixedSize() - } else { - Text("Plugged In") - .font(.headline) - .fontWeight(.medium) - .foregroundStyle(.secondary) - .opacity(isLuminanceReduced ? 0.8 : 1.0) - .fixedSize() - } - } } .tint(.primary) .padding([.leading, .top, .bottom]) @@ -183,12 +152,15 @@ struct NodeInfoView: View { @Environment(\.isLuminanceReduced) var isLuminanceReduced var nodeName: String - var timerRange: ClosedRange + var uptimeSeconds: UInt32 var channelUtilization: Float var airtime: Float - var batteryLevel: UInt32 - var nodes: Int - var nodesOnline: Int + var sentPackets: UInt32 + var receivedPackets: UInt32 + var badReceivedPackets: UInt32 + var nodesOnline: UInt32 + var totalNodes: UInt32 + var timerRange: ClosedRange var body: some View { VStack(alignment: .leading, spacing: 0) { From 716c09cf94101d8bd81f2530a94425855bac11c0 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sat, 24 Aug 2024 19:26:50 -0700 Subject: [PATCH 02/42] Update live activity to use new local stats packet --- Widgets/WidgetsLiveActivity.swift | 37 +++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/Widgets/WidgetsLiveActivity.swift b/Widgets/WidgetsLiveActivity.swift index 690fc298..95a1bc94 100644 --- a/Widgets/WidgetsLiveActivity.swift +++ b/Widgets/WidgetsLiveActivity.swift @@ -130,19 +130,20 @@ struct LiveActivityView: View { var body: some View { HStack { + Spacer() Image(colorScheme == .light ? "m-logo-black" : "m-logo-white") .resizable() .clipShape(ContainerRelativeShape()) .opacity(isLuminanceReduced ? 0.5 : 1.0) .aspectRatio(contentMode: .fit) - .frame(width: 65) + .frame(minWidth: 25, idealWidth: 45, maxWidth: 55) Spacer() NodeInfoView(isLuminanceReduced: _isLuminanceReduced, nodeName: nodeName, uptimeSeconds: uptimeSeconds, channelUtilization: channelUtilization, airtime: airtime, sentPackets: sentPackets, receivedPackets: receivedPackets, badReceivedPackets: badReceivedPackets, nodesOnline: nodesOnline, totalNodes: totalNodes, timerRange: timerRange) Spacer() } .tint(.primary) .padding([.leading, .top, .bottom]) - .padding(.trailing, 32) + .padding(.trailing, 25) .activityBackgroundTint(colorScheme == .light ? Color("LiveActivityBackground") : Color("AccentColorDimmed")) .activitySystemActionForegroundColor(.primary) } @@ -168,24 +169,36 @@ struct NodeInfoView: View { .font(nodeName.count > 14 ? .callout : .title3) .fontWeight(.semibold) .foregroundStyle(.tint) - Text("\(String(format: "Ch. Util: %.2f", channelUtilization))%") - .font(.headline) + Text("\(String(format: "Ch. Util: %.2f", channelUtilization))% \(String(format: "Airtime: %.2f", airtime))%") + .font(.caption) .fontWeight(.medium) .foregroundStyle(.secondary) .opacity(isLuminanceReduced ? 0.8 : 1.0) .fixedSize() - Text("\(String(format: "Airtime: %.2f", airtime))%") - .font(.headline) + Text("Packets Sent \(sentPackets)") + .font(.caption) + .fontWeight(.medium) + .foregroundStyle(.secondary) + .opacity(isLuminanceReduced ? 0.8 : 1.0) + .fixedSize() + Text("Packets Received \(receivedPackets)") + .font(.caption) + .fontWeight(.medium) + .foregroundStyle(.secondary) + .opacity(isLuminanceReduced ? 0.8 : 1.0) + .fixedSize() + Text("Bad Packets Received \(badReceivedPackets)") + .font(.caption) + .fontWeight(.medium) + .foregroundStyle(.secondary) + .opacity(isLuminanceReduced ? 0.8 : 1.0) + .fixedSize() + Text("\(String(format: "Connected: %d nodes online", nodesOnline, totalNodes))") + .font(.caption) .fontWeight(.medium) .foregroundStyle(.secondary) .opacity(isLuminanceReduced ? 0.8 : 1.0) .fixedSize() -// Text("\(String(format: "Connected: %d of %d online", nodesOnline, nodes))") -// .font(.callout) -// .fontWeight(.medium) -// .foregroundStyle(.secondary) -// .opacity(isLuminanceReduced ? 0.8 : 1.0) -// .fixedSize() let now = Date() Text("Last Heard: \(now.formatted())") .font(.caption) From 8ac18430e2e222ca3554ba4b50687722cc2b99a6 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sat, 24 Aug 2024 20:56:40 -0700 Subject: [PATCH 03/42] Dynamic island --- Widgets/WidgetsLiveActivity.swift | 63 +++++++++++++++++++++---------- 1 file changed, 43 insertions(+), 20 deletions(-) diff --git a/Widgets/WidgetsLiveActivity.swift b/Widgets/WidgetsLiveActivity.swift index 95a1bc94..5335db7c 100644 --- a/Widgets/WidgetsLiveActivity.swift +++ b/Widgets/WidgetsLiveActivity.swift @@ -28,41 +28,62 @@ struct WidgetsLiveActivity: Widget { } dynamicIsland: { context in DynamicIsland { DynamicIslandExpandedRegion(.leading) { - Text("Network") - .font(.headline) - .fontWeight(.bold) + HStack(alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/) { + Spacer() + Text("Mesh") + .font(.callout) + .fontWeight(.medium) + .foregroundStyle(.primary) + .padding(.bottom, 10) + .fixedSize() + Spacer() + } + Text("\(context.state.nodesOnline) online") + .font(.caption) .foregroundStyle(.secondary) .fixedSize() - .padding(.top, 10) Text("\(String(format: "Ch. Util: %.2f", context.state.channelUtilization))%") - .font(.headline) - .fontWeight(.medium) + .font(.caption) .foregroundStyle(.secondary) .fixedSize() Text("\(String(format: "Airtime: %.2f", context.state.airtime))%") - .font(.headline) - .fontWeight(.medium) + .font(.caption) .foregroundStyle(.secondary) .fixedSize() - Spacer() } DynamicIslandExpandedRegion(.center) { - // Used to be battery - } - DynamicIslandExpandedRegion(.trailing, priority: 1) { TimerView(timerRange: context.state.timerRange) .tint(Color("LightIndigo")) - + } + DynamicIslandExpandedRegion(.trailing, priority: 1) { + HStack(alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/) { + Spacer() + Text("Packets") + .font(.callout) + .fontWeight(.medium) + .foregroundStyle(.primary) + .padding(.bottom, 10) + .fixedSize() + Spacer() + } + Text("Sent \(context.state.sentPackets)") + .font(.caption) + .foregroundStyle(.secondary) + .fixedSize() + Text("Received \(context.state.receivedPackets)") + .font(.caption) + .foregroundStyle(.secondary) + .fixedSize() + Text("Bad \(context.state.badReceivedPackets)") + .font(.caption) + .foregroundStyle(.secondary) + .fixedSize() } DynamicIslandExpandedRegion(.bottom) { - Text(context.attributes.name) - .font(context.attributes.name.count > 14 ? .callout : .title3) - .fontWeight(.semibold) - .foregroundStyle(.tint) Text("Last Heard: \(Date().formatted())") .font(.caption) .fontWeight(.medium) - .foregroundStyle(.secondary) + .foregroundStyle(.tint) .fixedSize() } @@ -240,8 +261,9 @@ struct TimerView: View { var body: some View { VStack(alignment: .center) { - Text("NEXT UPDATE") - .font(.caption) + Text("UPDATE IN") + .font(.caption2) + .allowsTightening(/*@START_MENU_TOKEN@*/true/*@END_MENU_TOKEN@*/) .fontWeight(.medium) .foregroundStyle(.secondary) .opacity(isLuminanceReduced ? 0.5 : 1.0) @@ -257,6 +279,7 @@ struct TimerView: View { .foregroundStyle(.secondary) .frame(width: 30, height: 30) .opacity(isLuminanceReduced ? 0.5 : 1.0) + .offset(y: -5) } } } From 799f06f87333e142b1e7b3ddf014cd64f560148a Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sat, 24 Aug 2024 21:54:21 -0700 Subject: [PATCH 04/42] Multicolor timer icon --- Widgets/WidgetsLiveActivity.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Widgets/WidgetsLiveActivity.swift b/Widgets/WidgetsLiveActivity.swift index 5335db7c..f667d3af 100644 --- a/Widgets/WidgetsLiveActivity.swift +++ b/Widgets/WidgetsLiveActivity.swift @@ -275,6 +275,7 @@ struct TimerView: View { .fontWeight(.semibold) .foregroundStyle(.tint) Image(systemName: "timer") + .symbolRenderingMode(.multicolor) .resizable() .foregroundStyle(.secondary) .frame(width: 30, height: 30) From b9d7c1588322fb9178ad4be6166a059cd7451c08 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 25 Aug 2024 09:08:49 -0700 Subject: [PATCH 05/42] Add dupes to bad labels to make it clear these are not all errors --- Widgets/WidgetsLiveActivity.swift | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Widgets/WidgetsLiveActivity.swift b/Widgets/WidgetsLiveActivity.swift index f667d3af..0235f566 100644 --- a/Widgets/WidgetsLiveActivity.swift +++ b/Widgets/WidgetsLiveActivity.swift @@ -38,7 +38,7 @@ struct WidgetsLiveActivity: Widget { .fixedSize() Spacer() } - Text("\(context.state.nodesOnline) online") + Text("\(context.state.nodesOnline) nodes online") .font(.caption) .foregroundStyle(.secondary) .fixedSize() @@ -66,15 +66,15 @@ struct WidgetsLiveActivity: Widget { .fixedSize() Spacer() } - Text("Sent \(context.state.sentPackets)") + Text("Sent: \(context.state.sentPackets)") .font(.caption) .foregroundStyle(.secondary) .fixedSize() - Text("Received \(context.state.receivedPackets)") + Text("Received: \(context.state.receivedPackets)") .font(.caption) .foregroundStyle(.secondary) .fixedSize() - Text("Bad \(context.state.badReceivedPackets)") + Text("Dupe / Bad: \(context.state.badReceivedPackets)") .font(.caption) .foregroundStyle(.secondary) .fixedSize() @@ -196,19 +196,19 @@ struct NodeInfoView: View { .foregroundStyle(.secondary) .opacity(isLuminanceReduced ? 0.8 : 1.0) .fixedSize() - Text("Packets Sent \(sentPackets)") + Text("Packets Sent: \(sentPackets)") .font(.caption) .fontWeight(.medium) .foregroundStyle(.secondary) .opacity(isLuminanceReduced ? 0.8 : 1.0) .fixedSize() - Text("Packets Received \(receivedPackets)") + Text("Packets Received: \(receivedPackets)") .font(.caption) .fontWeight(.medium) .foregroundStyle(.secondary) .opacity(isLuminanceReduced ? 0.8 : 1.0) .fixedSize() - Text("Bad Packets Received \(badReceivedPackets)") + Text("Dupe / Bad Packets: \(badReceivedPackets)") .font(.caption) .fontWeight(.medium) .foregroundStyle(.secondary) From 4361a082c5583a15445b145632755323f24c5cee Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 25 Aug 2024 09:50:01 -0700 Subject: [PATCH 06/42] Sync up online timeframes with the stats at seen in the last 2 hours --- Meshtastic/Extensions/CoreData/NodeInfoEntityExtension.swift | 4 ++-- Meshtastic/Views/Messages/UserList.swift | 2 +- Meshtastic/Views/Nodes/NodeList.swift | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Meshtastic/Extensions/CoreData/NodeInfoEntityExtension.swift b/Meshtastic/Extensions/CoreData/NodeInfoEntityExtension.swift index 07ee9117..c1bd5bec 100644 --- a/Meshtastic/Extensions/CoreData/NodeInfoEntityExtension.swift +++ b/Meshtastic/Extensions/CoreData/NodeInfoEntityExtension.swift @@ -52,8 +52,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 diff --git a/Meshtastic/Views/Messages/UserList.swift b/Meshtastic/Views/Messages/UserList.swift index bf953d5b..3a2c4a47 100644 --- a/Meshtastic/Views/Messages/UserList.swift +++ b/Meshtastic/Views/Messages/UserList.swift @@ -307,7 +307,7 @@ struct UserList: View { } /// Online if isOnline { - let isOnlinePredicate = NSPredicate(format: "userNode.lastHeard >= %@", Calendar.current.date(byAdding: .minute, value: -15, to: Date())! as NSDate) + let isOnlinePredicate = NSPredicate(format: "userNode.lastHeard >= %@", Calendar.current.date(byAdding: .minute, value: -120, to: Date())! as NSDate) predicates.append(isOnlinePredicate) } /// Encrypted diff --git a/Meshtastic/Views/Nodes/NodeList.swift b/Meshtastic/Views/Nodes/NodeList.swift index 67636328..c5c92418 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -390,7 +390,7 @@ struct NodeList: View { } /// Online if isOnline { - let isOnlinePredicate = NSPredicate(format: "lastHeard >= %@", Calendar.current.date(byAdding: .minute, value: -15, to: Date())! as NSDate) + let isOnlinePredicate = NSPredicate(format: "lastHeard >= %@", Calendar.current.date(byAdding: .minute, value: -120, to: Date())! as NSDate) predicates.append(isOnlinePredicate) } /// Encrypted From 25f8e424ed7b9a49b706c7c5b43b2f339a8f542d Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 25 Aug 2024 10:08:32 -0700 Subject: [PATCH 07/42] Sync up isonline with the firmware add local stats extension --- Meshtastic/Extensions/CoreData/NodeInfoEntityExtension.swift | 4 ++++ Meshtastic/Views/Messages/UserList.swift | 3 +++ Meshtastic/Views/Nodes/MeshMap.swift | 2 +- Meshtastic/Views/Nodes/NodeList.swift | 5 +++++ 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/Meshtastic/Extensions/CoreData/NodeInfoEntityExtension.swift b/Meshtastic/Extensions/CoreData/NodeInfoEntityExtension.swift index c1bd5bec..ba8e38fa 100644 --- a/Meshtastic/Extensions/CoreData/NodeInfoEntityExtension.swift +++ b/Meshtastic/Extensions/CoreData/NodeInfoEntityExtension.swift @@ -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 } diff --git a/Meshtastic/Views/Messages/UserList.swift b/Meshtastic/Views/Messages/UserList.swift index 3a2c4a47..7ba2205c 100644 --- a/Meshtastic/Views/Messages/UserList.swift +++ b/Meshtastic/Views/Messages/UserList.swift @@ -227,6 +227,9 @@ struct UserList: View { .onChange(of: maxDistance) { _ in searchUserList() } + .onReceive(users.publisher) { _ in + searchUserList() + } .onAppear { searchUserList() } diff --git a/Meshtastic/Views/Nodes/MeshMap.swift b/Meshtastic/Views/Nodes/MeshMap.swift index 9e09a0c0..f471caaf 100644 --- a/Meshtastic/Views/Nodes/MeshMap.swift +++ b/Meshtastic/Views/Nodes/MeshMap.swift @@ -126,7 +126,7 @@ struct MeshMap: View { guard case .map(let selectedNodeNum) = router.navigationState else { return } // TODO: handle deep link for waypoints } - .onChange(of: (selectedMapLayer)) { newMapLayer in + .onChange(of: selectedMapLayer) { newMapLayer in switch selectedMapLayer { case .standard: UserDefaults.mapLayer = newMapLayer diff --git a/Meshtastic/Views/Nodes/NodeList.swift b/Meshtastic/Views/Nodes/NodeList.swift index c5c92418..c0f16dd8 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -344,6 +344,11 @@ struct NodeList: View { self.selectedNode = nil } } + .onReceive(nodes.publisher) { _ in + Task { + await searchNodeList() + } + } .onAppear { Task { await searchNodeList() From cfdcc5a2c3e97c74c0a2f4f96959afbee1b73377 Mon Sep 17 00:00:00 2001 From: Benjamin Faershtein <119711889+RCGV1@users.noreply.github.com> Date: Sun, 25 Aug 2024 21:14:25 -0700 Subject: [PATCH 08/42] Added Shortcuts provider and Shut Down, Reboot, and Factory Reset Intents. Also added logging for intent errors. --- Localizable.xcstrings | 47 +++++++++++++++---- Meshtastic.xcodeproj/project.pbxproj | 16 +++++++ Meshtastic/AppIntents/AppIntentErrors.swift | 9 +++- .../AppIntents/FactoryResetNodeIntent.swift | 41 ++++++++++++++++ .../AppIntents/MessageChannelIntent.swift | 2 +- .../AppIntents/NodePositionIntent.swift | 1 + Meshtastic/AppIntents/RestartNodeIntent.swift | 42 +++++++++++++++++ .../AppIntents/SendWaypointIntent.swift | 2 +- Meshtastic/AppIntents/ShortcutsProvider.swift | 37 +++++++++++++++ .../AppIntents/ShutDownNodeIntent.swift | 41 ++++++++++++++++ 10 files changed, 226 insertions(+), 12 deletions(-) create mode 100644 Meshtastic/AppIntents/FactoryResetNodeIntent.swift create mode 100644 Meshtastic/AppIntents/RestartNodeIntent.swift create mode 100644 Meshtastic/AppIntents/ShortcutsProvider.swift create mode 100644 Meshtastic/AppIntents/ShutDownNodeIntent.swift diff --git a/Localizable.xcstrings b/Localizable.xcstrings index f1806ddf..74281fbd 100644 --- a/Localizable.xcstrings +++ b/Localizable.xcstrings @@ -1166,6 +1166,9 @@ }, "Are you sure you want to delete this message?" : { + }, + "Are you sure you want to factory reset the node?" : { + }, "are.you.sure" : { "localizations" : { @@ -6325,7 +6328,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." : { @@ -7209,6 +7212,9 @@ }, "Factory Reset" : { + }, + "Factory Reset Node" : { + }, "Factory reset your device and app? " : { @@ -7227,12 +7233,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" : { @@ -14563,11 +14569,15 @@ }, "Message" : { + }, + "Message Channel" : { + }, "Message content exceeds 228 bytes." : { + }, "Message Status Options" : { - + }, "message.details" : { "localizations" : { @@ -16319,6 +16329,9 @@ } } } + }, + "Perform a factory reset on the node you are connected to" : { + }, "phone.gps" : { "localizations" : { @@ -17024,6 +17037,9 @@ } } } + }, + "Reboot Node?" : { + }, "reboot.node" : { "localizations" : { @@ -17389,6 +17405,12 @@ }, "Reset NodeDB" : { + }, + "Restart Node" : { + + }, + "Restart to the node you are connected to" : { + }, "restore" : { @@ -19131,13 +19153,16 @@ "Send" : { }, - "Send a channel message" : { + "Send a Channel Message" : { }, "Send a message to a certain meshtastic channel" : { }, - "Send a waypoint" : { + "Send a shutdown to the node you are connected to" : { + + }, + "Send a Waypoint" : { }, "Send ASCII bell with alert message. Useful for triggering external notification on bell." : { @@ -19876,6 +19901,12 @@ }, "Show Weather" : { + }, + "Shut Down Node" : { + + }, + "Shut Down Node?" : { + }, "Shutdown Node?" : { diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 6b02e8f1..09d0f827 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -36,6 +36,10 @@ BCB613832C672A2600485544 /* MessageChannelIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCB613822C672A2600485544 /* MessageChannelIntent.swift */; }; BCB613852C68703800485544 /* NodePositionIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCB613842C68703800485544 /* NodePositionIntent.swift */; }; BCB613872C69A0FB00485544 /* AppIntentErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCB613862C69A0FB00485544 /* AppIntentErrors.swift */; }; + BCE2D3C32C7ADF42008E6199 /* ShutDownNodeIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCE2D3C22C7ADF42008E6199 /* ShutDownNodeIntent.swift */; }; + BCE2D3C52C7AE369008E6199 /* RestartNodeIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCE2D3C42C7AE369008E6199 /* RestartNodeIntent.swift */; }; + BCE2D3C72C7B0D0A008E6199 /* ShortcutsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCE2D3C62C7B0D0A008E6199 /* ShortcutsProvider.swift */; }; + BCE2D3C92C7C377F008E6199 /* FactoryResetNodeIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCE2D3C82C7C377F008E6199 /* FactoryResetNodeIntent.swift */; }; C9697FA527933B8C00250207 /* SQLite in Frameworks */ = {isa = PBXBuildFile; productRef = C9697FA427933B8C00250207 /* SQLite */; }; D93068D32B8129510066FBC8 /* MessageContextMenuItems.swift in Sources */ = {isa = PBXBuildFile; fileRef = D93068D22B8129510066FBC8 /* MessageContextMenuItems.swift */; }; D93068D52B812B700066FBC8 /* MessageDestination.swift in Sources */ = {isa = PBXBuildFile; fileRef = D93068D42B812B700066FBC8 /* MessageDestination.swift */; }; @@ -275,6 +279,10 @@ BCB613822C672A2600485544 /* MessageChannelIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageChannelIntent.swift; sourceTree = ""; }; BCB613842C68703800485544 /* NodePositionIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodePositionIntent.swift; sourceTree = ""; }; BCB613862C69A0FB00485544 /* AppIntentErrors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIntentErrors.swift; sourceTree = ""; }; + BCE2D3C22C7ADF42008E6199 /* ShutDownNodeIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShutDownNodeIntent.swift; sourceTree = ""; }; + BCE2D3C42C7AE369008E6199 /* RestartNodeIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestartNodeIntent.swift; sourceTree = ""; }; + BCE2D3C62C7B0D0A008E6199 /* ShortcutsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutsProvider.swift; sourceTree = ""; }; + BCE2D3C82C7C377F008E6199 /* FactoryResetNodeIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FactoryResetNodeIntent.swift; sourceTree = ""; }; D93068D22B8129510066FBC8 /* MessageContextMenuItems.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageContextMenuItems.swift; sourceTree = ""; }; D93068D42B812B700066FBC8 /* MessageDestination.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageDestination.swift; sourceTree = ""; }; D93068D62B8146690066FBC8 /* MessageText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageText.swift; sourceTree = ""; }; @@ -573,6 +581,10 @@ BCB613822C672A2600485544 /* MessageChannelIntent.swift */, BCB613842C68703800485544 /* NodePositionIntent.swift */, BCB613862C69A0FB00485544 /* AppIntentErrors.swift */, + BCE2D3C22C7ADF42008E6199 /* ShutDownNodeIntent.swift */, + BCE2D3C42C7AE369008E6199 /* RestartNodeIntent.swift */, + BCE2D3C82C7C377F008E6199 /* FactoryResetNodeIntent.swift */, + BCE2D3C62C7B0D0A008E6199 /* ShortcutsProvider.swift */, ); path = AppIntents; sourceTree = ""; @@ -1257,6 +1269,7 @@ 25F26B1F2C2F611300C9CD9D /* AppData.swift in Sources */, 25F26B1E2C2F610D00C9CD9D /* Logger.swift in Sources */, 259792252C2F114500AD1659 /* ChannelEntityExtension.swift in Sources */, + BCE2D3C52C7AE369008E6199 /* RestartNodeIntent.swift in Sources */, 259792262C2F114500AD1659 /* PositionEntityExtension.swift in Sources */, 259792272C2F114500AD1659 /* TraceRouteEntityExtension.swift in Sources */, DDDB444829F8A9C900EE2349 /* String.swift in Sources */, @@ -1361,6 +1374,7 @@ D93068D32B8129510066FBC8 /* MessageContextMenuItems.swift in Sources */, DD8EBF43285058FA00426DCA /* DisplayConfig.swift in Sources */, DD964FC42974767D007C176F /* MapViewFitExtension.swift in Sources */, + BCE2D3C72C7B0D0A008E6199 /* ShortcutsProvider.swift in Sources */, DD47E3D626F17ED900029299 /* CircleText.swift in Sources */, DDC2E18F26CE25FE0042C5E4 /* ContentView.swift in Sources */, DD2553572855B02500E55709 /* LoRaConfig.swift in Sources */, @@ -1421,6 +1435,7 @@ BCB613872C69A0FB00485544 /* AppIntentErrors.swift in Sources */, DD73FD1128750779000852D6 /* PositionLog.swift in Sources */, DD15E4F52B8BFC8E00654F61 /* PaxCounterLog.swift in Sources */, + BCE2D3C32C7ADF42008E6199 /* ShutDownNodeIntent.swift in Sources */, 25F5D5C22C3F6E4B008036E3 /* AppState.swift in Sources */, DD3CC6C028E7A60700FA9159 /* MessagingEnums.swift in Sources */, DD6F657B2C6EC2900053C113 /* LockLegend.swift in Sources */, @@ -1436,6 +1451,7 @@ DDDB26442AAC0206003AFCB7 /* NodeDetail.swift in Sources */, DD77093F2AA1B146007A8BF0 /* UIColor.swift in Sources */, DDF6B2482A9AEBF500BA6931 /* StoreForwardConfig.swift in Sources */, + BCE2D3C92C7C377F008E6199 /* FactoryResetNodeIntent.swift in Sources */, DD8169F9271F1A6100F4AB02 /* MeshLogger.swift in Sources */, DD93800B2BA3F968008BEC06 /* NodeMapContent.swift in Sources */, DD41582A28585C32009B0E59 /* RangeTestConfig.swift in Sources */, diff --git a/Meshtastic/AppIntents/AppIntentErrors.swift b/Meshtastic/AppIntents/AppIntentErrors.swift index c20ead7c..8e80b6b6 100644 --- a/Meshtastic/AppIntents/AppIntentErrors.swift +++ b/Meshtastic/AppIntents/AppIntentErrors.swift @@ -6,6 +6,7 @@ // import Foundation +import OSLog class AppIntentErrors { enum AppIntentError: Swift.Error, CustomLocalizedStringResourceConvertible { @@ -14,8 +15,12 @@ class AppIntentErrors { var localizedStringResource: LocalizedStringResource { switch self { - case let .message(message): return "Error: \(message)" - case .notConnected: return "No Connected Node" + case let .message(message): + Logger.services.error("App Intent: \(message)") + return "Error: \(message)" + case .notConnected: + Logger.services.error("App Intent: No Connected Node") + return "No Connected Node" } } } diff --git a/Meshtastic/AppIntents/FactoryResetNodeIntent.swift b/Meshtastic/AppIntents/FactoryResetNodeIntent.swift new file mode 100644 index 00000000..f83a507b --- /dev/null +++ b/Meshtastic/AppIntents/FactoryResetNodeIntent.swift @@ -0,0 +1,41 @@ +// +// FactoryResetNodeIntent.swift +// Meshtastic +// +// Created by Benjamin Faershtein on 8/25/24. +// + +import Foundation +import AppIntents + +struct FactoryResetNodeIntent: AppIntent { + static var title: LocalizedStringResource = "Factory Reset Node" + static var description: IntentDescription = "Perform a factory reset on the node you are connected to" + + func perform() async throws -> some IntentResult { + // Request user confirmation before performing the factory reset + try await requestConfirmation(result: .result(dialog: "Are you sure you want to factory reset the node?"),confirmationActionName: ConfirmationActionName + .custom(acceptLabel: "Factory Reset", acceptAlternatives: [], denyLabel: "Cancel", denyAlternatives: [], destructive: true)) + + // Ensure the node is connected + if !BLEManager.shared.isConnected { + throw AppIntentErrors.AppIntentError.notConnected + } + + // Safely unwrap the connected node information + if let connectedPeripheralNum = BLEManager.shared.connectedPeripheral?.num, + let connectedNode = getNodeInfo(id: connectedPeripheralNum, context: PersistenceController.shared.container.viewContext), + let fromUser = connectedNode.user, + let toUser = connectedNode.user { + + // Attempt to send a factory reset command, throw an error if it fails + if !BLEManager.shared.sendFactoryReset(fromUser: fromUser, toUser: toUser) { + throw AppIntentErrors.AppIntentError.message("Failed to perform factory reset") + } + } else { + throw AppIntentErrors.AppIntentError.message("Failed to retrieve connected node or required data") + } +// + return .result() + } +} diff --git a/Meshtastic/AppIntents/MessageChannelIntent.swift b/Meshtastic/AppIntents/MessageChannelIntent.swift index 53e202a6..4f104285 100644 --- a/Meshtastic/AppIntents/MessageChannelIntent.swift +++ b/Meshtastic/AppIntents/MessageChannelIntent.swift @@ -9,7 +9,7 @@ import Foundation import AppIntents struct MessageChannelIntent: AppIntent { - static var title: LocalizedStringResource = "Send a channel message" + static var title: LocalizedStringResource = "Send a Channel Message" static var description: IntentDescription = "Send a message to a certain meshtastic channel" diff --git a/Meshtastic/AppIntents/NodePositionIntent.swift b/Meshtastic/AppIntents/NodePositionIntent.swift index 496ca3e3..a173df0d 100644 --- a/Meshtastic/AppIntents/NodePositionIntent.swift +++ b/Meshtastic/AppIntents/NodePositionIntent.swift @@ -31,6 +31,7 @@ struct NodePositionIntent: AppIntent { } let nodeInfo = fetchedNode[0] + nodeInfo.latestEnvironmentMetrics?.batteryLevel if let latitude = nodeInfo.latestPosition?.coordinate.latitude, let longitude = nodeInfo.latestPosition?.coordinate.longitude { let nodeLocation = CLLocation(latitude: latitude, longitude: longitude) diff --git a/Meshtastic/AppIntents/RestartNodeIntent.swift b/Meshtastic/AppIntents/RestartNodeIntent.swift new file mode 100644 index 00000000..9bf8fd87 --- /dev/null +++ b/Meshtastic/AppIntents/RestartNodeIntent.swift @@ -0,0 +1,42 @@ +// +// RestartNodeIntent.swift +// Meshtastic +// +// Created by Benjamin Faershtein on 8/24/24. +// + +import Foundation +import AppIntents + +struct RestartNodeIntent: AppIntent { + static var title: LocalizedStringResource = "Restart Node" + + static var description: IntentDescription = "Restart to the node you are connected to" + + + func perform() async throws -> some IntentResult { + + try await requestConfirmation(result: .result(dialog: "Reboot Node?")) + + if !BLEManager.shared.isConnected { + throw AppIntentErrors.AppIntentError.notConnected + } + // Safely unwrap the connectedNode using if let + if let connectedPeripheralNum = BLEManager.shared.connectedPeripheral?.num, + let connectedNode = getNodeInfo(id: connectedPeripheralNum, context: PersistenceController.shared.container.viewContext), + let fromUser = connectedNode.user, + let toUser = connectedNode.user, + let adminIndex = connectedNode.myInfo?.adminIndex { + + // Attempt to send shutdown, throw an error if it fails + if !BLEManager.shared.sendReboot(fromUser: fromUser, toUser: toUser, adminIndex: adminIndex) { + throw AppIntentErrors.AppIntentError.message("Failed to restart") + } + } else { + throw AppIntentErrors.AppIntentError.message("Failed to retrieve connected node or required data") + } + + return .result() + } +} + diff --git a/Meshtastic/AppIntents/SendWaypointIntent.swift b/Meshtastic/AppIntents/SendWaypointIntent.swift index a94e8b7d..392d232a 100644 --- a/Meshtastic/AppIntents/SendWaypointIntent.swift +++ b/Meshtastic/AppIntents/SendWaypointIntent.swift @@ -12,7 +12,7 @@ import MeshtasticProtobufs struct SendWaypointIntent: AppIntent { - static var title = LocalizedStringResource("Send a waypoint") + static var title = LocalizedStringResource("Send a Waypoint") @Parameter(title: "Name", default: "Dropped Pin") var nameParameter: String? diff --git a/Meshtastic/AppIntents/ShortcutsProvider.swift b/Meshtastic/AppIntents/ShortcutsProvider.swift new file mode 100644 index 00000000..6a82f3f5 --- /dev/null +++ b/Meshtastic/AppIntents/ShortcutsProvider.swift @@ -0,0 +1,37 @@ +// +// ShortcutsProvider.swift +// Meshtastic +// +// Created by Benjamin Faershtein on 8/24/24. +// + +import Foundation +import AppIntents + +struct ShortcutsProvider: AppShortcutsProvider { + static var appShortcuts: [AppShortcut] { + AppShortcut(intent: ShutDownNodeIntent(), + phrases: ["Shut down node in \(.applicationName)", + "Turn off node in \(.applicationName)", + "Power down node in \(.applicationName)", + "Deactivate node in \(.applicationName)"], + shortTitle: "Shut Down Node", + systemImageName: "power") + + AppShortcut(intent: RestartNodeIntent(), + phrases: ["Restart node in \(.applicationName)", + "Reboot node in \(.applicationName)", + "Reset node in \(.applicationName)", + "Start node again in \(.applicationName)"], + shortTitle: "Restart Node", + systemImageName: "arrow.circlepath") + + AppShortcut(intent: MessageChannelIntent(), + phrases: ["Message channel in \(.applicationName)",], + shortTitle: "Message Channel", + systemImageName: "message") + } +} + + + diff --git a/Meshtastic/AppIntents/ShutDownNodeIntent.swift b/Meshtastic/AppIntents/ShutDownNodeIntent.swift new file mode 100644 index 00000000..ea57548d --- /dev/null +++ b/Meshtastic/AppIntents/ShutDownNodeIntent.swift @@ -0,0 +1,41 @@ +// +// ShutDownNodeIntent.swift +// Meshtastic +// +// Created by Benjamin Faershtein on 8/24/24. +// + +import Foundation +import AppIntents + +struct ShutDownNodeIntent: AppIntent { + static var title: LocalizedStringResource = "Shut Down Node" + + static var description: IntentDescription = "Send a shutdown to the node you are connected to" + + + func perform() async throws -> some IntentResult { + try await requestConfirmation(result: .result(dialog: "Shut Down Node?")) + + if !BLEManager.shared.isConnected { + throw AppIntentErrors.AppIntentError.notConnected + } + + // Safely unwrap the connectedNode using if let + if let connectedPeripheralNum = BLEManager.shared.connectedPeripheral?.num, + let connectedNode = getNodeInfo(id: connectedPeripheralNum, context: PersistenceController.shared.container.viewContext), + let fromUser = connectedNode.user, + let toUser = connectedNode.user, + let adminIndex = connectedNode.myInfo?.adminIndex { + + // Attempt to send shutdown, throw an error if it fails + if !BLEManager.shared.sendShutdown(fromUser: fromUser, toUser: toUser, adminIndex: adminIndex) { + throw AppIntentErrors.AppIntentError.message("Failed to shut down") + } + } else { + throw AppIntentErrors.AppIntentError.message("Failed to retrieve connected node or required data") + } + + return .result() + } +} From d3a7de2f764f90b3b3f4c0681ffdbee26b907fa4 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 25 Aug 2024 23:02:43 -0700 Subject: [PATCH 09/42] Seperate apps settings and data clearing, update logic for online nodes text --- Localizable.xcstrings | 24 ++++- .../CoreData/NodeInfoEntityExtension.swift | 6 +- Meshtastic/Views/Bluetooth/Connect.swift | 92 ++++++++++++++----- .../Views/Messages/ChannelMessageList.swift | 3 - Meshtastic/Views/Settings/AppSettings.swift | 4 +- Meshtastic/Views/Settings/Channels.swift | 9 +- .../Views/Settings/Channels/ChannelForm.swift | 24 ++++- .../Settings/Config/Module/MQTTConfig.swift | 15 +-- Widgets/WidgetsLiveActivity.swift | 42 ++++++--- 9 files changed, 159 insertions(+), 60 deletions(-) diff --git a/Localizable.xcstrings b/Localizable.xcstrings index 9989f2b7..e6c29271 100644 --- a/Localizable.xcstrings +++ b/Localizable.xcstrings @@ -140,6 +140,16 @@ }, "%@%%" : { + }, + "%@%% %@%%" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "%1$@%% %2$@%%" + } + } + } }, "%@Β°F" : { @@ -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" : { @@ -16072,6 +16085,12 @@ }, "Override automatic OLED screen detection." : { + }, + "Packets Received: %d" : { + + }, + "Packets Sent: %d" : { + }, "password" : { "localizations" : { @@ -17387,6 +17406,9 @@ }, "Requires that there be an accelerometer on your device." : { + }, + "Reset App Settings" : { + }, "Reset NodeDB" : { @@ -22679,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." : { diff --git a/Meshtastic/Extensions/CoreData/NodeInfoEntityExtension.swift b/Meshtastic/Extensions/CoreData/NodeInfoEntityExtension.swift index ba8e38fa..2effa9ae 100644 --- a/Meshtastic/Extensions/CoreData/NodeInfoEntityExtension.swift +++ b/Meshtastic/Extensions/CoreData/NodeInfoEntityExtension.swift @@ -22,9 +22,9 @@ 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 latestLocalStats: TelemetryEntity? { +// return self.telemetries?.filtered(using: NSPredicate(format: "metricsType == 6")).lastObject as? TelemetryEntity +// } var hasPositions: Bool { return positions?.count ?? 0 > 0 diff --git a/Meshtastic/Views/Bluetooth/Connect.swift b/Meshtastic/Views/Bluetooth/Connect.swift index 58d8757b..a7c7d5b8 100644 --- a/Meshtastic/Views/Bluetooth/Connect.swift +++ b/Meshtastic/Views/Bluetooth/Connect.swift @@ -52,38 +52,80 @@ struct Connect: View { if #available(iOS 17.0, macOS 14.0, *) { TipView(BluetoothConnectionTip(), arrowEdge: .bottom) } - HStack { - VStack(alignment: .center) { - CircleText(text: node?.user?.shortName ?? "?", color: Color(UIColor(hex: UInt32(node?.num ?? 0))), circleSize: 90) - } - .padding(.trailing) - VStack(alignment: .leading) { - if node != nil { - Text(connectedPeripheral.longName).font(.title2) + VStack(alignment: .leading) { + HStack { + VStack(alignment: .center) { + CircleText(text: node?.user?.shortName ?? "?", color: Color(UIColor(hex: UInt32(node?.num ?? 0))), circleSize: 90) + .padding(.trailing, 5) + if node?.latestDeviceMetrics != nil { + BatteryCompact(batteryLevel: node?.latestDeviceMetrics?.batteryLevel ?? 0, font: .caption, iconFont: .callout, color: .accentColor) + .padding(.trailing, 5) + } } - Text("ble.name").font(.callout)+Text(": \(bleManager.connectedPeripheral?.peripheral.name ?? "unknown".localized)") - .font(.callout).foregroundColor(Color.gray) - if node != nil { - Text("firmware.version").font(.callout)+Text(": \(node?.metadata?.firmwareVersion ?? "unknown".localized)") + .padding(.trailing) + VStack(alignment: .leading) { + if node != nil { + Text(connectedPeripheral.longName).font(.title2) + } + Text("ble.name").font(.callout)+Text(": \(bleManager.connectedPeripheral?.peripheral.name ?? "unknown".localized)") .font(.callout).foregroundColor(Color.gray) - } - if bleManager.isSubscribed { - Text("subscribed").font(.callout) - .foregroundColor(.green) - } else { - - HStack { - if #available(iOS 17.0, macOS 14.0, *) { - Image(systemName: "square.stack.3d.down.forward") - .symbolRenderingMode(.multicolor) - .symbolEffect(.variableColor.reversing.cumulative, options: .repeat(20).speed(3)) + if node != nil { + Text("firmware.version").font(.callout)+Text(": \(node?.metadata?.firmwareVersion ?? "unknown".localized)") + .font(.callout).foregroundColor(Color.gray) + } + if bleManager.isSubscribed { + Text("subscribed").font(.callout) + .foregroundColor(.green) + } else { + HStack { + if #available(iOS 17.0, macOS 14.0, *) { + Image(systemName: "square.stack.3d.down.forward") + .symbolRenderingMode(.multicolor) + .symbolEffect(.variableColor.reversing.cumulative, options: .repeat(20).speed(3)) + .foregroundColor(.orange) + } + Text("communicating").font(.callout) .foregroundColor(.orange) } - Text("communicating").font(.callout) - .foregroundColor(.orange) } } } + VStack { + let localStats = node?.telemetries?.filtered(using: NSPredicate(format: "metricsType == 6")).lastObject as? TelemetryEntity + if localStats != nil { + Divider() + if localStats?.numTotalNodes ?? 0 >= 100 { + Text("\(String(format: "Connected: %d nodes online", localStats?.numOnlineNodes ?? 0))") + .font(.callout) + .fontWeight(.medium) + .foregroundStyle(.secondary) + .fixedSize() + } else { + Text("\(String(format: "Connected: %d of %d nodes online", localStats?.numOnlineNodes ?? 0, localStats?.numTotalNodes ?? 0))") + .font(.callout) + .fontWeight(.medium) + .foregroundStyle(.secondary) + .fixedSize() + } + Text("\(String(format: "Ch. Util: %.2f", localStats?.channelUtilization ?? 0))% \(String(format: "Airtime: %.2f", localStats?.airUtilTx ?? 0))%") + .font(.caption) + .fontWeight(.medium) + .foregroundStyle(.secondary) + Text("Packets Sent: \(localStats?.numPacketsTx ?? 0)") + .font(.caption) + .fontWeight(.medium) + .foregroundStyle(.secondary) + Text("Packets Received: \(localStats?.numPacketsRx ?? 0)") + .font(.caption) + .fontWeight(.medium) + .foregroundStyle(.secondary) + Text("Dupe / Bad Packets: \(localStats?.numPacketsRxBad ?? 0)") + .font(.caption) + .fontWeight(.medium) + .foregroundStyle(.secondary) + .fixedSize() + } + } } .font(.caption) .foregroundColor(Color.gray) diff --git a/Meshtastic/Views/Messages/ChannelMessageList.swift b/Meshtastic/Views/Messages/ChannelMessageList.swift index 91a922c9..36cc5592 100644 --- a/Meshtastic/Views/Messages/ChannelMessageList.swift +++ b/Meshtastic/Views/Messages/ChannelMessageList.swift @@ -98,9 +98,6 @@ struct ChannelMessageList: View { Text("\(ackErrorVal?.display ?? "Empty Ack Error")").fixedSize(horizontal: false, vertical: true) .foregroundStyle(ackErrorVal?.color ?? Color.red) .font(.caption2) - } else { - let messageDate = message.timestamp - Text(" \(messageDate.formattedDate(format: MessageText.dateFormatString))").font(.caption2).foregroundColor(.gray) } } } diff --git a/Meshtastic/Views/Settings/AppSettings.swift b/Meshtastic/Views/Settings/AppSettings.swift index 02286655..0bb86bc1 100644 --- a/Meshtastic/Views/Settings/AppSettings.swift +++ b/Meshtastic/Views/Settings/AppSettings.swift @@ -76,9 +76,11 @@ struct AppSettings: View { } clearCoreDataDatabase(context: context, includeRoutes: true) context.refreshAllObjects() - UserDefaults.standard.reset() } } + Button("Reset App Settings", systemImage: "clipboard") { + UserDefaults.standard.reset() + } } if totalDownloadedTileSize != "0MB" { Section(header: Text("Map Tile Data")) { diff --git a/Meshtastic/Views/Settings/Channels.swift b/Meshtastic/Views/Settings/Channels.swift index ce48b658..3bb6ab38 100644 --- a/Meshtastic/Views/Settings/Channels.swift +++ b/Meshtastic/Views/Settings/Channels.swift @@ -98,7 +98,12 @@ struct Channels: View { preciseLocation = false positionsEnabled = false } else { - if positionPrecision == 32 { + if channelKey == "AQ==" { + preciseLocation = false + if positionPrecision < 10 || positionPrecision > 16 { + positionPrecision = 13 + } + } else if positionPrecision == 32 { preciseLocation = true positionsEnabled = true } else { @@ -250,7 +255,7 @@ struct Channels: View { channelKey = key positionsEnabled = false preciseLocation = false - positionPrecision = 0 + positionPrecision = 13 uplink = false downlink = false hasChanges = true diff --git a/Meshtastic/Views/Settings/Channels/ChannelForm.swift b/Meshtastic/Views/Settings/Channels/ChannelForm.swift index 780a19b4..28fe1116 100644 --- a/Meshtastic/Views/Settings/Channels/ChannelForm.swift +++ b/Meshtastic/Views/Settings/Channels/ChannelForm.swift @@ -157,11 +157,20 @@ struct ChannelForm: View { if !preciseLocation { VStack(alignment: .leading) { Label("Approximate Location", systemImage: "location.slash.circle.fill") - Slider(value: $positionPrecision, in: 10...19, step: 1) { - } minimumValueLabel: { - Image(systemName: "minus") - } maximumValueLabel: { - Image(systemName: "plus") + if channelKeySize == -1 { + Slider(value: $positionPrecision, in: 10...16, step: 1) { + } minimumValueLabel: { + Image(systemName: "minus") + } maximumValueLabel: { + Image(systemName: "plus") + } + } else { + Slider(value: $positionPrecision, in: 10...19, step: 1) { + } minimumValueLabel: { + Image(systemName: "minus") + } maximumValueLabel: { + Image(systemName: "plus") + } } Text(PositionPrecision(rawValue: Int(positionPrecision))?.description ?? "") .foregroundColor(.gray) @@ -199,6 +208,11 @@ struct ChannelForm: View { .onChange(of: channelKey) { _ in hasChanges = true } + .onChange(of: channelKeySize) { _ in + if channelKeySize == -1 { + preciseLocation = false + } + } .onChange(of: channelRole) { _ in hasChanges = true } diff --git a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift index d6176526..8ef411ea 100644 --- a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift @@ -24,7 +24,7 @@ struct MQTTConfig: View { @State var password = "" @State var encryptionEnabled = true @State var jsonEnabled = false - @State var tlsEnabled = true + @State var tlsEnabled = false @State var root = "msh" @State var selectedTopic = "" @State var mqttConnected: Bool = false @@ -234,7 +234,7 @@ struct MQTTConfig: View { .listRowSeparator(/*@START_MENU_TOKEN@*/.visible/*@END_MENU_TOKEN@*/) Toggle(isOn: $tlsEnabled) { Label("TLS Enabled", systemImage: "checkmark.shield.fill") - Text("Your MQTT Server must support TLS.") + Text("Your MQTT Server must support TLS. Not available via the public mqtt server.") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) } @@ -288,9 +288,6 @@ struct MQTTConfig: View { jsonEnabled = false } if newProxyToClientEnabled != node?.mqttConfig?.proxyToClientEnabled { hasChanges = true } - if newProxyToClientEnabled { - jsonEnabled = false - } } .onChange(of: address) { newAddress in if node != nil && node?.mqttConfig != nil { @@ -324,8 +321,12 @@ struct MQTTConfig: View { } if newJsonEnabled != node?.mqttConfig?.jsonEnabled { hasChanges = true } } - .onChange(of: tlsEnabled) { - if $0 != node?.mqttConfig?.tlsEnabled { hasChanges = true } + .onChange(of: tlsEnabled) { newTlsEnabled in + if address.lowercased() == "mqtt.meshtastic.org" { + tlsEnabled = false + } else { + if newTlsEnabled != node?.mqttConfig?.tlsEnabled { hasChanges = true } + } } .onChange(of: mqttConnected) { newMqttConnected in if newMqttConnected == false { diff --git a/Widgets/WidgetsLiveActivity.swift b/Widgets/WidgetsLiveActivity.swift index 0235f566..14d9762f 100644 --- a/Widgets/WidgetsLiveActivity.swift +++ b/Widgets/WidgetsLiveActivity.swift @@ -23,7 +23,7 @@ struct WidgetsLiveActivity: Widget { nodesOnline: context.state.nodesOnline, totalNodes: context.state.totalNodes, timerRange: context.state.timerRange) - .widgetURL(URL(string: "meshtastic:///node/\(context.attributes.name)")) + .widgetURL(URL(string: "meshtastic:///bluetooth")) } dynamicIsland: { context in DynamicIsland { @@ -38,10 +38,17 @@ struct WidgetsLiveActivity: Widget { .fixedSize() Spacer() } - Text("\(context.state.nodesOnline) nodes online") - .font(.caption) - .foregroundStyle(.secondary) - .fixedSize() + if context.state.nodesOnline >= 100 { + Text("100+ online") + .font(.caption) + .foregroundStyle(.secondary) + .fixedSize() + } else { + Text("\(context.state.nodesOnline) of \(context.state.totalNodes) online") + .font(.caption) + .foregroundStyle(.secondary) + .fixedSize() + } Text("\(String(format: "Ch. Util: %.2f", context.state.channelUtilization))%") .font(.caption) .foregroundStyle(.secondary) @@ -74,7 +81,7 @@ struct WidgetsLiveActivity: Widget { .font(.caption) .foregroundStyle(.secondary) .fixedSize() - Text("Dupe / Bad: \(context.state.badReceivedPackets)") + Text("Dupe / Bad \(context.state.badReceivedPackets)") .font(.caption) .foregroundStyle(.secondary) .fixedSize() @@ -108,7 +115,7 @@ struct WidgetsLiveActivity: Widget { .contentMargins(.trailing, 32, for: .expanded) .contentMargins([.leading, .top, .bottom], 6, for: .compactLeading) .contentMargins(.all, 6, for: .minimal) - .widgetURL(URL(string: "meshtastic:///node/\(context.attributes.name)")) + .widgetURL(URL(string: "meshtastic:///bluetooth")) } } } @@ -214,12 +221,21 @@ struct NodeInfoView: View { .foregroundStyle(.secondary) .opacity(isLuminanceReduced ? 0.8 : 1.0) .fixedSize() - Text("\(String(format: "Connected: %d nodes online", nodesOnline, totalNodes))") - .font(.caption) - .fontWeight(.medium) - .foregroundStyle(.secondary) - .opacity(isLuminanceReduced ? 0.8 : 1.0) - .fixedSize() + if totalNodes >= 100 { + Text("\(String(format: "Connected: %d nodes online", nodesOnline))") + .font(.caption) + .fontWeight(.medium) + .foregroundStyle(.secondary) + .opacity(isLuminanceReduced ? 0.8 : 1.0) + .fixedSize() + } else { + Text("\(String(format: "Connected: %d of %d nodes online", nodesOnline, totalNodes))") + .font(.caption) + .fontWeight(.medium) + .foregroundStyle(.secondary) + .opacity(isLuminanceReduced ? 0.8 : 1.0) + .fixedSize() + } let now = Date() Text("Last Heard: \(now.formatted())") .font(.caption) From 698eb7b5d08519ba11236739dc9daf96b2b83ad9 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Mon, 26 Aug 2024 22:31:03 -0700 Subject: [PATCH 10/42] Update app settings clear button --- Meshtastic/Views/Settings/AppSettings.swift | 5 +++- Meshtastic/Views/Settings/Channels.swift | 6 ++++- .../Views/Settings/Channels/ChannelForm.swift | 27 +++++++------------ 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/Meshtastic/Views/Settings/AppSettings.swift b/Meshtastic/Views/Settings/AppSettings.swift index 0bb86bc1..f641b77b 100644 --- a/Meshtastic/Views/Settings/AppSettings.swift +++ b/Meshtastic/Views/Settings/AppSettings.swift @@ -78,8 +78,11 @@ struct AppSettings: View { context.refreshAllObjects() } } - Button("Reset App Settings", systemImage: "clipboard") { + Button { UserDefaults.standard.reset() + } label: { + Label("Reset App Settings", systemImage: "arrow.counterclockwise.circle") + .foregroundColor(.red) } } if totalDownloadedTileSize != "0MB" { diff --git a/Meshtastic/Views/Settings/Channels.swift b/Meshtastic/Views/Settings/Channels.swift index 3bb6ab38..61cddcea 100644 --- a/Meshtastic/Views/Settings/Channels.swift +++ b/Meshtastic/Views/Settings/Channels.swift @@ -92,7 +92,11 @@ struct Channels: View { positionPrecision = 32 preciseLocation = true positionsEnabled = true - + if channelKey == "AQ==" { + positionPrecision = 13 + preciseLocation = false + positionsEnabled = true + } } else if !supportedVersion && channelRole == 2 { positionPrecision = 0 preciseLocation = false diff --git a/Meshtastic/Views/Settings/Channels/ChannelForm.swift b/Meshtastic/Views/Settings/Channels/ChannelForm.swift index 28fe1116..52e1f59e 100644 --- a/Meshtastic/Views/Settings/Channels/ChannelForm.swift +++ b/Meshtastic/Views/Settings/Channels/ChannelForm.swift @@ -157,20 +157,12 @@ struct ChannelForm: View { if !preciseLocation { VStack(alignment: .leading) { Label("Approximate Location", systemImage: "location.slash.circle.fill") - if channelKeySize == -1 { - Slider(value: $positionPrecision, in: 10...16, step: 1) { - } minimumValueLabel: { - Image(systemName: "minus") - } maximumValueLabel: { - Image(systemName: "plus") - } - } else { - Slider(value: $positionPrecision, in: 10...19, step: 1) { - } minimumValueLabel: { - Image(systemName: "minus") - } maximumValueLabel: { - Image(systemName: "plus") - } + + Slider(value: $positionPrecision, in: 10...16, step: 1) { + } minimumValueLabel: { + Image(systemName: "minus") + } maximumValueLabel: { + Image(systemName: "plus") } Text(PositionPrecision(rawValue: Int(positionPrecision))?.description ?? "") .foregroundColor(.gray) @@ -211,6 +203,7 @@ struct ChannelForm: View { .onChange(of: channelKeySize) { _ in if channelKeySize == -1 { preciseLocation = false + channelKey = "AQ==" } } .onChange(of: channelRole) { _ in @@ -220,7 +213,7 @@ struct ChannelForm: View { if loc == true { positionPrecision = 32 } else { - positionPrecision = 14 + positionPrecision = 13 } hasChanges = true } @@ -230,7 +223,7 @@ struct ChannelForm: View { .onChange(of: positionsEnabled) { pe in if pe { if positionPrecision == 0 { - positionPrecision = 32 + positionPrecision = 13 } } else { positionPrecision = 0 @@ -243,7 +236,7 @@ struct ChannelForm: View { .onChange(of: downlink) { _ in hasChanges = true } - .onAppear { + .onFirstAppear { let tempKey = Data(base64Encoded: channelKey) ?? Data() if tempKey.count == channelKeySize || channelKeySize == -1 { hasValidKey = true From f757909aa44dac83c741d1714836abf0d8316a22 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Mon, 26 Aug 2024 22:53:12 -0700 Subject: [PATCH 11/42] remove local stats call --- Meshtastic/Extensions/CoreData/NodeInfoEntityExtension.swift | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Meshtastic/Extensions/CoreData/NodeInfoEntityExtension.swift b/Meshtastic/Extensions/CoreData/NodeInfoEntityExtension.swift index 2effa9ae..c1bd5bec 100644 --- a/Meshtastic/Extensions/CoreData/NodeInfoEntityExtension.swift +++ b/Meshtastic/Extensions/CoreData/NodeInfoEntityExtension.swift @@ -22,10 +22,6 @@ 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 } From 3df98d4345991b9e9b29ac9ebe079cc1813f268a Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Mon, 26 Aug 2024 23:46:06 -0700 Subject: [PATCH 12/42] Linting errors --- Meshtastic/AppIntents/ShortcutsProvider.swift | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/Meshtastic/AppIntents/ShortcutsProvider.swift b/Meshtastic/AppIntents/ShortcutsProvider.swift index 6a82f3f5..e0f96ec3 100644 --- a/Meshtastic/AppIntents/ShortcutsProvider.swift +++ b/Meshtastic/AppIntents/ShortcutsProvider.swift @@ -25,13 +25,10 @@ struct ShortcutsProvider: AppShortcutsProvider { "Start node again in \(.applicationName)"], shortTitle: "Restart Node", systemImageName: "arrow.circlepath") - + AppShortcut(intent: MessageChannelIntent(), - phrases: ["Message channel in \(.applicationName)",], - shortTitle: "Message Channel", - systemImageName: "message") + phrases: ["Message channel in \(.applicationName)"], + shortTitle: "Message Channel", + systemImageName: "message") } } - - - From b188e3c18f91b8ca69deccdce8dee16707f2bfa2 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 27 Aug 2024 20:44:17 -0700 Subject: [PATCH 13/42] update channel form --- Meshtastic/Views/Settings/Channels/ChannelForm.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Meshtastic/Views/Settings/Channels/ChannelForm.swift b/Meshtastic/Views/Settings/Channels/ChannelForm.swift index 52e1f59e..d44d959e 100644 --- a/Meshtastic/Views/Settings/Channels/ChannelForm.swift +++ b/Meshtastic/Views/Settings/Channels/ChannelForm.swift @@ -211,6 +211,11 @@ struct ChannelForm: View { } .onChange(of: preciseLocation) { loc in if loc == true { + if channelKey == "AQ==" { + preciseLocation = false + } else { + positionPrecision = 32 + } positionPrecision = 32 } else { positionPrecision = 13 From eec7f593c502a18a3d9db9aeb5ce471f6edeba78 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 27 Aug 2024 21:17:35 -0700 Subject: [PATCH 14/42] Clean up labels and siri phrases --- Localizable.xcstrings | 15 +++++------ .../AppIntents/FactoryResetNodeIntent.swift | 2 +- .../AppIntents/MessageChannelIntent.swift | 2 +- Meshtastic/AppIntents/RestartNodeIntent.swift | 9 +++---- Meshtastic/AppIntents/ShortcutsProvider.swift | 26 ++++++++++--------- .../AppIntents/ShutDownNodeIntent.swift | 6 ++--- 6 files changed, 27 insertions(+), 33 deletions(-) diff --git a/Localizable.xcstrings b/Localizable.xcstrings index 3ae57f93..cf5fc247 100644 --- a/Localizable.xcstrings +++ b/Localizable.xcstrings @@ -7225,9 +7225,6 @@ }, "Factory Reset" : { - }, - "Factory Reset Node" : { - }, "Factory reset your device and app? " : { @@ -8167,6 +8164,9 @@ } } } + }, + "Group Message" : { + }, "Gusts %@" : { @@ -14582,9 +14582,6 @@ }, "Message" : { - }, - "Message Channel" : { - }, "Message content exceeds 228 bytes." : { @@ -17428,7 +17425,7 @@ "Reset NodeDB" : { }, - "Restart Node" : { + "Restart" : { }, "Restart to the node you are connected to" : { @@ -19175,7 +19172,7 @@ "Send" : { }, - "Send a Channel Message" : { + "Send a Group Message" : { }, "Send a message to a certain meshtastic channel" : { @@ -19924,7 +19921,7 @@ "Show Weather" : { }, - "Shut Down Node" : { + "Shut Down" : { }, "Shut Down Node?" : { diff --git a/Meshtastic/AppIntents/FactoryResetNodeIntent.swift b/Meshtastic/AppIntents/FactoryResetNodeIntent.swift index f83a507b..7ff2bd92 100644 --- a/Meshtastic/AppIntents/FactoryResetNodeIntent.swift +++ b/Meshtastic/AppIntents/FactoryResetNodeIntent.swift @@ -9,7 +9,7 @@ import Foundation import AppIntents struct FactoryResetNodeIntent: AppIntent { - static var title: LocalizedStringResource = "Factory Reset Node" + static var title: LocalizedStringResource = "Factory Reset" static var description: IntentDescription = "Perform a factory reset on the node you are connected to" func perform() async throws -> some IntentResult { diff --git a/Meshtastic/AppIntents/MessageChannelIntent.swift b/Meshtastic/AppIntents/MessageChannelIntent.swift index 4f104285..29d0a6c2 100644 --- a/Meshtastic/AppIntents/MessageChannelIntent.swift +++ b/Meshtastic/AppIntents/MessageChannelIntent.swift @@ -9,7 +9,7 @@ import Foundation import AppIntents struct MessageChannelIntent: AppIntent { - static var title: LocalizedStringResource = "Send a Channel Message" + static var title: LocalizedStringResource = "Send a Group Message" static var description: IntentDescription = "Send a message to a certain meshtastic channel" diff --git a/Meshtastic/AppIntents/RestartNodeIntent.swift b/Meshtastic/AppIntents/RestartNodeIntent.swift index 9bf8fd87..7ae8095a 100644 --- a/Meshtastic/AppIntents/RestartNodeIntent.swift +++ b/Meshtastic/AppIntents/RestartNodeIntent.swift @@ -9,13 +9,12 @@ import Foundation import AppIntents struct RestartNodeIntent: AppIntent { - static var title: LocalizedStringResource = "Restart Node" + static var title: LocalizedStringResource = "Restart" static var description: IntentDescription = "Restart to the node you are connected to" - func perform() async throws -> some IntentResult { - + try await requestConfirmation(result: .result(dialog: "Reboot Node?")) if !BLEManager.shared.isConnected { @@ -27,7 +26,7 @@ struct RestartNodeIntent: AppIntent { let fromUser = connectedNode.user, let toUser = connectedNode.user, let adminIndex = connectedNode.myInfo?.adminIndex { - + // Attempt to send shutdown, throw an error if it fails if !BLEManager.shared.sendReboot(fromUser: fromUser, toUser: toUser, adminIndex: adminIndex) { throw AppIntentErrors.AppIntentError.message("Failed to restart") @@ -35,8 +34,6 @@ struct RestartNodeIntent: AppIntent { } else { throw AppIntentErrors.AppIntentError.message("Failed to retrieve connected node or required data") } - return .result() } } - diff --git a/Meshtastic/AppIntents/ShortcutsProvider.swift b/Meshtastic/AppIntents/ShortcutsProvider.swift index e0f96ec3..b21c7e7d 100644 --- a/Meshtastic/AppIntents/ShortcutsProvider.swift +++ b/Meshtastic/AppIntents/ShortcutsProvider.swift @@ -11,24 +11,26 @@ import AppIntents struct ShortcutsProvider: AppShortcutsProvider { static var appShortcuts: [AppShortcut] { AppShortcut(intent: ShutDownNodeIntent(), - phrases: ["Shut down node in \(.applicationName)", - "Turn off node in \(.applicationName)", - "Power down node in \(.applicationName)", - "Deactivate node in \(.applicationName)"], - shortTitle: "Shut Down Node", + phrases: ["Shut down \(.applicationName) node", + "Shut down my \(.applicationName) node", + "Turn off \(.applicationName) node", + "Power down \(.applicationName) node", + "Deactivate \(.applicationName) node"], + shortTitle: "Shut Down", systemImageName: "power") AppShortcut(intent: RestartNodeIntent(), - phrases: ["Restart node in \(.applicationName)", - "Reboot node in \(.applicationName)", - "Reset node in \(.applicationName)", - "Start node again in \(.applicationName)"], - shortTitle: "Restart Node", + phrases: ["Restart \(.applicationName) node", + "Restart my \(.applicationName) node", + "Reboot \(.applicationName) node", + "Reboot my \(.applicationName) node"], + shortTitle: "Restart", systemImageName: "arrow.circlepath") AppShortcut(intent: MessageChannelIntent(), - phrases: ["Message channel in \(.applicationName)"], - shortTitle: "Message Channel", + phrases: ["Message a \(.applicationName) channel", + "Send a \(.applicationName) group message"], + shortTitle: "Group Message", systemImageName: "message") } } diff --git a/Meshtastic/AppIntents/ShutDownNodeIntent.swift b/Meshtastic/AppIntents/ShutDownNodeIntent.swift index ea57548d..dcb43f3c 100644 --- a/Meshtastic/AppIntents/ShutDownNodeIntent.swift +++ b/Meshtastic/AppIntents/ShutDownNodeIntent.swift @@ -9,10 +9,9 @@ import Foundation import AppIntents struct ShutDownNodeIntent: AppIntent { - static var title: LocalizedStringResource = "Shut Down Node" + static var title: LocalizedStringResource = "Shut Down" static var description: IntentDescription = "Send a shutdown to the node you are connected to" - func perform() async throws -> some IntentResult { try await requestConfirmation(result: .result(dialog: "Shut Down Node?")) @@ -27,7 +26,7 @@ struct ShutDownNodeIntent: AppIntent { let fromUser = connectedNode.user, let toUser = connectedNode.user, let adminIndex = connectedNode.myInfo?.adminIndex { - + // Attempt to send shutdown, throw an error if it fails if !BLEManager.shared.sendShutdown(fromUser: fromUser, toUser: toUser, adminIndex: adminIndex) { throw AppIntentErrors.AppIntentError.message("Failed to shut down") @@ -35,7 +34,6 @@ struct ShutDownNodeIntent: AppIntent { } else { throw AppIntentErrors.AppIntentError.message("Failed to retrieve connected node or required data") } - return .result() } } From 620e329521a6b9059a8b0f603cc7d4a5c835f26a Mon Sep 17 00:00:00 2001 From: Blake McAnally Date: Wed, 28 Aug 2024 07:02:55 -0500 Subject: [PATCH 15/42] Refactor router state to more closely match tab bar behavior --- Meshtastic/Router/NavigationState.swift | 47 +------ Meshtastic/Router/Router.swift | 30 +++-- Meshtastic/Views/ContentView.swift | 9 +- Meshtastic/Views/Messages/Messages.swift | 34 ++--- Meshtastic/Views/Nodes/MeshMap.swift | 2 +- Meshtastic/Views/Nodes/NodeList.swift | 7 +- Meshtastic/Views/Settings/Settings.swift | 7 +- MeshtasticTests/RouterTests.swift | 151 ++++++++++++++++++----- 8 files changed, 158 insertions(+), 129 deletions(-) diff --git a/Meshtastic/Router/NavigationState.swift b/Meshtastic/Router/NavigationState.swift index 0ff61108..0a85f4ab 100644 --- a/Meshtastic/Router/NavigationState.swift +++ b/Meshtastic/Router/NavigationState.swift @@ -55,17 +55,7 @@ enum SettingsNavigationState: String { case firmwareUpdates } -enum NavigationState: Hashable { - case messages(MessagesNavigationState? = nil) - case bluetooth - case nodes(selectedNodeNum: Int64? = nil) - case map(MapNavigationState? = nil) - case settings(SettingsNavigationState? = nil) -} - -// MARK: Tab Bar - -extension NavigationState { +struct NavigationState: Hashable { enum Tab: String, Hashable { case messages case bluetooth @@ -74,34 +64,9 @@ extension NavigationState { case settings } - var tab: Tab { - get { - switch self { - case .messages: - .messages - case .bluetooth: - .bluetooth - case .nodes: - .nodes - case .map: - .map - case .settings: - .settings - } - } - set { - self = switch newValue { - case .messages: - .messages() - case .bluetooth: - .bluetooth - case .nodes: - .nodes() - case .map: - .map() - case .settings: - .settings() - } - } - } + var selectedTab: Tab = .bluetooth + var messages: MessagesNavigationState? + var nodeListSelectedNodeNum: Int64? + var map: MapNavigationState? + var settings: SettingsNavigationState? } diff --git a/Meshtastic/Router/Router.swift b/Meshtastic/Router/Router.swift index 51803ed4..718c71b1 100644 --- a/Meshtastic/Router/Router.swift +++ b/Meshtastic/Router/Router.swift @@ -12,7 +12,9 @@ class Router: ObservableObject { private var cancellables: Set = [] init( - navigationState: NavigationState = .bluetooth + navigationState: NavigationState = NavigationState( + selectedTab: .bluetooth + ) ) { self.navigationState = navigationState @@ -21,10 +23,6 @@ class Router: ObservableObject { }.store(in: &cancellables) } - func route(to destination: NavigationState) { - navigationState = destination - } - func route(url: URL) { guard url.scheme == "meshtastic" else { Logger.services.error("πŸ›£ Received routing URL \(url, privacy: .public) with invalid scheme. Ignoring route.") @@ -38,7 +36,7 @@ class Router: ObservableObject { if components.path == "/messages" { routeMessages(components) } else if components.path == "/bluetooth" { - route(to: .bluetooth) + navigationState.selectedTab = .bluetooth } else if components.path == "/nodes" { routeNodes(components) } else if components.path == "/map" { @@ -75,7 +73,8 @@ class Router: ObservableObject { } else { nil } - route(to: .messages(state)) + navigationState.selectedTab = .messages + navigationState.messages = state } private func routeNodes(_ components: URLComponents) { @@ -83,7 +82,9 @@ class Router: ObservableObject { .first(where: { $0.name == "nodenum" })? .value .flatMap(Int64.init) - route(to: .nodes(selectedNodeNum: nodeId)) + + navigationState.selectedTab = .nodes + navigationState.nodeListSelectedNodeNum = nodeId } private func routeMap(_ components: URLComponents) { @@ -95,12 +96,14 @@ class Router: ObservableObject { .first(where: { $0.name == "waypointId" })? .value .flatMap(Int64.init) - if let nodeId { - route(to: .map(.selectedNode(nodeId))) + + navigationState.selectedTab = .map + navigationState.map = if let nodeId { + .selectedNode(nodeId) } else if let waypointId { - route(to: .map(.waypoint(waypointId))) + .waypoint(waypointId) } else { - route(to: .map()) + nil } } @@ -112,6 +115,7 @@ class Router: ObservableObject { .flatMap(String.init) .flatMap(SettingsNavigationState.init(rawValue:)) - route(to: .settings(settingFromPath)) + navigationState.selectedTab = .settings + navigationState.settings = settingFromPath } } diff --git a/Meshtastic/Views/ContentView.swift b/Meshtastic/Views/ContentView.swift index e8384c35..23b25f0c 100644 --- a/Meshtastic/Views/ContentView.swift +++ b/Meshtastic/Views/ContentView.swift @@ -13,14 +13,7 @@ struct ContentView: View { var router: Router var body: some View { - TabView(selection: Binding( - get: { - appState.router.navigationState.tab - }, - set: { newValue in - appState.router.navigationState.tab = newValue - } - )) { + TabView(selection: $appState.router.navigationState.selectedTab) { Messages( router: appState.router, unreadChannelMessages: $appState.unreadChannelMessages, diff --git a/Meshtastic/Views/Messages/Messages.swift b/Meshtastic/Views/Messages/Messages.swift index 88a6f2a2..09b8d1af 100644 --- a/Meshtastic/Views/Messages/Messages.swift +++ b/Meshtastic/Views/Messages/Messages.swift @@ -26,21 +26,6 @@ struct Messages: View { @Binding var unreadDirectMessages: Int - // Aliases the navigation state for the NavigationSplitView sidebar selection - private var messagesSelection: Binding { - Binding( - get: { - guard case .messages(let state) = router.navigationState else { - return nil - } - return state - }, - set: { newValue in - router.navigationState = .messages(newValue) - } - ) - } - @State var node: NodeInfoEntity? @State private var userSelection: UserEntity? // Nothing selected by default. @State private var channelSelection: ChannelEntity? // Nothing selected by default. @@ -49,7 +34,7 @@ struct Messages: View { var body: some View { NavigationSplitView(columnVisibility: $columnVisibility) { - List(selection: messagesSelection) { + List(selection: $router.navigationState.messages) { NavigationLink(value: MessagesNavigationState.channels()) { Label { Text("channels") @@ -88,11 +73,12 @@ struct Messages: View { .navigationBarTitleDisplayMode(.large) .navigationBarItems(leading: MeshtasticLogo()) } content: { - if case .messages(.channels) = router.navigationState { + switch router.navigationState.messages { + case .channels(let channelId, let messageId): ChannelList(node: $node, channelSelection: $channelSelection) - } else if case .messages(.directMessages) = router.navigationState { + case .directMessages(let userNum, let messageId): UserList(node: $node, userSelection: $userSelection) - } else if case .messages(nil) = router.navigationState { + case nil: Text("Select a conversation type") } } detail: { @@ -100,9 +86,9 @@ struct Messages: View { ChannelMessageList(myInfo: myInfo, channel: channelSelection) } else if let userSelection { UserMessageList(user: userSelection) - } else if case .messages(.channels) = router.navigationState { + } else if case .channels = router.navigationState.messages { Text("Select a channel") - } else if case .messages(.directMessages) = router.navigationState { + } else if case .directMessages = router.navigationState.messages { Text("Select a conversation") } }.onChange(of: router.navigationState) { _ in @@ -116,11 +102,7 @@ struct Messages: View { node = getNodeInfo(id: nodeId, context: context) } - guard case .messages(let state) = router.navigationState else { - return - } - - guard let state else { + guard let state = router.navigationState.messages else { channelSelection = nil userSelection = nil return diff --git a/Meshtastic/Views/Nodes/MeshMap.swift b/Meshtastic/Views/Nodes/MeshMap.swift index f471caaf..595abb15 100644 --- a/Meshtastic/Views/Nodes/MeshMap.swift +++ b/Meshtastic/Views/Nodes/MeshMap.swift @@ -123,7 +123,7 @@ struct MeshMap: View { MapSettingsForm(traffic: $showTraffic, pointsOfInterest: $showPointsOfInterest, mapLayer: $selectedMapLayer, meshMap: $isMeshMap) } .onChange(of: router.navigationState) { - guard case .map(let selectedNodeNum) = router.navigationState else { return } + guard case .map = router.navigationState.selectedTab else { return } // TODO: handle deep link for waypoints } .onChange(of: selectedMapLayer) { newMapLayer in diff --git a/Meshtastic/Views/Nodes/NodeList.swift b/Meshtastic/Views/Nodes/NodeList.swift index c0f16dd8..7df0283c 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -335,11 +335,8 @@ struct NodeList: View { } } .onChange(of: router.navigationState) { _ in - // Handle deep link routing - if case .nodes(let selected) = router.navigationState { - self.selectedNode = selected.flatMap { - getNodeInfo(id: $0, context: context) - } + if let selected = router.navigationState.nodeListSelectedNodeNum { + self.selectedNode = getNodeInfo(id: selected, context: context) } else { self.selectedNode = nil } diff --git a/Meshtastic/Views/Settings/Settings.swift b/Meshtastic/Views/Settings/Settings.swift index 814f3ab9..e88816ab 100644 --- a/Meshtastic/Views/Settings/Settings.swift +++ b/Meshtastic/Views/Settings/Settings.swift @@ -299,13 +299,10 @@ struct Settings: View { NavigationStack( path: Binding<[SettingsNavigationState]>( get: { - guard case .settings(let route) = router.navigationState, let setting = route else { - return [] - } - return [setting] + [router.navigationState.settings].compactMap { $0 } }, set: { newPath in - router.navigationState = .settings(newPath.first) + router.navigationState.settings = newPath.first } ) ) { diff --git a/MeshtasticTests/RouterTests.swift b/MeshtasticTests/RouterTests.swift index 81b07276..a3ecee6d 100644 --- a/MeshtasticTests/RouterTests.swift +++ b/MeshtasticTests/RouterTests.swift @@ -5,53 +5,144 @@ import XCTest final class RouterTests: XCTestCase { - func testInitialState() throws { - XCTAssertEqual(Router().navigationState, .bluetooth) + func testInitialState() async throws { + let router = await Router() + let tab = await router.navigationState.selectedTab + XCTAssertEqual(tab, .bluetooth) } - func testRouteTo() throws { - let router = Router(navigationState: .bluetooth) - router.route(to: .settings(.about)) - XCTAssertEqual(router.navigationState, .settings(.about)) + func testRouteMessages() async throws { + try await assertRoute( + router: Router(), + "meshtastic:///messages", + NavigationState(selectedTab: .messages) + ) } - func testRouteURL() throws { - // Messages - try assertRoute("meshtastic:///messages", .messages()) - try assertRoute( + func testRouteMessagesWithChannelIdAndMessageId() async throws { + try await assertRoute( + router: Router(), "meshtastic:///messages?channelId=0&messageId=1122334455", - .messages(.channels(channelId: 0, messageId: 1122334455)) + NavigationState( + selectedTab: .messages, + messages: .channels( + channelId: 0, + messageId: 1122334455 + ) + ) ) - try assertRoute( + } + + func testRouteMessagesWithUserNumAndMessageId() async throws { + try await assertRoute( + router: Router(), "meshtastic:///messages?userNum=123456789&messageId=9876543210", - .messages(.directMessages(userNum: 123456789, messageId: 9876543210)) + NavigationState( + selectedTab: .messages, + messages: .directMessages( + userNum: 123456789, + messageId: 9876543210 + ) + ) ) + } - // Bluetooth - try assertRoute("meshtastic:///bluetooth", .bluetooth) + func testRouteBluetooth() async throws { + try await assertRoute( + router: Router(), + "meshtastic:///bluetooth", + NavigationState(selectedTab: .bluetooth) + ) + } - // Nodes - try assertRoute("meshtastic:///nodes", .nodes()) - try assertRoute("meshtastic:///nodes?nodenum=1234567890", .nodes(selectedNodeNum: 1234567890)) + func testRouteNodes() async throws { + try await assertRoute( + router: Router(), + "meshtastic:///nodes", + NavigationState(selectedTab: .nodes) + ) + } - // Map - try assertRoute("meshtastic:///map", .map()) - try assertRoute("meshtastic:///map?waypointId=123456", .map(.waypoint(123456))) - try assertRoute("meshtastic:///map?nodenum=1234567890", .map(.selectedNode(1234567890))) + func testRouteNodesWithNodeNum() async throws { + try await assertRoute( + router: Router(), + "meshtastic:///nodes?nodenum=1234567890", + NavigationState( + selectedTab: .nodes, + nodeListSelectedNodeNum: 1234567890 + ) + ) + } - // Settings - try assertRoute("meshtastic:///settings", .settings()) - try assertRoute("meshtastic:///settings/about", .settings(.about)) - try assertRoute("meshtastic:///settings/invalidSetting", .settings()) + func testRouteMap() async throws { + try await assertRoute( + router: Router(), + "meshtastic:///map", + NavigationState(selectedTab: .map) + ) + } + + func testRouteMapWithWaypointId() async throws { + try await assertRoute( + router: Router(), + "meshtastic:///map?waypointId=123456", + NavigationState( + selectedTab: .map, + map: .waypoint(123456) + ) + ) + } + + func testRouteMapWithNodeNum() async throws { + try await assertRoute( + router: Router(), + "meshtastic:///map?nodenum=1234567890", + NavigationState( + selectedTab: .map, + map: .selectedNode(1234567890) + ) + ) + } + + func testRouteSettings() async throws { + try await assertRoute( + router: Router(), + "meshtastic:///settings", + NavigationState( + selectedTab: .settings + ) + ) + } + + func testRouteSettingsAbout() async throws { + try await assertRoute( + router: Router(), + "meshtastic:///settings/about", + NavigationState( + selectedTab: .settings, + settings: .about + ) + ) + } + + func testRouteSettingsInvalidSetting() async throws { + try await assertRoute( + router: Router(), + "meshtastic:///settings/invalidSetting", + NavigationState( + selectedTab: .settings + ) + ) } private func assertRoute( - router: Router = Router(), + router: Router, _ urlString: String, _ destination: NavigationState - ) throws { + ) async throws { let url = try XCTUnwrap(URL(string: urlString)) - router.route(url: url) - XCTAssertEqual(router.navigationState, destination) + await router.route(url: url) + let state = await router.navigationState + XCTAssertEqual(state, destination) } } From a8149e0c660428e227351ead416722b4ada6dc2d Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Wed, 28 Aug 2024 08:36:45 -0700 Subject: [PATCH 16/42] Only send battery nofifications from the local node --- Meshtastic/Helpers/MeshPackets.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index 12ed1f1b..f46600aa 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -757,7 +757,7 @@ func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManage // Connected Device Metrics // ------------------------ // Low Battery notification - if connectedNode != Int64(packet.from) { + if connectedNode == Int64(packet.from) { if UserDefaults.lowBatteryNotifications && telemetry.batteryLevel > 0 && telemetry.batteryLevel < 4 { let manager = LocalNotificationManager() manager.notifications = [ From 583c992cb8b2f3376213cb28fec7ff305577787a Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Wed, 28 Aug 2024 11:32:22 -0700 Subject: [PATCH 17/42] Remove publisher change event that runs way too often --- Meshtastic/Views/Messages/UserList.swift | 3 --- Meshtastic/Views/Nodes/NodeList.swift | 5 ----- 2 files changed, 8 deletions(-) diff --git a/Meshtastic/Views/Messages/UserList.swift b/Meshtastic/Views/Messages/UserList.swift index 7ba2205c..3a2c4a47 100644 --- a/Meshtastic/Views/Messages/UserList.swift +++ b/Meshtastic/Views/Messages/UserList.swift @@ -227,9 +227,6 @@ struct UserList: View { .onChange(of: maxDistance) { _ in searchUserList() } - .onReceive(users.publisher) { _ in - searchUserList() - } .onAppear { searchUserList() } diff --git a/Meshtastic/Views/Nodes/NodeList.swift b/Meshtastic/Views/Nodes/NodeList.swift index c0f16dd8..c5c92418 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -344,11 +344,6 @@ struct NodeList: View { self.selectedNode = nil } } - .onReceive(nodes.publisher) { _ in - Task { - await searchNodeList() - } - } .onAppear { Task { await searchNodeList() From 01b354e4a461f7ede2fc30e59716dcca1052b5d6 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Thu, 29 Aug 2024 08:50:31 -0700 Subject: [PATCH 18/42] Ack error font --- Meshtastic/Views/Messages/UserMessageList.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Meshtastic/Views/Messages/UserMessageList.swift b/Meshtastic/Views/Messages/UserMessageList.swift index a53f788d..cb483745 100644 --- a/Meshtastic/Views/Messages/UserMessageList.swift +++ b/Meshtastic/Views/Messages/UserMessageList.swift @@ -84,6 +84,7 @@ struct UserMessageList: View { } else if currentUser && message.ackError > 0 { Text("\(ackErrorVal?.display ?? "Empty Ack Error")").fixedSize(horizontal: false, vertical: true) .foregroundStyle(ackErrorVal?.color ?? Color.red) + .font(.caption2) } } } From bda868bd1a9feec3e509e143b90f5c03dcfc1b4b Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Fri, 30 Aug 2024 08:57:36 -0700 Subject: [PATCH 19/42] set position source --- Meshtastic.xcodeproj/project.pbxproj | 8 ++++---- Meshtastic/Helpers/BLEManager.swift | 23 +++++++++++++++++++---- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 550bbc78..83fac71b 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -1686,7 +1686,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.5.2; + MARKETING_VERSION = 2.5.3; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; @@ -1721,7 +1721,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.5.2; + MARKETING_VERSION = 2.5.3; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; @@ -1753,7 +1753,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.5.2; + MARKETING_VERSION = 2.5.3; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1786,7 +1786,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.5.2; + MARKETING_VERSION = 2.5.3; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 2d8726ed..045a3694 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -1153,7 +1153,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } @MainActor - public func getPositionFromPhoneGPS(destNum: Int64) -> Position? { + public func getPositionFromPhoneGPS(destNum: Int64, fixedPosition: Bool) -> Position? { var positionPacket = Position() if #available(iOS 17.0, macOS 14.0, *) { @@ -1172,7 +1172,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate positionPacket.timestamp = UInt32(timestamp.timeIntervalSince1970) positionPacket.altitude = Int32(lastLocation.altitude) positionPacket.satsInView = UInt32(LocationsHandler.satsInView) - let currentSpeed = lastLocation.speed if currentSpeed > 0 && (!currentSpeed.isNaN || !currentSpeed.isInfinite) { positionPacket.groundSpeed = UInt32(currentSpeed) @@ -1181,6 +1180,14 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate if (currentHeading > 0 && currentHeading <= 360) && (!currentHeading.isNaN || !currentHeading.isInfinite) { positionPacket.groundTrack = UInt32(currentHeading) } + /// Set location source for time + if !fixedPosition { + /// From GPS treat time as good + positionPacket.locationSource = Position.LocSource.locExternal + } else { + /// From GPS, but time can be old and have drifted + positionPacket.locationSource = Position.LocSource.locManual + } } else { @@ -1199,6 +1206,14 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate if (currentHeading > 0 && currentHeading <= 360) && (!currentHeading.isNaN || !currentHeading.isInfinite) { positionPacket.groundTrack = UInt32(currentHeading) } + /// Set location source for time + if !fixedPosition { + /// From GPS treat time as good + positionPacket.locationSource = Position.LocSource.locExternal + } else { + /// From GPS, but time can be old and have drifted + positionPacket.locationSource = Position.LocSource.locManual + } } return positionPacket } @@ -1206,7 +1221,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate @MainActor public func setFixedPosition(fromUser: UserEntity, channel: Int32) -> Bool { var adminPacket = AdminMessage() - guard let positionPacket = getPositionFromPhoneGPS(destNum: fromUser.num) else { + guard let positionPacket = getPositionFromPhoneGPS(destNum: fromUser.num, fixedPosition: true) else { return false } adminPacket.setFixedPosition = positionPacket @@ -1261,7 +1276,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate @MainActor public func sendPosition(channel: Int32, destNum: Int64, wantResponse: Bool) -> Bool { let fromNodeNum = connectedPeripheral.num - guard let positionPacket = getPositionFromPhoneGPS(destNum: destNum) else { + guard let positionPacket = getPositionFromPhoneGPS(destNum: destNum, fixedPosition: false) else { Logger.services.error("Unable to get position data from device GPS to send to node") return false } From ec2df295d17f89ace9a4e34520c9a8394d054267 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sat, 31 Aug 2024 13:54:40 -0700 Subject: [PATCH 20/42] set time source properly --- Meshtastic/Helpers/BLEManager.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 045a3694..08b53801 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -1329,7 +1329,8 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate var adminPacket = AdminMessage() adminPacket.setTimeOnly = UInt32(Date().timeIntervalSince1970) var meshPacket: MeshPacket = MeshPacket() - meshPacket.to = 0 + meshPacket.to = UInt32(self.connectedPeripheral.num) + meshPacket.from = UInt32(self.connectedPeripheral.num) meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Date: Sat, 31 Aug 2024 15:04:25 -0700 Subject: [PATCH 21/42] Adjust precision on channel form --- Meshtastic/Views/Settings/Channels/ChannelForm.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Meshtastic/Views/Settings/Channels/ChannelForm.swift b/Meshtastic/Views/Settings/Channels/ChannelForm.swift index d44d959e..d0a111e5 100644 --- a/Meshtastic/Views/Settings/Channels/ChannelForm.swift +++ b/Meshtastic/Views/Settings/Channels/ChannelForm.swift @@ -202,7 +202,9 @@ struct ChannelForm: View { } .onChange(of: channelKeySize) { _ in if channelKeySize == -1 { - preciseLocation = false + if channelRole == 0 { + preciseLocation = false + } channelKey = "AQ==" } } @@ -211,7 +213,7 @@ struct ChannelForm: View { } .onChange(of: preciseLocation) { loc in if loc == true { - if channelKey == "AQ==" { + if channelKey == "AQ==" && channelRole == 0 { preciseLocation = false } else { positionPrecision = 32 From bff990357390cbedf03284524e501e7aeed96124 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sat, 31 Aug 2024 15:35:26 -0700 Subject: [PATCH 22/42] Packets bad. GUVWAF has verified that these are bad packets, may have fixed a bug up stream in the process that was exposed by having the data available. --- Localizable.xcstrings | 6 +++--- Meshtastic/Views/Bluetooth/Connect.swift | 2 +- Widgets/WidgetsLiveActivity.swift | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Localizable.xcstrings b/Localizable.xcstrings index cf5fc247..411af16d 100644 --- a/Localizable.xcstrings +++ b/Localizable.xcstrings @@ -1428,6 +1428,9 @@ }, "Bad" : { + }, + "Bad Packets: %d" : { + }, "Bandwidth" : { @@ -6725,9 +6728,6 @@ }, "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" : { diff --git a/Meshtastic/Views/Bluetooth/Connect.swift b/Meshtastic/Views/Bluetooth/Connect.swift index a7c7d5b8..2a1eea84 100644 --- a/Meshtastic/Views/Bluetooth/Connect.swift +++ b/Meshtastic/Views/Bluetooth/Connect.swift @@ -119,7 +119,7 @@ struct Connect: View { .font(.caption) .fontWeight(.medium) .foregroundStyle(.secondary) - Text("Dupe / Bad Packets: \(localStats?.numPacketsRxBad ?? 0)") + Text("Bad Packets: \(localStats?.numPacketsRxBad ?? 0)") .font(.caption) .fontWeight(.medium) .foregroundStyle(.secondary) diff --git a/Widgets/WidgetsLiveActivity.swift b/Widgets/WidgetsLiveActivity.swift index 14d9762f..e6177129 100644 --- a/Widgets/WidgetsLiveActivity.swift +++ b/Widgets/WidgetsLiveActivity.swift @@ -81,7 +81,7 @@ struct WidgetsLiveActivity: Widget { .font(.caption) .foregroundStyle(.secondary) .fixedSize() - Text("Dupe / Bad \(context.state.badReceivedPackets)") + Text("Bad \(context.state.badReceivedPackets)") .font(.caption) .foregroundStyle(.secondary) .fixedSize() @@ -215,7 +215,7 @@ struct NodeInfoView: View { .foregroundStyle(.secondary) .opacity(isLuminanceReduced ? 0.8 : 1.0) .fixedSize() - Text("Dupe / Bad Packets: \(badReceivedPackets)") + Text("Bad Packets: \(badReceivedPackets)") .font(.caption) .fontWeight(.medium) .foregroundStyle(.secondary) From 10293c617ac6c173951221d9a4fb8bba06485525 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 1 Sep 2024 00:56:24 -0700 Subject: [PATCH 23/42] Display both keys in the case of a key mismatch --- Localizable.xcstrings | 6 ++++++ Meshtastic/Views/Nodes/Helpers/NodeDetail.swift | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/Localizable.xcstrings b/Localizable.xcstrings index 411af16d..7298c16e 100644 --- a/Localizable.xcstrings +++ b/Localizable.xcstrings @@ -15389,6 +15389,9 @@ }, "Never" : { + }, + "New Key%@" : { + }, "Newer firmware is available" : { @@ -16756,6 +16759,9 @@ }, "Public Key Mismatch" : { + }, + "Public Key%@" : { + }, "PWD" : { diff --git a/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift b/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift index 6724e805..f6e701a6 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift @@ -54,6 +54,12 @@ struct NodeDetail: View { Text("The public key does not match the recorded key. You may delete the node and let it exchange keys again, but this may indicate a more serious security problem. Contact the user through another trusted channel, to determine if the key change was due to a factory reset or other intentional action.") .font(.caption) .foregroundStyle(.red) + Text("Public Key\(user.publicKey?.base64EncodedString() ?? "Empty Key")") + .monospaced() + .allowsTightening(/*@START_MENU_TOKEN@*/true/*@END_MENU_TOKEN@*/) + Text("New Key\(user.newPublicKey?.base64EncodedString() ?? "Empty Key")") + .monospaced() + .allowsTightening(/*@START_MENU_TOKEN@*/true/*@END_MENU_TOKEN@*/) } } icon: { Image(systemName: "key.slash.fill") From 01e303d74b10b5d34fe6132054c3d1ae08710baa Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Mon, 2 Sep 2024 21:35:01 -0700 Subject: [PATCH 24/42] Add a button to drop a pin for a waypoints in maps --- Localizable.xcstrings | 3 +++ .../Nodes/Helpers/Map/WaypointForm.swift | 25 +++++++++++-------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/Localizable.xcstrings b/Localizable.xcstrings index 7298c16e..fc3ff448 100644 --- a/Localizable.xcstrings +++ b/Localizable.xcstrings @@ -6728,6 +6728,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." : { + }, + "Drop Pin in Maps" : { + }, "echo" : { "localizations" : { diff --git a/Meshtastic/Views/Nodes/Helpers/Map/WaypointForm.swift b/Meshtastic/Views/Nodes/Helpers/Map/WaypointForm.swift index 17573745..99b3b12c 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/WaypointForm.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/WaypointForm.swift @@ -269,10 +269,8 @@ struct WaypointForm: View { .fixedSize(horizontal: false, vertical: true) } icon: { Image(systemName: "doc.plaintext") - .symbolRenderingMode(.hierarchical) - .frame(width: 35) } - .padding(.bottom, 5) + .padding(.bottom) } /// Coordinate Label { @@ -280,11 +278,18 @@ struct WaypointForm: View { .textSelection(.enabled) .foregroundColor(.primary) } icon: { - Image(systemName: "mappin.and.ellipse") - .symbolRenderingMode(.hierarchical) - .frame(width: 35) + Image(systemName: "mappin.circle") } - .padding(.bottom, 5) + .padding(.bottom) + // Drop Maps Pin + Button(action: { + if let url = URL(string: "http://maps.apple.com/?ll=\(waypoint.coordinate.latitude),\(waypoint.coordinate.longitude)&q=\(waypoint.name ?? "Dropped Pin")") { + UIApplication.shared.open(url) + } + }) { + Label("Drop Pin in Maps", systemImage: "mappin.and.ellipse") + } + .padding(.bottom) /// Created Label { Text("Created: \(waypoint.created?.formatted() ?? "?")") @@ -292,9 +297,8 @@ struct WaypointForm: View { } icon: { Image(systemName: "clock.badge.checkmark") .symbolRenderingMode(.hierarchical) - .frame(width: 35) } - .padding(.bottom, 5) + .padding(.bottom) /// Updated if waypoint.lastUpdated != nil { Label { @@ -303,9 +307,8 @@ struct WaypointForm: View { } icon: { Image(systemName: "clock.arrow.circlepath") .symbolRenderingMode(.hierarchical) - .frame(width: 35) } - .padding(.bottom, 5) + .padding(.bottom) } /// Expires if waypoint.expire != nil { From 3934bdcbc6222a60d2f14956102ac12f133039cf Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Mon, 2 Sep 2024 22:23:56 -0700 Subject: [PATCH 25/42] Don't set keymatch to false if we get a pki failed error --- Meshtastic/Helpers/MeshPackets.swift | 5 ----- Meshtastic/Views/Nodes/Helpers/NodeDetail.swift | 3 +++ 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index f46600aa..efd80d72 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -632,11 +632,6 @@ func routingPacket (packet: MeshPacket, connectedNodeNum: Int64, context: NSMana } } fetchedMessage[0].ackError = Int32(routingMessage.errorReason.rawValue) - if routingError == RoutingError.pkiFailed { - fetchedMessage[0].toUser?.keyMatch = false - fetchedMessage[0].toUser?.newPublicKey = fetchedMessage[0].publicKey - } - if routingMessage.errorReason == Routing.Error.none { fetchedMessage[0].receivedACK = true diff --git a/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift b/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift index f6e701a6..9195d068 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift @@ -55,11 +55,14 @@ struct NodeDetail: View { .font(.caption) .foregroundStyle(.red) Text("Public Key\(user.publicKey?.base64EncodedString() ?? "Empty Key")") + .font(.caption2) .monospaced() .allowsTightening(/*@START_MENU_TOKEN@*/true/*@END_MENU_TOKEN@*/) Text("New Key\(user.newPublicKey?.base64EncodedString() ?? "Empty Key")") + .font(.caption2) .monospaced() .allowsTightening(/*@START_MENU_TOKEN@*/true/*@END_MENU_TOKEN@*/) + } } icon: { Image(systemName: "key.slash.fill") From a94fd470e5b6ed9dd55b507653d6658588bfb248 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Mon, 2 Sep 2024 22:42:51 -0700 Subject: [PATCH 26/42] Remove the keys again --- Localizable.xcstrings | 9 +++------ Meshtastic/Views/Nodes/Helpers/NodeDetail.swift | 15 +++------------ 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/Localizable.xcstrings b/Localizable.xcstrings index 7298c16e..d1bbd58b 100644 --- a/Localizable.xcstrings +++ b/Localizable.xcstrings @@ -15389,9 +15389,6 @@ }, "Never" : { - }, - "New Key%@" : { - }, "Newer firmware is available" : { @@ -16759,9 +16756,6 @@ }, "Public Key Mismatch" : { - }, - "Public Key%@" : { - }, "PWD" : { @@ -21128,6 +21122,9 @@ }, "The minimum distance change in meters to be considered for a smart position broadcast." : { + }, + "The most recent public key for this node does not match the previously recorded key. You can delete the node and let it exchange keys again, but this also may indicate a more serious security problem. Contact the user through another trusted channel to determine if the key change was due to a factory reset or other intentional action." : { + }, "The public key authorized to send admin messages to this node." : { diff --git a/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift b/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift index 9195d068..11defc79 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift @@ -51,18 +51,9 @@ struct NodeDetail: View { Text("Public Key Mismatch") .font(.title3) .foregroundStyle(.red) - Text("The public key does not match the recorded key. You may delete the node and let it exchange keys again, but this may indicate a more serious security problem. Contact the user through another trusted channel, to determine if the key change was due to a factory reset or other intentional action.") - .font(.caption) - .foregroundStyle(.red) - Text("Public Key\(user.publicKey?.base64EncodedString() ?? "Empty Key")") - .font(.caption2) - .monospaced() - .allowsTightening(/*@START_MENU_TOKEN@*/true/*@END_MENU_TOKEN@*/) - Text("New Key\(user.newPublicKey?.base64EncodedString() ?? "Empty Key")") - .font(.caption2) - .monospaced() - .allowsTightening(/*@START_MENU_TOKEN@*/true/*@END_MENU_TOKEN@*/) - + Text("The most recent public key for this node does not match the previously recorded key. You can delete the node and let it exchange keys again, but this also may indicate a more serious security problem. Contact the user through another trusted channel to determine if the key change was due to a factory reset or other intentional action.") + .foregroundStyle(.secondary) + .font(.callout) } } icon: { Image(systemName: "key.slash.fill") From 5fbb4ecec94942b9b1ee0fb22046f753d2fa9600 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 3 Sep 2024 07:12:15 -0700 Subject: [PATCH 27/42] Clean up waypoint form --- Localizable.xcstrings | 14 ++++------- .../Nodes/Helpers/Map/WaypointForm.swift | 23 ++++++++++++++----- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/Localizable.xcstrings b/Localizable.xcstrings index fc3ff448..b880a514 100644 --- a/Localizable.xcstrings +++ b/Localizable.xcstrings @@ -4911,15 +4911,8 @@ } } }, - "Coordinates: %@, %@" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "Coordinates: %1$@, %2$@" - } - } - } + "Coordinates:" : { + }, "copy" : { "localizations" : { @@ -10934,6 +10927,9 @@ }, "Location" : { + }, + "Location:" : { + }, "Location: %@" : { diff --git a/Meshtastic/Views/Nodes/Helpers/Map/WaypointForm.swift b/Meshtastic/Views/Nodes/Helpers/Map/WaypointForm.swift index 99b3b12c..35916eb6 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/WaypointForm.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/WaypointForm.swift @@ -28,6 +28,8 @@ struct WaypointForm: View { @State private var expire: Date = Date.now.addingTimeInterval(60 * 480) // 1 minute * 480 = 8 Hours @State private var locked: Bool = false @State private var lockedTo: Int64 = 0 + @State private var detents: Set = [.medium, .fraction(0.85)] + @State private var selectedDetent: PresentationDetent = .medium var body: some View { NavigationStack { @@ -39,9 +41,12 @@ struct WaypointForm: View { let distance = CLLocation(latitude: LocationHelper.currentLocation.latitude, longitude: LocationHelper.currentLocation.longitude).distance(from: CLLocation(latitude: waypoint.coordinate.latitude, longitude: waypoint.coordinate.longitude )) Section(header: Text("Coordinate") ) { HStack { - Text("Location: \(String(format: "%.5f", waypoint.coordinate.latitude) + "," + String(format: "%.5f", waypoint.coordinate.longitude))") + Text("Location:") + .foregroundColor(.secondary) + Text("\(String(format: "%.5f", waypoint.coordinate.latitude) + "," + String(format: "%.5f", waypoint.coordinate.longitude))") .textSelection(.enabled) - .foregroundColor(Color.gray) + .foregroundColor(.secondary) + .font(.caption) } HStack { if waypoint.coordinate.latitude != 0 && waypoint.coordinate.longitude != 0 { @@ -124,6 +129,7 @@ struct WaypointForm: View { .toggleStyle(SwitchToggleStyle(tint: .accentColor)) } } + .scrollDismissesKeyboard(.immediately) HStack { Button { /// Send a new or exiting waypoint @@ -239,7 +245,7 @@ struct WaypointForm: View { } else { VStack { HStack { - CircleText(text: String(UnicodeScalar(Int(waypoint.icon)) ?? "πŸ“"), color: Color.orange, circleSize: 65) + CircleText(text: String(UnicodeScalar(Int(waypoint.icon)) ?? "πŸ“"), color: Color.orange, circleSize: 50) Spacer() Text(waypoint.name ?? "?") .font(.largeTitle) @@ -250,6 +256,7 @@ struct WaypointForm: View { } else { Button { editMode = true + selectedDetent = .fraction(0.85) } label: { Image(systemName: "square.and.pencil" ) .font(.largeTitle) @@ -274,9 +281,12 @@ struct WaypointForm: View { } /// Coordinate Label { - Text("Coordinates: \(String(format: "%.6f", waypoint.coordinate.latitude)), \(String(format: "%.6f", waypoint.coordinate.longitude))") - .textSelection(.enabled) + Text("Coordinates:") .foregroundColor(.primary) + Text("\(String(format: "%.6f", waypoint.coordinate.latitude)), \(String(format: "%.6f", waypoint.coordinate.longitude))") + .textSelection(.enabled) + .foregroundColor(.secondary) + .font(.caption2) } icon: { Image(systemName: "mappin.circle") } @@ -381,7 +391,8 @@ struct WaypointForm: View { longitude = waypoint.coordinate.longitude } } - .presentationDetents([.fraction(0.75)]) + .presentationDetents(detents, selection: $selectedDetent) + .presentationBackgroundInteraction(.enabled(upThrough: .fraction(0.85))) .presentationDragIndicator(.visible) } } From 9d911a8d2c1360a3e3a25a70c8190df350721d64 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 3 Sep 2024 18:01:32 -0700 Subject: [PATCH 28/42] hook up session passkey to bluetooth config --- Localizable.xcstrings | 3 --- Meshtastic/Helpers/BLEManager.swift | 3 +++ Meshtastic/Helpers/MeshPackets.swift | 4 +++- Meshtastic/Views/Messages/UserList.swift | 2 ++ .../Settings/Config/BluetoothConfig.swift | 23 +++++++++---------- 5 files changed, 19 insertions(+), 16 deletions(-) diff --git a/Localizable.xcstrings b/Localizable.xcstrings index df568dc1..1c7ebdc7 100644 --- a/Localizable.xcstrings +++ b/Localizable.xcstrings @@ -5431,9 +5431,6 @@ }, "Device is managed by a mesh administrator." : { - }, - "Device Logging Enabled" : { - }, "Device Metrics" : { diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 08b53801..87551c4f 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -2564,6 +2564,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate var adminPacket = AdminMessage() adminPacket.getConfigRequest = AdminMessage.ConfigType.bluetoothConfig + if UserDefaults.enableAdministration { + adminPacket.sessionPasskey = toUser.userNode?.sessionPasskey ?? Data() + } var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index efd80d72..9f3186db 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -50,6 +50,7 @@ func generateMessageMarkdown (message: String) -> String { func localConfig (config: Config, context: NSManagedObjectContext, nodeNum: Int64, nodeLongName: String) { + let remote = nodeNum != UserDefaults.preferredPeripheralNum if config.payloadVariant == Config.OneOf_PayloadVariant.bluetooth(config.bluetooth) { upsertBluetoothConfigPacket(config: config.bluetooth, nodeNum: nodeNum, context: context) } else if config.payloadVariant == Config.OneOf_PayloadVariant.device(config.device) { @@ -496,7 +497,8 @@ func adminAppPacket (packet: MeshPacket, context: NSManagedObjectContext) { } else if adminMessage.payloadVariant == AdminMessage.OneOf_PayloadVariant.getConfigResponse(adminMessage.getConfigResponse) { let config = adminMessage.getConfigResponse if config.payloadVariant == Config.OneOf_PayloadVariant.bluetooth(config.bluetooth) { - upsertBluetoothConfigPacket(config: config.bluetooth, nodeNum: Int64(packet.from), context: context) + upsertBluetoothConfigPacket(config: config.bluetooth, nodeNum: Int64(packet.from), sessionPasskey: adminMessage.sessionPasskey, context: context) + /// upsertBluetoothConfigPacket(config: config.bluetooth, nodeNum: Int64(packet.from), context: context) } else if config.payloadVariant == Config.OneOf_PayloadVariant.device(config.device) { upsertDeviceConfigPacket(config: config.device, nodeNum: Int64(packet.from), context: context) } else if config.payloadVariant == Config.OneOf_PayloadVariant.display(config.display) { diff --git a/Meshtastic/Views/Messages/UserList.swift b/Meshtastic/Views/Messages/UserList.swift index 3a2c4a47..44b92621 100644 --- a/Meshtastic/Views/Messages/UserList.swift +++ b/Meshtastic/Views/Messages/UserList.swift @@ -46,6 +46,7 @@ struct UserList: View { NSSortDescriptor(key: "userNode.favorite", ascending: false), NSSortDescriptor(key: "pkiEncrypted", ascending: false), NSSortDescriptor(key: "longName", ascending: true)], + predicate: NSPredicate(format: "hwModelId != nil"), animation: .default ) private var users: FetchedResults @@ -95,6 +96,7 @@ struct UserList: View { Text(user.longName ?? "unknown".localized) .font(.headline) .allowsTightening(true) + Text(user.hwModel ?? "") Spacer() if user.userNode?.favorite ?? false { Image(systemName: "star.fill") diff --git a/Meshtastic/Views/Settings/Config/BluetoothConfig.swift b/Meshtastic/Views/Settings/Config/BluetoothConfig.swift index 75132df1..2ec96942 100644 --- a/Meshtastic/Views/Settings/Config/BluetoothConfig.swift +++ b/Meshtastic/Views/Settings/Config/BluetoothConfig.swift @@ -19,7 +19,6 @@ struct BluetoothConfig: View { @State var mode = 0 @State var fixedPin = "123456" @State var shortPin = false - @State var deviceLoggingEnabled = false var pinLength: Int = 6 let numberFormatter: NumberFormatter = { let formatter = NumberFormatter() @@ -70,10 +69,6 @@ struct BluetoothConfig: View { .foregroundColor(.red) } } - Toggle(isOn: $deviceLoggingEnabled) { - Label("Device Logging Enabled", systemImage: "ladybug") - } - .toggleStyle(SwitchToggleStyle(tint: .accentColor)) } } .disabled(self.bleManager.connectedPeripheral == nil || node?.bluetoothConfig == nil) @@ -85,7 +80,6 @@ struct BluetoothConfig: View { bc.enabled = enabled bc.mode = BluetoothModes(rawValue: mode)?.protoEnumValue() ?? Config.BluetoothConfig.PairingMode.randomPin bc.fixedPin = UInt32(fixedPin) ?? 123456 - bc.deviceLoggingEnabled = deviceLoggingEnabled let adminMessageId = bleManager.saveBluetoothConfig(config: bc, fromUser: connectedNode.user!, toUser: node!.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) if adminMessageId > 0 { // Should show a saved successfully alert once I know that to be true @@ -106,13 +100,22 @@ struct BluetoothConfig: View { ) } ) - .onAppear { + .onFirstAppear { // Need to request a BluetoothConfig from the remote node before allowing changes if let connectedPeripheral = bleManager.connectedPeripheral, let node, node.bluetoothConfig == nil { Logger.mesh.info("empty bluetooth config") let connectedNode = getNodeInfo(id: connectedPeripheral.num, context: context) if let connectedNode { - _ = bleManager.requestBluetoothConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + if UserDefaults.enableAdministration { + /// 2.5 Administration with session passkey + let expiration = node.sessionExpiration ?? Date() + if expiration < Date() || node.bluetoothConfig == nil { + _ = bleManager.requestBluetoothConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + } + } else { + /// Legacy Administration + _ = bleManager.requestBluetoothConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + } } } } @@ -125,15 +128,11 @@ struct BluetoothConfig: View { .onChange(of: fixedPin) { newFixedPin in if newFixedPin != String(node?.bluetoothConfig?.fixedPin ?? -1) { hasChanges = true } } - .onChange(of: deviceLoggingEnabled) { - if $0 != node?.bluetoothConfig?.deviceLoggingEnabled { hasChanges = true } - } } func setBluetoothValues() { self.enabled = node?.bluetoothConfig?.enabled ?? true self.mode = Int(node?.bluetoothConfig?.mode ?? 0) self.fixedPin = String(node?.bluetoothConfig?.fixedPin ?? 123456) - self.deviceLoggingEnabled = node?.bluetoothConfig?.deviceLoggingEnabled ?? false self.hasChanges = false } } From a4fe551e039edc29d7cc2eb740dfd6dd313d76a4 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Wed, 4 Sep 2024 10:06:34 -0700 Subject: [PATCH 29/42] Update several config sections to use the new pki admin structures --- Localizable.xcstrings | 22 ++------ Meshtastic/Extensions/View.swift | 4 +- Meshtastic/Helpers/MeshPackets.swift | 13 ++--- Meshtastic/Persistence/UpdateCoreData.swift | 2 - .../Settings/Config/BluetoothConfig.swift | 2 +- .../Views/Settings/Config/ConfigHeader.swift | 5 +- .../Views/Settings/Config/DeviceConfig.swift | 21 +++++-- .../Views/Settings/Config/DisplayConfig.swift | 21 +++++-- .../Views/Settings/Config/LoRaConfig.swift | 19 +++++-- .../Settings/Config/SecurityConfig.swift | 55 +++++++++---------- 10 files changed, 88 insertions(+), 76 deletions(-) diff --git a/Localizable.xcstrings b/Localizable.xcstrings index 1c7ebdc7..1857e7d1 100644 --- a/Localizable.xcstrings +++ b/Localizable.xcstrings @@ -109,7 +109,7 @@ "%@ Channels?" : { }, - "%@ config data was requested over the admin channel but no response has been returned from the remote node. You can check the status of admin message requests in the admin message log." : { + "%@ config data was requested over the admin channel but no response has been returned from the remote node." : { }, "%@ dB" : { @@ -1865,9 +1865,6 @@ } } } - }, - "Bluetooth Logs" : { - }, "bluetooth.config" : { "localizations" : { @@ -5070,6 +5067,9 @@ }, "Debug Log" : { + }, + "Debug Logs" : { + }, "Debug Logs%@" : { @@ -5361,9 +5361,6 @@ } } } - }, - "Developer" : { - }, "Developers" : { @@ -16073,7 +16070,7 @@ "Other data sources" : { }, - "Output live debug logging over serial." : { + "Output live debug logging over serial, view and export position-redacted device logs over Bluetooth." : { }, "Output pin buzzer GPIO " : { @@ -19273,9 +19270,6 @@ }, "Serial Console over the Stream API." : { - }, - "Serial Debug Logs" : { - }, "serial.config" : { "localizations" : { @@ -22508,12 +22502,6 @@ }, "Via Mqtt" : { - }, - "View and export position-redacted device logs over Bluetooth" : { - - }, - "View Logs" : { - }, "voltage" : { "localizations" : { diff --git a/Meshtastic/Extensions/View.swift b/Meshtastic/Extensions/View.swift index 7349f36d..cec5b003 100644 --- a/Meshtastic/Extensions/View.swift +++ b/Meshtastic/Extensions/View.swift @@ -8,13 +8,13 @@ import SwiftUI public extension View { - func onFirstAppear(_ action: @escaping () -> ()) -> some View { + func onFirstAppear(_ action: @escaping () -> Void) -> some View { modifier(FirstAppear(action: action)) } } private struct FirstAppear: ViewModifier { - let action: () -> () + let action: () -> Void // Use this to only fire your block one time @State private var hasAppeared = false diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index 9f3186db..261ffcf2 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -498,19 +498,18 @@ func adminAppPacket (packet: MeshPacket, context: NSManagedObjectContext) { let config = adminMessage.getConfigResponse if config.payloadVariant == Config.OneOf_PayloadVariant.bluetooth(config.bluetooth) { upsertBluetoothConfigPacket(config: config.bluetooth, nodeNum: Int64(packet.from), sessionPasskey: adminMessage.sessionPasskey, context: context) - /// upsertBluetoothConfigPacket(config: config.bluetooth, nodeNum: Int64(packet.from), context: context) } else if config.payloadVariant == Config.OneOf_PayloadVariant.device(config.device) { - upsertDeviceConfigPacket(config: config.device, nodeNum: Int64(packet.from), context: context) + upsertDeviceConfigPacket(config: config.device, nodeNum: Int64(packet.from), sessionPasskey: adminMessage.sessionPasskey, context: context) } else if config.payloadVariant == Config.OneOf_PayloadVariant.display(config.display) { - upsertDisplayConfigPacket(config: config.display, nodeNum: Int64(packet.from), context: context) + upsertDisplayConfigPacket(config: config.display, nodeNum: Int64(packet.from), sessionPasskey: adminMessage.sessionPasskey, context: context) } else if config.payloadVariant == Config.OneOf_PayloadVariant.lora(config.lora) { - upsertLoRaConfigPacket(config: config.lora, nodeNum: Int64(packet.from), context: context) + upsertLoRaConfigPacket(config: config.lora, nodeNum: Int64(packet.from), sessionPasskey: adminMessage.sessionPasskey, context: context) } else if config.payloadVariant == Config.OneOf_PayloadVariant.network(config.network) { - upsertNetworkConfigPacket(config: config.network, nodeNum: Int64(packet.from), context: context) + upsertNetworkConfigPacket(config: config.network, nodeNum: Int64(packet.from), sessionPasskey: adminMessage.sessionPasskey, context: context) } else if config.payloadVariant == Config.OneOf_PayloadVariant.position(config.position) { - upsertPositionConfigPacket(config: config.position, nodeNum: Int64(packet.from), context: context) + upsertPositionConfigPacket(config: config.position, nodeNum: Int64(packet.from), sessionPasskey: adminMessage.sessionPasskey, context: context) } else if config.payloadVariant == Config.OneOf_PayloadVariant.power(config.power) { - upsertPowerConfigPacket(config: config.power, nodeNum: Int64(packet.from), context: context) + upsertPowerConfigPacket(config: config.power, nodeNum: Int64(packet.from), sessionPasskey: adminMessage.sessionPasskey, context: context) } else if config.payloadVariant == Config.OneOf_PayloadVariant.security(config.security) { upsertSecurityConfigPacket(config: config.security, nodeNum: Int64(packet.from), sessionPasskey: adminMessage.sessionPasskey, context: context) } diff --git a/Meshtastic/Persistence/UpdateCoreData.swift b/Meshtastic/Persistence/UpdateCoreData.swift index 7c43448b..b3e4dba6 100644 --- a/Meshtastic/Persistence/UpdateCoreData.swift +++ b/Meshtastic/Persistence/UpdateCoreData.swift @@ -409,13 +409,11 @@ func upsertBluetoothConfigPacket(config: Config.BluetoothConfig, nodeNum: Int64, newBluetoothConfig.enabled = config.enabled newBluetoothConfig.mode = Int32(config.mode.rawValue) newBluetoothConfig.fixedPin = Int32(config.fixedPin) - newBluetoothConfig.deviceLoggingEnabled = config.deviceLoggingEnabled fetchedNode[0].bluetoothConfig = newBluetoothConfig } else { fetchedNode[0].bluetoothConfig?.enabled = config.enabled fetchedNode[0].bluetoothConfig?.mode = Int32(config.mode.rawValue) fetchedNode[0].bluetoothConfig?.fixedPin = Int32(config.fixedPin) - fetchedNode[0].bluetoothConfig?.deviceLoggingEnabled = config.deviceLoggingEnabled } if sessionPasskey != nil { fetchedNode[0].sessionPasskey = sessionPasskey diff --git a/Meshtastic/Views/Settings/Config/BluetoothConfig.swift b/Meshtastic/Views/Settings/Config/BluetoothConfig.swift index 2ec96942..1fdd3a89 100644 --- a/Meshtastic/Views/Settings/Config/BluetoothConfig.swift +++ b/Meshtastic/Views/Settings/Config/BluetoothConfig.swift @@ -102,7 +102,7 @@ struct BluetoothConfig: View { ) .onFirstAppear { // Need to request a BluetoothConfig from the remote node before allowing changes - if let connectedPeripheral = bleManager.connectedPeripheral, let node, node.bluetoothConfig == nil { + if let connectedPeripheral = bleManager.connectedPeripheral, let node { Logger.mesh.info("empty bluetooth config") let connectedNode = getNodeInfo(id: connectedPeripheral.num, context: context) if let connectedNode { diff --git a/Meshtastic/Views/Settings/Config/ConfigHeader.swift b/Meshtastic/Views/Settings/Config/ConfigHeader.swift index 3f59a01a..d1af3a6a 100644 --- a/Meshtastic/Views/Settings/Config/ConfigHeader.swift +++ b/Meshtastic/Views/Settings/Config/ConfigHeader.swift @@ -17,8 +17,9 @@ struct ConfigHeader: View { } else if node != nil && node?.num ?? 0 != bleManager.connectedPeripheral?.num ?? 0 { // Let users know what is going on if they are using remote admin and don't have the config yet - if node?[keyPath: config] == nil { - Text("\(title) config data was requested over the admin channel but no response has been returned from the remote node. You can check the status of admin message requests in the admin message log.") + let expiration = node?.sessionExpiration ?? Date() + if node?[keyPath: config] == nil || expiration < node?.sessionExpiration ?? Date() { + Text("\(title) config data was requested over the admin channel but no response has been returned from the remote node.") .font(.callout) .foregroundColor(.orange) } else { diff --git a/Meshtastic/Views/Settings/Config/DeviceConfig.swift b/Meshtastic/Views/Settings/Config/DeviceConfig.swift index 13d3aa83..ea01e66f 100644 --- a/Meshtastic/Views/Settings/Config/DeviceConfig.swift +++ b/Meshtastic/Views/Settings/Config/DeviceConfig.swift @@ -242,13 +242,24 @@ struct DeviceConfig: View { ) } ) - .onAppear { + .onFirstAppear { // Need to request a DeviceConfig from the remote node before allowing changes - if bleManager.connectedPeripheral != nil && node?.deviceConfig == nil { + if let connectedPeripheral = bleManager.connectedPeripheral, let node { Logger.mesh.info("empty device config") - let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1, context: context) - if node != nil && connectedNode != nil && connectedNode?.user != nil { - _ = bleManager.requestDeviceConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) + let connectedNode = getNodeInfo(id: connectedPeripheral.num, context: context) + if let connectedNode { + if UserDefaults.enableAdministration { + /// 2.5 Administration with session passkey + let expiration = node.sessionExpiration ?? Date() + if expiration < Date() || node.deviceConfig == nil { + _ = bleManager.requestDeviceConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + } + } else { + if node.deviceConfig == nil { + /// Legacy Administration + _ = bleManager.requestDeviceConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + } + } } } } diff --git a/Meshtastic/Views/Settings/Config/DisplayConfig.swift b/Meshtastic/Views/Settings/Config/DisplayConfig.swift index 1f3f6de4..0ef44bc1 100644 --- a/Meshtastic/Views/Settings/Config/DisplayConfig.swift +++ b/Meshtastic/Views/Settings/Config/DisplayConfig.swift @@ -163,13 +163,22 @@ struct DisplayConfig: View { ) } ) - .onAppear { - // Need to request a LoRaConfig from the remote node before allowing changes - if bleManager.connectedPeripheral != nil && node?.displayConfig == nil { + .onFirstAppear { + // Need to request a DisplayConfig from the remote node before allowing changes + if let connectedPeripheral = bleManager.connectedPeripheral, let node { Logger.mesh.info("empty display config") - let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? 0, context: context) - if node != nil && connectedNode != nil { - _ = bleManager.requestDisplayConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) + let connectedNode = getNodeInfo(id: connectedPeripheral.num, context: context) + if let connectedNode { + if UserDefaults.enableAdministration { + /// 2.5 Administration with session passkey + let expiration = node.sessionExpiration ?? Date() + if expiration < Date() || node.displayConfig == nil { + _ = bleManager.requestDisplayConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + } + } else { + /// Legacy Administration + _ = bleManager.requestDisplayConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + } } } } diff --git a/Meshtastic/Views/Settings/Config/LoRaConfig.swift b/Meshtastic/Views/Settings/Config/LoRaConfig.swift index dad7c1a4..b9926b8a 100644 --- a/Meshtastic/Views/Settings/Config/LoRaConfig.swift +++ b/Meshtastic/Views/Settings/Config/LoRaConfig.swift @@ -232,13 +232,22 @@ struct LoRaConfig: View { ) } ) - .onAppear { + .onFirstAppear { // Need to request a LoRaConfig from the remote node before allowing changes - if bleManager.connectedPeripheral != nil && node?.loRaConfig == nil { + if let connectedPeripheral = bleManager.connectedPeripheral, let node { Logger.mesh.info("empty lora config") - let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context) - if node != nil && connectedNode != nil { - _ = bleManager.requestLoRaConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) + let connectedNode = getNodeInfo(id: connectedPeripheral.num, context: context) + if let connectedNode { + if UserDefaults.enableAdministration { + /// 2.5 Administration with session passkey + let expiration = node.sessionExpiration ?? Date() + if expiration < Date() || node.loRaConfig == nil { + _ = bleManager.requestLoRaConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + } + } else { + /// Legacy Administration + _ = bleManager.requestLoRaConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + } } } } diff --git a/Meshtastic/Views/Settings/Config/SecurityConfig.swift b/Meshtastic/Views/Settings/Config/SecurityConfig.swift index e07ed945..5576ac6f 100644 --- a/Meshtastic/Views/Settings/Config/SecurityConfig.swift +++ b/Meshtastic/Views/Settings/Config/SecurityConfig.swift @@ -30,7 +30,6 @@ struct SecurityConfig: View { @State var isManaged = false @State var serialEnabled = false @State var debugLogApiEnabled = false - @State var bluetoothLoggingEnabled = false @State var adminChannelEnabled = false var body: some View { @@ -71,10 +70,15 @@ struct SecurityConfig: View { } } Section(header: Text("Logs")) { - Toggle(isOn: $bluetoothLoggingEnabled) { - Label("Bluetooth Logs", systemImage: "dot.radiowaves.right") - Text("View and export position-redacted device logs over Bluetooth") - Link("View Logs", destination: URL(string: "meshtastic:///settings/debugLogs")!) + Toggle(isOn: $serialEnabled) { + Label("Serial Console", systemImage: "terminal") + Text("Serial Console over the Stream API.") + } + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + + Toggle(isOn: $debugLogApiEnabled) { + Label("Debug Logs", systemImage: "ant.fill") + Text("Output live debug logging over serial, view and export position-redacted device logs over Bluetooth.") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) } @@ -92,20 +96,6 @@ struct SecurityConfig: View { } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) } - Section(header: Text("Developer")) { - Toggle(isOn: $serialEnabled) { - Label("Serial Console", systemImage: "terminal") - Text("Serial Console over the Stream API.") - } - .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - if serialEnabled { - Toggle(isOn: $debugLogApiEnabled) { - Label("Serial Debug Logs", systemImage: "ant.fill") - Text("Output live debug logging over serial.") - } - .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - } - } } } .scrollDismissesKeyboard(.immediately) @@ -126,9 +116,6 @@ struct SecurityConfig: View { .onChange(of: debugLogApiEnabled) { if $0 != node?.securityConfig?.debugLogApiEnabled { hasChanges = true } } - .onChange(of: bluetoothLoggingEnabled) { - if $0 != node?.securityConfig?.bluetoothLoggingEnabled { hasChanges = true } - } .onChange(of: adminChannelEnabled) { if $0 != node?.securityConfig?.adminChannelEnabled { hasChanges = true } } @@ -162,11 +149,23 @@ struct SecurityConfig: View { hasChanges = true } .onFirstAppear { - // Need to request a Power config from the remote node before allowing changes - if bleManager.connectedPeripheral != nil && node?.securityConfig == nil { - let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? 0, context: context) - if node != nil && connectedNode != nil { - _ = bleManager.requestSecurityConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) + // Need to request a DeviceConfig from the remote node before allowing changes + if let connectedPeripheral = bleManager.connectedPeripheral, let node { + Logger.mesh.info("empty security config") + let connectedNode = getNodeInfo(id: connectedPeripheral.num, context: context) + if let connectedNode { + if UserDefaults.enableAdministration { + /// 2.5 Administration with session passkey + let expiration = node.sessionExpiration ?? Date() + if expiration < Date() || node.securityConfig == nil { + _ = bleManager.requestSecurityConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + } + } else { + if node.deviceConfig == nil { + /// Legacy Administration + _ = bleManager.requestSecurityConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + } + } } } } @@ -190,7 +189,6 @@ struct SecurityConfig: View { config.isManaged = isManaged config.serialEnabled = serialEnabled config.debugLogApiEnabled = debugLogApiEnabled - config.bluetoothLoggingEnabled = bluetoothLoggingEnabled config.adminChannelEnabled = adminChannelEnabled let adminMessageId = bleManager.saveSecurityConfig( @@ -215,7 +213,6 @@ struct SecurityConfig: View { self.isManaged = node?.securityConfig?.isManaged ?? false self.serialEnabled = node?.securityConfig?.serialEnabled ?? false self.debugLogApiEnabled = node?.securityConfig?.debugLogApiEnabled ?? false - self.bluetoothLoggingEnabled = node?.securityConfig?.bluetoothLoggingEnabled ?? false self.adminChannelEnabled = node?.securityConfig?.adminChannelEnabled ?? false self.hasChanges = false } From 44d65d8185dc50d66f860df673b89573c36cb687 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Wed, 4 Sep 2024 10:27:06 -0700 Subject: [PATCH 30/42] Additional config sections --- Meshtastic.xcodeproj/project.pbxproj | 2 +- .../Views/Settings/Config/NetworkConfig.swift | 19 +++++++++++ .../Settings/Config/PositionConfig.swift | 23 +++++++------ .../Views/Settings/Config/PowerConfig.swift | 32 ++++++++++++++++--- 4 files changed, 61 insertions(+), 15 deletions(-) diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 83fac71b..43f27393 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -719,8 +719,8 @@ DD41582528582E9B009B0E59 /* DeviceConfig.swift */, DD8EBF42285058FA00426DCA /* DisplayConfig.swift */, DD2553562855B02500E55709 /* LoRaConfig.swift */, - DD2553582855B52700E55709 /* PositionConfig.swift */, DD8ED9C42898D51F00B3B0AB /* NetworkConfig.swift */, + DD2553582855B52700E55709 /* PositionConfig.swift */, D93068DA2B81C85E0066FBC8 /* PowerConfig.swift */, DD1BD0F22C63C65E008C0C70 /* SecurityConfig.swift */, DD61937B2863877A00E59241 /* Module */, diff --git a/Meshtastic/Views/Settings/Config/NetworkConfig.swift b/Meshtastic/Views/Settings/Config/NetworkConfig.swift index 0554c766..dd767b6c 100644 --- a/Meshtastic/Views/Settings/Config/NetworkConfig.swift +++ b/Meshtastic/Views/Settings/Config/NetworkConfig.swift @@ -128,6 +128,25 @@ struct NetworkConfig: View { } } } + .onFirstAppear { + // Need to request a NetworkConfig from the remote node before allowing changes + if let connectedPeripheral = bleManager.connectedPeripheral, let node { + Logger.mesh.info("empty network config") + let connectedNode = getNodeInfo(id: connectedPeripheral.num, context: context) + if let connectedNode { + if UserDefaults.enableAdministration { + /// 2.5 Administration with session passkey + let expiration = node.sessionExpiration ?? Date() + if expiration < Date() || node.networkConfig == nil { + _ = bleManager.requestNetworkConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + } + } else { + /// Legacy Administration + _ = bleManager.requestNetworkConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + } + } + } + } .onChange(of: wifiEnabled) { if $0 != node?.networkConfig?.wifiEnabled { hasChanges = true } } diff --git a/Meshtastic/Views/Settings/Config/PositionConfig.swift b/Meshtastic/Views/Settings/Config/PositionConfig.swift index 0f71b379..392486da 100644 --- a/Meshtastic/Views/Settings/Config/PositionConfig.swift +++ b/Meshtastic/Views/Settings/Config/PositionConfig.swift @@ -376,18 +376,23 @@ struct PositionConfig: View { ) } ) - .onAppear { + .onFirstAppear { supportedVersion = bleManager.connectedVersion == "0.0.0" || self.minimumVersion.compare(bleManager.connectedVersion, options: .numeric) == .orderedAscending || minimumVersion.compare(bleManager.connectedVersion, options: .numeric) == .orderedSame - // Need to request a PositionConfig from the remote node before allowing changes - if let connectedPeripheral = bleManager.connectedPeripheral, node?.positionConfig == nil { + // Need to request a NetworkConfig from the remote node before allowing changes + if let connectedPeripheral = bleManager.connectedPeripheral, let node { Logger.mesh.info("empty position config") let connectedNode = getNodeInfo(id: connectedPeripheral.num, context: context) - if let node, let connectedNode { - _ = bleManager.requestPositionConfig( - fromUser: connectedNode.user!, - toUser: node.user!, - adminIndex: connectedNode.myInfo?.adminIndex ?? 0 - ) + if let connectedNode { + if UserDefaults.enableAdministration { + /// 2.5 Administration with session passkey + let expiration = node.sessionExpiration ?? Date() + if expiration < Date() || node.positionConfig == nil { + _ = bleManager.requestPositionConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + } + } else { + /// Legacy Administration + _ = bleManager.requestPositionConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + } } } } diff --git a/Meshtastic/Views/Settings/Config/PowerConfig.swift b/Meshtastic/Views/Settings/Config/PowerConfig.swift index 42b6a534..cf213f8e 100644 --- a/Meshtastic/Views/Settings/Config/PowerConfig.swift +++ b/Meshtastic/Views/Settings/Config/PowerConfig.swift @@ -1,5 +1,6 @@ import SwiftUI import MeshtasticProtobufs +import OSLog struct PowerConfig: View { @Environment(\.managedObjectContext) private var context @@ -118,6 +119,17 @@ struct PowerConfig: View { } } .onAppear { + + // Need to request a Power config from the remote node before allowing changes + if bleManager.connectedPeripheral != nil && node?.powerConfig == nil { + let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? 0, context: context) + if node != nil && connectedNode != nil { + _ = bleManager.requestPowerConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) + } + } + } + + .onFirstAppear { Api().loadDeviceHardwareData { (hw) in for device in hw { let currentHardware = node?.user?.hwModel ?? "UNSET" @@ -127,11 +139,21 @@ struct PowerConfig: View { } } } - // Need to request a Power config from the remote node before allowing changes - if bleManager.connectedPeripheral != nil && node?.powerConfig == nil { - let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? 0, context: context) - if node != nil && connectedNode != nil { - _ = bleManager.requestPowerConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) + // Need to request a NetworkConfig from the remote node before allowing changes + if let connectedPeripheral = bleManager.connectedPeripheral, let node { + Logger.mesh.info("empty power config") + let connectedNode = getNodeInfo(id: connectedPeripheral.num, context: context) + if let connectedNode { + if UserDefaults.enableAdministration { + /// 2.5 Administration with session passkey + let expiration = node.sessionExpiration ?? Date() + if expiration < Date() || node.positionConfig == nil { + _ = bleManager.requestPositionConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + } + } else { + /// Legacy Administration + _ = bleManager.requestPowerConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + } } } } From e294a2480c59120853ac49f44658c738d5b5928a Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Wed, 4 Sep 2024 12:22:25 -0700 Subject: [PATCH 31/42] Remove debugging hardware version --- Meshtastic/Views/Messages/UserList.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Meshtastic/Views/Messages/UserList.swift b/Meshtastic/Views/Messages/UserList.swift index 44b92621..4e09859f 100644 --- a/Meshtastic/Views/Messages/UserList.swift +++ b/Meshtastic/Views/Messages/UserList.swift @@ -96,7 +96,6 @@ struct UserList: View { Text(user.longName ?? "unknown".localized) .font(.headline) .allowsTightening(true) - Text(user.hwModel ?? "") Spacer() if user.userNode?.favorite ?? false { Image(systemName: "star.fill") From 23d62e8e1237e4e0a52e44f5c26519b2c31ff65f Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Wed, 4 Sep 2024 18:02:17 -0700 Subject: [PATCH 32/42] Set public key on upsert of existing node --- Meshtastic/Persistence/UpdateCoreData.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Meshtastic/Persistence/UpdateCoreData.swift b/Meshtastic/Persistence/UpdateCoreData.swift index b3e4dba6..f58a0a64 100644 --- a/Meshtastic/Persistence/UpdateCoreData.swift +++ b/Meshtastic/Persistence/UpdateCoreData.swift @@ -269,6 +269,8 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) fetchedNode[0].user!.role = Int32(nodeInfoMessage.user.role.rawValue) fetchedNode[0].user!.hwModel = String(describing: nodeInfoMessage.user.hwModel).uppercased() fetchedNode[0].user!.hwModelId = Int32(nodeInfoMessage.user.hwModel.rawValue) + fetchedNode[0].user!.pkiEncrypted = packet.pkiEncrypted + fetchedNode[0].user!.publicKey = packet.publicKey Task { Api().loadDeviceHardwareData { (hw) in let dh = hw.first(where: { $0.hwModel == fetchedNode[0].user?.hwModelId ?? 0 }) From 1853afde73e29e7c2381b4ae63af28f596f61866 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Wed, 4 Sep 2024 18:13:35 -0700 Subject: [PATCH 33/42] allow retry of pki failed --- Meshtastic/Enums/RoutingError.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Meshtastic/Enums/RoutingError.swift b/Meshtastic/Enums/RoutingError.swift index 108fedd2..8b50de89 100644 --- a/Meshtastic/Enums/RoutingError.swift +++ b/Meshtastic/Enums/RoutingError.swift @@ -95,7 +95,7 @@ enum RoutingError: Int, CaseIterable, Identifiable { case .notAuthorized: return true case .pkiFailed: - return false + return true case .pkiUnknownPubkey: return false } From 6e6027263a2ef9a9bbd02fa68cf75254fab1e1a0 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Thu, 5 Sep 2024 10:39:54 -0700 Subject: [PATCH 34/42] Log node info packet json --- Localizable.xcstrings | 5 +---- Meshtastic/Helpers/BLEManager.swift | 1 + Meshtastic/Helpers/MeshPackets.swift | 2 +- Meshtastic/Persistence/UpdateCoreData.swift | 7 +++++-- .../Views/Settings/Config/DeviceConfig.swift | 16 ---------------- .../Views/Settings/Config/SecurityConfig.swift | 3 +-- 6 files changed, 9 insertions(+), 25 deletions(-) diff --git a/Localizable.xcstrings b/Localizable.xcstrings index 1857e7d1..b6673742 100644 --- a/Localizable.xcstrings +++ b/Localizable.xcstrings @@ -5426,7 +5426,7 @@ "Device GPS" : { }, - "Device is managed by a mesh administrator." : { + "Device is managed by a mesh administrator, the user is unable to access any of the device settings." : { }, "Device Metrics" : { @@ -6923,9 +6923,6 @@ }, "Enabling Ethernet will disable the bluetooth connection to the app." : { - }, - "Enabling Managed mode will restrict access to all radio configurations, such as short/long names, regions, channels, modules, etc. and will only be accessible through the Admin channel. To avoid being locked out, make sure the Admin channel is working properly before enabling it." : { - }, "Enabling WiFi will disable the bluetooth connection to the app." : { diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 87551c4f..11597647 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -766,6 +766,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate case .waypointApp: waypointPacket(packet: decodedInfo.packet, context: context) case .nodeinfoApp: + MeshLogger.log("πŸ•ΈοΈ MESH PACKET received for Node Info App for PKI LOCK debugging \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") if !invalidVersion { upsertNodeInfoPacket(packet: decodedInfo.packet, context: context) } case .routingApp: if !invalidVersion { routingPacket(packet: decodedInfo.packet, connectedNodeNum: self.connectedPeripheral.num, context: context) } diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index 261ffcf2..3b6b2b65 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -366,7 +366,7 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje fetchedNode[0].user = UserEntity(context: context) } // Set the public key for a user if it is empty, don't update - if fetchedNode[0].user?.publicKey?.isEmpty == nil && !nodeInfo.user.publicKey.isEmpty { + if fetchedNode[0].user?.publicKey == nil && !nodeInfo.user.publicKey.isEmpty { fetchedNode[0].user?.pkiEncrypted = true fetchedNode[0].user?.publicKey = nodeInfo.user.publicKey } diff --git a/Meshtastic/Persistence/UpdateCoreData.swift b/Meshtastic/Persistence/UpdateCoreData.swift index f58a0a64..039b654e 100644 --- a/Meshtastic/Persistence/UpdateCoreData.swift +++ b/Meshtastic/Persistence/UpdateCoreData.swift @@ -269,8 +269,11 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) fetchedNode[0].user!.role = Int32(nodeInfoMessage.user.role.rawValue) fetchedNode[0].user!.hwModel = String(describing: nodeInfoMessage.user.hwModel).uppercased() fetchedNode[0].user!.hwModelId = Int32(nodeInfoMessage.user.hwModel.rawValue) - fetchedNode[0].user!.pkiEncrypted = packet.pkiEncrypted - fetchedNode[0].user!.publicKey = packet.publicKey + + if !packet.publicKey.isEmpty { + fetchedNode[0].user!.pkiEncrypted = packet.pkiEncrypted + fetchedNode[0].user!.publicKey = packet.publicKey + } Task { Api().loadDeviceHardwareData { (hw) in let dh = hw.first(where: { $0.hwModel == fetchedNode[0].user?.hwModelId ?? 0 }) diff --git a/Meshtastic/Views/Settings/Config/DeviceConfig.swift b/Meshtastic/Views/Settings/Config/DeviceConfig.swift index ea01e66f..9382b297 100644 --- a/Meshtastic/Views/Settings/Config/DeviceConfig.swift +++ b/Meshtastic/Views/Settings/Config/DeviceConfig.swift @@ -29,7 +29,6 @@ struct DeviceConfig: View { @State var nodeInfoBroadcastSecs = 10800 @State var doubleTapAsButtonPress = false @State var ledHeartbeatEnabled = true - @State var isManaged = false @State var tzdef = "" var body: some View { @@ -62,12 +61,6 @@ struct DeviceConfig: View { } .pickerStyle(DefaultPickerStyle()) - Toggle(isOn: $isManaged) { - Label("Managed Device", systemImage: "gearshape.arrow.triangle.2.circlepath") - Text("Enabling Managed mode will restrict access to all radio configurations, such as short/long names, regions, channels, modules, etc. and will only be accessible through the Admin channel. To avoid being locked out, make sure the Admin channel is working properly before enabling it.") - } - .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - Picker("Node Info Broadcast Interval", selection: $nodeInfoBroadcastSecs ) { ForEach(UpdateIntervals.allCases) { ui in if ui.rawValue >= 3600 { @@ -213,13 +206,8 @@ struct DeviceConfig: View { dc.rebroadcastMode = RebroadcastModes(rawValue: rebroadcastMode)?.protoEnumValue() ?? RebroadcastModes.all.protoEnumValue() dc.nodeInfoBroadcastSecs = UInt32(nodeInfoBroadcastSecs) dc.doubleTapAsButtonPress = doubleTapAsButtonPress - dc.isManaged = isManaged dc.tzdef = tzdef dc.ledHeartbeatDisabled = !ledHeartbeatEnabled - if isManaged { - serialEnabled = false - debugLogEnabled = false - } let adminMessageId = bleManager.saveDeviceConfig(config: dc, fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) if adminMessageId > 0 { // Should show a saved successfully alert once I know that to be true @@ -287,9 +275,6 @@ struct DeviceConfig: View { .onChange(of: doubleTapAsButtonPress) { if $0 != node?.deviceConfig?.doubleTapAsButtonPress { hasChanges = true } } - .onChange(of: isManaged) { - if $0 != node?.deviceConfig?.isManaged { hasChanges = true } - } .onChange(of: tzdef) { newTzdef in if newTzdef != node?.deviceConfig?.tzdef { hasChanges = true } } @@ -312,7 +297,6 @@ struct DeviceConfig: View { } self.doubleTapAsButtonPress = node?.deviceConfig?.doubleTapAsButtonPress ?? false self.ledHeartbeatEnabled = node?.deviceConfig?.ledHeartbeatEnabled ?? true - self.isManaged = node?.deviceConfig?.isManaged ?? false self.tzdef = node?.deviceConfig?.tzdef ?? "" if self.tzdef.isEmpty { self.tzdef = TimeZone.current.posixDescription diff --git a/Meshtastic/Views/Settings/Config/SecurityConfig.swift b/Meshtastic/Views/Settings/Config/SecurityConfig.swift index 5576ac6f..6388ea6e 100644 --- a/Meshtastic/Views/Settings/Config/SecurityConfig.swift +++ b/Meshtastic/Views/Settings/Config/SecurityConfig.swift @@ -75,7 +75,6 @@ struct SecurityConfig: View { Text("Serial Console over the Stream API.") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - Toggle(isOn: $debugLogApiEnabled) { Label("Debug Logs", systemImage: "ant.fill") Text("Output live debug logging over serial, view and export position-redacted device logs over Bluetooth.") @@ -86,7 +85,7 @@ struct SecurityConfig: View { if adminKey.length > 0 || adminChannelEnabled { Toggle(isOn: $isManaged) { Label("Managed Device", systemImage: "gearshape.arrow.triangle.2.circlepath") - Text("Device is managed by a mesh administrator.") + Text("Device is managed by a mesh administrator, the user is unable to access any of the device settings.") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) } From b32cabec54e7dba50cdbc947570b57f0f5b1601d Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Thu, 5 Sep 2024 13:42:46 -0700 Subject: [PATCH 35/42] set keys on create user catch --- Meshtastic/Persistence/UpdateCoreData.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Meshtastic/Persistence/UpdateCoreData.swift b/Meshtastic/Persistence/UpdateCoreData.swift index 039b654e..624dbb20 100644 --- a/Meshtastic/Persistence/UpdateCoreData.swift +++ b/Meshtastic/Persistence/UpdateCoreData.swift @@ -209,6 +209,8 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) } else { if packet.from > Constants.minimumNodeNum { let newUser = createUser(num: Int64(packet.from), context: context) + newNode.user?.pkiEncrypted = packet.pkiEncrypted + newNode.user?.publicKey = packet.publicKey newNode.user = newUser } } From d95cf2d1901e45151d0fdbabef764c82511f9f72 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Thu, 5 Sep 2024 14:07:40 -0700 Subject: [PATCH 36/42] Add lock to node list --- Meshtastic/Views/Nodes/Helpers/NodeListItem.swift | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift b/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift index 63aff338..481666a1 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift @@ -30,8 +30,22 @@ struct NodeListItem: View { } VStack(alignment: .leading) { HStack { + if node.user?.pkiEncrypted ?? false { + if !(node.user?.keyMatch ?? false) { + /// Public Key on the User and the Public Key on the Last Message don't match + Image(systemName: "key.slash") + .foregroundColor(.red) + } else { + Image(systemName: "lock.fill") + .foregroundColor(.green) + } + } else { + Image(systemName: "lock.open.fill") + .foregroundColor(.yellow) + } Text(node.user?.longName ?? "unknown".localized) .font(.headline) + .fontWeight(.regular) .allowsTightening(true) if node.favorite { Spacer() From d0e10ef3301b5485c8210398d44b433b705c33a4 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Thu, 5 Sep 2024 19:31:29 -0700 Subject: [PATCH 37/42] Finish up the admin, make the admin dropdown a navigationlink so you can read the full long name --- Localizable.xcstrings | 3 -- Meshtastic/Views/Settings/Channels.swift | 9 +++--- .../Views/Settings/Channels/ChannelForm.swift | 26 ++++++++--------- .../Settings/Config/BluetoothConfig.swift | 16 +++++----- .../Views/Settings/Config/DeviceConfig.swift | 22 +++++++------- .../Views/Settings/Config/DisplayConfig.swift | 16 +++++----- .../Views/Settings/Config/LoRaConfig.swift | 16 +++++----- .../Config/Module/AmbientLightingConfig.swift | 23 +++++++++++---- .../Config/Module/CannedMessagesConfig.swift | 23 +++++++++++---- .../Config/Module/DetectionSensorConfig.swift | 25 +++++++++++----- .../Module/ExternalNotificationConfig.swift | 27 ++++++++++++----- .../Settings/Config/Module/MQTTConfig.swift | 23 +++++++++++---- .../Config/Module/PaxCounterConfig.swift | 25 ++++++++++++---- .../Config/Module/RangeTestConfig.swift | 25 +++++++++++----- .../Settings/Config/Module/RtttlConfig.swift | 25 ++++++++++++---- .../Settings/Config/Module/SerialConfig.swift | 25 +++++++++++----- .../Config/Module/StoreForwardConfig.swift | 25 +++++++++++----- .../Config/Module/TelemetryConfig.swift | 21 ++++++++++---- .../Views/Settings/Config/NetworkConfig.swift | 16 +++++----- .../Settings/Config/PositionConfig.swift | 16 +++++----- .../Views/Settings/Config/PowerConfig.swift | 29 +++++++------------ .../Settings/Config/SecurityConfig.swift | 22 +++++++------- Meshtastic/Views/Settings/Settings.swift | 6 ++-- 23 files changed, 296 insertions(+), 168 deletions(-) diff --git a/Localizable.xcstrings b/Localizable.xcstrings index b6673742..05c2bbb8 100644 --- a/Localizable.xcstrings +++ b/Localizable.xcstrings @@ -4581,9 +4581,6 @@ }, "Configure" : { - }, - "Configuring Node" : { - }, "Connect to a Node" : { diff --git a/Meshtastic/Views/Settings/Channels.swift b/Meshtastic/Views/Settings/Channels.swift index 61cddcea..f039534c 100644 --- a/Meshtastic/Views/Settings/Channels.swift +++ b/Meshtastic/Views/Settings/Channels.swift @@ -95,7 +95,6 @@ struct Channels: View { if channelKey == "AQ==" { positionPrecision = 13 preciseLocation = false - positionsEnabled = true } } else if !supportedVersion && channelRole == 2 { positionPrecision = 0 @@ -104,7 +103,7 @@ struct Channels: View { } else { if channelKey == "AQ==" { preciseLocation = false - if positionPrecision < 10 || positionPrecision > 16 { + if (positionPrecision > 0 && positionPrecision < 10) || positionPrecision > 16 { positionPrecision = 13 } } else if positionPrecision == 32 { @@ -226,7 +225,7 @@ struct Channels: View { } label: { Label("save", systemImage: "square.and.arrow.down") } - .disabled(bleManager.connectedPeripheral == nil || !hasChanges || !hasValidKey) + .disabled(bleManager.connectedPeripheral == nil)// || !hasChanges)// !hasValidKey) .buttonStyle(.bordered) .buttonBorderShape(.capsule) .controlSize(.large) @@ -259,10 +258,9 @@ struct Channels: View { channelKey = key positionsEnabled = false preciseLocation = false - positionPrecision = 13 + positionPrecision = 0 uplink = false downlink = false - hasChanges = true let newChannel = ChannelEntity(context: context) newChannel.id = channelIndex @@ -274,6 +272,7 @@ struct Channels: View { newChannel.psk = Data(base64Encoded: channelKey) ?? Data() newChannel.positionPrecision = Int32(positionPrecision) selectedChannel = newChannel + hasChanges = true } label: { Label("Add Channel", systemImage: "plus.square") diff --git a/Meshtastic/Views/Settings/Channels/ChannelForm.swift b/Meshtastic/Views/Settings/Channels/ChannelForm.swift index d0a111e5..d21e0f42 100644 --- a/Meshtastic/Views/Settings/Channels/ChannelForm.swift +++ b/Meshtastic/Views/Settings/Channels/ChannelForm.swift @@ -130,7 +130,6 @@ struct ChannelForm: View { } Section(header: Text("position")) { - VStack(alignment: .leading) { Toggle(isOn: $positionsEnabled) { Label(channelRole == 1 ? "Positions Enabled" : "Allow Position Requests", systemImage: positionsEnabled ? "mappin" : "mappin.slash") @@ -140,20 +139,21 @@ struct ChannelForm: View { } if positionsEnabled { - VStack(alignment: .leading) { - Toggle(isOn: $preciseLocation) { - Label("Precise Location", systemImage: "scope") - } - .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - .disabled(!supportedVersion) - .listRowSeparator(.visible) - .onChange(of: preciseLocation) { pl in - if pl == false { - positionPrecision = 13 + if channelKey != "AQ==" && channelRole > 0 { + VStack(alignment: .leading) { + Toggle(isOn: $preciseLocation) { + Label("Precise Location", systemImage: "scope") + } + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + .disabled(!supportedVersion) + .listRowSeparator(.visible) + .onChange(of: preciseLocation) { pl in + if pl == false { + positionPrecision = 13 + } } } } - if !preciseLocation { VStack(alignment: .leading) { Label("Approximate Location", systemImage: "location.slash.circle.fill") @@ -213,7 +213,7 @@ struct ChannelForm: View { } .onChange(of: preciseLocation) { loc in if loc == true { - if channelKey == "AQ==" && channelRole == 0 { + if channelKey == "AQ==" { preciseLocation = false } else { positionPrecision = 32 diff --git a/Meshtastic/Views/Settings/Config/BluetoothConfig.swift b/Meshtastic/Views/Settings/Config/BluetoothConfig.swift index 1fdd3a89..46211dac 100644 --- a/Meshtastic/Views/Settings/Config/BluetoothConfig.swift +++ b/Meshtastic/Views/Settings/Config/BluetoothConfig.swift @@ -106,15 +106,17 @@ struct BluetoothConfig: View { Logger.mesh.info("empty bluetooth config") let connectedNode = getNodeInfo(id: connectedPeripheral.num, context: context) if let connectedNode { - if UserDefaults.enableAdministration { - /// 2.5 Administration with session passkey - let expiration = node.sessionExpiration ?? Date() - if expiration < Date() || node.bluetoothConfig == nil { + if node.num != connectedNode.num { + if UserDefaults.enableAdministration { + /// 2.5 Administration with session passkey + let expiration = node.sessionExpiration ?? Date() + if expiration < Date() || node.bluetoothConfig == nil { + _ = bleManager.requestBluetoothConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + } + } else { + /// Legacy Administration _ = bleManager.requestBluetoothConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) } - } else { - /// Legacy Administration - _ = bleManager.requestBluetoothConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) } } } diff --git a/Meshtastic/Views/Settings/Config/DeviceConfig.swift b/Meshtastic/Views/Settings/Config/DeviceConfig.swift index 9382b297..dc17446e 100644 --- a/Meshtastic/Views/Settings/Config/DeviceConfig.swift +++ b/Meshtastic/Views/Settings/Config/DeviceConfig.swift @@ -236,16 +236,18 @@ struct DeviceConfig: View { Logger.mesh.info("empty device config") let connectedNode = getNodeInfo(id: connectedPeripheral.num, context: context) if let connectedNode { - if UserDefaults.enableAdministration { - /// 2.5 Administration with session passkey - let expiration = node.sessionExpiration ?? Date() - if expiration < Date() || node.deviceConfig == nil { - _ = bleManager.requestDeviceConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) - } - } else { - if node.deviceConfig == nil { - /// Legacy Administration - _ = bleManager.requestDeviceConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + if node.num != connectedNode.num { + if UserDefaults.enableAdministration { + /// 2.5 Administration with session passkey + let expiration = node.sessionExpiration ?? Date() + if expiration < Date() || node.deviceConfig == nil { + _ = bleManager.requestDeviceConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + } + } else { + if node.deviceConfig == nil { + /// Legacy Administration + _ = bleManager.requestDeviceConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + } } } } diff --git a/Meshtastic/Views/Settings/Config/DisplayConfig.swift b/Meshtastic/Views/Settings/Config/DisplayConfig.swift index 0ef44bc1..1e9781a6 100644 --- a/Meshtastic/Views/Settings/Config/DisplayConfig.swift +++ b/Meshtastic/Views/Settings/Config/DisplayConfig.swift @@ -169,15 +169,17 @@ struct DisplayConfig: View { Logger.mesh.info("empty display config") let connectedNode = getNodeInfo(id: connectedPeripheral.num, context: context) if let connectedNode { - if UserDefaults.enableAdministration { - /// 2.5 Administration with session passkey - let expiration = node.sessionExpiration ?? Date() - if expiration < Date() || node.displayConfig == nil { + if node.num != connectedNode.num { + if UserDefaults.enableAdministration { + /// 2.5 Administration with session passkey + let expiration = node.sessionExpiration ?? Date() + if expiration < Date() || node.displayConfig == nil { + _ = bleManager.requestDisplayConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + } + } else { + /// Legacy Administration _ = bleManager.requestDisplayConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) } - } else { - /// Legacy Administration - _ = bleManager.requestDisplayConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) } } } diff --git a/Meshtastic/Views/Settings/Config/LoRaConfig.swift b/Meshtastic/Views/Settings/Config/LoRaConfig.swift index b9926b8a..43729f86 100644 --- a/Meshtastic/Views/Settings/Config/LoRaConfig.swift +++ b/Meshtastic/Views/Settings/Config/LoRaConfig.swift @@ -238,15 +238,17 @@ struct LoRaConfig: View { Logger.mesh.info("empty lora config") let connectedNode = getNodeInfo(id: connectedPeripheral.num, context: context) if let connectedNode { - if UserDefaults.enableAdministration { - /// 2.5 Administration with session passkey - let expiration = node.sessionExpiration ?? Date() - if expiration < Date() || node.loRaConfig == nil { + if node.num != connectedNode.num { + if UserDefaults.enableAdministration { + /// 2.5 Administration with session passkey + let expiration = node.sessionExpiration ?? Date() + if expiration < Date() || node.loRaConfig == nil { + _ = bleManager.requestLoRaConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + } + } else { + /// Legacy Administration _ = bleManager.requestLoRaConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) } - } else { - /// Legacy Administration - _ = bleManager.requestLoRaConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) } } } diff --git a/Meshtastic/Views/Settings/Config/Module/AmbientLightingConfig.swift b/Meshtastic/Views/Settings/Config/Module/AmbientLightingConfig.swift index 76c98752..fe3faa51 100644 --- a/Meshtastic/Views/Settings/Config/Module/AmbientLightingConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/AmbientLightingConfig.swift @@ -6,6 +6,7 @@ // import MeshtasticProtobufs import SwiftUI +import OSLog @available(iOS 17.0, macOS 14.0, *) struct AmbientLightingConfig: View { @@ -85,12 +86,24 @@ struct AmbientLightingConfig: View { ) } ) - .onAppear { + .onFirstAppear { // Need to request a Ambient Lighting Config from the remote node before allowing changes - if bleManager.connectedPeripheral != nil && node?.ambientLightingConfig == nil { - let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context) - if node != nil && connectedNode != nil { - _ = bleManager.requestAmbientLightingConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) + if let connectedPeripheral = bleManager.connectedPeripheral, let node { + Logger.mesh.info("empty ambient lighting config") + let connectedNode = getNodeInfo(id: connectedPeripheral.num, context: context) + if let connectedNode { + if node.num != connectedNode.num { + if UserDefaults.enableAdministration { + /// 2.5 Administration with session passkey + let expiration = node.sessionExpiration ?? Date() + if expiration < Date() || node.ambientLightingConfig == nil { + _ = bleManager.requestAmbientLightingConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + } + } else { + /// Legacy Administration + _ = bleManager.requestAmbientLightingConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + } + } } } } diff --git a/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift b/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift index b4ec0b63..5b94e8c3 100644 --- a/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift @@ -233,13 +233,24 @@ struct CannedMessagesConfig: View { ) } ) - .onAppear { + .onFirstAppear { // Need to request a CannedMessagesModuleConfig from the remote node before allowing changes - if bleManager.connectedPeripheral != nil && node?.cannedMessageConfig == nil { - Logger.mesh.info("empty canned messages module config") - let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context) - if node != nil && connectedNode != nil { - _ = bleManager.requestCannedMessagesModuleConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) + if let connectedPeripheral = bleManager.connectedPeripheral, let node { + Logger.mesh.info("empty canned message config") + let connectedNode = getNodeInfo(id: connectedPeripheral.num, context: context) + if let connectedNode { + if node.num != connectedNode.num { + if UserDefaults.enableAdministration && node.num != connectedNode.num { + /// 2.5 Administration with session passkey + let expiration = node.sessionExpiration ?? Date() + if expiration < Date() || node.cannedMessageConfig == nil { + _ = bleManager.requestCannedMessagesModuleConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + } + } else { + /// Legacy Administration + _ = bleManager.requestCannedMessagesModuleConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + } + } } } } diff --git a/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift b/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift index 1ff1ed86..e67898eb 100644 --- a/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift @@ -189,13 +189,24 @@ struct DetectionSensorConfig: View { ) } ) - .onAppear { - // Need to request a Detection Sensor Module Config from the remote node before allowing changes - if bleManager.connectedPeripheral != nil && node?.detectionSensorConfig == nil { - Logger.mesh.info("empty detection sensor module config") - let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context) - if node != nil && connectedNode != nil { - _ = bleManager.requestDetectionSensorModuleConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) + .onFirstAppear { + // Need to request a DetectionSensorModuleConfig from the remote node before allowing changes + if let connectedPeripheral = bleManager.connectedPeripheral, let node { + Logger.mesh.info("empty detection sensor config") + let connectedNode = getNodeInfo(id: connectedPeripheral.num, context: context) + if let connectedNode { + if node.num != connectedNode.num { + if UserDefaults.enableAdministration && node.num != connectedNode.num { + /// 2.5 Administration with session passkey + let expiration = node.sessionExpiration ?? Date() + if expiration < Date() || node.detectionSensorConfig == nil { + _ = bleManager.requestDetectionSensorModuleConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + } + } else { + /// Legacy Administration + _ = bleManager.requestDetectionSensorModuleConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + } + } } } } diff --git a/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift b/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift index 32cacbd3..57c3a672 100644 --- a/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift @@ -199,13 +199,24 @@ struct ExternalNotificationConfig: View { ) } ) - .onAppear { - // Need to request a TelemetryModuleConfig from the remote node before allowing changes - if bleManager.connectedPeripheral != nil && node?.externalNotificationConfig == nil { - Logger.mesh.info("empty external notification module config") - let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context) - if node != nil && connectedNode != nil { - _ = bleManager.requestExternalNotificationModuleConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) + .onFirstAppear { + // Need to request a ExternalNotificationModuleConfig from the remote node before allowing changes + if let connectedPeripheral = bleManager.connectedPeripheral, let node { + Logger.mesh.info("empty external notificaiton module config") + let connectedNode = getNodeInfo(id: connectedPeripheral.num, context: context) + if let connectedNode { + if node.num != connectedNode.num { + if UserDefaults.enableAdministration && node.num != connectedNode.num { + /// 2.5 Administration with session passkey + let expiration = node.sessionExpiration ?? Date() + if expiration < Date() || node.externalNotificationConfig == nil { + _ = bleManager.requestExternalNotificationModuleConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + } + } else { + /// Legacy Administration + _ = bleManager.requestExternalNotificationModuleConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + } + } } } } @@ -279,7 +290,7 @@ struct ExternalNotificationConfig: View { if newNagTimeout != node!.externalNotificationConfig!.nagTimeout { hasChanges = true } } } - .onChange(of: useI2SAsBuzzer) { + .onChange(of: useI2SAsBuzzer) { if let val = node?.externalNotificationConfig?.useI2SAsBuzzer { hasChanges = $0 != val } diff --git a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift index 8ef411ea..d6cbbf2b 100644 --- a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift @@ -347,13 +347,24 @@ struct MQTTConfig: View { if newMapPublishIntervalSecs != node!.mqttConfig!.mapPublishIntervalSecs { hasChanges = true } } } - .onAppear { - // Need to request a TelemetryModuleConfig from the remote node before allowing changes - if bleManager.connectedPeripheral != nil && node?.mqttConfig == nil { + .onFirstAppear { + // Need to request a MqttModuleConfig from the remote node before allowing changes + if let connectedPeripheral = bleManager.connectedPeripheral, let node { Logger.mesh.info("empty mqtt module config") - let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context) - if node != nil && connectedNode != nil { - _ = bleManager.requestMqttModuleConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) + let connectedNode = getNodeInfo(id: connectedPeripheral.num, context: context) + if let connectedNode { + if node.num != connectedNode.num { + if UserDefaults.enableAdministration && node.num != connectedNode.num { + /// 2.5 Administration with session passkey + let expiration = node.sessionExpiration ?? Date() + if expiration < Date() || node.mqttConfig == nil { + _ = bleManager.requestMqttModuleConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + } + } else { + /// Legacy Administration + _ = bleManager.requestMqttModuleConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + } + } } } } diff --git a/Meshtastic/Views/Settings/Config/Module/PaxCounterConfig.swift b/Meshtastic/Views/Settings/Config/Module/PaxCounterConfig.swift index 6e7bef08..1141bc7d 100644 --- a/Meshtastic/Views/Settings/Config/Module/PaxCounterConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/PaxCounterConfig.swift @@ -7,6 +7,7 @@ import MeshtasticProtobufs import SwiftUI +import OSLog struct PaxCounterConfig: View { @Environment(\.managedObjectContext) private var context @@ -57,12 +58,24 @@ struct PaxCounterConfig: View { name: "\(bleManager.connectedPeripheral?.shortName ?? "?")" ) }) - .onAppear { - // Need to request a PAX Counter module config from the remote node before allowing changes - if bleManager.connectedPeripheral != nil && node?.paxCounterConfig == nil { - let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? 0, context: context) - if node != nil && connectedNode != nil { - _ = bleManager.requestPaxCounterModuleConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) + .onFirstAppear { + // Need to request a PaxCounterModuleConfig from the remote node before allowing changes + if let connectedPeripheral = bleManager.connectedPeripheral, let node { + Logger.mesh.info("empty pax counter module config") + let connectedNode = getNodeInfo(id: connectedPeripheral.num, context: context) + if let connectedNode { + if node.num != connectedNode.num { + if UserDefaults.enableAdministration && node.num != connectedNode.num { + /// 2.5 Administration with session passkey + let expiration = node.sessionExpiration ?? Date() + if expiration < Date() || node.paxCounterConfig == nil { + _ = bleManager.requestPaxCounterModuleConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + } + } else { + /// Legacy Administration + _ = bleManager.requestPaxCounterModuleConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + } + } } } } diff --git a/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift b/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift index 001ad2b1..872294d8 100644 --- a/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift @@ -81,13 +81,24 @@ struct RangeTestConfig: View { ) } ) - .onAppear { - // Need to request a RangeTestModule Config from the remote node before allowing changes - if bleManager.connectedPeripheral != nil && node?.rangeTestConfig == nil { - Logger.mesh.debug("empty range test module config") - let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context) - if node != nil && connectedNode != nil { - _ = bleManager.requestRangeTestModuleConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) + .onFirstAppear { + // Need to request a RangeTestModuleConfig from the remote node before allowing changes + if let connectedPeripheral = bleManager.connectedPeripheral, let node { + Logger.mesh.info("empty range test module config") + let connectedNode = getNodeInfo(id: connectedPeripheral.num, context: context) + if let connectedNode { + if node.num != connectedNode.num { + if UserDefaults.enableAdministration && node.num != connectedNode.num { + /// 2.5 Administration with session passkey + let expiration = node.sessionExpiration ?? Date() + if expiration < Date() || node.rangeTestConfig == nil { + _ = bleManager.requestRangeTestModuleConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + } + } else { + /// Legacy Administration + _ = bleManager.requestRangeTestModuleConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + } + } } } } diff --git a/Meshtastic/Views/Settings/Config/Module/RtttlConfig.swift b/Meshtastic/Views/Settings/Config/Module/RtttlConfig.swift index 95f237a3..46b02848 100644 --- a/Meshtastic/Views/Settings/Config/Module/RtttlConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/RtttlConfig.swift @@ -6,6 +6,7 @@ // import SwiftUI +import OSLog struct RtttlConfig: View { @Environment(\.managedObjectContext) var context @@ -71,12 +72,24 @@ struct RtttlConfig: View { ) } ) - .onAppear { - // Need to request a Rtttl Config from the remote node before allowing changes - if bleManager.connectedPeripheral != nil && (node?.rtttlConfig == nil || node?.rtttlConfig?.ringtone?.count ?? 0 == 0) { - let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context) - if node != nil && connectedNode != nil { - _ = bleManager.requestRtttlConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) + .onFirstAppear { + // Need to request a RtttlConfig from the remote node before allowing changes + if let connectedPeripheral = bleManager.connectedPeripheral, let node { + Logger.mesh.info("empty range test module config") + let connectedNode = getNodeInfo(id: connectedPeripheral.num, context: context) + if let connectedNode { + if node.num != connectedNode.num { + if UserDefaults.enableAdministration && node.num != connectedNode.num { + /// 2.5 Administration with session passkey + let expiration = node.sessionExpiration ?? Date() + if expiration < Date() || node.rtttlConfig == nil { + _ = bleManager.requestRtttlConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + } + } else { + /// Legacy Administration + _ = bleManager.requestRtttlConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + } + } } } } diff --git a/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift b/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift index c7243a87..893ddca1 100644 --- a/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift @@ -136,20 +136,31 @@ struct SerialConfig: View { ) } ) - .onAppear { + .onFirstAppear { // Need to request a SerialModuleConfig from the remote node before allowing changes - if bleManager.connectedPeripheral != nil && node?.serialConfig == nil { - Logger.mesh.debug("empty serial module config") - let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context) - if node != nil && connectedNode != nil { - _ = bleManager.requestSerialModuleConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) + if let connectedPeripheral = bleManager.connectedPeripheral, let node { + Logger.mesh.info("empty serial module config") + let connectedNode = getNodeInfo(id: connectedPeripheral.num, context: context) + if let connectedNode { + if node.num != connectedNode.num { + if UserDefaults.enableAdministration && node.num != connectedNode.num { + /// 2.5 Administration with session passkey + let expiration = node.sessionExpiration ?? Date() + if expiration < Date() || node.serialConfig == nil { + _ = bleManager.requestSerialModuleConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + } + } else { + /// Legacy Administration + _ = bleManager.requestSerialModuleConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + } + } } } } .onChange(of: enabled) { if $0 != node?.serialConfig?.enabled { hasChanges = true } } - .onChange(of: echo) { + .onChange(of: echo) { if $0 != node?.serialConfig?.echo { hasChanges = true } } .onChange(of: rxd) { newRxd in diff --git a/Meshtastic/Views/Settings/Config/Module/StoreForwardConfig.swift b/Meshtastic/Views/Settings/Config/Module/StoreForwardConfig.swift index 4829a5fa..8fff8979 100644 --- a/Meshtastic/Views/Settings/Config/Module/StoreForwardConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/StoreForwardConfig.swift @@ -146,13 +146,24 @@ struct StoreForwardConfig: View { ) } ) - .onAppear { - // Need to request a Detection Sensor Module Config from the remote node before allowing changes - if bleManager.connectedPeripheral != nil && node?.storeForwardConfig == nil { - Logger.mesh.debug("empty store and forward module config") - let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context) - if node != nil && connectedNode != nil { - _ = bleManager.requestStoreAndForwardModuleConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) + .onFirstAppear { + // Need to request a StoreForwardModuleConfig from the remote node before allowing changes + if let connectedPeripheral = bleManager.connectedPeripheral, let node { + Logger.mesh.info("empty store & forward module config") + let connectedNode = getNodeInfo(id: connectedPeripheral.num, context: context) + if let connectedNode { + if node.num != connectedNode.num { + if UserDefaults.enableAdministration && node.num != connectedNode.num { + /// 2.5 Administration with session passkey + let expiration = node.sessionExpiration ?? Date() + if expiration < Date() || node.storeForwardConfig == nil { + _ = bleManager.requestStoreAndForwardModuleConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + } + } else { + /// Legacy Administration + _ = bleManager.requestStoreAndForwardModuleConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + } + } } } } diff --git a/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift b/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift index df811677..afef5727 100644 --- a/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift @@ -134,13 +134,24 @@ struct TelemetryConfig: View { ) } ) - .onAppear { + .onFirstAppear { // Need to request a TelemetryModuleConfig from the remote node before allowing changes - if bleManager.connectedPeripheral != nil && node?.telemetryConfig == nil { + if let connectedPeripheral = bleManager.connectedPeripheral, let node { Logger.mesh.info("empty telemetry module config") - let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context) - if node != nil && connectedNode != nil { - _ = bleManager.requestTelemetryModuleConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) + let connectedNode = getNodeInfo(id: connectedPeripheral.num, context: context) + if let connectedNode { + if node.num != connectedNode.num { + if UserDefaults.enableAdministration && node.num != connectedNode.num { + /// 2.5 Administration with session passkey + let expiration = node.sessionExpiration ?? Date() + if expiration < Date() || node.telemetryConfig == nil { + _ = bleManager.requestTelemetryModuleConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + } + } else { + /// Legacy Administration + _ = bleManager.requestTelemetryModuleConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + } + } } } } diff --git a/Meshtastic/Views/Settings/Config/NetworkConfig.swift b/Meshtastic/Views/Settings/Config/NetworkConfig.swift index dd767b6c..928bad57 100644 --- a/Meshtastic/Views/Settings/Config/NetworkConfig.swift +++ b/Meshtastic/Views/Settings/Config/NetworkConfig.swift @@ -134,15 +134,17 @@ struct NetworkConfig: View { Logger.mesh.info("empty network config") let connectedNode = getNodeInfo(id: connectedPeripheral.num, context: context) if let connectedNode { - if UserDefaults.enableAdministration { - /// 2.5 Administration with session passkey - let expiration = node.sessionExpiration ?? Date() - if expiration < Date() || node.networkConfig == nil { + if node.num != connectedNode.num { + if UserDefaults.enableAdministration { + /// 2.5 Administration with session passkey + let expiration = node.sessionExpiration ?? Date() + if expiration < Date() || node.networkConfig == nil { + _ = bleManager.requestNetworkConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + } + } else { + /// Legacy Administration _ = bleManager.requestNetworkConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) } - } else { - /// Legacy Administration - _ = bleManager.requestNetworkConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) } } } diff --git a/Meshtastic/Views/Settings/Config/PositionConfig.swift b/Meshtastic/Views/Settings/Config/PositionConfig.swift index 392486da..4497c1f7 100644 --- a/Meshtastic/Views/Settings/Config/PositionConfig.swift +++ b/Meshtastic/Views/Settings/Config/PositionConfig.swift @@ -383,15 +383,17 @@ struct PositionConfig: View { Logger.mesh.info("empty position config") let connectedNode = getNodeInfo(id: connectedPeripheral.num, context: context) if let connectedNode { - if UserDefaults.enableAdministration { - /// 2.5 Administration with session passkey - let expiration = node.sessionExpiration ?? Date() - if expiration < Date() || node.positionConfig == nil { + if node.num != connectedNode.num { + if UserDefaults.enableAdministration { + /// 2.5 Administration with session passkey + let expiration = node.sessionExpiration ?? Date() + if expiration < Date() || node.positionConfig == nil { + _ = bleManager.requestPositionConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + } + } else { + /// Legacy Administration _ = bleManager.requestPositionConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) } - } else { - /// Legacy Administration - _ = bleManager.requestPositionConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) } } } diff --git a/Meshtastic/Views/Settings/Config/PowerConfig.swift b/Meshtastic/Views/Settings/Config/PowerConfig.swift index cf213f8e..9ba0fe0a 100644 --- a/Meshtastic/Views/Settings/Config/PowerConfig.swift +++ b/Meshtastic/Views/Settings/Config/PowerConfig.swift @@ -118,17 +118,6 @@ struct PowerConfig: View { .font(.subheadline) } } - .onAppear { - - // Need to request a Power config from the remote node before allowing changes - if bleManager.connectedPeripheral != nil && node?.powerConfig == nil { - let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? 0, context: context) - if node != nil && connectedNode != nil { - _ = bleManager.requestPowerConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) - } - } - } - .onFirstAppear { Api().loadDeviceHardwareData { (hw) in for device in hw { @@ -144,15 +133,17 @@ struct PowerConfig: View { Logger.mesh.info("empty power config") let connectedNode = getNodeInfo(id: connectedPeripheral.num, context: context) if let connectedNode { - if UserDefaults.enableAdministration { - /// 2.5 Administration with session passkey - let expiration = node.sessionExpiration ?? Date() - if expiration < Date() || node.positionConfig == nil { - _ = bleManager.requestPositionConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + if node.num != connectedNode.num { + if UserDefaults.enableAdministration { + /// 2.5 Administration with session passkey + let expiration = node.sessionExpiration ?? Date() + if expiration < Date() || node.powerConfig == nil { + _ = bleManager.requestPowerConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + } + } else { + /// Legacy Administration + _ = bleManager.requestPowerConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) } - } else { - /// Legacy Administration - _ = bleManager.requestPowerConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) } } } diff --git a/Meshtastic/Views/Settings/Config/SecurityConfig.swift b/Meshtastic/Views/Settings/Config/SecurityConfig.swift index 6388ea6e..e58de058 100644 --- a/Meshtastic/Views/Settings/Config/SecurityConfig.swift +++ b/Meshtastic/Views/Settings/Config/SecurityConfig.swift @@ -153,16 +153,18 @@ struct SecurityConfig: View { Logger.mesh.info("empty security config") let connectedNode = getNodeInfo(id: connectedPeripheral.num, context: context) if let connectedNode { - if UserDefaults.enableAdministration { - /// 2.5 Administration with session passkey - let expiration = node.sessionExpiration ?? Date() - if expiration < Date() || node.securityConfig == nil { - _ = bleManager.requestSecurityConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) - } - } else { - if node.deviceConfig == nil { - /// Legacy Administration - _ = bleManager.requestSecurityConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + if node.num != connectedNode.num { + if UserDefaults.enableAdministration { + /// 2.5 Administration with session passkey + let expiration = node.sessionExpiration ?? Date() + if expiration < Date() || node.securityConfig == nil { + _ = bleManager.requestSecurityConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + } + } else { + if node.deviceConfig == nil { + /// Legacy Administration + _ = bleManager.requestSecurityConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + } } } } diff --git a/Meshtastic/Views/Settings/Settings.swift b/Meshtastic/Views/Settings/Settings.swift index e88816ab..481fb29d 100644 --- a/Meshtastic/Views/Settings/Settings.swift +++ b/Meshtastic/Views/Settings/Settings.swift @@ -346,7 +346,7 @@ struct Settings: View { if bleManager.connectedPeripheral != nil { Section("Configure") { if node?.canRemoteAdmin ?? false { - Picker("Configuring Node", selection: $selectedNode) { + Picker("Node", selection: $selectedNode) { if selectedNode == 0 { Text("Connect to a Node").tag(0) } @@ -365,6 +365,7 @@ struct Settings: View { } icon: { Image(systemName: "av.remote") } + .font(.caption2) .tag(Int(node.num)) } else if !UserDefaults.enableAdministration && node.metadata != nil { /// Nodes using the old admin system Label { @@ -390,8 +391,7 @@ struct Settings: View { } } } - .pickerStyle(.automatic) - .labelsHidden() + .pickerStyle(.navigationLink) .onChange(of: selectedNode) { newValue in if selectedNode > 0 { let node = nodes.first(where: { $0.num == newValue }) From d8cb04ddcc7dd1aee7ddc1f0cdf1c9cb25c1d1cc Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Thu, 5 Sep 2024 19:38:28 -0700 Subject: [PATCH 38/42] Remove json logging --- Meshtastic/Helpers/BLEManager.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 11597647..87551c4f 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -766,7 +766,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate case .waypointApp: waypointPacket(packet: decodedInfo.packet, context: context) case .nodeinfoApp: - MeshLogger.log("πŸ•ΈοΈ MESH PACKET received for Node Info App for PKI LOCK debugging \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") if !invalidVersion { upsertNodeInfoPacket(packet: decodedInfo.packet, context: context) } case .routingApp: if !invalidVersion { routingPacket(packet: decodedInfo.packet, connectedNodeNum: self.connectedPeripheral.num, context: context) } From 8954b21faa15b212e66350621e51f12eb89f428b Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Fri, 6 Sep 2024 09:21:40 -0700 Subject: [PATCH 39/42] stop unwrapping nuilable positon --- Meshtastic/Views/Nodes/Helpers/Map/NodeMapSwiftUI.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Meshtastic/Views/Nodes/Helpers/Map/NodeMapSwiftUI.swift b/Meshtastic/Views/Nodes/Helpers/Map/NodeMapSwiftUI.swift index 7906f8b5..14df3986 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/NodeMapSwiftUI.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/NodeMapSwiftUI.swift @@ -130,7 +130,9 @@ struct NodeMapSwiftUI: View { if node.positions?.count ?? 0 > 1 { position = .automatic } else { - position = .camera(MapCamera(centerCoordinate: mostRecent!.coordinate, distance: 8000, heading: 0, pitch: 60)) + if let mr = mostRecent?.coordinate { + position = .camera(MapCamera(centerCoordinate: mr.coordinate, distance: 8000, heading: 0, pitch: 60)) + } } if self.scene == nil { Task { From 10751e374cffe21ae9f2c574ea08117ea2cbcd10 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Fri, 6 Sep 2024 09:23:54 -0700 Subject: [PATCH 40/42] Dont be dumb --- Meshtastic.xcodeproj/project.pbxproj | 8 ++++---- Meshtastic/Views/Nodes/Helpers/Map/NodeMapSwiftUI.swift | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 43f27393..1e63593b 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -1686,7 +1686,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.5.3; + MARKETING_VERSION = 2.5.4; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; @@ -1721,7 +1721,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.5.3; + MARKETING_VERSION = 2.5.4; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; @@ -1753,7 +1753,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.5.3; + MARKETING_VERSION = 2.5.4; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1786,7 +1786,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.5.3; + MARKETING_VERSION = 2.5.4; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/Meshtastic/Views/Nodes/Helpers/Map/NodeMapSwiftUI.swift b/Meshtastic/Views/Nodes/Helpers/Map/NodeMapSwiftUI.swift index 14df3986..340117ac 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/NodeMapSwiftUI.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/NodeMapSwiftUI.swift @@ -130,8 +130,8 @@ struct NodeMapSwiftUI: View { if node.positions?.count ?? 0 > 1 { position = .automatic } else { - if let mr = mostRecent?.coordinate { - position = .camera(MapCamera(centerCoordinate: mr.coordinate, distance: 8000, heading: 0, pitch: 60)) + if let mrCoord = mostRecent?.coordinate { + position = .camera(MapCamera(centerCoordinate: mrCoord, distance: 8000, heading: 0, pitch: 60)) } } if self.scene == nil { From 4f813c12d335db38de58dbdc6e600e2290ea7eab Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Fri, 6 Sep 2024 17:05:25 -0700 Subject: [PATCH 41/42] Clean up precision ranges for locations on the map report and public key --- Meshtastic/Views/Messages/UserList.swift | 46 +++++++++++++----- Meshtastic/Views/Settings/Channels.swift | 6 +-- .../Views/Settings/Channels/ChannelForm.swift | 2 +- .../Settings/Config/Module/MQTTConfig.swift | 48 ++++++------------- 4 files changed, 52 insertions(+), 50 deletions(-) diff --git a/Meshtastic/Views/Messages/UserList.swift b/Meshtastic/Views/Messages/UserList.swift index 4e09859f..4db57180 100644 --- a/Meshtastic/Views/Messages/UserList.swift +++ b/Meshtastic/Views/Messages/UserList.swift @@ -35,7 +35,6 @@ struct UserList: View { var boolFilters: [Bool] {[ isFavorite, isOnline, - isPkiEncrypted, isEnvironment, distanceFilter, roleFilter @@ -45,11 +44,12 @@ struct UserList: View { sortDescriptors: [NSSortDescriptor(key: "lastMessage", ascending: false), NSSortDescriptor(key: "userNode.favorite", ascending: false), NSSortDescriptor(key: "pkiEncrypted", ascending: false), + NSSortDescriptor(key: "userNode.lastHeard", ascending: false), NSSortDescriptor(key: "longName", ascending: true)], - predicate: NSPredicate(format: "hwModelId != nil"), + predicate: NSPredicate(format: "longName != ''"), animation: .default ) - private var users: FetchedResults + var users: FetchedResults @Binding var node: NodeInfoEntity? @Binding var userSelection: UserEntity? @@ -202,34 +202,55 @@ struct UserList: View { DirectMessagesHelp() } .onChange(of: searchText) { _ in - searchUserList() + Task { + await searchUserList() + } } .onChange(of: viaLora) { _ in if !viaLora && !viaMqtt { viaMqtt = true } - searchUserList() + Task { + await searchUserList() + } } .onChange(of: viaMqtt) { _ in if !viaLora && !viaMqtt { viaLora = true } - searchUserList() + Task { + await searchUserList() + } } .onChange(of: [deviceRoles]) { _ in - searchUserList() + Task { + await searchUserList() + } } .onChange(of: hopsAway) { _ in - searchUserList() + Task { + await searchUserList() + } } .onChange(of: [boolFilters]) { _ in - searchUserList() + Task { + await searchUserList() + } } .onChange(of: maxDistance) { _ in - searchUserList() + Task { + await searchUserList() + } + } + .onChange(of: isPkiEncrypted) { _ in + Task { + await searchUserList() + } } .onAppear { - searchUserList() + Task { + await searchUserList() + } } .safeAreaInset(edge: .bottom, alignment: .leading) { HStack { @@ -267,8 +288,7 @@ struct UserList: View { .scrollDismissesKeyboard(.immediately) } } - - private func searchUserList() { + private func searchUserList() async { /// Case Insensitive Search Text Predicates let searchPredicates = ["userId", "numString", "hwModel", "hwDisplayName", "longName", "shortName"].map { property in diff --git a/Meshtastic/Views/Settings/Channels.swift b/Meshtastic/Views/Settings/Channels.swift index f039534c..ca55b95d 100644 --- a/Meshtastic/Views/Settings/Channels.swift +++ b/Meshtastic/Views/Settings/Channels.swift @@ -93,7 +93,7 @@ struct Channels: View { preciseLocation = true positionsEnabled = true if channelKey == "AQ==" { - positionPrecision = 13 + positionPrecision = 14 preciseLocation = false } } else if !supportedVersion && channelRole == 2 { @@ -103,8 +103,8 @@ struct Channels: View { } else { if channelKey == "AQ==" { preciseLocation = false - if (positionPrecision > 0 && positionPrecision < 10) || positionPrecision > 16 { - positionPrecision = 13 + if (positionPrecision > 0 && positionPrecision < 11) || positionPrecision > 14 { + positionPrecision = 14 } } else if positionPrecision == 32 { preciseLocation = true diff --git a/Meshtastic/Views/Settings/Channels/ChannelForm.swift b/Meshtastic/Views/Settings/Channels/ChannelForm.swift index d21e0f42..51cb8dfc 100644 --- a/Meshtastic/Views/Settings/Channels/ChannelForm.swift +++ b/Meshtastic/Views/Settings/Channels/ChannelForm.swift @@ -158,7 +158,7 @@ struct ChannelForm: View { VStack(alignment: .leading) { Label("Approximate Location", systemImage: "location.slash.circle.fill") - Slider(value: $positionPrecision, in: 10...16, step: 1) { + Slider(value: $positionPrecision, in: 11...14, step: 1) { } minimumValueLabel: { Image(systemName: "minus") } maximumValueLabel: { diff --git a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift index d6cbbf2b..0caf3625 100644 --- a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift @@ -32,7 +32,6 @@ struct MQTTConfig: View { @State var nearbyTopics = [String]() @State var mapReportingEnabled = false @State var mapPublishIntervalSecs = 3600 - @State var preciseLocation: Bool = false @State var mapPositionPrecision: Double = 13.0 let locale = Locale.current @@ -105,35 +104,17 @@ struct MQTTConfig: View { } } .pickerStyle(DefaultPickerStyle()) - VStack(alignment: .leading) { - Toggle(isOn: $preciseLocation) { - Label("Precise Location", systemImage: "scope") - } - .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - .listRowSeparator(.visible) - .onChange(of: preciseLocation) { pl in - if pl == false { - mapPositionPrecision = 12 - } else { - mapPositionPrecision = 32 - } - } - } - - if !preciseLocation { - VStack(alignment: .leading) { - Label("Approximate Location", systemImage: "location.slash.circle.fill") - Slider(value: $mapPositionPrecision, in: 11...16, step: 1) { - } minimumValueLabel: { - Image(systemName: "minus") - } maximumValueLabel: { - Image(systemName: "plus") - } - Text(PositionPrecision(rawValue: Int(mapPositionPrecision))?.description ?? "") - .foregroundColor(.gray) - .font(.callout) + Label("Approximate Location", systemImage: "location.slash.circle.fill") + Slider(value: $mapPositionPrecision, in: 11...14, step: 1) { + } minimumValueLabel: { + Image(systemName: "minus") + } maximumValueLabel: { + Image(systemName: "plus") } + Text(PositionPrecision(rawValue: Int(mapPositionPrecision))?.description ?? "") + .foregroundColor(.gray) + .font(.callout) } } } @@ -429,11 +410,12 @@ struct MQTTConfig: View { self.mqttConnected = bleManager.mqttProxyConnected self.mapReportingEnabled = node?.mqttConfig?.mapReportingEnabled ?? false self.mapPublishIntervalSecs = Int(node?.mqttConfig?.mapPublishIntervalSecs ?? 3600) - self.mapPositionPrecision = Double(node?.mqttConfig?.mapPositionPrecision ?? 12) - if mapPositionPrecision == 0.0 { - self.mapPositionPrecision = 12 + self.mapPositionPrecision = Double(node?.mqttConfig?.mapPositionPrecision ?? 14) + if mapPositionPrecision < 11 || mapPositionPrecision > 14 { + self.mapPositionPrecision = 14 + self.hasChanges = true + } else { + self.hasChanges = false } - self.preciseLocation = mapPositionPrecision == 32 - self.hasChanges = false } } From adab69dab7adcdbb19f7e117e3669d724e9cf3e7 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 8 Sep 2024 11:10:58 -0700 Subject: [PATCH 42/42] Default precision of 14 --- Meshtastic/Views/Settings/Channels/ChannelForm.swift | 6 +++--- Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Meshtastic/Views/Settings/Channels/ChannelForm.swift b/Meshtastic/Views/Settings/Channels/ChannelForm.swift index 51cb8dfc..e4930b7a 100644 --- a/Meshtastic/Views/Settings/Channels/ChannelForm.swift +++ b/Meshtastic/Views/Settings/Channels/ChannelForm.swift @@ -149,7 +149,7 @@ struct ChannelForm: View { .listRowSeparator(.visible) .onChange(of: preciseLocation) { pl in if pl == false { - positionPrecision = 13 + positionPrecision = 14 } } } @@ -220,7 +220,7 @@ struct ChannelForm: View { } positionPrecision = 32 } else { - positionPrecision = 13 + positionPrecision = 14 } hasChanges = true } @@ -230,7 +230,7 @@ struct ChannelForm: View { .onChange(of: positionsEnabled) { pe in if pe { if positionPrecision == 0 { - positionPrecision = 13 + positionPrecision = 14 } } else { positionPrecision = 0 diff --git a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift index 0caf3625..1996c744 100644 --- a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift @@ -32,7 +32,7 @@ struct MQTTConfig: View { @State var nearbyTopics = [String]() @State var mapReportingEnabled = false @State var mapPublishIntervalSecs = 3600 - @State var mapPositionPrecision: Double = 13.0 + @State var mapPositionPrecision: Double = 14.0 let locale = Locale.current