From 277d2105326e8cf7d4a7318d3b97f080800659c8 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 21 Jun 2022 01:12:08 -0700 Subject: [PATCH 1/5] Add speed and heading to location manager --- MeshtasticApple/Helpers/BLEManager.swift | 1 + MeshtasticApple/Helpers/LocationHelper.swift | 20 ++++++++++++++++++- .../Views/Settings/LoRaConfig.swift | 5 +++-- .../Views/Settings/PositionConfig.swift | 18 ++++++++++------- MeshtasticApple/Views/Settings/Settings.swift | 15 ++++++++++---- 5 files changed, 45 insertions(+), 14 deletions(-) diff --git a/MeshtasticApple/Helpers/BLEManager.swift b/MeshtasticApple/Helpers/BLEManager.swift index d442b3a0..d815eb0c 100644 --- a/MeshtasticApple/Helpers/BLEManager.swift +++ b/MeshtasticApple/Helpers/BLEManager.swift @@ -688,6 +688,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph positionPacket.longitudeI = Int32(LocationHelper.currentLocation.longitude * 1e7) positionPacket.time = UInt32(LocationHelper.currentTimestamp.timeIntervalSince1970) positionPacket.altitude = Int32(LocationHelper.currentAltitude) + var meshPacket = MeshPacket() meshPacket.to = UInt32(destNum) diff --git a/MeshtasticApple/Helpers/LocationHelper.swift b/MeshtasticApple/Helpers/LocationHelper.swift index b4ec565d..1137860a 100644 --- a/MeshtasticApple/Helpers/LocationHelper.swift +++ b/MeshtasticApple/Helpers/LocationHelper.swift @@ -8,10 +8,12 @@ class LocationHelper: NSObject, ObservableObject { static let DefaultLocation = CLLocationCoordinate2D(latitude: 37.3346, longitude: -122.0090) static let DefaultAltitude = CLLocationDistance(integerLiteral: 0) + static let DefaultSpeed = CLLocationSpeed(integerLiteral: 0) + static let DefaultHeading = CLLocationDirection(integerLiteral: 0) static var currentLocation: CLLocationCoordinate2D { - guard let location = shared.locationManager.location else { + guard let location = shared.locationManager.location else { return DefaultLocation } return location.coordinate @@ -25,6 +27,22 @@ class LocationHelper: NSObject, ObservableObject { return altitude } + static var currentSpeed: CLLocationSpeed { + + guard let speed = shared.locationManager.location?.speed else { + return DefaultSpeed + } + return speed + } + + static var currentHeading: CLLocationDirection { + + guard let speed = shared.locationManager.location?.course else { + return DefaultHeading + } + return speed + } + static var currentTimestamp: Date { guard let timestamp = shared.locationManager.location?.timestamp else { diff --git a/MeshtasticApple/Views/Settings/LoRaConfig.swift b/MeshtasticApple/Views/Settings/LoRaConfig.swift index 7bef9ffa..5395ccfa 100644 --- a/MeshtasticApple/Views/Settings/LoRaConfig.swift +++ b/MeshtasticApple/Views/Settings/LoRaConfig.swift @@ -189,11 +189,11 @@ struct LoRaConfig: View { @State private var isPresentingSaveConfirm: Bool = false @State var initialLoad: Bool = true + @State var hasChanges = false @State var region = 0 @State var modemPreset = 0 @State var hopLimit = 0 - @State var hasChanges = false var body: some View { @@ -242,6 +242,7 @@ struct LoRaConfig: View { isPresentingSaveConfirm = true } label: { + Label("Save", systemImage: "square.and.arrow.down") } .disabled(bleManager.connectedPeripheral == nil || !hasChanges) @@ -250,6 +251,7 @@ struct LoRaConfig: View { .controlSize(.large) .padding() .confirmationDialog( + "Are you sure?", isPresented: $isPresentingSaveConfirm ) { @@ -314,7 +316,6 @@ struct LoRaConfig: View { hasChanges = true } } - .navigationViewStyle(StackNavigationViewStyle()) } } diff --git a/MeshtasticApple/Views/Settings/PositionConfig.swift b/MeshtasticApple/Views/Settings/PositionConfig.swift index 05c27867..20e3fe13 100644 --- a/MeshtasticApple/Views/Settings/PositionConfig.swift +++ b/MeshtasticApple/Views/Settings/PositionConfig.swift @@ -111,6 +111,8 @@ struct PositionConfig: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager + var node: NodeInfoEntity + @State var smartPositionEnabled = true @State var deviceGpsEnabled = true @State var fixedPosition = false @@ -181,6 +183,8 @@ struct PositionConfig: View { } } + .disabled(!(node.myInfo?.hasGps ?? true)) + Section(header: Text("Position Packet")) { Toggle(isOn: $smartPositionEnabled) { @@ -213,44 +217,44 @@ struct PositionConfig: View { Label("Altitude", systemImage: "arrow.up") } - .toggleStyle(DefaultToggleStyle()) + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) .listRowSeparator(.visible) Toggle(isOn: $includePosSatsinview) { Label("Number of satellites", systemImage: "skew") } - .toggleStyle(DefaultToggleStyle()) + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + .disabled(!(node.myInfo?.hasGps ?? true)) .listRowSeparator(.visible) Toggle(isOn: $includePosSeqNos) { //64 Label("Sequence number", systemImage: "number") } - .toggleStyle(DefaultToggleStyle()) + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) .listRowSeparator(.visible) Toggle(isOn: $includePosTimestamp) { //128 Label("Timestamp", systemImage: "clock") } - .toggleStyle(DefaultToggleStyle()) + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) .listRowSeparator(.visible) Toggle(isOn: $includePosHeading) { //128 Label("Vehicle heading", systemImage: "location.circle") } - .toggleStyle(DefaultToggleStyle()) + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) .listRowSeparator(.visible) Toggle(isOn: $includePosSpeed) { //128 Label("Vehicle speed", systemImage: "speedometer") } - .toggleStyle(DefaultToggleStyle()) + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) .listRowSeparator(.visible) - } } } diff --git a/MeshtasticApple/Views/Settings/Settings.swift b/MeshtasticApple/Views/Settings/Settings.swift index c580f17e..d43f4982 100644 --- a/MeshtasticApple/Views/Settings/Settings.swift +++ b/MeshtasticApple/Views/Settings/Settings.swift @@ -25,6 +25,8 @@ struct Settings: View { NavigationView { List { + let connectedNodeNum = bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.num : 0 + Section("General") { NavigationLink { AppSettings() @@ -43,7 +45,7 @@ struct Settings: View { } } - Section("Radio Configuration (Non-Functional Interaction Previews except for LoRa Config)") { + Section("Radio Configuration") { NavigationLink { DeviceConfig() @@ -53,6 +55,8 @@ struct Settings: View { .symbolRenderingMode(.hierarchical) Text("Device") } + .disabled(bleManager.connectedPeripheral == nil) + NavigationLink { DisplayConfig() } label: { @@ -61,8 +65,7 @@ struct Settings: View { .symbolRenderingMode(.hierarchical) Text("Display (Device Screen)") } - - let connectedNodeNum = bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.num : 0 + .disabled(bleManager.connectedPeripheral == nil) NavigationLink() { @@ -77,7 +80,7 @@ struct Settings: View { .disabled(bleManager.connectedPeripheral == nil) NavigationLink { - PositionConfig() + PositionConfig(node: nodes.first(where: { $0.num == connectedNodeNum }) ?? NodeInfoEntity()) } label: { Image(systemName: "location") @@ -85,6 +88,7 @@ struct Settings: View { Text("Position") } + .disabled(bleManager.connectedPeripheral == nil) } Section("Module Configuration") { @@ -107,6 +111,8 @@ struct Settings: View { Text("Range Test") } + .disabled(!(nodes.first(where: { $0.num == connectedNodeNum })?.myInfo?.hasWifi ?? true) || bleManager.connectedPeripheral == nil) + NavigationLink { TelemetryConfig() } label: { @@ -116,6 +122,7 @@ struct Settings: View { Text("Telemetry (Sensors)") } + .disabled(true) } // Not Implemented: // External Notifications - Not Working From c7b98cb7f4f7246f6c5d70aceacd687d08283e22 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 21 Jun 2022 02:43:37 -0700 Subject: [PATCH 2/5] Device Config --- MeshtasticApple/Helpers/BLEManager.swift | 38 ++++- MeshtasticApple/Helpers/MeshPackets.swift | 77 +++++++++- .../contents | 11 +- .../Views/Settings/DeviceConfig.swift | 131 +++++++++++++++--- .../Views/Settings/LoRaConfig.swift | 1 - MeshtasticApple/Views/Settings/Settings.swift | 2 +- 6 files changed, 233 insertions(+), 27 deletions(-) diff --git a/MeshtasticApple/Helpers/BLEManager.swift b/MeshtasticApple/Helpers/BLEManager.swift index d815eb0c..4c80aaea 100644 --- a/MeshtasticApple/Helpers/BLEManager.swift +++ b/MeshtasticApple/Helpers/BLEManager.swift @@ -688,7 +688,8 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph positionPacket.longitudeI = Int32(LocationHelper.currentLocation.longitude * 1e7) positionPacket.time = UInt32(LocationHelper.currentTimestamp.timeIntervalSince1970) positionPacket.altitude = Int32(LocationHelper.currentAltitude) - + positionPacket.groundSpeed = UInt32(LocationHelper.currentSpeed) + positionPacket.groundTrack = UInt32(LocationHelper.currentHeading) var meshPacket = MeshPacket() meshPacket.to = UInt32(destNum) @@ -850,6 +851,41 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph return false } + public func saveDeviceConfig(config: Config.DeviceConfig, destNum: Int64, wantResponse: Bool) -> Bool { + + var adminPacket = AdminMessage() + adminPacket.setConfig.device = config + + var meshPacket: MeshPacket = MeshPacket() + meshPacket.to = UInt32(connectedPeripheral.num) + meshPacket.from = 0 //UInt32(connectedPeripheral.num) + meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Bool { var adminPacket = AdminMessage() diff --git a/MeshtasticApple/Helpers/MeshPackets.swift b/MeshtasticApple/Helpers/MeshPackets.swift index bb8a0861..2fe55593 100644 --- a/MeshtasticApple/Helpers/MeshPackets.swift +++ b/MeshtasticApple/Helpers/MeshPackets.swift @@ -52,6 +52,79 @@ func localConfig (config: Config, meshlogging: Bool, context:NSManagedObjectCont // // print("🖥️ Has Display config") // } + if config.payloadVariant == Config.OneOf_PayloadVariant.device(config.device) { + + var isDefault = false + + if (try! config.device.jsonString()) == "{}" { + + isDefault = true + } + + let fetchNodeInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") + fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) + + do { + + let fetchedNode = try context.fetch(fetchNodeInfoRequest) as! [NodeInfoEntity] + // Found a node, save Device Config + if !fetchedNode.isEmpty { + + if fetchedNode[0].deviceConfig == nil { + + let newDeviceConfig = DeviceConfigEntity(context: context) + + if isDefault { + + // Client default protobuf value of 0 + newDeviceConfig.role = 0 + newDeviceConfig.serialEnabled = true + newDeviceConfig.debugLogEnabled = false + + } else { + + // Client default protobuf value of 0 + newDeviceConfig.role = Int32(config.device.role.rawValue) + newDeviceConfig.serialEnabled = !config.device.serialDisabled + newDeviceConfig.debugLogEnabled = config.device.debugLogEnabled + } + fetchedNode[0].deviceConfig = newDeviceConfig + + } else { + + if isDefault { + + // Client default protobuf value of 0 + fetchedNode[0].deviceConfig?.role = 0 + fetchedNode[0].deviceConfig?.serialEnabled = true + fetchedNode[0].deviceConfig?.debugLogEnabled = false + + } else { + // Client default protobuf value of 0 + fetchedNode[0].deviceConfig?.role = Int32(config.device.role.rawValue) + fetchedNode[0].deviceConfig?.serialEnabled = !config.device.serialDisabled + fetchedNode[0].deviceConfig?.debugLogEnabled = config.device.debugLogEnabled + } + } + + do { + + try context.save() + if meshlogging { MeshLogger.log("💾 Updated Device Config for node number: \(String(nodeNum))") } + + } catch { + + context.rollback() + + let nsError = error as NSError + print("💥 Error Updating Core Data MyInfoEntity: \(nsError)") + } + } + + } catch { + + } + } if config.payloadVariant == Config.OneOf_PayloadVariant.lora(config.lora) { @@ -119,14 +192,14 @@ func localConfig (config: Config, meshlogging: Bool, context:NSManagedObjectCont do { try context.save() - if meshlogging { MeshLogger.log("💾 Updated LoRaConfig for node number: \(String(nodeNum))") } + if meshlogging { MeshLogger.log("💾 Updated LoRa Config for node number: \(String(nodeNum))") } } catch { context.rollback() let nsError = error as NSError - print("💥 Error Updating Core Data MyInfoEntity: \(nsError)") + print("💥 Error Updating Core Data LoRaConfigEntity: \(nsError)") } } diff --git a/MeshtasticApple/Meshtastic.xcdatamodeld/MeshtasticDataModel v 4.xcdatamodel/contents b/MeshtasticApple/Meshtastic.xcdatamodeld/MeshtasticDataModel v 4.xcdatamodel/contents index 3039ae3b..1f15c865 100644 --- a/MeshtasticApple/Meshtastic.xcdatamodeld/MeshtasticDataModel v 4.xcdatamodel/contents +++ b/MeshtasticApple/Meshtastic.xcdatamodeld/MeshtasticDataModel v 4.xcdatamodel/contents @@ -1,5 +1,12 @@ + + + + + + + @@ -53,6 +60,7 @@ + @@ -104,9 +112,10 @@ - + + \ No newline at end of file diff --git a/MeshtasticApple/Views/Settings/DeviceConfig.swift b/MeshtasticApple/Views/Settings/DeviceConfig.swift index 75099874..795f34ab 100644 --- a/MeshtasticApple/Views/Settings/DeviceConfig.swift +++ b/MeshtasticApple/Views/Settings/DeviceConfig.swift @@ -20,29 +20,48 @@ enum DeviceRoles: Int, CaseIterable, Identifiable { switch self { case .client: - return "Client (default)" + return "Client (default) - App connected client." case .clientMute: 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." + 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: return "Router Client - 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." } } } + func protoEnumValue() -> Config.DeviceConfig.Role { + + switch self { + + case .client: + return Config.DeviceConfig.Role.client + case .clientMute: + return Config.DeviceConfig.Role.clientMute + case .router: + return Config.DeviceConfig.Role.router + case .routerClient: + return Config.DeviceConfig.Role.routerClient + } + } } struct DeviceConfig: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager - + + var node: NodeInfoEntity + + @State private var isPresentingFactoryResetConfirm: Bool = false + @State private var isPresentingSaveConfirm: Bool = false + @State var initialLoad: Bool = true + @State var hasChanges = false + @State var deviceRole = 0 @State var serialEnabled = true @State var debugLogEnabled = false - @State private var isPresentingFactoryResetConfirm: Bool = false - var body: some View { VStack { @@ -77,24 +96,64 @@ struct DeviceConfig: View { } } - Button("Factory Reset", role: .destructive) { + HStack { - isPresentingFactoryResetConfirm = true - } - .disabled(bleManager.connectedPeripheral == nil) - .buttonStyle(.bordered) - .buttonBorderShape(.capsule) - .controlSize(.large) - .padding() - .confirmationDialog( - "Are you sure?", - isPresented: $isPresentingFactoryResetConfirm - ) { - Button("Erase all device settings?", role: .destructive) { + Button { + + isPresentingSaveConfirm = true - if !bleManager.sendFactoryReset(destNum: bleManager.connectedPeripheral.num, wantResponse: false) { + } label: { + + Label("Save", systemImage: "square.and.arrow.down") + } + .disabled(bleManager.connectedPeripheral == nil || !hasChanges) + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + .padding() + .confirmationDialog( + + "Are you sure?", + isPresented: $isPresentingSaveConfirm + ) { + Button("Save Device Config to \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")?") { - print("Factory Reset Failed") + var dc = Config.DeviceConfig() + dc.role = DeviceRoles(rawValue: deviceRole)!.protoEnumValue() + dc.serialDisabled = !serialEnabled + dc.debugLogEnabled = debugLogEnabled + + if bleManager.saveDeviceConfig(config: dc, 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 { + + } + } + } + + Button("Factory Reset", role: .destructive) { + + isPresentingFactoryResetConfirm = true + } + .disabled(bleManager.connectedPeripheral == nil) + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + .padding() + .confirmationDialog( + "Are you sure?", + isPresented: $isPresentingFactoryResetConfirm + ) { + Button("Erase all device settings?", role: .destructive) { + + if !bleManager.sendFactoryReset(destNum: bleManager.connectedPeripheral.num, wantResponse: false) { + + print("Factory Reset Failed") + } } } } @@ -110,7 +169,37 @@ struct DeviceConfig: View { }) .onAppear { - self.bleManager.context = context + if self.initialLoad{ + + self.bleManager.context = context + + self.deviceRole = Int(node.deviceConfig?.role ?? 0) + self.serialEnabled = (node.deviceConfig?.serialEnabled ?? true) + self.debugLogEnabled = node.deviceConfig?.debugLogEnabled ?? false + self.hasChanges = false + self.initialLoad = false + } + } + .onChange(of: deviceRole) { newRole in + + if newRole != node.deviceConfig!.role { + + hasChanges = true + } + } + .onChange(of: serialEnabled) { newSerial in + + if newSerial != node.deviceConfig!.serialEnabled { + + hasChanges = true + } + } + .onChange(of: debugLogEnabled) { newDebugLog in + + if newDebugLog != node.deviceConfig!.debugLogEnabled { + + hasChanges = true + } } .navigationViewStyle(StackNavigationViewStyle()) } diff --git a/MeshtasticApple/Views/Settings/LoRaConfig.swift b/MeshtasticApple/Views/Settings/LoRaConfig.swift index 5395ccfa..de7f76e3 100644 --- a/MeshtasticApple/Views/Settings/LoRaConfig.swift +++ b/MeshtasticApple/Views/Settings/LoRaConfig.swift @@ -287,7 +287,6 @@ struct LoRaConfig: View { if self.initialLoad{ self.bleManager.context = context - print("got hops \(node.loRaConfig?.hopLimit ?? 0)") self.hopLimit = Int(node.loRaConfig?.hopLimit ?? 0) self.region = Int(node.loRaConfig?.regionCode ?? 0) self.modemPreset = Int(node.loRaConfig?.modemPreset ?? 0) diff --git a/MeshtasticApple/Views/Settings/Settings.swift b/MeshtasticApple/Views/Settings/Settings.swift index d43f4982..1025f5e1 100644 --- a/MeshtasticApple/Views/Settings/Settings.swift +++ b/MeshtasticApple/Views/Settings/Settings.swift @@ -48,7 +48,7 @@ struct Settings: View { Section("Radio Configuration") { NavigationLink { - DeviceConfig() + DeviceConfig(node: nodes.first(where: { $0.num == connectedNodeNum }) ?? NodeInfoEntity()) } label: { Image(systemName: "flipphone") From 0d3b6276daa3d0315d094c6bacf93dd3102ae044 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 21 Jun 2022 10:02:05 -0700 Subject: [PATCH 3/5] Display Settings --- MeshtasticApple/Helpers/BLEManager.swift | 35 ++++++ MeshtasticApple/Helpers/MeshPackets.swift | 103 +++++++++++++----- .../contents | 15 ++- .../Views/Settings/DisplayConfig.swift | 96 +++++++++++++++- MeshtasticApple/Views/Settings/Settings.swift | 4 +- 5 files changed, 217 insertions(+), 36 deletions(-) diff --git a/MeshtasticApple/Helpers/BLEManager.swift b/MeshtasticApple/Helpers/BLEManager.swift index 4c80aaea..f0abf250 100644 --- a/MeshtasticApple/Helpers/BLEManager.swift +++ b/MeshtasticApple/Helpers/BLEManager.swift @@ -886,6 +886,41 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph return false } + public func saveDisplayConfig(config: Config.DisplayConfig, destNum: Int64, wantResponse: Bool) -> Bool { + + var adminPacket = AdminMessage() + adminPacket.setConfig.display = config + + var meshPacket: MeshPacket = MeshPacket() + meshPacket.to = UInt32(connectedPeripheral.num) + meshPacket.from = 0 //UInt32(connectedPeripheral.num) + meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Bool { var adminPacket = AdminMessage() diff --git a/MeshtasticApple/Helpers/MeshPackets.swift b/MeshtasticApple/Helpers/MeshPackets.swift index 2fe55593..4d91a654 100644 --- a/MeshtasticApple/Helpers/MeshPackets.swift +++ b/MeshtasticApple/Helpers/MeshPackets.swift @@ -14,25 +14,7 @@ func localConfig (config: Config, meshlogging: Bool, context:NSManagedObjectCont // 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 (try! config.device.jsonString()) == "{}" { -// -// print("📟 Default Device config") -// -// } else { -// -// 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") @@ -44,14 +26,6 @@ func localConfig (config: Config, meshlogging: Bool, context:NSManagedObjectCont // print(try! config.power.jsonString()) // } // -// if (try! config.display.jsonString()) == "{}" { -// -// print("🖥️ Default Display config") -// -// } else { -// -// print("🖥️ Has Display config") -// } if config.payloadVariant == Config.OneOf_PayloadVariant.device(config.device) { var isDefault = false @@ -59,6 +33,7 @@ func localConfig (config: Config, meshlogging: Bool, context:NSManagedObjectCont if (try! config.device.jsonString()) == "{}" { isDefault = true + print("📟 Default Device config") } let fetchNodeInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") @@ -117,7 +92,79 @@ func localConfig (config: Config, meshlogging: Bool, context:NSManagedObjectCont context.rollback() let nsError = error as NSError - print("💥 Error Updating Core Data MyInfoEntity: \(nsError)") + print("💥 Error Updating Core Data DeviceConfigEntity: \(nsError)") + } + } + + } catch { + + } + } + + if config.payloadVariant == Config.OneOf_PayloadVariant.display(config.display) { + + var isDefault = false + + if (try! config.display.jsonString()) == "{}" { + + isDefault = true + print("🖥️ Default Display config") + } + + let fetchNodeInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") + fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) + + do { + + let fetchedNode = try context.fetch(fetchNodeInfoRequest) as! [NodeInfoEntity] + // Found a node, save Device Config + if !fetchedNode.isEmpty { + + if fetchedNode[0].displayConfig == nil { + + let newDisplayConfig = DisplayConfigEntity(context: context) + + if isDefault { + + newDisplayConfig.screenOnSeconds = 0 + newDisplayConfig.screenCarouselInterval = 0 + newDisplayConfig.gpsFormat = 0 + + } else { + + newDisplayConfig.gpsFormat = Int32(config.display.gpsFormat.rawValue) + newDisplayConfig.screenOnSeconds = Int32(config.display.screenOnSecs) + newDisplayConfig.screenCarouselInterval = Int32(config.display.autoScreenCarouselSecs) + } + fetchedNode[0].displayConfig = newDisplayConfig + + } else { + + if isDefault { + + fetchedNode[0].displayConfig?.screenOnSeconds = 0 + fetchedNode[0].displayConfig?.screenCarouselInterval = 0 + fetchedNode[0].displayConfig?.gpsFormat = 0 + + } else { + + fetchedNode[0].displayConfig?.gpsFormat = Int32(config.display.gpsFormat.rawValue) + fetchedNode[0].displayConfig?.screenOnSeconds = Int32(config.display.screenOnSecs) + fetchedNode[0].displayConfig?.screenCarouselInterval = Int32(config.display.autoScreenCarouselSecs) + } + } + + do { + + try context.save() + if meshlogging { MeshLogger.log("💾 Updated Display Config for node number: \(String(nodeNum))") } + + } catch { + + context.rollback() + + let nsError = error as NSError + print("💥 Error Updating Core Data DisplayConfigEntity: \(nsError)") } } diff --git a/MeshtasticApple/Meshtastic.xcdatamodeld/MeshtasticDataModel v 4.xcdatamodel/contents b/MeshtasticApple/Meshtastic.xcdatamodeld/MeshtasticDataModel v 4.xcdatamodel/contents index 1f15c865..5a300379 100644 --- a/MeshtasticApple/Meshtastic.xcdatamodeld/MeshtasticDataModel v 4.xcdatamodel/contents +++ b/MeshtasticApple/Meshtastic.xcdatamodeld/MeshtasticDataModel v 4.xcdatamodel/contents @@ -2,15 +2,22 @@ - + + + + + + + + - + @@ -61,6 +68,7 @@ + @@ -112,10 +120,11 @@ - + + \ No newline at end of file diff --git a/MeshtasticApple/Views/Settings/DisplayConfig.swift b/MeshtasticApple/Views/Settings/DisplayConfig.swift index d7c9936f..6c33fcb8 100644 --- a/MeshtasticApple/Views/Settings/DisplayConfig.swift +++ b/MeshtasticApple/Views/Settings/DisplayConfig.swift @@ -7,7 +7,7 @@ import SwiftUI -enum GpsFormat: Int, CaseIterable, Identifiable { +enum GpsFormats: Int, CaseIterable, Identifiable { case gpsFormatDec = 0 case gpsFormatDms = 1 @@ -35,6 +35,24 @@ enum GpsFormat: Int, CaseIterable, Identifiable { } } } + func protoEnumValue() -> Config.DisplayConfig.GpsCoordinateFormat { + + switch self { + + case .gpsFormatDec: + return Config.DisplayConfig.GpsCoordinateFormat.gpsFormatDec + case .gpsFormatDms: + return Config.DisplayConfig.GpsCoordinateFormat.gpsFormatDms + case .gpsFormatUtm: + return Config.DisplayConfig.GpsCoordinateFormat.gpsFormatUtm + case .gpsFormatMgrs: + return Config.DisplayConfig.GpsCoordinateFormat.gpsFormatMgrs + case .gpsFormatOlc: + return Config.DisplayConfig.GpsCoordinateFormat.gpsFormatOlc + case .gpsFormatOsgr: + return Config.DisplayConfig.GpsCoordinateFormat.gpsFormatOsgr + } + } } // Default of 0 is One Minute @@ -109,6 +127,11 @@ struct DisplayConfig: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager + + var node: NodeInfoEntity + @State private var isPresentingSaveConfirm: Bool = false + @State var initialLoad: Bool = true + @State var hasChanges = false @State var screenOnSeconds = 0 @State var screenCarouselInterval = 0 @@ -146,7 +169,7 @@ struct DisplayConfig: View { } Section(header: Text("Format")) { Picker("GPS Format", selection: $gpsFormat ) { - ForEach(GpsFormat.allCases) { lu in + ForEach(GpsFormats.allCases) { lu in Text(lu.description) } } @@ -157,6 +180,43 @@ struct DisplayConfig: View { .listRowSeparator(.visible) } } + + Button { + + isPresentingSaveConfirm = true + + } label: { + + Label("Save", systemImage: "square.and.arrow.down") + } + .disabled(bleManager.connectedPeripheral == nil || !hasChanges) + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + .padding() + .confirmationDialog( + + "Are you sure?", + isPresented: $isPresentingSaveConfirm + ) { + Button("Save Display Config to \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")?") { + + var dc = Config.DisplayConfig() + dc.gpsFormat = GpsFormats(rawValue: gpsFormat)!.protoEnumValue() + dc.screenOnSecs = UInt32(screenOnSeconds) + dc.autoScreenCarouselSecs = UInt32(screenCarouselInterval) + + if bleManager.saveDisplayConfig(config: dc, 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("Display Config") .navigationBarItems(trailing: @@ -167,7 +227,37 @@ struct DisplayConfig: View { }) .onAppear { - self.bleManager.context = context + if self.initialLoad{ + + self.bleManager.context = context + + self.gpsFormat = Int(node.displayConfig?.gpsFormat ?? 0) + self.screenOnSeconds = Int(node.displayConfig?.screenOnSeconds ?? 0) + self.screenCarouselInterval = Int(node.displayConfig?.screenCarouselInterval ?? 0) + self.hasChanges = false + self.initialLoad = false + } + } + .onChange(of: screenOnSeconds) { newScreenSecs in + + if newScreenSecs != node.displayConfig!.screenOnSeconds { + + hasChanges = true + } + } + .onChange(of: screenCarouselInterval) { newCarouselSecs in + + if newCarouselSecs != node.displayConfig!.screenCarouselInterval { + + hasChanges = true + } + } + .onChange(of: gpsFormat) { newGpsFormat in + + if newGpsFormat != node.displayConfig!.gpsFormat { + + hasChanges = true + } } .navigationViewStyle(StackNavigationViewStyle()) } diff --git a/MeshtasticApple/Views/Settings/Settings.swift b/MeshtasticApple/Views/Settings/Settings.swift index 1025f5e1..ba137128 100644 --- a/MeshtasticApple/Views/Settings/Settings.swift +++ b/MeshtasticApple/Views/Settings/Settings.swift @@ -58,7 +58,7 @@ struct Settings: View { .disabled(bleManager.connectedPeripheral == nil) NavigationLink { - DisplayConfig() + DisplayConfig(node: nodes.first(where: { $0.num == connectedNodeNum }) ?? NodeInfoEntity()) } label: { Image(systemName: "display") @@ -93,7 +93,7 @@ struct Settings: View { Section("Module Configuration") { NavigationLink { - DisplayConfig() + PositionConfig(node: nodes.first(where: { $0.num == connectedNodeNum }) ?? NodeInfoEntity()) } label: { Image(systemName: "list.bullet.rectangle.fill") From 3e3bec380d0d4655a10a017cb40b6b6a37f7929b Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 21 Jun 2022 10:15:47 -0700 Subject: [PATCH 4/5] Start with position --- MeshtasticApple/Helpers/BLEManager.swift | 35 +++++++++++++++++++ .../Views/Settings/PositionConfig.swift | 7 ++-- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/MeshtasticApple/Helpers/BLEManager.swift b/MeshtasticApple/Helpers/BLEManager.swift index f0abf250..9af895ae 100644 --- a/MeshtasticApple/Helpers/BLEManager.swift +++ b/MeshtasticApple/Helpers/BLEManager.swift @@ -955,4 +955,39 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph return false } + + public func savePositionConfig(config: Config.PositionConfig, destNum: Int64, wantResponse: Bool) -> Bool { + + var adminPacket = AdminMessage() + adminPacket.setConfig.position = config + + var meshPacket: MeshPacket = MeshPacket() + meshPacket.to = UInt32(connectedPeripheral.num) + meshPacket.from = 0 //UInt32(connectedPeripheral.num) + meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Date: Tue, 21 Jun 2022 13:10:30 -0700 Subject: [PATCH 5/5] Position Config --- MeshtasticApple/Helpers/MeshPackets.swift | 85 +++++++++++++++++++ .../contents | 20 ++++- .../Views/Settings/PositionConfig.swift | 74 +++++++++++++++- 3 files changed, 174 insertions(+), 5 deletions(-) diff --git a/MeshtasticApple/Helpers/MeshPackets.swift b/MeshtasticApple/Helpers/MeshPackets.swift index 4d91a654..644ac847 100644 --- a/MeshtasticApple/Helpers/MeshPackets.swift +++ b/MeshtasticApple/Helpers/MeshPackets.swift @@ -254,6 +254,91 @@ func localConfig (config: Config, meshlogging: Bool, context:NSManagedObjectCont } } + + if config.payloadVariant == Config.OneOf_PayloadVariant.position(config.position) { + + var isDefault = false + + if (try! config.position.jsonString()) == "{}" { + + isDefault = true + } + + let fetchNodeInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") + fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) + + do { + + let fetchedNode = try context.fetch(fetchNodeInfoRequest) as! [NodeInfoEntity] + // Found a node, save LoRa Config + if !fetchedNode.isEmpty { + + if fetchedNode[0].positionConfig == nil { + + let newPositionConfig = PositionConfigEntity(context: context) + + if isDefault { + + newPositionConfig.smartPositionEnabled = true + newPositionConfig.deviceGpsEnabled = true + newPositionConfig.fixedPosition = false + newPositionConfig.gpsUpdateInterval = 0 + newPositionConfig.gpsAttemptTime = 0 + newPositionConfig.positionBroadcastSeconds = 0 + + } else { + + newPositionConfig.smartPositionEnabled = !config.position.positionBroadcastSmartDisabled + newPositionConfig.deviceGpsEnabled = !config.position.gpsDisabled + newPositionConfig.fixedPosition = config.position.fixedPosition + newPositionConfig.gpsUpdateInterval = Int32(config.position.gpsUpdateInterval) + newPositionConfig.gpsAttemptTime = Int32(config.position.gpsAttemptTime) + newPositionConfig.positionBroadcastSeconds = Int32(config.position.positionBroadcastSecs) + } + + fetchedNode[0].positionConfig = newPositionConfig + + } else { + + if isDefault { + + fetchedNode[0].positionConfig?.smartPositionEnabled = true + fetchedNode[0].positionConfig?.deviceGpsEnabled = true + fetchedNode[0].positionConfig?.fixedPosition = false + fetchedNode[0].positionConfig?.gpsUpdateInterval = 0 + fetchedNode[0].positionConfig?.gpsAttemptTime = 0 + fetchedNode[0].positionConfig?.positionBroadcastSeconds = 0 + + } else { + + fetchedNode[0].positionConfig?.smartPositionEnabled = !config.position.positionBroadcastSmartDisabled + fetchedNode[0].positionConfig?.deviceGpsEnabled = !config.position.gpsDisabled + fetchedNode[0].positionConfig?.fixedPosition = config.position.fixedPosition + fetchedNode[0].positionConfig?.gpsUpdateInterval = Int32(config.position.gpsUpdateInterval) + fetchedNode[0].positionConfig?.gpsAttemptTime = Int32(config.position.gpsAttemptTime) + fetchedNode[0].positionConfig?.positionBroadcastSeconds = Int32(config.position.positionBroadcastSecs) + + } + } + + do { + + try context.save() + if meshlogging { MeshLogger.log("💾 Updated Position Config for node number: \(String(nodeNum))") } + + } catch { + + context.rollback() + + let nsError = error as NSError + print("💥 Error Updating Core Data PositionConfigEntity: \(nsError)") + } + } + + } catch { + + } + } } func myInfoPacket (myInfo: MyNodeInfo, meshLogging: Bool, context: NSManagedObjectContext) -> MyInfoEntity? { diff --git a/MeshtasticApple/Meshtastic.xcdatamodeld/MeshtasticDataModel v 4.xcdatamodel/contents b/MeshtasticApple/Meshtastic.xcdatamodeld/MeshtasticDataModel v 4.xcdatamodel/contents index 5a300379..c32d279b 100644 --- a/MeshtasticApple/Meshtastic.xcdatamodeld/MeshtasticDataModel v 4.xcdatamodel/contents +++ b/MeshtasticApple/Meshtastic.xcdatamodeld/MeshtasticDataModel v 4.xcdatamodel/contents @@ -2,14 +2,14 @@ - + - + @@ -17,7 +17,7 @@ - + @@ -71,6 +71,7 @@ + @@ -80,6 +81,16 @@ + + + + + + + + + + @@ -120,11 +131,12 @@ - + + \ No newline at end of file diff --git a/MeshtasticApple/Views/Settings/PositionConfig.swift b/MeshtasticApple/Views/Settings/PositionConfig.swift index 92bfb732..c16d99c9 100644 --- a/MeshtasticApple/Views/Settings/PositionConfig.swift +++ b/MeshtasticApple/Views/Settings/PositionConfig.swift @@ -258,6 +258,46 @@ struct PositionConfig: View { .listRowSeparator(.visible) } } + + Button { + + isPresentingSaveConfirm = true + + } label: { + + Label("Save", systemImage: "square.and.arrow.down") + } + .disabled(bleManager.connectedPeripheral == nil || !hasChanges) + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + .padding() + .confirmationDialog( + + "Are you sure?", + isPresented: $isPresentingSaveConfirm + ) { + Button("Save Position Config to \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")?") { + + var pc = Config.PositionConfig() + pc.positionBroadcastSmartDisabled = !smartPositionEnabled + pc.gpsDisabled = !deviceGpsEnabled + pc.fixedPosition = fixedPosition + pc.gpsUpdateInterval = UInt32(gpsUpdateInterval) + pc.gpsAttemptTime = UInt32(gpsAttemptTime) + pc.positionBroadcastSecs = UInt32(positionBroadcastSeconds) + + if bleManager.savePositionConfig(config: pc, 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("Position Config") .navigationBarItems(trailing: @@ -268,7 +308,39 @@ struct PositionConfig: View { }) .onAppear { - self.bleManager.context = context + if self.initialLoad{ + + self.bleManager.context = context + self.smartPositionEnabled = node.positionConfig?.smartPositionEnabled ?? true + self.deviceGpsEnabled = node.positionConfig?.deviceGpsEnabled ?? true + self.fixedPosition = node.positionConfig?.fixedPosition ?? false + self.gpsUpdateInterval = Int(node.positionConfig?.gpsUpdateInterval ?? 0) + self.gpsAttemptTime = Int(node.positionConfig?.gpsAttemptTime ?? 0) + self.positionBroadcastSeconds = Int(node.positionConfig?.positionBroadcastSeconds ?? 0) + self.hasChanges = false + self.initialLoad = false + } + } + .onChange(of: smartPositionEnabled) { newSmartPosition in + + if newSmartPosition != node.positionConfig!.smartPositionEnabled { + + hasChanges = true + } + } + .onChange(of: deviceGpsEnabled) { newDeviceGps in + + if newDeviceGps != node.positionConfig!.deviceGpsEnabled { + + hasChanges = true + } + } + .onChange(of: fixedPosition) { newFixed in + + if newFixed != node.positionConfig!.fixedPosition { + + hasChanges = true + } } .navigationViewStyle(StackNavigationViewStyle()) }