From 3ff173d0a02ebd5013c89473968e7ce5b1af0dea Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 22 Sep 2024 23:31:26 -0700 Subject: [PATCH 01/19] Trace route mockup --- Localizable.xcstrings | 3 + Meshtastic.xcodeproj/project.pbxproj | 16 +- .../Meshtastic.xcdatamodeld/.xccurrentversion | 2 +- .../contents | 3 + .../contents | 476 ++++++++++++++++++ Meshtastic/Views/Layouts/TraceRoute.swift | 68 +++ Meshtastic/Views/Messages/MessageText.swift | 2 +- Meshtastic/Views/Nodes/TraceRouteLog.swift | 68 ++- 8 files changed, 627 insertions(+), 11 deletions(-) create mode 100644 Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 45.xcdatamodel/contents create mode 100644 Meshtastic/Views/Layouts/TraceRoute.swift diff --git a/Localizable.xcstrings b/Localizable.xcstrings index 9650f2ca9..3062e3365 100644 --- a/Localizable.xcstrings +++ b/Localizable.xcstrings @@ -15,6 +15,9 @@ }, " Whether or not use INPUT_PULLUP mode for GPIO pin. Only applicable if the board uses pull-up resistors on the pin" : { + }, + "-12dB" : { + }, ": %@" : { diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 353226be4..c8a3006f5 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -100,6 +100,7 @@ DD6193752862F6E600E59241 /* ExternalNotificationConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6193742862F6E600E59241 /* ExternalNotificationConfig.swift */; }; DD6193772862F90F00E59241 /* CannedMessagesConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6193762862F90F00E59241 /* CannedMessagesConfig.swift */; }; DD6193792863875F00E59241 /* SerialConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6193782863875F00E59241 /* SerialConfig.swift */; }; + DD6D5A332CA1178300ED3032 /* TraceRoute.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6D5A322CA1178300ED3032 /* TraceRoute.swift */; }; DD6F65722C6AB8EC0053C113 /* SecureInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6F65712C6AB8EC0053C113 /* SecureInput.swift */; }; DD6F65742C6CB80A0053C113 /* View.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6F65732C6CB80A0053C113 /* View.swift */; }; DD6F65762C6EA5490053C113 /* AckErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6F65752C6EA5490053C113 /* AckErrors.swift */; }; @@ -365,6 +366,8 @@ DD6193762862F90F00E59241 /* CannedMessagesConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CannedMessagesConfig.swift; sourceTree = ""; }; DD6193782863875F00E59241 /* SerialConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SerialConfig.swift; sourceTree = ""; }; DD68BAE72C417A74004C01A0 /* MeshtasticDataModelV 40.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 40.xcdatamodel"; sourceTree = ""; }; + DD6D5A322CA1178300ED3032 /* TraceRoute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TraceRoute.swift; sourceTree = ""; }; + DD6D5A342CA13BA600ED3032 /* MeshtasticDataModelV 45.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 45.xcdatamodel"; sourceTree = ""; }; DD6F65712C6AB8EC0053C113 /* SecureInput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureInput.swift; sourceTree = ""; }; DD6F65732C6CB80A0053C113 /* View.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = View.swift; sourceTree = ""; }; DD6F65752C6EA5490053C113 /* AckErrors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AckErrors.swift; sourceTree = ""; }; @@ -747,6 +750,14 @@ path = Module; sourceTree = ""; }; + DD6D5A312CA1176A00ED3032 /* Layouts */ = { + isa = PBXGroup; + children = ( + DD6D5A322CA1178300ED3032 /* TraceRoute.swift */, + ); + path = Layouts; + sourceTree = ""; + }; DD6F65772C6EAB860053C113 /* Help */ = { isa = PBXGroup; children = ( @@ -901,6 +912,7 @@ DDC2E18726CE24E40042C5E4 /* Views */ = { isa = PBXGroup; children = ( + DD6D5A312CA1176A00ED3032 /* Layouts */, C9483F6B2773016700998F6B /* MapKitMap */, DDC2E18D26CE25CB0042C5E4 /* Helpers */, DD47E3D726F2F21A00029299 /* Bluetooth */, @@ -1433,6 +1445,7 @@ DD6F65742C6CB80A0053C113 /* View.swift in Sources */, DD1933762B0835D500771CD5 /* PositionAltitudeChart.swift in Sources */, DD415828285859C4009B0E59 /* TelemetryConfig.swift in Sources */, + DD6D5A332CA1178300ED3032 /* TraceRoute.swift in Sources */, DDB6CCFB2AAF805100945AF6 /* NodeMapSwiftUI.swift in Sources */, BCB613872C69A0FB00485544 /* AppIntentErrors.swift in Sources */, DD73FD1128750779000852D6 /* PositionLog.swift in Sources */, @@ -1899,6 +1912,7 @@ DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */ = { isa = XCVersionGroup; children = ( + DD6D5A342CA13BA600ED3032 /* MeshtasticDataModelV 45.xcdatamodel */, DD7CF8DA2C93663C008BD10E /* MeshtasticDataModelV 44.xcdatamodel */, DD7E235F2C7AA3E50078ACDF /* MeshtasticDataModelV 43.xcdatamodel */, DD1BD0F12C61D3AD008C0C70 /* MeshtasticDataModelV 42.xcdatamodel */, @@ -1944,7 +1958,7 @@ DD5D0A9A2931AD6B00F7EA61 /* MeshtasticDataModelV2.xcdatamodel */, DD3CC6BB28E366DF00FA9159 /* MeshtasticDataModel.xcdatamodel */, ); - currentVersion = DD7CF8DA2C93663C008BD10E /* MeshtasticDataModelV 44.xcdatamodel */; + currentVersion = DD6D5A342CA13BA600ED3032 /* MeshtasticDataModelV 45.xcdatamodel */; name = Meshtastic.xcdatamodeld; path = Meshtastic/Meshtastic.xcdatamodeld; sourceTree = ""; diff --git a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion index 8a88003bd..4269da215 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion +++ b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion @@ -3,6 +3,6 @@ _XCCurrentVersionName - MeshtasticDataModelV 44.xcdatamodel + MeshtasticDataModelV 45.xcdatamodel diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 44.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 44.xcdatamodel/contents index 98de03479..ae7c08e44 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 44.xcdatamodel/contents +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 44.xcdatamodel/contents @@ -428,11 +428,14 @@ + + + diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 45.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 45.xcdatamodel/contents new file mode 100644 index 000000000..98de03479 --- /dev/null +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 45.xcdatamodel/contents @@ -0,0 +1,476 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Meshtastic/Views/Layouts/TraceRoute.swift b/Meshtastic/Views/Layouts/TraceRoute.swift new file mode 100644 index 000000000..bb79f7474 --- /dev/null +++ b/Meshtastic/Views/Layouts/TraceRoute.swift @@ -0,0 +1,68 @@ +// +// TraceRoute.swift +// Meshtastic +// +// Created by Garth Vander Houwen on 9/22/24. +// +import SwiftUI + +struct Rotation: LayoutValueKey { + static let defaultValue: Binding? = nil +} + +struct TraceRouteComponent: View { + var animation: Animation? + @ViewBuilder let content: () -> V + @State private var rotation: Angle = .zero + + var body: some View { + content() + .rotationEffect(rotation) + .layoutValue(key: Rotation.self, value: $rotation.animation(animation)) + } +} + +struct TraceRoute: Layout { + var animatableData: AnimatablePair { + get { + AnimatablePair(rotation.radians, radius) + } + set { + rotation = Angle.radians(newValue.first) + radius = newValue.second + } + } + + var radius: CGFloat + var rotation: Angle + + func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize { + let maxSize = subviews.map { $0.sizeThatFits(proposal) }.reduce(CGSize.zero) { + return CGSize(width: max($0.width, $1.width), height: max($0.height, $1.height)) + } + return CGSize(width: (maxSize.width / 2 + radius) * 2, + height: (maxSize.height / 2 + radius) * 2) + } + + func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) { + let angleStep = (Angle.degrees(360).radians / Double(subviews.count)) + + for (index, subview) in subviews.enumerated() { + let angle = angleStep * CGFloat(index) + rotation.radians + + var point = CGPoint(x: 0, y: -radius).applying(CGAffineTransform(rotationAngle: angle)) + point.x += bounds.midX + point.y += bounds.midY + + subview.place(at: point, anchor: .center, proposal: .unspecified) + + DispatchQueue.main.async { + if index % 2 == 0 { + subview[Rotation.self]?.wrappedValue = .zero + } else { + subview[Rotation.self]?.wrappedValue = .radians(angle) + } + } + } + } +} diff --git a/Meshtastic/Views/Messages/MessageText.swift b/Meshtastic/Views/Messages/MessageText.swift index 1f2cc0172..25347c25b 100644 --- a/Meshtastic/Views/Messages/MessageText.swift +++ b/Meshtastic/Views/Messages/MessageText.swift @@ -30,7 +30,7 @@ struct MessageText: View { .background(isCurrentUser ? .accentColor : Color(.gray)) .cornerRadius(15) .overlay { - if message.pkiEncrypted && message.ackError == 0 && message.realACK { + if message.pkiEncrypted && message.ackError <= 0 && (message.realACK || message.ackError == nil) { VStack(alignment: .trailing) { Spacer() HStack { diff --git a/Meshtastic/Views/Nodes/TraceRouteLog.swift b/Meshtastic/Views/Nodes/TraceRouteLog.swift index 345a42991..0f947ecfb 100644 --- a/Meshtastic/Views/Nodes/TraceRouteLog.swift +++ b/Meshtastic/Views/Nodes/TraceRouteLog.swift @@ -15,7 +15,6 @@ struct TraceRouteLog: View { @ObservedObject var locationsHandler = LocationsHandler.shared @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager - @State private var isPresentingClearLogConfirm: Bool = false @State var isExporting = false @State var exportString = "" @@ -26,13 +25,21 @@ struct TraceRouteLog: View { @State var mapStyle: MapStyle = MapStyle.standard(elevation: .realistic, emphasis: MapStyle.StandardEmphasis.muted, pointsOfInterest: .all, showsTraffic: true) @State var position = MapCameraPosition.automatic let distanceFormatter = MKDistanceFormatter() + /// Mockup Values + let colors: [Color] = [.yellow, .orange, .red, .pink, .purple, .blue, .cyan, .green] + let nums: [Int64] = [366311664, 0, 3662955168, 0, 3663982804, 0, 4202719792, 0, 603700594, 0, 836212501, 0, 3663116644, 0, 8362955168] + let snr: [Double] = [-115.00, 17.5, 7.0, 8.9, -24.0, 5.5, 6.0, 7.5] + @State private var hops: Int = 16 /// Max of 16 (2 8 hop routes) + /// State for the circle of routes + @State var angle: Angle = .zero + @State var radius: CGFloat = 230.00 + @State var animation: Animation? var body: some View { HStack(alignment: .top) { VStack { VStack { List(node.traceRoutes?.reversed() as? [TraceRouteEntity] ?? [], id: \.self, selection: $selectedRoute) { route in - Label { Text("\(route.time?.formatted() ?? "unknown".localized) - \(route.response ? (route.hops?.count == 0 && route.response ? "Direct" : "\(route.hops?.count ?? 0) \(route.hops?.count ?? 0 == 1 ? "Hop": "Hops")") : "No Response")") } icon: { @@ -45,8 +52,33 @@ struct TraceRouteLog: View { .frame(minHeight: 200, maxHeight: 230) VStack { if selectedRoute != nil { + if true {// selectedRoute?.hops?.count ?? 2 > 3 { + VStack { + Spacer() + HStack(spacing: 15) { + TraceRoute(radius: radius, rotation: angle) { + contents() + } + } + .onAppear { + // Set the view rotation animation after the view appeared, + // to avoid animating initial rotation + DispatchQueue.main.async { + animation = .easeInOut(duration: 1.0) + withAnimation(.easeInOut(duration: 2.0)) { + angle = (angle == .degrees(-90) ? .degrees(-90) : .degrees(-90)) + } + } + } + .onTapGesture { + withAnimation(.easeInOut(duration: 2.0)) { + angle = (angle == .degrees(-90) ? .degrees(90) : .degrees(-90)) + } + } + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + } if selectedRoute?.response ?? false && selectedRoute?.hops?.count ?? 0 > 0 { - Label { Text("Route: \(selectedRoute?.routeText ?? "unknown".localized)") } icon: { @@ -94,8 +126,6 @@ struct TraceRouteLog: View { MapPolyline(coordinates: traceRouteCoords) .stroke(.blue, style: dashed) } - } else if selectedRoute?.hops?.count ?? 0 == 0 { - } } .frame(maxWidth: .infinity, maxHeight: .infinity) @@ -104,10 +134,10 @@ struct TraceRouteLog: View { /// Distance if selectedRoute?.node?.positions?.count ?? 0 > 0, selectedRoute?.coordinate != nil, - let mostRecent = selectedRoute?.node?.positions?.lastObject as? PositionEntity { - + let mostRecent = selectedRoute?.node?.positions?.lastObject as? PositionEntity { + let startPoint = CLLocation(latitude: selectedRoute?.coordinate?.latitude ?? LocationsHandler.DefaultLocation.latitude, longitude: selectedRoute?.coordinate?.longitude ?? LocationsHandler.DefaultLocation.longitude) - + if startPoint.distance(from: CLLocation(latitude: LocationsHandler.DefaultLocation.latitude, longitude: LocationsHandler.DefaultLocation.longitude)) > 0.0 { let metersAway = selectedRoute?.coordinate?.distance(from: CLLocationCoordinate2D(latitude: mostRecent.latitude ?? LocationsHandler.DefaultLocation.latitude, longitude: mostRecent.longitude ?? LocationsHandler.DefaultLocation.longitude)) Label { @@ -145,4 +175,26 @@ struct TraceRouteLog: View { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") }) } + @ViewBuilder func contents(animation: Animation? = nil) -> some View { + ForEach(0.. 0 { + Text("-12dB") + .font(.caption) + .foregroundColor(colors[idx%colors.count].opacity(0.7)) + } + } + } else { + Image(systemName: "arrowshape.right.fill") + .resizable() + .frame(width: 35, height: 35) + .foregroundColor(colors[idx%colors.count].opacity(0.7)) + } + } + } + } } From 8876babd55ece1d7c6015b4dd7ac7b39aacaf274 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 22 Sep 2024 23:32:11 -0700 Subject: [PATCH 02/19] Bump version --- Meshtastic.xcodeproj/project.pbxproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index c8a3006f5..354615880 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -1700,7 +1700,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.5.6; + MARKETING_VERSION = 2.5.7; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; @@ -1735,7 +1735,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.5.6; + MARKETING_VERSION = 2.5.7; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; @@ -1767,7 +1767,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.5.6; + MARKETING_VERSION = 2.5.7; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1800,7 +1800,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.5.6; + MARKETING_VERSION = 2.5.7; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; From 1de64e6659167b7bc26275bbfb465703b18af9f7 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Mon, 23 Sep 2024 10:46:38 -0700 Subject: [PATCH 03/19] Move wheel of traceroutes into details --- Localizable.xcstrings | 18 ++----- Meshtastic/Views/Nodes/TraceRouteLog.swift | 56 +++++++++++----------- 2 files changed, 30 insertions(+), 44 deletions(-) diff --git a/Localizable.xcstrings b/Localizable.xcstrings index 3062e3365..391fdce44 100644 --- a/Localizable.xcstrings +++ b/Localizable.xcstrings @@ -24,21 +24,6 @@ }, ": %d" : { - }, - ".dot" : { - - }, - ".gauge" : { - - }, - ".gradient" : { - - }, - ".pill" : { - - }, - ".text" : { - }, "(Re)define PIN_GPS_EN for your board." : { @@ -19142,6 +19127,9 @@ }, "Send" : { + }, + "Send ${messageContent} to ${channelNumber}" : { + }, "Send a Group Message" : { diff --git a/Meshtastic/Views/Nodes/TraceRouteLog.swift b/Meshtastic/Views/Nodes/TraceRouteLog.swift index 0f947ecfb..9498f3e6e 100644 --- a/Meshtastic/Views/Nodes/TraceRouteLog.swift +++ b/Meshtastic/Views/Nodes/TraceRouteLog.swift @@ -32,7 +32,7 @@ struct TraceRouteLog: View { @State private var hops: Int = 16 /// Max of 16 (2 8 hop routes) /// State for the circle of routes @State var angle: Angle = .zero - @State var radius: CGFloat = 230.00 + @State var radius: CGFloat = 175.00 @State var animation: Animation? var body: some View { @@ -52,32 +52,6 @@ struct TraceRouteLog: View { .frame(minHeight: 200, maxHeight: 230) VStack { if selectedRoute != nil { - if true {// selectedRoute?.hops?.count ?? 2 > 3 { - VStack { - Spacer() - HStack(spacing: 15) { - TraceRoute(radius: radius, rotation: angle) { - contents() - } - } - .onAppear { - // Set the view rotation animation after the view appeared, - // to avoid animating initial rotation - DispatchQueue.main.async { - animation = .easeInOut(duration: 1.0) - withAnimation(.easeInOut(duration: 2.0)) { - angle = (angle == .degrees(-90) ? .degrees(-90) : .degrees(-90)) - } - } - } - .onTapGesture { - withAnimation(.easeInOut(duration: 2.0)) { - angle = (angle == .degrees(-90) ? .degrees(90) : .degrees(-90)) - } - } - } - .frame(maxWidth: .infinity, maxHeight: .infinity) - } if selectedRoute?.response ?? false && selectedRoute?.hops?.count ?? 0 > 0 { Label { Text("Route: \(selectedRoute?.routeText ?? "unknown".localized)") @@ -135,9 +109,7 @@ struct TraceRouteLog: View { if selectedRoute?.node?.positions?.count ?? 0 > 0, selectedRoute?.coordinate != nil, let mostRecent = selectedRoute?.node?.positions?.lastObject as? PositionEntity { - let startPoint = CLLocation(latitude: selectedRoute?.coordinate?.latitude ?? LocationsHandler.DefaultLocation.latitude, longitude: selectedRoute?.coordinate?.longitude ?? LocationsHandler.DefaultLocation.longitude) - if startPoint.distance(from: CLLocation(latitude: LocationsHandler.DefaultLocation.latitude, longitude: LocationsHandler.DefaultLocation.longitude)) > 0.0 { let metersAway = selectedRoute?.coordinate?.distance(from: CLLocationCoordinate2D(latitude: mostRecent.latitude ?? LocationsHandler.DefaultLocation.latitude, longitude: mostRecent.longitude ?? LocationsHandler.DefaultLocation.longitude)) Label { @@ -162,6 +134,32 @@ struct TraceRouteLog: View { Spacer() } } + if true {// selectedRoute?.hops?.count ?? 2 > 3 { + VStack { + Spacer() + HStack(spacing: 15) { + TraceRoute(radius: radius, rotation: angle) { + contents() + } + } + .onAppear { + // Set the view rotation animation after the view appeared, + // to avoid animating initial rotation + DispatchQueue.main.async { + animation = .easeInOut(duration: 1.0) + withAnimation(.easeInOut(duration: 2.0)) { + angle = (angle == .degrees(-90) ? .degrees(-90) : .degrees(-90)) + } + } + } + .onTapGesture { + withAnimation(.easeInOut(duration: 2.0)) { + angle = (angle == .degrees(-90) ? .degrees(90) : .degrees(-90)) + } + } + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + } } else { ContentUnavailableView("Select a Trace Route", systemImage: "signpost.right.and.left") } From 78903f442ab43bfed5f0085291fc5a5ef9123a7c Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 24 Sep 2024 16:04:14 -0700 Subject: [PATCH 04/19] Wheel of traceroute --- Localizable.xcstrings | 14 +- Meshtastic/Helpers/BLEManager.swift | 36 +++- .../contents | 4 +- Meshtastic/Views/Nodes/TraceRouteLog.swift | 198 +++++++++--------- 4 files changed, 143 insertions(+), 109 deletions(-) diff --git a/Localizable.xcstrings b/Localizable.xcstrings index 391fdce44..958098694 100644 --- a/Localizable.xcstrings +++ b/Localizable.xcstrings @@ -15,9 +15,6 @@ }, " Whether or not use INPUT_PULLUP mode for GPIO pin. Only applicable if the board uses pull-up resistors on the pin" : { - }, - "-12dB" : { - }, ": %@" : { @@ -21859,8 +21856,15 @@ "Trace Route Log" : { }, - "Trace route received directly by %@" : { - + "Trace route received directly by %@ with a SNR of %@ dB" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Trace route received directly by %1$@ with a SNR of %2$@ dB" + } + } + } }, "Trace Route Sent" : { diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index aae1a8ab6..63cf18839 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -825,7 +825,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate // MeshLogger.log("πŸ•ΈοΈ MESH PACKET received for Audio App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") MeshLogger.log("πŸ•ΈοΈ MESH PACKET received for Audio App UNHANDLED UNHANDLED") case .tracerouteApp: - if let routingMessage = try? RouteDiscovery(serializedData: decodedInfo.packet.decoded.payload) { + if let routingMessage = try? RouteDiscovery(serializedBytes: decodedInfo.packet.decoded.payload) { let traceRoute = getTraceRoute(id: Int64(decodedInfo.packet.decoded.requestID), context: context) traceRoute?.response = true traceRoute?.route = routingMessage.route @@ -836,13 +836,45 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } else { var routeString = "You --> " var hopNodes: [TraceRouteHopEntity] = [] - for node in routingMessage.route { + for (index, node) in routingMessage.route.enumerated() { var hopNode = getNodeInfo(id: Int64(node), context: context) if hopNode == nil && hopNode?.num ?? 0 > 0 && node != 4294967295 { hopNode = createNodeInfo(num: Int64(node), context: context) } let traceRouteHop = TraceRouteHopEntity(context: context) traceRouteHop.time = Date() + traceRouteHop.snr = Float(routingMessage.snrTowards[index] / 4) + if hopNode?.hasPositions ?? false { + traceRoute?.hasPositions = true + if let mostRecent = hopNode?.positions?.lastObject as? PositionEntity, mostRecent.time! >= Calendar.current.date(byAdding: .minute, value: -60, to: Date())! { + traceRouteHop.altitude = mostRecent.altitude + traceRouteHop.latitudeI = mostRecent.latitudeI + traceRouteHop.longitudeI = mostRecent.longitudeI + traceRouteHop.name = hopNode?.user?.longName ?? "unknown".localized + } else { + traceRoute?.hasPositions = false + } + } else { + traceRoute?.hasPositions = false + } + traceRouteHop.num = hopNode?.num ?? 0 + if hopNode != nil { + if decodedInfo.packet.rxTime > 0 { + hopNode?.lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(decodedInfo.packet.rxTime))) + } + hopNodes.append(traceRouteHop) + } + routeString += "\(hopNode?.user?.longName ?? (node == 4294967295 ? "Repeater" : String(hopNode?.num.toHex() ?? "unknown".localized))) \(hopNode?.viaMqtt ?? false ? "MQTT" : "") \(traceRouteHop.snr > 0 ? hopNode?.snr ?? 0.0 : 0.0)dB --> " + } + for (index, node) in routingMessage.routeBack.enumerated() { + var hopNode = getNodeInfo(id: Int64(node), context: context) + if hopNode == nil && hopNode?.num ?? 0 > 0 && node != 4294967295 { + hopNode = createNodeInfo(num: Int64(node), context: context) + } + let traceRouteHop = TraceRouteHopEntity(context: context) + traceRouteHop.time = Date() + traceRouteHop.back = true + traceRouteHop.snr = Float(routingMessage.snrBack[index] / 4) if hopNode?.hasPositions ?? false { traceRoute?.hasPositions = true if let mostRecent = hopNode?.positions?.lastObject as? PositionEntity, mostRecent.time! >= Calendar.current.date(byAdding: .minute, value: -60, to: Date())! { diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 45.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 45.xcdatamodel/contents index 98de03479..265c23f81 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 45.xcdatamodel/contents +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 45.xcdatamodel/contents @@ -1,5 +1,5 @@ - + @@ -428,10 +428,12 @@ + + diff --git a/Meshtastic/Views/Nodes/TraceRouteLog.swift b/Meshtastic/Views/Nodes/TraceRouteLog.swift index 9498f3e6e..87e101f17 100644 --- a/Meshtastic/Views/Nodes/TraceRouteLog.swift +++ b/Meshtastic/Views/Nodes/TraceRouteLog.swift @@ -12,6 +12,7 @@ import MapKit @available(iOS 17.0, macOS 14.0, *) struct TraceRouteLog: View { + private var idiom: UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom } @ObservedObject var locationsHandler = LocationsHandler.shared @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager @@ -25,14 +26,9 @@ struct TraceRouteLog: View { @State var mapStyle: MapStyle = MapStyle.standard(elevation: .realistic, emphasis: MapStyle.StandardEmphasis.muted, pointsOfInterest: .all, showsTraffic: true) @State var position = MapCameraPosition.automatic let distanceFormatter = MKDistanceFormatter() - /// Mockup Values - let colors: [Color] = [.yellow, .orange, .red, .pink, .purple, .blue, .cyan, .green] - let nums: [Int64] = [366311664, 0, 3662955168, 0, 3663982804, 0, 4202719792, 0, 603700594, 0, 836212501, 0, 3663116644, 0, 8362955168] - let snr: [Double] = [-115.00, 17.5, 7.0, 8.9, -24.0, 5.5, 6.0, 7.5] - @State private var hops: Int = 16 /// Max of 16 (2 8 hop routes) + let modemPreset: ModemPresets = ModemPresets(rawValue: UserDefaults.modemPreset) ?? ModemPresets.longFast /// State for the circle of routes @State var angle: Angle = .zero - @State var radius: CGFloat = 175.00 @State var animation: Animation? var body: some View { @@ -49,8 +45,9 @@ struct TraceRouteLog: View { } .listStyle(.plain) } - .frame(minHeight: 200, maxHeight: 230) - VStack { + .frame(minHeight: CGFloat(node.traceRoutes?.count ?? 0 * 40), maxHeight: 150) + Divider() + ScrollView { if selectedRoute != nil { if selectedRoute?.response ?? false && selectedRoute?.hops?.count ?? 0 > 0 { Label { @@ -59,112 +56,115 @@ struct TraceRouteLog: View { Image(systemName: "signpost.right.and.left") .symbolRenderingMode(.hierarchical) } - .font(.title2) + .font(.title3) } else if selectedRoute?.response ?? false { Label { - Text("Trace route received directly by \(selectedRoute?.node?.user?.longName ?? "unknown".localized)") + Text("Trace route received directly by \(selectedRoute?.node?.user?.longName ?? "unknown".localized) with a SNR of \(String(format: "%.2f", selectedRoute?.node?.snr ?? 0.0)) dB") } icon: { Image(systemName: "signpost.right.and.left") .symbolRenderingMode(.hierarchical) } - .font(.title2) + .font(.title3) + } else { + VStack { + Label { + Text("Trace route sent to \(selectedRoute?.node?.user?.longName ?? "unknown".localized)") + } icon: { + Image(systemName: "signpost.right.and.left") + .symbolRenderingMode(.hierarchical) + } + .font(idiom == .phone ? .headline : .largeTitle) + } } - if selectedRoute?.response ?? false { - if selectedRoute?.hasPositions ?? false { - Map(position: $position, bounds: MapCameraBounds(minimumDistance: 1, maximumDistance: .infinity), scope: mapScope) { - Annotation("You", coordinate: selectedRoute?.coordinate ?? LocationHelper.DefaultLocation) { - ZStack { - Circle() - .fill(Color(.green)) - .strokeBorder(.white, lineWidth: 3) - .frame(width: 15, height: 15) - } - } - .annotationTitles(.automatic) - // Direct Trace Route - if selectedRoute?.response ?? false && selectedRoute?.hops?.count ?? 0 == 0 { - if selectedRoute?.node?.positions?.count ?? 0 > 0, let mostRecent = selectedRoute?.node?.positions?.lastObject as? PositionEntity { - let traceRouteCoords: [CLLocationCoordinate2D] = [selectedRoute?.coordinate ?? LocationsHandler.DefaultLocation, mostRecent.coordinate] - Annotation(selectedRoute?.node?.user?.shortName ?? "???", coordinate: mostRecent.nodeCoordinate ?? LocationHelper.DefaultLocation) { - ZStack { - Circle() - .fill(Color(.black)) - .strokeBorder(.white, lineWidth: 3) - .frame(width: 15, height: 15) - } - } - let dashed = StrokeStyle( - lineWidth: 2, - lineCap: .round, lineJoin: .round, dash: [7, 10] - ) - MapPolyline(coordinates: traceRouteCoords) - .stroke(.blue, style: dashed) - } + if selectedRoute?.hops?.count ?? 2 > 3 { + HStack(alignment: .center) { + GeometryReader { geometry in + let size = ((geometry.size.width >= geometry.size.height ? geometry.size.height : geometry.size.width) / 2) - (idiom == .phone ? 50 : 70) + Spacer() + TraceRoute(radius: size, rotation: angle) { + contents() } + .padding(.leading) } - .frame(maxWidth: .infinity, maxHeight: .infinity) + .scaledToFit() } - VStack { - /// Distance - if selectedRoute?.node?.positions?.count ?? 0 > 0, - selectedRoute?.coordinate != nil, - let mostRecent = selectedRoute?.node?.positions?.lastObject as? PositionEntity { - let startPoint = CLLocation(latitude: selectedRoute?.coordinate?.latitude ?? LocationsHandler.DefaultLocation.latitude, longitude: selectedRoute?.coordinate?.longitude ?? LocationsHandler.DefaultLocation.longitude) - if startPoint.distance(from: CLLocation(latitude: LocationsHandler.DefaultLocation.latitude, longitude: LocationsHandler.DefaultLocation.longitude)) > 0.0 { - let metersAway = selectedRoute?.coordinate?.distance(from: CLLocationCoordinate2D(latitude: mostRecent.latitude ?? LocationsHandler.DefaultLocation.latitude, longitude: mostRecent.longitude ?? LocationsHandler.DefaultLocation.longitude)) - Label { - Text("distance".localized + ": \(distanceFormatter.string(fromDistance: Double(metersAway ?? 0)))") - .foregroundColor(.primary) - } icon: { - Image(systemName: "lines.measurement.horizontal") - .symbolRenderingMode(.hierarchical) - } + .onAppear { + // Set the view rotation animation after the view appeared, + // to avoid animating initial rotation + DispatchQueue.main.async { + animation = .easeInOut(duration: 1.0) + withAnimation(.easeInOut(duration: 2.0)) { + angle = (angle == .degrees(-90) ? .degrees(-90) : .degrees(-90)) } } } - } else { - VStack { - Label { - Text("Trace route sent to \(selectedRoute?.node?.user?.longName ?? "unknown".localized)") - } icon: { - Image(systemName: "signpost.right.and.left") - .symbolRenderingMode(.hierarchical) + .onTapGesture { + withAnimation(.easeInOut(duration: 2.0)) { + angle = (angle == .degrees(-90) ? .degrees(90) : .degrees(-90)) } - .font(.title3) - Spacer() } } - if true {// selectedRoute?.hops?.count ?? 2 > 3 { - VStack { - Spacer() - HStack(spacing: 15) { - TraceRoute(radius: radius, rotation: angle) { - contents() + if selectedRoute?.hasPositions ?? false { + Map(position: $position, bounds: MapCameraBounds(minimumDistance: 1, maximumDistance: .infinity), scope: mapScope) { + Annotation("You", coordinate: selectedRoute?.coordinate ?? LocationHelper.DefaultLocation) { + ZStack { + Circle() + .fill(Color(.green)) + .strokeBorder(.white, lineWidth: 3) + .frame(width: 15, height: 15) } } - .onAppear { - // Set the view rotation animation after the view appeared, - // to avoid animating initial rotation - DispatchQueue.main.async { - animation = .easeInOut(duration: 1.0) - withAnimation(.easeInOut(duration: 2.0)) { - angle = (angle == .degrees(-90) ? .degrees(-90) : .degrees(-90)) + .annotationTitles(.automatic) + // Direct Trace Route + if selectedRoute?.response ?? false && selectedRoute?.hops?.count ?? 0 == 0 { + if selectedRoute?.node?.positions?.count ?? 0 > 0, let mostRecent = selectedRoute?.node?.positions?.lastObject as? PositionEntity { + let traceRouteCoords: [CLLocationCoordinate2D] = [selectedRoute?.coordinate ?? LocationsHandler.DefaultLocation, mostRecent.coordinate] + Annotation(selectedRoute?.node?.user?.shortName ?? "???", coordinate: mostRecent.nodeCoordinate ?? LocationHelper.DefaultLocation) { + ZStack { + Circle() + .fill(Color(.black)) + .strokeBorder(.white, lineWidth: 3) + .frame(width: 15, height: 15) + } } + let dashed = StrokeStyle( + lineWidth: 2, + lineCap: .round, lineJoin: .round, dash: [7, 10] + ) + MapPolyline(coordinates: traceRouteCoords) + .stroke(.blue, style: dashed) } } - .onTapGesture { - withAnimation(.easeInOut(duration: 2.0)) { - angle = (angle == .degrees(-90) ? .degrees(90) : .degrees(-90)) + } + .frame(maxWidth: .infinity, minHeight: 250) + if selectedRoute?.response ?? false { + VStack { + /// Distance + if selectedRoute?.node?.positions?.count ?? 0 > 0, + selectedRoute?.coordinate != nil, + let mostRecent = selectedRoute?.node?.positions?.lastObject as? PositionEntity { + let startPoint = CLLocation(latitude: selectedRoute?.coordinate?.latitude ?? LocationsHandler.DefaultLocation.latitude, longitude: selectedRoute?.coordinate?.longitude ?? LocationsHandler.DefaultLocation.longitude) + if startPoint.distance(from: CLLocation(latitude: LocationsHandler.DefaultLocation.latitude, longitude: LocationsHandler.DefaultLocation.longitude)) > 0.0 { + let metersAway = selectedRoute?.coordinate?.distance(from: CLLocationCoordinate2D(latitude: mostRecent.latitude ?? LocationsHandler.DefaultLocation.latitude, longitude: mostRecent.longitude ?? LocationsHandler.DefaultLocation.longitude)) + Label { + Text("distance".localized + ": \(distanceFormatter.string(fromDistance: Double(metersAway ?? 0)))") + .foregroundColor(.primary) + } icon: { + Image(systemName: "lines.measurement.horizontal") + .symbolRenderingMode(.hierarchical) + } + } } } } - .frame(maxWidth: .infinity, maxHeight: .infinity) + Spacer() + .padding(.bottom, 125) } } else { ContentUnavailableView("Select a Trace Route", systemImage: "signpost.right.and.left") } } - Spacer() + .edgesIgnoringSafeArea(.bottom) } .navigationTitle("Trace Route Log") } @@ -174,24 +174,20 @@ struct TraceRouteLog: View { }) } @ViewBuilder func contents(animation: Animation? = nil) -> some View { - ForEach(0.. 0 { - Text("-12dB") - .font(.caption) - .foregroundColor(colors[idx%colors.count].opacity(0.7)) - } - } - } else { - Image(systemName: "arrowshape.right.fill") - .resizable() - .frame(width: 35, height: 35) - .foregroundColor(colors[idx%colors.count].opacity(0.7)) + let nodeColor = UIColor(hex: UInt32(truncatingIfNeeded: idx.num)) + let snrColor = getSnrColor(snr: idx.snr, preset: modemPreset) + VStack { + CircleText(text: String(idx.num.toHex().suffix(4)), color: Color(nodeColor), circleSize: idiom == .phone ? 70 : 100) + Text("\(String(format: "%.2f", idx.snr)) dB") + .font(idiom == .phone ? .caption : .headline) + .foregroundColor(snrColor) } + Image(systemName: "arrowshape.right.fill") + .resizable() + .frame(width: idiom == .phone ? 25 : 40, height: idiom == .phone ? 25 : 40) + .foregroundColor(snrColor.opacity(0.7)) } } } From 5e992146563c0dc7e426d9a7486d70f412b8ea1a Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 24 Sep 2024 16:06:19 -0700 Subject: [PATCH 05/19] Bump version --- Meshtastic.xcodeproj/project.pbxproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 354615880..d9146f08a 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -1700,7 +1700,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.5.7; + MARKETING_VERSION = 2.5.8; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; @@ -1735,7 +1735,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.5.7; + MARKETING_VERSION = 2.5.8; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; @@ -1767,7 +1767,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.5.7; + MARKETING_VERSION = 2.5.8; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1800,7 +1800,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.5.7; + MARKETING_VERSION = 2.5.8; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; From c400a2273a810a05d3bbece96284e3c09f9dd617 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 24 Sep 2024 16:15:42 -0700 Subject: [PATCH 06/19] Leave wheel of traceroute mockup --- Localizable.xcstrings | 3 +++ Meshtastic/Views/Nodes/TraceRouteLog.swift | 29 ++++++++++++++++++++-- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/Localizable.xcstrings b/Localizable.xcstrings index 958098694..78871f947 100644 --- a/Localizable.xcstrings +++ b/Localizable.xcstrings @@ -15,6 +15,9 @@ }, " Whether or not use INPUT_PULLUP mode for GPIO pin. Only applicable if the board uses pull-up resistors on the pin" : { + }, + "-12dB" : { + }, ": %@" : { diff --git a/Meshtastic/Views/Nodes/TraceRouteLog.swift b/Meshtastic/Views/Nodes/TraceRouteLog.swift index 87e101f17..0b3852054 100644 --- a/Meshtastic/Views/Nodes/TraceRouteLog.swift +++ b/Meshtastic/Views/Nodes/TraceRouteLog.swift @@ -26,9 +26,16 @@ struct TraceRouteLog: View { @State var mapStyle: MapStyle = MapStyle.standard(elevation: .realistic, emphasis: MapStyle.StandardEmphasis.muted, pointsOfInterest: .all, showsTraffic: true) @State var position = MapCameraPosition.automatic let distanceFormatter = MKDistanceFormatter() - let modemPreset: ModemPresets = ModemPresets(rawValue: UserDefaults.modemPreset) ?? ModemPresets.longFast + var modemPreset: ModemPresets = ModemPresets(rawValue: UserDefaults.modemPreset) ?? ModemPresets.longFast + + /// Mockup Values + let colors: [Color] = [.yellow, .orange, .red, .pink, .purple, .blue, .cyan, .green] + let nums: [Int64] = [366311664, 0, 3662955168, 0, 3663982804, 0, 4202719792, 0, 603700594, 0, 836212501, 0, 3663116644, 0, 8362955168] + let snr: [Double] = [-115.00, 17.5, 7.0, 8.9, -24.0, 5.5, 6.0, 7.5] + @State private var hops: Int = 16 /// Max of 16 (2 8 hop routes) /// State for the circle of routes @State var angle: Angle = .zero + //@State var radius: CGFloat = 175.00 @State var animation: Animation? var body: some View { @@ -76,7 +83,7 @@ struct TraceRouteLog: View { .font(idiom == .phone ? .headline : .largeTitle) } } - if selectedRoute?.hops?.count ?? 2 > 3 { + if true { // selectedRoute?.hops?.count ?? 2 > 3 { HStack(alignment: .center) { GeometryReader { geometry in let size = ((geometry.size.width >= geometry.size.height ? geometry.size.height : geometry.size.width) / 2) - (idiom == .phone ? 50 : 70) @@ -174,6 +181,24 @@ struct TraceRouteLog: View { }) } @ViewBuilder func contents(animation: Animation? = nil) -> some View { + ForEach(0.. Date: Tue, 24 Sep 2024 17:17:42 -0700 Subject: [PATCH 07/19] Hook up the wheel of traceroutes --- Localizable.xcstrings | 3 -- Meshtastic/Resources/DeviceHardware.json | 16 ++++++++ Meshtastic/Views/Nodes/TraceRouteLog.swift | 46 +++++++--------------- 3 files changed, 31 insertions(+), 34 deletions(-) diff --git a/Localizable.xcstrings b/Localizable.xcstrings index 78871f947..958098694 100644 --- a/Localizable.xcstrings +++ b/Localizable.xcstrings @@ -15,9 +15,6 @@ }, " Whether or not use INPUT_PULLUP mode for GPIO pin. Only applicable if the board uses pull-up resistors on the pin" : { - }, - "-12dB" : { - }, ": %@" : { diff --git a/Meshtastic/Resources/DeviceHardware.json b/Meshtastic/Resources/DeviceHardware.json index cbad8df92..eaf5db0b6 100644 --- a/Meshtastic/Resources/DeviceHardware.json +++ b/Meshtastic/Resources/DeviceHardware.json @@ -391,6 +391,22 @@ "activelySupported": true, "displayName": "Heltec Vision Master E290" }, + { + "hwModel": 69, + "hwModelSlug": "HELTEC_MESH_NODE_T114", + "platformioTarget": "heltec-mesh-node-t114", + "architecture": "nrf52840", + "activelySupported": false, + "displayName": "Heltec Mesh Node T114" + }, + { + "hwModel": 70, + "hwModelSlug": "SENSECAP_INDICATOR", + "platformioTarget": "seeed-sensecap-indicator", + "architecture": "esp32-s3", + "activelySupported": true, + "displayName": "SenseCAP Indicator" + }, { "hwModel": 71, "hwModelSlug": "TRACKER_T1000_E", diff --git a/Meshtastic/Views/Nodes/TraceRouteLog.swift b/Meshtastic/Views/Nodes/TraceRouteLog.swift index 0b3852054..c2a328703 100644 --- a/Meshtastic/Views/Nodes/TraceRouteLog.swift +++ b/Meshtastic/Views/Nodes/TraceRouteLog.swift @@ -26,16 +26,10 @@ struct TraceRouteLog: View { @State var mapStyle: MapStyle = MapStyle.standard(elevation: .realistic, emphasis: MapStyle.StandardEmphasis.muted, pointsOfInterest: .all, showsTraffic: true) @State var position = MapCameraPosition.automatic let distanceFormatter = MKDistanceFormatter() - var modemPreset: ModemPresets = ModemPresets(rawValue: UserDefaults.modemPreset) ?? ModemPresets.longFast - - /// Mockup Values - let colors: [Color] = [.yellow, .orange, .red, .pink, .purple, .blue, .cyan, .green] - let nums: [Int64] = [366311664, 0, 3662955168, 0, 3663982804, 0, 4202719792, 0, 603700594, 0, 836212501, 0, 3663116644, 0, 8362955168] - let snr: [Double] = [-115.00, 17.5, 7.0, 8.9, -24.0, 5.5, 6.0, 7.5] - @State private var hops: Int = 16 /// Max of 16 (2 8 hop routes) /// State for the circle of routes + var modemPreset: ModemPresets = ModemPresets(rawValue: UserDefaults.modemPreset) ?? ModemPresets.longFast + @State private var indexes: Int = 0 @State var angle: Angle = .zero - //@State var radius: CGFloat = 175.00 @State var animation: Animation? var body: some View { @@ -99,6 +93,7 @@ struct TraceRouteLog: View { // Set the view rotation animation after the view appeared, // to avoid animating initial rotation DispatchQueue.main.async { + indexes = (selectedRoute?.hops?.array.count ?? 0) * 2 animation = .easeInOut(duration: 1.0) withAnimation(.easeInOut(duration: 2.0)) { angle = (angle == .degrees(-90) ? .degrees(-90) : .degrees(-90)) @@ -181,38 +176,27 @@ struct TraceRouteLog: View { }) } @ViewBuilder func contents(animation: Animation? = nil) -> some View { - ForEach(0.. Date: Tue, 24 Sep 2024 17:32:42 -0700 Subject: [PATCH 08/19] remove myinfo request --- Meshtastic/Helpers/BLEManager.swift | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 63cf18839..dba3b26b1 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -680,12 +680,10 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate do { disconnectPeripheral(reconnect: false) try container.restorePersistentStore(from: databasePath) - context.refreshAllObjects() - let request = MyInfoEntity.fetchRequest() - try context.fetch(request) UserDefaults.preferredPeripheralNum = Int(myInfo?.myNodeNum ?? 0) - connectTo(peripheral: peripheral) + context.refreshAllObjects() Logger.data.notice("πŸ—‚οΈ Restored Core data for /\(UserDefaults.preferredPeripheralNum, privacy: .public)") + connectTo(peripheral: peripheral) } catch { Logger.data.error("πŸ—‚οΈ Restore Core data copy error: \(error, privacy: .public)") } From 1cf2aef8600489f732f10426d07b91a2d6a8accc Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Wed, 25 Sep 2024 11:19:39 -0700 Subject: [PATCH 09/19] Remove mockup data --- Meshtastic/Views/Nodes/TraceRouteLog.swift | 62 ++++++++++++++++++---- 1 file changed, 52 insertions(+), 10 deletions(-) diff --git a/Meshtastic/Views/Nodes/TraceRouteLog.swift b/Meshtastic/Views/Nodes/TraceRouteLog.swift index c2a328703..45f0f1a14 100644 --- a/Meshtastic/Views/Nodes/TraceRouteLog.swift +++ b/Meshtastic/Views/Nodes/TraceRouteLog.swift @@ -6,6 +6,7 @@ // import SwiftUI +import CoreData #if canImport(MapKit) import MapKit #endif @@ -77,15 +78,16 @@ struct TraceRouteLog: View { .font(idiom == .phone ? .headline : .largeTitle) } } - if true { // selectedRoute?.hops?.count ?? 2 > 3 { + if selectedRoute?.hops?.count ?? 2 > 3 { HStack(alignment: .center) { GeometryReader { geometry in - let size = ((geometry.size.width >= geometry.size.height ? geometry.size.height : geometry.size.width) / 2) - (idiom == .phone ? 50 : 70) + let size = ((geometry.size.width >= geometry.size.height ? geometry.size.height : geometry.size.width) / 2) - (idiom == .phone ? 45 : 85) Spacer() - TraceRoute(radius: size, rotation: angle) { + TraceRoute(radius: size < 600 ? size : 600, rotation: angle) { contents() } - .padding(.leading) + .padding(.leading, idiom == .phone ? 0 : 20) + Spacer() } .scaledToFit() } @@ -178,26 +180,66 @@ struct TraceRouteLog: View { @ViewBuilder func contents(animation: Animation? = nil) -> some View { ForEach(0.. [TraceRouteHopEntity] { + /// static let context = PersistenceController.preview.container.viewContext + var array = [TraceRouteHopEntity]() + let trh1 = TraceRouteHopEntity(context: context) + trh1.num = 366311664 + trh1.snr = 12.5 + let trh2 = TraceRouteHopEntity(context: context) + trh2.num = 3662955168 + trh2.snr = -115.00 + let trh3 = TraceRouteHopEntity(context: context) + trh3.num = 3663982804 + trh3.snr = 17.5 + let trh4 = TraceRouteHopEntity(context: context) + trh4.num = 4202719792 + trh4.snr = 7.0 + let trh5 = TraceRouteHopEntity(context: context) + trh5.num = 603700594 + trh5.snr = 8.9 + let trh6 = TraceRouteHopEntity(context: context) + trh6.num = 836212501 + trh6.snr = -24.0 + let trh7 = TraceRouteHopEntity(context: context) + trh7.num = 3663116644 + trh7.snr = -6.0 + let trh8 = TraceRouteHopEntity(context: context) + trh8.num = 8362955168 + trh8.snr = 7.5 + array.append(trh1) + array.append(trh2) + array.append(trh3) + array.append(trh4) + array.append(trh5) + array.append(trh6) + array.append(trh7) + array.append(trh8) + return array +} From 5024272c0be5fc167956fac6171bfe92c5045071 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Thu, 26 Sep 2024 16:48:18 -0700 Subject: [PATCH 10/19] Split trace route text into to and fro --- Localizable.xcstrings | 3 + .../CoreData/TraceRouteEntityExtension.swift | 30 ----- Meshtastic/Helpers/BLEManager.swift | 42 ++++--- .../contents | 5 +- Meshtastic/Views/Nodes/TraceRouteLog.swift | 115 ++++++++++-------- 5 files changed, 91 insertions(+), 104 deletions(-) diff --git a/Localizable.xcstrings b/Localizable.xcstrings index 958098694..aaa84ccd3 100644 --- a/Localizable.xcstrings +++ b/Localizable.xcstrings @@ -17579,6 +17579,9 @@ }, "Rotary 1" : { + }, + "Route Back: %@" : { + }, "Route Lines" : { diff --git a/Meshtastic/Extensions/CoreData/TraceRouteEntityExtension.swift b/Meshtastic/Extensions/CoreData/TraceRouteEntityExtension.swift index 4e7cdb60a..804aacf88 100644 --- a/Meshtastic/Extensions/CoreData/TraceRouteEntityExtension.swift +++ b/Meshtastic/Extensions/CoreData/TraceRouteEntityExtension.swift @@ -10,36 +10,6 @@ import CoreLocation import MapKit import SwiftUI -extension TraceRouteEntity { - - var latitude: Double? { - - let d = Double(latitudeI) - if d == 0 { - return 0 - } - return d / 1e7 - } - - var longitude: Double? { - - let d = Double(longitudeI) - if d == 0 { - return 0 - } - return d / 1e7 - } - - var coordinate: CLLocationCoordinate2D? { - if latitudeI != 0 && longitudeI != 0 { - let coord = CLLocationCoordinate2D(latitude: latitude!, longitude: longitude!) - return coord - } else { - return nil - } - } -} - extension TraceRouteHopEntity { var latitude: Double? { diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 79a6525e2..30b444e1f 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -468,15 +468,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate traceRoute.id = Int64(meshPacket.id) traceRoute.time = Date() traceRoute.node = receivingNode - // Grab the most recent postion, within the last hour - if connectedNode?.positions?.count ?? 0 > 0, let mostRecent = connectedNode?.positions?.lastObject as? PositionEntity { - if mostRecent.time! >= Calendar.current.date(byAdding: .hour, value: -24, to: Date())! { - traceRoute.altitude = mostRecent.altitude - traceRoute.latitudeI = mostRecent.latitudeI - traceRoute.longitudeI = mostRecent.longitudeI - traceRoute.hasPositions = true - } - } do { try context.save() Logger.data.info("πŸ’Ύ Saved TraceRoute sent to node: \(String(receivingNode?.user?.longName ?? "unknown".localized), privacy: .public)") @@ -840,21 +831,39 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate if let routingMessage = try? RouteDiscovery(serializedBytes: decodedInfo.packet.decoded.payload) { let traceRoute = getTraceRoute(id: Int64(decodedInfo.packet.decoded.requestID), context: context) traceRoute?.response = true - traceRoute?.route = routingMessage.route if routingMessage.route.count == 0 { let logString = String.localizedStringWithFormat("mesh.log.traceroute.received.direct %@".localized, String(decodedInfo.packet.from)) MeshLogger.log("πŸͺ§ \(logString)") - } else { - var routeString = "You --> " var hopNodes: [TraceRouteHopEntity] = [] + /// Add the connected node to the list of hops + var connectedHopNode = getNodeInfo(id: Int64(self.connectedPeripheral.num), context: context) + let connectedHop = TraceRouteHopEntity(context: context) + connectedHop.name = traceRoute?.node?.user?.longName ?? "unknown".localized + connectedHop.num = traceRoute?.num ?? 0 + connectedHop.time = Date() + if connectedHopNode?.hasPositions ?? false { + traceRoute?.hasPositions = true + if let mostRecent = traceRoute?.node?.positions?.lastObject as? PositionEntity, mostRecent.time! >= Calendar.current.date(byAdding: .minute, value: -60, to: Date())! { + connectedHop.altitude = mostRecent.altitude + connectedHop.latitudeI = mostRecent.latitudeI + connectedHop.longitudeI = mostRecent.longitudeI + traceRoute?.hasPositions = true + } else { + traceRoute?.hasPositions = false + } + } else { + traceRoute?.hasPositions = false + } + hopNodes.append(connectedHop) + var routeString = "You --> " for (index, node) in routingMessage.route.enumerated() { var hopNode = getNodeInfo(id: Int64(node), context: context) if hopNode == nil && hopNode?.num ?? 0 > 0 && node != 4294967295 { hopNode = createNodeInfo(num: Int64(node), context: context) } let traceRouteHop = TraceRouteHopEntity(context: context) - traceRouteHop.time = Date() + traceRouteHop.snr = Float(routingMessage.snrTowards[index] / 4) if hopNode?.hasPositions ?? false { traceRoute?.hasPositions = true @@ -876,8 +885,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } hopNodes.append(traceRouteHop) } - routeString += "\(hopNode?.user?.longName ?? (node == 4294967295 ? "Repeater" : String(hopNode?.num.toHex() ?? "unknown".localized))) \(hopNode?.viaMqtt ?? false ? "MQTT" : "") \(traceRouteHop.snr > 0 ? hopNode?.snr ?? 0.0 : 0.0)dB --> " + routeString += "\(hopNode?.user?.longName ?? (node == 4294967295 ? "Repeater" : String(hopNode?.num.toHex() ?? "unknown".localized))) \(hopNode?.viaMqtt ?? false ? "MQTT" : "") (\(traceRouteHop.snr > 0 ? hopNode?.snr ?? 0.0 : 0.0)dB) --> " } + var routeBackString = traceRoute?.node?.user?.longName ?? "unknown".localized for (index, node) in routingMessage.routeBack.enumerated() { var hopNode = getNodeInfo(id: Int64(node), context: context) if hopNode == nil && hopNode?.num ?? 0 > 0 && node != 4294967295 { @@ -907,10 +917,10 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } hopNodes.append(traceRouteHop) } - routeString += "\(hopNode?.user?.longName ?? (node == 4294967295 ? "Repeater" : String(hopNode?.num.toHex() ?? "unknown".localized))) \(hopNode?.viaMqtt ?? false ? "MQTT" : "") --> " + routeString += "\(hopNode?.user?.longName ?? (node == 4294967295 ? "Repeater" : String(hopNode?.num.toHex() ?? "unknown".localized))) \(hopNode?.viaMqtt ?? false ? "MQTT" : "") (\(traceRouteHop.snr > 0 ? hopNode?.snr ?? 0.0 : 0.0)dB) --> " } - routeString += traceRoute?.node?.user?.longName ?? "unknown".localized traceRoute?.routeText = routeString + traceRoute?.routeBackText = routeBackString traceRoute?.hops = NSOrderedSet(array: hopNodes) do { try context.save() diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 45.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 45.xcdatamodel/contents index 265c23f81..5681bb76a 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 45.xcdatamodel/contents +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 45.xcdatamodel/contents @@ -413,14 +413,11 @@ - - - - + diff --git a/Meshtastic/Views/Nodes/TraceRouteLog.swift b/Meshtastic/Views/Nodes/TraceRouteLog.swift index 45f0f1a14..03c2b5bac 100644 --- a/Meshtastic/Views/Nodes/TraceRouteLog.swift +++ b/Meshtastic/Views/Nodes/TraceRouteLog.swift @@ -55,7 +55,14 @@ struct TraceRouteLog: View { Label { Text("Route: \(selectedRoute?.routeText ?? "unknown".localized)") } icon: { - Image(systemName: "signpost.right.and.left") + Image(systemName: "signpost.right") + .symbolRenderingMode(.hierarchical) + } + .font(.title3) + Label { + Text("Route Back: \(selectedRoute?.routeBackText ?? "unknown".localized)") + } icon: { + Image(systemName: "signpost.left") .symbolRenderingMode(.hierarchical) } .font(.title3) @@ -78,7 +85,7 @@ struct TraceRouteLog: View { .font(idiom == .phone ? .headline : .largeTitle) } } - if selectedRoute?.hops?.count ?? 2 > 3 { + if selectedRoute?.hops?.count ?? 0 >= 3 { HStack(alignment: .center) { GeometryReader { geometry in let size = ((geometry.size.width >= geometry.size.height ? geometry.size.height : geometry.size.width) / 2) - (idiom == .phone ? 45 : 85) @@ -109,58 +116,58 @@ struct TraceRouteLog: View { } } if selectedRoute?.hasPositions ?? false { - Map(position: $position, bounds: MapCameraBounds(minimumDistance: 1, maximumDistance: .infinity), scope: mapScope) { - Annotation("You", coordinate: selectedRoute?.coordinate ?? LocationHelper.DefaultLocation) { - ZStack { - Circle() - .fill(Color(.green)) - .strokeBorder(.white, lineWidth: 3) - .frame(width: 15, height: 15) - } - } - .annotationTitles(.automatic) - // Direct Trace Route - if selectedRoute?.response ?? false && selectedRoute?.hops?.count ?? 0 == 0 { - if selectedRoute?.node?.positions?.count ?? 0 > 0, let mostRecent = selectedRoute?.node?.positions?.lastObject as? PositionEntity { - let traceRouteCoords: [CLLocationCoordinate2D] = [selectedRoute?.coordinate ?? LocationsHandler.DefaultLocation, mostRecent.coordinate] - Annotation(selectedRoute?.node?.user?.shortName ?? "???", coordinate: mostRecent.nodeCoordinate ?? LocationHelper.DefaultLocation) { - ZStack { - Circle() - .fill(Color(.black)) - .strokeBorder(.white, lineWidth: 3) - .frame(width: 15, height: 15) - } - } - let dashed = StrokeStyle( - lineWidth: 2, - lineCap: .round, lineJoin: .round, dash: [7, 10] - ) - MapPolyline(coordinates: traceRouteCoords) - .stroke(.blue, style: dashed) - } - } - } - .frame(maxWidth: .infinity, minHeight: 250) - if selectedRoute?.response ?? false { - VStack { - /// Distance - if selectedRoute?.node?.positions?.count ?? 0 > 0, - selectedRoute?.coordinate != nil, - let mostRecent = selectedRoute?.node?.positions?.lastObject as? PositionEntity { - let startPoint = CLLocation(latitude: selectedRoute?.coordinate?.latitude ?? LocationsHandler.DefaultLocation.latitude, longitude: selectedRoute?.coordinate?.longitude ?? LocationsHandler.DefaultLocation.longitude) - if startPoint.distance(from: CLLocation(latitude: LocationsHandler.DefaultLocation.latitude, longitude: LocationsHandler.DefaultLocation.longitude)) > 0.0 { - let metersAway = selectedRoute?.coordinate?.distance(from: CLLocationCoordinate2D(latitude: mostRecent.latitude ?? LocationsHandler.DefaultLocation.latitude, longitude: mostRecent.longitude ?? LocationsHandler.DefaultLocation.longitude)) - Label { - Text("distance".localized + ": \(distanceFormatter.string(fromDistance: Double(metersAway ?? 0)))") - .foregroundColor(.primary) - } icon: { - Image(systemName: "lines.measurement.horizontal") - .symbolRenderingMode(.hierarchical) - } - } - } - } - } +// Map(position: $position, bounds: MapCameraBounds(minimumDistance: 1, maximumDistance: .infinity), scope: mapScope) { +// Annotation("You", coordinate: selectedRoute?.coordinate ?? LocationHelper.DefaultLocation) { +// ZStack { +// Circle() +// .fill(Color(.green)) +// .strokeBorder(.white, lineWidth: 3) +// .frame(width: 15, height: 15) +// } +// } +// .annotationTitles(.automatic) +// // Direct Trace Route +// if selectedRoute?.response ?? false && selectedRoute?.hops?.count ?? 0 == 0 { +// if selectedRoute?.node?.positions?.count ?? 0 > 0, let mostRecent = selectedRoute?.node?.positions?.lastObject as? PositionEntity { +// let traceRouteCoords: [CLLocationCoordinate2D] = [selectedRoute?.coordinate ?? LocationsHandler.DefaultLocation, mostRecent.coordinate] +// Annotation(selectedRoute?.node?.user?.shortName ?? "???", coordinate: mostRecent.nodeCoordinate ?? LocationHelper.DefaultLocation) { +// ZStack { +// Circle() +// .fill(Color(.black)) +// .strokeBorder(.white, lineWidth: 3) +// .frame(width: 15, height: 15) +// } +// } +// let dashed = StrokeStyle( +// lineWidth: 2, +// lineCap: .round, lineJoin: .round, dash: [7, 10] +// ) +// MapPolyline(coordinates: traceRouteCoords) +// .stroke(.blue, style: dashed) +// } +// } +// } +// .frame(maxWidth: .infinity, minHeight: 250) +// if selectedRoute?.response ?? false { +// VStack { +// /// Distance +// if selectedRoute?.node?.positions?.count ?? 0 > 0, +// selectedRoute?.coordinate != nil, +// let mostRecent = selectedRoute?.node?.positions?.lastObject as? PositionEntity { +// let startPoint = CLLocation(latitude: selectedRoute?.coordinate?.latitude ?? LocationsHandler.DefaultLocation.latitude, longitude: selectedRoute?.coordinate?.longitude ?? LocationsHandler.DefaultLocation.longitude) +// if startPoint.distance(from: CLLocation(latitude: LocationsHandler.DefaultLocation.latitude, longitude: LocationsHandler.DefaultLocation.longitude)) > 0.0 { +// let metersAway = selectedRoute?.coordinate?.distance(from: CLLocationCoordinate2D(latitude: mostRecent.latitude ?? LocationsHandler.DefaultLocation.latitude, longitude: mostRecent.longitude ?? LocationsHandler.DefaultLocation.longitude)) +// Label { +// Text("distance".localized + ": \(distanceFormatter.string(fromDistance: Double(metersAway ?? 0)))") +// .foregroundColor(.primary) +// } icon: { +// Image(systemName: "lines.measurement.horizontal") +// .symbolRenderingMode(.hierarchical) +// } +// } +// } +// } +// } Spacer() .padding(.bottom, 125) } From ae2bc0c8829674873df35df2b67f947c1c0f675d Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Thu, 26 Sep 2024 17:21:29 -0700 Subject: [PATCH 11/19] Show map more often --- Meshtastic/Helpers/BLEManager.swift | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 30b444e1f..805cfc01e 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -872,11 +872,8 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate traceRouteHop.latitudeI = mostRecent.latitudeI traceRouteHop.longitudeI = mostRecent.longitudeI traceRouteHop.name = hopNode?.user?.longName ?? "unknown".localized - } else { - traceRoute?.hasPositions = false + traceRoute?.hasPositions = true } - } else { - traceRoute?.hasPositions = false } traceRouteHop.num = hopNode?.num ?? 0 if hopNode != nil { @@ -904,11 +901,8 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate traceRouteHop.latitudeI = mostRecent.latitudeI traceRouteHop.longitudeI = mostRecent.longitudeI traceRouteHop.name = hopNode?.user?.longName ?? "unknown".localized - } else { - traceRoute?.hasPositions = false + traceRoute?.hasPositions = true } - } else { - traceRoute?.hasPositions = false } traceRouteHop.num = hopNode?.num ?? 0 if hopNode != nil { From 7e5ee3c4a47e8c0f3d786a5115d964d0b68d41c9 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Fri, 27 Sep 2024 08:35:57 -0700 Subject: [PATCH 12/19] clean up positions in traceroutes --- Meshtastic/Helpers/BLEManager.swift | 21 +++++++------------ .../contents | 1 - 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 805cfc01e..cb558f014 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -837,23 +837,17 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } else { var hopNodes: [TraceRouteHopEntity] = [] /// Add the connected node to the list of hops - var connectedHopNode = getNodeInfo(id: Int64(self.connectedPeripheral.num), context: context) + let connectedHopNode = getNodeInfo(id: Int64(self.connectedPeripheral.num), context: context) let connectedHop = TraceRouteHopEntity(context: context) connectedHop.name = traceRoute?.node?.user?.longName ?? "unknown".localized - connectedHop.num = traceRoute?.num ?? 0 connectedHop.time = Date() if connectedHopNode?.hasPositions ?? false { - traceRoute?.hasPositions = true - if let mostRecent = traceRoute?.node?.positions?.lastObject as? PositionEntity, mostRecent.time! >= Calendar.current.date(byAdding: .minute, value: -60, to: Date())! { + if let mostRecent = traceRoute?.node?.positions?.lastObject as? PositionEntity, mostRecent.time! >= Calendar.current.date(byAdding: .hour, value: -24, to: Date())! { connectedHop.altitude = mostRecent.altitude connectedHop.latitudeI = mostRecent.latitudeI connectedHop.longitudeI = mostRecent.longitudeI traceRoute?.hasPositions = true - } else { - traceRoute?.hasPositions = false } - } else { - traceRoute?.hasPositions = false } hopNodes.append(connectedHop) var routeString = "You --> " @@ -866,8 +860,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate traceRouteHop.snr = Float(routingMessage.snrTowards[index] / 4) if hopNode?.hasPositions ?? false { - traceRoute?.hasPositions = true - if let mostRecent = hopNode?.positions?.lastObject as? PositionEntity, mostRecent.time! >= Calendar.current.date(byAdding: .minute, value: -60, to: Date())! { + if let mostRecent = hopNode?.positions?.lastObject as? PositionEntity, mostRecent.time! >= Calendar.current.date(byAdding: .hour, value: -24, to: Date())! { traceRouteHop.altitude = mostRecent.altitude traceRouteHop.latitudeI = mostRecent.latitudeI traceRouteHop.longitudeI = mostRecent.longitudeI @@ -895,8 +888,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate traceRouteHop.back = true traceRouteHop.snr = Float(routingMessage.snrBack[index] / 4) if hopNode?.hasPositions ?? false { - traceRoute?.hasPositions = true - if let mostRecent = hopNode?.positions?.lastObject as? PositionEntity, mostRecent.time! >= Calendar.current.date(byAdding: .minute, value: -60, to: Date())! { + if let mostRecent = hopNode?.positions?.lastObject as? PositionEntity, mostRecent.time! >= Calendar.current.date(byAdding: .hour, value: -24, to: Date())! { traceRouteHop.altitude = mostRecent.altitude traceRouteHop.latitudeI = mostRecent.latitudeI traceRouteHop.longitudeI = mostRecent.longitudeI @@ -911,7 +903,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } hopNodes.append(traceRouteHop) } - routeString += "\(hopNode?.user?.longName ?? (node == 4294967295 ? "Repeater" : String(hopNode?.num.toHex() ?? "unknown".localized))) \(hopNode?.viaMqtt ?? false ? "MQTT" : "") (\(traceRouteHop.snr > 0 ? hopNode?.snr ?? 0.0 : 0.0)dB) --> " + routeBackString += "\(hopNode?.user?.longName ?? (node == 4294967295 ? "Repeater" : String(hopNode?.num.toHex() ?? "unknown".localized))) \(hopNode?.viaMqtt ?? false ? "MQTT" : "") (\(traceRouteHop.snr > 0 ? hopNode?.snr ?? 0.0 : 0.0)dB) --> " } traceRoute?.routeText = routeString traceRoute?.routeBackText = routeBackString @@ -952,7 +944,8 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate lastConnectionError = "" isSubscribed = true Logger.mesh.info("🀜 [BLE] Want Config Complete. ID:\(decodedInfo.configCompleteID)") - sendTime() + if sendTime() { + } peripherals.removeAll(where: { $0.peripheral.state == CBPeripheralState.disconnected }) // Config conplete returns so we don't read the characteristic again diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 45.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 45.xcdatamodel/contents index 5681bb76a..e3fb02a1d 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 45.xcdatamodel/contents +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 45.xcdatamodel/contents @@ -415,7 +415,6 @@ - From bb5320035bccc7753bf0babd0613dc15aca484e6 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 29 Sep 2024 10:25:37 -0700 Subject: [PATCH 13/19] Fix potential traceroute position crashes --- .../CoreData/NodeInfoEntityExtension.swift | 2 +- Meshtastic/Helpers/BLEManager.swift | 16 ++++++++-------- .../MeshtasticDataModelV 45.xcdatamodel/contents | 1 + 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/Meshtastic/Extensions/CoreData/NodeInfoEntityExtension.swift b/Meshtastic/Extensions/CoreData/NodeInfoEntityExtension.swift index c1bd5beca..7585fb1ec 100644 --- a/Meshtastic/Extensions/CoreData/NodeInfoEntityExtension.swift +++ b/Meshtastic/Extensions/CoreData/NodeInfoEntityExtension.swift @@ -23,7 +23,7 @@ extension NodeInfoEntity { } var hasPositions: Bool { - return positions?.count ?? 0 > 0 + return self.positions?.count ?? 0 > 0 } var hasDeviceMetrics: Bool { diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index cb558f014..83b37dc7b 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -832,7 +832,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate let traceRoute = getTraceRoute(id: Int64(decodedInfo.packet.decoded.requestID), context: context) traceRoute?.response = true if routingMessage.route.count == 0 { - let logString = String.localizedStringWithFormat("mesh.log.traceroute.received.direct %@".localized, String(decodedInfo.packet.from)) + let snr = routingMessage.snrBack.count > 0 ? routingMessage.snrBack[0] / 4 : 0 + traceRoute?.snr = Float(snr) + let logString = String.localizedStringWithFormat("mesh.log.traceroute.received.direct %@".localized, String(snr)) MeshLogger.log("πŸͺ§ \(logString)") } else { var hopNodes: [TraceRouteHopEntity] = [] @@ -841,7 +843,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate let connectedHop = TraceRouteHopEntity(context: context) connectedHop.name = traceRoute?.node?.user?.longName ?? "unknown".localized connectedHop.time = Date() - if connectedHopNode?.hasPositions ?? false { + if let cn = connectedHopNode, cn.hasPositions { if let mostRecent = traceRoute?.node?.positions?.lastObject as? PositionEntity, mostRecent.time! >= Calendar.current.date(byAdding: .hour, value: -24, to: Date())! { connectedHop.altitude = mostRecent.altitude connectedHop.latitudeI = mostRecent.latitudeI @@ -859,12 +861,11 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate let traceRouteHop = TraceRouteHopEntity(context: context) traceRouteHop.snr = Float(routingMessage.snrTowards[index] / 4) - if hopNode?.hasPositions ?? false { - if let mostRecent = hopNode?.positions?.lastObject as? PositionEntity, mostRecent.time! >= Calendar.current.date(byAdding: .hour, value: -24, to: Date())! { + if let hn = hopNode, hn.hasPositions { + if let mostRecent = hn.positions?.lastObject as? PositionEntity, mostRecent.time! >= Calendar.current.date(byAdding: .hour, value: -24, to: Date())! { traceRouteHop.altitude = mostRecent.altitude traceRouteHop.latitudeI = mostRecent.latitudeI traceRouteHop.longitudeI = mostRecent.longitudeI - traceRouteHop.name = hopNode?.user?.longName ?? "unknown".localized traceRoute?.hasPositions = true } } @@ -887,12 +888,11 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate traceRouteHop.time = Date() traceRouteHop.back = true traceRouteHop.snr = Float(routingMessage.snrBack[index] / 4) - if hopNode?.hasPositions ?? false { - if let mostRecent = hopNode?.positions?.lastObject as? PositionEntity, mostRecent.time! >= Calendar.current.date(byAdding: .hour, value: -24, to: Date())! { + if let hn = hopNode, hn.hasPositions { + if let mostRecent = hn.positions?.lastObject as? PositionEntity, mostRecent.time! >= Calendar.current.date(byAdding: .hour, value: -24, to: Date())! { traceRouteHop.altitude = mostRecent.altitude traceRouteHop.latitudeI = mostRecent.latitudeI traceRouteHop.longitudeI = mostRecent.longitudeI - traceRouteHop.name = hopNode?.user?.longName ?? "unknown".localized traceRoute?.hasPositions = true } } diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 45.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 45.xcdatamodel/contents index e3fb02a1d..c16a68b01 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 45.xcdatamodel/contents +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 45.xcdatamodel/contents @@ -418,6 +418,7 @@ + From 02ff0605333dacc60d467c3b20d270be06ee6d7d Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 29 Sep 2024 10:40:49 -0700 Subject: [PATCH 14/19] remove extra node lookup --- Meshtastic/Helpers/BLEManager.swift | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 83b37dc7b..0ffb0af2b 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -838,18 +838,14 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate MeshLogger.log("πŸͺ§ \(logString)") } else { var hopNodes: [TraceRouteHopEntity] = [] - /// Add the connected node to the list of hops - let connectedHopNode = getNodeInfo(id: Int64(self.connectedPeripheral.num), context: context) let connectedHop = TraceRouteHopEntity(context: context) connectedHop.name = traceRoute?.node?.user?.longName ?? "unknown".localized connectedHop.time = Date() - if let cn = connectedHopNode, cn.hasPositions { - if let mostRecent = traceRoute?.node?.positions?.lastObject as? PositionEntity, mostRecent.time! >= Calendar.current.date(byAdding: .hour, value: -24, to: Date())! { - connectedHop.altitude = mostRecent.altitude - connectedHop.latitudeI = mostRecent.latitudeI - connectedHop.longitudeI = mostRecent.longitudeI - traceRoute?.hasPositions = true - } + if let mostRecent = traceRoute?.node?.positions?.lastObject as? PositionEntity, mostRecent.time! >= Calendar.current.date(byAdding: .hour, value: -24, to: Date())! { + connectedHop.altitude = mostRecent.altitude + connectedHop.latitudeI = mostRecent.latitudeI + connectedHop.longitudeI = mostRecent.longitudeI + traceRoute?.hasPositions = true } hopNodes.append(connectedHop) var routeString = "You --> " @@ -859,7 +855,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate hopNode = createNodeInfo(num: Int64(node), context: context) } let traceRouteHop = TraceRouteHopEntity(context: context) - traceRouteHop.snr = Float(routingMessage.snrTowards[index] / 4) if let hn = hopNode, hn.hasPositions { if let mostRecent = hn.positions?.lastObject as? PositionEntity, mostRecent.time! >= Calendar.current.date(byAdding: .hour, value: -24, to: Date())! { From 01c22745e35234e7ebc544da8ce6071256f3883e Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Mon, 30 Sep 2024 08:23:47 -0700 Subject: [PATCH 15/19] Assorted cleanup --- Localizable.xcstrings | 11 +++++- Meshtastic/Helpers/BLEManager.swift | 29 ++++++++------ Meshtastic/Helpers/LocationsHandler.swift | 6 +-- .../contents | 1 + Meshtastic/Views/Nodes/TraceRouteLog.swift | 39 ++++++++++++++----- .../Config/Module/StoreForwardConfig.swift | 2 +- 6 files changed, 63 insertions(+), 25 deletions(-) diff --git a/Localizable.xcstrings b/Localizable.xcstrings index aaa84ccd3..11d7c54bd 100644 --- a/Localizable.xcstrings +++ b/Localizable.xcstrings @@ -194,6 +194,9 @@ }, "256 bit" : { + }, + "A Trace Route was sent, no response has been received." : { + }, "about" : { "localizations" : { @@ -20156,7 +20159,7 @@ "Store and forward clients can request history from routers on the network." : { }, - "Store and forward router devices must also be in the router or router client device role and requires a ESP32 device with PSRAM." : { + "Store and forward router devices require a ESP32 device with PSRAM." : { }, "storeforward" : { @@ -21874,6 +21877,12 @@ }, "Trace route sent to %@" : { + }, + "Trace route to %@ was not sent." : { + + }, + "Trace Route was rate limited. You can send a trace route a maximum of once every thirty seconds." : { + }, "Traffic" : { diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 0ffb0af2b..bb1cb8c4e 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -592,7 +592,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate return } do { - let logRecord = try LogRecord(serializedData: characteristic.value!) + let logRecord = try LogRecord(serializedBytes: characteristic.value!) var message = logRecord.source.isEmpty ? logRecord.message : "[\(logRecord.source)] \(logRecord.message)" switch logRecord.level { case .debug: @@ -613,14 +613,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate // Ignore fail to parse as LogRecord } - case LEGACY_LOGRADIO_UUID: - if characteristic.value == nil || characteristic.value!.isEmpty { - return - } - if let log = String(data: characteristic.value!, encoding: .utf8) { - handleRadioLog(radioLog: log) - } - case FROMRADIO_UUID: if characteristic.value == nil || characteristic.value!.isEmpty { @@ -629,7 +621,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate var decodedInfo = FromRadio() do { - decodedInfo = try FromRadio(serializedData: characteristic.value!) + decodedInfo = try FromRadio(serializedBytes: characteristic.value!) } catch { Logger.services.error("πŸ’₯ \(error.localizedDescription, privacy: .public) \(characteristic.value!, privacy: .public)") @@ -644,6 +636,21 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate ) mqttManager.mqttClientProxy?.publish(message) } else if decodedInfo.payloadVariant == FromRadio.OneOf_PayloadVariant.clientNotification(decodedInfo.clientNotification) { + if decodedInfo.clientNotification.hasReplyID { + /// Set Sent bool on TraceRouteEntity to false if we got rate limited + if decodedInfo.clientNotification.message.starts(with: "TraceRoute") { + let traceRoute = getTraceRoute(id: Int64(decodedInfo.clientNotification.replyID), context: context) + traceRoute?.sent = false + do { + try context.save() + Logger.data.info("πŸ’Ύ [TraceRouteEntity] Trace Route Rate Limited") + } catch { + context.rollback() + let nsError = error as NSError + Logger.data.error("πŸ’₯ [TraceRouteEntity] Error Updating Core Data: \(nsError, privacy: .public)") + } + } + } let manager = LocalNotificationManager() manager.notifications = [ Notification( @@ -916,7 +923,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } } case .neighborinfoApp: - if let neighborInfo = try? NeighborInfo(serializedData: decodedInfo.packet.decoded.payload) { + if let neighborInfo = try? NeighborInfo(serializedBytes: decodedInfo.packet.decoded.payload) { // MeshLogger.log("πŸ•ΈοΈ MESH PACKET received for Neighbor Info App UNHANDLED") MeshLogger.log("πŸ•ΈοΈ MESH PACKET received for Neighbor Info App UNHANDLED \(neighborInfo)") } diff --git a/Meshtastic/Helpers/LocationsHandler.swift b/Meshtastic/Helpers/LocationsHandler.swift index 9b16e3de9..20d7725f8 100644 --- a/Meshtastic/Helpers/LocationsHandler.swift +++ b/Meshtastic/Helpers/LocationsHandler.swift @@ -85,15 +85,15 @@ import OSLog if smartPostion { let age = -location.timestamp.timeIntervalSinceNow if age > 10 { - Logger.services.warning("πŸ“ [App] Bad Location \(self.count, privacy: .public): Too Old \(age, privacy: .public) seconds ago \(location, privacy: .private)") + Logger.services.info("πŸ“ [App] Smart Position - Bad Location: Too Old \(age, privacy: .public) seconds ago \(location, privacy: .private)") return false } if location.horizontalAccuracy < 0 { - Logger.services.warning("πŸ“ [App] Bad Location \(self.count, privacy: .public): Horizontal Accuracy: \(location.horizontalAccuracy) \(location, privacy: .private)") + Logger.services.info("πŸ“ [App] Smart Position - Bad Location: Horizontal Accuracy: \(location.horizontalAccuracy) \(location, privacy: .private)") return false } if location.horizontalAccuracy > 5 { - Logger.services.warning("πŸ“ [App] Bad Location \(self.count, privacy: .public): Horizontal Accuracy: \(location.horizontalAccuracy) \(location, privacy: .private)") + Logger.services.info("πŸ“ [App] Smart Position - Bad Location: Horizontal Accuracy: \(location.horizontalAccuracy) \(location, privacy: .private)") return false } } diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 45.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 45.xcdatamodel/contents index c16a68b01..65a05ff9d 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 45.xcdatamodel/contents +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 45.xcdatamodel/contents @@ -418,6 +418,7 @@ + diff --git a/Meshtastic/Views/Nodes/TraceRouteLog.swift b/Meshtastic/Views/Nodes/TraceRouteLog.swift index 03c2b5bac..f32cbc810 100644 --- a/Meshtastic/Views/Nodes/TraceRouteLog.swift +++ b/Meshtastic/Views/Nodes/TraceRouteLog.swift @@ -39,7 +39,8 @@ struct TraceRouteLog: View { VStack { List(node.traceRoutes?.reversed() as? [TraceRouteEntity] ?? [], id: \.self, selection: $selectedRoute) { route in Label { - Text("\(route.time?.formatted() ?? "unknown".localized) - \(route.response ? (route.hops?.count == 0 && route.response ? "Direct" : "\(route.hops?.count ?? 0) \(route.hops?.count ?? 0 == 1 ? "Hop": "Hops")") : "No Response")") + Text("\(route.time?.formatted() ?? "unknown".localized) - \(route.response ? (route.hops?.count == 0 && route.response ? "Direct" : "\(route.hops?.count ?? 0) \(route.hops?.count ?? 0 == 1 ? "Hop": "Hops")") : (route.sent ? "No Response" : "Not Sent"))") + .font(.callout) } icon: { Image(systemName: route.response ? (route.hops?.count == 0 && route.response ? "person.line.dotted.person" : "point.3.connected.trianglepath.dotted") : "person.slash") .symbolRenderingMode(.hierarchical) @@ -47,7 +48,7 @@ struct TraceRouteLog: View { } .listStyle(.plain) } - .frame(minHeight: CGFloat(node.traceRoutes?.count ?? 0 * 40), maxHeight: 150) + .frame(minHeight: CGFloat(node.traceRoutes?.count ?? 0 * 40), maxHeight: 250) Divider() ScrollView { if selectedRoute != nil { @@ -74,16 +75,36 @@ struct TraceRouteLog: View { .symbolRenderingMode(.hierarchical) } .font(.title3) + } else if !(selectedRoute?.sent ?? true) { + Label { + VStack { + Text("Trace route to \(selectedRoute?.node?.user?.longName ?? "unknown".localized) was not sent.") + .font(idiom == .phone ? .body : .largeTitle) + .fontWeight(.semibold) + Text("Trace Route was rate limited. You can send a trace route a maximum of once every thirty seconds.") + .font(idiom == .phone ? .caption : .body) + .foregroundStyle(.secondary) + .padding() + } + } icon: { + Image(systemName: "square.and.arrow.up.trianglebadge.exclamationmark") + .symbolRenderingMode(.hierarchical) + } } else { - VStack { - Label { + Label { + VStack { Text("Trace route sent to \(selectedRoute?.node?.user?.longName ?? "unknown".localized)") - } icon: { - Image(systemName: "signpost.right.and.left") - .symbolRenderingMode(.hierarchical) + .font(idiom == .phone ? .body : .largeTitle) + .fontWeight(.semibold) + Text("A Trace Route was sent, no response has been received.") + .font(idiom == .phone ? .caption : .body) + .foregroundStyle(.secondary) + .padding() } - .font(idiom == .phone ? .headline : .largeTitle) - } + } icon: { + Image(systemName: "signpost.right.and.left") + .symbolRenderingMode(.hierarchical) + } } if selectedRoute?.hops?.count ?? 0 >= 3 { HStack(alignment: .center) { diff --git a/Meshtastic/Views/Settings/Config/Module/StoreForwardConfig.swift b/Meshtastic/Views/Settings/Config/Module/StoreForwardConfig.swift index 8fff8979c..aeee96015 100644 --- a/Meshtastic/Views/Settings/Config/Module/StoreForwardConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/StoreForwardConfig.swift @@ -56,7 +56,7 @@ struct StoreForwardConfig: View { } VStack { if isRouter { - Text("Store and forward router devices must also be in the router or router client device role and requires a ESP32 device with PSRAM.") + Text("Store and forward router devices require a ESP32 device with PSRAM.") .foregroundColor(.gray) .font(.callout) } else { From 2d4373e2f00e195e431ec8e3eda5a6c08e1c28b6 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Mon, 30 Sep 2024 12:05:32 -0700 Subject: [PATCH 16/19] fix snr error --- Meshtastic/Helpers/BLEManager.swift | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index bb1cb8c4e..865f8d234 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -862,7 +862,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate hopNode = createNodeInfo(num: Int64(node), context: context) } let traceRouteHop = TraceRouteHopEntity(context: context) - traceRouteHop.snr = Float(routingMessage.snrTowards[index] / 4) + if routingMessage.snrTowards.count >= index + 1 { + traceRouteHop.snr = Float(routingMessage.snrTowards[index] / 4) + } if let hn = hopNode, hn.hasPositions { if let mostRecent = hn.positions?.lastObject as? PositionEntity, mostRecent.time! >= Calendar.current.date(byAdding: .hour, value: -24, to: Date())! { traceRouteHop.altitude = mostRecent.altitude @@ -889,7 +891,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate let traceRouteHop = TraceRouteHopEntity(context: context) traceRouteHop.time = Date() traceRouteHop.back = true - traceRouteHop.snr = Float(routingMessage.snrBack[index] / 4) + if routingMessage.snrBack.count >= index + 1 { + traceRouteHop.snr = Float(routingMessage.snrBack[index] / 4) + } if let hn = hopNode, hn.hasPositions { if let mostRecent = hn.positions?.lastObject as? PositionEntity, mostRecent.time! >= Calendar.current.date(byAdding: .hour, value: -24, to: Date())! { traceRouteHop.altitude = mostRecent.altitude From 1adf9d9d5887074ad7a2e4fd2c46175f5e33d888 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Fri, 4 Oct 2024 18:28:42 -0700 Subject: [PATCH 17/19] Delete for traceroutes --- .../contents | 2 +- Meshtastic/Views/Nodes/TraceRouteLog.swift | 15 ++++- .../Views/Settings/Logs/LogDetail.swift | 61 +++++++++++-------- 3 files changed, 50 insertions(+), 28 deletions(-) diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 45.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 45.xcdatamodel/contents index 65a05ff9d..2a5e4a561 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 45.xcdatamodel/contents +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 45.xcdatamodel/contents @@ -421,7 +421,7 @@ - + diff --git a/Meshtastic/Views/Nodes/TraceRouteLog.swift b/Meshtastic/Views/Nodes/TraceRouteLog.swift index f32cbc810..f08281920 100644 --- a/Meshtastic/Views/Nodes/TraceRouteLog.swift +++ b/Meshtastic/Views/Nodes/TraceRouteLog.swift @@ -7,6 +7,7 @@ import SwiftUI import CoreData +import OSLog #if canImport(MapKit) import MapKit #endif @@ -45,6 +46,18 @@ struct TraceRouteLog: View { Image(systemName: route.response ? (route.hops?.count == 0 && route.response ? "person.line.dotted.person" : "point.3.connected.trianglepath.dotted") : "person.slash") .symbolRenderingMode(.hierarchical) } + .swipeActions { + Button(role: .destructive) { + context.delete(route) + do { + try context.save() + } catch let error as NSError { + Logger.data.error("\(error.localizedDescription)") + } + } label: { + Label("delete", systemImage: "trash") + } + } } .listStyle(.plain) } @@ -208,7 +221,7 @@ struct TraceRouteLog: View { @ViewBuilder func contents(animation: Animation? = nil) -> some View { ForEach(0.. Date: Sat, 5 Oct 2024 09:53:33 -0700 Subject: [PATCH 18/19] add packet filter for message lock icon --- 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 75d2b85d9..7d0c6a6b1 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -876,7 +876,7 @@ func textMessageAppPacket( if fetchedUsers.first(where: { $0.num == packet.from }) != nil { newMessage.fromUser = fetchedUsers.first(where: { $0.num == packet.from }) /// Set the public key for the message - if newMessage.fromUser?.pkiEncrypted ?? false { + if newMessage.fromUser?.pkiEncrypted ?? false && packet.pkiEncrypted { newMessage.pkiEncrypted = true newMessage.publicKey = packet.publicKey } From 6f07eea9f8e6fc4f93cd4b78f297f3456fe5611c Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sat, 5 Oct 2024 09:55:08 -0700 Subject: [PATCH 19/19] Remove testing data --- Meshtastic/Views/Nodes/TraceRouteLog.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Meshtastic/Views/Nodes/TraceRouteLog.swift b/Meshtastic/Views/Nodes/TraceRouteLog.swift index f08281920..22d190d52 100644 --- a/Meshtastic/Views/Nodes/TraceRouteLog.swift +++ b/Meshtastic/Views/Nodes/TraceRouteLog.swift @@ -221,7 +221,7 @@ struct TraceRouteLog: View { @ViewBuilder func contents(animation: Animation? = nil) -> some View { ForEach(0..