From 4d61d5d18d5d56d591b968fcc0c73caa550f7af1 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Thu, 21 Dec 2023 16:50:31 -0800 Subject: [PATCH 1/9] 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 4b935710..b6394ac6 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -1482,7 +1482,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.2.15; + MARKETING_VERSION = 2.2.16; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; @@ -1516,7 +1516,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.2.15; + MARKETING_VERSION = 2.2.16; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; @@ -1638,7 +1638,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.2.15; + MARKETING_VERSION = 2.2.16; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1671,7 +1671,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.2.15; + MARKETING_VERSION = 2.2.16; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; From 0807fbfcaa256029101a43069b2e50fe531c9e48 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Thu, 21 Dec 2023 18:00:52 -0800 Subject: [PATCH 2/9] Delete user and node when sending a nodedb remove --- Meshtastic/Helpers/BLEManager.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index ba567b4c..34ff1f52 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -1311,6 +1311,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate if connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected{ do { connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) + context!.delete(node.user!) context!.delete(node) try context!.save() return true From 089e13db0817fb8a73d0ac0206a1904d94a5b882 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Thu, 21 Dec 2023 19:33:45 -0800 Subject: [PATCH 3/9] Add user role to node list --- Meshtastic/Helpers/LocationsHandler.swift | 6 +++-- Meshtastic/Helpers/MeshPackets.swift | 2 ++ .../Views/Nodes/Helpers/NodeListItem.swift | 9 +++++++- Meshtastic/Views/Nodes/TraceRouteLog.swift | 22 ++++++++----------- 4 files changed, 23 insertions(+), 16 deletions(-) diff --git a/Meshtastic/Helpers/LocationsHandler.swift b/Meshtastic/Helpers/LocationsHandler.swift index cd9707af..ebe05903 100644 --- a/Meshtastic/Helpers/LocationsHandler.swift +++ b/Meshtastic/Helpers/LocationsHandler.swift @@ -40,7 +40,6 @@ import CoreLocation self.manager = CLLocationManager() // Creating a location manager instance is safe to call here in `MainActor`. locationsArray = [CLLocation]() enableSmartPosition = true - self.manager.distanceFilter = 5 } func startLocationUpdates() { @@ -66,7 +65,7 @@ import CoreLocation locationAdded = true } if !locationAdded { - print("Bad Location \(self.count): \(loc)") + //print("Bad Location \(self.count): \(loc)") } } } @@ -85,12 +84,15 @@ import CoreLocation func addLocation(_ location: CLLocation) -> Bool { let age = -location.timestamp.timeIntervalSinceNow if age > 10 { + print("Bad Location \(self.count): Too Old \(location)") return false } if location.horizontalAccuracy < 0 { + print("Bad Location \(self.count): Horizontal Accuracy: \(location.horizontalAccuracy) \(location)") return false } if location.horizontalAccuracy > 100 { + print("Bad Location \(self.count): Horizontal Accuracy: \(location.horizontalAccuracy) \(location)") return false } locationsArray.append(location) diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index b8f48d7e..92139355 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -268,6 +268,7 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje newUser.shortName = nodeInfo.user.shortName newUser.hwModel = String(describing: nodeInfo.user.hwModel).uppercased() newUser.isLicensed = nodeInfo.user.isLicensed + newUser.role = Int32(nodeInfo.user.role.rawValue) newNode.user = newUser } else { let newUser = UserEntity(context: context) @@ -337,6 +338,7 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje fetchedNode[0].user!.longName = nodeInfo.user.longName fetchedNode[0].user!.shortName = nodeInfo.user.shortName fetchedNode[0].user!.isLicensed = nodeInfo.user.isLicensed + fetchedNode[0].user!.role = Int32(nodeInfo.user.role.rawValue) fetchedNode[0].user!.hwModel = String(describing: nodeInfo.user.hwModel).uppercased() } else { if (fetchedNode[0].user == nil) { diff --git a/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift b/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift index 2fb63b53..fec2e026 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift @@ -52,6 +52,14 @@ struct NodeListItem: View { LastHeardText(lastHeard: node.lastHeard) .font(.callout) } + HStack { + let role = DeviceRoles(rawValue: Int(node.user?.role ?? 0)) + Image(systemName: role?.systemName ?? "figure") + .font(.callout) + .symbolRenderingMode(.hierarchical) + Text("Role: \(role?.name ?? "unknown".localized)") + .font(.callout) + } if node.positions?.count ?? 0 > 0 && connectedNode != node.num { HStack { let lastPostion = node.positions!.reversed()[0] as! PositionEntity @@ -89,7 +97,6 @@ struct NodeListItem: View { .font(.callout) } } - if !connected { HStack { let preset = ModemPresets(rawValue: Int(modemPreset)) diff --git a/Meshtastic/Views/Nodes/TraceRouteLog.swift b/Meshtastic/Views/Nodes/TraceRouteLog.swift index a5e00a8b..dde3db46 100644 --- a/Meshtastic/Views/Nodes/TraceRouteLog.swift +++ b/Meshtastic/Views/Nodes/TraceRouteLog.swift @@ -46,9 +46,14 @@ struct TraceRouteLog: View { VStack { if selectedRoute != nil { if selectedRoute?.response ?? false && selectedRoute?.hops?.count ?? 0 > 0 { - Text("Received by \(selectedRoute?.node?.user?.longName ?? "unknown".localized)") - Text("Route: \(selectedRoute?.routeText ?? "unknown".localized)") - .font(.title3) + + Label { + Text("Route: \(selectedRoute?.routeText ?? "unknown".localized)") + } icon: { + Image(systemName: "signpost.right.and.left") + .symbolRenderingMode(.hierarchical) + } + .font(.title2) } else if selectedRoute?.response ?? false { Label { Text("Trace route received directly by \(selectedRoute?.node?.user?.longName ?? "unknown".localized)") @@ -56,7 +61,7 @@ struct TraceRouteLog: View { Image(systemName: "signpost.right.and.left") .symbolRenderingMode(.hierarchical) } - .font(.title3) + .font(.title2) } let hopsArray = selectedRoute?.hops?.array as? [TraceRouteHopEntity] ?? [] @@ -128,15 +133,6 @@ struct TraceRouteLog: View { .symbolRenderingMode(.hierarchical) } .font(.title3) - Divider() - Label { - Text("\(selectedRoute?.time?.formatted() ?? "") - No response") - - } icon: { - Image(systemName: "person.slash") - .symbolRenderingMode(.hierarchical) - } - .font(.callout) Spacer() } } From bde7bb684b329af6a9c32f553b73949444e3c9dc Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Thu, 21 Dec 2023 20:17:55 -0800 Subject: [PATCH 4/9] Automatically favorite connected node --- Meshtastic/Helpers/BLEManager.swift | 13 +++++++++++++ Meshtastic/Views/Nodes/NodeList.swift | 2 ++ 2 files changed, 15 insertions(+) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 34ff1f52..360e0723 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -734,6 +734,19 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate if fetchedNodeInfo.count == 1 && fetchedNodeInfo[0].storeForwardConfig?.enabled == true { wantStoreAndForwardPackets = true; } + if fetchedNodeInfo.count == 1 { + if !(fetchedNodeInfo[0].user?.vip ?? false) { + fetchedNodeInfo[0].user?.vip = true + do { + try context!.save() + + } catch { + context!.rollback() + let nsError = error as NSError + print("💥 Core Data error. Error: \(nsError)") + } + } + } } catch { print("Failed to find a node info for the connected node") diff --git a/Meshtastic/Views/Nodes/NodeList.swift b/Meshtastic/Views/Nodes/NodeList.swift index a61b9269..e28df31f 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -12,6 +12,7 @@ struct NodeList: View { @State private var columnVisibility = NavigationSplitViewVisibility.all @State private var selectedNode: NodeInfoEntity? @State private var isPresentingTraceRouteSentAlert = false + @State private var isPresentingDeleteNodeAlert = false @SceneStorage("selectedDetailView") var selectedDetailView: String? @@ -85,6 +86,7 @@ struct NodeList: View { } if bleManager.connectedPeripheral != nil { Button (role: .destructive) { + //isPresentingDeleteNodeAlert let success = bleManager.removeNode(node: node, connectedNodeNum: Int64(connectedNodeNum)) if !success { print("Failed to delete node \(node.user?.longName ?? "unknown".localized)") From 668fe30806494ec4e38286c341b89368deca0036 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Fri, 22 Dec 2023 06:31:27 -0800 Subject: [PATCH 5/9] Use locationshandler for GPS data on ios17 --- Meshtastic/Helpers/LocationsHandler.swift | 2 +- Meshtastic/Persistence/UpdateCoreData.swift | 10 ++- Meshtastic/Views/Settings/AppSettings.swift | 81 ++++++++++++++++----- 3 files changed, 70 insertions(+), 23 deletions(-) diff --git a/Meshtastic/Helpers/LocationsHandler.swift b/Meshtastic/Helpers/LocationsHandler.swift index ebe05903..d214f5ee 100644 --- a/Meshtastic/Helpers/LocationsHandler.swift +++ b/Meshtastic/Helpers/LocationsHandler.swift @@ -84,7 +84,7 @@ import CoreLocation func addLocation(_ location: CLLocation) -> Bool { let age = -location.timestamp.timeIntervalSinceNow if age > 10 { - print("Bad Location \(self.count): Too Old \(location)") + print("Bad Location \(self.count): Too Old \(age) seconds ago \(location)") return false } if location.horizontalAccuracy < 0 { diff --git a/Meshtastic/Persistence/UpdateCoreData.swift b/Meshtastic/Persistence/UpdateCoreData.swift index 2e6ed8a2..3a2cf34c 100644 --- a/Meshtastic/Persistence/UpdateCoreData.swift +++ b/Meshtastic/Persistence/UpdateCoreData.swift @@ -156,7 +156,9 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) // Update an existing node fetchedNode[0].id = Int64(packet.from) fetchedNode[0].num = Int64(packet.from) - fetchedNode[0].lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(packet.rxTime))) + if packet.rxTime > 0 { + fetchedNode[0].lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(packet.rxTime))) + } fetchedNode[0].snr = packet.rxSnr fetchedNode[0].rssi = packet.rxRssi @@ -268,7 +270,11 @@ func upsertPositionPacket (packet: MeshPacket, context: NSManagedObjectContext) mutablePositions.add(position) fetchedNode[0].id = Int64(packet.from) fetchedNode[0].num = Int64(packet.from) - fetchedNode[0].lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(positionMessage.time))) + if positionMessage.time > 0 { + fetchedNode[0].lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(positionMessage.time))) + } else if packet.rxTime > 0 { + fetchedNode[0].lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(packet.rxTime))) + } fetchedNode[0].snr = packet.rxSnr fetchedNode[0].rssi = packet.rxRssi fetchedNode[0].positions = mutablePositions.copy() as? NSOrderedSet diff --git a/Meshtastic/Views/Settings/AppSettings.swift b/Meshtastic/Views/Settings/AppSettings.swift index d1c18930..ffa51ba4 100644 --- a/Meshtastic/Views/Settings/AppSettings.swift +++ b/Meshtastic/Views/Settings/AppSettings.swift @@ -32,29 +32,70 @@ struct AppSettings: View { .toggleStyle(SwitchToggleStyle(tint: .accentColor)) } Section(header: Text("phone.gps")) { - let accuracy = Measurement(value: locationHelper.locationManager.location?.horizontalAccuracy ?? 300, unit: UnitLength.meters) - let altitiude = Measurement(value: locationHelper.locationManager.location?.altitude ?? 0, unit: UnitLength.meters) - let speed = Measurement(value: locationHelper.locationManager.location?.speed ?? 0, unit: UnitSpeed.kilometersPerHour) - HStack { - Label("Accuracy \(accuracy.formatted())", systemImage: "scope") + if #available(iOS 17.0, macOS 14.0, *) { + let horizontalAccuracy = Measurement(value: LocationsHandler.shared.lastLocation.horizontalAccuracy, unit: UnitLength.meters) + let verticalAccuracy = Measurement(value: LocationsHandler.shared.lastLocation.verticalAccuracy, unit: UnitLength.meters) + let altitiude = Measurement(value: LocationsHandler.shared.lastLocation.altitude, unit: UnitLength.meters) + let speed = Measurement(value: LocationsHandler.shared.lastLocation.speed, unit: UnitSpeed.kilometersPerHour) + let speedAccuracy = Measurement(value: LocationsHandler.shared.lastLocation.speedAccuracy, unit: UnitSpeed.metersPerSecond) + Label("Coordinate \(String(format: "%.5f", LocationsHandler.shared.lastLocation.coordinate.latitude)), \(String(format: "%.5f", LocationsHandler.shared.lastLocation.coordinate.longitude))", systemImage: "mappin") .font(.footnote) - Label("Sats \(LocationHelper.satsInView)", systemImage: "sparkles") + .textSelection(.enabled) + HStack { + Label("Accuracy \(horizontalAccuracy.formatted())", systemImage: "scope") + .font(.footnote) + Label("Sats \(LocationsHandler.satsInView)", systemImage: "sparkles") + .font(.footnote) + } + HStack { + if LocationsHandler.shared.lastLocation.verticalAccuracy > 0 { + Label("Altitude \(altitiude.formatted())", systemImage: "mountain.2") + .font(.footnote) + } + Label("Accuracy \(verticalAccuracy.formatted())", systemImage: "lines.measurement.vertical") + .font(.footnote) + } + if LocationsHandler.shared.lastLocation.courseAccuracy > 0 { + let degrees = Angle.degrees(Double(LocationsHandler.shared.lastLocation.course)) + Label { + let heading = Measurement(value: degrees.degrees, unit: UnitAngle.degrees) + Text("Heading: \(heading.formatted())") + } icon: { + Image(systemName: "location.north") + .symbolRenderingMode(.hierarchical) + .rotationEffect(degrees) + } .font(.footnote) - } - Label("Coordinate \(String(format: "%.5f", locationHelper.locationManager.location?.coordinate.latitude ?? 0)), \(String(format: "%.5f", locationHelper.locationManager.location?.coordinate.longitude ?? 0))", systemImage: "mappin") - .font(.footnote) - .textSelection(.enabled) - if locationHelper.locationManager.location?.verticalAccuracy ?? 0 > 0 { - Label("Altitude \(altitiude.formatted())", systemImage: "mountain.2") - .font(.footnote) - } - if locationHelper.locationManager.location?.courseAccuracy ?? 0 > 0 { - Label("Heading \(String(format: "%.2f", locationHelper.locationManager.location?.course ?? 0))°", systemImage: "location.circle") - .font(.footnote) - } - if locationHelper.locationManager.location?.speedAccuracy ?? 0 > 0 { - Label("Speed \(speed.formatted())", systemImage: "speedometer") + } + if LocationsHandler.shared.lastLocation.speedAccuracy > 0 { + Label("Speed \(speed.formatted())", systemImage: "speedometer") + .font(.footnote) + } + } else { + let accuracy = Measurement(value: locationHelper.locationManager.location?.horizontalAccuracy ?? 300, unit: UnitLength.meters) + let altitiude = Measurement(value: locationHelper.locationManager.location?.altitude ?? 0, unit: UnitLength.meters) + let speed = Measurement(value: locationHelper.locationManager.location?.speed ?? 0, unit: UnitSpeed.kilometersPerHour) + HStack { + Label("Accuracy \(accuracy.formatted())", systemImage: "scope") + .font(.footnote) + Label("Sats \(LocationHelper.satsInView)", systemImage: "sparkles") + .font(.footnote) + } + Label("Coordinate \(String(format: "%.5f", locationHelper.locationManager.location?.coordinate.latitude ?? 0)), \(String(format: "%.5f", locationHelper.locationManager.location?.coordinate.longitude ?? 0))", systemImage: "mappin") .font(.footnote) + .textSelection(.enabled) + if locationHelper.locationManager.location?.verticalAccuracy ?? 0 > 0 { + Label("Altitude \(altitiude.formatted())", systemImage: "mountain.2") + .font(.footnote) + } + if locationHelper.locationManager.location?.courseAccuracy ?? 0 > 0 { + Label("Heading \(String(format: "%.2f", locationHelper.locationManager.location?.course ?? 0))°", systemImage: "location.circle") + .font(.footnote) + } + if locationHelper.locationManager.location?.speedAccuracy ?? 0 > 0 { + Label("Speed \(speed.formatted())", systemImage: "speedometer") + .font(.footnote) + } } } Section(header: Text("Location Settings")) { From ee6afcc0cbbdc9f1cb2dfe4317e15a704340015a Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Fri, 22 Dec 2023 08:25:59 -0800 Subject: [PATCH 6/9] Updated GPS view for ios 17 --- Meshtastic.xcodeproj/project.pbxproj | 4 ++ .../CoreData/NodeInfoEntityExtension.swift | 7 ++- Meshtastic/MeshtasticApp.swift | 9 +++ .../Views/Nodes/Helpers/NodeListItem.swift | 5 ++ Meshtastic/Views/Settings/AppSettings.swift | 39 +----------- Meshtastic/Views/Settings/GPSStatus.swift | 62 +++++++++++++++++++ 6 files changed, 85 insertions(+), 41 deletions(-) create mode 100644 Meshtastic/Views/Settings/GPSStatus.swift diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index b6394ac6..e3ff05c8 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -21,6 +21,7 @@ DD1925B928CDA93900720036 /* SerialConfigEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1925B828CDA93900720036 /* SerialConfigEnums.swift */; }; DD1933762B0835D500771CD5 /* PositionAltitudeChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1933752B0835D500771CD5 /* PositionAltitudeChart.swift */; }; DD1933782B084F4200771CD5 /* Measurement.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1933772B084F4200771CD5 /* Measurement.swift */; }; + DD1B8F402B35E2F10022AABC /* GPSStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1B8F3F2B35E2F10022AABC /* GPSStatus.swift */; }; DD1BF2F92776FE2E008C8D2F /* UserMessageList.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1BF2F82776FE2E008C8D2F /* UserMessageList.swift */; }; DD2160AF28C5552500C17253 /* MQTTConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2160AE28C5552500C17253 /* MQTTConfig.swift */; }; DD23A50F26FD1B4400D9B90C /* PeripheralModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD23A50E26FD1B4400D9B90C /* PeripheralModel.swift */; }; @@ -235,6 +236,7 @@ DD1925B828CDA93900720036 /* SerialConfigEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SerialConfigEnums.swift; sourceTree = ""; }; DD1933752B0835D500771CD5 /* PositionAltitudeChart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PositionAltitudeChart.swift; sourceTree = ""; }; DD1933772B084F4200771CD5 /* Measurement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Measurement.swift; sourceTree = ""; }; + DD1B8F3F2B35E2F10022AABC /* GPSStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GPSStatus.swift; sourceTree = ""; }; DD1BF2F82776FE2E008C8D2F /* UserMessageList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserMessageList.swift; sourceTree = ""; }; DD2160AE28C5552500C17253 /* MQTTConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MQTTConfig.swift; sourceTree = ""; }; DD23A50E26FD1B4400D9B90C /* PeripheralModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeripheralModel.swift; sourceTree = ""; }; @@ -552,6 +554,7 @@ DD3CC6B428E33FD100FA9159 /* ShareChannels.swift */, DDCE4E2B2869F92900BE9F8F /* UserConfig.swift */, DD61937A2863876A00E59241 /* Config */, + DD1B8F3F2B35E2F10022AABC /* GPSStatus.swift */, ); path = Settings; sourceTree = ""; @@ -1222,6 +1225,7 @@ DDB75A112A059258006ED576 /* Url.swift in Sources */, DDAD49ED2AFB39DC00B4425D /* MeshMap.swift in Sources */, DD8169FB271F1F3A00F4AB02 /* MeshLog.swift in Sources */, + DD1B8F402B35E2F10022AABC /* GPSStatus.swift in Sources */, DD8ED9C52898D51F00B3B0AB /* NetworkConfig.swift in Sources */, DDC3B274283F411B00AC321C /* LastHeardText.swift in Sources */, DDDE5A1029AFE69700490C6C /* MeshActivityAttributes.swift in Sources */, diff --git a/Meshtastic/Extensions/CoreData/NodeInfoEntityExtension.swift b/Meshtastic/Extensions/CoreData/NodeInfoEntityExtension.swift index c041a7dd..09673104 100644 --- a/Meshtastic/Extensions/CoreData/NodeInfoEntityExtension.swift +++ b/Meshtastic/Extensions/CoreData/NodeInfoEntityExtension.swift @@ -14,13 +14,11 @@ extension NodeInfoEntity { } var hasDeviceMetrics: Bool { - let deviceMetrics = telemetries?.filter{ ($0 as AnyObject).metricsType == 0 } return deviceMetrics?.count ?? 0 > 0 } var hasEnvironmentMetrics: Bool { - let environmentMetrics = telemetries?.filter{ ($0 as AnyObject).metricsType == 1 } return environmentMetrics?.count ?? 0 > 0 } @@ -28,8 +26,11 @@ extension NodeInfoEntity { return user?.sensorMessageList.count ?? 0 > 0 } + var hasTraceRoutes: Bool { + return traceRoutes?.count ?? 0 > 0 + } + var isOnline: Bool { - let fifteenMinutesAgo = Calendar.current.date(byAdding: .minute, value: -15, to: Date()) if lastHeard?.compare(fifteenMinutesAgo!) == .orderedDescending { return true diff --git a/Meshtastic/MeshtasticApp.swift b/Meshtastic/MeshtasticApp.swift index 5dee2b41..a8195b42 100644 --- a/Meshtastic/MeshtasticApp.swift +++ b/Meshtastic/MeshtasticApp.swift @@ -8,9 +8,18 @@ import TipKit @main struct MeshtasticAppleApp: App { + + private static var documentsFolder: URL { + do { + return try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true) + } catch { + fatalError("Can't find documents directory.") + } + } @UIApplicationDelegateAdaptor(MeshtasticAppDelegate.self) var appDelegate let persistenceController = PersistenceController.shared @ObservedObject private var bleManager: BLEManager = BLEManager() + @Environment(\.scenePhase) var scenePhase @State var saveChannels = false diff --git a/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift b/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift index fec2e026..b8fafe88 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift @@ -123,6 +123,11 @@ struct NodeListItem: View { .symbolRenderingMode(.hierarchical) .font(.callout) } + if node.hasTraceRoutes { + Image(systemName: "signpost.right.and.left") + .symbolRenderingMode(.hierarchical) + .font(.callout) + } } .padding(.top, 3) } diff --git a/Meshtastic/Views/Settings/AppSettings.swift b/Meshtastic/Views/Settings/AppSettings.swift index ffa51ba4..f6baab3d 100644 --- a/Meshtastic/Views/Settings/AppSettings.swift +++ b/Meshtastic/Views/Settings/AppSettings.swift @@ -33,44 +33,7 @@ struct AppSettings: View { } Section(header: Text("phone.gps")) { if #available(iOS 17.0, macOS 14.0, *) { - let horizontalAccuracy = Measurement(value: LocationsHandler.shared.lastLocation.horizontalAccuracy, unit: UnitLength.meters) - let verticalAccuracy = Measurement(value: LocationsHandler.shared.lastLocation.verticalAccuracy, unit: UnitLength.meters) - let altitiude = Measurement(value: LocationsHandler.shared.lastLocation.altitude, unit: UnitLength.meters) - let speed = Measurement(value: LocationsHandler.shared.lastLocation.speed, unit: UnitSpeed.kilometersPerHour) - let speedAccuracy = Measurement(value: LocationsHandler.shared.lastLocation.speedAccuracy, unit: UnitSpeed.metersPerSecond) - Label("Coordinate \(String(format: "%.5f", LocationsHandler.shared.lastLocation.coordinate.latitude)), \(String(format: "%.5f", LocationsHandler.shared.lastLocation.coordinate.longitude))", systemImage: "mappin") - .font(.footnote) - .textSelection(.enabled) - HStack { - Label("Accuracy \(horizontalAccuracy.formatted())", systemImage: "scope") - .font(.footnote) - Label("Sats \(LocationsHandler.satsInView)", systemImage: "sparkles") - .font(.footnote) - } - HStack { - if LocationsHandler.shared.lastLocation.verticalAccuracy > 0 { - Label("Altitude \(altitiude.formatted())", systemImage: "mountain.2") - .font(.footnote) - } - Label("Accuracy \(verticalAccuracy.formatted())", systemImage: "lines.measurement.vertical") - .font(.footnote) - } - if LocationsHandler.shared.lastLocation.courseAccuracy > 0 { - let degrees = Angle.degrees(Double(LocationsHandler.shared.lastLocation.course)) - Label { - let heading = Measurement(value: degrees.degrees, unit: UnitAngle.degrees) - Text("Heading: \(heading.formatted())") - } icon: { - Image(systemName: "location.north") - .symbolRenderingMode(.hierarchical) - .rotationEffect(degrees) - } - .font(.footnote) - } - if LocationsHandler.shared.lastLocation.speedAccuracy > 0 { - Label("Speed \(speed.formatted())", systemImage: "speedometer") - .font(.footnote) - } + GPSStatus() } else { let accuracy = Measurement(value: locationHelper.locationManager.location?.horizontalAccuracy ?? 300, unit: UnitLength.meters) let altitiude = Measurement(value: locationHelper.locationManager.location?.altitude ?? 0, unit: UnitLength.meters) diff --git a/Meshtastic/Views/Settings/GPSStatus.swift b/Meshtastic/Views/Settings/GPSStatus.swift new file mode 100644 index 00000000..72308fd9 --- /dev/null +++ b/Meshtastic/Views/Settings/GPSStatus.swift @@ -0,0 +1,62 @@ +// +// GPSStatus.swift +// Meshtastic +// +// Copyright(c) by Garth Vander Houwen 12/22/23. +// + +import SwiftUI +import CoreLocation + +@available(iOS 17.0, macOS 14.0, *) +struct GPSStatus: View { + + @ObservedObject var locationsHandler: LocationsHandler = LocationsHandler.shared + + var body: some View { + + let horizontalAccuracy = Measurement(value: locationsHandler.lastLocation.horizontalAccuracy, unit: UnitLength.meters) + let verticalAccuracy = Measurement(value: locationsHandler.lastLocation.verticalAccuracy, unit: UnitLength.meters) + let altitiude = Measurement(value: locationsHandler.lastLocation.altitude, unit: UnitLength.meters) + let speed = Measurement(value: locationsHandler.lastLocation.speed, unit: UnitSpeed.kilometersPerHour) + let speedAccuracy = Measurement(value: locationsHandler.lastLocation.speedAccuracy, unit: UnitSpeed.metersPerSecond) + let courseAccuracy = Measurement(value: locationsHandler.lastLocation.courseAccuracy, unit: UnitAngle.degrees) + Label("Coordinate \(String(format: "%.5f", locationsHandler.lastLocation.coordinate.latitude)), \(String(format: "%.5f", LocationsHandler.shared.lastLocation.coordinate.longitude))", systemImage: "mappin") + .font(.footnote) + .textSelection(.enabled) + HStack { + Label("Accuracy \(horizontalAccuracy.formatted())", systemImage: "scope") + .font(.footnote) + Label("Sats Estimate \(LocationsHandler.satsInView)", systemImage: "sparkles") + .font(.footnote) + } + HStack { + if locationsHandler.lastLocation.verticalAccuracy > 0 { + Label("Altitude \(altitiude.formatted())", systemImage: "mountain.2") + .font(.footnote) + } + Label("Accuracy \(verticalAccuracy.formatted())", systemImage: "lines.measurement.vertical") + .font(.caption2) + } + HStack { + let degrees = Angle.degrees(LocationsHandler.shared.lastLocation.course) + Label { + let heading = Measurement(value: degrees.degrees, unit: UnitAngle.degrees) + Text("Heading: \(heading.formatted())") + } icon: { + Image(systemName: "location.north") + .symbolRenderingMode(.hierarchical) + .rotationEffect(degrees) + } + .font(.footnote) + Label("Accuracy \(courseAccuracy.formatted())", systemImage: "safari") + .font(.caption2) + } + HStack { + Label("Speed \(speed.formatted())", systemImage: "speedometer") + .font(.footnote) + Label("Accuracy \(speedAccuracy.formatted())", systemImage: "gauge.with.dots.needle.bottom.50percent.badge.plus") + .font(.caption2) + } + } +} From 658985b18aeaee4dfad1c03fca9060ef5a4d6be3 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Fri, 22 Dec 2023 16:10:00 -0800 Subject: [PATCH 7/9] Delete confirmation dialog --- Meshtastic/Views/Nodes/NodeList.swift | 28 +++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/Meshtastic/Views/Nodes/NodeList.swift b/Meshtastic/Views/Nodes/NodeList.swift index e28df31f..fec6c003 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -13,6 +13,7 @@ struct NodeList: View { @State private var selectedNode: NodeInfoEntity? @State private var isPresentingTraceRouteSentAlert = false @State private var isPresentingDeleteNodeAlert = false + @State private var deleteNodeId: Int64 = 0 @SceneStorage("selectedDetailView") var selectedDetailView: String? @@ -86,11 +87,8 @@ struct NodeList: View { } if bleManager.connectedPeripheral != nil { Button (role: .destructive) { - //isPresentingDeleteNodeAlert - let success = bleManager.removeNode(node: node, connectedNodeNum: Int64(connectedNodeNum)) - if !success { - print("Failed to delete node \(node.user?.longName ?? "unknown".localized)") - } + deleteNodeId = node.num + isPresentingDeleteNodeAlert = true } label: { Label("Delete Node", systemImage: "trash") } @@ -109,7 +107,25 @@ struct NodeList: View { .searchable(text: nodesQuery, prompt: "Find a node") .navigationTitle(String.localizedStringWithFormat("nodes %@".localized, String(nodes.count))) .listStyle(.plain) - + .confirmationDialog( + + "are.you.sure", + isPresented: $isPresentingDeleteNodeAlert, + titleVisibility: .visible + ) { + Button("Delete Node") { + let deleteNode = getNodeInfo(id: deleteNodeId, context: context) + if connectedNode != nil { + + } + if deleteNode != nil { + let success = bleManager.removeNode(node: deleteNode!, connectedNodeNum: Int64(connectedNodeNum)) + if !success { + print("Failed to delete node \(deleteNode?.user?.longName ?? "unknown".localized)") + } + } + } + } .navigationSplitViewColumnWidth(min: 100, ideal: 250, max: 500) .navigationBarItems(leading: MeshtasticLogo(), From 61840f5591ddada291f9b20da5b0cb9e6159e972 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Fri, 22 Dec 2023 16:43:33 -0800 Subject: [PATCH 8/9] Cleanup --- Meshtastic/MeshtasticApp.swift | 7 ------- Meshtastic/Views/Settings/GPSStatus.swift | 2 -- 2 files changed, 9 deletions(-) diff --git a/Meshtastic/MeshtasticApp.swift b/Meshtastic/MeshtasticApp.swift index a8195b42..48e4c020 100644 --- a/Meshtastic/MeshtasticApp.swift +++ b/Meshtastic/MeshtasticApp.swift @@ -9,13 +9,6 @@ import TipKit @main struct MeshtasticAppleApp: App { - private static var documentsFolder: URL { - do { - return try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true) - } catch { - fatalError("Can't find documents directory.") - } - } @UIApplicationDelegateAdaptor(MeshtasticAppDelegate.self) var appDelegate let persistenceController = PersistenceController.shared @ObservedObject private var bleManager: BLEManager = BLEManager() diff --git a/Meshtastic/Views/Settings/GPSStatus.swift b/Meshtastic/Views/Settings/GPSStatus.swift index 72308fd9..ea8a9c52 100644 --- a/Meshtastic/Views/Settings/GPSStatus.swift +++ b/Meshtastic/Views/Settings/GPSStatus.swift @@ -12,9 +12,7 @@ import CoreLocation struct GPSStatus: View { @ObservedObject var locationsHandler: LocationsHandler = LocationsHandler.shared - var body: some View { - let horizontalAccuracy = Measurement(value: locationsHandler.lastLocation.horizontalAccuracy, unit: UnitLength.meters) let verticalAccuracy = Measurement(value: locationsHandler.lastLocation.verticalAccuracy, unit: UnitLength.meters) let altitiude = Measurement(value: locationsHandler.lastLocation.altitude, unit: UnitLength.meters) From 0dbc426c508d2255cf4dd2de56912490d3e9b35e Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Fri, 22 Dec 2023 19:08:32 -0800 Subject: [PATCH 9/9] Set default GPS interval values --- Meshtastic/Persistence/UpdateCoreData.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Meshtastic/Persistence/UpdateCoreData.swift b/Meshtastic/Persistence/UpdateCoreData.swift index 3a2cf34c..f39420e9 100644 --- a/Meshtastic/Persistence/UpdateCoreData.swift +++ b/Meshtastic/Persistence/UpdateCoreData.swift @@ -600,6 +600,8 @@ func upsertPositionConfigPacket(config: Meshtastic.Config.PositionConfig, nodeNu newPositionConfig.broadcastSmartMinimumIntervalSecs = Int32(config.broadcastSmartMinimumIntervalSecs) newPositionConfig.broadcastSmartMinimumDistance = Int32(config.broadcastSmartMinimumDistance) newPositionConfig.positionFlags = Int32(config.positionFlags) + newPositionConfig.gpsAttemptTime = 900 + newPositionConfig.gpsUpdateInterval = 120 fetchedNode[0].positionConfig = newPositionConfig } else { fetchedNode[0].positionConfig?.smartPositionEnabled = config.positionBroadcastSmartEnabled @@ -611,6 +613,8 @@ func upsertPositionConfigPacket(config: Meshtastic.Config.PositionConfig, nodeNu fetchedNode[0].positionConfig?.positionBroadcastSeconds = Int32(config.positionBroadcastSecs) fetchedNode[0].positionConfig?.broadcastSmartMinimumIntervalSecs = Int32(config.broadcastSmartMinimumIntervalSecs) fetchedNode[0].positionConfig?.broadcastSmartMinimumDistance = Int32(config.broadcastSmartMinimumDistance) + fetchedNode[0].positionConfig?.gpsAttemptTime = 900 + fetchedNode[0].positionConfig?.gpsUpdateInterval = 120 fetchedNode[0].positionConfig?.positionFlags = Int32(config.positionFlags) } do {