From 7ab0a616c1ceaa835247a0e4d67ae154105307bd Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sat, 11 Feb 2023 07:47:49 -0800 Subject: [PATCH 01/19] Un replace quotes --- Meshtastic/Helpers/BLEManager.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index e8b47d24..493088a0 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -674,7 +674,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { newMessage.messagePayloadMarkdown = generateMessageMarkdown(message: message) let dataType = PortNum.textMessageApp - let payloadData: Data = message.data(using: String.Encoding.utf8)! + var messageQuotesReplaced = message.replacingOccurrences(of: "’", with: "'") + messageQuotesReplaced = message.replacingOccurrences(of: "”", with: "\"") + let payloadData: Data = messageQuotesReplaced.data(using: String.Encoding.utf8)! var dataMessage = DataMessage() dataMessage.payload = payloadData From ff14dbd6bfb58680f18d4c70385e445571912a5c Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sat, 11 Feb 2023 07:48:39 -0800 Subject: [PATCH 02/19] bump version --- Meshtastic.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index ea2501f9..dd1bf823 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -1075,7 +1075,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.0.14; + MARKETING_VERSION = 2.0.15; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; @@ -1108,7 +1108,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.0.14; + MARKETING_VERSION = 2.0.15; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; From 322edc9e0d11eaf04850d157f12afbec8cc96249 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 12 Feb 2023 20:11:50 -0800 Subject: [PATCH 03/19] Fix share channels crash --- Meshtastic/Views/Settings/ShareChannels.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Meshtastic/Views/Settings/ShareChannels.swift b/Meshtastic/Views/Settings/ShareChannels.swift index da2655ec..79d91a28 100644 --- a/Meshtastic/Views/Settings/ShareChannels.swift +++ b/Meshtastic/Views/Settings/ShareChannels.swift @@ -288,7 +288,7 @@ struct ShareChannels: View { loRaConfig.usePreset = node?.loRaConfig?.usePreset ?? true loRaConfig.channelNum = UInt32(node?.loRaConfig?.channelNum ?? 0) channelSet.loraConfig = loRaConfig - if node != nil && node?.myInfo != nil { + if node?.myInfo?.channels != nil && node?.myInfo?.channels?.count ?? 0 > 0 { for ch in node!.myInfo!.channels!.array as! [ChannelEntity] { if ch.role > 0 { From d4d5f383bc791edf6e6b9f22f33206bb2966d86e Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Mon, 13 Feb 2023 07:27:41 -0800 Subject: [PATCH 04/19] Get metadata if null for connected node --- Meshtastic/Views/Settings/Settings.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Meshtastic/Views/Settings/Settings.swift b/Meshtastic/Views/Settings/Settings.swift index a36e79bb..d22fd37b 100644 --- a/Meshtastic/Views/Settings/Settings.swift +++ b/Meshtastic/Views/Settings/Settings.swift @@ -81,7 +81,7 @@ struct Settings: View { let connectedNode = nodes.first(where: { $0.num == connectedNodeNum }) connectedNodeNum = Int(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.num : 0) - if node?.metadata == nil && node!.num != connectedNodeNum { + if node?.metadata == nil { let adminMessageId = bleManager.requestDeviceMetadata(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode!.myInfo!.adminIndex, context: context) if adminMessageId > 0 { From 4ec1f7b20054a9ae05df69b6c5c9b407de1ad88d Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Wed, 15 Feb 2023 14:52:49 -0800 Subject: [PATCH 05/19] * Map Updates * Clean up call sign on licensed user view * Narrow formatting for weather temp --- Meshtastic.xcodeproj/project.pbxproj | 4 -- Meshtastic/Helpers/BLEManager.swift | 56 ++++++++++++++++--- .../Views/Map/Custom/MapViewSwiftUI.swift | 15 +++-- Meshtastic/Views/Nodes/NodeDetail.swift | 2 +- Meshtastic/Views/Settings/LicensedUser.swift | 8 --- Meshtastic/Views/Settings/UserConfig.swift | 6 +- 6 files changed, 60 insertions(+), 31 deletions(-) delete mode 100644 Meshtastic/Views/Settings/LicensedUser.swift diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index dd1bf823..6337fbda 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -60,7 +60,6 @@ DD5E5212298EE33B00D21B61 /* apponly.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5E5200298EE33B00D21B61 /* apponly.pb.swift */; }; DD5E5213298EE33B00D21B61 /* deviceonly.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5E5201298EE33B00D21B61 /* deviceonly.pb.swift */; }; DD5E523A298EFA5300D21B61 /* TelemetryWeather.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5E5239298EFA5300D21B61 /* TelemetryWeather.swift */; }; - DD5E523C298F02D400D21B61 /* LicensedUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5E523B298F02D400D21B61 /* LicensedUser.swift */; }; DD5E523F298F5A9E00D21B61 /* AirQualityIndexCompact.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5E523E298F5A9E00D21B61 /* AirQualityIndexCompact.swift */; }; DD6193752862F6E600E59241 /* ExternalNotificationConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6193742862F6E600E59241 /* ExternalNotificationConfig.swift */; }; DD6193772862F90F00E59241 /* CannedMessagesConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6193762862F90F00E59241 /* CannedMessagesConfig.swift */; }; @@ -194,7 +193,6 @@ DD5E5200298EE33B00D21B61 /* apponly.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = apponly.pb.swift; sourceTree = ""; }; DD5E5201298EE33B00D21B61 /* deviceonly.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = deviceonly.pb.swift; sourceTree = ""; }; DD5E5239298EFA5300D21B61 /* TelemetryWeather.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelemetryWeather.swift; sourceTree = ""; }; - DD5E523B298F02D400D21B61 /* LicensedUser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LicensedUser.swift; sourceTree = ""; }; DD5E523E298F5A9E00D21B61 /* AirQualityIndexCompact.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirQualityIndexCompact.swift; sourceTree = ""; }; DD6193742862F6E600E59241 /* ExternalNotificationConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExternalNotificationConfig.swift; sourceTree = ""; }; DD6193762862F90F00E59241 /* CannedMessagesConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CannedMessagesConfig.swift; sourceTree = ""; }; @@ -343,7 +341,6 @@ DD0F791A28713C8A00A6FDAD /* AdminMessageList.swift */, DD4A911D2708C65400501B7E /* AppSettings.swift */, DDA0B6B1294CDC55001356EC /* Channels.swift */, - DD5E523B298F02D400D21B61 /* LicensedUser.swift */, DD8169FA271F1F3A00F4AB02 /* MeshLog.swift */, DD86D40B287F401000BAEB7A /* SaveChannelQRCode.swift */, DD3501882852FC3B000FC853 /* Settings.swift */, @@ -858,7 +855,6 @@ DDA6B2E928419CF2003E8C16 /* MeshPackets.swift in Sources */, DDCE4E2C2869F92900BE9F8F /* UserConfig.swift in Sources */, DD6193752862F6E600E59241 /* ExternalNotificationConfig.swift in Sources */, - DD5E523C298F02D400D21B61 /* LicensedUser.swift in Sources */, DDB6ABE428B13FFF00384BA1 /* DisplayEnums.swift in Sources */, DD86D40A287F04F100BAEB7A /* InvalidVersion.swift in Sources */, DDD94A502845C8F5004A87A0 /* DateTimeText.swift in Sources */, diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 493088a0..8f43c58d 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -8,7 +8,7 @@ import MapKit // Meshtastic BLE Device Manager // --------------------------------------------------------------------------------------- class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { - + private static var documentsFolder: URL { do { return try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true) @@ -16,11 +16,12 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { fatalError("Can't find documents directory.") } } - + var context: NSManagedObjectContext? var userSettings: UserSettings? private var centralManager: CBCentralManager! - + private let restoreKey = "Meshtastic.BLE.Manager" + @Published var peripherals: [Peripheral] = [] @Published var connectedPeripheral: Peripheral! @Published var lastConnectionError: String @@ -35,35 +36,36 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { public var isConnected: Bool = false public var isSubscribed: Bool = false private var configNonce: UInt32 = 1 - + var timeoutTimer: Timer? var timeoutTimerCount = 0 var timeoutTimerRuns = 0 var positionTimer: Timer? let emptyNodeNum: UInt32 = 4294967295 - + /* Meshtastic Service Details */ var TORADIO_characteristic: CBCharacteristic! var FROMRADIO_characteristic: CBCharacteristic! var FROMNUM_characteristic: CBCharacteristic! - + let meshtasticServiceCBUUID = CBUUID(string: "0x6BA1B218-15A8-461F-9FA8-5DCAE273EAFD") let TORADIO_UUID = CBUUID(string: "0xF75C76D2-129E-4DAD-A1DD-7866124401E7") let FROMRADIO_UUID = CBUUID(string: "0x2C55E69E-4993-11ED-B878-0242AC120002") let EOL_FROMRADIO_UUID = CBUUID(string: "0x8BA2BCC2-EE02-4A55-A531-C525C5E454D5") let FROMNUM_UUID = CBUUID(string: "0xED9DA18C-A800-4F66-A670-AA7547E34453") - + //private var meshLoggingEnabled: Bool = true let meshLog = documentsFolder.appendingPathComponent("meshlog.txt") - + // MARK: init BLEManager override init() { self.lastConnectionError = "" self.connectedVersion = "0.0.0" super.init() centralManager = CBCentralManager(delegate: self, queue: nil) + //centralManager = CBCentralManager(delegate: self, queue: nil, options: [CBCentralManagerOptionRestoreIdentifierKey: restoreKey]) } - + // MARK: Scanning for BLE Devices // Scan for nearby BLE devices using the Meshtastic BLE service ID func startScanning() { @@ -130,6 +132,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { print("ℹ️ BLE Disconnecting from: \(connectedPeripheral.name) to connect to \(peripheral.name ?? "Unknown")") disconnectPeripheral() } + centralManager?.connect(peripheral) // Invalidate any existing timer if timeoutTimer != nil { @@ -2001,4 +2004,39 @@ extension BLEManager: CBCentralManagerDelegate { let visibleDuration = Calendar.current.date(byAdding: .second, value: -5, to: today)! self.peripherals.removeAll(where: { $0.lastUpdate < visibleDuration}) } + + func centralManager(_ central: CBCentralManager, willRestoreState dict: [String : Any]) { + + guard let peripherals = dict[CBCentralManagerRestoredStatePeripheralsKey] as? [CBPeripheral] else { + return + } + print(peripherals) + if peripherals.count > 0 { + //connectedPeripheral.peripheral = peripherals[0] + // 5 + //connectedPeripheral.peripheral.delegate = self + + for peripheral in peripherals { + + switch peripheral.state { + case .connecting: // I've only seen this happen when + // re-launching attached to Xcode. + print("Xcode Restore") + + case .connected: // Store for connection / requesting + // notifications when BT starts. + print("Actual restore") + //centralManager.connect(peripheral) + default: break + } + + + + // connectedPeripheral.peripheral + //connectedPeripheral.peripheral = peripheral + //connectedPeripheral.peripheral.delegate = self + } + } + print("willRestoreState Hit!") + } } diff --git a/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift b/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift index 70e8a9e0..929536c9 100644 --- a/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift +++ b/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift @@ -12,8 +12,8 @@ struct MapViewSwiftUI: UIViewRepresentable { var onLongPress: (_ waypointCoordinate: CLLocationCoordinate2D) -> Void var onWaypointEdit: (_ waypointId: Int ) -> Void let mapView = MKMapView() - let positions: [PositionEntity] - let waypoints: [WaypointEntity] + dynamic var positions: [PositionEntity] + dynamic let waypoints: [WaypointEntity] let mapViewType: MKMapType let centerOnPositionsOnly: Bool @@ -120,17 +120,20 @@ struct MapViewSwiftUI: UIViewRepresentable { annotationView.tag = -1 return annotationView case let positionAnnotation as PositionEntity: - let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "node") as? MKMarkerAnnotationView ?? MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: "Node") + let reuseID = String(positionAnnotation.nodePosition?.num ?? 0) + "-" + String(positionAnnotation.time?.timeIntervalSince1970 ?? 0) + + let latest = parent.positions.last(where: { $0.nodePosition?.num ?? 0 == positionAnnotation.nodePosition?.num ?? -1 && true }) + + let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "node") as? MKMarkerAnnotationView ?? MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: reuseID ) annotationView.tag = -1 annotationView.canShowCallout = true annotationView.glyphText = "📟" - let latest = parent.positions.last(where: { $0.nodePosition?.num ?? 0 == positionAnnotation.nodePosition?.num ?? -1 }) - if latest == positionAnnotation { annotationView.markerTintColor = .systemRed annotationView.displayPriority = .required annotationView.titleVisibility = .visible + // annotationView.clusteringIdentifier = "nodeGroupLatest" } else { annotationView.markerTintColor = UIColor(.indigo) @@ -173,7 +176,7 @@ struct MapViewSwiftUI: UIViewRepresentable { annotationView.rightCalloutAccessoryView = detailsIcon return annotationView case let waypointAnnotation as WaypointEntity: - let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "waypoint") as? MKMarkerAnnotationView ?? MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: "Waypoint") + let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "waypoint") as? MKMarkerAnnotationView ?? MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: String(waypointAnnotation.id)) annotationView.tag = Int(waypointAnnotation.id) annotationView.isEnabled = true annotationView.canShowCallout = true diff --git a/Meshtastic/Views/Nodes/NodeDetail.swift b/Meshtastic/Views/Nodes/NodeDetail.swift index 057cac1b..113633e9 100644 --- a/Meshtastic/Views/Nodes/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/NodeDetail.swift @@ -84,7 +84,7 @@ struct NodeDetail: View { .pickerStyle(.menu) .padding(5) VStack { - Label(temperature?.formatted() ?? "??", systemImage: symbolName) + Label(temperature?.formatted(.measurement(width: .narrow)) ?? "??", systemImage: symbolName) .font(.caption) } .padding(10) diff --git a/Meshtastic/Views/Settings/LicensedUser.swift b/Meshtastic/Views/Settings/LicensedUser.swift deleted file mode 100644 index 113c4c1e..00000000 --- a/Meshtastic/Views/Settings/LicensedUser.swift +++ /dev/null @@ -1,8 +0,0 @@ -// -// LicensedUser.swift -// Meshtastic -// -// Created by Garth Vander Houwen on 2/4/23. -// - -import Foundation diff --git a/Meshtastic/Views/Settings/UserConfig.swift b/Meshtastic/Views/Settings/UserConfig.swift index dc5a208e..3501397b 100644 --- a/Meshtastic/Views/Settings/UserConfig.swift +++ b/Meshtastic/Views/Settings/UserConfig.swift @@ -48,8 +48,8 @@ struct UserConfig: View { .onChange(of: longName, perform: { value in let totalBytes = longName.utf8.count // Only mess with the value if it is too big - if totalBytes > 36 { - let firstNBytes = Data(longName.utf8.prefix(36)) + if totalBytes > (isLicensed ? 8 : 36) { + let firstNBytes = Data(longName.utf8.prefix(isLicensed ? 8 : 36)) if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) { // Set the longName back to the last place where it was the right size longName = maxBytesString @@ -59,7 +59,7 @@ struct UserConfig: View { } .keyboardType(.default) .disableAutocorrection(true) - Text("\(String(isLicensed ? "Call Sign" : "Long Name")) can be up to 36 bytes long.") + Text("\(String(isLicensed ? "Call Sign" : "Long Name")) can be up to \(isLicensed ? "8" : "36") bytes long.") .font(.caption2) HStack { From 91ce572accc45e4e9c338480a3918fd266739da8 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Fri, 17 Feb 2023 10:35:37 -0800 Subject: [PATCH 06/19] Update protobufs, show alert and clear database on change of preferred peripheral --- Meshtastic.xcodeproj/project.pbxproj | 8 +- Meshtastic/Helpers/BLEManager.swift | 6 + .../Meshtastic.xcdatamodeld/.xccurrentversion | 2 +- .../contents | 2 +- .../contents | 300 ++++++++++++++++++ .../Protobufs/meshtastic/admin.pb.swift | 10 + .../Protobufs/meshtastic/config.pb.swift | 11 + .../meshtastic/device_metadata.pb.swift | 157 --------- Meshtastic/Protobufs/meshtastic/mesh.pb.swift | 172 ++++++++++ Meshtastic/Views/Bluetooth/Connect.swift | 55 +++- Meshtastic/Views/ContentView.swift | 2 +- .../Views/Map/Custom/MapViewSwiftUI.swift | 2 +- Meshtastic/Views/Nodes/NodeDetail.swift | 5 +- Meshtastic/Views/Settings/AppSettings.swift | 4 - 14 files changed, 547 insertions(+), 189 deletions(-) create mode 100644 Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV8.xcdatamodel/contents delete mode 100644 Meshtastic/Protobufs/meshtastic/device_metadata.pb.swift diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 6337fbda..27c1dcce 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -50,7 +50,6 @@ DD5E5208298EE33B00D21B61 /* rtttl.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5E51F6298EE33B00D21B61 /* rtttl.pb.swift */; }; DD5E5209298EE33B00D21B61 /* module_config.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5E51F7298EE33B00D21B61 /* module_config.pb.swift */; }; DD5E520A298EE33B00D21B61 /* channel.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5E51F8298EE33B00D21B61 /* channel.pb.swift */; }; - DD5E520B298EE33B00D21B61 /* device_metadata.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5E51F9298EE33B00D21B61 /* device_metadata.pb.swift */; }; DD5E520C298EE33B00D21B61 /* portnums.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5E51FA298EE33B00D21B61 /* portnums.pb.swift */; }; DD5E520D298EE33B00D21B61 /* storeforward.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5E51FB298EE33B00D21B61 /* storeforward.pb.swift */; }; DD5E520E298EE33B00D21B61 /* mqtt.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5E51FC298EE33B00D21B61 /* mqtt.pb.swift */; }; @@ -183,7 +182,6 @@ DD5E51F6298EE33B00D21B61 /* rtttl.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = rtttl.pb.swift; sourceTree = ""; }; DD5E51F7298EE33B00D21B61 /* module_config.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = module_config.pb.swift; sourceTree = ""; }; DD5E51F8298EE33B00D21B61 /* channel.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = channel.pb.swift; sourceTree = ""; }; - DD5E51F9298EE33B00D21B61 /* device_metadata.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = device_metadata.pb.swift; sourceTree = ""; }; DD5E51FA298EE33B00D21B61 /* portnums.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = portnums.pb.swift; sourceTree = ""; }; DD5E51FB298EE33B00D21B61 /* storeforward.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = storeforward.pb.swift; sourceTree = ""; }; DD5E51FC298EE33B00D21B61 /* mqtt.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = mqtt.pb.swift; sourceTree = ""; }; @@ -237,6 +235,7 @@ DDB6ABE128B13FB500384BA1 /* PositionConfigEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PositionConfigEnums.swift; sourceTree = ""; }; DDB6ABE328B13FFF00384BA1 /* DisplayEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayEnums.swift; sourceTree = ""; }; DDB6ABE528B1406100384BA1 /* LoraConfigEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoraConfigEnums.swift; sourceTree = ""; }; + DDBA45EC299ED78100DEEDDC /* MeshtasticDataModelV8.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV8.xcdatamodel; sourceTree = ""; }; DDC2E15426CE248E0042C5E4 /* Meshtastic.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Meshtastic.app; sourceTree = BUILT_PRODUCTS_DIR; }; DDC2E15726CE248E0042C5E4 /* MeshtasticApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshtasticApp.swift; sourceTree = ""; }; DDC2E15B26CE248F0042C5E4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = ../Assets.xcassets; sourceTree = ""; }; @@ -363,7 +362,6 @@ DD5E51F6298EE33B00D21B61 /* rtttl.pb.swift */, DD5E51F7298EE33B00D21B61 /* module_config.pb.swift */, DD5E51F8298EE33B00D21B61 /* channel.pb.swift */, - DD5E51F9298EE33B00D21B61 /* device_metadata.pb.swift */, DD5E51FA298EE33B00D21B61 /* portnums.pb.swift */, DD5E51FB298EE33B00D21B61 /* storeforward.pb.swift */, DD5E51FC298EE33B00D21B61 /* mqtt.pb.swift */, @@ -847,7 +845,6 @@ DD8169FB271F1F3A00F4AB02 /* MeshLog.swift in Sources */, DD8ED9C52898D51F00B3B0AB /* NetworkConfig.swift in Sources */, DDC3B274283F411B00AC321C /* LastHeardText.swift in Sources */, - DD5E520B298EE33B00D21B61 /* device_metadata.pb.swift in Sources */, DD2E65262767A01F00E45FC5 /* NodeDetail.swift in Sources */, DD1925B928CDA93900720036 /* SerialConfigEnums.swift in Sources */, DD86D4112881D16900BAEB7A /* WriteCsvFile.swift in Sources */, @@ -1280,6 +1277,7 @@ DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */ = { isa = XCVersionGroup; children = ( + DDBA45EC299ED78100DEEDDC /* MeshtasticDataModelV8.xcdatamodel */, DD5E51CC2986643400D21B61 /* MeshtasticDataModelV7.xcdatamodel */, DD964FC029724F6D007C176F /* MeshtasticDataModelV6.xcdatamodel */, DD457BC4295D5E35004BCE4D /* MeshtasticDataModelV5.xcdatamodel */, @@ -1288,7 +1286,7 @@ DD5D0A9A2931AD6B00F7EA61 /* MeshtasticDataModelV2.xcdatamodel */, DD3CC6BB28E366DF00FA9159 /* MeshtasticDataModel.xcdatamodel */, ); - currentVersion = DD5E51CC2986643400D21B61 /* MeshtasticDataModelV7.xcdatamodel */; + currentVersion = DDBA45EC299ED78100DEEDDC /* MeshtasticDataModelV8.xcdatamodel */; name = Meshtastic.xcdatamodeld; path = Meshtastic/Meshtastic.xcdatamodeld; sourceTree = ""; diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 8f43c58d..4bad7d2e 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -498,6 +498,12 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { } } } + // Device Metadata + if decodedInfo.metadata.firmwareVersion.count > 0 && !invalidVersion { + nowKnown = true + deviceMetadataPacket(metadata: decodedInfo.metadata, fromNum: connectedPeripheral.num, context: context!) + } + // Log any other unknownApp calls if !nowKnown { MeshLogger.log("🕸️ MESH PACKET received for Unknown App UNHANDLED \(try! decodedInfo.packet.jsonString())") } diff --git a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion index aab08b69..25c323b3 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion +++ b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion @@ -3,6 +3,6 @@ _XCCurrentVersionName - MeshtasticDataModelV7.xcdatamodel + MeshtasticDataModelV8.xcdatamodel diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV7.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV7.xcdatamodel/contents index 35e5b8ef..cae65994 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV7.xcdatamodel/contents +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV7.xcdatamodel/contents @@ -1,5 +1,5 @@ - + diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV8.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV8.xcdatamodel/contents new file mode 100644 index 00000000..1945a68a --- /dev/null +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV8.xcdatamodel/contents @@ -0,0 +1,300 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Meshtastic/Protobufs/meshtastic/admin.pb.swift b/Meshtastic/Protobufs/meshtastic/admin.pb.swift index 1cda1982..eb4aafba 100644 --- a/Meshtastic/Protobufs/meshtastic/admin.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/admin.pb.swift @@ -794,6 +794,10 @@ struct HamParameters { /// Ensure your radio is capable of operating of the selected frequency before setting this. var frequency: Float = 0 + /// + /// Optional short name of user + var shortName: String = String() + var unknownFields = SwiftProtobuf.UnknownStorage() init() {} @@ -1335,6 +1339,7 @@ extension HamParameters: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementa 1: .standard(proto: "call_sign"), 2: .standard(proto: "tx_power"), 3: .same(proto: "frequency"), + 4: .standard(proto: "short_name"), ] mutating func decodeMessage(decoder: inout D) throws { @@ -1346,6 +1351,7 @@ extension HamParameters: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementa case 1: try { try decoder.decodeSingularStringField(value: &self.callSign) }() case 2: try { try decoder.decodeSingularInt32Field(value: &self.txPower) }() case 3: try { try decoder.decodeSingularFloatField(value: &self.frequency) }() + case 4: try { try decoder.decodeSingularStringField(value: &self.shortName) }() default: break } } @@ -1361,6 +1367,9 @@ extension HamParameters: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementa if self.frequency != 0 { try visitor.visitSingularFloatField(value: self.frequency, fieldNumber: 3) } + if !self.shortName.isEmpty { + try visitor.visitSingularStringField(value: self.shortName, fieldNumber: 4) + } try unknownFields.traverse(visitor: &visitor) } @@ -1368,6 +1377,7 @@ extension HamParameters: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementa if lhs.callSign != rhs.callSign {return false} if lhs.txPower != rhs.txPower {return false} if lhs.frequency != rhs.frequency {return false} + if lhs.shortName != rhs.shortName {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } diff --git a/Meshtastic/Protobufs/meshtastic/config.pb.swift b/Meshtastic/Protobufs/meshtastic/config.pb.swift index 0013ae82..c6bfb474 100644 --- a/Meshtastic/Protobufs/meshtastic/config.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/config.pb.swift @@ -172,6 +172,11 @@ struct Config { /// Sets the role of node var rebroadcastMode: Config.DeviceConfig.RebroadcastMode = .all + /// + /// Send our nodeinfo this often + /// Defaults to 900 Seconds (15 minutes) + var nodeInfoBroadcastSecs: UInt32 = 0 + var unknownFields = SwiftProtobuf.UnknownStorage() /// @@ -1561,6 +1566,7 @@ extension Config.DeviceConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImpl 4: .standard(proto: "button_gpio"), 5: .standard(proto: "buzzer_gpio"), 6: .standard(proto: "rebroadcast_mode"), + 7: .standard(proto: "node_info_broadcast_secs"), ] mutating func decodeMessage(decoder: inout D) throws { @@ -1575,6 +1581,7 @@ extension Config.DeviceConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImpl case 4: try { try decoder.decodeSingularUInt32Field(value: &self.buttonGpio) }() case 5: try { try decoder.decodeSingularUInt32Field(value: &self.buzzerGpio) }() case 6: try { try decoder.decodeSingularEnumField(value: &self.rebroadcastMode) }() + case 7: try { try decoder.decodeSingularUInt32Field(value: &self.nodeInfoBroadcastSecs) }() default: break } } @@ -1599,6 +1606,9 @@ extension Config.DeviceConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImpl if self.rebroadcastMode != .all { try visitor.visitSingularEnumField(value: self.rebroadcastMode, fieldNumber: 6) } + if self.nodeInfoBroadcastSecs != 0 { + try visitor.visitSingularUInt32Field(value: self.nodeInfoBroadcastSecs, fieldNumber: 7) + } try unknownFields.traverse(visitor: &visitor) } @@ -1609,6 +1619,7 @@ extension Config.DeviceConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImpl if lhs.buttonGpio != rhs.buttonGpio {return false} if lhs.buzzerGpio != rhs.buzzerGpio {return false} if lhs.rebroadcastMode != rhs.rebroadcastMode {return false} + if lhs.nodeInfoBroadcastSecs != rhs.nodeInfoBroadcastSecs {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } diff --git a/Meshtastic/Protobufs/meshtastic/device_metadata.pb.swift b/Meshtastic/Protobufs/meshtastic/device_metadata.pb.swift deleted file mode 100644 index 4c930c43..00000000 --- a/Meshtastic/Protobufs/meshtastic/device_metadata.pb.swift +++ /dev/null @@ -1,157 +0,0 @@ -// DO NOT EDIT. -// swift-format-ignore-file -// -// Generated by the Swift generator plugin for the protocol buffer compiler. -// Source: meshtastic/device_metadata.proto -// -// For information on using the generated types, please see the documentation: -// https://github.com/apple/swift-protobuf/ - -import Foundation -import SwiftProtobuf - -// If the compiler emits an error on this type, it is because this file -// was generated by a version of the `protoc` Swift plug-in that is -// incompatible with the version of SwiftProtobuf to which you are linking. -// Please ensure that you are building against the same version of the API -// that was used to generate this file. -fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { - struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {} - typealias Version = _2 -} - -/// -/// Device metadata response -struct DeviceMetadata { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// - /// Device firmware version string - var firmwareVersion: String = String() - - /// - /// Device state version - var deviceStateVersion: UInt32 = 0 - - /// - /// Indicates whether the device can shutdown CPU natively or via power management chip - var canShutdown: Bool = false - - /// - /// Indicates that the device has native wifi capability - var hasWifi_p: Bool = false - - /// - /// Indicates that the device has native bluetooth capability - var hasBluetooth_p: Bool = false - - /// - /// Indicates that the device has an ethernet peripheral - var hasEthernet_p: Bool = false - - /// - /// Indicates that the device's role in the mesh - var role: Config.DeviceConfig.Role = .client - - /// - /// Indicates the device's current enabled position flags - var positionFlags: UInt32 = 0 - - /// - /// Device hardware model - var hwModel: HardwareModel = .unset - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} -} - -#if swift(>=5.5) && canImport(_Concurrency) -extension DeviceMetadata: @unchecked Sendable {} -#endif // swift(>=5.5) && canImport(_Concurrency) - -// MARK: - Code below here is support for the SwiftProtobuf runtime. - -fileprivate let _protobuf_package = "meshtastic" - -extension DeviceMetadata: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".DeviceMetadata" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .standard(proto: "firmware_version"), - 2: .standard(proto: "device_state_version"), - 3: .same(proto: "canShutdown"), - 4: .same(proto: "hasWifi"), - 5: .same(proto: "hasBluetooth"), - 6: .same(proto: "hasEthernet"), - 7: .same(proto: "role"), - 8: .standard(proto: "position_flags"), - 9: .standard(proto: "hw_model"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularStringField(value: &self.firmwareVersion) }() - case 2: try { try decoder.decodeSingularUInt32Field(value: &self.deviceStateVersion) }() - case 3: try { try decoder.decodeSingularBoolField(value: &self.canShutdown) }() - case 4: try { try decoder.decodeSingularBoolField(value: &self.hasWifi_p) }() - case 5: try { try decoder.decodeSingularBoolField(value: &self.hasBluetooth_p) }() - case 6: try { try decoder.decodeSingularBoolField(value: &self.hasEthernet_p) }() - case 7: try { try decoder.decodeSingularEnumField(value: &self.role) }() - case 8: try { try decoder.decodeSingularUInt32Field(value: &self.positionFlags) }() - case 9: try { try decoder.decodeSingularEnumField(value: &self.hwModel) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if !self.firmwareVersion.isEmpty { - try visitor.visitSingularStringField(value: self.firmwareVersion, fieldNumber: 1) - } - if self.deviceStateVersion != 0 { - try visitor.visitSingularUInt32Field(value: self.deviceStateVersion, fieldNumber: 2) - } - if self.canShutdown != false { - try visitor.visitSingularBoolField(value: self.canShutdown, fieldNumber: 3) - } - if self.hasWifi_p != false { - try visitor.visitSingularBoolField(value: self.hasWifi_p, fieldNumber: 4) - } - if self.hasBluetooth_p != false { - try visitor.visitSingularBoolField(value: self.hasBluetooth_p, fieldNumber: 5) - } - if self.hasEthernet_p != false { - try visitor.visitSingularBoolField(value: self.hasEthernet_p, fieldNumber: 6) - } - if self.role != .client { - try visitor.visitSingularEnumField(value: self.role, fieldNumber: 7) - } - if self.positionFlags != 0 { - try visitor.visitSingularUInt32Field(value: self.positionFlags, fieldNumber: 8) - } - if self.hwModel != .unset { - try visitor.visitSingularEnumField(value: self.hwModel, fieldNumber: 9) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: DeviceMetadata, rhs: DeviceMetadata) -> Bool { - if lhs.firmwareVersion != rhs.firmwareVersion {return false} - if lhs.deviceStateVersion != rhs.deviceStateVersion {return false} - if lhs.canShutdown != rhs.canShutdown {return false} - if lhs.hasWifi_p != rhs.hasWifi_p {return false} - if lhs.hasBluetooth_p != rhs.hasBluetooth_p {return false} - if lhs.hasEthernet_p != rhs.hasEthernet_p {return false} - if lhs.role != rhs.role {return false} - if lhs.positionFlags != rhs.positionFlags {return false} - if lhs.hwModel != rhs.hwModel {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} diff --git a/Meshtastic/Protobufs/meshtastic/mesh.pb.swift b/Meshtastic/Protobufs/meshtastic/mesh.pb.swift index 0f474574..12b53077 100644 --- a/Meshtastic/Protobufs/meshtastic/mesh.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/mesh.pb.swift @@ -158,6 +158,10 @@ enum HardwareModel: SwiftProtobuf.Enum { /// New BETAFPV ELRS Micro TX Module 2.4G with ESP32 CPU case betafpv2400Tx // = 45 + /// + /// BetaFPV ExpressLRS "Nano" TX Module 900MHz with ESP32 CPU + case betafpv900NanoTx // = 46 + /// /// Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits. case privateHw // = 255 @@ -201,6 +205,7 @@ enum HardwareModel: SwiftProtobuf.Enum { case 43: self = .heltecV3 case 44: self = .heltecWslV3 case 45: self = .betafpv2400Tx + case 46: self = .betafpv900NanoTx case 255: self = .privateHw default: self = .UNRECOGNIZED(rawValue) } @@ -240,6 +245,7 @@ enum HardwareModel: SwiftProtobuf.Enum { case .heltecV3: return 43 case .heltecWslV3: return 44 case .betafpv2400Tx: return 45 + case .betafpv900NanoTx: return 46 case .privateHw: return 255 case .UNRECOGNIZED(let i): return i } @@ -284,6 +290,7 @@ extension HardwareModel: CaseIterable { .heltecV3, .heltecWslV3, .betafpv2400Tx, + .betafpv900NanoTx, .privateHw, ] } @@ -1949,6 +1956,16 @@ struct FromRadio { set {_uniqueStorage()._payloadVariant = .xmodemPacket(newValue)} } + /// + /// Device metadata message + var metadata: DeviceMetadata { + get { + if case .metadata(let v)? = _storage._payloadVariant {return v} + return DeviceMetadata() + } + set {_uniqueStorage()._payloadVariant = .metadata(newValue)} + } + var unknownFields = SwiftProtobuf.UnknownStorage() /// @@ -1995,6 +2012,9 @@ struct FromRadio { /// /// File Transfer Chunk case xmodemPacket(XModem) + /// + /// Device metadata message + case metadata(DeviceMetadata) #if !swift(>=4.1) static func ==(lhs: FromRadio.OneOf_PayloadVariant, rhs: FromRadio.OneOf_PayloadVariant) -> Bool { @@ -2046,6 +2066,10 @@ struct FromRadio { guard case .xmodemPacket(let l) = lhs, case .xmodemPacket(let r) = rhs else { preconditionFailure() } return l == r }() + case (.metadata, .metadata): return { + guard case .metadata(let l) = lhs, case .metadata(let r) = rhs else { preconditionFailure() } + return l == r + }() default: return false } } @@ -2192,6 +2216,54 @@ struct Compressed { init() {} } +/// +/// Device metadata response +struct DeviceMetadata { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + /// + /// Device firmware version string + var firmwareVersion: String = String() + + /// + /// Device state version + var deviceStateVersion: UInt32 = 0 + + /// + /// Indicates whether the device can shutdown CPU natively or via power management chip + var canShutdown: Bool = false + + /// + /// Indicates that the device has native wifi capability + var hasWifi_p: Bool = false + + /// + /// Indicates that the device has native bluetooth capability + var hasBluetooth_p: Bool = false + + /// + /// Indicates that the device has an ethernet peripheral + var hasEthernet_p: Bool = false + + /// + /// Indicates that the device's role in the mesh + var role: Config.DeviceConfig.Role = .client + + /// + /// Indicates the device's current enabled position flags + var positionFlags: UInt32 = 0 + + /// + /// Device hardware model + var hwModel: HardwareModel = .unset + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} +} + #if swift(>=5.5) && canImport(_Concurrency) extension HardwareModel: @unchecked Sendable {} extension Constants: @unchecked Sendable {} @@ -2220,6 +2292,7 @@ extension FromRadio.OneOf_PayloadVariant: @unchecked Sendable {} extension ToRadio: @unchecked Sendable {} extension ToRadio.OneOf_PayloadVariant: @unchecked Sendable {} extension Compressed: @unchecked Sendable {} +extension DeviceMetadata: @unchecked Sendable {} #endif // swift(>=5.5) && canImport(_Concurrency) // MARK: - Code below here is support for the SwiftProtobuf runtime. @@ -2260,6 +2333,7 @@ extension HardwareModel: SwiftProtobuf._ProtoNameProviding { 43: .same(proto: "HELTEC_V3"), 44: .same(proto: "HELTEC_WSL_V3"), 45: .same(proto: "BETAFPV_2400_TX"), + 46: .same(proto: "BETAFPV_900_NANO_TX"), 255: .same(proto: "PRIVATE_HW"), ] } @@ -3401,6 +3475,7 @@ extension FromRadio: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation 10: .same(proto: "channel"), 11: .same(proto: "queueStatus"), 12: .same(proto: "xmodemPacket"), + 13: .same(proto: "metadata"), ] fileprivate class _StorageClass { @@ -3566,6 +3641,19 @@ extension FromRadio: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation _storage._payloadVariant = .xmodemPacket(v) } }() + case 13: try { + var v: DeviceMetadata? + var hadOneofValue = false + if let current = _storage._payloadVariant { + hadOneofValue = true + if case .metadata(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + _storage._payloadVariant = .metadata(v) + } + }() default: break } } @@ -3626,6 +3714,10 @@ extension FromRadio: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation guard case .xmodemPacket(let v)? = _storage._payloadVariant else { preconditionFailure() } try visitor.visitSingularMessageField(value: v, fieldNumber: 12) }() + case .metadata?: try { + guard case .metadata(let v)? = _storage._payloadVariant else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 13) + }() case nil: break } } @@ -3781,3 +3873,83 @@ extension Compressed: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio return true } } + +extension DeviceMetadata: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".DeviceMetadata" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "firmware_version"), + 2: .standard(proto: "device_state_version"), + 3: .same(proto: "canShutdown"), + 4: .same(proto: "hasWifi"), + 5: .same(proto: "hasBluetooth"), + 6: .same(proto: "hasEthernet"), + 7: .same(proto: "role"), + 8: .standard(proto: "position_flags"), + 9: .standard(proto: "hw_model"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularStringField(value: &self.firmwareVersion) }() + case 2: try { try decoder.decodeSingularUInt32Field(value: &self.deviceStateVersion) }() + case 3: try { try decoder.decodeSingularBoolField(value: &self.canShutdown) }() + case 4: try { try decoder.decodeSingularBoolField(value: &self.hasWifi_p) }() + case 5: try { try decoder.decodeSingularBoolField(value: &self.hasBluetooth_p) }() + case 6: try { try decoder.decodeSingularBoolField(value: &self.hasEthernet_p) }() + case 7: try { try decoder.decodeSingularEnumField(value: &self.role) }() + case 8: try { try decoder.decodeSingularUInt32Field(value: &self.positionFlags) }() + case 9: try { try decoder.decodeSingularEnumField(value: &self.hwModel) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if !self.firmwareVersion.isEmpty { + try visitor.visitSingularStringField(value: self.firmwareVersion, fieldNumber: 1) + } + if self.deviceStateVersion != 0 { + try visitor.visitSingularUInt32Field(value: self.deviceStateVersion, fieldNumber: 2) + } + if self.canShutdown != false { + try visitor.visitSingularBoolField(value: self.canShutdown, fieldNumber: 3) + } + if self.hasWifi_p != false { + try visitor.visitSingularBoolField(value: self.hasWifi_p, fieldNumber: 4) + } + if self.hasBluetooth_p != false { + try visitor.visitSingularBoolField(value: self.hasBluetooth_p, fieldNumber: 5) + } + if self.hasEthernet_p != false { + try visitor.visitSingularBoolField(value: self.hasEthernet_p, fieldNumber: 6) + } + if self.role != .client { + try visitor.visitSingularEnumField(value: self.role, fieldNumber: 7) + } + if self.positionFlags != 0 { + try visitor.visitSingularUInt32Field(value: self.positionFlags, fieldNumber: 8) + } + if self.hwModel != .unset { + try visitor.visitSingularEnumField(value: self.hwModel, fieldNumber: 9) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: DeviceMetadata, rhs: DeviceMetadata) -> Bool { + if lhs.firmwareVersion != rhs.firmwareVersion {return false} + if lhs.deviceStateVersion != rhs.deviceStateVersion {return false} + if lhs.canShutdown != rhs.canShutdown {return false} + if lhs.hasWifi_p != rhs.hasWifi_p {return false} + if lhs.hasBluetooth_p != rhs.hasBluetooth_p {return false} + if lhs.hasEthernet_p != rhs.hasEthernet_p {return false} + if lhs.role != rhs.role {return false} + if lhs.positionFlags != rhs.positionFlags {return false} + if lhs.hwModel != rhs.hwModel {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} diff --git a/Meshtastic/Views/Bluetooth/Connect.swift b/Meshtastic/Views/Bluetooth/Connect.swift index d9e29e1b..7280d277 100644 --- a/Meshtastic/Views/Bluetooth/Connect.swift +++ b/Meshtastic/Views/Bluetooth/Connect.swift @@ -21,6 +21,8 @@ struct Connect: View { @State var isPreferredRadio: Bool = false @State var isUnsetRegion = false @State var invalidFirmwareVersion = false + @State var isPresentingPreferredPeripherialDialog = false + @State var showDialogForNextPeripherialChange = true var body: some View { @@ -61,23 +63,44 @@ struct Connect: View { Toggle("preferred.radio", isOn: $bleManager.preferredPeripheral) .toggleStyle(SwitchToggleStyle(tint: .accentColor)) .labelsHidden() + .confirmationDialog( + "Are you sure? Switching your preferred peripheral will clear all app data from the phone.", + isPresented: $isPresentingPreferredPeripherialDialog, + titleVisibility: .visible + ) { + Button("Confirm", role: .destructive) { + + if bleManager.preferredPeripheral { + if bleManager.connectedPeripheral != nil { + userSettings.preferredPeripheralId = bleManager.connectedPeripheral!.peripheral.identifier.uuidString + userSettings.preferredNodeNum = bleManager.connectedPeripheral!.num + bleManager.preferredPeripheral = true + isPreferredRadio = true + showDialogForNextPeripherialChange = false + } + } else { + + if bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.peripheral.identifier.uuidString == userSettings.preferredPeripheralId { + userSettings.preferredPeripheralId = "" + userSettings.preferredNodeNum = 0 + bleManager.preferredPeripheral = false + isPreferredRadio = false + } + } + bleManager.disconnectPeripheral() + clearCoreDataDatabase(context: context) + } + Button("Cancel", role: .cancel) { + showDialogForNextPeripherialChange = false + bleManager.preferredPeripheral = !bleManager.preferredPeripheral + } + } .onChange(of: bleManager.preferredPeripheral) { value in - if value { - if bleManager.connectedPeripheral != nil { - userSettings.preferredPeripheralId = bleManager.connectedPeripheral!.peripheral.identifier.uuidString - userSettings.preferredNodeNum = bleManager.connectedPeripheral!.num - bleManager.preferredPeripheral = true - isPreferredRadio = true - } - } else { - - if bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.peripheral.identifier.uuidString == userSettings.preferredPeripheralId { - - userSettings.preferredPeripheralId = "" - userSettings.preferredNodeNum = 0 - bleManager.preferredPeripheral = false - isPreferredRadio = false - } + + if showDialogForNextPeripherialChange { + isPresentingPreferredPeripherialDialog = true + } else { + showDialogForNextPeripherialChange = true } } } diff --git a/Meshtastic/Views/ContentView.swift b/Meshtastic/Views/ContentView.swift index a3f99164..534062c0 100644 --- a/Meshtastic/Views/ContentView.swift +++ b/Meshtastic/Views/ContentView.swift @@ -25,7 +25,7 @@ struct ContentView: View { TabView(selection: $selection) { - if userSettings.preferredNodeNum > 0 { + if userSettings.preferredPeripheralId.count > 0 { Contacts() .tabItem { diff --git a/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift b/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift index 929536c9..7a887550 100644 --- a/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift +++ b/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift @@ -139,7 +139,7 @@ struct MapViewSwiftUI: UIViewRepresentable { annotationView.markerTintColor = UIColor(.indigo) annotationView.displayPriority = .defaultHigh annotationView.titleVisibility = .adaptive - annotationView.clusteringIdentifier = "nodeGroup" + //annotationView.clusteringIdentifier = "nodeGroup" } annotationView.titleVisibility = .adaptive diff --git a/Meshtastic/Views/Nodes/NodeDetail.swift b/Meshtastic/Views/Nodes/NodeDetail.swift index 113633e9..a73b0254 100644 --- a/Meshtastic/Views/Nodes/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/NodeDetail.swift @@ -351,12 +351,11 @@ struct NodeDetail: View { } } - if (self.bleManager.connectedPeripheral != nil && self.bleManager.connectedPeripheral.num == node.num) - || (self.bleManager.connectedPeripheral != nil && node.metadata != nil) { + if (self.bleManager.connectedPeripheral != nil && node.metadata != nil) { HStack { let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context) - if node.metadata?.canShutdown ?? false { + if node.metadata?.canShutdown ?? false || hwModelString == "RAK4631" {//node.metadata?.hwModel ?? "UNSET" == "RAK4631" { Button(action: { showingShutdownConfirm = true diff --git a/Meshtastic/Views/Settings/AppSettings.swift b/Meshtastic/Views/Settings/AppSettings.swift index b0802fb9..d8e2cecc 100644 --- a/Meshtastic/Views/Settings/AppSettings.swift +++ b/Meshtastic/Views/Settings/AppSettings.swift @@ -13,10 +13,6 @@ struct AppSettings: View { @State private var isPresentingCoreDataResetConfirm = false @State private var preferredDeviceConnected = false - var perferredPeripheral: String { - UserDefaults.standard.object(forKey: "preferredPeripheralName") as? String ?? "" - } - var body: some View { VStack { Form { From 434ed20ff29795dc8c52a45d6d604de50ee378b1 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 19 Feb 2023 09:57:43 -0800 Subject: [PATCH 07/19] * Map pin clustering cleanup * Show icon direction in pins with available heading --- Meshtastic/Helpers/Extensions.swift | 17 +++++++++++++++++ .../Views/Map/Custom/MapViewSwiftUI.swift | 15 +++++++++++---- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/Meshtastic/Helpers/Extensions.swift b/Meshtastic/Helpers/Extensions.swift index 39a2b6af..3437086e 100644 --- a/Meshtastic/Helpers/Extensions.swift +++ b/Meshtastic/Helpers/Extensions.swift @@ -72,6 +72,23 @@ extension Int { } } +extension UIImage { + func rotate(radians: Float) -> UIImage? { + var newSize = CGRect(origin: CGPoint.zero, size: self.size).applying(CGAffineTransform(rotationAngle: CGFloat(radians))).size + newSize.width = floor(newSize.width) + newSize.height = floor(newSize.height) + UIGraphicsBeginImageContextWithOptions(newSize, false, self.scale) + let context = UIGraphicsGetCurrentContext()! + context.translateBy(x: newSize.width/2, y: newSize.height/2) + context.rotate(by: CGFloat(radians)) + self.draw(in: CGRect(x: -self.size.width/2, y: -self.size.height/2, width: self.size.width, height: self.size.height)) + let newImage = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + + return newImage + } +} + extension String { func base64urlToBase64() -> String { diff --git a/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift b/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift index 7a887550..6cce2dcb 100644 --- a/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift +++ b/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift @@ -7,6 +7,10 @@ import SwiftUI import MapKit +func degreesToRadians(_ number: Double) -> Double { + return number * .pi / 180 +} + struct MapViewSwiftUI: UIViewRepresentable { var onLongPress: (_ waypointCoordinate: CLLocationCoordinate2D) -> Void @@ -116,7 +120,7 @@ struct MapViewSwiftUI: UIViewRepresentable { case _ as MKClusterAnnotation: let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "nodeGroup") as? MKMarkerAnnotationView ?? MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: "NodeGroup") annotationView.markerTintColor = .brown//.systemRed - annotationView.displayPriority = .defaultLow + //annotationView.displayPriority = .defaultLow annotationView.tag = -1 return annotationView case let positionAnnotation as PositionEntity: @@ -127,19 +131,16 @@ struct MapViewSwiftUI: UIViewRepresentable { let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "node") as? MKMarkerAnnotationView ?? MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: reuseID ) annotationView.tag = -1 annotationView.canShowCallout = true - annotationView.glyphText = "📟" if latest == positionAnnotation { annotationView.markerTintColor = .systemRed annotationView.displayPriority = .required annotationView.titleVisibility = .visible - // annotationView.clusteringIdentifier = "nodeGroupLatest" } else { annotationView.markerTintColor = UIColor(.indigo) annotationView.displayPriority = .defaultHigh annotationView.titleVisibility = .adaptive - //annotationView.clusteringIdentifier = "nodeGroup" } annotationView.titleVisibility = .adaptive @@ -165,8 +166,14 @@ struct MapViewSwiftUI: UIViewRepresentable { subtitle.text! += "Speed: \(formatter.string(from: Measurement(value: Double(positionAnnotation.speed), unit: UnitSpeed.kilometersPerHour))) \n" } if pf.contains(.Heading) { + + annotationView.glyphImage = UIImage(systemName: "location.north.fill")?.rotate(radians: Float(degreesToRadians(Double(positionAnnotation.heading)))) subtitle.text! += "Heading: \(String(positionAnnotation.heading)) \n" + } else { + annotationView.glyphText = "📟" } + } else { + annotationView.glyphText = "📟" } subtitle.text! += positionAnnotation.time?.formatted() ?? "Unknown \n" subtitle.numberOfLines = 0 From 844c424c1b67473d2eb9e7bf9a3be3db160eaee4 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Mon, 20 Feb 2023 19:52:46 -0800 Subject: [PATCH 08/19] Hook up app setting for map type to mesh map Default to hybridFlyover Use short name for node annotation title, add long name to annotation detail Add map settings section to app settings --- Meshtastic.xcodeproj/project.pbxproj | 4 --- Meshtastic/Model/MapLocation.swift | 15 ---------- Meshtastic/Model/UserSettings.swift | 2 +- .../Persistence/PositionEntityExtension.swift | 2 +- .../Views/Map/Custom/MapViewSwiftUI.swift | 10 ++++--- Meshtastic/Views/Nodes/NodeMap.swift | 29 +++++++++++++++++-- Meshtastic/Views/Settings/AppSettings.swift | 17 ++++++----- 7 files changed, 45 insertions(+), 34 deletions(-) delete mode 100644 Meshtastic/Model/MapLocation.swift diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 27c1dcce..07d7f740 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -38,7 +38,6 @@ DD4F23CD28779A3C001D37CB /* EnvironmentMetricsLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4F23CC28779A3C001D37CB /* EnvironmentMetricsLog.swift */; }; DD5394FC276993AD00AD86B1 /* SwiftProtobuf in Frameworks */ = {isa = PBXBuildFile; productRef = DD5394FB276993AD00AD86B1 /* SwiftProtobuf */; }; DD5394FE276BA0EF00AD86B1 /* PositionEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5394FD276BA0EF00AD86B1 /* PositionEntityExtension.swift */; }; - DD539502276DAA6A00AD86B1 /* MapLocation.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD539501276DAA6A00AD86B1 /* MapLocation.swift */; }; DD58C5F22919AD3C00D5BEFB /* ChannelEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD58C5F12919AD3C00D5BEFB /* ChannelEntityExtension.swift */; }; DD5D0A9C2931B9F200F7EA61 /* EthernetModes.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5D0A9B2931B9F200F7EA61 /* EthernetModes.swift */; }; DD5E5202298EE33B00D21B61 /* admin.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5E51F0298EE33B00D21B61 /* admin.pb.swift */; }; @@ -168,7 +167,6 @@ DD4A911D2708C65400501B7E /* AppSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettings.swift; sourceTree = ""; }; DD4F23CC28779A3C001D37CB /* EnvironmentMetricsLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnvironmentMetricsLog.swift; sourceTree = ""; }; DD5394FD276BA0EF00AD86B1 /* PositionEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PositionEntityExtension.swift; sourceTree = ""; }; - DD539501276DAA6A00AD86B1 /* MapLocation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapLocation.swift; sourceTree = ""; }; DD58C5F12919AD3C00D5BEFB /* ChannelEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelEntityExtension.swift; sourceTree = ""; }; DD5D0A9A2931AD6B00F7EA61 /* MeshtasticDataModelV2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV2.xcdatamodel; sourceTree = ""; }; DD5D0A9B2931B9F200F7EA61 /* EthernetModes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EthernetModes.swift; sourceTree = ""; }; @@ -543,7 +541,6 @@ isa = PBXGroup; children = ( DD23A50E26FD1B4400D9B90C /* PeripheralModel.swift */, - DD539501276DAA6A00AD86B1 /* MapLocation.swift */, DD35018A2852FC79000FC853 /* UserSettings.swift */, ); path = Model; @@ -872,7 +869,6 @@ DD5E5210298EE33B00D21B61 /* telemetry.pb.swift in Sources */, DD5E5205298EE33B00D21B61 /* mesh.pb.swift in Sources */, DD8169F9271F1A6100F4AB02 /* MeshLogger.swift in Sources */, - DD539502276DAA6A00AD86B1 /* MapLocation.swift in Sources */, DD41582A28585C32009B0E59 /* RangeTestConfig.swift in Sources */, DD1925B728CDA5A400720036 /* CannedMessagesConfigEnums.swift in Sources */, DD5E5211298EE33B00D21B61 /* remote_hardware.pb.swift in Sources */, diff --git a/Meshtastic/Model/MapLocation.swift b/Meshtastic/Model/MapLocation.swift deleted file mode 100644 index 77fd9d02..00000000 --- a/Meshtastic/Model/MapLocation.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// MapLocation.swift -// MeshtasticApple -// -// Created by Garth Vander Houwen on 12/17/21. -// -import Foundation -import MapKit - -struct MapLocation: Identifiable { - - let id = UUID() - let name: String - let coordinate: CLLocationCoordinate2D -} diff --git a/Meshtastic/Model/UserSettings.swift b/Meshtastic/Model/UserSettings.swift index 2b95b871..2ccecdb8 100644 --- a/Meshtastic/Model/UserSettings.swift +++ b/Meshtastic/Model/UserSettings.swift @@ -59,7 +59,7 @@ class UserSettings: ObservableObject { self.provideLocation = UserDefaults.standard.object(forKey: "provideLocation") as? Bool ?? false self.provideLocationInterval = UserDefaults.standard.object(forKey: "provideLocationInterval") as? Int ?? 900 self.keyboardType = UserDefaults.standard.object(forKey: "keyboardType") as? Int ?? 0 - self.meshMapType = UserDefaults.standard.string(forKey: "meshMapType") ?? "hybrid" + self.meshMapType = UserDefaults.standard.string(forKey: "meshMapType") ?? "hybridFlyover" self.meshMapCustomTileServer = UserDefaults.standard.string(forKey: "meshMapCustomTileServer") ?? "" } } diff --git a/Meshtastic/Persistence/PositionEntityExtension.swift b/Meshtastic/Persistence/PositionEntityExtension.swift index 6bda7147..23ec54b2 100644 --- a/Meshtastic/Persistence/PositionEntityExtension.swift +++ b/Meshtastic/Persistence/PositionEntityExtension.swift @@ -52,6 +52,6 @@ extension PositionEntity { extension PositionEntity: MKAnnotation { public var coordinate: CLLocationCoordinate2D { nodeCoordinate ?? LocationHelper.DefaultLocation } - public var title: String? { nodePosition?.user?.longName ?? NSLocalizedString("unknown", comment: "Unknown") } + public var title: String? { nodePosition?.user?.shortName ?? NSLocalizedString("unknown", comment: "Unknown") } public var subtitle: String? { time?.formatted() } } diff --git a/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift b/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift index 6cce2dcb..de52d486 100644 --- a/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift +++ b/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift @@ -16,9 +16,10 @@ struct MapViewSwiftUI: UIViewRepresentable { var onLongPress: (_ waypointCoordinate: CLLocationCoordinate2D) -> Void var onWaypointEdit: (_ waypointId: Int ) -> Void let mapView = MKMapView() - dynamic var positions: [PositionEntity] + dynamic let positions: [PositionEntity] dynamic let waypoints: [WaypointEntity] - let mapViewType: MKMapType + dynamic let mapViewType: MKMapType + let centerOnPositionsOnly: Bool // Offline Maps @@ -64,7 +65,7 @@ struct MapViewSwiftUI: UIViewRepresentable { if self.customMapOverlay != self.presentCustomMapOverlayHash || self.loadedLastUpdatedLocalMapFile != self.lastUpdatedLocalMapFile { mapView.removeOverlays(mapView.overlays) if self.customMapOverlay != nil { - + let fileManager = FileManager.default let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first! let tilePath = documentsDirectory.appendingPathComponent("offline_map.mbtiles", isDirectory: false).path @@ -148,7 +149,8 @@ struct MapViewSwiftUI: UIViewRepresentable { leftIcon.backgroundColor = UIColor(.indigo) annotationView.leftCalloutAccessoryView = leftIcon let subtitle = UILabel() - subtitle.text = "Latitude: \(String(format: "%.5f", positionAnnotation.coordinate.latitude)) \n" + subtitle.text = "Long Name: \(positionAnnotation.nodePosition?.user?.longName ?? "Unknown") \n" + subtitle.text! += "Latitude: \(String(format: "%.5f", positionAnnotation.coordinate.latitude)) \n" subtitle.text! += "Longitude: \(String(format: "%.5f", positionAnnotation.coordinate.longitude)) \n" let distanceFormatter = MKDistanceFormatter() subtitle.text! += "Altitude: \(distanceFormatter.string(fromDistance: Double(positionAnnotation.altitude))) \n" diff --git a/Meshtastic/Views/Nodes/NodeMap.swift b/Meshtastic/Views/Nodes/NodeMap.swift index b13caa0c..043be960 100644 --- a/Meshtastic/Views/Nodes/NodeMap.swift +++ b/Meshtastic/Views/Nodes/NodeMap.swift @@ -29,6 +29,8 @@ struct NodeMap: View { } } } + @AppStorage("meshMapType") private var meshMapType = "standard" + //&& nodePosition != nil @FetchRequest(sortDescriptors: [NSSortDescriptor(key: "time", ascending: true)], predicate: NSPredicate(format: "time >= %@ && nodePosition != nil", Calendar.current.startOfDay(for: Date()) as NSDate), animation: .none) @@ -40,7 +42,7 @@ struct NodeMap: View { ), animation: .none) private var waypoints: FetchedResults - @State private var mapType: MKMapType = .standard + @State private var mapType: MKMapType = .hybridFlyover @State var waypointCoordinate: CLLocationCoordinate2D = LocationHelper.DefaultLocation @State var editingWaypoint: Int = 0 @State private var presentingWaypointForm = false @@ -71,7 +73,7 @@ struct NodeMap: View { } }, positions: Array(positions), waypoints: Array(waypoints), - mapViewType: mapType ?? MKMapType.standard, + mapViewType: mapType , centerOnPositionsOnly: false, customMapOverlay: self.customMapOverlay, overlays: self.overlays @@ -109,6 +111,29 @@ struct NodeMap: View { .onAppear(perform: { self.bleManager.context = context self.bleManager.userSettings = userSettings + + switch meshMapType { + case "standard": + mapType = .standard + break + case "mutedStandard": + mapType = .mutedStandard + break + case "hybrid": + mapType = .hybrid + break + case "hybridFlyover": + mapType = .hybridFlyover + break + case "satellite": + mapType = .satellite + break + case "satelliteFlyover": + mapType = .satelliteFlyover + break + default: + mapType = .hybridFlyover + } }) } } diff --git a/Meshtastic/Views/Settings/AppSettings.swift b/Meshtastic/Views/Settings/AppSettings.swift index d8e2cecc..5d616d47 100644 --- a/Meshtastic/Views/Settings/AppSettings.swift +++ b/Meshtastic/Views/Settings/AppSettings.swift @@ -35,13 +35,6 @@ struct AppSettings: View { } } .pickerStyle(DefaultPickerStyle()) - - Picker("map.type", selection: $userSettings.meshMapType) { - ForEach(MeshMapType.allCases) { map in - Text(map.description) - } - } - .pickerStyle(DefaultPickerStyle()) } @@ -66,6 +59,16 @@ struct AppSettings: View { .listRowSeparator(.visible) } } + + Section(header: Text("map options")) { + + Picker("map.type", selection: $userSettings.meshMapType) { + ForEach(MeshMapType.allCases) { map in + Text(map.description) + } + } + .pickerStyle(DefaultPickerStyle()) + } } HStack { Button { From 527ff53150ef29ab7f52ff735a8bbc9eb2aeac59 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Mon, 20 Feb 2023 20:00:00 -0800 Subject: [PATCH 09/19] Back to standard as a default map type hook up map the setting to the user default for meshMapType --- Meshtastic/Model/UserSettings.swift | 2 +- Meshtastic/Views/Nodes/NodeDetail.swift | 23 +++++++++++++++++++++++ Meshtastic/Views/Nodes/NodeMap.swift | 4 ++-- 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/Meshtastic/Model/UserSettings.swift b/Meshtastic/Model/UserSettings.swift index 2ccecdb8..e7c4846d 100644 --- a/Meshtastic/Model/UserSettings.swift +++ b/Meshtastic/Model/UserSettings.swift @@ -59,7 +59,7 @@ class UserSettings: ObservableObject { self.provideLocation = UserDefaults.standard.object(forKey: "provideLocation") as? Bool ?? false self.provideLocationInterval = UserDefaults.standard.object(forKey: "provideLocationInterval") as? Int ?? 900 self.keyboardType = UserDefaults.standard.object(forKey: "keyboardType") as? Int ?? 0 - self.meshMapType = UserDefaults.standard.string(forKey: "meshMapType") ?? "hybridFlyover" + self.meshMapType = UserDefaults.standard.string(forKey: "meshMapType") ?? "standard" self.meshMapCustomTileServer = UserDefaults.standard.string(forKey: "meshMapCustomTileServer") ?? "" } } diff --git a/Meshtastic/Views/Nodes/NodeDetail.swift b/Meshtastic/Views/Nodes/NodeDetail.swift index a73b0254..97785de0 100644 --- a/Meshtastic/Views/Nodes/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/NodeDetail.swift @@ -13,6 +13,7 @@ struct NodeDetail: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager @Environment(\.colorScheme) var colorScheme: ColorScheme + @AppStorage("meshMapType") private var meshMapType = "standard" @State private var mapType: MKMapType = .standard @State var waypointCoordinate: CLLocationCoordinate2D? @State var editingWaypoint: Int = 0 @@ -435,6 +436,28 @@ struct NodeDetail: View { }) .onAppear { self.bleManager.context = context + switch meshMapType { + case "standard": + mapType = .standard + break + case "mutedStandard": + mapType = .mutedStandard + break + case "hybrid": + mapType = .hybrid + break + case "hybridFlyover": + mapType = .hybridFlyover + break + case "satellite": + mapType = .satellite + break + case "satelliteFlyover": + mapType = .satelliteFlyover + break + default: + mapType = .hybridFlyover + } } .task(id: node.num) { do { diff --git a/Meshtastic/Views/Nodes/NodeMap.swift b/Meshtastic/Views/Nodes/NodeMap.swift index 043be960..45fa6765 100644 --- a/Meshtastic/Views/Nodes/NodeMap.swift +++ b/Meshtastic/Views/Nodes/NodeMap.swift @@ -29,7 +29,7 @@ struct NodeMap: View { } } } - @AppStorage("meshMapType") private var meshMapType = "standard" + @AppStorage("meshMapType") private var meshMapType = "hybridFlyover" //&& nodePosition != nil @FetchRequest(sortDescriptors: [NSSortDescriptor(key: "time", ascending: true)], @@ -42,7 +42,7 @@ struct NodeMap: View { ), animation: .none) private var waypoints: FetchedResults - @State private var mapType: MKMapType = .hybridFlyover + @State private var mapType: MKMapType = .standard @State var waypointCoordinate: CLLocationCoordinate2D = LocationHelper.DefaultLocation @State var editingWaypoint: Int = 0 @State private var presentingWaypointForm = false From 99bb8610aea7a9cedb4e35fc29196e85218b73cc Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Mon, 20 Feb 2023 22:15:35 -0800 Subject: [PATCH 10/19] Humidity --- Meshtastic/Views/Nodes/NodeDetail.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Meshtastic/Views/Nodes/NodeDetail.swift b/Meshtastic/Views/Nodes/NodeDetail.swift index 97785de0..652852e0 100644 --- a/Meshtastic/Views/Nodes/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/NodeDetail.swift @@ -40,6 +40,7 @@ struct NodeDetail: View { /// The current weather condition for the city. @State private var condition: WeatherCondition? @State private var temperature: Measurement? + @State private var humidity: Int? @State private var symbolName: String = "cloud.fill" @State private var attributionLink: URL? @@ -87,6 +88,10 @@ struct NodeDetail: View { VStack { Label(temperature?.formatted(.measurement(width: .narrow)) ?? "??", systemImage: symbolName) .font(.caption) + + + Label("\(humidity ?? 0)%", systemImage: "humidity") + .font(.caption2) } .padding(10) .background(.thinMaterial, in: RoundedRectangle(cornerRadius: 12, style: .continuous)) @@ -469,6 +474,7 @@ struct NodeDetail: View { let weather = try await WeatherService.shared.weather(for: mostRecent.nodeLocation!) condition = weather.currentWeather.condition temperature = weather.currentWeather.temperature + humidity = Int(weather.currentWeather.humidity * 100) symbolName = weather.currentWeather.symbolName let attribution = try await WeatherService.shared.attribution From 822028fb7bb4c2c29ac12ed7328b9200e8d12fb0 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 21 Feb 2023 19:03:11 -0800 Subject: [PATCH 11/19] Pin Icons for device roles --- Meshtastic/Enums/AppSettingsEnums.swift | 24 +++++++++++++++++++ .../contents | 1 + .../Views/Map/Custom/MapViewSwiftUI.swift | 24 ++++++++++++++++--- Meshtastic/Views/Settings/AppSettings.swift | 23 ++++++++++++------ de.lproj/Localizable.strings | 1 + en.lproj/Localizable.strings | 3 ++- zh-Hans.lproj/Localizable.strings | 1 + 7 files changed, 66 insertions(+), 11 deletions(-) diff --git a/Meshtastic/Enums/AppSettingsEnums.swift b/Meshtastic/Enums/AppSettingsEnums.swift index 53ce177b..32beae6d 100644 --- a/Meshtastic/Enums/AppSettingsEnums.swift +++ b/Meshtastic/Enums/AppSettingsEnums.swift @@ -35,6 +35,30 @@ enum KeyboardType: Int, CaseIterable, Identifiable { } } +enum CenteringMode: Int, CaseIterable, Identifiable { + + case allAnnotations = 0 + case allPositions = 1 + case latestPosition = 2 + case clientGps = 7 + + var id: Int { self.rawValue } + var description: String { + get { + switch self { + case .allAnnotations: + return "Center of All Annotations"// NSLocalizedString("default", comment: "Default Keyboard") + case .allPositions: + return "Center of All Node Postions"// NSLocalizedString("ascii.capable", comment: "ASCII Capable Keyboard") + case .latestPosition: + return "Latest Node Position"//NSLocalizedString("twitter", comment: "Twitter Keyboard") + case .clientGps: + return "Client GPS Location"//NSLocalizedString("email.address", comment: "Email Address Keyboard") + } + } + } +} + enum MeshMapType: String, CaseIterable, Identifiable { case standard = "standard" diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV8.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV8.xcdatamodel/contents index 1945a68a..a3184912 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV8.xcdatamodel/contents +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV8.xcdatamodel/contents @@ -219,6 +219,7 @@ + diff --git a/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift b/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift index de52d486..503c908b 100644 --- a/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift +++ b/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift @@ -43,6 +43,7 @@ struct MapViewSwiftUI: UIViewRepresentable { mapView.mapType = mapViewType mapView.setUserTrackingMode(.none, animated: true) // Other MKMapView Settings + mapView.preferredConfiguration.elevationStyle = .realistic mapView.isPitchEnabled = true mapView.isRotateEnabled = true mapView.isScrollEnabled = true @@ -54,6 +55,9 @@ struct MapViewSwiftUI: UIViewRepresentable { mapView.showsUserLocation = true #if targetEnvironment(macCatalyst) mapView.showsZoomControls = true + mapView.showsPitchControl = true + #else + mapView.showsPointsOfInterest = true #endif mapView.delegate = context.coordinator return mapView @@ -144,6 +148,9 @@ struct MapViewSwiftUI: UIViewRepresentable { annotationView.titleVisibility = .adaptive } + + //annotationView.tag = -1 + annotationView.canShowCallout = true annotationView.titleVisibility = .adaptive let leftIcon = UIImageView(image: annotationView.glyphText?.image()) leftIcon.backgroundColor = UIColor(.indigo) @@ -155,6 +162,19 @@ struct MapViewSwiftUI: UIViewRepresentable { let distanceFormatter = MKDistanceFormatter() subtitle.text! += "Altitude: \(distanceFormatter.string(fromDistance: Double(positionAnnotation.altitude))) \n" if positionAnnotation.nodePosition?.metadata != nil { + + if DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.client || + DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.clientMute || + DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.routerClient{ + annotationView.glyphImage = UIImage(systemName: "flipphone") + } else if DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.repeater { + annotationView.glyphImage = UIImage(systemName: "repeat") + } else if DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.router { + annotationView.glyphImage = UIImage(systemName: "wifi.router.fill") + } else if DeviceRoles(rawValue: Int(positionAnnotation.nodePosition!.metadata?.role ?? 0)) == DeviceRoles.tracker { + annotationView.glyphImage = UIImage(systemName: "location.viewfinder") + } + let pf = PositionFlags(rawValue: Int(positionAnnotation.nodePosition?.metadata?.positionFlags ?? 3)) if pf.contains(.Satsinview) { subtitle.text! += "Sats in view: \(String(positionAnnotation.satsInView)) \n" @@ -171,11 +191,9 @@ struct MapViewSwiftUI: UIViewRepresentable { annotationView.glyphImage = UIImage(systemName: "location.north.fill")?.rotate(radians: Float(degreesToRadians(Double(positionAnnotation.heading)))) subtitle.text! += "Heading: \(String(positionAnnotation.heading)) \n" - } else { - annotationView.glyphText = "📟" } } else { - annotationView.glyphText = "📟" + annotationView.glyphImage = UIImage(systemName: "flipphone") } subtitle.text! += positionAnnotation.time?.formatted() ?? "Unknown \n" subtitle.numberOfLines = 0 diff --git a/Meshtastic/Views/Settings/AppSettings.swift b/Meshtastic/Views/Settings/AppSettings.swift index 5d616d47..e478e57c 100644 --- a/Meshtastic/Views/Settings/AppSettings.swift +++ b/Meshtastic/Views/Settings/AppSettings.swift @@ -60,14 +60,23 @@ struct AppSettings: View { } } - Section(header: Text("map options")) { + Section(header: Text("global map options")) { - Picker("map.type", selection: $userSettings.meshMapType) { - ForEach(MeshMapType.allCases) { map in - Text(map.description) - } - } - .pickerStyle(DefaultPickerStyle()) + Picker("map.type", selection: $userSettings.meshMapType) { + ForEach(MeshMapType.allCases) { map in + Text(map.description) + } + } + .pickerStyle(DefaultPickerStyle()) + } + + Section(header: Text("mesh map options")) { + Picker("map.centering", selection: $userSettings.meshMapType) { + ForEach(CenteringMode.allCases) { cm in + Text(cm.description) + } + } + .pickerStyle(DefaultPickerStyle()) } } HStack { diff --git a/de.lproj/Localizable.strings b/de.lproj/Localizable.strings index 32add33f..8db9471a 100644 --- a/de.lproj/Localizable.strings +++ b/de.lproj/Localizable.strings @@ -137,6 +137,7 @@ "lora"="LoRa"; "lora.config"="LoRa Einstellungen"; "map"="Mesh Karte"; +"map.centering"="Centering"; "map.type"="kartentyp"; "mesh.log"="Mesh Log"; "mesh.log.bluetooth.config %@"="Bluetooth Konfiguration empfangen: %@"; diff --git a/en.lproj/Localizable.strings b/en.lproj/Localizable.strings index fc958b77..542377e5 100644 --- a/en.lproj/Localizable.strings +++ b/en.lproj/Localizable.strings @@ -137,7 +137,8 @@ "lora"="LoRa"; "lora.config"="LoRa Config"; "map"="Mesh Map"; -"map.type"="Map Type"; +"map.type"="Default Type"; +"map.centering"="Centering"; "mesh.log"="Mesh Log"; "mesh.log.bluetooth.config %@"="Bluetooth config received: %@"; "mesh.log.cannedmessage.config %@"="Canned Message module config received: %@"; diff --git a/zh-Hans.lproj/Localizable.strings b/zh-Hans.lproj/Localizable.strings index bf6c29d1..3a337e5a 100644 --- a/zh-Hans.lproj/Localizable.strings +++ b/zh-Hans.lproj/Localizable.strings @@ -137,6 +137,7 @@ "lora"="LoRa"; "lora.config"="LoRa 配置"; "map"="Mesh 地图"; +"map.centering"="Centering"; "map.type"="地图类型"; "mesh.log"="Mesh 日志"; "mesh.log.bluetooth.config %@"="Bluetooth config received: %@"; From da7576a6509b064a9d1fe11055387f3340a5a0c3 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 21 Feb 2023 19:07:23 -0800 Subject: [PATCH 12/19] Map cleanup --- Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift b/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift index 503c908b..0331b883 100644 --- a/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift +++ b/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift @@ -123,9 +123,9 @@ struct MapViewSwiftUI: UIViewRepresentable { switch annotation { case _ as MKClusterAnnotation: - let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "nodeGroup") as? MKMarkerAnnotationView ?? MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: "NodeGroup") + let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "nodeGroup") as? MKMarkerAnnotationView ?? MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: "WaypointGroup") annotationView.markerTintColor = .brown//.systemRed - //annotationView.displayPriority = .defaultLow + annotationView.displayPriority = .defaultLow annotationView.tag = -1 return annotationView case let positionAnnotation as PositionEntity: @@ -147,9 +147,7 @@ struct MapViewSwiftUI: UIViewRepresentable { annotationView.displayPriority = .defaultHigh annotationView.titleVisibility = .adaptive } - - - //annotationView.tag = -1 + annotationView.tag = -1 annotationView.canShowCallout = true annotationView.titleVisibility = .adaptive let leftIcon = UIImageView(image: annotationView.glyphText?.image()) From 07ceaed23abb24249f0bdb03ff546494e684d37e Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 21 Feb 2023 21:57:22 -0800 Subject: [PATCH 13/19] Centering modes --- Meshtastic/Enums/AppSettingsEnums.swift | 11 ++--- Meshtastic/Model/UserSettings.swift | 7 ++++ .../Views/Map/Custom/MapViewSwiftUI.swift | 41 +++++++++++++------ Meshtastic/Views/Nodes/NodeDetail.swift | 4 +- Meshtastic/Views/Nodes/NodeMap.swift | 7 +++- Meshtastic/Views/Settings/AppSettings.swift | 2 +- 6 files changed, 49 insertions(+), 23 deletions(-) diff --git a/Meshtastic/Enums/AppSettingsEnums.swift b/Meshtastic/Enums/AppSettingsEnums.swift index 32beae6d..7d17601d 100644 --- a/Meshtastic/Enums/AppSettingsEnums.swift +++ b/Meshtastic/Enums/AppSettingsEnums.swift @@ -39,21 +39,18 @@ enum CenteringMode: Int, CaseIterable, Identifiable { case allAnnotations = 0 case allPositions = 1 - case latestPosition = 2 - case clientGps = 7 + case clientGps = 2 var id: Int { self.rawValue } var description: String { get { switch self { case .allAnnotations: - return "Center of All Annotations"// NSLocalizedString("default", comment: "Default Keyboard") + return "All Annotations"// NSLocalizedString("default", comment: "Default Keyboard") case .allPositions: - return "Center of All Node Postions"// NSLocalizedString("ascii.capable", comment: "ASCII Capable Keyboard") - case .latestPosition: - return "Latest Node Position"//NSLocalizedString("twitter", comment: "Twitter Keyboard") + return "All Node Postions"// NSLocalizedString("ascii.capable", comment: "ASCII Capable Keyboard") case .clientGps: - return "Client GPS Location"//NSLocalizedString("email.address", comment: "Email Address Keyboard") + return "Client GPS"//NSLocalizedString("email.address", comment: "Email Address Keyboard") } } } diff --git a/Meshtastic/Model/UserSettings.swift b/Meshtastic/Model/UserSettings.swift index e7c4846d..45a161c0 100644 --- a/Meshtastic/Model/UserSettings.swift +++ b/Meshtastic/Model/UserSettings.swift @@ -45,6 +45,12 @@ class UserSettings: ObservableObject { UserDefaults.standard.set(meshMapType, forKey: "meshMapType") } } + @Published var meshMapCenteringMode: Int { + didSet { + UserDefaults.standard.set(meshMapCenteringMode, forKey: "meshMapCenteringMode") + UserDefaults.standard.synchronize() + } + } @Published var meshMapCustomTileServer: String { didSet { UserDefaults.standard.set(meshMapCustomTileServer, forKey: "meshMapCustomTileServer") @@ -60,6 +66,7 @@ class UserSettings: ObservableObject { self.provideLocationInterval = UserDefaults.standard.object(forKey: "provideLocationInterval") as? Int ?? 900 self.keyboardType = UserDefaults.standard.object(forKey: "keyboardType") as? Int ?? 0 self.meshMapType = UserDefaults.standard.string(forKey: "meshMapType") ?? "standard" + self.meshMapCenteringMode = UserDefaults.standard.object(forKey: "meshMapCenteringMode") as? Int ?? 0 self.meshMapCustomTileServer = UserDefaults.standard.string(forKey: "meshMapCustomTileServer") ?? "" } } diff --git a/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift b/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift index 0331b883..62396ec2 100644 --- a/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift +++ b/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift @@ -16,9 +16,10 @@ struct MapViewSwiftUI: UIViewRepresentable { var onLongPress: (_ waypointCoordinate: CLLocationCoordinate2D) -> Void var onWaypointEdit: (_ waypointId: Int ) -> Void let mapView = MKMapView() - dynamic let positions: [PositionEntity] - dynamic let waypoints: [WaypointEntity] - dynamic let mapViewType: MKMapType + let positions: [PositionEntity] + let waypoints: [WaypointEntity] + let mapViewType: MKMapType + let centeringMode: CenteringMode let centerOnPositionsOnly: Bool @@ -33,16 +34,27 @@ struct MapViewSwiftUI: UIViewRepresentable { func makeUIView(context: Context) -> MKMapView { // Parameters - mapView.addAnnotations(waypoints) - if centerOnPositionsOnly { - mapView.fit(annotations: positions, andShow: true) - } else { - mapView.addAnnotations(positions) - mapView.fitAllAnnotations() - } mapView.mapType = mapViewType - mapView.setUserTrackingMode(.none, animated: true) + mapView.addAnnotations(waypoints) + // Logic to manage the map centering options + switch centeringMode { + case .allAnnotations: + mapView.addAnnotations(positions) + mapView.fitAllAnnotations() + case .allPositions: + mapView.fit(annotations: positions, andShow: true) + case .clientGps: + + let span = MKCoordinateSpan(latitudeDelta: 0.001, longitudeDelta: 0.001) + let center = CLLocationCoordinate2D(latitude: LocationHelper.currentLocation.latitude, longitude: LocationHelper.currentLocation.longitude) + let region = MKCoordinateRegion(center: center, span: span) + mapView.setRegion(region, animated: true) + mapView.setUserTrackingMode(.followWithHeading, animated: true) + mapView.addAnnotations(positions) + } + // Other MKMapView Settings + mapView.showsUserLocation = true mapView.preferredConfiguration.elevationStyle = .realistic mapView.isPitchEnabled = true mapView.isRotateEnabled = true @@ -52,7 +64,7 @@ struct MapViewSwiftUI: UIViewRepresentable { mapView.showsCompass = true mapView.showsScale = true mapView.showsTraffic = true - mapView.showsUserLocation = true + #if targetEnvironment(macCatalyst) mapView.showsZoomControls = true mapView.showsPitchControl = true @@ -90,6 +102,11 @@ struct MapViewSwiftUI: UIViewRepresentable { } DispatchQueue.main.async { + if centeringMode == CenteringMode.clientGps { + let span = MKCoordinateSpan(latitudeDelta: 0.001, longitudeDelta: 0.001) + let region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: LocationHelper.currentLocation.latitude, longitude: LocationHelper.currentLocation.longitude), span: span) + mapView.setRegion(region, animated: true) + } mapView.removeAnnotations(mapView.annotations) mapView.addAnnotations(positions) mapView.addAnnotations(waypoints) diff --git a/Meshtastic/Views/Nodes/NodeDetail.swift b/Meshtastic/Views/Nodes/NodeDetail.swift index 652852e0..9e967ed7 100644 --- a/Meshtastic/Views/Nodes/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/NodeDetail.swift @@ -67,7 +67,9 @@ struct NodeDetail: View { editingWaypoint = wpId presentingWaypointForm = true } - }, positions: annotations, waypoints: Array(waypoints), mapViewType: mapType, + }, positions: annotations, waypoints: Array(waypoints), + mapViewType: mapType, + centeringMode: .allPositions, centerOnPositionsOnly: true, customMapOverlay: self.customMapOverlay, overlays: self.overlays diff --git a/Meshtastic/Views/Nodes/NodeMap.swift b/Meshtastic/Views/Nodes/NodeMap.swift index 45fa6765..008c5d2b 100644 --- a/Meshtastic/Views/Nodes/NodeMap.swift +++ b/Meshtastic/Views/Nodes/NodeMap.swift @@ -30,6 +30,7 @@ struct NodeMap: View { } } @AppStorage("meshMapType") private var meshMapType = "hybridFlyover" + @AppStorage("meshMapCenteringMode") private var meshMapCenteringMode = 0 //&& nodePosition != nil @FetchRequest(sortDescriptors: [NSSortDescriptor(key: "time", ascending: true)], @@ -43,6 +44,7 @@ struct NodeMap: View { private var waypoints: FetchedResults @State private var mapType: MKMapType = .standard + @State private var mapCenteringMode: CenteringMode = .allAnnotations @State var waypointCoordinate: CLLocationCoordinate2D = LocationHelper.DefaultLocation @State var editingWaypoint: Int = 0 @State private var presentingWaypointForm = false @@ -73,7 +75,8 @@ struct NodeMap: View { } }, positions: Array(positions), waypoints: Array(waypoints), - mapViewType: mapType , + mapViewType: mapType, + centeringMode: mapCenteringMode, centerOnPositionsOnly: false, customMapOverlay: self.customMapOverlay, overlays: self.overlays @@ -111,7 +114,7 @@ struct NodeMap: View { .onAppear(perform: { self.bleManager.context = context self.bleManager.userSettings = userSettings - + mapCenteringMode = CenteringMode(rawValue: meshMapCenteringMode) ?? CenteringMode.allAnnotations switch meshMapType { case "standard": mapType = .standard diff --git a/Meshtastic/Views/Settings/AppSettings.swift b/Meshtastic/Views/Settings/AppSettings.swift index e478e57c..4113945f 100644 --- a/Meshtastic/Views/Settings/AppSettings.swift +++ b/Meshtastic/Views/Settings/AppSettings.swift @@ -71,7 +71,7 @@ struct AppSettings: View { } Section(header: Text("mesh map options")) { - Picker("map.centering", selection: $userSettings.meshMapType) { + Picker("map.centering", selection: $userSettings.meshMapCenteringMode) { ForEach(CenteringMode.allCases) { cm in Text(cm.description) } From 2df0654a8f2c49e072885ff598bfbb4855f79606 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Wed, 22 Feb 2023 08:09:38 -0800 Subject: [PATCH 14/19] Centering on update --- .../Views/Map/Custom/MapViewSwiftUI.swift | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift b/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift index 62396ec2..e3f54baa 100644 --- a/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift +++ b/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift @@ -45,7 +45,7 @@ struct MapViewSwiftUI: UIViewRepresentable { mapView.fit(annotations: positions, andShow: true) case .clientGps: - let span = MKCoordinateSpan(latitudeDelta: 0.001, longitudeDelta: 0.001) + let span = MKCoordinateSpan(latitudeDelta: 0.003, longitudeDelta: 0.003) let center = CLLocationCoordinate2D(latitude: LocationHelper.currentLocation.latitude, longitude: LocationHelper.currentLocation.longitude) let region = MKCoordinateRegion(center: center, span: span) mapView.setRegion(region, animated: true) @@ -68,8 +68,6 @@ struct MapViewSwiftUI: UIViewRepresentable { #if targetEnvironment(macCatalyst) mapView.showsZoomControls = true mapView.showsPitchControl = true - #else - mapView.showsPointsOfInterest = true #endif mapView.delegate = context.coordinator return mapView @@ -102,14 +100,20 @@ struct MapViewSwiftUI: UIViewRepresentable { } DispatchQueue.main.async { - if centeringMode == CenteringMode.clientGps { - let span = MKCoordinateSpan(latitudeDelta: 0.001, longitudeDelta: 0.001) + mapView.removeAnnotations(mapView.annotations) + mapView.addAnnotations(waypoints) + switch centeringMode { + case .allAnnotations: + mapView.addAnnotations(positions) + mapView.fitAllAnnotations() + case .allPositions: + mapView.fit(annotations: positions, andShow: true) + case .clientGps: + mapView.addAnnotations(positions) + let span = MKCoordinateSpan(latitudeDelta: 0.003, longitudeDelta: 0.003) let region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: LocationHelper.currentLocation.latitude, longitude: LocationHelper.currentLocation.longitude), span: span) mapView.setRegion(region, animated: true) } - mapView.removeAnnotations(mapView.annotations) - mapView.addAnnotations(positions) - mapView.addAnnotations(waypoints) } } From 6e52ae60fef2ed66f6a669df7d882686fee33b69 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Wed, 22 Feb 2023 09:37:31 -0800 Subject: [PATCH 15/19] Make latest position a boolean value instead of trying to query it over and over. --- Meshtastic/Persistence/UpdateCoreData.swift | 13 +++++++++++++ Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift | 5 +---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/Meshtastic/Persistence/UpdateCoreData.swift b/Meshtastic/Persistence/UpdateCoreData.swift index 4e37461c..e04170a6 100644 --- a/Meshtastic/Persistence/UpdateCoreData.swift +++ b/Meshtastic/Persistence/UpdateCoreData.swift @@ -119,7 +119,18 @@ func upsertPositionPacket (packet: MeshPacket, context: NSManagedObjectContext) let fetchedNode = try context.fetch(fetchNodePositionRequest) as! [NodeInfoEntity] if fetchedNode.count == 1 { + // Unset the current latest position for this node + let fetchCurrentLatestPositionsRequest: NSFetchRequest = NSFetchRequest.init(entityName: "PositionEntity") + fetchCurrentLatestPositionsRequest.predicate = NSPredicate(format: "nodePosition.num == %lld && latest = true", Int64(packet.from)) + let fetchedPositions = try context.fetch(fetchCurrentLatestPositionsRequest) as! [PositionEntity] + if fetchedPositions.count > 0 { + for position in fetchedPositions { + position.latest = false + } + } + let position = PositionEntity(context: context) + position.latest = true position.snr = packet.rxSnr position.seqNo = Int32(positionMessage.seqNumber) position.latitudeI = positionMessage.latitudeI @@ -134,12 +145,14 @@ func upsertPositionPacket (packet: MeshPacket, context: NSManagedObjectContext) position.time = Date(timeIntervalSince1970: TimeInterval(Int64(positionMessage.time))) } let mutablePositions = fetchedNode[0].positions!.mutableCopy() as! NSMutableOrderedSet + mutablePositions.add(position) fetchedNode[0].id = Int64(packet.from) fetchedNode[0].num = Int64(packet.from) fetchedNode[0].lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(positionMessage.time))) fetchedNode[0].snr = packet.rxSnr fetchedNode[0].positions = mutablePositions.copy() as? NSOrderedSet + do { try context.save() print("💾 Updated Node Position Coordinates, SNR and Time from Position App Packet For: \(fetchedNode[0].num)") diff --git a/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift b/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift index e3f54baa..14c55fc6 100644 --- a/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift +++ b/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift @@ -151,14 +151,11 @@ struct MapViewSwiftUI: UIViewRepresentable { return annotationView case let positionAnnotation as PositionEntity: let reuseID = String(positionAnnotation.nodePosition?.num ?? 0) + "-" + String(positionAnnotation.time?.timeIntervalSince1970 ?? 0) - - let latest = parent.positions.last(where: { $0.nodePosition?.num ?? 0 == positionAnnotation.nodePosition?.num ?? -1 && true }) - let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "node") as? MKMarkerAnnotationView ?? MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: reuseID ) annotationView.tag = -1 annotationView.canShowCallout = true - if latest == positionAnnotation { + if positionAnnotation.latest { annotationView.markerTintColor = .systemRed annotationView.displayPriority = .required annotationView.titleVisibility = .visible From 48994f445d999bec34c8e4d12a2bdc76f043fe5c Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Wed, 22 Feb 2023 13:16:33 -0800 Subject: [PATCH 16/19] Auto recentering --- Meshtastic/Model/UserSettings.swift | 7 ++ .../Views/Map/Custom/MapViewSwiftUI.swift | 68 ++++++++++++------- Meshtastic/Views/Settings/AppSettings.swift | 6 ++ de.lproj/Localizable.strings | 1 + en.lproj/Localizable.strings | 3 +- zh-Hans.lproj/Localizable.strings | 1 + 6 files changed, 62 insertions(+), 24 deletions(-) diff --git a/Meshtastic/Model/UserSettings.swift b/Meshtastic/Model/UserSettings.swift index 45a161c0..675d238c 100644 --- a/Meshtastic/Model/UserSettings.swift +++ b/Meshtastic/Model/UserSettings.swift @@ -51,6 +51,12 @@ class UserSettings: ObservableObject { UserDefaults.standard.synchronize() } } + @Published var meshMapRecentering: Bool { + didSet { + UserDefaults.standard.set(meshMapCenteringMode, forKey: "meshMapRecentering") + UserDefaults.standard.synchronize() + } + } @Published var meshMapCustomTileServer: String { didSet { UserDefaults.standard.set(meshMapCustomTileServer, forKey: "meshMapCustomTileServer") @@ -67,6 +73,7 @@ class UserSettings: ObservableObject { self.keyboardType = UserDefaults.standard.object(forKey: "keyboardType") as? Int ?? 0 self.meshMapType = UserDefaults.standard.string(forKey: "meshMapType") ?? "standard" self.meshMapCenteringMode = UserDefaults.standard.object(forKey: "meshMapCenteringMode") as? Int ?? 0 + self.meshMapRecentering = UserDefaults.standard.object(forKey: "meshMapRecentering") as? Bool ?? true self.meshMapCustomTileServer = UserDefaults.standard.string(forKey: "meshMapCustomTileServer") ?? "" } } diff --git a/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift b/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift index 14c55fc6..ec549754 100644 --- a/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift +++ b/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift @@ -22,6 +22,7 @@ struct MapViewSwiftUI: UIViewRepresentable { let centeringMode: CenteringMode let centerOnPositionsOnly: Bool + @AppStorage("meshMapRecenter") private var recenter = true // Offline Maps //make this view dependent on the UserDefault that is updated when importing a new map file @@ -38,19 +39,19 @@ struct MapViewSwiftUI: UIViewRepresentable { mapView.addAnnotations(waypoints) // Logic to manage the map centering options switch centeringMode { - case .allAnnotations: - mapView.addAnnotations(positions) - mapView.fitAllAnnotations() - case .allPositions: - mapView.fit(annotations: positions, andShow: true) - case .clientGps: - - let span = MKCoordinateSpan(latitudeDelta: 0.003, longitudeDelta: 0.003) - let center = CLLocationCoordinate2D(latitude: LocationHelper.currentLocation.latitude, longitude: LocationHelper.currentLocation.longitude) - let region = MKCoordinateRegion(center: center, span: span) - mapView.setRegion(region, animated: true) - mapView.setUserTrackingMode(.followWithHeading, animated: true) - mapView.addAnnotations(positions) + case .allAnnotations: + mapView.addAnnotations(positions) + mapView.fitAllAnnotations() + case .allPositions: + mapView.fit(annotations: positions, andShow: true) + case .clientGps: + + let span = MKCoordinateSpan(latitudeDelta: 0.003, longitudeDelta: 0.003) + let center = CLLocationCoordinate2D(latitude: LocationHelper.currentLocation.latitude, longitude: LocationHelper.currentLocation.longitude) + let region = MKCoordinateRegion(center: center, span: span) + mapView.setRegion(region, animated: true) + mapView.setUserTrackingMode(.followWithHeading, animated: true) + mapView.addAnnotations(positions) } // Other MKMapView Settings @@ -61,14 +62,27 @@ struct MapViewSwiftUI: UIViewRepresentable { mapView.isScrollEnabled = true mapView.isZoomEnabled = true mapView.showsBuildings = true - mapView.showsCompass = true mapView.showsScale = true mapView.showsTraffic = true - #if targetEnvironment(macCatalyst) +#if targetEnvironment(macCatalyst) + // Show the default always visible compass and the mac only controls + mapView.showsCompass = true mapView.showsZoomControls = true mapView.showsPitchControl = true - #endif +#else + +#if os(iOS) + // Hide the default compass that only appears when you are not going north and instead always show the compass in the bottom right corner of the map + mapView.showsCompass = false + let compassButton = MKCompassButton(mapView: mapView) // Make a new compass + compassButton.compassVisibility = .visible // Make it visible + mapView.addSubview(compassButton) // Add it to the view + compassButton.translatesAutoresizingMaskIntoConstraints = false + compassButton.trailingAnchor.constraint(equalTo: mapView.trailingAnchor, constant: -5).isActive = true + compassButton.bottomAnchor.constraint(equalTo: mapView.bottomAnchor, constant: -25).isActive = true +#endif +#endif mapView.delegate = context.coordinator return mapView } @@ -79,7 +93,7 @@ struct MapViewSwiftUI: UIViewRepresentable { if self.customMapOverlay != self.presentCustomMapOverlayHash || self.loadedLastUpdatedLocalMapFile != self.lastUpdatedLocalMapFile { mapView.removeOverlays(mapView.overlays) if self.customMapOverlay != nil { - + let fileManager = FileManager.default let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first! let tilePath = documentsDirectory.appendingPathComponent("offline_map.mbtiles", isDirectory: false).path @@ -105,14 +119,22 @@ struct MapViewSwiftUI: UIViewRepresentable { switch centeringMode { case .allAnnotations: mapView.addAnnotations(positions) - mapView.fitAllAnnotations() + if recenter { + mapView.fitAllAnnotations() + } case .allPositions: - mapView.fit(annotations: positions, andShow: true) + if recenter { + mapView.fit(annotations: positions, andShow: true) + } else { + mapView.addAnnotations(positions) + } case .clientGps: mapView.addAnnotations(positions) - let span = MKCoordinateSpan(latitudeDelta: 0.003, longitudeDelta: 0.003) - let region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: LocationHelper.currentLocation.latitude, longitude: LocationHelper.currentLocation.longitude), span: span) + if recenter { + let span = MKCoordinateSpan(latitudeDelta: 0.003, longitudeDelta: 0.003) + let region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: LocationHelper.currentLocation.latitude, longitude: LocationHelper.currentLocation.longitude), span: span) mapView.setRegion(region, animated: true) + } } } } @@ -289,9 +311,9 @@ struct MapViewSwiftUI: UIViewRepresentable { } public func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer { - + if let index = self.overlays.firstIndex(where: { overlay_ in overlay_.shape.hash == overlay.hash }) { - + let unwrappedOverlay = self.overlays[index] if let circleOverlay = unwrappedOverlay.shape as? MKCircle { let renderer = MKCircleRenderer(circle: circleOverlay) diff --git a/Meshtastic/Views/Settings/AppSettings.swift b/Meshtastic/Views/Settings/AppSettings.swift index 4113945f..133a7b81 100644 --- a/Meshtastic/Views/Settings/AppSettings.swift +++ b/Meshtastic/Views/Settings/AppSettings.swift @@ -77,6 +77,12 @@ struct AppSettings: View { } } .pickerStyle(DefaultPickerStyle()) + + Toggle(isOn: $userSettings.meshMapRecentering) { + + Label("map.recentering", systemImage: "rectangle.center.inset.filled") + } + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) } } HStack { diff --git a/de.lproj/Localizable.strings b/de.lproj/Localizable.strings index 8db9471a..f6785b90 100644 --- a/de.lproj/Localizable.strings +++ b/de.lproj/Localizable.strings @@ -138,6 +138,7 @@ "lora.config"="LoRa Einstellungen"; "map"="Mesh Karte"; "map.centering"="Centering"; +"map.recentering"="Automatic Re-centering"; "map.type"="kartentyp"; "mesh.log"="Mesh Log"; "mesh.log.bluetooth.config %@"="Bluetooth Konfiguration empfangen: %@"; diff --git a/en.lproj/Localizable.strings b/en.lproj/Localizable.strings index 542377e5..36951b44 100644 --- a/en.lproj/Localizable.strings +++ b/en.lproj/Localizable.strings @@ -138,7 +138,8 @@ "lora.config"="LoRa Config"; "map"="Mesh Map"; "map.type"="Default Type"; -"map.centering"="Centering"; +"map.centering"="Centering Mode"; +"map.recentering"="Automatic Re-centering"; "mesh.log"="Mesh Log"; "mesh.log.bluetooth.config %@"="Bluetooth config received: %@"; "mesh.log.cannedmessage.config %@"="Canned Message module config received: %@"; diff --git a/zh-Hans.lproj/Localizable.strings b/zh-Hans.lproj/Localizable.strings index 3a337e5a..9a54174b 100644 --- a/zh-Hans.lproj/Localizable.strings +++ b/zh-Hans.lproj/Localizable.strings @@ -138,6 +138,7 @@ "lora.config"="LoRa 配置"; "map"="Mesh 地图"; "map.centering"="Centering"; +"map.recentering"="Automatic Re-centering"; "map.type"="地图类型"; "mesh.log"="Mesh 日志"; "mesh.log.bluetooth.config %@"="Bluetooth config received: %@"; From 673592d0a50d24539b77a350edcd81d081548538 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Wed, 22 Feb 2023 14:16:27 -0800 Subject: [PATCH 17/19] Undo ble preferred peripheral updates for now --- Meshtastic/Views/Bluetooth/Connect.swift | 73 +++++++------------ .../Views/Map/Custom/MapViewSwiftUI.swift | 2 +- 2 files changed, 26 insertions(+), 49 deletions(-) diff --git a/Meshtastic/Views/Bluetooth/Connect.swift b/Meshtastic/Views/Bluetooth/Connect.swift index 7280d277..0f7affb4 100644 --- a/Meshtastic/Views/Bluetooth/Connect.swift +++ b/Meshtastic/Views/Bluetooth/Connect.swift @@ -21,13 +21,11 @@ struct Connect: View { @State var isPreferredRadio: Bool = false @State var isUnsetRegion = false @State var invalidFirmwareVersion = false - @State var isPresentingPreferredPeripherialDialog = false - @State var showDialogForNextPeripherialChange = true - var body: some View { + var body: some View { NavigationStack { - VStack { + VStack { List { if bleManager.isSwitchedOn { Section(header: Text("connected.radio").font(.title)) { @@ -63,44 +61,23 @@ struct Connect: View { Toggle("preferred.radio", isOn: $bleManager.preferredPeripheral) .toggleStyle(SwitchToggleStyle(tint: .accentColor)) .labelsHidden() - .confirmationDialog( - "Are you sure? Switching your preferred peripheral will clear all app data from the phone.", - isPresented: $isPresentingPreferredPeripherialDialog, - titleVisibility: .visible - ) { - Button("Confirm", role: .destructive) { - - if bleManager.preferredPeripheral { - if bleManager.connectedPeripheral != nil { - userSettings.preferredPeripheralId = bleManager.connectedPeripheral!.peripheral.identifier.uuidString - userSettings.preferredNodeNum = bleManager.connectedPeripheral!.num - bleManager.preferredPeripheral = true - isPreferredRadio = true - showDialogForNextPeripherialChange = false - } - } else { - - if bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.peripheral.identifier.uuidString == userSettings.preferredPeripheralId { - userSettings.preferredPeripheralId = "" - userSettings.preferredNodeNum = 0 - bleManager.preferredPeripheral = false - isPreferredRadio = false - } - } - bleManager.disconnectPeripheral() - clearCoreDataDatabase(context: context) - } - Button("Cancel", role: .cancel) { - showDialogForNextPeripherialChange = false - bleManager.preferredPeripheral = !bleManager.preferredPeripheral - } - } .onChange(of: bleManager.preferredPeripheral) { value in - - if showDialogForNextPeripherialChange { - isPresentingPreferredPeripherialDialog = true - } else { - showDialogForNextPeripherialChange = true + if value { + if bleManager.connectedPeripheral != nil { + userSettings.preferredPeripheralId = bleManager.connectedPeripheral!.peripheral.identifier.uuidString + userSettings.preferredNodeNum = bleManager.connectedPeripheral!.num + bleManager.preferredPeripheral = true + isPreferredRadio = true + } + } else { + + if bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.peripheral.identifier.uuidString == userSettings.preferredPeripheralId { + + userSettings.preferredPeripheralId = "" + userSettings.preferredNodeNum = 0 + bleManager.preferredPeripheral = false + isPreferredRadio = false + } } } } @@ -225,7 +202,7 @@ struct Connect: View { HStack(alignment: .center) { Spacer() - + #if targetEnvironment(macCatalyst) if bleManager.connectedPeripheral != nil { @@ -249,15 +226,15 @@ struct Connect: View { } #endif Spacer() - } + } .padding(.bottom, 10) - } - .navigationTitle("bluetooth") + } + .navigationTitle("bluetooth") .navigationBarItems(leading: MeshtasticLogo(), trailing: ZStack { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") }) - } + } .sheet(isPresented: $invalidFirmwareVersion, onDismiss: didDismissSheet) { InvalidVersion(minimumVersion: self.bleManager.minimumVersion, version: self.bleManager.connectedVersion) .presentationDetents([.large]) @@ -290,7 +267,7 @@ struct Connect: View { } } } - .onAppear(perform: { + .onAppear(perform: { self.bleManager.context = context self.bleManager.userSettings = userSettings @@ -308,7 +285,7 @@ struct Connect: View { isPreferredRadio = false } }) - } + } func didDismissSheet() { bleManager.disconnectPeripheral(reconnect: false) } diff --git a/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift b/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift index ec549754..4f40e95c 100644 --- a/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift +++ b/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift @@ -22,7 +22,7 @@ struct MapViewSwiftUI: UIViewRepresentable { let centeringMode: CenteringMode let centerOnPositionsOnly: Bool - @AppStorage("meshMapRecenter") private var recenter = true + @AppStorage("meshMapRecenter") private var recenter = false // Offline Maps //make this view dependent on the UserDefault that is updated when importing a new map file From 3002d377a00490b8c62beb971fff566b904ae7a7 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Wed, 22 Feb 2023 16:12:34 -0800 Subject: [PATCH 18/19] Name app setting properly --- Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift b/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift index 4f40e95c..3534b784 100644 --- a/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift +++ b/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift @@ -22,7 +22,7 @@ struct MapViewSwiftUI: UIViewRepresentable { let centeringMode: CenteringMode let centerOnPositionsOnly: Bool - @AppStorage("meshMapRecenter") private var recenter = false + @AppStorage("meshMapRecentering") private var recenter = false // Offline Maps //make this view dependent on the UserDefault that is updated when importing a new map file @@ -114,6 +114,7 @@ struct MapViewSwiftUI: UIViewRepresentable { } DispatchQueue.main.async { + print(recenter) mapView.removeAnnotations(mapView.annotations) mapView.addAnnotations(waypoints) switch centeringMode { From b79e63e573328ac7b352cea16a166af1dfd00098 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Wed, 22 Feb 2023 16:33:47 -0800 Subject: [PATCH 19/19] Remove unused function --- Meshtastic/Helpers/BLEManager.swift | 68 +++++++++---------- .../Views/Map/Custom/MapViewSwiftUI.swift | 1 - 2 files changed, 34 insertions(+), 35 deletions(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 4bad7d2e..167e1c0f 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -2011,38 +2011,38 @@ extension BLEManager: CBCentralManagerDelegate { self.peripherals.removeAll(where: { $0.lastUpdate < visibleDuration}) } - func centralManager(_ central: CBCentralManager, willRestoreState dict: [String : Any]) { - - guard let peripherals = dict[CBCentralManagerRestoredStatePeripheralsKey] as? [CBPeripheral] else { - return - } - print(peripherals) - if peripherals.count > 0 { - //connectedPeripheral.peripheral = peripherals[0] - // 5 - //connectedPeripheral.peripheral.delegate = self - - for peripheral in peripherals { - - switch peripheral.state { - case .connecting: // I've only seen this happen when - // re-launching attached to Xcode. - print("Xcode Restore") - - case .connected: // Store for connection / requesting - // notifications when BT starts. - print("Actual restore") - //centralManager.connect(peripheral) - default: break - } - - - - // connectedPeripheral.peripheral - //connectedPeripheral.peripheral = peripheral - //connectedPeripheral.peripheral.delegate = self - } - } - print("willRestoreState Hit!") - } +// func centralManager(_ central: CBCentralManager, willRestoreState dict: [String : Any]) { +// +// guard let peripherals = dict[CBCentralManagerRestoredStatePeripheralsKey] as? [CBPeripheral] else { +// return +// } +// print(peripherals) +// if peripherals.count > 0 { +// //connectedPeripheral.peripheral = peripherals[0] +// // 5 +// //connectedPeripheral.peripheral.delegate = self +// +// for peripheral in peripherals { +// +// switch peripheral.state { +// case .connecting: // I've only seen this happen when +// // re-launching attached to Xcode. +// print("Xcode Restore") +// +// case .connected: // Store for connection / requesting +// // notifications when BT starts. +// print("Actual restore") +// //centralManager.connect(peripheral) +// default: break +// } +// +// +// +// // connectedPeripheral.peripheral +// //connectedPeripheral.peripheral = peripheral +// //connectedPeripheral.peripheral.delegate = self +// } +// } +// print("willRestoreState Hit!") +// } } diff --git a/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift b/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift index 3534b784..4cc5ef0b 100644 --- a/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift +++ b/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift @@ -114,7 +114,6 @@ struct MapViewSwiftUI: UIViewRepresentable { } DispatchQueue.main.async { - print(recenter) mapView.removeAnnotations(mapView.annotations) mapView.addAnnotations(waypoints) switch centeringMode {