From 18630efa586cc88f6780a001ae1fe5527c7ddcf2 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Mon, 20 Jun 2022 12:51:55 -0700 Subject: [PATCH] Update Protobufs, finish LoRaConfig UI --- MeshtasticApple/Helpers/BLEManager.swift | 5 +- MeshtasticApple/Helpers/MeshPackets.swift | 37 +++- MeshtasticApple/Protobufs/config.pb.swift | 200 ++++++------------ .../Views/Settings/DeviceConfig.swift | 2 +- .../Views/Settings/LoRaConfig.swift | 106 +++++++--- .../Views/Settings/PositionConfig.swift | 40 +++- 6 files changed, 202 insertions(+), 188 deletions(-) diff --git a/MeshtasticApple/Helpers/BLEManager.swift b/MeshtasticApple/Helpers/BLEManager.swift index 00488f53..a4bc4254 100644 --- a/MeshtasticApple/Helpers/BLEManager.swift +++ b/MeshtasticApple/Helpers/BLEManager.swift @@ -433,8 +433,9 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph } else if decodedInfo.config.isInitialized { - //localConfig(config: decodedInfo.config, meshlogging: meshLoggingEnabled, context: context!, nodeLongName: self.connectedPeripheral.longName) - if meshLoggingEnabled { MeshLogger.log("ℹ️ MESH PACKET received for Unknown App decodedInfo.config.isInitialized \(try! decodedInfo.packet.jsonString())") } + localConfig(config: decodedInfo.config, meshlogging: meshLoggingEnabled, context: context!, nodeLongName: self.connectedPeripheral.longName) + if meshLoggingEnabled { MeshLogger.log("ℹ️ MESH PACKET received for Unknown App decodedInfo.config.isInitialized \(try! decodedInfo.config.jsonString())") } + } else { diff --git a/MeshtasticApple/Helpers/MeshPackets.swift b/MeshtasticApple/Helpers/MeshPackets.swift index 8a8b3304..68976fb2 100644 --- a/MeshtasticApple/Helpers/MeshPackets.swift +++ b/MeshtasticApple/Helpers/MeshPackets.swift @@ -9,11 +9,11 @@ import Foundation import CoreData import SwiftUI -func localConfig (config: LocalConfig, meshlogging: Bool, context:NSManagedObjectContext, nodeLongName: String) { +func localConfig (config: Config, meshlogging: Bool, context:NSManagedObjectContext, nodeLongName: String) { // We don't care about any of the Power settings // We don't want to manage wifi from the phone app and disconnect our device - if meshlogging { MeshLogger.log("⚙️ Local Config version \(config.version) received for \(nodeLongName)") } + //if meshlogging { MeshLogger.log("⚙️ Local Config version \(config.version) received for \(nodeLongName)") } if (try! config.device.jsonString()) == "{}" { @@ -24,6 +24,26 @@ func localConfig (config: LocalConfig, meshlogging: Bool, context:NSManagedObjec print("📟 Has Device config") } + if (try! config.position.jsonString()) == "{}" { + + print("📍 Default Position config") + + } else { + + print("📍 Has Position config") + } + + if (try! config.power.jsonString() == "{\"lsSecs\":300}") { + + print("📍 Default Power config") + print(try! config.power.jsonString()) + + } else { + + print("📍 Has Power config") + print(try! config.power.jsonString()) + } + if (try! config.display.jsonString()) == "{}" { print("🖥️ Default Display config") @@ -33,6 +53,10 @@ func localConfig (config: LocalConfig, meshlogging: Bool, context:NSManagedObjec print("🖥️ Has Display config") } + + + + if (try! config.lora.jsonString()) == "{}" { print("📡 Default LoRa config") @@ -41,15 +65,6 @@ func localConfig (config: LocalConfig, meshlogging: Bool, context:NSManagedObjec print("📡 Has LoRa config") } - - if (try! config.position.jsonString()) == "{}" { - - print("📍 Default Position config") - - } else { - - print("📍 Has Position config") - } } func myInfoPacket (myInfo: MyNodeInfo, meshLogging: Bool, context: NSManagedObjectContext) -> MyInfoEntity? { diff --git a/MeshtasticApple/Protobufs/config.pb.swift b/MeshtasticApple/Protobufs/config.pb.swift index d446dc4a..b93e2a68 100644 --- a/MeshtasticApple/Protobufs/config.pb.swift +++ b/MeshtasticApple/Protobufs/config.pb.swift @@ -26,11 +26,9 @@ struct Config { // methods supported on all messages. /// - /// TODO: REPLACE + /// Payload Variant var payloadVariant: Config.OneOf_PayloadVariant? = nil - /// - /// TODO: REPLACE var device: Config.DeviceConfig { get { if case .device(let v)? = payloadVariant {return v} @@ -39,8 +37,6 @@ struct Config { set {payloadVariant = .device(newValue)} } - /// - /// TODO: REPLACE var position: Config.PositionConfig { get { if case .position(let v)? = payloadVariant {return v} @@ -49,8 +45,6 @@ struct Config { set {payloadVariant = .position(newValue)} } - /// - /// TODO: REPLACE var power: Config.PowerConfig { get { if case .power(let v)? = payloadVariant {return v} @@ -59,8 +53,6 @@ struct Config { set {payloadVariant = .power(newValue)} } - /// - /// TODO: REPLACE var wifi: Config.WiFiConfig { get { if case .wifi(let v)? = payloadVariant {return v} @@ -69,8 +61,6 @@ struct Config { set {payloadVariant = .wifi(newValue)} } - /// - /// TODO: REPLACE var display: Config.DisplayConfig { get { if case .display(let v)? = payloadVariant {return v} @@ -79,8 +69,6 @@ struct Config { set {payloadVariant = .display(newValue)} } - /// - /// TODO: REPLACE var lora: Config.LoRaConfig { get { if case .lora(let v)? = payloadVariant {return v} @@ -92,25 +80,13 @@ struct Config { var unknownFields = SwiftProtobuf.UnknownStorage() /// - /// TODO: REPLACE + /// Payload Variant enum OneOf_PayloadVariant: Equatable { - /// - /// TODO: REPLACE case device(Config.DeviceConfig) - /// - /// TODO: REPLACE case position(Config.PositionConfig) - /// - /// TODO: REPLACE case power(Config.PowerConfig) - /// - /// TODO: REPLACE case wifi(Config.WiFiConfig) - /// - /// TODO: REPLACE case display(Config.DisplayConfig) - /// - /// TODO: REPLACE case lora(Config.LoRaConfig) #if !swift(>=4.1) @@ -166,7 +142,6 @@ struct Config { /// /// This setting is never saved to disk, but if set, all device settings will be returned to factory defaults. - /// (Region, serial number etc... will be preserved) var factoryReset: Bool = false /// @@ -182,7 +157,6 @@ struct Config { /// /// Defines the device's role on the Mesh network - /// unset - 0 enum Role: SwiftProtobuf.Enum { typealias RawValue = Int @@ -191,24 +165,19 @@ struct Config { case client // = 0 /// - /// ClientMute device role - /// This is like the client but packets will not hop over this node. Would be - /// useful if you want to save power by not contributing to the mesh. + /// Client Mute device role + /// Same as a client except packets will not hop over this node, does not contribute to routing packets for mesh. case clientMute // = 1 /// /// Router device role. - /// Uses an agressive algirithem for the flood networking so packets will - /// prefer to be routed over this node. Also assume that this will be - /// unattended and so will turn off the wifi/ble radio as well as the oled screen. + /// Mesh packets will prefer to be routed over this node. This node will not be used by client apps. + /// The wifi/ble radios and the oled screen will be put to sleep. case router // = 2 /// - /// RouterClient device role - /// Uses an agressive algirithem for the flood networking so packets will - /// prefer to be routed over this node. Similiar power management as a regular - /// client, so the RouterClient can be used as both a Router and a Client. Useful - /// as a well placed base station that you could also use to send messages. + /// Router Client device role + /// Mesh packets will prefer to be routed over this node. The Router Client can be used as both a Router and an app connected Client. case routerClient // = 3 case UNRECOGNIZED(Int) @@ -331,6 +300,18 @@ struct Config { /// /// Include positional timestamp (from GPS solution) case posTimestamp // = 128 + + /// + /// Include positional heading + /// Intended for use with vehicle not walking speeds + /// walking speeds are likely to be error prone like the compass + case posHeading // = 256 + + /// + /// Include positional speed + /// Intended for use with vehicle not walking speeds + /// walking speeds are likely to be error prone like the compass + case posSpeed // = 512 case UNRECOGNIZED(Int) init() { @@ -348,6 +329,8 @@ struct Config { case 32: self = .posSatinview case 64: self = .posSeqNos case 128: self = .posTimestamp + case 256: self = .posHeading + case 512: self = .posSpeed default: self = .UNRECOGNIZED(rawValue) } } @@ -363,6 +346,8 @@ struct Config { case .posSatinview: return 32 case .posSeqNos: return 64 case .posTimestamp: return 128 + case .posHeading: return 256 + case .posSpeed: return 512 case .UNRECOGNIZED(let i): return i } } @@ -389,7 +374,7 @@ struct Config { /// If set, we are powered from a low-current source (i.e. solar), so even if it looks like we have power flowing in /// we should try to minimize power consumption as much as possible. /// YOU DO NOT NEED TO SET THIS IF YOU'VE set is_router (it is implied in that case). - /// CLI Only Option + /// Advanced Option var isPowerSaving: Bool = false /// @@ -399,11 +384,13 @@ struct Config { /// /// Ratio of voltage divider for battery pin eg. 3.20 (R1=100k, R2=220k) /// Overrides the ADC_MULTIPLIER defined in variant for battery voltage calculation. + /// Should be set to floating point value between 2 and 4 + /// Fixes issues on Heltec v2 var adcMultiplierOverride: Float = 0 /// /// Wait Bluetooth Seconds - /// The number of seconds for to wait before turning of BLE in No Bluetooth states\ + /// The number of seconds for to wait before turning off BLE in No Bluetooth states /// 0 for default of 1 minute var waitBluetoothSecs: UInt32 = 0 @@ -425,7 +412,7 @@ struct Config { /// Light Sleep Seconds /// In light sleep the CPU is suspended, LoRa radio is on, BLE is off an GPS is on /// ESP32 Only - /// 0 for default of 3600 + /// 0 for default of 300 var lsSecs: UInt32 = 0 /// @@ -441,73 +428,22 @@ struct Config { /// **TBEAM 1.1 Only** enum ChargeCurrent: SwiftProtobuf.Enum { typealias RawValue = Int - - /// - /// TODO: REPLACE case maunset // = 0 - - /// - /// TODO: REPLACE case ma100 // = 1 - - /// - /// TODO: REPLACE case ma190 // = 2 - - /// - /// TODO: REPLACE case ma280 // = 3 - - /// - /// TODO: REPLACE case ma360 // = 4 - - /// - /// TODO: REPLACE case ma450 // = 5 - - /// - /// TODO: REPLACE case ma550 // = 6 - - /// - /// TODO: REPLACE case ma630 // = 7 - - /// - /// TODO: REPLACE case ma700 // = 8 - - /// - /// TODO: REPLACE case ma780 // = 9 - - /// - /// TODO: REPLACE case ma880 // = 10 - - /// - /// TODO: REPLACE case ma960 // = 11 - - /// - /// TODO: REPLACE case ma1000 // = 12 - - /// - /// TODO: REPLACE case ma1080 // = 13 - - /// - /// TODO: REPLACE case ma1160 // = 14 - - /// - /// TODO: REPLACE case ma1240 // = 15 - - /// - /// TODO: REPLACE case ma1320 // = 16 case UNRECOGNIZED(Int) @@ -567,7 +503,7 @@ struct Config { } /// - /// TODO: REPLACE + /// WiFi Config struct WiFiConfig { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for @@ -604,13 +540,12 @@ struct Config { // methods supported on all messages. /// - /// Power management state machine option. - /// See [power management](/docs/software/other/power) for details. - /// 0 for default of one minute + /// Number of seconds the screen stays on after pressing the user button or receiving a message + /// 0 for default of one minute MAXUINT for always on var screenOnSecs: UInt32 = 0 /// - /// How the GPS coordinates are displayed on the OLED screen. + /// How the GPS coordinates are formatted on the OLED screen. var gpsFormat: Config.DisplayConfig.GpsCoordinateFormat = .gpsFormatDec /// @@ -636,24 +571,24 @@ struct Config { case gpsFormatDms // = 1 /// - /// GPS coordinates are displayed in Universal Transverse Mercator format: + /// Universal Transverse Mercator format: /// ZZB EEEEEE NNNNNNN, where Z is zone, B is band, E is easting, N is northing case gpsFormatUtm // = 2 /// - /// GPS coordinates are displayed in Military Grid Reference System format: + /// Military Grid Reference System format: /// ZZB CD EEEEE NNNNN, where Z is zone, B is band, C is the east 100k square, D is the north 100k square, /// E is easting, N is northing case gpsFormatMgrs // = 3 /// - /// GPS coordinates are displayed in Open Location Code (aka Plus Codes). + /// Open Location Code (aka Plus Codes). case gpsFormatOlc // = 4 /// - /// GPS coordinates are displayed in Ordnance Survey Grid Reference (the National Grid System of the UK). - /// Format: AB EEEEE NNNNN, where A is the east 100k square, B is the north 100k square, E is the easting, - /// N is the northing + /// Ordnance Survey Grid Reference (the National Grid System of the UK). + /// Format: AB EEEEE NNNNN, where A is the east 100k square, B is the north 100k square, + /// E is the easting, N is the northing case gpsFormatOsgr // = 5 case UNRECOGNIZED(Int) @@ -691,7 +626,7 @@ struct Config { } /// - /// TODO: REPLACE + /// Lora Config struct LoRaConfig { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for @@ -705,7 +640,6 @@ struct Config { var txPower: Int32 = 0 /// - /// Note: This is the 'old' mechanism for specifying channel parameters. /// Either modem_config or bandwidth/spreading/coding will be specified - NOT BOTH. /// As a heuristic: If bandwidth is specified, do not use modem_config. /// Because protobufs take ZERO space when the value is zero this works out nicely. @@ -736,11 +670,12 @@ struct Config { var frequencyOffset: Float = 0 /// - /// The region code for my radio (US, CN, EU433, etc...) + /// The region code for the radio (US, CN, EU433, etc...) var region: Config.LoRaConfig.RegionCode = .unset /// /// Overrides HOPS_RELIABLE and sets the maximum number of hops. This can't be greater than 7. + /// 0 for default of 3 var hopLimit: UInt32 = 0 /// @@ -756,66 +691,59 @@ struct Config { var unknownFields = SwiftProtobuf.UnknownStorage() - /// - /// The frequency/regulatory region the user has selected. - /// Note: In 1.0 builds (which must still be supported by the android app for a - /// long time) this field will be unpopulated. - /// If firmware is ever upgraded from an old 1.0ish build, the old - /// MyNodeInfo.region string will be used to set UserPreferences.region and the - /// old value will be no longer set. enum RegionCode: SwiftProtobuf.Enum { typealias RawValue = Int /// - /// TODO: REPLACE + /// Region is not set case unset // = 0 /// - /// TODO: REPLACE + /// United States case us // = 1 /// - /// TODO: REPLACE + /// European Union 433mhz case eu433 // = 2 /// - /// TODO: REPLACE + /// European Union 433mhz case eu868 // = 3 /// - /// TODO: REPLACE + /// China case cn // = 4 /// - /// TODO: REPLACE + /// Japan case jp // = 5 /// - /// TODO: REPLACE + /// Australia / New Zealand case anz // = 6 /// - /// TODO: REPLACE + /// Korea case kr // = 7 /// - /// TODO: REPLACE + /// Taiwan case tw // = 8 /// - /// TODO: REPLACE + /// Russia case ru // = 9 /// - /// TODO: REPLACE + /// India case `in` // = 10 /// - /// TODO: REPLACE + /// New Zealand 865mhz case nz865 // = 11 /// - /// TODO: REPLACE + /// Thailand case th // = 12 case UNRECOGNIZED(Int) @@ -870,31 +798,31 @@ struct Config { typealias RawValue = Int /// - /// TODO: REPLACE + /// Long Range - Fast case longFast // = 0 /// - /// TODO: REPLACE + /// Long Range - Slow case longSlow // = 1 /// - /// TODO: REPLACE + /// Very Long Range - Slow case vlongSlow // = 2 /// - /// TODO: REPLACE + /// Medium Range - Slow case midSlow // = 3 /// - /// TODO: REPLACE + /// Medium Range - Fast case midFast // = 4 /// - /// TODO: REPLACE + /// Short Range - Slow case shortSlow // = 5 /// - /// TODO: REPLACE + /// Short Range - Fast case shortFast // = 6 case UNRECOGNIZED(Int) @@ -960,6 +888,8 @@ extension Config.PositionConfig.PositionFlags: CaseIterable { .posSatinview, .posSeqNos, .posTimestamp, + .posHeading, + .posSpeed, ] } @@ -1337,6 +1267,8 @@ extension Config.PositionConfig.PositionFlags: SwiftProtobuf._ProtoNameProviding 32: .same(proto: "POS_SATINVIEW"), 64: .same(proto: "POS_SEQ_NOS"), 128: .same(proto: "POS_TIMESTAMP"), + 256: .same(proto: "POS_HEADING"), + 512: .same(proto: "POS_SPEED"), ] } diff --git a/MeshtasticApple/Views/Settings/DeviceConfig.swift b/MeshtasticApple/Views/Settings/DeviceConfig.swift index 90fbe166..75099874 100644 --- a/MeshtasticApple/Views/Settings/DeviceConfig.swift +++ b/MeshtasticApple/Views/Settings/DeviceConfig.swift @@ -22,7 +22,7 @@ enum DeviceRoles: Int, CaseIterable, Identifiable { case .client: return "Client (default)" case .clientMute: - return "Client Mute - Packets will not hop over this node, does not contribute to routing packets for mesh." + return "Client Mute - Same as a client except packets will not hop over this node, does not contribute to routing packets for mesh." case .router: return "Router - Mesh packets will prefer to be routed over this node. This node will not be used by client apps. The wifi/ble radios and the oled screen will be put to sleep." case .routerClient: diff --git a/MeshtasticApple/Views/Settings/LoRaConfig.swift b/MeshtasticApple/Views/Settings/LoRaConfig.swift index f4c2abe1..17078884 100644 --- a/MeshtasticApple/Views/Settings/LoRaConfig.swift +++ b/MeshtasticApple/Views/Settings/LoRaConfig.swift @@ -28,7 +28,7 @@ enum RegionCodes : Int, CaseIterable, Identifiable { get { switch self { case .unset: - return "UNSET - Please set a Region" + return "Please set a region" case .us: return "United States" case .eu433: @@ -107,19 +107,19 @@ enum ModemPresets : Int, CaseIterable, Identifiable { switch self { case .LongFast: - return "Long Fast" + return "Long Range - Fast" case .LongSlow: - return "Long Slow" + return "Long Range - Slow" case .VLongSlow: - return "Very Long Slow" + return "Very Long Range - Slow" case .MidSlow: - return "Mid Slow" + return "Medium Range - Slow" case .MidFast: - return "Mid Fast" + return "Medium Range - Fast" case .ShortSlow: - return "Short Slow" + return "Short Range - Slow" case .ShortFast: - return "Short Fast" + return "Short Range - Fast" } } } @@ -146,12 +146,48 @@ enum ModemPresets : Int, CaseIterable, Identifiable { } } +enum HopValues : Int, CaseIterable, Identifiable { + + case oneHop = 1 + case twoHops = 2 + case threeHops = 0 + case fourHops = 4 + case fiveHops = 5 + case sixHops = 6 + case sevenHops = 7 + + var id: Int { self.rawValue } + var description: String { + get { + switch self { + + case .oneHop: + return "One Hop" + case .twoHops: + return "Two Hops" + case .threeHops: + return "Three Hops" + case .fourHops: + return "Four Hops" + case .fiveHops: + return "Five Hops" + case .sixHops: + return "Six Hops" + case .sevenHops: + return "Seven Hops" + } + } + } +} + struct LoRaConfig: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager - @State var region = 1 + @State private var isPresentingSaveConfirm: Bool = false + + @State var region = 0 @State var modemPreset = 0 @State var hopLimit = 0 @State var hasChanges = false @@ -169,10 +205,9 @@ struct LoRaConfig: View { } } .pickerStyle(DefaultPickerStyle()) - Text("The region where you will be using your Meshtastic LoRa radios.") + Text("The region where you will be using your radios.") .font(.caption) .listRowSeparator(.visible) - .listRowSeparator(.visible) } Section(header: Text("Modem")) { Picker("Presets", selection: $region ) { @@ -181,38 +216,27 @@ struct LoRaConfig: View { } } .pickerStyle(DefaultPickerStyle()) - Text("Available modem presets.") + Text("Available modem presets, default is Long Fast.") .font(.caption) .listRowSeparator(.visible) - .listRowSeparator(.visible) } Section(header: Text("Mesh Options")) { Picker("Number of hops", selection: $hopLimit) { - ForEach(0..<8) { - if $0 == 0 { - Text("Default") - } else { - Text("\($0) Hops") - } + ForEach(HopValues.allCases) { hop in + Text(hop.description) } } .pickerStyle(DefaultPickerStyle()) + Text("Sets the maximum number of hops, default is 3.") + .font(.caption) + .listRowSeparator(.visible) } } Button { - var lc = Config.LoRaConfig() - lc.hopLimit = UInt32(hopLimit) - lc.region = RegionCodes(rawValue: region)!.protoEnumValue() - lc.modemPreset = ModemPresets(rawValue: modemPreset)!.protoEnumValue() - - if bleManager.saveLoRaConfig(config: lc, destNum: bleManager.connectedPeripheral.num, wantResponse: false) { - - } else { - - } + isPresentingSaveConfirm = true } label: { Label("Save", systemImage: "square.and.arrow.down") @@ -222,6 +246,28 @@ struct LoRaConfig: View { .buttonBorderShape(.capsule) .controlSize(.large) .padding() + .confirmationDialog( + "Are you sure?", + isPresented: $isPresentingSaveConfirm + ) { + Button("Save LoRa Config to device?") { + + var lc = Config.LoRaConfig() + lc.hopLimit = UInt32(hopLimit) + lc.region = RegionCodes(rawValue: region)!.protoEnumValue() + lc.modemPreset = ModemPresets(rawValue: modemPreset)!.protoEnumValue() + + if bleManager.saveLoRaConfig(config: lc, destNum: bleManager.connectedPeripheral.num, wantResponse: false) { + + // Should show a saved successfully alert once I know that to be true + // for now just disable the button after a successful save + hasChanges = false + + } else { + + } + } + } } .navigationTitle("LoRa Config") @@ -235,7 +281,7 @@ struct LoRaConfig: View { self.bleManager.context = context } - .onChange(of: region) { newRegion in + .onChange(of: region) { newModemPreset in hasChanges = true } diff --git a/MeshtasticApple/Views/Settings/PositionConfig.swift b/MeshtasticApple/Views/Settings/PositionConfig.swift index ffb675e4..05c27867 100644 --- a/MeshtasticApple/Views/Settings/PositionConfig.swift +++ b/MeshtasticApple/Views/Settings/PositionConfig.swift @@ -118,8 +118,14 @@ struct PositionConfig: View { @State var gpsAttemptTime = 0 @State var positionBroadcastSeconds = 0 - @State var includeAltitude = false - @State var includeSatInView = false + @State var includePosAltitude = false + @State var includePosSatsinview = false + @State var includePosSeqNos = false + @State var includePosTimestamp = false + @State var includePosSpeed = false + @State var includePosHeading = false + + var body: some View { @@ -203,30 +209,44 @@ struct PositionConfig: View { .font(.caption) .listRowSeparator(.visible) - Toggle(isOn: $includeAltitude) { + Toggle(isOn: $includePosAltitude) { - Label("Include Altitude", systemImage: "arrow.up") + Label("Altitude", systemImage: "arrow.up") } .toggleStyle(DefaultToggleStyle()) .listRowSeparator(.visible) - Toggle(isOn: $includeSatInView) { + Toggle(isOn: $includePosSatsinview) { - Label("Include number of satellites in view", systemImage: "skew") + Label("Number of satellites", systemImage: "skew") } .toggleStyle(DefaultToggleStyle()) .listRowSeparator(.visible) - Toggle(isOn: $includeSatInView) { //64 + Toggle(isOn: $includePosSeqNos) { //64 - Label("Include a sequence number incremented per packet", systemImage: "number") + Label("Sequence number", systemImage: "number") } .toggleStyle(DefaultToggleStyle()) .listRowSeparator(.visible) - Toggle(isOn: $includeSatInView) { //128 + Toggle(isOn: $includePosTimestamp) { //128 - Label("Include positional timestamp", systemImage: "clock") + Label("Timestamp", systemImage: "clock") + } + .toggleStyle(DefaultToggleStyle()) + .listRowSeparator(.visible) + + Toggle(isOn: $includePosHeading) { //128 + + Label("Vehicle heading", systemImage: "location.circle") + } + .toggleStyle(DefaultToggleStyle()) + .listRowSeparator(.visible) + + Toggle(isOn: $includePosSpeed) { //128 + + Label("Vehicle speed", systemImage: "speedometer") } .toggleStyle(DefaultToggleStyle()) .listRowSeparator(.visible)