diff --git a/Meshtastic Client.xcodeproj/project.pbxproj b/Meshtastic Client.xcodeproj/project.pbxproj index 90634861..693d02dd 100644 --- a/Meshtastic Client.xcodeproj/project.pbxproj +++ b/Meshtastic Client.xcodeproj/project.pbxproj @@ -738,7 +738,7 @@ CODE_SIGN_ENTITLEMENTS = MeshtasticClient/MeshtasticClient.entitlements; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 13; + CURRENT_PROJECT_VERSION = 14; DEVELOPMENT_ASSET_PATHS = "\"MeshtasticClient/Preview Content\""; DEVELOPMENT_TEAM = GCH7VS5Y9R; ENABLE_PREVIEWS = YES; @@ -769,7 +769,7 @@ CODE_SIGN_ENTITLEMENTS = MeshtasticClient/MeshtasticClient.entitlements; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 13; + CURRENT_PROJECT_VERSION = 14; DEVELOPMENT_ASSET_PATHS = "\"MeshtasticClient/Preview Content\""; DEVELOPMENT_TEAM = GCH7VS5Y9R; ENABLE_PREVIEWS = YES; diff --git a/MeshtasticClient/Helpers/BLEManager.swift b/MeshtasticClient/Helpers/BLEManager.swift index 50ba0728..a71975d1 100644 --- a/MeshtasticClient/Helpers/BLEManager.swift +++ b/MeshtasticClient/Helpers/BLEManager.swift @@ -379,28 +379,15 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph } switch characteristic.uuid { - case FROMNUM_UUID: - //Dont need to call readValue here, the value is already here from the notify - //readValue in turn calls this callback so we end up spamming the node with fromnum reads continuously - //peripheral.readValue(for: FROMNUM_characteristic) - - let characteristicValue: [UInt8] = [UInt8](characteristic.value!) - let bigEndianUInt32 = characteristicValue.withUnsafeBytes { $0.load(as: UInt32.self) } - let returnValue = CFByteOrderGetCurrent() == CFByteOrder(CFByteOrderLittleEndian.rawValue) - ? UInt32(bigEndian: bigEndianUInt32) : bigEndianUInt32 - // print(returnValue) case FROMRADIO_UUID: if characteristic.value == nil || characteristic.value!.isEmpty { return } - // print(characteristic.value ?? "no value") - // print(characteristic.value?.hexDescription ?? "no value") + var decodedInfo = FromRadio() decodedInfo = try! FromRadio(serializedData: characteristic.value!) - // print("Print DecodedInfo") - // print(decodedInfo) // MARK: Incoming MyInfo Packet if decodedInfo.myInfo.myNodeNum != 0 { @@ -417,8 +404,8 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph myInfo.hasGps = decodedInfo.myInfo.hasGps_p myInfo.channelUtilization = decodedInfo.myInfo.channelUtilization - // Swift does strings weird, this does work - let lastDotIndex = decodedInfo.myInfo.firmwareVersion.lastIndex(of: ".")//.lastIndex(of: ".", offsetBy: -1) + // Swift does strings weird, this does work to get the version without the github hash + let lastDotIndex = decodedInfo.myInfo.firmwareVersion.lastIndex(of: ".") var version = decodedInfo.myInfo.firmwareVersion[...(lastDotIndex ?? String.Index(utf16Offset: 6, in: decodedInfo.myInfo.firmwareVersion))] version = version.dropLast() myInfo.firmwareVersion = String(version) @@ -494,15 +481,18 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph print("💥 Fetch MyInfo Error") } - // Use a timer to keep track of position updates, context to pass the radio name with the timer and the RunLoop to prevent - // the timer from running on the main UI thread - if self.positionTimer != nil { - self.positionTimer!.invalidate() + // MARK: Share Location Position Update Timer + // Use context to pass the radio name with the timer + // Use a RunLoop to prevent the timer from running on the main UI thread + if userSettings?.provideLocation ?? false { + + if self.positionTimer != nil { + self.positionTimer!.invalidate() + } + let context = ["name": "@\(peripheral.name ?? "Unknown")"] + self.positionTimer = Timer.scheduledTimer(timeInterval: TimeInterval((userSettings?.provideLocationInterval ?? 900)), target: self, selector: #selector(positionTimerFired), userInfo: context, repeats: true) + RunLoop.current.add(self.positionTimer!, forMode: .common) } - let context = ["name": "@\(peripheral.name ?? "Unknown")"] - self.positionTimer = Timer.scheduledTimer(timeInterval: 30.0, target: self, selector: #selector(positionTimerFired), userInfo: context, repeats: true) - RunLoop.current.add(self.positionTimer!, forMode: .common) - } // MARK: Incoming Node Info Packet @@ -801,8 +791,8 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph try context!.save() - if meshLoggingEnabled { MeshLogger.log("💾 Updated NodeInfo SNR and Time from Node Info App Packet For: \(fetchedNode[0].num)")} - print("💾 Updated NodeInfo SNR and Time from Packet For: \(fetchedNode[0].num)") + if meshLoggingEnabled { MeshLogger.log("💾 Updated NodeInfo SNR \(decodedInfo.packet.rxSnr) and Time from Node Info App Packet For: \(fetchedNode[0].num)")} + print("💾 Updated NodeInfo SNR \(decodedInfo.packet.rxSnr) and Time from Packet For: \(fetchedNode[0].num)") } catch { @@ -873,9 +863,9 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph try context!.save() if meshLoggingEnabled { - MeshLogger.log("💾 Updated NodeInfo Position Coordinates, SNR and Time from Position App Packet For: \(fetchedNode[0].num)") + MeshLogger.log("💾 Updated NodeInfo Position Coordinates, SNR \(decodedInfo.packet.rxSnr) and Time from Position App Packet For: \(fetchedNode[0].num)") } - print("💾 Updated NodeInfo Position Coordinates, SNR and Time from Position App Packet For:: \(fetchedNode[0].num)") + print("💾 Updated NodeInfo Position Coordinates, SNR \(decodedInfo.packet.rxSnr) and Time from Position App Packet For:: \(fetchedNode[0].num)") } catch { @@ -919,10 +909,11 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph let nsError = error as NSError print("💥 Error Saving ACK for message MessageID \(decodedInfo.packet.id) Error: \(nsError)") } - } + } else { - if meshLoggingEnabled { MeshLogger.log("ℹ️ MESH PACKET received for Routing App UNHANDLED \(try decodedInfo.packet.jsonString())") } - print("ℹ️ MESH PACKET received for Routing App UNHANDLED \(try decodedInfo.packet.jsonString())") + if meshLoggingEnabled { MeshLogger.log("ℹ️ MESH PACKET received for Routing App UNHANDLED \(try decodedInfo.packet.jsonString())") } + print("ℹ️ MESH PACKET received for Routing App UNHANDLED \(try decodedInfo.packet.jsonString())") + } } else if decodedInfo.packet.decoded.portnum == PortNum.environmentalMeasurementApp { @@ -1194,10 +1185,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph print("Failed to send positon to device") } - } - // Request config to update MyNodeInfo data periodically as well as all nodes - } } } diff --git a/MeshtasticClient/Views/Settings/AppSettings.swift b/MeshtasticClient/Views/Settings/AppSettings.swift index 3b4f286b..caf7f255 100644 --- a/MeshtasticClient/Views/Settings/AppSettings.swift +++ b/MeshtasticClient/Views/Settings/AppSettings.swift @@ -53,6 +53,31 @@ enum MeshMapType: String, CaseIterable, Identifiable { } } +enum LocationUpdateInterval: Int, CaseIterable, Identifiable { + + case oneMinute = 60 + case fiveMinutes = 300 + case tenMinutes = 600 + case fifteenMinutes = 900 + + var id: Int { self.rawValue } + var description: String { + get { + switch self { + case .oneMinute: + return "One Minute" + case .fiveMinutes: + return "Five Minutes" + case .tenMinutes: + return "Ten Minutes" + case .fifteenMinutes: + return "Fifteen Minutes" + } + } + } +} + + class UserSettings: ObservableObject { @Published var meshtasticUsername: String { didSet { @@ -74,6 +99,11 @@ class UserSettings: ObservableObject { UserDefaults.standard.set(provideLocation, forKey: "provideLocation") } } + @Published var provideLocationInterval: Int { + didSet { + UserDefaults.standard.set(provideLocationInterval, forKey: "provideLocationInterval") + } + } @Published var keyboardType: Int { didSet { UserDefaults.standard.set(keyboardType, forKey: "keyboardType") @@ -90,7 +120,6 @@ class UserSettings: ObservableObject { UserDefaults.standard.set(meshMapType, forKey: "meshMapType") } } - @Published var meshMapCustomTileServer: String { didSet { UserDefaults.standard.set(meshMapCustomTileServer, forKey: "meshMapCustomTileServer") @@ -103,6 +132,7 @@ class UserSettings: ObservableObject { self.preferredPeripheralName = UserDefaults.standard.object(forKey: "preferredPeripheralName") as? String ?? "" self.preferredPeripheralId = UserDefaults.standard.object(forKey: "preferredPeripheralId") as? String ?? "" self.provideLocation = UserDefaults.standard.object(forKey: "provideLocation") as? Bool ?? false + self.provideLocationInterval = UserDefaults.standard.object(forKey: "provideLocationInterval") as? Int ?? 900 self.keyboardType = UserDefaults.standard.object(forKey: "keyboardType") as? Int ?? 0 self.meshActivityLog = UserDefaults.standard.object(forKey: "meshActivityLog") as? Bool ?? false self.meshMapType = UserDefaults.standard.string(forKey: "meshMapType") ?? "hybrid" @@ -139,23 +169,42 @@ struct AppSettings: View { .keyboardType(.asciiCapable) .disableAutocorrection(true) .listRowSeparator(.visible) + + HStack { + Label("Radio", systemImage: "flipphone") + Text(userSettings.preferredPeripheralName) + .foregroundColor(.gray) + + } + Text("This option is set via the preferred radio toggle for the connected device on the bluetooth tab.") + .font(.caption) + .listRowSeparator(.hidden) + Text("The preferred radio will automatically reconnect if it becomes disconnected and is still within range.") + .font(.caption2) + .foregroundColor(.gray) + + } + Section(header: Text("LOCATION OPTIONS")) { + Toggle(isOn: $userSettings.provideLocation) { Label("Provide location to mesh", systemImage: "location.circle.fill") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - .listRowSeparator(.visible) - Label("Preferred Radio", systemImage: "flipphone") - .listRowSeparator(.hidden) - Text(userSettings.preferredPeripheralName) - .foregroundColor(.gray) - Text("This option is set via the preferred radio toggle for the connected device on the bluetooth tab.") - .font(.caption) - .listRowSeparator(.hidden) - Text("The preferred radio will automatically reconnect if it becomes disconnected and is still within range. This device is assumed to be the primary radio used for messaging.") - .font(.caption2) - .foregroundColor(.gray) - + + if userSettings.provideLocation { + + Picker(" Update Interval", selection: $userSettings.provideLocationInterval) { + ForEach(LocationUpdateInterval.allCases) { lu in + Text(lu.description) + } + } + .pickerStyle(DefaultPickerStyle()) + + Text("How frequently your phone will send your location to the device, location updates to the mesh are managed by the device.") + .font(.caption) + .listRowSeparator(.visible) + } } Section(header: Text("MESSAGING OPTIONS")) { @@ -181,10 +230,10 @@ struct AppSettings: View { Label("Log all Mesh activity", systemImage: "network") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - if userSettings.meshActivityLog { - NavigationLink(destination: MeshLog()) { - Text("View Mesh Log") - } + if userSettings.meshActivityLog { + NavigationLink(destination: MeshLog()) { + Text("View Mesh Log") + } .listRowSeparator(.visible) } }