From 0dcdca5e23d1658ba3f55a9faa19275435026735 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Thu, 29 Feb 2024 21:26:17 -0800 Subject: [PATCH 01/13] Fix messaging bug, fix bug where node would not create user --- Meshtastic.xcodeproj/project.pbxproj | 8 +-- Meshtastic/Helpers/MeshPackets.swift | 2 +- .../contents | 4 +- Meshtastic/Persistence/UpdateCoreData.swift | 53 +++++++++++++------ 4 files changed, 44 insertions(+), 23 deletions(-) diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index d7a7997a..8b279cbd 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -1583,7 +1583,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.2.25; + MARKETING_VERSION = 2.2.26; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; @@ -1617,7 +1617,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.2.25; + MARKETING_VERSION = 2.2.26; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; @@ -1739,7 +1739,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.2.25; + MARKETING_VERSION = 2.2.26; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1772,7 +1772,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.2.25; + MARKETING_VERSION = 2.2.26; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index ad763b23..aeb39c3a 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -775,7 +775,7 @@ func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManage func textMessageAppPacket(packet: MeshPacket, wantRangeTestPackets: Bool, connectedNode: Int64, storeForward: Bool = false, context: NSManagedObjectContext) { var messageText = String(bytes: packet.decoded.payload, encoding: .utf8) - if !wantRangeTestPackets && ((messageText?.starts(with: "seq ")) != nil) { + if !wantRangeTestPackets && (String(messageText ?? "").starts(with: "seq ")) { return } var storeForwardBroadcast = false diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 28.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 28.xcdatamodel/contents index d3da3c8d..00f91752 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 28.xcdatamodel/contents +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 28.xcdatamodel/contents @@ -1,5 +1,5 @@ - + @@ -249,7 +249,7 @@ - + diff --git a/Meshtastic/Persistence/UpdateCoreData.swift b/Meshtastic/Persistence/UpdateCoreData.swift index c6b290a5..341004f7 100644 --- a/Meshtastic/Persistence/UpdateCoreData.swift +++ b/Meshtastic/Persistence/UpdateCoreData.swift @@ -147,11 +147,23 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) newNode.channel = Int32(packet.channel) if let nodeInfoMessage = try? NodeInfo(serializedData: packet.decoded.payload) { newNode.channel = Int32(nodeInfoMessage.channel) - print(packet.channel) - print("Channel From Message\(nodeInfoMessage.channel)") } if let newUserMessage = try? User(serializedData: packet.decoded.payload) { - let newUser = UserEntity(context: context) + + 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 + + } else { + + let newUser = UserEntity(context: context) newUser.userId = newUserMessage.id newUser.num = Int64(packet.from) newUser.longName = newUserMessage.longName @@ -159,6 +171,17 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) newUser.role = Int32(newUserMessage.role.rawValue) newUser.hwModel = String(describing: newUserMessage.hwModel).uppercased() newNode.user = newUser + } + } 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 newNode.user == nil { @@ -177,7 +200,6 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) print("💥 Error Inserting New Core Data MyInfoEntity: \(nsError)") } newNode.myInfo = myInfoEntity - //newNode.objectWillChange.send() } else { // Update an existing node @@ -211,20 +233,19 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) fetchedNode[0].user!.shortName = nodeInfoMessage.user.shortName fetchedNode[0].user!.role = Int32(nodeInfoMessage.user.role.rawValue) fetchedNode[0].user!.hwModel = String(describing: nodeInfoMessage.user.hwModel).uppercased() - } else { - if (fetchedNode[0].user == nil) { - let newUser = UserEntity(context: context) - newUser.num = Int64(nodeInfoMessage.num) - let userId = String(format:"%2X", nodeInfoMessage.num) - newUser.userId = "!\(userId)" - let last4 = String(userId.suffix(4)) - newUser.longName = "Meshtastic \(last4)" - newUser.shortName = last4 - newUser.hwModel = "UNSET" - fetchedNode[0].user! = newUser - } } } + 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" + fetchedNode[0].user! = newUser + } do { try context.save() print("💾 Updated NodeInfo from Node Info App Packet For: \(fetchedNode[0].num)") From 78b902dae0b4634408ef1ceec8a315ea52ac741d Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sat, 2 Mar 2024 08:31:29 -0800 Subject: [PATCH 02/13] Update mesh logs to not spit out JSON Enhance node list search --- Meshtastic.xcodeproj/project.pbxproj | 12 +- Meshtastic/Helpers/BLEManager.swift | 27 +++-- .../contents | 55 +++++---- .../Views/Nodes/Helpers/NodeListItem.swift | 12 +- Meshtastic/Views/Nodes/NodeList.swift | 104 ++++++++++++------ Meshtastic/Views/Nodes/PaxCounterLog.swift | 4 +- 6 files changed, 130 insertions(+), 84 deletions(-) diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 8b279cbd..156262d1 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -1583,7 +1583,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.2.26; + MARKETING_VERSION = 2.2.27; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; @@ -1617,7 +1617,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.2.26; + MARKETING_VERSION = 2.2.27; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; @@ -1727,7 +1727,7 @@ CODE_SIGN_ENTITLEMENTS = Widgets/WidgetsExtension.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 840; DEVELOPMENT_TEAM = GCH7VS5Y9R; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Widgets/Info.plist; @@ -1739,7 +1739,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.2.26; + MARKETING_VERSION = 2.2.27; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1760,7 +1760,7 @@ CODE_SIGN_ENTITLEMENTS = Widgets/WidgetsExtension.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 840; DEVELOPMENT_TEAM = GCH7VS5Y9R; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Widgets/Info.plist; @@ -1772,7 +1772,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.2.26; + MARKETING_VERSION = 2.2.27; 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 a0e6af59..163e19a3 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -607,11 +607,14 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate case .adminApp: adminAppPacket(packet: decodedInfo.packet, context: context!) case .replyApp: - MeshLogger.log("🕸️ MESH PACKET received for Reply App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") + MeshLogger.log("🕸️ MESH PACKET received for Reply App handling as a text message") + textMessageAppPacket(packet: decodedInfo.packet, wantRangeTestPackets: wantRangeTestPackets, connectedNode: (self.connectedPeripheral != nil ? connectedPeripheral.num : 0), context: context!) case .ipTunnelApp: - MeshLogger.log("🕸️ MESH PACKET received for IP Tunnel App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") + //MeshLogger.log("🕸️ MESH PACKET received for IP Tunnel App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") + MeshLogger.log("🕸️ MESH PACKET received for IP Tunnel App UNHANDLED UNHANDLED") case .serialApp: - MeshLogger.log("🕸️ MESH PACKET received for Serial App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") + //MeshLogger.log("🕸️ MESH PACKET received for Serial App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") + MeshLogger.log("🕸️ MESH PACKET received for Serial App UNHANDLED UNHANDLED") case .storeForwardApp: if wantStoreAndForwardPackets { storeAndForwardPacket(packet: decodedInfo.packet, connectedNodeNum: (self.connectedPeripheral != nil ? connectedPeripheral.num : 0), context: context!) @@ -628,17 +631,23 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate case .telemetryApp: if !invalidVersion { telemetryPacket(packet: decodedInfo.packet, connectedNode: (self.connectedPeripheral != nil ? connectedPeripheral.num : 0), context: context!) } case .textMessageCompressedApp: - MeshLogger.log("🕸️ MESH PACKET received for Text Message Compressed App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") + //MeshLogger.log("🕸️ MESH PACKET received for Text Message Compressed App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") + MeshLogger.log("🕸️ MESH PACKET received for Text Message Compressed App UNHANDLED") case .zpsApp: - MeshLogger.log("🕸️ MESH PACKET received for ZPS App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") + // MeshLogger.log("🕸️ MESH PACKET received for Zero Positioning System App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") + MeshLogger.log("🕸️ MESH PACKET received for Zero Positioning System App UNHANDLED") case .privateApp: - MeshLogger.log("🕸️ MESH PACKET received for Private App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") + //MeshLogger.log("🕸️ MESH PACKET received for Private App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") + MeshLogger.log("🕸️ MESH PACKET received for Private App UNHANDLED UNHANDLED") case .atakForwarder: - MeshLogger.log("🕸️ MESH PACKET received for ATAK Forwarder App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") + //MeshLogger.log("🕸️ MESH PACKET received for ATAK Forwarder App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") + MeshLogger.log("🕸️ MESH PACKET received for ATAK Forwarder App UNHANDLED UNHANDLED") case .simulatorApp: - MeshLogger.log("🕸️ MESH PACKET received for Simulator App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") + //MeshLogger.log("🕸️ MESH PACKET received for Simulator App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") + MeshLogger.log("🕸️ MESH PACKET received for Simulator App UNHANDLED UNHANDLED") case .audioApp: - MeshLogger.log("🕸️ MESH PACKET received for Audio App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") + //MeshLogger.log("🕸️ MESH PACKET received for Audio App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") + MeshLogger.log("🕸️ MESH PACKET received for Audio App UNHANDLED UNHANDLED") case .tracerouteApp: if let routingMessage = try? RouteDiscovery(serializedData: decodedInfo.packet.decoded.payload) { let traceRoute = getTraceRoute(id: Int64(decodedInfo.packet.decoded.requestID), context: context!) diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 28.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 28.xcdatamodel/contents index 00f91752..7c468a65 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 28.xcdatamodel/contents +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 28.xcdatamodel/contents @@ -230,8 +230,7 @@ - - + @@ -239,31 +238,31 @@ - - - - - - - - - - + + + + + + + + + + - - - - - - - - - - - - - - + + + + + + + + + + + + + + @@ -422,8 +421,8 @@ - - + + diff --git a/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift b/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift index 4dd1bf7c..053622ae 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift @@ -170,11 +170,13 @@ struct NodeListItem: View { .font(.callout) .frame(width: 30) } - if node.hasTraceRoutes { - Image(systemName: "signpost.right.and.left") - .symbolRenderingMode(.hierarchical) - .font(.callout) - .frame(width: 30) + if #available(iOS 17.0, macOS 14.0, *) { + if node.hasTraceRoutes { + Image(systemName: "signpost.right.and.left") + .symbolRenderingMode(.hierarchical) + .font(.callout) + .frame(width: 30) + } } } } diff --git a/Meshtastic/Views/Nodes/NodeList.swift b/Meshtastic/Views/Nodes/NodeList.swift index 48bfeb80..569b6651 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -7,6 +7,28 @@ import SwiftUI import CoreLocation +struct NodeSearchState { + var searchText = "" + var searchScope = SearchScopes.all + var predicate: NSPredicate = .init() + + enum SearchScopes: CaseIterable, Identifiable { + case all + case lora + case mqtt + + var id: Self { self } + + var title: LocalizedStringKey { + switch self { + case .all: return "All" + case .lora: return "LoRa" + case .mqtt: return "MQTT" + } + } + } +} + struct NodeList: View { @State private var columnVisibility = NavigationSplitViewVisibility.all @@ -15,18 +37,11 @@ struct NodeList: View { @State private var isPresentingClientHistorySentAlert = false @State private var isPresentingDeleteNodeAlert = false @State private var deleteNodeId: Int64 = 0 + @State private var searchState = NodeSearchState() @SceneStorage("selectedDetailView") var selectedDetailView: String? @State private var searchText = "" - var nodesQuery: Binding { - Binding { - searchText - } set: { newValue in - searchText = newValue - nodes.nsPredicate = newValue.isEmpty ? nil : NSPredicate(format: "user.longName CONTAINS[c] %@ OR user.shortName CONTAINS[c] %@", newValue, newValue) - } - } @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager @@ -37,8 +52,6 @@ struct NodeList: View { var nodes: FetchedResults - - var body: some View { NavigationSplitView(columnVisibility: $columnVisibility) { @@ -124,7 +137,12 @@ struct NodeList: View { Text("Any missed messages will be delivered again.") } } - .searchable(text: nodesQuery, prompt: "Find a node") + .searchable(text: $searchState.searchText, placement: nodes.count > 10 ? .navigationBarDrawer(displayMode: .always) : .automatic, prompt: "Find a node") + .searchScopes($searchState.searchScope) { + ForEach(NodeSearchState.SearchScopes.allCases) { scope in + Text(scope.title).tag(scope) + } + } .navigationTitle(String.localizedStringWithFormat("nodes %@".localized, String(nodes.count))) .listStyle(.plain) .confirmationDialog( @@ -195,33 +213,51 @@ struct NodeList: View { } .navigationSplitViewStyle(.balanced) -// .onChange(of: selectedNode) { _ in -// if selectedNode == nil { -// columnVisibility = .all -// } else { -// columnVisibility = .doubleColumn -// } -// } + .onChange(of: searchState.searchText) { _ in + runSearch() + } + .onChange(of: searchState.searchScope) { _ in + runSearch() + } .onAppear { if self.bleManager.context == nil { self.bleManager.context = context } } - -// } detail: { -// VStack { -// Button("Detail Only") { -// columnVisibility = .detailOnly -// } -// -// Button("Content and Detail") { -// columnVisibility = .doubleColumn -// } -// -// Button("Show All") { -// columnVisibility = .all -// } -// } -// } + } + + private func runSearch() { + /// Case Insensitive Search Text Predicates + var searchPredicates = ["user.userId", "user.hwModel", "user.longName", "user.shortName"].map { property in + return NSPredicate(format: "%K CONTAINS[c] %@", property, searchState.searchText) + } + /// Create a compound predicate using each text search preicate as an OR + let textSearchPredicate = NSCompoundPredicate(type: .or, subpredicates: searchPredicates) + + /// Set the predicate to nil if the search string is empty + if searchState.searchText.isEmpty { + nodes.nsPredicate = nil + return + } + + /// Add a predicate for the search scope if selected + if searchState.searchScope != .all { + + if searchState.searchScope == .lora { + let loraPredicate = NSPredicate(format: "viaMqtt == NO") + let scopePredicate = NSCompoundPredicate(type: .and, subpredicates: [loraPredicate]) + nodes.nsPredicate = NSCompoundPredicate(type: .and, subpredicates: [textSearchPredicate, scopePredicate]) + return + + } else if searchState.searchScope == .mqtt { + let mqttPredicate = NSPredicate(format: "viaMqtt == YES") + let scopePredicate = NSCompoundPredicate(type: .and, subpredicates: [mqttPredicate]) + nodes.nsPredicate = NSCompoundPredicate(type: .and, subpredicates: [textSearchPredicate, scopePredicate]) + return + } + } else { + /// Use the text search predicate + nodes.nsPredicate = textSearchPredicate + } } } diff --git a/Meshtastic/Views/Nodes/PaxCounterLog.swift b/Meshtastic/Views/Nodes/PaxCounterLog.swift index 3b04d662..b47ba1a1 100644 --- a/Meshtastic/Views/Nodes/PaxCounterLog.swift +++ b/Meshtastic/Views/Nodes/PaxCounterLog.swift @@ -75,8 +75,8 @@ struct PaxCounterLog: View { .chartXAxis(.automatic) .chartYScale(domain: 0...maxValue) .chartForegroundStyleScale([ - "paxcounter.ble": .blue, - "paxcounter.wifi": .orange, + "paxcounter.ble".localized: .blue, + "paxcounter.wifi".localized: .orange, "paxcounter.total".localized: .green ]) .chartLegend(position: .automatic, alignment: .bottom) From 1db42dbbcdaa89580888e76cb1d423c9d7d9109d Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sat, 2 Mar 2024 10:18:57 -0800 Subject: [PATCH 03/13] Add exchange position node list menu item, add improved search to the user list. --- Meshtastic/Helpers/BLEManager.swift | 3 - Meshtastic/Views/Messages/UserList.swift | 167 ++++++++++++----------- Meshtastic/Views/Nodes/NodeList.swift | 33 ++++- 3 files changed, 115 insertions(+), 88 deletions(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 163e19a3..cf663f10 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -1039,9 +1039,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } catch { return false } - return false - - var meshPacket = MeshPacket() meshPacket.to = UInt32(destNum) diff --git a/Meshtastic/Views/Messages/UserList.swift b/Meshtastic/Views/Messages/UserList.swift index e4fa4b55..542e7650 100644 --- a/Meshtastic/Views/Messages/UserList.swift +++ b/Meshtastic/Views/Messages/UserList.swift @@ -17,12 +17,19 @@ struct UserList: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager @State private var searchText = "" + var usersQuery: Binding { Binding { searchText } set: { newValue in searchText = newValue - users.nsPredicate = newValue.isEmpty ? nil : NSPredicate(format: "longName CONTAINS[c] %@ OR shortName CONTAINS[c] %@", newValue, newValue) + /// Case Insensitive Search Text Predicates + let searchPredicates = ["userId", "hwModel", "longName", "shortName"].map { property in + return NSPredicate(format: "%K CONTAINS[c] %@", property, searchText) + } + /// Create a compound predicate using each text search predicate as an OR + let textSearchPredicate = NSCompoundPredicate(type: .or, subpredicates: searchPredicates) + users.nsPredicate = newValue.isEmpty ? nil : textSearchPredicate } } @FetchRequest( @@ -48,94 +55,94 @@ struct UserList: View { let lastMessageDay = Calendar.current.dateComponents([.day], from: lastMessageTime).day ?? 0 let currentDay = Calendar.current.dateComponents([.day], from: Date()).day ?? 0 if user.num != bleManager.connectedPeripheral?.num ?? 0 { - NavigationLink(destination: UserMessageList(user: user)) { - ZStack { - Image(systemName: "circle.fill") - .opacity(user.unreadMessages > 0 ? 1 : 0) - .font(.system(size: 10)) - .foregroundColor(.accentColor) - .brightness(0.2) - } - - CircleText(text: user.shortName ?? "?", color: Color(UIColor(hex: UInt32(user.num)))) - - VStack(alignment: .leading){ - HStack{ - Text(user.longName ?? "unknown".localized) - .font(.headline) - Spacer() - if user.vip { - Image(systemName: "star.fill") - .foregroundColor(.yellow) + NavigationLink(destination: UserMessageList(user: user)) { + ZStack { + Image(systemName: "circle.fill") + .opacity(user.unreadMessages > 0 ? 1 : 0) + .font(.system(size: 10)) + .foregroundColor(.accentColor) + .brightness(0.2) + } + + CircleText(text: user.shortName ?? "?", color: Color(UIColor(hex: UInt32(user.num)))) + + VStack(alignment: .leading){ + HStack{ + Text(user.longName ?? "unknown".localized) + .font(.headline) + Spacer() + if user.vip { + Image(systemName: "star.fill") + .foregroundColor(.yellow) + } + if user.messageList.count > 0 { + if lastMessageDay == currentDay { + Text(lastMessageTime, style: .time ) + .font(.footnote) + .foregroundColor(.secondary) + } else if lastMessageDay == (currentDay - 1) { + Text("Yesterday") + .font(.footnote) + .foregroundColor(.secondary) + } else if lastMessageDay < (currentDay - 1) && lastMessageDay > (currentDay - 5) { + Text(lastMessageTime.formattedDate(format: dateFormatString)) + .font(.footnote) + .foregroundColor(.secondary) + } else if lastMessageDay < (currentDay - 1800) { + Text(lastMessageTime.formattedDate(format: dateFormatString)) + .font(.footnote) + .foregroundColor(.secondary) + } + } } + if user.messageList.count > 0 { - if lastMessageDay == currentDay { - Text(lastMessageTime, style: .time ) - .font(.footnote) - .foregroundColor(.secondary) - } else if lastMessageDay == (currentDay - 1) { - Text("Yesterday") - .font(.footnote) - .foregroundColor(.secondary) - } else if lastMessageDay < (currentDay - 1) && lastMessageDay > (currentDay - 5) { - Text(lastMessageTime.formattedDate(format: dateFormatString)) - .font(.footnote) - .foregroundColor(.secondary) - } else if lastMessageDay < (currentDay - 1800) { - Text(lastMessageTime.formattedDate(format: dateFormatString)) + HStack(alignment: .top) { + Text("\(mostRecent != nil ? mostRecent!.messagePayload! : " ")") .font(.footnote) .foregroundColor(.secondary) } } } - - if user.messageList.count > 0 { - HStack(alignment: .top) { - Text("\(mostRecent != nil ? mostRecent!.messagePayload! : " ")") - .font(.footnote) - .foregroundColor(.secondary) + } + .frame(height: 62) + .contextMenu { + Button { + user.vip = !user.vip + do { + try context.save() + } catch { + context.rollback() + print("💥 Save User VIP Error") + } + } label: { + Label(user.vip ? "Un-Favorite" : "Favorite", systemImage: user.vip ? "star.slash.fill" : "star.fill") + } + Button { + user.mute = !user.mute + do { + try context.save() + } catch { + context.rollback() + print("💥 Save User Mute Error") + } + } label: { + Label(user.mute ? "Show Alerts" : "Hide Alerts", systemImage: user.mute ? "bell" : "bell.slash") + } + if user.messageList.count > 0 { + Button(role: .destructive) { + isPresentingDeleteUserMessagesConfirm = true + userSelection = user + } label: { + Label("Delete Messages", systemImage: "trash") } } } - } - .frame(height: 62) - .contextMenu { - Button { - user.vip = !user.vip - do { - try context.save() - } catch { - context.rollback() - print("💥 Save User VIP Error") - } - } label: { - Label(user.vip ? "Un-Favorite" : "Favorite", systemImage: user.vip ? "star.slash.fill" : "star.fill") - } - Button { - user.mute = !user.mute - do { - try context.save() - } catch { - context.rollback() - print("💥 Save User Mute Error") - } - } label: { - Label(user.mute ? "Show Alerts" : "Hide Alerts", systemImage: user.mute ? "bell" : "bell.slash") - } - if user.messageList.count > 0 { - Button(role: .destructive) { - isPresentingDeleteUserMessagesConfirm = true - userSelection = user - } label: { - Label("Delete Messages", systemImage: "trash") - } - } - } - .confirmationDialog( - "This conversation will be deleted.", - isPresented: $isPresentingDeleteUserMessagesConfirm, - titleVisibility: .visible - ) { + .confirmationDialog( + "This conversation will be deleted.", + isPresented: $isPresentingDeleteUserMessagesConfirm, + titleVisibility: .visible + ) { Button(role: .destructive) { deleteUserMessages(user: userSelection!, context: context) context.refresh(node!.user!, mergeChanges: true) @@ -149,7 +156,7 @@ struct UserList: View { } .listStyle(.plain) .navigationTitle(String.localizedStringWithFormat("contacts %@".localized, String(users.count == 0 ? 0 : users.count - 1))) - .searchable(text: usersQuery, prompt: "Find a contact") + .searchable(text: usersQuery, placement: users.count > 10 ? .navigationBarDrawer(displayMode: .always) : .automatic, prompt: "Find a contact") } } } diff --git a/Meshtastic/Views/Nodes/NodeList.swift b/Meshtastic/Views/Nodes/NodeList.swift index 569b6651..122f8f10 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -36,6 +36,7 @@ struct NodeList: View { @State private var isPresentingTraceRouteSentAlert = false @State private var isPresentingClientHistorySentAlert = false @State private var isPresentingDeleteNodeAlert = false + @State private var isPresentingPositionSentAlert = false @State private var deleteNodeId: Int64 = 0 @State private var searchState = NodeSearchState() @@ -89,7 +90,21 @@ struct NodeList: View { } label: { Label(node.user!.mute ? "Show Alerts" : "Hide Alerts", systemImage: node.user!.mute ? "bell" : "bell.slash") } - if connectedNodeNum != node.num { + if bleManager.connectedPeripheral != nil { + Button { + let positionSent = bleManager.sendPosition( + channel: node.channel, + destNum: node.num, + wantResponse: true + ) + if positionSent { + isPresentingPositionSentAlert = true + } + } label: { + Label("Exchange Positions", systemImage: "arrow.triangle.2.circlepath") + } + } + if bleManager.connectedPeripheral != nil && connectedNodeNum != node.num { Button { let success = bleManager.sendTraceRouteRequest(destNum: node.user?.num ?? 0, wantResponse: true) if success { @@ -120,6 +135,14 @@ struct NodeList: View { } } } + .alert( + "Position Sent", + isPresented: $isPresentingPositionSentAlert + ) { + Button("OK", role: .cancel) { } + } message: { + Text("Your position has been sent with a request for a response with their position.") + } .alert( "Trace Route Sent", isPresented: $isPresentingTraceRouteSentAlert @@ -214,10 +237,10 @@ struct NodeList: View { } .navigationSplitViewStyle(.balanced) .onChange(of: searchState.searchText) { _ in - runSearch() + searchNodeList() } .onChange(of: searchState.searchScope) { _ in - runSearch() + searchNodeList() } .onAppear { if self.bleManager.context == nil { @@ -226,9 +249,9 @@ struct NodeList: View { } } - private func runSearch() { + private func searchNodeList() { /// Case Insensitive Search Text Predicates - var searchPredicates = ["user.userId", "user.hwModel", "user.longName", "user.shortName"].map { property in + let searchPredicates = ["user.userId", "user.hwModel", "user.longName", "user.shortName"].map { property in return NSPredicate(format: "%K CONTAINS[c] %@", property, searchState.searchText) } /// Create a compound predicate using each text search preicate as an OR From b22ca2db81dc24851dc187dad1345663ccea3790 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sat, 2 Mar 2024 16:38:59 -0800 Subject: [PATCH 04/13] =?UTF-8?q?Hops=20away!=20=F0=9F=90=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Meshtastic.xcodeproj/project.pbxproj | 4 +- Meshtastic/Helpers/MeshPackets.swift | 2 +- .../Meshtastic.xcdatamodeld/.xccurrentversion | 2 +- .../contents | 3 +- .../contents | 449 ++++++++++++++++++ .../Views/Nodes/Helpers/NodeListItem.swift | 17 +- 6 files changed, 470 insertions(+), 7 deletions(-) create mode 100644 Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 29.xcdatamodel/contents diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 156262d1..8e1b5c2b 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -294,6 +294,7 @@ DD3501882852FC3B000FC853 /* Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = ""; }; DD3619132B1EE20700C41C8C /* MeshtasticDataModelV21.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV21.xcdatamodel; sourceTree = ""; }; DD3619142B1EF9F900C41C8C /* LocationsHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationsHandler.swift; sourceTree = ""; }; + DD398EBD2B93F640002B4C51 /* MeshtasticDataModelV 29.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 29.xcdatamodel"; sourceTree = ""; }; DD3CC6B428E33FD100FA9159 /* ShareChannels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareChannels.swift; sourceTree = ""; }; DD3CC6BB28E366DF00FA9159 /* MeshtasticDataModel.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModel.xcdatamodel; sourceTree = ""; }; DD3CC6BD28E4CD9800FA9159 /* BatteryGauge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryGauge.swift; sourceTree = ""; }; @@ -1883,6 +1884,7 @@ DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */ = { isa = XCVersionGroup; children = ( + DD398EBD2B93F640002B4C51 /* MeshtasticDataModelV 29.xcdatamodel */, DD0E20FF2B892E1300F2D100 /* MeshtasticDataModelV 28.xcdatamodel */, D93069062B81D8900066FBC8 /* MeshtasticDataModelV 27.xcdatamodel */, DD05296F2B77F454008E44CD /* MeshtasticDataModelV 26.xcdatamodel */, @@ -1912,7 +1914,7 @@ DD5D0A9A2931AD6B00F7EA61 /* MeshtasticDataModelV2.xcdatamodel */, DD3CC6BB28E366DF00FA9159 /* MeshtasticDataModel.xcdatamodel */, ); - currentVersion = DD0E20FF2B892E1300F2D100 /* MeshtasticDataModelV 28.xcdatamodel */; + currentVersion = DD398EBD2B93F640002B4C51 /* MeshtasticDataModelV 29.xcdatamodel */; name = Meshtastic.xcdatamodeld; path = Meshtastic/Meshtastic.xcdatamodeld; sourceTree = ""; diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index aeb39c3a..c02835e7 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -775,7 +775,7 @@ func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManage func textMessageAppPacket(packet: MeshPacket, wantRangeTestPackets: Bool, connectedNode: Int64, storeForward: Bool = false, context: NSManagedObjectContext) { var messageText = String(bytes: packet.decoded.payload, encoding: .utf8) - if !wantRangeTestPackets && (String(messageText ?? "").starts(with: "seq ")) { + if !wantRangeTestPackets && (String(messageText ?? "seq ").starts(with: "seq ")) { return } var storeForwardBroadcast = false diff --git a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion index 37d79244..a647b881 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion +++ b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion @@ -3,6 +3,6 @@ _XCCurrentVersionName - MeshtasticDataModelV 28.xcdatamodel + MeshtasticDataModelV 29.xcdatamodel diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 28.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 28.xcdatamodel/contents index 7c468a65..348b7fea 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 28.xcdatamodel/contents +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 28.xcdatamodel/contents @@ -230,7 +230,8 @@ - + + diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 29.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 29.xcdatamodel/contents new file mode 100644 index 00000000..aed0e3e0 --- /dev/null +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 29.xcdatamodel/contents @@ -0,0 +1,449 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift b/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift index 053622ae..67922046 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift @@ -23,6 +23,16 @@ struct NodeListItem: View { VStack(alignment: .leading) { CircleText(text: node.user?.shortName ?? "?", color: Color(UIColor(hex: UInt32(node.num))), circleSize: 70) .padding(.trailing, 5) + if node.hopsAway == 0 { + HStack { + Image(systemName: "hare") + .font(.callout) + .symbolRenderingMode(.hierarchical) + Image(systemName: "\(node.hopsAway).square") + .font(.title2) + .symbolRenderingMode(.hierarchical) + } + } BatteryLevelCompact(node: node, font: .caption, iconFont: .callout, color: .accentColor) .padding(.trailing, 5) } @@ -118,6 +128,7 @@ struct NodeListItem: View { } } HStack { + if node.channel > 0 { Image(systemName: "fibrechannel") .font(.callout) @@ -180,12 +191,12 @@ struct NodeListItem: View { } } } - if !connected { - HStack { + if !node.viaMqtt && connectedNode != node.num { + HStack (alignment: .bottom) { let preset = ModemPresets(rawValue: Int(modemPreset)) LoRaSignalStrengthMeter(snr: node.snr, rssi: node.rssi, preset: preset ?? ModemPresets.longFast, compact: true) - } + .padding(.top) } } .frame(maxWidth: .infinity, alignment: .leading) From 2ba208fa8ebb8fd28f3896070b024d1f8fa40af5 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sat, 9 Mar 2024 20:07:12 -0800 Subject: [PATCH 05/13] Bump version and protos --- Meshtastic.xcodeproj/project.pbxproj | 8 +- Meshtastic/Persistence/UpdateCoreData.swift | 2 + .../Protobufs/meshtastic/deviceonly.pb.swift | 30 +++ Meshtastic/Protobufs/meshtastic/mesh.pb.swift | 229 +++++++++++++----- .../Protobufs/meshtastic/portnums.pb.swift | 10 +- .../Protobufs/meshtastic/telemetry.pb.swift | 8 + Meshtastic/Views/Messages/UserList.swift | 1 + .../Views/Nodes/Helpers/NodeListItem.swift | 47 ++-- Meshtastic/Views/Nodes/MeshMap.swift | 1 + Meshtastic/Views/Nodes/NodeList.swift | 1 + Meshtastic/Views/Nodes/PaxCounterLog.swift | 10 +- Meshtastic/Views/Settings/Channels.swift | 2 +- protobufs | 2 +- 13 files changed, 250 insertions(+), 101 deletions(-) diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 8e1b5c2b..4cd9deb0 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -1584,7 +1584,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.2.27; + MARKETING_VERSION = 2.3.0; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; @@ -1618,7 +1618,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.2.27; + MARKETING_VERSION = 2.3.0; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; @@ -1740,7 +1740,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.2.27; + MARKETING_VERSION = 2.3.0; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1773,7 +1773,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.2.27; + MARKETING_VERSION = 2.3.0; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/Meshtastic/Persistence/UpdateCoreData.swift b/Meshtastic/Persistence/UpdateCoreData.swift index 341004f7..cdd0d91e 100644 --- a/Meshtastic/Persistence/UpdateCoreData.swift +++ b/Meshtastic/Persistence/UpdateCoreData.swift @@ -147,6 +147,7 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) newNode.channel = Int32(packet.channel) if let nodeInfoMessage = try? NodeInfo(serializedData: packet.decoded.payload) { newNode.channel = Int32(nodeInfoMessage.channel) + newNode.hopsAway = Int32(truncatingIfNeeded: nodeInfoMessage.hopsAway) } if let newUserMessage = try? User(serializedData: packet.decoded.payload) { @@ -215,6 +216,7 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) if let nodeInfoMessage = try? NodeInfo(serializedData: packet.decoded.payload) { fetchedNode[0].channel = Int32(nodeInfoMessage.channel) + fetchedNode[0].hopsAway = Int32(truncatingIfNeeded: nodeInfoMessage.hopsAway) if nodeInfoMessage.hasDeviceMetrics { let telemetry = TelemetryEntity(context: context) telemetry.batteryLevel = Int32(nodeInfoMessage.deviceMetrics.batteryLevel) diff --git a/Meshtastic/Protobufs/meshtastic/deviceonly.pb.swift b/Meshtastic/Protobufs/meshtastic/deviceonly.pb.swift index 821b9370..048c99aa 100644 --- a/Meshtastic/Protobufs/meshtastic/deviceonly.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/deviceonly.pb.swift @@ -254,6 +254,20 @@ struct NodeInfoLite { set {_uniqueStorage()._channel = newValue} } + /// + /// True if we witnessed the node over MQTT instead of LoRA transport + var viaMqtt: Bool { + get {return _storage._viaMqtt} + set {_uniqueStorage()._viaMqtt = newValue} + } + + /// + /// Number of hops away from us this node is (0 if adjacent) + var hopsAway: UInt32 { + get {return _storage._hopsAway} + set {_uniqueStorage()._hopsAway = newValue} + } + var unknownFields = SwiftProtobuf.UnknownStorage() init() {} @@ -583,6 +597,8 @@ extension NodeInfoLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat 5: .standard(proto: "last_heard"), 6: .standard(proto: "device_metrics"), 7: .same(proto: "channel"), + 8: .standard(proto: "via_mqtt"), + 9: .standard(proto: "hops_away"), ] fileprivate class _StorageClass { @@ -593,6 +609,8 @@ extension NodeInfoLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat var _lastHeard: UInt32 = 0 var _deviceMetrics: DeviceMetrics? = nil var _channel: UInt32 = 0 + var _viaMqtt: Bool = false + var _hopsAway: UInt32 = 0 static let defaultInstance = _StorageClass() @@ -606,6 +624,8 @@ extension NodeInfoLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat _lastHeard = source._lastHeard _deviceMetrics = source._deviceMetrics _channel = source._channel + _viaMqtt = source._viaMqtt + _hopsAway = source._hopsAway } } @@ -631,6 +651,8 @@ extension NodeInfoLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat case 5: try { try decoder.decodeSingularFixed32Field(value: &_storage._lastHeard) }() case 6: try { try decoder.decodeSingularMessageField(value: &_storage._deviceMetrics) }() case 7: try { try decoder.decodeSingularUInt32Field(value: &_storage._channel) }() + case 8: try { try decoder.decodeSingularBoolField(value: &_storage._viaMqtt) }() + case 9: try { try decoder.decodeSingularUInt32Field(value: &_storage._hopsAway) }() default: break } } @@ -664,6 +686,12 @@ extension NodeInfoLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat if _storage._channel != 0 { try visitor.visitSingularUInt32Field(value: _storage._channel, fieldNumber: 7) } + if _storage._viaMqtt != false { + try visitor.visitSingularBoolField(value: _storage._viaMqtt, fieldNumber: 8) + } + if _storage._hopsAway != 0 { + try visitor.visitSingularUInt32Field(value: _storage._hopsAway, fieldNumber: 9) + } } try unknownFields.traverse(visitor: &visitor) } @@ -680,6 +708,8 @@ extension NodeInfoLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat if _storage._lastHeard != rhs_storage._lastHeard {return false} if _storage._deviceMetrics != rhs_storage._deviceMetrics {return false} if _storage._channel != rhs_storage._channel {return false} + if _storage._viaMqtt != rhs_storage._viaMqtt {return false} + if _storage._hopsAway != rhs_storage._hopsAway {return false} return true } if !storagesAreEqual {return false} diff --git a/Meshtastic/Protobufs/meshtastic/mesh.pb.swift b/Meshtastic/Protobufs/meshtastic/mesh.pb.swift index caa1f1bc..bdc8da97 100644 --- a/Meshtastic/Protobufs/meshtastic/mesh.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/mesh.pb.swift @@ -1434,8 +1434,7 @@ struct MeshPacket { } /// - /// The (immediatSee Priority description for more details.y should be fixed32 instead, this encoding only - /// hurts the ble link though. + /// The (immediate) destination for this packet var to: UInt32 { get {return _storage._to} set {_uniqueStorage()._to = newValue} @@ -1566,6 +1565,14 @@ struct MeshPacket { set {_uniqueStorage()._viaMqtt = newValue} } + /// + /// Hop limit with which the original packet started. Sent via LoRa using three bits in the unencrypted header. + /// When receiving a packet, the difference between hop_start and hop_limit gives how many hops it traveled. + var hopStart: UInt32 { + get {return _storage._hopStart} + set {_uniqueStorage()._hopStart = newValue} + } + var unknownFields = SwiftProtobuf.UnknownStorage() enum OneOf_PayloadVariant: Equatable { @@ -1779,62 +1786,86 @@ struct NodeInfo { /// /// The node number - var num: UInt32 = 0 + var num: UInt32 { + get {return _storage._num} + set {_uniqueStorage()._num = newValue} + } /// /// The user info for this node var user: User { - get {return _user ?? User()} - set {_user = newValue} + get {return _storage._user ?? User()} + set {_uniqueStorage()._user = newValue} } /// Returns true if `user` has been explicitly set. - var hasUser: Bool {return self._user != nil} + var hasUser: Bool {return _storage._user != nil} /// Clears the value of `user`. Subsequent reads from it will return its default value. - mutating func clearUser() {self._user = nil} + mutating func clearUser() {_uniqueStorage()._user = nil} /// /// This position data. Note: before 1.2.14 we would also store the last time we've heard from this node in position.time, that is no longer true. /// Position.time now indicates the last time we received a POSITION from that node. var position: Position { - get {return _position ?? Position()} - set {_position = newValue} + get {return _storage._position ?? Position()} + set {_uniqueStorage()._position = newValue} } /// Returns true if `position` has been explicitly set. - var hasPosition: Bool {return self._position != nil} + var hasPosition: Bool {return _storage._position != nil} /// Clears the value of `position`. Subsequent reads from it will return its default value. - mutating func clearPosition() {self._position = nil} + mutating func clearPosition() {_uniqueStorage()._position = nil} /// /// Returns the Signal-to-noise ratio (SNR) of the last received message, /// as measured by the receiver. Return SNR of the last received message in dB - var snr: Float = 0 + var snr: Float { + get {return _storage._snr} + set {_uniqueStorage()._snr = newValue} + } /// /// Set to indicate the last time we received a packet from this node - var lastHeard: UInt32 = 0 + var lastHeard: UInt32 { + get {return _storage._lastHeard} + set {_uniqueStorage()._lastHeard = newValue} + } /// /// The latest device metrics for the node. var deviceMetrics: DeviceMetrics { - get {return _deviceMetrics ?? DeviceMetrics()} - set {_deviceMetrics = newValue} + get {return _storage._deviceMetrics ?? DeviceMetrics()} + set {_uniqueStorage()._deviceMetrics = newValue} } /// Returns true if `deviceMetrics` has been explicitly set. - var hasDeviceMetrics: Bool {return self._deviceMetrics != nil} + var hasDeviceMetrics: Bool {return _storage._deviceMetrics != nil} /// Clears the value of `deviceMetrics`. Subsequent reads from it will return its default value. - mutating func clearDeviceMetrics() {self._deviceMetrics = nil} + mutating func clearDeviceMetrics() {_uniqueStorage()._deviceMetrics = nil} /// /// local channel index we heard that node on. Only populated if its not the default channel. - var channel: UInt32 = 0 + var channel: UInt32 { + get {return _storage._channel} + set {_uniqueStorage()._channel = newValue} + } + + /// + /// True if we witnessed the node over MQTT instead of LoRA transport + var viaMqtt: Bool { + get {return _storage._viaMqtt} + set {_uniqueStorage()._viaMqtt = newValue} + } + + /// + /// Number of hops away from us this node is (0 if adjacent) + var hopsAway: UInt32 { + get {return _storage._hopsAway} + set {_uniqueStorage()._hopsAway = newValue} + } var unknownFields = SwiftProtobuf.UnknownStorage() init() {} - fileprivate var _user: User? = nil - fileprivate var _position: Position? = nil - fileprivate var _deviceMetrics: DeviceMetrics? = nil + fileprivate var _storage = _StorageClass.defaultInstance } /// @@ -3369,6 +3400,7 @@ extension MeshPacket: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio 12: .standard(proto: "rx_rssi"), 13: .same(proto: "delayed"), 14: .standard(proto: "via_mqtt"), + 15: .standard(proto: "hop_start"), ] fileprivate class _StorageClass { @@ -3385,6 +3417,7 @@ extension MeshPacket: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio var _rxRssi: Int32 = 0 var _delayed: MeshPacket.Delayed = .noDelay var _viaMqtt: Bool = false + var _hopStart: UInt32 = 0 static let defaultInstance = _StorageClass() @@ -3404,6 +3437,7 @@ extension MeshPacket: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio _rxRssi = source._rxRssi _delayed = source._delayed _viaMqtt = source._viaMqtt + _hopStart = source._hopStart } } @@ -3455,6 +3489,7 @@ extension MeshPacket: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio case 12: try { try decoder.decodeSingularInt32Field(value: &_storage._rxRssi) }() case 13: try { try decoder.decodeSingularEnumField(value: &_storage._delayed) }() case 14: try { try decoder.decodeSingularBoolField(value: &_storage._viaMqtt) }() + case 15: try { try decoder.decodeSingularUInt32Field(value: &_storage._hopStart) }() default: break } } @@ -3514,6 +3549,9 @@ extension MeshPacket: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio if _storage._viaMqtt != false { try visitor.visitSingularBoolField(value: _storage._viaMqtt, fieldNumber: 14) } + if _storage._hopStart != 0 { + try visitor.visitSingularUInt32Field(value: _storage._hopStart, fieldNumber: 15) + } } try unknownFields.traverse(visitor: &visitor) } @@ -3536,6 +3574,7 @@ extension MeshPacket: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio if _storage._rxRssi != rhs_storage._rxRssi {return false} if _storage._delayed != rhs_storage._delayed {return false} if _storage._viaMqtt != rhs_storage._viaMqtt {return false} + if _storage._hopStart != rhs_storage._hopStart {return false} return true } if !storagesAreEqual {return false} @@ -3575,63 +3614,123 @@ extension NodeInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB 5: .standard(proto: "last_heard"), 6: .standard(proto: "device_metrics"), 7: .same(proto: "channel"), + 8: .standard(proto: "via_mqtt"), + 9: .standard(proto: "hops_away"), ] + fileprivate class _StorageClass { + var _num: UInt32 = 0 + var _user: User? = nil + var _position: Position? = nil + var _snr: Float = 0 + var _lastHeard: UInt32 = 0 + var _deviceMetrics: DeviceMetrics? = nil + var _channel: UInt32 = 0 + var _viaMqtt: Bool = false + var _hopsAway: UInt32 = 0 + + static let defaultInstance = _StorageClass() + + private init() {} + + init(copying source: _StorageClass) { + _num = source._num + _user = source._user + _position = source._position + _snr = source._snr + _lastHeard = source._lastHeard + _deviceMetrics = source._deviceMetrics + _channel = source._channel + _viaMqtt = source._viaMqtt + _hopsAway = source._hopsAway + } + } + + fileprivate mutating func _uniqueStorage() -> _StorageClass { + if !isKnownUniquelyReferenced(&_storage) { + _storage = _StorageClass(copying: _storage) + } + return _storage + } + mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularUInt32Field(value: &self.num) }() - case 2: try { try decoder.decodeSingularMessageField(value: &self._user) }() - case 3: try { try decoder.decodeSingularMessageField(value: &self._position) }() - case 4: try { try decoder.decodeSingularFloatField(value: &self.snr) }() - case 5: try { try decoder.decodeSingularFixed32Field(value: &self.lastHeard) }() - case 6: try { try decoder.decodeSingularMessageField(value: &self._deviceMetrics) }() - case 7: try { try decoder.decodeSingularUInt32Field(value: &self.channel) }() - default: break + _ = _uniqueStorage() + try withExtendedLifetime(_storage) { (_storage: _StorageClass) in + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularUInt32Field(value: &_storage._num) }() + case 2: try { try decoder.decodeSingularMessageField(value: &_storage._user) }() + case 3: try { try decoder.decodeSingularMessageField(value: &_storage._position) }() + case 4: try { try decoder.decodeSingularFloatField(value: &_storage._snr) }() + case 5: try { try decoder.decodeSingularFixed32Field(value: &_storage._lastHeard) }() + case 6: try { try decoder.decodeSingularMessageField(value: &_storage._deviceMetrics) }() + case 7: try { try decoder.decodeSingularUInt32Field(value: &_storage._channel) }() + case 8: try { try decoder.decodeSingularBoolField(value: &_storage._viaMqtt) }() + case 9: try { try decoder.decodeSingularUInt32Field(value: &_storage._hopsAway) }() + default: break + } } } } func traverse(visitor: inout V) throws { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every if/case branch local when no optimizations - // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and - // https://github.com/apple/swift-protobuf/issues/1182 - if self.num != 0 { - try visitor.visitSingularUInt32Field(value: self.num, fieldNumber: 1) - } - try { if let v = self._user { - try visitor.visitSingularMessageField(value: v, fieldNumber: 2) - } }() - try { if let v = self._position { - try visitor.visitSingularMessageField(value: v, fieldNumber: 3) - } }() - if self.snr != 0 { - try visitor.visitSingularFloatField(value: self.snr, fieldNumber: 4) - } - if self.lastHeard != 0 { - try visitor.visitSingularFixed32Field(value: self.lastHeard, fieldNumber: 5) - } - try { if let v = self._deviceMetrics { - try visitor.visitSingularMessageField(value: v, fieldNumber: 6) - } }() - if self.channel != 0 { - try visitor.visitSingularUInt32Field(value: self.channel, fieldNumber: 7) + try withExtendedLifetime(_storage) { (_storage: _StorageClass) in + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + if _storage._num != 0 { + try visitor.visitSingularUInt32Field(value: _storage._num, fieldNumber: 1) + } + try { if let v = _storage._user { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() + try { if let v = _storage._position { + try visitor.visitSingularMessageField(value: v, fieldNumber: 3) + } }() + if _storage._snr != 0 { + try visitor.visitSingularFloatField(value: _storage._snr, fieldNumber: 4) + } + if _storage._lastHeard != 0 { + try visitor.visitSingularFixed32Field(value: _storage._lastHeard, fieldNumber: 5) + } + try { if let v = _storage._deviceMetrics { + try visitor.visitSingularMessageField(value: v, fieldNumber: 6) + } }() + if _storage._channel != 0 { + try visitor.visitSingularUInt32Field(value: _storage._channel, fieldNumber: 7) + } + if _storage._viaMqtt != false { + try visitor.visitSingularBoolField(value: _storage._viaMqtt, fieldNumber: 8) + } + if _storage._hopsAway != 0 { + try visitor.visitSingularUInt32Field(value: _storage._hopsAway, fieldNumber: 9) + } } try unknownFields.traverse(visitor: &visitor) } static func ==(lhs: NodeInfo, rhs: NodeInfo) -> Bool { - if lhs.num != rhs.num {return false} - if lhs._user != rhs._user {return false} - if lhs._position != rhs._position {return false} - if lhs.snr != rhs.snr {return false} - if lhs.lastHeard != rhs.lastHeard {return false} - if lhs._deviceMetrics != rhs._deviceMetrics {return false} - if lhs.channel != rhs.channel {return false} + if lhs._storage !== rhs._storage { + let storagesAreEqual: Bool = withExtendedLifetime((lhs._storage, rhs._storage)) { (_args: (_StorageClass, _StorageClass)) in + let _storage = _args.0 + let rhs_storage = _args.1 + if _storage._num != rhs_storage._num {return false} + if _storage._user != rhs_storage._user {return false} + if _storage._position != rhs_storage._position {return false} + if _storage._snr != rhs_storage._snr {return false} + if _storage._lastHeard != rhs_storage._lastHeard {return false} + if _storage._deviceMetrics != rhs_storage._deviceMetrics {return false} + if _storage._channel != rhs_storage._channel {return false} + if _storage._viaMqtt != rhs_storage._viaMqtt {return false} + if _storage._hopsAway != rhs_storage._hopsAway {return false} + return true + } + if !storagesAreEqual {return false} + } if lhs.unknownFields != rhs.unknownFields {return false} return true } diff --git a/Meshtastic/Protobufs/meshtastic/portnums.pb.swift b/Meshtastic/Protobufs/meshtastic/portnums.pb.swift index ea5ce5bd..937ff635 100644 --- a/Meshtastic/Protobufs/meshtastic/portnums.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/portnums.pb.swift @@ -58,25 +58,25 @@ enum PortNum: SwiftProtobuf.Enum { /// /// The built-in position messaging app. - /// Payload is a [Position](/docs/developers/protobufs/api#position) message + /// Payload is a Position message. /// ENCODING: Protobuf case positionApp // = 3 /// /// The built-in user info app. - /// Payload is a [User](/docs/developers/protobufs/api#user) message + /// Payload is a User message. /// ENCODING: Protobuf case nodeinfoApp // = 4 /// /// Protocol control packets for mesh protocol use. - /// Payload is a [Routing](/docs/developers/protobufs/api#routing) message + /// Payload is a Routing message. /// ENCODING: Protobuf case routingApp // = 5 /// /// Admin control packets. - /// Payload is a [AdminMessage](/docs/developers/protobufs/api#adminmessage) message + /// Payload is a AdminMessage message. /// ENCODING: Protobuf case adminApp // = 6 @@ -90,7 +90,7 @@ enum PortNum: SwiftProtobuf.Enum { /// /// Waypoint payloads. - /// Payload is a [Waypoint](/docs/developers/protobufs/api#waypoint) message + /// Payload is a Waypoint message. /// ENCODING: Protobuf case waypointApp // = 8 diff --git a/Meshtastic/Protobufs/meshtastic/telemetry.pb.swift b/Meshtastic/Protobufs/meshtastic/telemetry.pb.swift index debcff60..72d378bc 100644 --- a/Meshtastic/Protobufs/meshtastic/telemetry.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/telemetry.pb.swift @@ -84,6 +84,10 @@ enum TelemetrySensorType: SwiftProtobuf.Enum { /// /// INA3221 3 Channel Voltage / Current Sensor case ina3221 // = 14 + + /// + /// BMP085/BMP180 High accuracy temperature and pressure (older Version of BMP280) + case bmp085 // = 15 case UNRECOGNIZED(Int) init() { @@ -107,6 +111,7 @@ enum TelemetrySensorType: SwiftProtobuf.Enum { case 12: self = .sht31 case 13: self = .pmsa003I case 14: self = .ina3221 + case 15: self = .bmp085 default: self = .UNRECOGNIZED(rawValue) } } @@ -128,6 +133,7 @@ enum TelemetrySensorType: SwiftProtobuf.Enum { case .sht31: return 12 case .pmsa003I: return 13 case .ina3221: return 14 + case .bmp085: return 15 case .UNRECOGNIZED(let i): return i } } @@ -154,6 +160,7 @@ extension TelemetrySensorType: CaseIterable { .sht31, .pmsa003I, .ina3221, + .bmp085, ] } @@ -450,6 +457,7 @@ extension TelemetrySensorType: SwiftProtobuf._ProtoNameProviding { 12: .same(proto: "SHT31"), 13: .same(proto: "PMSA003I"), 14: .same(proto: "INA3221"), + 15: .same(proto: "BMP085"), ] } diff --git a/Meshtastic/Views/Messages/UserList.swift b/Meshtastic/Views/Messages/UserList.swift index 542e7650..6b4ded5f 100644 --- a/Meshtastic/Views/Messages/UserList.swift +++ b/Meshtastic/Views/Messages/UserList.swift @@ -157,6 +157,7 @@ struct UserList: View { .listStyle(.plain) .navigationTitle(String.localizedStringWithFormat("contacts %@".localized, String(users.count == 0 ? 0 : users.count - 1))) .searchable(text: usersQuery, placement: users.count > 10 ? .navigationBarDrawer(displayMode: .always) : .automatic, prompt: "Find a contact") + .disableAutocorrection(true) } } } diff --git a/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift b/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift index 67922046..b309c925 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift @@ -21,18 +21,9 @@ struct NodeListItem: View { LazyVStack(alignment: .leading) { HStack { VStack(alignment: .leading) { + CircleText(text: node.user?.shortName ?? "?", color: Color(UIColor(hex: UInt32(node.num))), circleSize: 70) .padding(.trailing, 5) - if node.hopsAway == 0 { - HStack { - Image(systemName: "hare") - .font(.callout) - .symbolRenderingMode(.hierarchical) - Image(systemName: "\(node.hopsAway).square") - .font(.title2) - .symbolRenderingMode(.hierarchical) - } - } BatteryLevelCompact(node: node, font: .caption, iconFont: .callout, color: .accentColor) .padding(.trailing, 5) } @@ -128,26 +119,42 @@ struct NodeListItem: View { } } HStack { - - if node.channel > 0 { - Image(systemName: "fibrechannel") - .font(.callout) - .symbolRenderingMode(.hierarchical) - .frame(width: 30) - Text("Channel: \(node.channel)") - .foregroundColor(.gray) - .font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption) + if node.channel >= 0 { + HStack { + Image(systemName: "\(node.channel).circle.fill") + .font(.title2) + .symbolRenderingMode(.hierarchical) + .frame(width: 30) + .foregroundColor(.accentColor) + Text("Channel") + .foregroundColor(.gray) + .font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption) + } } + if node.viaMqtt && connectedNode != node.num { Image(systemName: "network") .symbolRenderingMode(.hierarchical) .font(.callout) .frame(width: 30) - Text("Via MQTT") + Text("MQTT") .foregroundColor(.gray) .font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption) } } + if node.hopsAway > 0 { + HStack { + Image(systemName: "hare") + .font(.callout) + .symbolRenderingMode(.hierarchical) + Text("Hops Away:") + .foregroundColor(.gray) + .font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption) + Image(systemName: "\(node.hopsAway).square") + .font(.title2) + .symbolRenderingMode(.hierarchical) + } + } if node.hasPositions || node.hasEnvironmentMetrics || node.hasDetectionSensorMetrics || node.hasTraceRoutes { HStack { Image(systemName: "scroll") diff --git a/Meshtastic/Views/Nodes/MeshMap.swift b/Meshtastic/Views/Nodes/MeshMap.swift index 1859993a..4394efce 100644 --- a/Meshtastic/Views/Nodes/MeshMap.swift +++ b/Meshtastic/Views/Nodes/MeshMap.swift @@ -123,6 +123,7 @@ struct MeshMap: View { if radius > 0.0 { MapCircle(center: position.coordinate, radius: radius) .foregroundStyle(Color(nodeColor).opacity(0.25)) + .stroke(.white, lineWidth: 2) } } /// Routes diff --git a/Meshtastic/Views/Nodes/NodeList.swift b/Meshtastic/Views/Nodes/NodeList.swift index 122f8f10..8d446b18 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -161,6 +161,7 @@ struct NodeList: View { } } .searchable(text: $searchState.searchText, placement: nodes.count > 10 ? .navigationBarDrawer(displayMode: .always) : .automatic, prompt: "Find a node") + .disableAutocorrection(true) .searchScopes($searchState.searchScope) { ForEach(NodeSearchState.SearchScopes.allCases) { scope in Text(scope.title).tag(scope) diff --git a/Meshtastic/Views/Nodes/PaxCounterLog.swift b/Meshtastic/Views/Nodes/PaxCounterLog.swift index b47ba1a1..df85fc01 100644 --- a/Meshtastic/Views/Nodes/PaxCounterLog.swift +++ b/Meshtastic/Views/Nodes/PaxCounterLog.swift @@ -111,11 +111,11 @@ struct PaxCounterLog: View { } else { ScrollView { let columns = [ - GridItem(.flexible(minimum: 20, maximum: 55), spacing: 0.1), - GridItem(.flexible(minimum: 20, maximum: 55), spacing: 0.1), - GridItem(.flexible(minimum: 20, maximum: 55), spacing: 0.1), - GridItem(.flexible(minimum: 60, maximum: 100), spacing: 0.1), - GridItem(.flexible(minimum: 130, maximum: 200), spacing: 0.1) + GridItem(.flexible(minimum: 20, maximum: 50), spacing: 0.1), + GridItem(.flexible(minimum: 20, maximum: 50), spacing: 0.1), + GridItem(.flexible(minimum: 20, maximum: 50), spacing: 0.1), + GridItem(.flexible(minimum: 60, maximum: 140), spacing: 0.1), + GridItem(.flexible(minimum: 100, maximum: 160), spacing: 0.1) ] LazyVGrid(columns: columns, alignment: .leading, spacing: 1) { GridRow { diff --git a/Meshtastic/Views/Settings/Channels.swift b/Meshtastic/Views/Settings/Channels.swift index ea91d13b..0f5cc6e5 100644 --- a/Meshtastic/Views/Settings/Channels.swift +++ b/Meshtastic/Views/Settings/Channels.swift @@ -268,7 +268,7 @@ struct Channels: View { if !preciseLocation { VStack(alignment: .leading) { - Label("Reduce Precision", systemImage: "location.viewfinder") + Label("Approximate Location", systemImage: "location.slash.circle.fill") Slider( value: $positionPrecision, in: 11...16, diff --git a/protobufs b/protobufs index 52415835..5a97acb1 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 5241583565ccbbb4986180bf4c6eb7f8a0dec285 +Subproject commit 5a97acb17543a10e114675a205e3274a83e721af From 7ecba4cabe0e03a655ce0817cacfad6ec5244e69 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sat, 9 Mar 2024 20:56:40 -0800 Subject: [PATCH 06/13] Disable debug log and serial output if managed mode is on --- Meshtastic/Views/Settings/Config/DeviceConfig.swift | 4 ++++ Meshtastic/Views/Settings/Settings.swift | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Meshtastic/Views/Settings/Config/DeviceConfig.swift b/Meshtastic/Views/Settings/Config/DeviceConfig.swift index 63ebf59b..6e190609 100644 --- a/Meshtastic/Views/Settings/Config/DeviceConfig.swift +++ b/Meshtastic/Views/Settings/Config/DeviceConfig.swift @@ -179,6 +179,10 @@ struct DeviceConfig: View { dc.nodeInfoBroadcastSecs = UInt32(nodeInfoBroadcastSecs) dc.doubleTapAsButtonPress = doubleTapAsButtonPress dc.isManaged = isManaged + if isManaged { + serialEnabled = false + debugLogEnabled = false + } let adminMessageId = bleManager.saveDeviceConfig(config: dc, fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) if adminMessageId > 0 { // Should show a saved successfully alert once I know that to be true diff --git a/Meshtastic/Views/Settings/Settings.swift b/Meshtastic/Views/Settings/Settings.swift index 88ffbf40..b98893e9 100644 --- a/Meshtastic/Views/Settings/Settings.swift +++ b/Meshtastic/Views/Settings/Settings.swift @@ -144,7 +144,7 @@ struct Settings: View { Text("Your region has a \(rc?.dutyCycle ?? 0)% hourly duty cycle, your radio will stop sending packets when it reaches the hourly limit.") .foregroundColor(.orange) .font(.caption) - Text("Limit all periodic broadcasts intervals especially telemetry and position. If you need to increase hops, do it on nodes at the edges, not the ones in the middle. MQTT is not advised when you are duty cycle restricted because the gateway node is then doing all the work.") + Text("Limit all periodic broadcast intervals especially telemetry and position. If you need to increase hops, do it on nodes at the edges, not the ones in the middle. MQTT is not advised when you are duty cycle restricted because the gateway node is then doing all the work.") .font(.caption2) .foregroundColor(.gray) } From 08de61ee71b591026e294ad12c9c19a555223018 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sat, 9 Mar 2024 23:37:00 -0800 Subject: [PATCH 07/13] Node list updates --- Meshtastic/Extensions/UserDefaults.swift | 9 +++++++++ .../Views/Nodes/Helpers/Map/NodeMapSwiftUI.swift | 1 + Meshtastic/Views/Nodes/Helpers/NodeInfoItem.swift | 5 +++-- Meshtastic/Views/Nodes/Helpers/NodeListItem.swift | 10 +--------- Meshtastic/Views/Nodes/NodeList.swift | 3 +-- Meshtastic/Views/Settings/Config/LoRaConfig.swift | 3 +++ 6 files changed, 18 insertions(+), 13 deletions(-) diff --git a/Meshtastic/Extensions/UserDefaults.swift b/Meshtastic/Extensions/UserDefaults.swift index b1b1785b..1963ed4f 100644 --- a/Meshtastic/Extensions/UserDefaults.swift +++ b/Meshtastic/Extensions/UserDefaults.swift @@ -27,6 +27,7 @@ extension UserDefaults { case enableDetectionNotifications case detectionSensorRole case enableSmartPosition + case modemPreset } func reset() { @@ -202,4 +203,12 @@ extension UserDefaults { UserDefaults.standard.set(newValue, forKey: "enableSmartPosition") } } + static var modemPreset: Int { + get { + UserDefaults.standard.integer(forKey: "modemPreset") + } + set { + UserDefaults.standard.set(newValue, forKey: "modemPreset") + } + } } diff --git a/Meshtastic/Views/Nodes/Helpers/Map/NodeMapSwiftUI.swift b/Meshtastic/Views/Nodes/Helpers/Map/NodeMapSwiftUI.swift index 1ce1c603..3511cfdb 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/NodeMapSwiftUI.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/NodeMapSwiftUI.swift @@ -107,6 +107,7 @@ struct NodeMapSwiftUI: View { if radius > 0.0 { MapCircle(center: position.coordinate, radius: radius) .foregroundStyle(Color(nodeColor).opacity(0.25)) + .stroke(.white, lineWidth: 2) } } Annotation(position.latest ? node.user?.shortName ?? "?": "", coordinate: position.coordinate) { diff --git a/Meshtastic/Views/Nodes/Helpers/NodeInfoItem.swift b/Meshtastic/Views/Nodes/Helpers/NodeInfoItem.swift index fb3ce8e4..0b381b9c 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeInfoItem.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeInfoItem.swift @@ -12,6 +12,7 @@ import MapKit struct NodeInfoItem: View { @ObservedObject var node: NodeInfoEntity + var modemPreset: ModemPresets = ModemPresets(rawValue: UserDefaults.modemPreset) ?? ModemPresets.longFast var body: some View { @@ -37,11 +38,11 @@ struct NodeInfoItem: View { if node.snr != 0 && !node.viaMqtt { Divider() VStack(alignment: .center) { - let signalStrength = getLoRaSignalStrength(snr: node.snr, rssi: node.rssi, preset: ModemPresets.longModerate) + let signalStrength = getLoRaSignalStrength(snr: node.snr, rssi: node.rssi, preset: modemPreset) LoRaSignalStrengthIndicator(signalStrength: signalStrength) Text("Signal \(signalStrength.description)").font(.footnote) Text("SNR \(String(format: "%.2f", node.snr))dB") - .foregroundColor(getSnrColor(snr: node.snr, preset: ModemPresets.longModerate)) + .foregroundColor(getSnrColor(snr: node.snr, preset: modemPreset)) .font(.caption2) Text("RSSI \(node.rssi)dB") .foregroundColor(getRssiColor(rssi: node.rssi)) diff --git a/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift b/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift index b309c925..4ffdffca 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift @@ -13,7 +13,6 @@ struct NodeListItem: View { @ObservedObject var node: NodeInfoEntity var connected: Bool var connectedNode: Int64 - var modemPreset: Int var body: some View { @@ -119,7 +118,7 @@ struct NodeListItem: View { } } HStack { - if node.channel >= 0 { + if node.channel > 0 { HStack { Image(systemName: "\(node.channel).circle.fill") .font(.title2) @@ -198,13 +197,6 @@ struct NodeListItem: View { } } } - if !node.viaMqtt && connectedNode != node.num { - HStack (alignment: .bottom) { - let preset = ModemPresets(rawValue: Int(modemPreset)) - LoRaSignalStrengthMeter(snr: node.snr, rssi: node.rssi, preset: preset ?? ModemPresets.longFast, compact: true) - } - .padding(.top) - } } .frame(maxWidth: .infinity, alignment: .leading) } diff --git a/Meshtastic/Views/Nodes/NodeList.swift b/Meshtastic/Views/Nodes/NodeList.swift index 8d446b18..56edacbb 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -62,8 +62,7 @@ struct NodeList: View { NodeListItem(node: node, connected: bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral?.num ?? -1 == node.num, - connectedNode: (bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral?.num ?? -1 : -1), - modemPreset: Int(connectedNode?.loRaConfig?.modemPreset ?? 0)) + connectedNode: (bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral?.num ?? -1 : -1)) .contextMenu { if node.user != nil { Button { diff --git a/Meshtastic/Views/Settings/Config/LoRaConfig.swift b/Meshtastic/Views/Settings/Config/LoRaConfig.swift index eace3897..3ca281c2 100644 --- a/Meshtastic/Views/Settings/Config/LoRaConfig.swift +++ b/Meshtastic/Views/Settings/Config/LoRaConfig.swift @@ -205,6 +205,9 @@ struct LoRaConfig: View { lc.sx126XRxBoostedGain = rxBoostedGain lc.overrideFrequency = overrideFrequency lc.ignoreMqtt = ignoreMqtt + if connectedNode?.num ?? -1 == node?.user?.num ?? 0 { + UserDefaults.modemPreset = modemPreset + } let adminMessageId = bleManager.saveLoRaConfig(config: lc, fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) if adminMessageId > 0 { // Should show a saved successfully alert once I know that to be true From 989b6c41e52ebaffa71585e29ab092259c1430a0 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 10 Mar 2024 00:25:02 -0800 Subject: [PATCH 08/13] Add 1 byte key to channel validation. --- Meshtastic/Views/Settings/Channels.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Meshtastic/Views/Settings/Channels.swift b/Meshtastic/Views/Settings/Channels.swift index 0f5cc6e5..db9c9a03 100644 --- a/Meshtastic/Views/Settings/Channels.swift +++ b/Meshtastic/Views/Settings/Channels.swift @@ -68,6 +68,8 @@ struct Channels: View { channelKeySize = 0 } else if channelKey == "AQ==" { channelKeySize = -1 + } else if channelKey.count == 4 { + channelKeySize = 1 } else if channelKey.count == 24 { channelKeySize = 16 } else if channelKey.count == 32 { @@ -303,7 +305,7 @@ struct Channels: View { } .onAppear { let tempKey = Data(base64Encoded: channelKey) ?? Data() - if tempKey.count == channelKeySize || channelKeySize == -1{ + if tempKey.count == channelKeySize || channelKeySize == -1 { hasValidKey = true } else { From 4fd8efe4b31dbe8f66966f113838ee6a29a8da5b Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 10 Mar 2024 00:34:21 -0800 Subject: [PATCH 09/13] Disasble auto correct on node and user lists --- Meshtastic/Views/Messages/UserList.swift | 2 +- Meshtastic/Views/Nodes/NodeList.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Meshtastic/Views/Messages/UserList.swift b/Meshtastic/Views/Messages/UserList.swift index 6b4ded5f..deab7b0d 100644 --- a/Meshtastic/Views/Messages/UserList.swift +++ b/Meshtastic/Views/Messages/UserList.swift @@ -157,7 +157,7 @@ struct UserList: View { .listStyle(.plain) .navigationTitle(String.localizedStringWithFormat("contacts %@".localized, String(users.count == 0 ? 0 : users.count - 1))) .searchable(text: usersQuery, placement: users.count > 10 ? .navigationBarDrawer(displayMode: .always) : .automatic, prompt: "Find a contact") - .disableAutocorrection(true) + .disableAutocorrection(true) } } } diff --git a/Meshtastic/Views/Nodes/NodeList.swift b/Meshtastic/Views/Nodes/NodeList.swift index 56edacbb..4ae2d1ac 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -160,7 +160,7 @@ struct NodeList: View { } } .searchable(text: $searchState.searchText, placement: nodes.count > 10 ? .navigationBarDrawer(displayMode: .always) : .automatic, prompt: "Find a node") - .disableAutocorrection(true) + .disableAutocorrection(true) .searchScopes($searchState.searchScope) { ForEach(NodeSearchState.SearchScopes.allCases) { scope in Text(scope.title).tag(scope) From 93ac03959b65e08c80bd8aa03678f28008f0a7c8 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 10 Mar 2024 03:30:11 -0700 Subject: [PATCH 10/13] Add mqtt to postion popover --- Meshtastic.xcodeproj/project.pbxproj | 8 ++++---- .../Nodes/Helpers/Map/PositionPopover.swift | 18 +++++++++++++++--- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 4cd9deb0..e54ac7fc 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -1572,7 +1572,7 @@ CODE_SIGN_ENTITLEMENTS = Meshtastic/Meshtastic.entitlements; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 841; DEVELOPMENT_ASSET_PATHS = "\"Meshtastic/Preview Content\""; DEVELOPMENT_TEAM = GCH7VS5Y9R; ENABLE_PREVIEWS = YES; @@ -1606,7 +1606,7 @@ CODE_SIGN_ENTITLEMENTS = Meshtastic/Meshtastic.entitlements; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 841; DEVELOPMENT_ASSET_PATHS = "\"Meshtastic/Preview Content\""; DEVELOPMENT_TEAM = GCH7VS5Y9R; ENABLE_PREVIEWS = YES; @@ -1728,7 +1728,7 @@ CODE_SIGN_ENTITLEMENTS = Widgets/WidgetsExtension.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 840; + CURRENT_PROJECT_VERSION = 841; DEVELOPMENT_TEAM = GCH7VS5Y9R; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Widgets/Info.plist; @@ -1761,7 +1761,7 @@ CODE_SIGN_ENTITLEMENTS = Widgets/WidgetsExtension.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 840; + CURRENT_PROJECT_VERSION = 841; DEVELOPMENT_TEAM = GCH7VS5Y9R; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Widgets/Info.plist; diff --git a/Meshtastic/Views/Nodes/Helpers/Map/PositionPopover.swift b/Meshtastic/Views/Nodes/Helpers/Map/PositionPopover.swift index 9b062da4..b5fc9fd8 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/PositionPopover.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/PositionPopover.swift @@ -130,6 +130,19 @@ struct PositionPopover: View { .frame(width: 35) } .padding(.bottom, 5) + if position.nodePosition?.viaMqtt ?? false { + + Label { + let heading = Measurement(value: degrees.degrees, unit: UnitAngle.degrees) + Text("MQTT") + } icon: { + Image(systemName: "network") + .symbolRenderingMode(.hierarchical) + .frame(width: 35) + .rotationEffect(degrees) + } + .padding(.bottom, 5) + } if let lastLocation = locationsHandler.locationsArray.last { /// Distance if lastLocation.distance(from: CLLocation(latitude: LocationsHandler.DefaultLocation.latitude, longitude: LocationsHandler.DefaultLocation.longitude)) > 0.0 { @@ -181,8 +194,7 @@ struct PositionPopover: View { } BatteryGauge(node: position.nodePosition!) } - let mpInt = Int(position.nodePosition?.loRaConfig?.modemPreset ?? 0) - LoRaSignalStrengthMeter(snr: position.nodePosition?.snr ?? 0.0, rssi: position.nodePosition?.rssi ?? 0, preset: ModemPresets(rawValue: mpInt) ?? ModemPresets.longFast, compact: false) + LoRaSignalStrengthMeter(snr: position.nodePosition?.snr ?? 0.0, rssi: position.nodePosition?.rssi ?? 0, preset: ModemPresets(rawValue: UserDefaults.modemPreset) ?? ModemPresets.longFast, compact: false) Spacer() } } @@ -202,7 +214,7 @@ struct PositionPopover: View { #endif } } - .presentationDetents([.fraction(0.45), .fraction(0.55), .fraction(0.65)]) + .presentationDetents([.fraction(0.55), .fraction(0.65), .fraction(0.75)]) .presentationDragIndicator(.visible) } } From 8a8cd6938300275a190cbefc7cd29e154ae1fcd4 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 10 Mar 2024 15:16:18 -0700 Subject: [PATCH 11/13] Client proxy manage topic update, don't show exchange positons for the connected node --- Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift | 4 ++-- Meshtastic/Views/Nodes/NodeList.swift | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift b/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift index 1196e1b5..ce3bcf49 100644 --- a/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift +++ b/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift @@ -21,7 +21,7 @@ class MqttClientProxyManager { private static let defaultKeepAliveInterval: Int32 = 60 weak var delegate: MqttClientProxyManagerDelegate? var mqttClientProxy: CocoaMQTT? - var topic = "msh/2/c" + var topic = "msh/2/e" var debugLog = false func connectFromConfigSettings(node: NodeInfoEntity) { let defaultServerAddress = "mqtt.meshtastic.org" @@ -41,7 +41,7 @@ class MqttClientProxyManager { let username = node.mqttConfig?.username let password = node.mqttConfig?.password let root = node.mqttConfig?.root?.count ?? 0 > 0 ? node.mqttConfig?.root : "msh" - let prefix = root! + "/2/c" + let prefix = root! + "/2/e" topic = prefix + "/#" let qos = CocoaMQTTQoS(rawValue: UInt8(1))! connect(host: host, port: port, useSsl: useSsl, username: username, password: password, topic: topic, qos: qos, cleanSession: true) diff --git a/Meshtastic/Views/Nodes/NodeList.swift b/Meshtastic/Views/Nodes/NodeList.swift index 4ae2d1ac..81ae069f 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -89,7 +89,7 @@ struct NodeList: View { } label: { Label(node.user!.mute ? "Show Alerts" : "Hide Alerts", systemImage: node.user!.mute ? "bell" : "bell.slash") } - if bleManager.connectedPeripheral != nil { + if bleManager.connectedPeripheral != nil && node.num != connectedNodeNum { Button { let positionSent = bleManager.sendPosition( channel: node.channel, From 61a98a09b9498dd8811df2cc20dcb9fef1e5bbeb Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 10 Mar 2024 20:17:54 -0700 Subject: [PATCH 12/13] Handle topic change keep less positions in memory --- Meshtastic.xcodeproj/project.pbxproj | 4 +- Meshtastic/Extensions/UserDefaults.swift | 9 ++++ Meshtastic/Helpers/BLEManager.swift | 1 + Meshtastic/Helpers/LocationsHandler.swift | 44 +++++++++---------- .../Helpers/Mqtt/MqttClientProxyManager.swift | 9 ++-- Meshtastic/Views/Settings/AppSettings.swift | 6 +++ Meshtastic/Views/Settings/Firmware.swift | 2 +- 7 files changed, 47 insertions(+), 28 deletions(-) diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index e54ac7fc..6cf4d0e2 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -1572,7 +1572,7 @@ CODE_SIGN_ENTITLEMENTS = Meshtastic/Meshtastic.entitlements; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 841; + CURRENT_PROJECT_VERSION = 842; DEVELOPMENT_ASSET_PATHS = "\"Meshtastic/Preview Content\""; DEVELOPMENT_TEAM = GCH7VS5Y9R; ENABLE_PREVIEWS = YES; @@ -1606,7 +1606,7 @@ CODE_SIGN_ENTITLEMENTS = Meshtastic/Meshtastic.entitlements; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 841; + CURRENT_PROJECT_VERSION = 842; DEVELOPMENT_ASSET_PATHS = "\"Meshtastic/Preview Content\""; DEVELOPMENT_TEAM = GCH7VS5Y9R; ENABLE_PREVIEWS = YES; diff --git a/Meshtastic/Extensions/UserDefaults.swift b/Meshtastic/Extensions/UserDefaults.swift index 1963ed4f..39cc9b89 100644 --- a/Meshtastic/Extensions/UserDefaults.swift +++ b/Meshtastic/Extensions/UserDefaults.swift @@ -28,6 +28,7 @@ extension UserDefaults { case detectionSensorRole case enableSmartPosition case modemPreset + case firmwareVersion } func reset() { @@ -211,4 +212,12 @@ extension UserDefaults { UserDefaults.standard.set(newValue, forKey: "modemPreset") } } + static var firmwareVersion: String { + get { + UserDefaults.standard.string(forKey: "firmwareVersion") ?? "0.0.0" + } + set { + UserDefaults.standard.set(newValue, forKey: "firmwareVersion") + } + } } diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index cf663f10..5d85d325 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -582,6 +582,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate nowKnown = true connectedVersion = String(version.dropLast()) appState.firmwareVersion = connectedVersion + UserDefaults.firmwareVersion = connectedVersion } let supportedVersion = connectedVersion == "0.0.0" || self.minimumVersion.compare(connectedVersion, options: .numeric) == .orderedAscending || minimumVersion.compare(connectedVersion, options: .numeric) == .orderedSame if !supportedVersion { diff --git a/Meshtastic/Helpers/LocationsHandler.swift b/Meshtastic/Helpers/LocationsHandler.swift index 82714d62..6ec44b90 100644 --- a/Meshtastic/Helpers/LocationsHandler.swift +++ b/Meshtastic/Helpers/LocationsHandler.swift @@ -60,14 +60,10 @@ import CoreLocation self.isStationary = update.isStationary var locationAdded: Bool - if enableSmartPosition { - locationAdded = addLocation(loc) - //print("Added Location \(self.count): \(loc)") - } else { - locationsArray.append(loc) - locationAdded = true - } - if locationAdded { + locationAdded = addLocation(loc, smartPostion: enableSmartPosition) + if !isRecording && locationAdded { + self.count = 1 + } else if locationAdded && isRecording { self.count += 1 } } @@ -84,19 +80,21 @@ import CoreLocation self.updatesStarted = false } - func addLocation(_ location: CLLocation) -> Bool { - let age = -location.timestamp.timeIntervalSinceNow - if age > 10 { - print("Bad Location \(self.count): Too Old \(age) seconds ago \(location)") - return false - } - if location.horizontalAccuracy < 0 { - print("Bad Location \(self.count): Horizontal Accuracy: \(location.horizontalAccuracy) \(location)") - return false - } - if location.horizontalAccuracy > 25 { - print("Bad Location \(self.count): Horizontal Accuracy: \(location.horizontalAccuracy) \(location)") - return false + func addLocation(_ location: CLLocation, smartPostion: Bool) -> Bool { + if smartPostion { + let age = -location.timestamp.timeIntervalSinceNow + if age > 10 { + print("Bad Location \(self.count): Too Old \(age) seconds ago \(location)") + return false + } + if location.horizontalAccuracy < 0 { + print("Bad Location \(self.count): Horizontal Accuracy: \(location.horizontalAccuracy) \(location)") + return false + } + if location.horizontalAccuracy > 25 { + print("Bad Location \(self.count): Horizontal Accuracy: \(location.horizontalAccuracy) \(location)") + return false + } } if isRecording { if let lastLocation = locationsArray.last { @@ -107,8 +105,10 @@ import CoreLocation elevationGain += gain } } + locationsArray.append(location) + } else { + locationsArray = [location] } - locationsArray.append(location) return true } diff --git a/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift b/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift index ce3bcf49..5a1a33d4 100644 --- a/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift +++ b/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift @@ -21,7 +21,7 @@ class MqttClientProxyManager { private static let defaultKeepAliveInterval: Int32 = 60 weak var delegate: MqttClientProxyManagerDelegate? var mqttClientProxy: CocoaMQTT? - var topic = "msh/2/e" + var topic = "msh" var debugLog = false func connectFromConfigSettings(node: NodeInfoEntity) { let defaultServerAddress = "mqtt.meshtastic.org" @@ -36,13 +36,16 @@ class MqttClientProxyManager { defaultServerPort = Int(fullHost.components(separatedBy: ":")[1]) ?? (useSsl ? 8883 : 1883) } } + let minimumVersion = "2.3.0" + let latestVersion = minimumVersion.compare(UserDefaults.firmwareVersion, options: .numeric) == .orderedSame + if let host = host { let port = defaultServerPort let username = node.mqttConfig?.username let password = node.mqttConfig?.password let root = node.mqttConfig?.root?.count ?? 0 > 0 ? node.mqttConfig?.root : "msh" - let prefix = root! + "/2/e" - topic = prefix + "/#" + let prefix = root! + topic = prefix + (latestVersion ? "/2/e" : "/2/c") + "/#" let qos = CocoaMQTTQoS(rawValue: UInt8(1))! connect(host: host, port: port, useSsl: useSsl, username: username, password: password, topic: topic, qos: qos, cleanSession: true) } diff --git a/Meshtastic/Views/Settings/AppSettings.swift b/Meshtastic/Views/Settings/AppSettings.swift index 9e559b62..cd9ef8f4 100644 --- a/Meshtastic/Views/Settings/AppSettings.swift +++ b/Meshtastic/Views/Settings/AppSettings.swift @@ -24,9 +24,15 @@ struct AppSettings: View { Label("appsettings.provide.location", systemImage: "location.circle.fill") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + Text("Use your phone's gps to provide a location to your node. Must have location access and precise location enabled for Meshtastic in Settings.") + .font(.caption2) + .foregroundColor(.gray) if provideLocation { Toggle(isOn: $enableSmartPosition) { Label("appsettings.smartposition", systemImage: "brain") + Text("Will only send a position to the phone if it is recent and of high horizontal accuracy.") + .font(.caption2) + .foregroundColor(.gray) } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) VStack { diff --git a/Meshtastic/Views/Settings/Firmware.swift b/Meshtastic/Views/Settings/Firmware.swift index 21aa3b82..98b29957 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.2.21" + @State var minimumVersion = "2.3.0" @State var version = "" @State private var currentDevice: DeviceHardware? @State private var latestStable: FirmwareRelease? From c36832586c3b372b4e62502aebe0e7c1d4fa7f7f Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 10 Mar 2024 22:14:46 -0700 Subject: [PATCH 13/13] Scoll to dismiss keyboard for the two searchable lists --- Meshtastic/Views/Messages/UserList.swift | 1 + Meshtastic/Views/Nodes/NodeList.swift | 1 + 2 files changed, 2 insertions(+) diff --git a/Meshtastic/Views/Messages/UserList.swift b/Meshtastic/Views/Messages/UserList.swift index deab7b0d..978e8c08 100644 --- a/Meshtastic/Views/Messages/UserList.swift +++ b/Meshtastic/Views/Messages/UserList.swift @@ -158,6 +158,7 @@ struct UserList: View { .navigationTitle(String.localizedStringWithFormat("contacts %@".localized, String(users.count == 0 ? 0 : users.count - 1))) .searchable(text: usersQuery, placement: users.count > 10 ? .navigationBarDrawer(displayMode: .always) : .automatic, prompt: "Find a contact") .disableAutocorrection(true) + .scrollDismissesKeyboard(.immediately) } } } diff --git a/Meshtastic/Views/Nodes/NodeList.swift b/Meshtastic/Views/Nodes/NodeList.swift index 81ae069f..c2532ad7 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -161,6 +161,7 @@ struct NodeList: View { } .searchable(text: $searchState.searchText, placement: nodes.count > 10 ? .navigationBarDrawer(displayMode: .always) : .automatic, prompt: "Find a node") .disableAutocorrection(true) + .scrollDismissesKeyboard(.immediately) .searchScopes($searchState.searchScope) { ForEach(NodeSearchState.SearchScopes.allCases) { scope in Text(scope.title).tag(scope)