From 8f6e1a2d0d83ca35456533eddddfeae9f087f18d Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Mon, 5 Feb 2024 21:46:16 -0800 Subject: [PATCH] Add app smart position and setting Update protobufs Use new GPS tri state on position config --- Meshtastic.xcodeproj/project.pbxproj | 4 +- Meshtastic/Enums/PositionConfigEnums.swift | 74 ++++ Meshtastic/Extensions/UserDefaults.swift | 9 + Meshtastic/Helpers/BLEManager.swift | 4 - Meshtastic/Helpers/LocationsHandler.swift | 6 +- .../Meshtastic.xcdatamodeld/.xccurrentversion | 2 +- .../contents | 411 ++++++++++++++++++ .../Protobufs/meshtastic/admin.pb.swift | 30 ++ .../Protobufs/meshtastic/config.pb.swift | 68 +++ Meshtastic/Protobufs/meshtastic/mesh.pb.swift | 20 + Meshtastic/Views/Settings/AppSettings.swift | 5 + .../Settings/Config/PositionConfig.swift | 71 ++- en.lproj/Localizable.strings | 3 + 13 files changed, 681 insertions(+), 26 deletions(-) create mode 100644 Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 25.xcdatamodel/contents diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 7edfcbf1..49932aa0 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -241,6 +241,7 @@ DD1BF2F82776FE2E008C8D2F /* UserMessageList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserMessageList.swift; sourceTree = ""; }; DD2160AE28C5552500C17253 /* MQTTConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MQTTConfig.swift; sourceTree = ""; }; DD23A50E26FD1B4400D9B90C /* PeripheralModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeripheralModel.swift; sourceTree = ""; }; + DD23D9AB2B7133F6003F5CBE /* MeshtasticDataModelV 25.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 25.xcdatamodel"; sourceTree = ""; }; DD2553562855B02500E55709 /* LoRaConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoRaConfig.swift; sourceTree = ""; }; DD2553582855B52700E55709 /* PositionConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PositionConfig.swift; sourceTree = ""; }; DD295CE92B323ED9002CC4AC /* MeshtasticDataModelV22.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV22.xcdatamodel; sourceTree = ""; }; @@ -1792,6 +1793,7 @@ DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */ = { isa = XCVersionGroup; children = ( + DD23D9AB2B7133F6003F5CBE /* MeshtasticDataModelV 25.xcdatamodel */, DDB234392B5CA9B000DA6FB1 /* MeshtasticDataModelV 24.xcdatamodel */, DD33DB602B3D1ECC003E1EA0 /* MeshtasticDataModelV 23.xcdatamodel */, DD295CE92B323ED9002CC4AC /* MeshtasticDataModelV22.xcdatamodel */, @@ -1817,7 +1819,7 @@ DD5D0A9A2931AD6B00F7EA61 /* MeshtasticDataModelV2.xcdatamodel */, DD3CC6BB28E366DF00FA9159 /* MeshtasticDataModel.xcdatamodel */, ); - currentVersion = DDB234392B5CA9B000DA6FB1 /* MeshtasticDataModelV 24.xcdatamodel */; + currentVersion = DD23D9AB2B7133F6003F5CBE /* MeshtasticDataModelV 25.xcdatamodel */; name = Meshtastic.xcdatamodeld; path = Meshtastic/Meshtastic.xcdatamodeld; sourceTree = ""; diff --git a/Meshtastic/Enums/PositionConfigEnums.swift b/Meshtastic/Enums/PositionConfigEnums.swift index 49f74cd6..5d6751d2 100644 --- a/Meshtastic/Enums/PositionConfigEnums.swift +++ b/Meshtastic/Enums/PositionConfigEnums.swift @@ -52,3 +52,77 @@ enum GpsFormats: Int, CaseIterable, Identifiable { } } } + +enum GpsUpdateIntervals: Int, CaseIterable, Identifiable { + + case thirtySeconds = 30 + case oneMinute = 60 + case fiveMinutes = 300 + case tenMinutes = 600 + case fifteenMinutes = 900 + case thirtyMinutes = 1800 + case oneHour = 3600 + case sixHours = 21600 + case twelveHours = 43200 + case twentyFourHours = 86400 + case maxInt32 = 2147483647 + + var id: Int { self.rawValue } + var description: String { + switch self { + case .thirtySeconds: + return "interval.thirty.seconds".localized + case .oneMinute: + return "interval.one.minute".localized + case .fiveMinutes: + return "interval.five.minutes".localized + case .tenMinutes: + return "interval.ten.minutes".localized + case .fifteenMinutes: + return "interval.fifteen.minutes".localized + case .thirtyMinutes: + return "interval.thirty.minutes".localized + case .oneHour: + return "interval.one.hour".localized + case .sixHours: + return "interval.six.hours".localized + case .twelveHours: + return "interval.twelve.hours".localized + case .twentyFourHours: + return "interval.twentyfour.hours".localized + case .maxInt32: + return "on.boot" + } + } +} + +enum GpsMode: Int, CaseIterable, Equatable { + case disabled = 0 + case enabled = 1 + case notPresent = 2 + + var id: Int { self.rawValue } + + var description: String { + switch self { + case .disabled: + return "gpsmode.disabled".localized + case .enabled: + return "gpsmode.enabled".localized + case .notPresent: + return "gpsmode.notPresent".localized + } + } + func protoEnumValue() -> Config.PositionConfig.GpsMode { + + switch self { + + case .enabled: + return Config.PositionConfig.GpsMode.enabled + case .disabled: + return Config.PositionConfig.GpsMode.disabled + case .notPresent: + return Config.PositionConfig.GpsMode.notPresent + } + } +} diff --git a/Meshtastic/Extensions/UserDefaults.swift b/Meshtastic/Extensions/UserDefaults.swift index 6a4c54a4..b1b1785b 100644 --- a/Meshtastic/Extensions/UserDefaults.swift +++ b/Meshtastic/Extensions/UserDefaults.swift @@ -26,6 +26,7 @@ extension UserDefaults { case mapUseLegacy case enableDetectionNotifications case detectionSensorRole + case enableSmartPosition } func reset() { @@ -193,4 +194,12 @@ extension UserDefaults { UserDefaults.standard.set(newValue.rawValue, forKey: "detectionSensorRole") } } + static var enableSmartPosition: Bool { + get { + UserDefaults.standard.bool(forKey: "enableSmartPosition") + } + set { + UserDefaults.standard.set(newValue, forKey: "enableSmartPosition") + } + } } diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index bcb027fd..0969d262 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -976,10 +976,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate if let lastLocation = LocationsHandler.shared.locationsArray.last { - /// Throw out crappy locations and only send a position if we are connected to a device - if fromNodeNum <= 0 || lastLocation.horizontalAccuracy < 0 || lastLocation.horizontalAccuracy > 100 { - return false - } positionPacket.latitudeI = Int32(lastLocation.coordinate.latitude * 1e7) positionPacket.longitudeI = Int32(lastLocation.coordinate.longitude * 1e7) let timestamp = lastLocation.timestamp diff --git a/Meshtastic/Helpers/LocationsHandler.swift b/Meshtastic/Helpers/LocationsHandler.swift index 4742914a..53712c24 100644 --- a/Meshtastic/Helpers/LocationsHandler.swift +++ b/Meshtastic/Helpers/LocationsHandler.swift @@ -15,7 +15,7 @@ import CoreLocation static let shared = LocationsHandler() // Create a single, shared instance of the object. private let manager: CLLocationManager private var background: CLBackgroundActivitySession? - var enableSmartPosition: Bool + var enableSmartPosition: Bool = UserDefaults.enableSmartPosition @Published var locationsArray: [CLLocation] @Published var isStationary = false @@ -41,8 +41,8 @@ import CoreLocation private init() { self.manager = CLLocationManager() // Creating a location manager instance is safe to call here in `MainActor`. + self.manager.allowsBackgroundLocationUpdates = true locationsArray = [CLLocation]() - enableSmartPosition = true } func startLocationUpdates() { @@ -55,7 +55,7 @@ import CoreLocation self.updatesStarted = true let updates = CLLocationUpdate.liveUpdates() for try await update in updates { - if !self.updatesStarted { break } // End location updates by breaking out of the loop. + if !self.updatesStarted { break } if let loc = update.location { self.isStationary = update.isStationary diff --git a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion index 66c74a0d..07493add 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion +++ b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion @@ -3,6 +3,6 @@ _XCCurrentVersionName - MeshtasticDataModelV 24.xcdatamodel + MeshtasticDataModelV 25.xcdatamodel diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 25.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 25.xcdatamodel/contents new file mode 100644 index 00000000..381347db --- /dev/null +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 25.xcdatamodel/contents @@ -0,0 +1,411 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Meshtastic/Protobufs/meshtastic/admin.pb.swift b/Meshtastic/Protobufs/meshtastic/admin.pb.swift index ab96c32c..baa3c742 100644 --- a/Meshtastic/Protobufs/meshtastic/admin.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/admin.pb.swift @@ -235,6 +235,16 @@ struct AdminMessage { set {payloadVariant = .enterDfuModeRequest(newValue)} } + /// + /// Delete the file by the specified path from the device + var deleteFileRequest: String { + get { + if case .deleteFileRequest(let v)? = payloadVariant {return v} + return String() + } + set {payloadVariant = .deleteFileRequest(newValue)} + } + /// /// Set the owner for this node var setOwner: User { @@ -460,6 +470,9 @@ struct AdminMessage { /// Only implemented on NRF52 currently case enterDfuModeRequest(Bool) /// + /// Delete the file by the specified path from the device + case deleteFileRequest(String) + /// /// Set the owner for this node case setOwner(User) /// @@ -598,6 +611,10 @@ struct AdminMessage { guard case .enterDfuModeRequest(let l) = lhs, case .enterDfuModeRequest(let r) = rhs else { preconditionFailure() } return l == r }() + case (.deleteFileRequest, .deleteFileRequest): return { + guard case .deleteFileRequest(let l) = lhs, case .deleteFileRequest(let r) = rhs else { preconditionFailure() } + return l == r + }() case (.setOwner, .setOwner): return { guard case .setOwner(let l) = lhs, case .setOwner(let r) = rhs else { preconditionFailure() } return l == r @@ -953,6 +970,7 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat 19: .standard(proto: "get_node_remote_hardware_pins_request"), 20: .standard(proto: "get_node_remote_hardware_pins_response"), 21: .standard(proto: "enter_dfu_mode_request"), + 22: .standard(proto: "delete_file_request"), 32: .standard(proto: "set_owner"), 33: .standard(proto: "set_channel"), 34: .standard(proto: "set_config"), @@ -1176,6 +1194,14 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat self.payloadVariant = .enterDfuModeRequest(v) } }() + case 22: try { + var v: String? + try decoder.decodeSingularStringField(value: &v) + if let v = v { + if self.payloadVariant != nil {try decoder.handleConflictingOneOf()} + self.payloadVariant = .deleteFileRequest(v) + } + }() case 32: try { var v: User? var hadOneofValue = false @@ -1407,6 +1433,10 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat guard case .enterDfuModeRequest(let v)? = self.payloadVariant else { preconditionFailure() } try visitor.visitSingularBoolField(value: v, fieldNumber: 21) }() + case .deleteFileRequest?: try { + guard case .deleteFileRequest(let v)? = self.payloadVariant else { preconditionFailure() } + try visitor.visitSingularStringField(value: v, fieldNumber: 22) + }() case .setOwner?: try { guard case .setOwner(let v)? = self.payloadVariant else { preconditionFailure() } try visitor.visitSingularMessageField(value: v, fieldNumber: 32) diff --git a/Meshtastic/Protobufs/meshtastic/config.pb.swift b/Meshtastic/Protobufs/meshtastic/config.pb.swift index b984ef22..ef43b94a 100644 --- a/Meshtastic/Protobufs/meshtastic/config.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/config.pb.swift @@ -415,6 +415,10 @@ struct Config { /// (Re)define PIN_GPS_EN for your board. var gpsEnGpio: UInt32 = 0 + /// + /// Set where GPS is enabled, disabled, or not present + var gpsMode: Config.PositionConfig.GpsMode = .disabled + var unknownFields = SwiftProtobuf.UnknownStorage() /// @@ -516,6 +520,46 @@ struct Config { } + enum GpsMode: SwiftProtobuf.Enum { + typealias RawValue = Int + + /// + /// GPS is present but disabled + case disabled // = 0 + + /// + /// GPS is present and enabled + case enabled // = 1 + + /// + /// GPS is not present on the device + case notPresent // = 2 + case UNRECOGNIZED(Int) + + init() { + self = .disabled + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .disabled + case 1: self = .enabled + case 2: self = .notPresent + default: self = .UNRECOGNIZED(rawValue) + } + } + + var rawValue: Int { + switch self { + case .disabled: return 0 + case .enabled: return 1 + case .notPresent: return 2 + case .UNRECOGNIZED(let i): return i + } + } + + } + init() {} } @@ -1366,6 +1410,15 @@ extension Config.PositionConfig.PositionFlags: CaseIterable { ] } +extension Config.PositionConfig.GpsMode: CaseIterable { + // The compiler won't synthesize support with the UNRECOGNIZED case. + static var allCases: [Config.PositionConfig.GpsMode] = [ + .disabled, + .enabled, + .notPresent, + ] +} + extension Config.NetworkConfig.AddressMode: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. static var allCases: [Config.NetworkConfig.AddressMode] = [ @@ -1471,6 +1524,7 @@ extension Config.DeviceConfig.Role: @unchecked Sendable {} extension Config.DeviceConfig.RebroadcastMode: @unchecked Sendable {} extension Config.PositionConfig: @unchecked Sendable {} extension Config.PositionConfig.PositionFlags: @unchecked Sendable {} +extension Config.PositionConfig.GpsMode: @unchecked Sendable {} extension Config.PowerConfig: @unchecked Sendable {} extension Config.NetworkConfig: @unchecked Sendable {} extension Config.NetworkConfig.AddressMode: @unchecked Sendable {} @@ -1776,6 +1830,7 @@ extension Config.PositionConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageIm 10: .standard(proto: "broadcast_smart_minimum_distance"), 11: .standard(proto: "broadcast_smart_minimum_interval_secs"), 12: .standard(proto: "gps_en_gpio"), + 13: .standard(proto: "gps_mode"), ] mutating func decodeMessage(decoder: inout D) throws { @@ -1796,6 +1851,7 @@ extension Config.PositionConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageIm case 10: try { try decoder.decodeSingularUInt32Field(value: &self.broadcastSmartMinimumDistance) }() case 11: try { try decoder.decodeSingularUInt32Field(value: &self.broadcastSmartMinimumIntervalSecs) }() case 12: try { try decoder.decodeSingularUInt32Field(value: &self.gpsEnGpio) }() + case 13: try { try decoder.decodeSingularEnumField(value: &self.gpsMode) }() default: break } } @@ -1838,6 +1894,9 @@ extension Config.PositionConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageIm if self.gpsEnGpio != 0 { try visitor.visitSingularUInt32Field(value: self.gpsEnGpio, fieldNumber: 12) } + if self.gpsMode != .disabled { + try visitor.visitSingularEnumField(value: self.gpsMode, fieldNumber: 13) + } try unknownFields.traverse(visitor: &visitor) } @@ -1854,6 +1913,7 @@ extension Config.PositionConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageIm if lhs.broadcastSmartMinimumDistance != rhs.broadcastSmartMinimumDistance {return false} if lhs.broadcastSmartMinimumIntervalSecs != rhs.broadcastSmartMinimumIntervalSecs {return false} if lhs.gpsEnGpio != rhs.gpsEnGpio {return false} + if lhs.gpsMode != rhs.gpsMode {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -1875,6 +1935,14 @@ extension Config.PositionConfig.PositionFlags: SwiftProtobuf._ProtoNameProviding ] } +extension Config.PositionConfig.GpsMode: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "DISABLED"), + 1: .same(proto: "ENABLED"), + 2: .same(proto: "NOT_PRESENT"), + ] +} + extension Config.PowerConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { static let protoMessageName: String = Config.protoMessageName + ".PowerConfig" static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ diff --git a/Meshtastic/Protobufs/meshtastic/mesh.pb.swift b/Meshtastic/Protobufs/meshtastic/mesh.pb.swift index 31ebb6a2..7496d559 100644 --- a/Meshtastic/Protobufs/meshtastic/mesh.pb.swift +++ b/Meshtastic/Protobufs/meshtastic/mesh.pb.swift @@ -126,6 +126,10 @@ enum HardwareModel: SwiftProtobuf.Enum { /// Makerfabs SenseLoRA Industrial Monitor (ESP32-S3 + RFM96) case senseloraS3 // = 28 + /// + /// Canary Radio Company - CanaryOne: https://canaryradio.io/products/canaryone + case canaryone // = 29 + /// /// --------------------------------------------------------------------------- /// Less common/prototype boards listed here (needs one more byte over the air) @@ -230,6 +234,14 @@ enum HardwareModel: SwiftProtobuf.Enum { /// with one cut and one jumper Meshtastic works case chatter2 // = 56 + /// + /// Heltec Wireless Paper, With ESP32-S3 CPU and E-Ink display + /// Older "V1.0" Variant, has no "version sticker" + /// E-Ink model is DEPG0213BNS800 + /// Tab on the screen protector is RED + /// Flex connector marking is FPC-7528B + case heltecWirelessPaperV10 // = 57 + /// /// ------------------------------------------------------------------------------------------------------------------------------------------ /// Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits. @@ -267,6 +279,7 @@ enum HardwareModel: SwiftProtobuf.Enum { case 26: self = .rak11310 case 27: self = .senseloraRp2040 case 28: self = .senseloraS3 + case 29: self = .canaryone case 32: self = .loraRelayV1 case 33: self = .nrf52840Dk case 34: self = .ppr @@ -292,6 +305,7 @@ enum HardwareModel: SwiftProtobuf.Enum { case 54: self = .ebyteEsp32S3 case 55: self = .esp32S3Pico case 56: self = .chatter2 + case 57: self = .heltecWirelessPaperV10 case 255: self = .privateHw default: self = .UNRECOGNIZED(rawValue) } @@ -323,6 +337,7 @@ enum HardwareModel: SwiftProtobuf.Enum { case .rak11310: return 26 case .senseloraRp2040: return 27 case .senseloraS3: return 28 + case .canaryone: return 29 case .loraRelayV1: return 32 case .nrf52840Dk: return 33 case .ppr: return 34 @@ -348,6 +363,7 @@ enum HardwareModel: SwiftProtobuf.Enum { case .ebyteEsp32S3: return 54 case .esp32S3Pico: return 55 case .chatter2: return 56 + case .heltecWirelessPaperV10: return 57 case .privateHw: return 255 case .UNRECOGNIZED(let i): return i } @@ -384,6 +400,7 @@ extension HardwareModel: CaseIterable { .rak11310, .senseloraRp2040, .senseloraS3, + .canaryone, .loraRelayV1, .nrf52840Dk, .ppr, @@ -409,6 +426,7 @@ extension HardwareModel: CaseIterable { .ebyteEsp32S3, .esp32S3Pico, .chatter2, + .heltecWirelessPaperV10, .privateHw, ] } @@ -2570,6 +2588,7 @@ extension HardwareModel: SwiftProtobuf._ProtoNameProviding { 26: .same(proto: "RAK11310"), 27: .same(proto: "SENSELORA_RP2040"), 28: .same(proto: "SENSELORA_S3"), + 29: .same(proto: "CANARYONE"), 32: .same(proto: "LORA_RELAY_V1"), 33: .same(proto: "NRF52840DK"), 34: .same(proto: "PPR"), @@ -2595,6 +2614,7 @@ extension HardwareModel: SwiftProtobuf._ProtoNameProviding { 54: .same(proto: "EBYTE_ESP32_S3"), 55: .same(proto: "ESP32_S3_PICO"), 56: .same(proto: "CHATTER_2"), + 57: .same(proto: "HELTEC_WIRELESS_PAPER_V1_0"), 255: .same(proto: "PRIVATE_HW"), ] } diff --git a/Meshtastic/Views/Settings/AppSettings.swift b/Meshtastic/Views/Settings/AppSettings.swift index 575718e0..9f4582e7 100644 --- a/Meshtastic/Views/Settings/AppSettings.swift +++ b/Meshtastic/Views/Settings/AppSettings.swift @@ -11,6 +11,7 @@ struct AppSettings: View { @State var totalDownloadedTileSize = "" @StateObject var locationHelper = LocationHelper() @State var provideLocation: Bool = UserDefaults.provideLocation + @State var enableSmartPosition: Bool = UserDefaults.enableSmartPosition @State var useLegacyMap: Bool = UserDefaults.mapUseLegacy @State var provideLocationInterval: Int = UserDefaults.provideLocationInterval @State private var isPresentingCoreDataResetConfirm = false @@ -67,6 +68,10 @@ struct AppSettings: View { Label("provide.location", systemImage: "location.circle.fill") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + Toggle(isOn: $enableSmartPosition) { + Label("appsettings.enablesmartposition", systemImage: "brain.fill") + } + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) if UserDefaults.provideLocation { VStack { Picker("update.interval", selection: $provideLocationInterval) { diff --git a/Meshtastic/Views/Settings/Config/PositionConfig.swift b/Meshtastic/Views/Settings/Config/PositionConfig.swift index a92f06ef..31dd2319 100644 --- a/Meshtastic/Views/Settings/Config/PositionConfig.swift +++ b/Meshtastic/Views/Settings/Config/PositionConfig.swift @@ -36,14 +36,16 @@ struct PositionConfig: View { @State var smartPositionEnabled = true @State var deviceGpsEnabled = true + @State var gpsMode = 0 @State var rxGpio = 0 @State var txGpio = 0 @State var gpsEnGpio = 0 @State var fixedPosition = false + @State var gpsUpdateInterval = 0 @State var positionBroadcastSeconds = 0 @State var broadcastSmartMinimumDistance = 0 @State var broadcastSmartMinimumIntervalSecs = 0 - @State var positionFlags = 3 + @State var positionFlags = 811 /// Position Flags /// Altitude value - 1 @@ -143,6 +145,35 @@ struct PositionConfig: View { .font(.caption) } } + Section(header: Text("Device GPS")) { + Picker("", selection: $gpsMode) { + ForEach(GpsMode.allCases, id: \.self) { at in + Text(at.description) + .tag(at.id) + } + } + .pickerStyle(SegmentedPickerStyle()) + .padding(.top, 5) + .padding(.bottom, 5) + + + if gpsMode == 1 { + Picker("Update Interval", selection: $gpsUpdateInterval) { + ForEach(GpsUpdateIntervals.allCases) { ui in + Text(ui.description) + } + } + Text("How often should we try to get a GPS position.") + .font(.caption) + } else { + Toggle(isOn: $fixedPosition) { + Label("Fixed Position", systemImage: "location.square.fill") + } + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + Text("If enabled your current phone location will be sent to the device and will broadcast over the mesh on the position interval. Fixed positon will always use the most recent position the device has.") + .font(.caption) + } + } Section(header: Text("Position Flags")) { Text("Optional fields to include when assembling position messages. the more fields are included, the larger the message will be - leading to longer airtime and a higher risk of packet loss") @@ -205,13 +236,9 @@ struct PositionConfig: View { .toggleStyle(SwitchToggleStyle(tint: .accentColor)) } } - Section(header: Text("Device GPS")) { - Toggle(isOn: $deviceGpsEnabled) { - Label("Device GPS Enabled", systemImage: "location") - } - .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - if deviceGpsEnabled { - + + if gpsMode == 1 { + Section(header: Text("Advanced Device GPS")) { Picker("GPS Receive GPIO", selection: $rxGpio) { ForEach(0..<49) { if $0 == 0 { @@ -244,13 +271,6 @@ struct PositionConfig: View { .pickerStyle(DefaultPickerStyle()) Text("(Re)define PIN_GPS_EN for your board.") .font(.caption) - } else { - Toggle(isOn: $fixedPosition) { - Label("Fixed Position", systemImage: "location.square.fill") - } - .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - Text("If enabled your current location will be set as a fixed position.") - .font(.caption) } } } @@ -283,8 +303,10 @@ struct PositionConfig: View { if connectedNode != nil { var pc = Config.PositionConfig() pc.positionBroadcastSmartEnabled = smartPositionEnabled - pc.gpsEnabled = deviceGpsEnabled + pc.gpsEnabled = gpsMode == 1 + pc.gpsMode = Config.PositionConfig.GpsMode(rawValue: gpsMode) ?? Config.PositionConfig.GpsMode.notPresent pc.fixedPosition = fixedPosition + pc.gpsUpdateInterval = UInt32(gpsUpdateInterval) pc.positionBroadcastSecs = UInt32(positionBroadcastSeconds) pc.broadcastSmartMinimumIntervalSecs = UInt32(broadcastSmartMinimumIntervalSecs) pc.broadcastSmartMinimumDistance = UInt32(broadcastSmartMinimumDistance) @@ -342,6 +364,11 @@ struct PositionConfig: View { if newDeviceGps != node!.positionConfig!.deviceGpsEnabled { hasChanges = true } } } + .onChange(of: gpsMode) { newGpsMode in + if node != nil && node!.positionConfig != nil { + if newGpsMode != node!.positionConfig!.gpsMode { hasChanges = true } + } + } .onChange(of: rxGpio) { newRxGpio in if node != nil && node!.positionConfig != nil { if newRxGpio != node!.positionConfig!.rxGpio { hasChanges = true } @@ -382,6 +409,11 @@ struct PositionConfig: View { if newBroadcastSmartMinimumDistance != node!.positionConfig!.broadcastSmartMinimumDistance { hasChanges = true } } } + .onChange(of: gpsUpdateInterval) { newGpsUpdateInterval in + if node != nil && node!.positionConfig != nil { + if newGpsUpdateInterval != node!.positionConfig!.gpsUpdateInterval { hasChanges = true } + } + } .onChange(of: includeAltitude) { altFlag in let pf = PositionFlags(rawValue: self.positionFlags) let existingValue = pf.contains(.Altitude) @@ -440,11 +472,16 @@ struct PositionConfig: View { } func setPositionValues() { self.smartPositionEnabled = node?.positionConfig?.smartPositionEnabled ?? true - self.deviceGpsEnabled = node?.positionConfig?.deviceGpsEnabled ?? true + self.deviceGpsEnabled = node?.positionConfig?.deviceGpsEnabled ?? false + self.gpsMode = Int(node?.positionConfig?.gpsMode ?? 0) + if node?.positionConfig?.deviceGpsEnabled ?? false && gpsMode != 1 { + self.gpsMode = 1 + } self.rxGpio = Int(node?.positionConfig?.rxGpio ?? 0) self.txGpio = Int(node?.positionConfig?.txGpio ?? 0) self.gpsEnGpio = Int(node?.positionConfig?.gpsEnGpio ?? 0) self.fixedPosition = node?.positionConfig?.fixedPosition ?? false + self.gpsUpdateInterval = Int(node?.positionConfig?.gpsUpdateInterval ?? 30) self.positionBroadcastSeconds = Int(node?.positionConfig?.positionBroadcastSeconds ?? 900) self.broadcastSmartMinimumIntervalSecs = Int(node?.positionConfig?.broadcastSmartMinimumIntervalSecs ?? 30) self.broadcastSmartMinimumDistance = Int(node?.positionConfig?.broadcastSmartMinimumDistance ?? 50) diff --git a/en.lproj/Localizable.strings b/en.lproj/Localizable.strings index 425b8e01..a2cef31e 100644 --- a/en.lproj/Localizable.strings +++ b/en.lproj/Localizable.strings @@ -100,6 +100,9 @@ "gpsformat.mgrs"="Military Grid Reference System"; "gpsformat.olc"="Open Location Code (aka Plus Codes)"; "gpsformat.osgr"="Ordnance Survey Grid Reference"; +"gpsmode.disabled"="Disabled"; +"gpsmode.enabled"="Enabled"; +"gpsmode.notPresent"="Not Present"; "heard"="Heard"; "heard.last"="Last Heard"; "hybrid"="Hybrid";