From 70fb1eb8d8b8ae092f90efa258dff4bc455a68b9 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 5 May 2024 08:38:27 -0700 Subject: [PATCH 01/17] Don't add channels with the same name Add additional precision options --- Meshtastic.xcodeproj/project.pbxproj | 8 ++-- Meshtastic/Helpers/BLEManager.swift | 47 ++++++++++++------- Meshtastic/MeshtasticApp.swift | 2 +- .../Map/MapContent/MeshMapContent.swift | 2 +- .../Map/MapContent/NodeMapContent.swift | 2 +- .../Views/Settings/Channels/ChannelForm.swift | 2 +- .../Views/Settings/SaveChannelQRCode.swift | 47 ++++++++++++++----- 7 files changed, 72 insertions(+), 38 deletions(-) diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index aa5a8a3f..a96e3707 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -1583,7 +1583,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.3.8; + MARKETING_VERSION = 2.3.9; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; @@ -1617,7 +1617,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.3.8; + MARKETING_VERSION = 2.3.9; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; @@ -1690,7 +1690,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.3.8; + MARKETING_VERSION = 2.3.9; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1723,7 +1723,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.3.8; + MARKETING_VERSION = 2.3.9; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index dd78784e..0c997f05 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -1328,33 +1328,43 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate if isConnected { var i: Int32 = 0 + var myInfo: MyInfoEntity // Before we get started delete the existing channels from the myNodeInfo if !addChannels { tryClearExistingChannels() - } else { - // We are trying to add a channel so lets get the last index - let fetchMyInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "MyInfoEntity") - fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", Int64(connectedPeripheral.num)) - do { - let fetchedMyInfo = try context?.fetch(fetchMyInfoRequest) as? [MyInfoEntity] ?? [] - if fetchedMyInfo.count == 1 { - if addChannels { - i = Int32(fetchedMyInfo[0].channels?.count ?? -1) - // Bail out if the index is negative or bigger than our max of 8 - if i < 0 || i > 8 { - return false - } - } - } - } catch { - print("Failed to find a node MyInfo to save these channels to") - } } + let decodedString = base64UrlString.base64urlToBase64() if let decodedData = Data(base64Encoded: decodedString) { do { let channelSet: ChannelSet = try ChannelSet(serializedData: decodedData) for cs in channelSet.settings { + if addChannels { + // We are trying to add a channel so lets get the last index + let fetchMyInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "MyInfoEntity") + fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", Int64(connectedPeripheral.num)) + do { + let fetchedMyInfo = try context?.fetch(fetchMyInfoRequest) as? [MyInfoEntity] ?? [] + if fetchedMyInfo.count == 1 { + i = Int32(fetchedMyInfo[0].channels?.count ?? -1) + myInfo = fetchedMyInfo[0] + // Bail out if the index is negative or bigger than our max of 8 + if i < 0 || i > 8 { + return false + } + // Bail out if there are no channels or if the same channel name already exists + guard let mutableChannels = myInfo.channels!.mutableCopy() as? NSMutableOrderedSet else { + return false + } + if mutableChannels.first(where: {($0 as AnyObject).name == cs.name }) is ChannelEntity { + return false + } + } + } catch { + print("Failed to find a node MyInfo to save these channels to") + } + } + var chan = Channel() if i == 0 { chan.role = Channel.Role.primary @@ -1364,6 +1374,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate chan.settings = cs chan.index = i i += 1 + var adminPacket = AdminMessage() adminPacket.setChannel = chan var meshPacket: MeshPacket = MeshPacket() diff --git a/Meshtastic/MeshtasticApp.swift b/Meshtastic/MeshtasticApp.swift index e9de24bc..0b8275dd 100644 --- a/Meshtastic/MeshtasticApp.swift +++ b/Meshtastic/MeshtasticApp.swift @@ -28,7 +28,7 @@ struct MeshtasticAppleApp: App { .environmentObject(bleManager) .sheet(isPresented: $saveChannels) { SaveChannelQRCode(channelSetLink: channelSettings ?? "Empty Channel URL", addChannels: addChannels, bleManager: bleManager) - .presentationDetents([.medium, .large]) + .presentationDetents([.large]) .presentationDragIndicator(.visible) } .onContinueUserActivity(NSUserActivityTypeBrowsingWeb) { userActivity in diff --git a/Meshtastic/Views/Nodes/Helpers/Map/MapContent/MeshMapContent.swift b/Meshtastic/Views/Nodes/Helpers/Map/MapContent/MeshMapContent.swift index 6a03aba8..52d3a97c 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/MapContent/MeshMapContent.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/MapContent/MeshMapContent.swift @@ -142,7 +142,7 @@ struct MeshMapContent: MapContent { } } /// Reduced Precision Map Circles - if 11...16 ~= position.precisionBits { + if 2...24 ~= position.precisionBits { let pp = PositionPrecision(rawValue: Int(position.precisionBits)) let radius : CLLocationDistance = pp?.precisionMeters ?? 0 if radius > 0.0 { diff --git a/Meshtastic/Views/Nodes/Helpers/Map/MapContent/NodeMapContent.swift b/Meshtastic/Views/Nodes/Helpers/Map/MapContent/NodeMapContent.swift index 83a5ccbc..fd869c30 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/MapContent/NodeMapContent.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/MapContent/NodeMapContent.swift @@ -49,7 +49,7 @@ struct NodeMapContent: MapContent { let pf = PositionFlags(rawValue: Int(position.nodePosition?.metadata?.positionFlags ?? 771)) let headingDegrees = Angle.degrees(Double(position.heading)) /// Reduced Precision Map Circle - if position.latest && 11...16 ~= position.precisionBits { + if position.latest && 2...24 ~= position.precisionBits { let pp = PositionPrecision(rawValue: Int(position.precisionBits)) let radius : CLLocationDistance = pp?.precisionMeters ?? 0 if radius > 0.0 { diff --git a/Meshtastic/Views/Settings/Channels/ChannelForm.swift b/Meshtastic/Views/Settings/Channels/ChannelForm.swift index 60e1b2dd..560d1d70 100644 --- a/Meshtastic/Views/Settings/Channels/ChannelForm.swift +++ b/Meshtastic/Views/Settings/Channels/ChannelForm.swift @@ -160,7 +160,7 @@ struct ChannelForm: View { if !preciseLocation { VStack(alignment: .leading) { Label("Approximate Location", systemImage: "location.slash.circle.fill") - Slider(value: $positionPrecision, in: 11...18, step: 1) { + Slider(value: $positionPrecision, in: 10...19, step: 1) { } minimumValueLabel: { Image(systemName: "minus") } maximumValueLabel: { diff --git a/Meshtastic/Views/Settings/SaveChannelQRCode.swift b/Meshtastic/Views/Settings/SaveChannelQRCode.swift index f9ca2557..7de80622 100644 --- a/Meshtastic/Views/Settings/SaveChannelQRCode.swift +++ b/Meshtastic/Views/Settings/SaveChannelQRCode.swift @@ -13,6 +13,7 @@ struct SaveChannelQRCode: View { var channelSetLink: String var addChannels: Bool = false var bleManager: BLEManager + @State var showError: Bool = false @State var connectedToDevice = false var body: some View { @@ -20,26 +21,48 @@ struct SaveChannelQRCode: View { Text("\(addChannels ? "Add" : "Replace all") Channels?") .font(.title) Text("These settings will \(addChannels ? "add" : "replace all") channels. The current LoRa Config will be replaced. After everything saves your device will reboot.") + .fixedSize(horizontal: false, vertical: true) .foregroundColor(.gray) .font(.title3) .padding() + if showError { + Text("Channels being added from the QR code did not save. When adding channels the names must be unique.") + .fixedSize(horizontal: false, vertical: true) + .foregroundColor(.red) + .font(.callout) + .padding() + } HStack { - - Button { - let success = bleManager.saveChannelSet(base64UrlString: channelSetLink, addChannels: addChannels) - if success { - dismiss() + if !showError { + Button { + let success = bleManager.saveChannelSet(base64UrlString: channelSetLink, addChannels: addChannels) + if success { + dismiss() + } else { + showError = true + } + } label: { + Label("save", systemImage: "square.and.arrow.down") } + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + .padding() + .disabled(!connectedToDevice) + } else { + Button { + dismiss() + } label: { + Label("cancel", systemImage: "xmark") - } label: { - Label("save", systemImage: "square.and.arrow.down") + } + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + .padding() } - .buttonStyle(.bordered) - .buttonBorderShape(.capsule) - .controlSize(.large) - .padding() - .disabled(!connectedToDevice) + #if targetEnvironment(macCatalyst) Button { From 4af2fbc749bdacc9f08f0bdbfd6f36658923e339 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 5 May 2024 09:22:29 -0700 Subject: [PATCH 02/17] Remove unused variable --- Meshtastic/Helpers/BLEManager.swift | 1 - .../MeshtasticDataModelV 36.xcdatamodel/contents | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 0c997f05..8fcae1e4 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -40,7 +40,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate var timeoutTimer: Timer? var timeoutTimerCount = 0 var positionTimer: Timer? - var lastPosition: CLLocationCoordinate2D? static let emptyNodeNum: UInt32 = 4294967295 let mqttManager = MqttClientProxyManager.shared var wantRangeTestPackets = false diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 36.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 36.xcdatamodel/contents index f7144b48..b9eddf6d 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 36.xcdatamodel/contents +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 36.xcdatamodel/contents @@ -1,5 +1,5 @@ - + From 8bcf40f54302c72a17a7f3aedd9eb8d061941c47 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Mon, 6 May 2024 23:03:51 -0700 Subject: [PATCH 03/17] Deep Links Updated position precision --- Meshtastic.xcodeproj/project.pbxproj | 20 ++++ Meshtastic/DeepLinks/DeepLinkManager.swift | 100 ++++++++++++++++++ Meshtastic/Helpers/BLEManager.swift | 10 +- Meshtastic/Helpers/MeshPackets.swift | 2 +- Meshtastic/Info.plist | 2 +- Meshtastic/MeshtasticApp.swift | 11 +- Meshtastic/Persistence/UpdateCoreData.swift | 2 +- Meshtastic/Views/ContentView.swift | 26 +++-- Meshtastic/Views/Messages/Messages.swift | 6 ++ .../Map/MapContent/MeshMapContent.swift | 2 +- .../Map/MapContent/NodeMapContent.swift | 2 +- Meshtastic/Views/Nodes/NodeList.swift | 5 + 12 files changed, 173 insertions(+), 15 deletions(-) create mode 100644 Meshtastic/DeepLinks/DeepLinkManager.swift diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index a96e3707..3ec5015c 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -128,6 +128,7 @@ DDA9515A2BC6624100CEA535 /* TelemetryWeather.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA951592BC6624100CEA535 /* TelemetryWeather.swift */; }; DDA9515C2BC6631200CEA535 /* TelemetryEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA9515B2BC6631200CEA535 /* TelemetryEnums.swift */; }; DDA9515E2BC6F56F00CEA535 /* IndoorAirQuality.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA9515D2BC6F56F00CEA535 /* IndoorAirQuality.swift */; }; + DDAA632E2BE7F84E00CC22D3 /* DeepLinkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAA632D2BE7F84E00CC22D3 /* DeepLinkManager.swift */; }; DDAB580D2B0DAA9E00147258 /* Routes.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAB580C2B0DAA9E00147258 /* Routes.swift */; }; DDAB580F2B0DAFBC00147258 /* LocationEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAB580E2B0DAFBC00147258 /* LocationEntityExtension.swift */; }; DDAD49ED2AFB39DC00B4425D /* MeshMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAD49EC2AFB39DC00B4425D /* MeshMap.swift */; }; @@ -386,6 +387,7 @@ DDA951592BC6624100CEA535 /* TelemetryWeather.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelemetryWeather.swift; sourceTree = ""; }; DDA9515B2BC6631200CEA535 /* TelemetryEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelemetryEnums.swift; sourceTree = ""; }; DDA9515D2BC6F56F00CEA535 /* IndoorAirQuality.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IndoorAirQuality.swift; sourceTree = ""; }; + DDAA632D2BE7F84E00CC22D3 /* DeepLinkManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLinkManager.swift; sourceTree = ""; }; DDAB580B2B0D913500147258 /* MeshtasticDataModelV20.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV20.xcdatamodel; sourceTree = ""; }; DDAB580C2B0DAA9E00147258 /* Routes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Routes.swift; sourceTree = ""; }; DDAB580E2B0DAFBC00147258 /* LocationEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationEntityExtension.swift; sourceTree = ""; }; @@ -573,6 +575,13 @@ path = CoreData; sourceTree = ""; }; + DD17C4D42BE7EC2200D45AC7 /* SwiftData */ = { + isa = PBXGroup; + children = ( + ); + path = SwiftData; + sourceTree = ""; + }; DD47E3CA26F0E50300029299 /* Nodes */ = { isa = PBXGroup; children = ( @@ -757,6 +766,14 @@ path = Channels; sourceTree = ""; }; + DDAA632C2BE7F83400CC22D3 /* DeepLinks */ = { + isa = PBXGroup; + children = ( + DDAA632D2BE7F84E00CC22D3 /* DeepLinkManager.swift */, + ); + path = DeepLinks; + sourceTree = ""; + }; DDAD49EB2AFAE82500B4425D /* Map */ = { isa = PBXGroup; children = ( @@ -818,6 +835,7 @@ children = ( DD7709392AA1ABA1007A8BF0 /* Tips */, DD90860A26F645B700DC5189 /* Meshtastic.entitlements */, + DDAA632C2BE7F83400CC22D3 /* DeepLinks */, DD8ED9C6289CE4A100B3B0AB /* Enums */, DD86D40D2881BDB300BAEB7A /* Export */, DDDB443E29F79A9400EE2349 /* Extensions */, @@ -950,6 +968,7 @@ DDC4D5662754996200A4208E /* Persistence */ = { isa = PBXGroup; children = ( + DD17C4D42BE7EC2200D45AC7 /* SwiftData */, DDC4D567275499A500A4208E /* Persistence.swift */, DD964FC52975DBFD007C176F /* QueryCoreData.swift */, DD3CC6C128EB9D4900FA9159 /* UpdateCoreData.swift */, @@ -1234,6 +1253,7 @@ DDAF8C5326EB1DF10058C060 /* BLEManager.swift in Sources */, DD15E4F32B8BA56E00654F61 /* PaxCounterConfig.swift in Sources */, DDDB445229F8ACF900EE2349 /* Date.swift in Sources */, + DDAA632E2BE7F84E00CC22D3 /* DeepLinkManager.swift in Sources */, DDC4D568275499A500A4208E /* Persistence.swift in Sources */, DDD6EEAF29BC024700383354 /* Firmware.swift in Sources */, DD77093B2AA1ABB8007A8BF0 /* BluetoothTips.swift in Sources */, diff --git a/Meshtastic/DeepLinks/DeepLinkManager.swift b/Meshtastic/DeepLinks/DeepLinkManager.swift new file mode 100644 index 00000000..037f50a1 --- /dev/null +++ b/Meshtastic/DeepLinks/DeepLinkManager.swift @@ -0,0 +1,100 @@ +// +// DeepLinkManager.swift +// Meshtastic +// +// Copyright(c) Garth Vander Houwen 5/5/24. +// + +import Foundation + +protocol DeepLinkTabManager { + func handle(deepLink: String, selectedTab: inout Tab) -> Bool +} + +@available(iOS 17.0, *) +@Observable +class DeepLinkManager { + var selectedTab: Tab = .ble + var features: [DeepLinkTabManager] + + init() { + self.features = [ + DeepLinkManagerMessages(), + DeepLinkManagerBluetooth(), + DeepLinkManagerNodes(), + DeepLinkManagerMap(), + DeepLinkManagerSettings() + ] + } + + func handleDeepLink(deepLink: String) { + for handler in features { + if handler.handle(deepLink: deepLink, selectedTab: &selectedTab) { + return + } + } + } +} + +class DeepLinkManagerBluetooth: DeepLinkTabManager { + func handle(deepLink: String, selectedTab: inout Tab) -> Bool { + if deepLink.contains("bluetooth") { + selectedTab = .ble + return true + } + return false + } +} + +class DeepLinkManagerMessages: DeepLinkTabManager { + + var channel: String = "" + var messageId: String = "" + + func handle(deepLink: String, selectedTab: inout Tab) -> Bool { + if deepLink.contains("messages") { + selectedTab = .messages + extractData(from: deepLink) + return true + } + + return false + } + private func extractData(from deepLink: String) { + let temp = deepLink.replacingOccurrences(of: "meshtastic://messages?", with: "") + let params = temp.components(separatedBy: "&") + guard params.count == 2 else { return } + channel = params[0].replacingOccurrences(of: "channel=", with: "") + messageId = params[1].replacingOccurrences(of: "messageId=", with: "") + } +} + +class DeepLinkManagerMap: DeepLinkTabManager { + func handle(deepLink: String, selectedTab: inout Tab) -> Bool { + if deepLink.contains("map") { + selectedTab = .map + return true + } + return false + } +} + +class DeepLinkManagerNodes: DeepLinkTabManager { + func handle(deepLink: String, selectedTab: inout Tab) -> Bool { + if deepLink.contains("nodes") { + selectedTab = .nodes + return true + } + return false + } +} + +class DeepLinkManagerSettings: DeepLinkTabManager { + func handle(deepLink: String, selectedTab: inout Tab) -> Bool { + if deepLink.contains("settings") { + selectedTab = .settings + return true + } + return false + } +} diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 8fcae1e4..3c5b4d51 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -5,6 +5,14 @@ import SwiftUI import MapKit import CocoaMQTT +public protocol DeviceConnection: AnyObject { + var isSubscribed: Bool { get } + var invalidVersion: Bool { get } + //var handledDeepLinks: [DeepLink.Type] { get } + //func canHandle(deepLink: DeepLink) -> Bool + //func handle(deepLink: DeepLink) +} + // --------------------------------------------------------------------------------------- // Meshtastic BLE Device Manager // --------------------------------------------------------------------------------------- @@ -20,7 +28,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate var context: NSManagedObjectContext? static let shared = BLEManager() - //var userSettings: UserSettings? private var centralManager: CBCentralManager! @Published var peripherals: [Peripheral] = [] @Published var connectedPeripheral: Peripheral! @@ -2633,6 +2640,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate newMessage.fromUser = fromUser newMessage.toUser = toUser + do { connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) try context!.save() diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index f61d1994..cad532fa 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -905,7 +905,7 @@ func textMessageAppPacket(packet: MeshPacket, wantRangeTestPackets: Bool, connec subtitle: "AKA \(newMessage.fromUser?.shortName ?? "?")", content: messageText!, target: "message", - path: "meshtastic://messages/channel/\(newMessage.messageId)") + path: "meshtastic://messages?channel=\(newMessage.channel)&messageId=\(newMessage.messageId)") ] manager.schedule() print("💬 iOS Notification Scheduled for text message from \(newMessage.fromUser?.longName ?? "unknown".localized)") diff --git a/Meshtastic/Info.plist b/Meshtastic/Info.plist index a2fde04b..2e319241 100644 --- a/Meshtastic/Info.plist +++ b/Meshtastic/Info.plist @@ -35,7 +35,7 @@ CFBundleTypeRole - Viewer + Editor CFBundleURLIconFile alpha CFBundleURLName diff --git a/Meshtastic/MeshtasticApp.swift b/Meshtastic/MeshtasticApp.swift index 0b8275dd..cd21ec67 100644 --- a/Meshtastic/MeshtasticApp.swift +++ b/Meshtastic/MeshtasticApp.swift @@ -6,9 +6,12 @@ import CoreData import TipKit #endif +@available(iOS 17.0, *) @main struct MeshtasticAppleApp: App { + let deepLinkManager = DeepLinkManager() + @UIApplicationDelegateAdaptor(MeshtasticAppDelegate.self) var appDelegate let persistenceController = PersistenceController.shared @ObservedObject private var bleManager: BLEManager = BLEManager.shared @@ -23,7 +26,7 @@ struct MeshtasticAppleApp: App { var body: some Scene { WindowGroup { - ContentView() + ContentView(deepLinkManager: deepLinkManager) .environment(\.managedObjectContext, persistenceController.container.viewContext) .environmentObject(bleManager) .sheet(isPresented: $saveChannels) { @@ -53,6 +56,10 @@ struct MeshtasticAppleApp: App { } } .onOpenURL(perform: { (url) in + + if url.absoluteString.lowercased().contains("meshtastic://") { + deepLinkManager.handleDeepLink(deepLink: url.absoluteString.lowercased()) + } print("Some sort of URL was received \(url)") self.incomingUrl = url @@ -62,6 +69,8 @@ struct MeshtasticAppleApp: App { } self.saveChannels = true print("User wants to open a Channel Settings URL: \(self.incomingUrl?.absoluteString ?? "No QR Code Link")") + } else if url.absoluteString.lowercased().contains("meshtastic://") { + deepLinkManager.handleDeepLink(deepLink: url.absoluteString.lowercased()) } else { saveChannels = false print("User wants to import a MBTILES offline map file: \(self.incomingUrl?.absoluteString ?? "No Tiles link")") diff --git a/Meshtastic/Persistence/UpdateCoreData.swift b/Meshtastic/Persistence/UpdateCoreData.swift index e9114b7d..87b11cb3 100644 --- a/Meshtastic/Persistence/UpdateCoreData.swift +++ b/Meshtastic/Persistence/UpdateCoreData.swift @@ -198,7 +198,7 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) subtitle: "\(newUser.longName ?? "unknown".localized)", content: "New Node has been discovered", target: "nodeInfo", - path: "meshtastic://nodeInfo" + path: "meshtastic://nodes?num=\(newUser.num)" ) ] manager.schedule() diff --git a/Meshtastic/Views/ContentView.swift b/Meshtastic/Views/ContentView.swift index 75e6fb8e..ffd8feee 100644 --- a/Meshtastic/Views/ContentView.swift +++ b/Meshtastic/Views/ContentView.swift @@ -4,11 +4,14 @@ import SwiftUI +@available(iOS 17.0, *) struct ContentView: View { + @State var deepLinkManager: DeepLinkManager + @StateObject var appState = AppState.shared var body: some View { TabView(selection: $appState.tabSelection) { - Messages() + Messages(deepLinkManager: deepLinkManager.features[0] as? DeepLinkManagerMessages) .tabItem { Label("messages", systemImage: "message") } @@ -19,7 +22,7 @@ struct ContentView: View { Label("bluetooth", systemImage: "antenna.radiowaves.left.and.right") } .tag(Tab.ble) - NodeList() + NodeList(deepLinkManager: deepLinkManager.features[2] as? DeepLinkManagerNodes) .tabItem { Label("nodes", systemImage: "flipphone") } @@ -54,14 +57,21 @@ struct ContentView: View { } } } +//#Preview { +// if #available(iOS 17.0, *) { +// // ContentView(deepLinkManager: .init()) +// } else { +// // Fallback on earlier versions +// } +//} -struct ContentView_Previews: PreviewProvider { - static var previews: some View { - ContentView() - } -} +//struct ContentView_Previews: PreviewProvider { +// static var previews: some View { +// ContentView() +// } +//} -enum Tab { +enum Tab: Hashable { case contacts case messages case map diff --git a/Meshtastic/Views/Messages/Messages.swift b/Meshtastic/Views/Messages/Messages.swift index 879a361c..871923ac 100644 --- a/Meshtastic/Views/Messages/Messages.swift +++ b/Meshtastic/Views/Messages/Messages.swift @@ -12,6 +12,8 @@ import TipKit #endif struct Messages: View { + + @State var deepLinkManager: DeepLinkManagerMessages @StateObject var appState = AppState.shared @Environment(\.managedObjectContext) var context @@ -27,6 +29,10 @@ struct Messages: View { case groupMessages case directMessages } + + init (deepLinkManager: DeepLinkManagerMessages? = nil) { + self.deepLinkManager = deepLinkManager ?? .init() + } var body: some View { diff --git a/Meshtastic/Views/Nodes/Helpers/Map/MapContent/MeshMapContent.swift b/Meshtastic/Views/Nodes/Helpers/Map/MapContent/MeshMapContent.swift index 52d3a97c..cbb50828 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/MapContent/MeshMapContent.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/MapContent/MeshMapContent.swift @@ -142,7 +142,7 @@ struct MeshMapContent: MapContent { } } /// Reduced Precision Map Circles - if 2...24 ~= position.precisionBits { + if 10...19 ~= position.precisionBits { let pp = PositionPrecision(rawValue: Int(position.precisionBits)) let radius : CLLocationDistance = pp?.precisionMeters ?? 0 if radius > 0.0 { diff --git a/Meshtastic/Views/Nodes/Helpers/Map/MapContent/NodeMapContent.swift b/Meshtastic/Views/Nodes/Helpers/Map/MapContent/NodeMapContent.swift index fd869c30..64cc43a8 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/MapContent/NodeMapContent.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/MapContent/NodeMapContent.swift @@ -49,7 +49,7 @@ struct NodeMapContent: MapContent { let pf = PositionFlags(rawValue: Int(position.nodePosition?.metadata?.positionFlags ?? 771)) let headingDegrees = Angle.degrees(Double(position.heading)) /// Reduced Precision Map Circle - if position.latest && 2...24 ~= position.precisionBits { + if position.latest && 10...19 ~= position.precisionBits { let pp = PositionPrecision(rawValue: Int(position.precisionBits)) let radius : CLLocationDistance = pp?.precisionMeters ?? 0 if radius > 0.0 { diff --git a/Meshtastic/Views/Nodes/NodeList.swift b/Meshtastic/Views/Nodes/NodeList.swift index 588fec7a..1f11b567 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -9,6 +9,7 @@ import CoreLocation struct NodeList: View { + @State var deepLinkManager: DeepLinkManagerNodes @State private var columnVisibility = NavigationSplitViewVisibility.all @State private var selectedNode: NodeInfoEntity? @State private var isPresentingTraceRouteSentAlert = false @@ -41,6 +42,10 @@ struct NodeList: View { var nodes: FetchedResults + init (deepLinkManager: DeepLinkManagerNodes? = nil) { + self.deepLinkManager = deepLinkManager ?? .init() + } + var body: some View { NavigationSplitView(columnVisibility: $columnVisibility) { From 61c4b23d497cc4bb24850a359189e6a99fa6747d Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Fri, 10 May 2024 12:14:08 -0700 Subject: [PATCH 04/17] Remove unnessary old hop count logic --- Meshtastic/Persistence/UpdateCoreData.swift | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Meshtastic/Persistence/UpdateCoreData.swift b/Meshtastic/Persistence/UpdateCoreData.swift index 87b11cb3..69893911 100644 --- a/Meshtastic/Persistence/UpdateCoreData.swift +++ b/Meshtastic/Persistence/UpdateCoreData.swift @@ -161,9 +161,8 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) if let nodeInfoMessage = try? NodeInfo(serializedData: packet.decoded.payload) { newNode.hopsAway = Int32(nodeInfoMessage.hopsAway) newNode.favorite = nodeInfoMessage.isFavorite - } else if packet.hopStart != 0 && packet.hopLimit <= packet.hopStart { - newNode.hopsAway = Int32(packet.hopStart - packet.hopLimit) } + if let newUserMessage = try? User(serializedData: packet.decoded.payload) { if newUserMessage.id.isEmpty { @@ -217,7 +216,15 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) } if newNode.user == nil { - print("Nil User on nodeinfo") + let newUser = UserEntity(context: context) + newUser.num = Int64(packet.from) + let userId = String(format:"%2X", packet.from) + newUser.userId = "!\(userId)" + let last4 = String(userId.suffix(4)) + newUser.longName = "Meshtastic \(last4)" + newUser.shortName = last4 + newUser.hwModel = "UNSET" + newNode.user = newUser } let myInfoEntity = MyInfoEntity(context: context) From 8e07453afd22dea5f5ae709650aa1bf5bfe14c04 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sat, 11 May 2024 10:50:58 -0700 Subject: [PATCH 05/17] Hid fixed postion checkbox if managing a node over the admin channel --- Meshtastic/Views/Settings/Config/PositionConfig.swift | 2 +- protobufs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Meshtastic/Views/Settings/Config/PositionConfig.swift b/Meshtastic/Views/Settings/Config/PositionConfig.swift index 1ba854fa..575de199 100644 --- a/Meshtastic/Views/Settings/Config/PositionConfig.swift +++ b/Meshtastic/Views/Settings/Config/PositionConfig.swift @@ -163,7 +163,7 @@ struct PositionConfig: View { .font(.callout) } } - if gpsMode != 1 { + if gpsMode != 1 && node?.num ?? 0 == bleManager.connectedPeripheral?.num ?? -1 { VStack(alignment: .leading) { Toggle(isOn: $fixedPosition) { Label("Fixed Position", systemImage: "location.square.fill") diff --git a/protobufs b/protobufs index dd7d64cc..f92900c5 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit dd7d64cc038a6365c119ec7508762cc45f405948 +Subproject commit f92900c5f884b04388fb7abf61d4df66783015e4 From a4c1fc4fe6a9afa22ab35994c5a95656e519e0fe Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 14 May 2024 22:39:07 -0700 Subject: [PATCH 06/17] Remove up and downlink from QR codes Open node details view from new node notification Fix speed Simple deep linking structure --- Meshtastic.xcodeproj/project.pbxproj | 12 --- Meshtastic/DeepLinks/DeepLinkManager.swift | 100 ------------------ Meshtastic/Helpers/BLEManager.swift | 67 ++++++------ Meshtastic/Helpers/MeshPackets.swift | 7 +- Meshtastic/MeshtasticApp.swift | 22 ++-- Meshtastic/MeshtasticAppDelegate.swift | 7 +- Meshtastic/Persistence/UpdateCoreData.swift | 6 +- Meshtastic/Views/ContentView.swift | 5 +- Meshtastic/Views/Messages/Messages.swift | 6 -- Meshtastic/Views/Nodes/MeshMap.swift | 60 ++++++----- Meshtastic/Views/Nodes/NodeList.swift | 43 ++++++-- Meshtastic/Views/Settings/ShareChannels.swift | 2 - protobufs | 2 +- 13 files changed, 123 insertions(+), 216 deletions(-) delete mode 100644 Meshtastic/DeepLinks/DeepLinkManager.swift diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 3ec5015c..c1a9f681 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -128,7 +128,6 @@ DDA9515A2BC6624100CEA535 /* TelemetryWeather.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA951592BC6624100CEA535 /* TelemetryWeather.swift */; }; DDA9515C2BC6631200CEA535 /* TelemetryEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA9515B2BC6631200CEA535 /* TelemetryEnums.swift */; }; DDA9515E2BC6F56F00CEA535 /* IndoorAirQuality.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA9515D2BC6F56F00CEA535 /* IndoorAirQuality.swift */; }; - DDAA632E2BE7F84E00CC22D3 /* DeepLinkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAA632D2BE7F84E00CC22D3 /* DeepLinkManager.swift */; }; DDAB580D2B0DAA9E00147258 /* Routes.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAB580C2B0DAA9E00147258 /* Routes.swift */; }; DDAB580F2B0DAFBC00147258 /* LocationEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAB580E2B0DAFBC00147258 /* LocationEntityExtension.swift */; }; DDAD49ED2AFB39DC00B4425D /* MeshMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAD49EC2AFB39DC00B4425D /* MeshMap.swift */; }; @@ -387,7 +386,6 @@ DDA951592BC6624100CEA535 /* TelemetryWeather.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelemetryWeather.swift; sourceTree = ""; }; DDA9515B2BC6631200CEA535 /* TelemetryEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelemetryEnums.swift; sourceTree = ""; }; DDA9515D2BC6F56F00CEA535 /* IndoorAirQuality.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IndoorAirQuality.swift; sourceTree = ""; }; - DDAA632D2BE7F84E00CC22D3 /* DeepLinkManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLinkManager.swift; sourceTree = ""; }; DDAB580B2B0D913500147258 /* MeshtasticDataModelV20.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV20.xcdatamodel; sourceTree = ""; }; DDAB580C2B0DAA9E00147258 /* Routes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Routes.swift; sourceTree = ""; }; DDAB580E2B0DAFBC00147258 /* LocationEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationEntityExtension.swift; sourceTree = ""; }; @@ -766,14 +764,6 @@ path = Channels; sourceTree = ""; }; - DDAA632C2BE7F83400CC22D3 /* DeepLinks */ = { - isa = PBXGroup; - children = ( - DDAA632D2BE7F84E00CC22D3 /* DeepLinkManager.swift */, - ); - path = DeepLinks; - sourceTree = ""; - }; DDAD49EB2AFAE82500B4425D /* Map */ = { isa = PBXGroup; children = ( @@ -835,7 +825,6 @@ children = ( DD7709392AA1ABA1007A8BF0 /* Tips */, DD90860A26F645B700DC5189 /* Meshtastic.entitlements */, - DDAA632C2BE7F83400CC22D3 /* DeepLinks */, DD8ED9C6289CE4A100B3B0AB /* Enums */, DD86D40D2881BDB300BAEB7A /* Export */, DDDB443E29F79A9400EE2349 /* Extensions */, @@ -1253,7 +1242,6 @@ DDAF8C5326EB1DF10058C060 /* BLEManager.swift in Sources */, DD15E4F32B8BA56E00654F61 /* PaxCounterConfig.swift in Sources */, DDDB445229F8ACF900EE2349 /* Date.swift in Sources */, - DDAA632E2BE7F84E00CC22D3 /* DeepLinkManager.swift in Sources */, DDC4D568275499A500A4208E /* Persistence.swift in Sources */, DDD6EEAF29BC024700383354 /* Firmware.swift in Sources */, DD77093B2AA1ABB8007A8BF0 /* BluetoothTips.swift in Sources */, diff --git a/Meshtastic/DeepLinks/DeepLinkManager.swift b/Meshtastic/DeepLinks/DeepLinkManager.swift deleted file mode 100644 index 037f50a1..00000000 --- a/Meshtastic/DeepLinks/DeepLinkManager.swift +++ /dev/null @@ -1,100 +0,0 @@ -// -// DeepLinkManager.swift -// Meshtastic -// -// Copyright(c) Garth Vander Houwen 5/5/24. -// - -import Foundation - -protocol DeepLinkTabManager { - func handle(deepLink: String, selectedTab: inout Tab) -> Bool -} - -@available(iOS 17.0, *) -@Observable -class DeepLinkManager { - var selectedTab: Tab = .ble - var features: [DeepLinkTabManager] - - init() { - self.features = [ - DeepLinkManagerMessages(), - DeepLinkManagerBluetooth(), - DeepLinkManagerNodes(), - DeepLinkManagerMap(), - DeepLinkManagerSettings() - ] - } - - func handleDeepLink(deepLink: String) { - for handler in features { - if handler.handle(deepLink: deepLink, selectedTab: &selectedTab) { - return - } - } - } -} - -class DeepLinkManagerBluetooth: DeepLinkTabManager { - func handle(deepLink: String, selectedTab: inout Tab) -> Bool { - if deepLink.contains("bluetooth") { - selectedTab = .ble - return true - } - return false - } -} - -class DeepLinkManagerMessages: DeepLinkTabManager { - - var channel: String = "" - var messageId: String = "" - - func handle(deepLink: String, selectedTab: inout Tab) -> Bool { - if deepLink.contains("messages") { - selectedTab = .messages - extractData(from: deepLink) - return true - } - - return false - } - private func extractData(from deepLink: String) { - let temp = deepLink.replacingOccurrences(of: "meshtastic://messages?", with: "") - let params = temp.components(separatedBy: "&") - guard params.count == 2 else { return } - channel = params[0].replacingOccurrences(of: "channel=", with: "") - messageId = params[1].replacingOccurrences(of: "messageId=", with: "") - } -} - -class DeepLinkManagerMap: DeepLinkTabManager { - func handle(deepLink: String, selectedTab: inout Tab) -> Bool { - if deepLink.contains("map") { - selectedTab = .map - return true - } - return false - } -} - -class DeepLinkManagerNodes: DeepLinkTabManager { - func handle(deepLink: String, selectedTab: inout Tab) -> Bool { - if deepLink.contains("nodes") { - selectedTab = .nodes - return true - } - return false - } -} - -class DeepLinkManagerSettings: DeepLinkTabManager { - func handle(deepLink: String, selectedTab: inout Tab) -> Bool { - if deepLink.contains("settings") { - selectedTab = .settings - return true - } - return false - } -} diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 3c5b4d51..cc5d7c7f 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -1008,52 +1008,45 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate public func getPositionFromPhoneGPS(destNum: Int64) -> Position? { var positionPacket = Position() - do { - if #available(iOS 17.0, macOS 14.0, *) { + if #available(iOS 17.0, macOS 14.0, *) { - if let lastLocation = LocationsHandler.shared.locationsArray.last { - - positionPacket.latitudeI = Int32(lastLocation.coordinate.latitude * 1e7) - positionPacket.longitudeI = Int32(lastLocation.coordinate.longitude * 1e7) - let timestamp = lastLocation.timestamp - positionPacket.time = UInt32(timestamp.timeIntervalSince1970) - positionPacket.timestamp = UInt32(timestamp.timeIntervalSince1970) - positionPacket.altitude = Int32(lastLocation.altitude) - positionPacket.satsInView = UInt32(LocationsHandler.satsInView) - - let currentSpeed = lastLocation.speed - if currentSpeed > 0 && (!currentSpeed.isNaN || !currentSpeed.isInfinite) { - positionPacket.groundSpeed = UInt32(currentSpeed * 3.6) - } - let currentHeading = lastLocation.course - if currentHeading > 0 && (!currentHeading.isNaN || !currentHeading.isInfinite) { - positionPacket.groundTrack = UInt32(currentHeading) - } - } + if let lastLocation = LocationsHandler.shared.locationsArray.last { - } else { - if destNum <= 0 || LocationHelper.currentLocation.distance(from: LocationHelper.DefaultLocation) == 0.0 { - return nil - } - - positionPacket.latitudeI = Int32(LocationHelper.currentLocation.latitude * 1e7) - positionPacket.longitudeI = Int32(LocationHelper.currentLocation.longitude * 1e7) - let timestamp = LocationHelper.shared.locationManager.location?.timestamp ?? Date() + positionPacket.latitudeI = Int32(lastLocation.coordinate.latitude * 1e7) + positionPacket.longitudeI = Int32(lastLocation.coordinate.longitude * 1e7) + let timestamp = lastLocation.timestamp positionPacket.time = UInt32(timestamp.timeIntervalSince1970) positionPacket.timestamp = UInt32(timestamp.timeIntervalSince1970) - positionPacket.altitude = Int32(LocationHelper.shared.locationManager.location?.altitude ?? 0) - positionPacket.satsInView = UInt32(LocationHelper.satsInView) - let currentSpeed = LocationHelper.shared.locationManager.location?.speed ?? 0 + positionPacket.altitude = Int32(lastLocation.altitude) + positionPacket.satsInView = UInt32(LocationsHandler.satsInView) + + let currentSpeed = lastLocation.speed if currentSpeed > 0 && (!currentSpeed.isNaN || !currentSpeed.isInfinite) { - positionPacket.groundSpeed = UInt32(currentSpeed * 3.6) + positionPacket.groundSpeed = UInt32(currentSpeed) } - let currentHeading = LocationHelper.shared.locationManager.location?.course ?? 0 - if currentHeading > 0 && (!currentHeading.isNaN || !currentHeading.isInfinite) { + let currentHeading = lastLocation.course + if (currentHeading > 0 && currentHeading <= 360) && (!currentHeading.isNaN || !currentHeading.isInfinite) { positionPacket.groundTrack = UInt32(currentHeading) } } - } catch { - return nil + + } else { + + positionPacket.latitudeI = Int32(LocationHelper.currentLocation.latitude * 1e7) + positionPacket.longitudeI = Int32(LocationHelper.currentLocation.longitude * 1e7) + let timestamp = LocationHelper.shared.locationManager.location?.timestamp ?? Date() + positionPacket.time = UInt32(timestamp.timeIntervalSince1970) + positionPacket.timestamp = UInt32(timestamp.timeIntervalSince1970) + positionPacket.altitude = Int32(LocationHelper.shared.locationManager.location?.altitude ?? 0) + positionPacket.satsInView = UInt32(LocationHelper.satsInView) + let currentSpeed = LocationHelper.shared.locationManager.location?.speed ?? 0 + if currentSpeed > 0 && (!currentSpeed.isNaN || !currentSpeed.isInfinite) { + positionPacket.groundSpeed = UInt32(currentSpeed) + } + let currentHeading = LocationHelper.shared.locationManager.location?.course ?? 0 + if (currentHeading > 0 && currentHeading <= 360) && (!currentHeading.isNaN || !currentHeading.isInfinite) { + positionPacket.groundTrack = UInt32(currentHeading) + } } return positionPacket } diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index cad532fa..d436aef7 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -315,7 +315,7 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje position.longitudeI = nodeInfo.position.longitudeI position.altitude = nodeInfo.position.altitude position.satsInView = Int32(nodeInfo.position.satsInView) - position.speed = Int32(nodeInfo.position.groundSpeed * UInt32(3.6)) + position.speed = Int32(nodeInfo.position.groundSpeed) position.heading = Int32(nodeInfo.position.groundTrack) position.time = Date(timeIntervalSince1970: TimeInterval(Int64(nodeInfo.position.time))) var newPostions = [PositionEntity]() @@ -738,7 +738,7 @@ func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManage subtitle: "AKA \(telemetry.nodeTelemetry?.user?.shortName ?? "UNK")", content: "Time to charge your radio, there is \(telemetry.batteryLevel)% battery remaining.", target: "nodes", - path: "meshtastic://nodes/\(telemetry.nodeTelemetry?.num ?? 0)/devicetelemetrylog" + path: "meshtastic://nodes?nodenum=\(telemetry.nodeTelemetry?.num ?? 0)" ) ] manager.schedule() @@ -972,9 +972,10 @@ func waypointPacket (packet: MeshPacket, context: NSManagedObjectContext) { subtitle: "\(icon) \(waypoint.name ?? "Dropped Pin")", content: "\(waypoint.longDescription ?? "\(latitude), \(longitude)")", target: "map", - path: "meshtastic://open-waypoint?id=\(waypoint.id)" + path: "meshtastic://map?waypontid=\(waypoint.id)" ) ] + print("meshtastic://map?waypontid=\(waypoint.id)") manager.schedule() } catch { context.rollback() diff --git a/Meshtastic/MeshtasticApp.swift b/Meshtastic/MeshtasticApp.swift index cd21ec67..2a8e37d1 100644 --- a/Meshtastic/MeshtasticApp.swift +++ b/Meshtastic/MeshtasticApp.swift @@ -9,9 +9,7 @@ import TipKit @available(iOS 17.0, *) @main struct MeshtasticAppleApp: App { - - let deepLinkManager = DeepLinkManager() - + @UIApplicationDelegateAdaptor(MeshtasticAppDelegate.self) var appDelegate let persistenceController = PersistenceController.shared @ObservedObject private var bleManager: BLEManager = BLEManager.shared @@ -26,7 +24,7 @@ struct MeshtasticAppleApp: App { var body: some Scene { WindowGroup { - ContentView(deepLinkManager: deepLinkManager) + ContentView() .environment(\.managedObjectContext, persistenceController.container.viewContext) .environmentObject(bleManager) .sheet(isPresented: $saveChannels) { @@ -56,10 +54,6 @@ struct MeshtasticAppleApp: App { } } .onOpenURL(perform: { (url) in - - if url.absoluteString.lowercased().contains("meshtastic://") { - deepLinkManager.handleDeepLink(deepLink: url.absoluteString.lowercased()) - } print("Some sort of URL was received \(url)") self.incomingUrl = url @@ -70,7 +64,15 @@ struct MeshtasticAppleApp: App { self.saveChannels = true print("User wants to open a Channel Settings URL: \(self.incomingUrl?.absoluteString ?? "No QR Code Link")") } else if url.absoluteString.lowercased().contains("meshtastic://") { - deepLinkManager.handleDeepLink(deepLink: url.absoluteString.lowercased()) + appState.navigationPath = url.absoluteString + let path = appState.navigationPath ?? "" + if path.starts(with: "meshtastic://map") { + AppState.shared.tabSelection = Tab.map + } else if path.starts(with: "meshtastic://nodes") { + AppState.shared.tabSelection = Tab.nodes + } + + } else { saveChannels = false print("User wants to import a MBTILES offline map file: \(self.incomingUrl?.absoluteString ?? "No Tiles link")") @@ -166,6 +168,6 @@ class AppState: ObservableObject { @Published var unreadDirectMessages: Int = 0 @Published var unreadChannelMessages: Int = 0 @Published var firmwareVersion: String = "0.0.0" - @Published var connectedNode: NodeInfoEntity? + //@Published var connectedNode: NodeInfoEntity? @Published var navigationPath: String? } diff --git a/Meshtastic/MeshtasticAppDelegate.swift b/Meshtastic/MeshtasticAppDelegate.swift index 94a6df6c..ee4177cf 100644 --- a/Meshtastic/MeshtasticAppDelegate.swift +++ b/Meshtastic/MeshtasticAppDelegate.swift @@ -33,13 +33,14 @@ class MeshtasticAppDelegate: NSObject, UIApplicationDelegate, UNUserNotification func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { let userInfo = response.notification.request.content.userInfo let targetValue = userInfo["target"] as? String - AppState.shared.navigationPath = userInfo["path"] as? String - print("\(AppState.shared.navigationPath ?? "EMPTY")") + let deepLink = userInfo["path"] as? String + AppState.shared.navigationPath = deepLink if targetValue == "map" { AppState.shared.tabSelection = Tab.map + } else if targetValue == "message" { AppState.shared.tabSelection = Tab.messages - } else if targetValue == "node" { + } else if targetValue == "nodes" { AppState.shared.tabSelection = Tab.nodes } completionHandler() diff --git a/Meshtastic/Persistence/UpdateCoreData.swift b/Meshtastic/Persistence/UpdateCoreData.swift index 69893911..093e8872 100644 --- a/Meshtastic/Persistence/UpdateCoreData.swift +++ b/Meshtastic/Persistence/UpdateCoreData.swift @@ -196,8 +196,8 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) title: "New Node", subtitle: "\(newUser.longName ?? "unknown".localized)", content: "New Node has been discovered", - target: "nodeInfo", - path: "meshtastic://nodes?num=\(newUser.num)" + target: "nodes", + path: "meshtastic://nodes?nodenum=\(newUser.num)" ) ] manager.schedule() @@ -345,7 +345,7 @@ func upsertPositionPacket (packet: MeshPacket, context: NSManagedObjectContext) position.longitudeI = positionMessage.longitudeI position.altitude = positionMessage.altitude position.satsInView = Int32(positionMessage.satsInView) - position.speed = Int32(positionMessage.groundSpeed * UInt32(3.6)) + position.speed = Int32(positionMessage.groundSpeed) position.heading = Int32(positionMessage.groundTrack) position.precisionBits = Int32(positionMessage.precisionBits) if positionMessage.timestamp != 0 { diff --git a/Meshtastic/Views/ContentView.swift b/Meshtastic/Views/ContentView.swift index ffd8feee..da24c465 100644 --- a/Meshtastic/Views/ContentView.swift +++ b/Meshtastic/Views/ContentView.swift @@ -6,12 +6,11 @@ import SwiftUI @available(iOS 17.0, *) struct ContentView: View { - @State var deepLinkManager: DeepLinkManager @StateObject var appState = AppState.shared var body: some View { TabView(selection: $appState.tabSelection) { - Messages(deepLinkManager: deepLinkManager.features[0] as? DeepLinkManagerMessages) + Messages() .tabItem { Label("messages", systemImage: "message") } @@ -22,7 +21,7 @@ struct ContentView: View { Label("bluetooth", systemImage: "antenna.radiowaves.left.and.right") } .tag(Tab.ble) - NodeList(deepLinkManager: deepLinkManager.features[2] as? DeepLinkManagerNodes) + NodeList() .tabItem { Label("nodes", systemImage: "flipphone") } diff --git a/Meshtastic/Views/Messages/Messages.swift b/Meshtastic/Views/Messages/Messages.swift index 871923ac..056e1630 100644 --- a/Meshtastic/Views/Messages/Messages.swift +++ b/Meshtastic/Views/Messages/Messages.swift @@ -13,8 +13,6 @@ import TipKit struct Messages: View { - @State var deepLinkManager: DeepLinkManagerMessages - @StateObject var appState = AppState.shared @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager @@ -29,10 +27,6 @@ struct Messages: View { case groupMessages case directMessages } - - init (deepLinkManager: DeepLinkManagerMessages? = nil) { - self.deepLinkManager = deepLinkManager ?? .init() - } var body: some View { diff --git a/Meshtastic/Views/Nodes/MeshMap.swift b/Meshtastic/Views/Nodes/MeshMap.swift index 5a848908..9eb3e718 100644 --- a/Meshtastic/Views/Nodes/MeshMap.swift +++ b/Meshtastic/Views/Nodes/MeshMap.swift @@ -35,9 +35,11 @@ struct MeshMap: View { @State var selectedPosition: PositionEntity? @State var editingWaypoint: WaypointEntity? @State var selectedWaypoint: WaypointEntity? + @State var selectedWaypointId: String? @State var newWaypointCoord: CLLocationCoordinate2D? @State var isMeshMap = true + var body: some View { NavigationStack { @@ -106,33 +108,33 @@ struct MeshMap: View { .sheet(isPresented: $isEditingSettings) { MapSettingsForm(traffic: $showTraffic, pointsOfInterest: $showPointsOfInterest, mapLayer: $selectedMapLayer, meshMap: $isMeshMap) } - .onChange(of: (appState.navigationPath)) { newPath in - - if ((newPath?.hasPrefix("meshtastic://open-waypoint")) != nil) { - guard let url = URL(string: appState.navigationPath ?? "NONE") else { - print("Invalid URL") - return - } - guard let components = URLComponents(url: url, resolvingAgainstBaseURL: true) else { - print("Invalid URL Components") - return - } - guard let action = components.host, action == "open-waypoint" else { - print("Unknown waypoint URL action") - return - } - guard let waypointId = components.queryItems?.first(where: { $0.name == "id" })?.value else { - print("Waypoint id not found") - return - } -// guard let waypoint = waypoints.first(where: { $0.id == Int64(waypointId) }) else { -// print("Waypoint not found") +// .onChange(of: (appState.navigationPath)) { newPath in +// +// if ((newPath?.hasPrefix("meshtastic://open-waypoint")) != nil) { +// guard let url = URL(string: appState.navigationPath ?? "NONE") else { +// print("Invalid URL") +// return +// } +// guard let components = URLComponents(url: url, resolvingAgainstBaseURL: true) else { +// print("Invalid URL Components") // return // } - //showWaypoints = true - //position = .camera(MapCamera(centerCoordinate: waypoint.coordinate, distance: 1000, heading: 0, pitch: 60)) - } - } +// guard let action = components.host, action == "open-waypoint" else { +// print("Unknown waypoint URL action") +// return +// } +// guard let waypointId = components.queryItems?.first(where: { $0.name == "id" })?.value else { +// print("Waypoint id not found") +// return +// } +//// guard let waypoint = waypoints.first(where: { $0.id == Int64(waypointId) }) else { +//// print("Waypoint not found") +//// return +//// } +// //showWaypoints = true +// //position = .camera(MapCamera(centerCoordinate: waypoint.coordinate, distance: 1000, heading: 0, pitch: 60)) +// } +// } .onChange(of: (selectedMapLayer)) { newMapLayer in switch selectedMapLayer { case .standard: @@ -174,6 +176,14 @@ struct MeshMap: View { if self.bleManager.context == nil { self.bleManager.context = context } + + + // if deepLinkManager.waypointId.length > 0 { + // let wayPointEntity = getWaypoint(id: Int64(deepLinkManager.waypointId) ?? -1, context: context) + //if wayPointEntity.id > 0 { + // position = .camera(MapCamera(centerCoordinate: wayPointEntity.coordinate, distance: 1000, heading: 0, pitch: 60)) + //} + switch selectedMapLayer { case .standard: mapStyle = MapStyle.standard(elevation: .realistic, pointsOfInterest: showPointsOfInterest ? .all : .excludingAll, showsTraffic: showTraffic) diff --git a/Meshtastic/Views/Nodes/NodeList.swift b/Meshtastic/Views/Nodes/NodeList.swift index 1f11b567..90c3d37b 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -9,7 +9,7 @@ import CoreLocation struct NodeList: View { - @State var deepLinkManager: DeepLinkManagerNodes + @StateObject var appState = AppState.shared @State private var columnVisibility = NavigationSplitViewVisibility.all @State private var selectedNode: NodeInfoEntity? @State private var isPresentingTraceRouteSentAlert = false @@ -42,13 +42,17 @@ struct NodeList: View { var nodes: FetchedResults - init (deepLinkManager: DeepLinkManagerNodes? = nil) { - self.deepLinkManager = deepLinkManager ?? .init() - } - var body: some View { NavigationSplitView(columnVisibility: $columnVisibility) { +// HStack { +// Button("Open Node") { +// UIApplication +// .shared +// .open(URL(string: "meshtastic://nodes?nodeNum=530606484")!) +// } +// } + let connectedNodeNum = Int(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral?.num ?? 0 : 0) let connectedNode = nodes.first(where: { $0.num == connectedNodeNum }) List(nodes, id: \.self, selection: $selectedNode) { node in @@ -208,6 +212,7 @@ struct NodeList: View { .disableAutocorrection(true) .scrollDismissesKeyboard(.immediately) .navigationTitle(String.localizedStringWithFormat("nodes %@".localized, String(nodes.count))) + .listStyle(.plain) .confirmationDialog( @@ -218,12 +223,11 @@ struct NodeList: View { Button("Delete Node") { let deleteNode = getNodeInfo(id: deleteNodeId, context: context) if connectedNode != nil { - - } - if deleteNode != nil { - let success = bleManager.removeNode(node: deleteNode!, connectedNodeNum: Int64(connectedNodeNum)) - if !success { - print("Failed to delete node \(deleteNode?.user?.longName ?? "unknown".localized)") + if deleteNode != nil { + let success = bleManager.removeNode(node: deleteNode!, connectedNodeNum: Int64(connectedNodeNum)) + if !success { + print("Failed to delete node \(deleteNode?.user?.longName ?? "unknown".localized)") + } } } } @@ -310,6 +314,23 @@ struct NodeList: View { .onChange(of: distanceFilter) { _ in searchNodeList() } + .onChange(of: (appState.navigationPath)) { newPath in + + if ((newPath?.hasPrefix("meshtastic://nodes")) != nil) { + + if let urlComponent = URLComponents(string: newPath ?? "") { + let queryItems = urlComponent.queryItems + let nodeNum = queryItems?.first(where: { $0.name == "nodenum" })?.value + if nodeNum == nil { + print("nodeNum not found") + } + else { + selectedNode = nodes.first(where: { $0.num == Int64(nodeNum ?? "-1") }) + AppState.shared.navigationPath = nil + } + } + } + } .onAppear { if self.bleManager.context == nil { self.bleManager.context = context diff --git a/Meshtastic/Views/Settings/ShareChannels.swift b/Meshtastic/Views/Settings/ShareChannels.swift index 686af46f..b2e2f1c3 100644 --- a/Meshtastic/Views/Settings/ShareChannels.swift +++ b/Meshtastic/Views/Settings/ShareChannels.swift @@ -278,8 +278,6 @@ struct ShareChannels: View { channelSettings.name = ch.name! channelSettings.psk = ch.psk! channelSettings.id = UInt32(ch.id) - channelSettings.uplinkEnabled = ch.uplinkEnabled - channelSettings.downlinkEnabled = ch.downlinkEnabled channelSet.settings.append(channelSettings) } } diff --git a/protobufs b/protobufs index f92900c5..dd7d64cc 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit f92900c5f884b04388fb7abf61d4df66783015e4 +Subproject commit dd7d64cc038a6365c119ec7508762cc45f405948 From 0997cc7dba08d6783cda8ade58c6ffd7b335ebb4 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 14 May 2024 23:07:09 -0700 Subject: [PATCH 07/17] Cleanup --- Meshtastic/Helpers/BLEManager.swift | 8 -------- Meshtastic/MeshtasticAppDelegate.swift | 1 - .../Nodes/Helpers/Map/MapContent/MeshMapContent.swift | 2 ++ 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index cc5d7c7f..578daa8d 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -5,14 +5,6 @@ import SwiftUI import MapKit import CocoaMQTT -public protocol DeviceConnection: AnyObject { - var isSubscribed: Bool { get } - var invalidVersion: Bool { get } - //var handledDeepLinks: [DeepLink.Type] { get } - //func canHandle(deepLink: DeepLink) -> Bool - //func handle(deepLink: DeepLink) -} - // --------------------------------------------------------------------------------------- // Meshtastic BLE Device Manager // --------------------------------------------------------------------------------------- diff --git a/Meshtastic/MeshtasticAppDelegate.swift b/Meshtastic/MeshtasticAppDelegate.swift index ee4177cf..81f316cd 100644 --- a/Meshtastic/MeshtasticAppDelegate.swift +++ b/Meshtastic/MeshtasticAppDelegate.swift @@ -37,7 +37,6 @@ class MeshtasticAppDelegate: NSObject, UIApplicationDelegate, UNUserNotification AppState.shared.navigationPath = deepLink if targetValue == "map" { AppState.shared.tabSelection = Tab.map - } else if targetValue == "message" { AppState.shared.tabSelection = Tab.messages } else if targetValue == "nodes" { diff --git a/Meshtastic/Views/Nodes/Helpers/Map/MapContent/MeshMapContent.swift b/Meshtastic/Views/Nodes/Helpers/Map/MapContent/MeshMapContent.swift index cbb50828..79b88081 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/MapContent/MeshMapContent.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/MapContent/MeshMapContent.swift @@ -11,6 +11,7 @@ import MapKit @available(iOS 17.0, macOS 14.0, *) struct MeshMapContent: MapContent { + @StateObject var appState = AppState.shared /// Parameters @Binding var showUserLocation: Bool @AppStorage("meshMapShowNodeHistory") private var showNodeHistory = false @@ -92,6 +93,7 @@ struct MeshMapContent: MapContent { } } + /// Node History and Route Lines for favorites if position.nodePosition?.favorite ?? false { if showRouteLines { From dbc05605d6d728610732bcfc5661911748fcf9e4 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Wed, 15 May 2024 08:06:27 -0700 Subject: [PATCH 08/17] Show alerts when app is in the foreground --- Meshtastic/MeshtasticAppDelegate.swift | 6 ++++-- Meshtastic/Views/Messages/Messages.swift | 20 ++++++++++++++++++++ Meshtastic/Views/Nodes/MeshMap.swift | 4 ---- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/Meshtastic/MeshtasticAppDelegate.swift b/Meshtastic/MeshtasticAppDelegate.swift index 81f316cd..ea3f6a49 100644 --- a/Meshtastic/MeshtasticAppDelegate.swift +++ b/Meshtastic/MeshtasticAppDelegate.swift @@ -7,7 +7,7 @@ import SwiftUI -class MeshtasticAppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate, ObservableObject { +class MeshtasticAppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate, ObservableObject { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { print("🚀 Meshtstic Apple App launched!") // Default User Default Values @@ -28,16 +28,18 @@ class MeshtasticAppDelegate: NSObject, UIApplicationDelegate, UNUserNotification return true } func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { + completionHandler(.banner) } // This method is called when user clicked on the notification func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { let userInfo = response.notification.request.content.userInfo let targetValue = userInfo["target"] as? String let deepLink = userInfo["path"] as? String + AppState.shared.navigationPath = deepLink if targetValue == "map" { AppState.shared.tabSelection = Tab.map - } else if targetValue == "message" { + } else if targetValue == "messages" { AppState.shared.tabSelection = Tab.messages } else if targetValue == "nodes" { AppState.shared.tabSelection = Tab.nodes diff --git a/Meshtastic/Views/Messages/Messages.swift b/Meshtastic/Views/Messages/Messages.swift index 056e1630..482f538f 100644 --- a/Meshtastic/Views/Messages/Messages.swift +++ b/Meshtastic/Views/Messages/Messages.swift @@ -66,6 +66,26 @@ struct Messages: View { .navigationTitle("messages") .navigationBarTitleDisplayMode(.large) .navigationBarItems(leading: MeshtasticLogo()) + .onChange(of: (appState.navigationPath)) { newPath in + + if ((newPath?.hasPrefix("meshtastic://messages")) != nil) { + + if let urlComponent = URLComponents(string: newPath ?? "") { + let queryItems = urlComponent.queryItems + let messageId = queryItems?.first(where: { $0.name == "messageId" })?.value + let channel = queryItems?.first(where: { $0.name == "channel" })?.value + + if channel == nil { + print("Channel not found") + } + else { + print("Channel \(channel)") + // selectedNode = nodes.first(where: { $0.num == Int64(nodeNum ?? "-1") }) + // AppState.shared.navigationPath = nil + } + } + } + } .onAppear { if self.bleManager.context == nil { self.bleManager.context = context diff --git a/Meshtastic/Views/Nodes/MeshMap.swift b/Meshtastic/Views/Nodes/MeshMap.swift index 9eb3e718..35b67397 100644 --- a/Meshtastic/Views/Nodes/MeshMap.swift +++ b/Meshtastic/Views/Nodes/MeshMap.swift @@ -177,13 +177,9 @@ struct MeshMap: View { self.bleManager.context = context } - - // if deepLinkManager.waypointId.length > 0 { // let wayPointEntity = getWaypoint(id: Int64(deepLinkManager.waypointId) ?? -1, context: context) //if wayPointEntity.id > 0 { // position = .camera(MapCamera(centerCoordinate: wayPointEntity.coordinate, distance: 1000, heading: 0, pitch: 60)) - //} - switch selectedMapLayer { case .standard: mapStyle = MapStyle.standard(elevation: .realistic, pointsOfInterest: showPointsOfInterest ? .all : .excludingAll, showsTraffic: showTraffic) From 3046361dd02adfdd9bfbab3520e6d76a69ef50c5 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Fri, 24 May 2024 18:15:53 -0700 Subject: [PATCH 09/17] Assorted changes --- Meshtastic/Helpers/MeshPackets.swift | 6 +++--- Meshtastic/MeshtasticAppDelegate.swift | 6 +++--- Meshtastic/Views/Messages/UserList.swift | 5 +++++ .../Helpers/Map/MapContent/MeshMapContent.swift | 9 +++++---- .../Helpers/Map/MapContent/NodeMapContent.swift | 13 +++++++++---- .../Views/Nodes/Helpers/Map/NodeMapSwiftUI.swift | 14 +++++++------- Meshtastic/Views/Nodes/Helpers/NodeDetail.swift | 2 +- Meshtastic/Views/Nodes/NodeList.swift | 7 +++++-- 8 files changed, 38 insertions(+), 24 deletions(-) diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index d436aef7..d79bc87e 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -871,8 +871,8 @@ func textMessageAppPacket(packet: MeshPacket, wantRangeTestPackets: Bool, connec title: "\(newMessage.fromUser?.longName ?? "unknown".localized)", subtitle: "AKA \(newMessage.fromUser?.shortName ?? "?")", content: messageText!, - target: "message", - path: "meshtastic://open-dm?userid=\(newMessage.fromUser?.num ?? 0)&id=\(newMessage.messageId)" + target: "messages", + path: "meshtastic://messages?userNum=\(newMessage.fromUser?.num ?? 0)&messageId=\(newMessage.messageId)" ) ] manager.schedule() @@ -904,7 +904,7 @@ func textMessageAppPacket(packet: MeshPacket, wantRangeTestPackets: Bool, connec title: "\(newMessage.fromUser?.longName ?? "unknown".localized)", subtitle: "AKA \(newMessage.fromUser?.shortName ?? "?")", content: messageText!, - target: "message", + target: "messages", path: "meshtastic://messages?channel=\(newMessage.channel)&messageId=\(newMessage.messageId)") ] manager.schedule() diff --git a/Meshtastic/MeshtasticAppDelegate.swift b/Meshtastic/MeshtasticAppDelegate.swift index ea3f6a49..c44a7f7d 100644 --- a/Meshtastic/MeshtasticAppDelegate.swift +++ b/Meshtastic/MeshtasticAppDelegate.swift @@ -11,7 +11,6 @@ class MeshtasticAppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificat func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { print("🚀 Meshtstic Apple App launched!") // Default User Default Values - UserDefaults.standard.register(defaults: ["blockRangeTest" : true]) UserDefaults.standard.register(defaults: ["meshMapRecentering" : true]) UserDefaults.standard.register(defaults: ["meshMapShowNodeHistory" : true]) UserDefaults.standard.register(defaults: ["meshMapShowRouteLines" : true]) @@ -27,10 +26,11 @@ class MeshtasticAppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificat } return true } + // Lets us show the notification in the app in the foreground func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { - completionHandler(.banner) + completionHandler([.list, .banner, .sound]) } - // This method is called when user clicked on the notification + // This method is called when a user clicks on the notification func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { let userInfo = response.notification.request.content.userInfo let targetValue = userInfo["target"] as? String diff --git a/Meshtastic/Views/Messages/UserList.swift b/Meshtastic/Views/Messages/UserList.swift index 03dcf882..2d13d28f 100644 --- a/Meshtastic/Views/Messages/UserList.swift +++ b/Meshtastic/Views/Messages/UserList.swift @@ -35,6 +35,7 @@ struct UserList: View { private var users: FetchedResults @State var node: NodeInfoEntity? + @State var selectedUserNum: Int64? @State private var userSelection: UserEntity? // Nothing selected by default. @State private var isPresentingDeleteUserMessagesConfirm: Bool = false @@ -203,6 +204,10 @@ struct UserList: View { .onChange(of: distanceFilter) { _ in searchUserList() } + .onChange(of: selectedUserNum) { newUserNum in + userSelection = users.first(where: { $0.num == newUserNum }) + print(userSelection) + } .onAppear { if self.bleManager.context == nil { self.bleManager.context = context diff --git a/Meshtastic/Views/Nodes/Helpers/Map/MapContent/MeshMapContent.swift b/Meshtastic/Views/Nodes/Helpers/Map/MapContent/MeshMapContent.swift index 79b88081..4270343c 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/MapContent/MeshMapContent.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/MapContent/MeshMapContent.swift @@ -40,13 +40,14 @@ struct MeshMapContent: MapContent { @MapContentBuilder var meshMap: some MapContent { - let lineCoords = Array(positions).compactMap({(position) -> CLLocationCoordinate2D in - return position.nodeCoordinate ?? LocationsHandler.DefaultLocation + let loraNodes = positions.filter { $0.nodePosition?.viaMqtt ?? true == false } + let loraCoords = Array(loraNodes).compactMap({(position) -> CLLocationCoordinate2D in + return position.nodeCoordinate ?? LocationsHandler.DefaultLocation }) /// Convex Hull if showConvexHull { - if lineCoords.count > 0 { - let hull = lineCoords.getConvexHull() + if loraCoords.count > 0 { + let hull = loraCoords.getConvexHull() MapPolygon(coordinates: hull) .stroke(.blue, lineWidth: 3) .foregroundStyle(.indigo.opacity(0.4)) diff --git a/Meshtastic/Views/Nodes/Helpers/Map/MapContent/NodeMapContent.swift b/Meshtastic/Views/Nodes/Helpers/Map/MapContent/NodeMapContent.swift index 64cc43a8..78d20407 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/MapContent/NodeMapContent.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/MapContent/NodeMapContent.swift @@ -40,6 +40,7 @@ struct NodeMapContent: MapContent { let lineCoords = positionArray.compactMap({(position) -> CLLocationCoordinate2D in return position.nodeCoordinate ?? LocationsHandler.DefaultLocation }) + /// Node Color from node.num let nodeColor = UIColor(hex: UInt32(node.num)) @@ -58,13 +59,17 @@ struct NodeMapContent: MapContent { .stroke(.white, lineWidth: 2) } } + let loraNodes = positions.filter { $0.nodePosition?.viaMqtt ?? true == false } + let loraCoords = Array(loraNodes).compactMap({(position) -> CLLocationCoordinate2D in + return position.nodeCoordinate ?? LocationsHandler.DefaultLocation + }) /// Convex Hull if showConvexHull { - if lineCoords.count > 0 { - let hull = lineCoords.getConvexHull() + if loraCoords.count > 0 { + let hull = loraCoords.getConvexHull() MapPolygon(coordinates: hull) - .stroke(Color(nodeColor.darker()), lineWidth: 3) - .foregroundStyle(Color(nodeColor).opacity(0.4)) + .stroke(.blue, lineWidth: 3) + .foregroundStyle(.indigo.opacity(0.4)) } } /// Route Lines diff --git a/Meshtastic/Views/Nodes/Helpers/Map/NodeMapSwiftUI.swift b/Meshtastic/Views/Nodes/Helpers/Map/NodeMapSwiftUI.swift index cf983edb..bab5aa81 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/NodeMapSwiftUI.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/NodeMapSwiftUI.swift @@ -87,13 +87,13 @@ struct NodeMapSwiftUI: View { switch selectedMapLayer { case .standard: UserDefaults.mapLayer = newMapLayer - mapStyle = MapStyle.standard(elevation: .realistic, pointsOfInterest: showPointsOfInterest ? .all : .excludingAll, showsTraffic: showTraffic) + mapStyle = MapStyle.standard(elevation: .flat, pointsOfInterest: showPointsOfInterest ? .all : .excludingAll, showsTraffic: showTraffic) case .hybrid: UserDefaults.mapLayer = newMapLayer - mapStyle = MapStyle.hybrid(elevation: .realistic, pointsOfInterest: showPointsOfInterest ? .all : .excludingAll, showsTraffic: showTraffic) + mapStyle = MapStyle.hybrid(elevation: .flat, pointsOfInterest: showPointsOfInterest ? .all : .excludingAll, showsTraffic: showTraffic) case .satellite: UserDefaults.mapLayer = newMapLayer - mapStyle = MapStyle.imagery(elevation: .realistic) + mapStyle = MapStyle.imagery(elevation: .flat) case .offline: return } @@ -118,13 +118,13 @@ struct NodeMapSwiftUI: View { UIApplication.shared.isIdleTimerDisabled = true switch selectedMapLayer { case .standard: - mapStyle = MapStyle.standard(elevation: .realistic, pointsOfInterest: showPointsOfInterest ? .all : .excludingAll, showsTraffic: showTraffic) + mapStyle = MapStyle.standard(elevation: .flat, pointsOfInterest: showPointsOfInterest ? .all : .excludingAll, showsTraffic: showTraffic) case .hybrid: - mapStyle = MapStyle.hybrid(elevation: .realistic, pointsOfInterest: showPointsOfInterest ? .all : .excludingAll, showsTraffic: showTraffic) + mapStyle = MapStyle.hybrid(elevation: .flat, pointsOfInterest: showPointsOfInterest ? .all : .excludingAll, showsTraffic: showTraffic) case .satellite: - mapStyle = MapStyle.imagery(elevation: .realistic) + mapStyle = MapStyle.imagery(elevation: .flat) case .offline: - mapStyle = MapStyle.hybrid(elevation: .realistic, pointsOfInterest: showPointsOfInterest ? .all : .excludingAll, showsTraffic: showTraffic) + mapStyle = MapStyle.hybrid(elevation: .flat, pointsOfInterest: showPointsOfInterest ? .all : .excludingAll, showsTraffic: showTraffic) } mostRecent = node.positions?.lastObject as? PositionEntity if node.positions?.count ?? 0 > 1 { diff --git a/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift b/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift index e4c4886e..d93a6ab2 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift @@ -35,7 +35,7 @@ struct NodeDetail: View { Label( title: { Text("\("uptime".localized)") - .font(.title2)+Text(": \(components)") + .font(.title3)+Text(": \(components)") .font(.title3) .foregroundColor(Color.gray) }, diff --git a/Meshtastic/Views/Nodes/NodeList.swift b/Meshtastic/Views/Nodes/NodeList.swift index 90c3d37b..a6e1368d 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -316,9 +316,12 @@ struct NodeList: View { } .onChange(of: (appState.navigationPath)) { newPath in - if ((newPath?.hasPrefix("meshtastic://nodes")) != nil) { + guard let deepLink = newPath else { + return + } + if deepLink.hasPrefix("meshtastic://nodes") { - if let urlComponent = URLComponents(string: newPath ?? "") { + if let urlComponent = URLComponents(string: deepLink) { let queryItems = urlComponent.queryItems let nodeNum = queryItems?.first(where: { $0.name == "nodenum" })?.value if nodeNum == nil { From 3aefd67d067fc95c56df4ac3b58f5a37feb51545 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 26 May 2024 10:37:22 -0700 Subject: [PATCH 10/17] Check for last postion before sending phone location --- Meshtastic.xcodeproj/project.pbxproj | 8 ----- Meshtastic/Helpers/BLEManager.swift | 38 ++++++++++----------- Meshtastic/Persistence/UpdateCoreData.swift | 1 - protobufs | 2 +- 4 files changed, 20 insertions(+), 29 deletions(-) diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index c1a9f681..a96e3707 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -573,13 +573,6 @@ path = CoreData; sourceTree = ""; }; - DD17C4D42BE7EC2200D45AC7 /* SwiftData */ = { - isa = PBXGroup; - children = ( - ); - path = SwiftData; - sourceTree = ""; - }; DD47E3CA26F0E50300029299 /* Nodes */ = { isa = PBXGroup; children = ( @@ -957,7 +950,6 @@ DDC4D5662754996200A4208E /* Persistence */ = { isa = PBXGroup; children = ( - DD17C4D42BE7EC2200D45AC7 /* SwiftData */, DDC4D567275499A500A4208E /* Persistence.swift */, DD964FC52975DBFD007C176F /* QueryCoreData.swift */, DD3CC6C128EB9D4900FA9159 /* UpdateCoreData.swift */, diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 578daa8d..729a6fa0 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -1002,26 +1002,26 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate var positionPacket = Position() if #available(iOS 17.0, macOS 14.0, *) { - if let lastLocation = LocationsHandler.shared.locationsArray.last { - - positionPacket.latitudeI = Int32(lastLocation.coordinate.latitude * 1e7) - positionPacket.longitudeI = Int32(lastLocation.coordinate.longitude * 1e7) - let timestamp = lastLocation.timestamp - positionPacket.time = UInt32(timestamp.timeIntervalSince1970) - positionPacket.timestamp = UInt32(timestamp.timeIntervalSince1970) - positionPacket.altitude = Int32(lastLocation.altitude) - positionPacket.satsInView = UInt32(LocationsHandler.satsInView) - - let currentSpeed = lastLocation.speed - if currentSpeed > 0 && (!currentSpeed.isNaN || !currentSpeed.isInfinite) { - positionPacket.groundSpeed = UInt32(currentSpeed) - } - let currentHeading = lastLocation.course - if (currentHeading > 0 && currentHeading <= 360) && (!currentHeading.isNaN || !currentHeading.isInfinite) { - positionPacket.groundTrack = UInt32(currentHeading) - } + guard let lastLocation = LocationsHandler.shared.locationsArray.last else { + return nil } - + positionPacket.latitudeI = Int32(lastLocation.coordinate.latitude * 1e7) + positionPacket.longitudeI = Int32(lastLocation.coordinate.longitude * 1e7) + let timestamp = lastLocation.timestamp + positionPacket.time = UInt32(timestamp.timeIntervalSince1970) + positionPacket.timestamp = UInt32(timestamp.timeIntervalSince1970) + positionPacket.altitude = Int32(lastLocation.altitude) + positionPacket.satsInView = UInt32(LocationsHandler.satsInView) + + let currentSpeed = lastLocation.speed + if currentSpeed > 0 && (!currentSpeed.isNaN || !currentSpeed.isInfinite) { + positionPacket.groundSpeed = UInt32(currentSpeed) + } + let currentHeading = lastLocation.course + if (currentHeading > 0 && currentHeading <= 360) && (!currentHeading.isNaN || !currentHeading.isInfinite) { + positionPacket.groundTrack = UInt32(currentHeading) + } + } else { positionPacket.latitudeI = Int32(LocationHelper.currentLocation.latitude * 1e7) diff --git a/Meshtastic/Persistence/UpdateCoreData.swift b/Meshtastic/Persistence/UpdateCoreData.swift index 093e8872..491ba4b5 100644 --- a/Meshtastic/Persistence/UpdateCoreData.swift +++ b/Meshtastic/Persistence/UpdateCoreData.swift @@ -120,7 +120,6 @@ public func clearCoreDataDatabase(context: NSManagedObjectContext, includeRoutes deleteRequest = NSBatchDeleteRequest(fetchRequest: query) } else if !includeRoutes { if !(entityName.contains("RouteEntity") || entityName.contains("LocationEntity")) { - print(entity.name?.lowercased()) deleteRequest = NSBatchDeleteRequest(fetchRequest: query) } } diff --git a/protobufs b/protobufs index dd7d64cc..f92900c5 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit dd7d64cc038a6365c119ec7508762cc45f405948 +Subproject commit f92900c5f884b04388fb7abf61d4df66783015e4 From 0b855a08bf2f9dd5b85d9afe825cfa2c7058aa44 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 26 May 2024 12:15:50 -0700 Subject: [PATCH 11/17] Check node num length before making a new node info or user core data object --- .../CoreData/UserEntityExtension.swift | 14 ++++++ Meshtastic/Helpers/MeshPackets.swift | 24 ++------- Meshtastic/Persistence/UpdateCoreData.swift | 50 +++++-------------- 3 files changed, 31 insertions(+), 57 deletions(-) diff --git a/Meshtastic/Extensions/CoreData/UserEntityExtension.swift b/Meshtastic/Extensions/CoreData/UserEntityExtension.swift index ecf4e5e2..e4887c78 100644 --- a/Meshtastic/Extensions/CoreData/UserEntityExtension.swift +++ b/Meshtastic/Extensions/CoreData/UserEntityExtension.swift @@ -6,6 +6,7 @@ // import Foundation +import CoreData extension UserEntity { @@ -27,3 +28,16 @@ extension UserEntity { return unreadMessages.count } } + + +public func createUser(num: Int64, context: NSManagedObjectContext) -> UserEntity { + let newUser = UserEntity(context: context) + newUser.num = Int64(num) + let userId = String(format:"%2X", num) + newUser.userId = "!\(userId)" + let last4 = String(userId.suffix(4)) + newUser.longName = "Meshtastic \(last4)" + newUser.shortName = last4 + newUser.hwModel = "UNSET" + return newUser +} diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index d79bc87e..78121fb0 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -294,16 +294,8 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje newUser.isLicensed = nodeInfo.user.isLicensed newUser.role = Int32(nodeInfo.user.role.rawValue) newNode.user = newUser - } else { - let newUser = UserEntity(context: context) - newUser.num = Int64(nodeInfo.num) - newUser.numString = String(nodeInfo.num) - let userId = String(format:"%2X", nodeInfo.num) - newUser.userId = "!\(userId)" - let last4 = String(userId.suffix(4)) - newUser.longName = "Meshtastic \(last4)" - newUser.shortName = last4 - newUser.hwModel = "UNSET" + } else if nodeInfo.num > Int16.max { + let newUser = createUser(num: Int64(nodeInfo.num), context: context) newNode.user = newUser } @@ -369,15 +361,9 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje fetchedNode[0].user!.role = Int32(nodeInfo.user.role.rawValue) fetchedNode[0].user!.hwModel = String(describing: nodeInfo.user.hwModel).uppercased() } else { - if (fetchedNode[0].user == nil) { - let newUser = UserEntity(context: context) - newUser.num = Int64(nodeInfo.num) - let userId = String(format:"%2X", nodeInfo.num) - newUser.userId = "!\(userId)" - let last4 = String(userId.suffix(4)) - newUser.longName = "Meshtastic \(last4)" - newUser.shortName = last4 - newUser.hwModel = "UNSET" + if (fetchedNode[0].user == nil && nodeInfo.num > Int16.max) { + + let newUser = createUser(num: Int64(nodeInfo.num), context: context) fetchedNode[0].user = newUser } } diff --git a/Meshtastic/Persistence/UpdateCoreData.swift b/Meshtastic/Persistence/UpdateCoreData.swift index 491ba4b5..46e27062 100644 --- a/Meshtastic/Persistence/UpdateCoreData.swift +++ b/Meshtastic/Persistence/UpdateCoreData.swift @@ -164,17 +164,11 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) if let newUserMessage = try? User(serializedData: packet.decoded.payload) { - if newUserMessage.id.isEmpty { - let newUser = UserEntity(context: context) - newUser.num = Int64(packet.from) - let userId = String(format:"%2X", packet.from) - newUser.userId = "!\(userId)" - let last4 = String(userId.suffix(4)) - newUser.longName = "Meshtastic \(last4)" - newUser.shortName = last4 - newUser.hwModel = "UNSET" - newNode.user = newUser - + if newUserMessage.id.isEmpty { + if packet.from > Int16.max { + let newUser = createUser(num: Int64(packet.from), context: context) + newNode.user = newUser + } } else { let newUser = UserEntity(context: context) @@ -203,27 +197,14 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) } } } else { - let newUser = UserEntity(context: context) - newUser.num = Int64(packet.from) - let userId = String(format:"%2X", packet.from) - newUser.userId = "!\(userId)" - let last4 = String(userId.suffix(4)) - newUser.longName = "Meshtastic \(last4)" - newUser.shortName = last4 - newUser.hwModel = "UNSET" - newNode.user = newUser + if packet.from > Int16.max { + let newUser = createUser(num: Int64(packet.from), context: context) + fetchedNode[0].user = newUser + } } - if newNode.user == nil { - let newUser = UserEntity(context: context) - newUser.num = Int64(packet.from) - let userId = String(format:"%2X", packet.from) - newUser.userId = "!\(userId)" - let last4 = String(userId.suffix(4)) - newUser.longName = "Meshtastic \(last4)" - newUser.shortName = last4 - newUser.hwModel = "UNSET" - newNode.user = newUser + if newNode.user == nil && packet.from > Int16.max { + newNode.user = createUser(num: Int64(packet.from), context: context) } let myInfoEntity = MyInfoEntity(context: context) @@ -280,14 +261,7 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) fetchedNode[0].hopsAway = Int32(packet.hopStart - packet.hopLimit) } if (fetchedNode[0].user == nil) { - let newUser = UserEntity(context: context) - newUser.num = Int64(packet.from) - let userId = String(format:"%2X", packet.from) - newUser.userId = "!\(userId)" - let last4 = String(userId.suffix(4)) - newUser.longName = "Meshtastic \(last4)" - newUser.shortName = last4 - newUser.hwModel = "UNSET" + let newUser = createUser(num: Int64(packet.from), context: context) fetchedNode[0].user! = newUser } do { From 473dd3a4243cebae4bb680cee328a1441f188fd2 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 26 May 2024 12:25:45 -0700 Subject: [PATCH 12/17] Update protobufs --- Meshtastic/Protobufs/meshtastic/mesh.pb.swift | 18 +++++ .../meshtastic/module_config.pb.swift | 20 ++++++ .../Protobufs/meshtastic/telemetry.pb.swift | 68 +++++++++++++++++++ protobufs | 2 +- 4 files changed, 107 insertions(+), 1 deletion(-) diff --git a/Meshtastic/Protobufs/meshtastic/mesh.pb.swift b/Meshtastic/Protobufs/meshtastic/mesh.pb.swift index a3dc0f6d..7d33c743 100644 --- a/Meshtastic/Protobufs/meshtastic/mesh.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/mesh.pb.swift @@ -278,6 +278,16 @@ enum HardwareModel: SwiftProtobuf.Enum { /// Adafruit NRF52840 feather express with SX1262, SSD1306 OLED and NEO6M GPS case twcMeshV4 // = 62 + /// + /// NRF52_PROMICRO_DIY + /// Promicro NRF52840 with SX1262/LLCC68, SSD1306 OLED and NEO6M GPS + case nrf52PromicroDiy // = 63 + + /// + /// RadioMaster 900 Bandit Nano, https://www.radiomasterrc.com/products/bandit-nano-expresslrs-rf-module + /// ESP32-D0WDQ6 With SX1276/SKY66122, SSD1306 OLED and No GPS + case radiomaster900BanditNano // = 64 + /// /// ------------------------------------------------------------------------------------------------------------------------------------------ /// 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. @@ -350,6 +360,8 @@ enum HardwareModel: SwiftProtobuf.Enum { case 60: self = .tdLorac case 61: self = .cdebyteEoraS3 case 62: self = .twcMeshV4 + case 63: self = .nrf52PromicroDiy + case 64: self = .radiomaster900BanditNano case 255: self = .privateHw default: self = .UNRECOGNIZED(rawValue) } @@ -416,6 +428,8 @@ enum HardwareModel: SwiftProtobuf.Enum { case .tdLorac: return 60 case .cdebyteEoraS3: return 61 case .twcMeshV4: return 62 + case .nrf52PromicroDiy: return 63 + case .radiomaster900BanditNano: return 64 case .privateHw: return 255 case .UNRECOGNIZED(let i): return i } @@ -487,6 +501,8 @@ extension HardwareModel: CaseIterable { .tdLorac, .cdebyteEoraS3, .twcMeshV4, + .nrf52PromicroDiy, + .radiomaster900BanditNano, .privateHw, ] } @@ -2787,6 +2803,8 @@ extension HardwareModel: SwiftProtobuf._ProtoNameProviding { 60: .same(proto: "TD_LORAC"), 61: .same(proto: "CDEBYTE_EORA_S3"), 62: .same(proto: "TWC_MESH_V4"), + 63: .same(proto: "NRF52_PROMICRO_DIY"), + 64: .same(proto: "RADIOMASTER_900_BANDIT_NANO"), 255: .same(proto: "PRIVATE_HW"), ] } diff --git a/Meshtastic/Protobufs/meshtastic/module_config.pb.swift b/Meshtastic/Protobufs/meshtastic/module_config.pb.swift index f6c28745..465e4e1f 100644 --- a/Meshtastic/Protobufs/meshtastic/module_config.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/module_config.pb.swift @@ -618,6 +618,14 @@ struct ModuleConfig { var paxcounterUpdateInterval: UInt32 = 0 + /// + /// WiFi RSSI threshold. Defaults to -80 + var wifiThreshold: Int32 = 0 + + /// + /// BLE RSSI threshold. Defaults to -80 + var bleThreshold: Int32 = 0 + var unknownFields = SwiftProtobuf.UnknownStorage() init() {} @@ -1921,6 +1929,8 @@ extension ModuleConfig.PaxcounterConfig: SwiftProtobuf.Message, SwiftProtobuf._M static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 1: .same(proto: "enabled"), 2: .standard(proto: "paxcounter_update_interval"), + 3: .standard(proto: "wifi_threshold"), + 4: .standard(proto: "ble_threshold"), ] mutating func decodeMessage(decoder: inout D) throws { @@ -1931,6 +1941,8 @@ extension ModuleConfig.PaxcounterConfig: SwiftProtobuf.Message, SwiftProtobuf._M switch fieldNumber { case 1: try { try decoder.decodeSingularBoolField(value: &self.enabled) }() case 2: try { try decoder.decodeSingularUInt32Field(value: &self.paxcounterUpdateInterval) }() + case 3: try { try decoder.decodeSingularInt32Field(value: &self.wifiThreshold) }() + case 4: try { try decoder.decodeSingularInt32Field(value: &self.bleThreshold) }() default: break } } @@ -1943,12 +1955,20 @@ extension ModuleConfig.PaxcounterConfig: SwiftProtobuf.Message, SwiftProtobuf._M if self.paxcounterUpdateInterval != 0 { try visitor.visitSingularUInt32Field(value: self.paxcounterUpdateInterval, fieldNumber: 2) } + if self.wifiThreshold != 0 { + try visitor.visitSingularInt32Field(value: self.wifiThreshold, fieldNumber: 3) + } + if self.bleThreshold != 0 { + try visitor.visitSingularInt32Field(value: self.bleThreshold, fieldNumber: 4) + } try unknownFields.traverse(visitor: &visitor) } static func ==(lhs: ModuleConfig.PaxcounterConfig, rhs: ModuleConfig.PaxcounterConfig) -> Bool { if lhs.enabled != rhs.enabled {return false} if lhs.paxcounterUpdateInterval != rhs.paxcounterUpdateInterval {return false} + if lhs.wifiThreshold != rhs.wifiThreshold {return false} + if lhs.bleThreshold != rhs.bleThreshold {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } diff --git a/Meshtastic/Protobufs/meshtastic/telemetry.pb.swift b/Meshtastic/Protobufs/meshtastic/telemetry.pb.swift index 7f9d3396..c778bf49 100644 --- a/Meshtastic/Protobufs/meshtastic/telemetry.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/telemetry.pb.swift @@ -92,6 +92,30 @@ enum TelemetrySensorType: SwiftProtobuf.Enum { /// /// RCWL-9620 Doppler Radar Distance Sensor, used for water level detection case rcwl9620 // = 16 + + /// + /// Sensirion High accuracy temperature and humidity + case sht4X // = 17 + + /// + /// VEML7700 high accuracy ambient light(Lux) digital 16-bit resolution sensor. + case veml7700 // = 18 + + /// + /// MLX90632 non-contact IR temperature sensor. + case mlx90632 // = 19 + + /// + /// TI OPT3001 Ambient Light Sensor + case opt3001 // = 20 + + /// + /// Lite On LTR-390UV-01 UV Light Sensor + case ltr390Uv // = 21 + + /// + /// AMS TSL25911FN RGB Light Sensor + case tsl25911Fn // = 22 case UNRECOGNIZED(Int) init() { @@ -117,6 +141,12 @@ enum TelemetrySensorType: SwiftProtobuf.Enum { case 14: self = .ina3221 case 15: self = .bmp085 case 16: self = .rcwl9620 + case 17: self = .sht4X + case 18: self = .veml7700 + case 19: self = .mlx90632 + case 20: self = .opt3001 + case 21: self = .ltr390Uv + case 22: self = .tsl25911Fn default: self = .UNRECOGNIZED(rawValue) } } @@ -140,6 +170,12 @@ enum TelemetrySensorType: SwiftProtobuf.Enum { case .ina3221: return 14 case .bmp085: return 15 case .rcwl9620: return 16 + case .sht4X: return 17 + case .veml7700: return 18 + case .mlx90632: return 19 + case .opt3001: return 20 + case .ltr390Uv: return 21 + case .tsl25911Fn: return 22 case .UNRECOGNIZED(let i): return i } } @@ -168,6 +204,12 @@ extension TelemetrySensorType: CaseIterable { .ina3221, .bmp085, .rcwl9620, + .sht4X, + .veml7700, + .mlx90632, + .opt3001, + .ltr390Uv, + .tsl25911Fn, ] } @@ -245,6 +287,14 @@ struct EnvironmentMetrics { /// RCWL9620 Doppler Radar Distance Sensor, used for water level detection. Float value in mm. var distance: Float = 0 + /// + /// VEML7700 high accuracy ambient light(Lux) digital 16-bit resolution sensor. + var lux: Float = 0 + + /// + /// VEML7700 high accuracy white light(irradiance) not calibrated digital 16-bit resolution sensor. + var whiteLux: Float = 0 + var unknownFields = SwiftProtobuf.UnknownStorage() init() {} @@ -479,6 +529,12 @@ extension TelemetrySensorType: SwiftProtobuf._ProtoNameProviding { 14: .same(proto: "INA3221"), 15: .same(proto: "BMP085"), 16: .same(proto: "RCWL9620"), + 17: .same(proto: "SHT4X"), + 18: .same(proto: "VEML7700"), + 19: .same(proto: "MLX90632"), + 20: .same(proto: "OPT3001"), + 21: .same(proto: "LTR390UV"), + 22: .same(proto: "TSL25911FN"), ] } @@ -549,6 +605,8 @@ extension EnvironmentMetrics: SwiftProtobuf.Message, SwiftProtobuf._MessageImple 6: .same(proto: "current"), 7: .same(proto: "iaq"), 8: .same(proto: "distance"), + 9: .same(proto: "lux"), + 10: .standard(proto: "white_lux"), ] mutating func decodeMessage(decoder: inout D) throws { @@ -565,6 +623,8 @@ extension EnvironmentMetrics: SwiftProtobuf.Message, SwiftProtobuf._MessageImple case 6: try { try decoder.decodeSingularFloatField(value: &self.current) }() case 7: try { try decoder.decodeSingularUInt32Field(value: &self.iaq) }() case 8: try { try decoder.decodeSingularFloatField(value: &self.distance) }() + case 9: try { try decoder.decodeSingularFloatField(value: &self.lux) }() + case 10: try { try decoder.decodeSingularFloatField(value: &self.whiteLux) }() default: break } } @@ -595,6 +655,12 @@ extension EnvironmentMetrics: SwiftProtobuf.Message, SwiftProtobuf._MessageImple if self.distance != 0 { try visitor.visitSingularFloatField(value: self.distance, fieldNumber: 8) } + if self.lux != 0 { + try visitor.visitSingularFloatField(value: self.lux, fieldNumber: 9) + } + if self.whiteLux != 0 { + try visitor.visitSingularFloatField(value: self.whiteLux, fieldNumber: 10) + } try unknownFields.traverse(visitor: &visitor) } @@ -607,6 +673,8 @@ extension EnvironmentMetrics: SwiftProtobuf.Message, SwiftProtobuf._MessageImple if lhs.current != rhs.current {return false} if lhs.iaq != rhs.iaq {return false} if lhs.distance != rhs.distance {return false} + if lhs.lux != rhs.lux {return false} + if lhs.whiteLux != rhs.whiteLux {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } diff --git a/protobufs b/protobufs index f92900c5..5f78a06c 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit f92900c5f884b04388fb7abf61d4df66783015e4 +Subproject commit 5f78a06c0fe5d0bd99c2fe206165212bdce89da0 From 0a14d74192c0528bcf2a3cccc7fa2c76aaf2d891 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 26 May 2024 12:26:54 -0700 Subject: [PATCH 13/17] Bump latest firmware version --- Meshtastic/Views/Settings/Firmware.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Meshtastic/Views/Settings/Firmware.swift b/Meshtastic/Views/Settings/Firmware.swift index 28967927..f27b1967 100644 --- a/Meshtastic/Views/Settings/Firmware.swift +++ b/Meshtastic/Views/Settings/Firmware.swift @@ -12,7 +12,7 @@ struct Firmware: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager var node: NodeInfoEntity? - @State var minimumVersion = "2.3.8" + @State var minimumVersion = "2.3.9" @State var version = "" @State private var currentDevice: DeviceHardware? @State private var latestStable: FirmwareRelease? From ba35e083096e0adb6e8f902cec324a145e8832d4 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 28 May 2024 12:33:49 -0700 Subject: [PATCH 14/17] Take off mesh map node limits, reduce max font size for circle text --- .../Extensions/CoreData/PositionEntityExtension.swift | 6 +++--- Meshtastic/Views/Helpers/CircleText.swift | 7 +++---- protobufs | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/Meshtastic/Extensions/CoreData/PositionEntityExtension.swift b/Meshtastic/Extensions/CoreData/PositionEntityExtension.swift index 680b88bc..9eb16a58 100644 --- a/Meshtastic/Extensions/CoreData/PositionEntityExtension.swift +++ b/Meshtastic/Extensions/CoreData/PositionEntityExtension.swift @@ -14,9 +14,9 @@ extension PositionEntity { static func allPositionsFetchRequest() -> NSFetchRequest { let request: NSFetchRequest = PositionEntity.fetchRequest() - request.fetchLimit = 100 - request.returnsObjectsAsFaults = false - request.includesSubentities = true + //request.fetchLimit = 100 + //request.returnsObjectsAsFaults = false + //request.includesSubentities = true request.returnsDistinctResults = true request.sortDescriptors = [NSSortDescriptor(key: "time", ascending: false)] let positionPredicate = NSPredicate(format: "nodePosition != nil && (nodePosition.user.shortName != nil || nodePosition.user.shortName != '') && latest == true") diff --git a/Meshtastic/Views/Helpers/CircleText.swift b/Meshtastic/Views/Helpers/CircleText.swift index 0a348349..96ca51b2 100644 --- a/Meshtastic/Views/Helpers/CircleText.swift +++ b/Meshtastic/Views/Helpers/CircleText.swift @@ -17,11 +17,10 @@ struct CircleText: View { .fill(color) .frame(width: circleSize, height: circleSize) Text(text) - .textCase(.uppercase) + .frame(width: circleSize * 0.96, height: circleSize * 0.96, alignment: .center) .foregroundColor(color.isLight() ? .black : .white) - .font(.system(size: 8000)) - .minimumScaleFactor(0.001) - .frame(width: circleSize * 0.95, height: circleSize * 0.95, alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/) + .font(.system(size: 3000)) + .minimumScaleFactor(0.002) } .aspectRatio(1, contentMode: .fit) } diff --git a/protobufs b/protobufs index 5f78a06c..dd7d64cc 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 5f78a06c0fe5d0bd99c2fe206165212bdce89da0 +Subproject commit dd7d64cc038a6365c119ec7508762cc45f405948 From 33d8f6bc1a2f342afe0fbe05783bdd919e4bf70c Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 28 May 2024 12:39:30 -0700 Subject: [PATCH 15/17] add back other core data filters --- Meshtastic/Extensions/CoreData/PositionEntityExtension.swift | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Meshtastic/Extensions/CoreData/PositionEntityExtension.swift b/Meshtastic/Extensions/CoreData/PositionEntityExtension.swift index 9eb16a58..5e20cc15 100644 --- a/Meshtastic/Extensions/CoreData/PositionEntityExtension.swift +++ b/Meshtastic/Extensions/CoreData/PositionEntityExtension.swift @@ -14,9 +14,8 @@ extension PositionEntity { static func allPositionsFetchRequest() -> NSFetchRequest { let request: NSFetchRequest = PositionEntity.fetchRequest() - //request.fetchLimit = 100 - //request.returnsObjectsAsFaults = false - //request.includesSubentities = true + request.returnsObjectsAsFaults = false + request.includesSubentities = true request.returnsDistinctResults = true request.sortDescriptors = [NSSortDescriptor(key: "time", ascending: false)] let positionPredicate = NSPredicate(format: "nodePosition != nil && (nodePosition.user.shortName != nil || nodePosition.user.shortName != '') && latest == true") From 58e7a53c302124b47c96b824ebf74fc6b6e965df Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 28 May 2024 12:47:06 -0700 Subject: [PATCH 16/17] 1000 node mesh map limit --- Meshtastic/Extensions/CoreData/PositionEntityExtension.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Meshtastic/Extensions/CoreData/PositionEntityExtension.swift b/Meshtastic/Extensions/CoreData/PositionEntityExtension.swift index 5e20cc15..bac4f840 100644 --- a/Meshtastic/Extensions/CoreData/PositionEntityExtension.swift +++ b/Meshtastic/Extensions/CoreData/PositionEntityExtension.swift @@ -14,6 +14,7 @@ extension PositionEntity { static func allPositionsFetchRequest() -> NSFetchRequest { let request: NSFetchRequest = PositionEntity.fetchRequest() + request.fetchLimit = 1000 request.returnsObjectsAsFaults = false request.includesSubentities = true request.returnsDistinctResults = true From b7aa76564fad8a0784d235909a17ae52d0abbf3e Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 28 May 2024 13:27:25 -0700 Subject: [PATCH 17/17] Clean up the circle text a bit --- Meshtastic/Views/Helpers/CircleText.swift | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Meshtastic/Views/Helpers/CircleText.swift b/Meshtastic/Views/Helpers/CircleText.swift index 96ca51b2..47b4ebbf 100644 --- a/Meshtastic/Views/Helpers/CircleText.swift +++ b/Meshtastic/Views/Helpers/CircleText.swift @@ -16,11 +16,20 @@ struct CircleText: View { Circle() .fill(color) .frame(width: circleSize, height: circleSize) + #if os(macOS) Text(text) - .frame(width: circleSize * 0.96, height: circleSize * 0.96, alignment: .center) + .frame(width: circleSize * 0.95, height: circleSize * 0.95, alignment: .center) .foregroundColor(color.isLight() ? .black : .white) .font(.system(size: 3000)) - .minimumScaleFactor(0.002) + .minimumScaleFactor(0.001) + #else + Text(text) + .frame(width: circleSize * 0.95, height: circleSize * 0.95, alignment: .center) + .foregroundColor(color.isLight() ? .black : .white) + .font(.system(size: 5000)) + .minimumScaleFactor(0.001) + #endif + } .aspectRatio(1, contentMode: .fit) }