From ebf9e8feec01daa7a3d21f497f843b91505607d4 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Thu, 18 Aug 2022 08:46:10 -0700 Subject: [PATCH 1/4] Initial BLE config view --- Meshtastic.xcodeproj/project.pbxproj | 8 +- .../Meshtastic.xcdatamodeld/.xccurrentversion | 2 +- .../contents | 2 +- .../contents | 231 ++++++++++++++++++ .../Settings/Config/BluetoothConfig.swift | 174 +++++++++++++ Meshtastic/Views/Settings/Settings.swift | 12 + 6 files changed, 426 insertions(+), 3 deletions(-) create mode 100644 Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModel v 7.xcdatamodel/contents create mode 100644 Meshtastic/Views/Settings/Config/BluetoothConfig.swift diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index f8cf8487..03924cf8 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -70,6 +70,7 @@ DDAF8C6E26ED19040058C060 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAF8C6D26ED19040058C060 /* Extensions.swift */; }; DDB2CC6E27F3EB47009C5FCC /* telemetry.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB2CC6D27F3EB47009C5FCC /* telemetry.pb.swift */; }; DDB3107228A6224100F1DE3D /* device_metadata.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB3107128A6224100F1DE3D /* device_metadata.pb.swift */; }; + DDB6ABD628AE742000384BA1 /* BluetoothConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB6ABD528AE742000384BA1 /* BluetoothConfig.swift */; }; DDC2E15826CE248E0042C5E4 /* MeshtasticApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC2E15726CE248E0042C5E4 /* MeshtasticApp.swift */; }; DDC2E15C26CE248F0042C5E4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DDC2E15B26CE248F0042C5E4 /* Assets.xcassets */; }; DDC2E15F26CE248F0042C5E4 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DDC2E15E26CE248F0042C5E4 /* Preview Assets.xcassets */; }; @@ -171,6 +172,8 @@ DDB2CC6D27F3EB47009C5FCC /* telemetry.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = telemetry.pb.swift; sourceTree = ""; }; DDB2CC6F27F3F0AC009C5FCC /* MeshtasticDataModel v 3.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModel v 3.xcdatamodel"; sourceTree = ""; }; DDB3107128A6224100F1DE3D /* device_metadata.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = device_metadata.pb.swift; sourceTree = ""; }; + DDB6ABD528AE742000384BA1 /* BluetoothConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BluetoothConfig.swift; sourceTree = ""; }; + DDB6ABD728AE8F5D00384BA1 /* MeshtasticDataModel v 7.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModel v 7.xcdatamodel"; sourceTree = ""; }; DDC2E15426CE248E0042C5E4 /* Meshtastic.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Meshtastic.app; sourceTree = BUILT_PRODUCTS_DIR; }; DDC2E15726CE248E0042C5E4 /* MeshtasticApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshtasticApp.swift; sourceTree = ""; }; DDC2E15B26CE248F0042C5E4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = ../Assets.xcassets; sourceTree = ""; }; @@ -278,6 +281,7 @@ DD61937A2863876A00E59241 /* Config */ = { isa = PBXGroup; children = ( + DDB6ABD528AE742000384BA1 /* BluetoothConfig.swift */, DD41582528582E9B009B0E59 /* DeviceConfig.swift */, DD8EBF42285058FA00426DCA /* DisplayConfig.swift */, DD2553562855B02500E55709 /* LoRaConfig.swift */, @@ -664,6 +668,7 @@ DD4C158C2824A91E0032668E /* module_config.pb.swift in Sources */, DD4F23CD28779A3C001D37CB /* TelemetryLog.swift in Sources */, DD6B85A828009258000ACD6B /* ShareChannel.swift in Sources */, + DDB6ABD628AE742000384BA1 /* BluetoothConfig.swift in Sources */, DDAF8C5326EB1DF10058C060 /* BLEManager.swift in Sources */, DDC4D568275499A500A4208E /* Persistence.swift in Sources */, DD90860E26F69BAE00DC5189 /* NodeMap.swift in Sources */, @@ -1102,6 +1107,7 @@ DD9D8F2D2764403B00080993 /* Meshtastic.xcdatamodeld */ = { isa = XCVersionGroup; children = ( + DDB6ABD728AE8F5D00384BA1 /* MeshtasticDataModel v 7.xcdatamodel */, DD8ED9C9289EA77E00B3B0AB /* MeshtasticDataModel v 6.xcdatamodel */, DD8ED9C328978D9D00B3B0AB /* MeshtasticDataModel v 5.xcdatamodel */, DD619373285CC7D600E59241 /* MeshtasticDataModel v 4.xcdatamodel */, @@ -1109,7 +1115,7 @@ DD45C77427BD4EF80011784F /* MeshtasticDataModel v2.xcdatamodel */, DD9D8F2E2764403B00080993 /* CoreDataSample.xcdatamodel */, ); - currentVersion = DD8ED9C9289EA77E00B3B0AB /* MeshtasticDataModel v 6.xcdatamodel */; + currentVersion = DDB6ABD728AE8F5D00384BA1 /* MeshtasticDataModel v 7.xcdatamodel */; name = Meshtastic.xcdatamodeld; path = Meshtastic/Meshtastic.xcdatamodeld; sourceTree = ""; diff --git a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion index 319888a5..bf45c105 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion +++ b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion @@ -3,6 +3,6 @@ _XCCurrentVersionName - MeshtasticDataModel v 6.xcdatamodel + MeshtasticDataModel v 7.xcdatamodel diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModel v 6.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModel v 6.xcdatamodel/contents index 6f9b7000..22353512 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModel v 6.xcdatamodel/contents +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModel v 6.xcdatamodel/contents @@ -1,5 +1,5 @@ - + diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModel v 7.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModel v 7.xcdatamodel/contents new file mode 100644 index 00000000..d58af5fd --- /dev/null +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModel v 7.xcdatamodel/contents @@ -0,0 +1,231 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Meshtastic/Views/Settings/Config/BluetoothConfig.swift b/Meshtastic/Views/Settings/Config/BluetoothConfig.swift new file mode 100644 index 00000000..1057f1eb --- /dev/null +++ b/Meshtastic/Views/Settings/Config/BluetoothConfig.swift @@ -0,0 +1,174 @@ +// +// BluetoothConfig.swift +// Meshtastic Apple +// +// Copyright (c) Garth Vander Houwen 8/18/22. +// + +import SwiftUI + +enum BluetoothModes: Int, CaseIterable, Identifiable { + + case randomPin = 0 + case fixedPin = 1 + case noPin = 2 + + var id: Int { self.rawValue } + var description: String { + get { + switch self { + case .randomPin: + return "Random" + case .fixedPin: + return "Fixed" + case .noPin: + return "None" + } + } + } + func protoEnumValue() -> Config.BluetoothConfig.PairingMode { + + switch self { + + case .randomPin: + return Config.BluetoothConfig.PairingMode.randomPin + case .fixedPin: + return Config.BluetoothConfig.PairingMode.fixedPin + case .noPin: + return Config.BluetoothConfig.PairingMode.noPin + } + } +} + +struct BluetoothConfig: 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 enabled = true + /// Determines the pairing strategy for the device + @State var mode = 0 + + /// Specified pin for PairingMode.FixedPin + @State var fixedPin = 123456 + + var body: some View { + + VStack { + + Form { + + Section(header: Text("Options")) { + + Toggle(isOn: $enabled) { + + Label("Enabled", systemImage: "antenna.radiowaves.left.and.right") + } + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + + + Picker("Pairing PIN", selection: $mode ) { + ForEach(BluetoothModes.allCases) { bm in + Text(bm.description) + } + } + .pickerStyle(DefaultPickerStyle()) + + if mode == 2 { + + + } + } + } + .disabled(bleManager.connectedPeripheral == nil) + + 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 Bluetooth Config to \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")?") { + + var bc = Config.BluetoothConfig() + bc.enabled = enabled + bc.mode = BluetoothModes(rawValue: mode)?.protoEnumValue() ?? Config.BluetoothConfig.PairingMode.randomPin + bc.fixedPin = UInt32(fixedPin) + + let adminMessageId = 0//bleManager.saveBluetoothConfig(config: bc, fromUser: node!.user!, toUser: node!.user!) + + if adminMessageId > 0 { + + // 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: + + ZStack { + + ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") + }) + .onAppear { + + if self.initialLoad{ + + self.bleManager.context = context + + self.enabled = node!.bluetoothConfig?.enabled ?? true + self.mode = Int(node!.bluetoothConfig?.mode ?? 0) + self.fixedPin = Int(node!.bluetoothConfig?.fixedPin ?? 123456) + self.hasChanges = false + self.initialLoad = false + } + } + .onChange(of: enabled) { newEnabled in + + if node != nil && node!.bluetoothConfig != nil { + + if newEnabled != node!.bluetoothConfig!.enabled { hasChanges = true } + } + } + .onChange(of: mode) { newMode in + + if node != nil && node!.bluetoothConfig != nil { + + if newMode != node!.bluetoothConfig!.mode { hasChanges = true } + } + } + .onChange(of: fixedPin) { newFixedPin in + + if node != nil && node!.bluetoothConfig != nil { + + if newFixedPin != node!.bluetoothConfig!.fixedPin { hasChanges = true } + } + } + + .navigationViewStyle(StackNavigationViewStyle()) + } +} diff --git a/Meshtastic/Views/Settings/Settings.swift b/Meshtastic/Views/Settings/Settings.swift index e1887074..db8d7702 100644 --- a/Meshtastic/Views/Settings/Settings.swift +++ b/Meshtastic/Views/Settings/Settings.swift @@ -69,6 +69,18 @@ struct Settings: View { } .disabled(bleManager.connectedPeripheral == nil) + NavigationLink() { + + BluetoothConfig(node: nodes.first(where: { $0.num == connectedNodeNum })) + } label: { + + Image(systemName: "antenna.radiowaves.left.and.right") + .symbolRenderingMode(.hierarchical) + + Text("Bluetooth (BLE)") + } + .disabled(bleManager.connectedPeripheral == nil) + NavigationLink { DeviceConfig(node: nodes.first(where: { $0.num == connectedNodeNum })) } label: { From 91ad2585907f7e702f5b0b094808563de50ff463 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Fri, 19 Aug 2022 23:26:02 -0700 Subject: [PATCH 2/4] Add Distance to node list --- Meshtastic.xcodeproj/project.pbxproj | 8 ++++ Meshtastic/Enums/BluetoothModes.swift | 39 ++++++++++++++++++ Meshtastic/Views/Helpers/DistanceText.swift | 22 ++++++++++ Meshtastic/Views/Nodes/NodeList.swift | 41 +++++++++++++++---- .../Settings/Config/BluetoothConfig.swift | 33 --------------- .../Settings/Config/PositionConfig.swift | 7 ++++ 6 files changed, 108 insertions(+), 42 deletions(-) create mode 100644 Meshtastic/Enums/BluetoothModes.swift create mode 100644 Meshtastic/Views/Helpers/DistanceText.swift diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 8cd75056..c22230c2 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -71,6 +71,8 @@ DDB2CC6E27F3EB47009C5FCC /* telemetry.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB2CC6D27F3EB47009C5FCC /* telemetry.pb.swift */; }; DDB3107228A6224100F1DE3D /* device_metadata.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB3107128A6224100F1DE3D /* device_metadata.pb.swift */; }; DDB6ABD628AE742000384BA1 /* BluetoothConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB6ABD528AE742000384BA1 /* BluetoothConfig.swift */; }; + DDB6ABD928B0A4BA00384BA1 /* BluetoothModes.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB6ABD828B0A4BA00384BA1 /* BluetoothModes.swift */; }; + DDB6ABDB28B0AC6000384BA1 /* DistanceText.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB6ABDA28B0AC6000384BA1 /* DistanceText.swift */; }; DDC2E15826CE248E0042C5E4 /* MeshtasticApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC2E15726CE248E0042C5E4 /* MeshtasticApp.swift */; }; DDC2E15C26CE248F0042C5E4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DDC2E15B26CE248F0042C5E4 /* Assets.xcassets */; }; DDC2E15F26CE248F0042C5E4 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DDC2E15E26CE248F0042C5E4 /* Preview Assets.xcassets */; }; @@ -174,6 +176,8 @@ DDB3107128A6224100F1DE3D /* device_metadata.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = device_metadata.pb.swift; sourceTree = ""; }; DDB6ABD528AE742000384BA1 /* BluetoothConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BluetoothConfig.swift; sourceTree = ""; }; DDB6ABD728AE8F5D00384BA1 /* MeshtasticDataModel v 7.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModel v 7.xcdatamodel"; sourceTree = ""; }; + DDB6ABD828B0A4BA00384BA1 /* BluetoothModes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BluetoothModes.swift; sourceTree = ""; }; + DDB6ABDA28B0AC6000384BA1 /* DistanceText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DistanceText.swift; sourceTree = ""; }; DDC2E15426CE248E0042C5E4 /* Meshtastic.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Meshtastic.app; sourceTree = BUILT_PRODUCTS_DIR; }; DDC2E15726CE248E0042C5E4 /* MeshtasticApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshtasticApp.swift; sourceTree = ""; }; DDC2E15B26CE248F0042C5E4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = ../Assets.xcassets; sourceTree = ""; }; @@ -318,6 +322,7 @@ isa = PBXGroup; children = ( DD8ED9C7289CE4B900B3B0AB /* RoutingError.swift */, + DDB6ABD828B0A4BA00384BA1 /* BluetoothModes.swift */, ); path = Enums; sourceTree = ""; @@ -470,6 +475,7 @@ DDC3B273283F411B00AC321C /* LastHeardText.swift */, DDA6B2EA28420A7B003E8C16 /* NodeAnnotation.swift */, DDD94A4F2845C8F5004A87A0 /* DateTimeText.swift */, + DDB6ABDA28B0AC6000384BA1 /* DistanceText.swift */, ); path = Helpers; sourceTree = ""; @@ -687,6 +693,7 @@ DD1BF2F92776FE2E008C8D2F /* UserMessageList.swift in Sources */, DDB2CC6E27F3EB47009C5FCC /* telemetry.pb.swift in Sources */, DD23A50F26FD1B4400D9B90C /* PeripheralModel.swift in Sources */, + DDB6ABDB28B0AC6000384BA1 /* DistanceText.swift in Sources */, C9A7BC1027759A9600760B50 /* PositionAnnotationView.swift in Sources */, DD882F5D2772E4640005BF05 /* Contacts.swift in Sources */, DD47E3CE26F103C600029299 /* NodeList.swift in Sources */, @@ -695,6 +702,7 @@ DDC2E18F26CE25FE0042C5E4 /* ContentView.swift in Sources */, DD17E5DE277D49D400010EC2 /* storeforward.pb.swift in Sources */, DD2553572855B02500E55709 /* LoRaConfig.swift in Sources */, + DDB6ABD928B0A4BA00384BA1 /* BluetoothModes.swift in Sources */, DDD9E4E4284B208E003777C5 /* UserEntityExtension.swift in Sources */, C9A88B55278B503C00BD810A /* MapViewModule.swift in Sources */, DD2553592855B52700E55709 /* PositionConfig.swift in Sources */, diff --git a/Meshtastic/Enums/BluetoothModes.swift b/Meshtastic/Enums/BluetoothModes.swift new file mode 100644 index 00000000..003b0a3f --- /dev/null +++ b/Meshtastic/Enums/BluetoothModes.swift @@ -0,0 +1,39 @@ +// +// BluetoothModes.swift +// Meshtastic +// +// Copyright(c) Garth Vander Houwen 8/19/22. +// + +enum BluetoothModes: Int, CaseIterable, Identifiable { + + case randomPin = 0 + case fixedPin = 1 + case noPin = 2 + + var id: Int { self.rawValue } + var description: String { + get { + switch self { + case .randomPin: + return "Random" + case .fixedPin: + return "Fixed" + case .noPin: + return "None" + } + } + } + func protoEnumValue() -> Config.BluetoothConfig.PairingMode { + + switch self { + + case .randomPin: + return Config.BluetoothConfig.PairingMode.randomPin + case .fixedPin: + return Config.BluetoothConfig.PairingMode.fixedPin + case .noPin: + return Config.BluetoothConfig.PairingMode.noPin + } + } +} diff --git a/Meshtastic/Views/Helpers/DistanceText.swift b/Meshtastic/Views/Helpers/DistanceText.swift new file mode 100644 index 00000000..1709b5f4 --- /dev/null +++ b/Meshtastic/Views/Helpers/DistanceText.swift @@ -0,0 +1,22 @@ +// +// DistanceText.swift +// Meshtastic +// +// Copyright(c) Garth Vander Houwen 8/19/22. +// + +import SwiftUI +import CoreLocation +import MapKit + +struct DistanceText: View { + + var meters: CLLocationDistance + + var body: some View { + + let distanceFormatter = MKDistanceFormatter() + + Text("Distance: \(distanceFormatter.string(fromDistance: Double(meters)))") + } +} diff --git a/Meshtastic/Views/Nodes/NodeList.swift b/Meshtastic/Views/Nodes/NodeList.swift index 95755bd4..16009570 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -9,6 +9,7 @@ // A view showing a list of devices that have been seen on the mesh network from the perspective of the connected device. import SwiftUI +import CoreLocation struct NodeList: View { @@ -35,8 +36,8 @@ struct NodeList: View { if nodes.count == 0 { Text("Scan for Radios").font(.largeTitle) - Text("No LoRa Mesh Nodes Found").font(.title2) - Text("Go to the bluetooth section in the bottom right menu and click the Start Scanning button to scan for nearby radios and find your Meshtastic device. Make sure your device is powered on and near your phone or tablet.") + Text("No Meshtastic Nodes Found").font(.title2) + Text("Go to the bluetooth section in the bottom right menu and click the Start Scanning button to scan for nearby radios and find your Meshtastic device. Make sure your device is powered on and near your iPhone, iPad or Mac.") .font(.body) Text("Once the device shows under Available Devices touch the device you want to connect to and it will pull node information over BLE and populate the node list and mesh map in the Meshtastic app.") Text("Views with bluetooth functionality will show an indicator in the upper right hand corner show if bluetooth is on, and if a device is connected.") @@ -87,14 +88,43 @@ struct NodeList: View { Image(systemName: "clock.badge.checkmark.fill").font(.title3) .foregroundColor(.accentColor).symbolRenderingMode(.hierarchical) + if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac { LastHeardText(lastHeard: node.lastHeard).font(.subheadline).foregroundColor(.gray) + } else { LastHeardText(lastHeard: node.lastHeard).font(.title3).foregroundColor(.gray) } } + + if node.positions?.count ?? 0 > 0 { + + Spacer() + HStack(alignment: .bottom) { + + let lastPostion = node.positions!.reversed()[0] as! PositionEntity + + let myCoord = CLLocation(latitude: LocationHelper.currentLocation.latitude, longitude: LocationHelper.currentLocation.longitude) + + let nodeCoord = CLLocation(latitude: lastPostion.coordinate!.latitude, longitude: lastPostion.coordinate!.longitude) + + let metersAway = nodeCoord.distance(from: myCoord) + + Image(systemName: "lines.measurement.horizontal").font(.title3) + .foregroundColor(.accentColor).symbolRenderingMode(.hierarchical) + + if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac { + + DistanceText(meters: metersAway).font(.subheadline).foregroundColor(.gray) + + } else { + + DistanceText(meters: metersAway).font(.title3).foregroundColor(.gray) + } + } + } } .padding([.leading, .top, .bottom]) } @@ -109,13 +139,6 @@ struct NodeList: View { self.bleManager.userSettings = userSettings self.bleManager.context = context self.initialLoad = false - -// if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac { -// -// if nodes.count > 0 { -// selection = "0" -// } -// } } } } diff --git a/Meshtastic/Views/Settings/Config/BluetoothConfig.swift b/Meshtastic/Views/Settings/Config/BluetoothConfig.swift index 1057f1eb..5f5c7e8f 100644 --- a/Meshtastic/Views/Settings/Config/BluetoothConfig.swift +++ b/Meshtastic/Views/Settings/Config/BluetoothConfig.swift @@ -7,39 +7,6 @@ import SwiftUI -enum BluetoothModes: Int, CaseIterable, Identifiable { - - case randomPin = 0 - case fixedPin = 1 - case noPin = 2 - - var id: Int { self.rawValue } - var description: String { - get { - switch self { - case .randomPin: - return "Random" - case .fixedPin: - return "Fixed" - case .noPin: - return "None" - } - } - } - func protoEnumValue() -> Config.BluetoothConfig.PairingMode { - - switch self { - - case .randomPin: - return Config.BluetoothConfig.PairingMode.randomPin - case .fixedPin: - return Config.BluetoothConfig.PairingMode.fixedPin - case .noPin: - return Config.BluetoothConfig.PairingMode.noPin - } - } -} - struct BluetoothConfig: View { @Environment(\.managedObjectContext) var context diff --git a/Meshtastic/Views/Settings/Config/PositionConfig.swift b/Meshtastic/Views/Settings/Config/PositionConfig.swift index a2348bea..19201fda 100644 --- a/Meshtastic/Views/Settings/Config/PositionConfig.swift +++ b/Meshtastic/Views/Settings/Config/PositionConfig.swift @@ -370,6 +370,13 @@ struct PositionConfig: View { if newSmartPosition != node!.positionConfig!.smartPositionEnabled { hasChanges = true } } } + .onChange(of: positionBroadcastSeconds) { newPositionBroadcastSeconds in + + if node != nil && node!.positionConfig != nil { + + if newPositionBroadcastSeconds != node!.positionConfig!.positionBroadcastSeconds { hasChanges = true } + } + } .onChange(of: deviceGpsEnabled) { newDeviceGps in if node != nil && node!.positionConfig != nil { From 07f28252fd7c50b2415b608ca92b8fdee3026060 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sat, 20 Aug 2022 12:15:14 -0700 Subject: [PATCH 3/4] Move enums, add distance to node list work on BLE settings --- Meshtastic.xcodeproj/project.pbxproj | 22 ++- Meshtastic/Enums/BluetoothModes.swift | 6 +- Meshtastic/Enums/DeviceRoles.swift | 48 +++++ Meshtastic/Enums/GpsFormats.swift | 56 ++++++ Meshtastic/Enums/LoraConfigEnums.swift | 181 ++++++++++++++++++ Meshtastic/Enums/ScreenIntervals.swift | 73 +++++++ Meshtastic/Enums/WiFiModes.swift | 42 ++++ Meshtastic/Helpers/Extensions.swift | 11 ++ Meshtastic/Helpers/MeshPackets.swift | 91 ++++++++- .../Settings/Config/BluetoothConfig.swift | 40 +++- .../Views/Settings/Config/DeviceConfig.swift | 40 ---- .../Views/Settings/Config/DisplayConfig.swift | 113 ----------- .../Views/Settings/Config/LoRaConfig.swift | 173 ----------------- .../Views/Settings/Config/WiFiConfig.swift | 38 +--- 14 files changed, 559 insertions(+), 375 deletions(-) create mode 100644 Meshtastic/Enums/DeviceRoles.swift create mode 100644 Meshtastic/Enums/GpsFormats.swift create mode 100644 Meshtastic/Enums/LoraConfigEnums.swift create mode 100644 Meshtastic/Enums/ScreenIntervals.swift create mode 100644 Meshtastic/Enums/WiFiModes.swift diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index c22230c2..e21580b3 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -73,6 +73,11 @@ DDB6ABD628AE742000384BA1 /* BluetoothConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB6ABD528AE742000384BA1 /* BluetoothConfig.swift */; }; DDB6ABD928B0A4BA00384BA1 /* BluetoothModes.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB6ABD828B0A4BA00384BA1 /* BluetoothModes.swift */; }; DDB6ABDB28B0AC6000384BA1 /* DistanceText.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB6ABDA28B0AC6000384BA1 /* DistanceText.swift */; }; + DDB6ABE028B13AC700384BA1 /* DeviceRoles.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB6ABDF28B13AC700384BA1 /* DeviceRoles.swift */; }; + DDB6ABE228B13FB500384BA1 /* GpsFormats.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB6ABE128B13FB500384BA1 /* GpsFormats.swift */; }; + DDB6ABE428B13FFF00384BA1 /* ScreenIntervals.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB6ABE328B13FFF00384BA1 /* ScreenIntervals.swift */; }; + DDB6ABE628B1406100384BA1 /* LoraConfigEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB6ABE528B1406100384BA1 /* LoraConfigEnums.swift */; }; + DDB6ABE828B141AF00384BA1 /* WiFiModes.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB6ABE728B141AF00384BA1 /* WiFiModes.swift */; }; DDC2E15826CE248E0042C5E4 /* MeshtasticApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC2E15726CE248E0042C5E4 /* MeshtasticApp.swift */; }; DDC2E15C26CE248F0042C5E4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DDC2E15B26CE248F0042C5E4 /* Assets.xcassets */; }; DDC2E15F26CE248F0042C5E4 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DDC2E15E26CE248F0042C5E4 /* Preview Assets.xcassets */; }; @@ -178,6 +183,11 @@ DDB6ABD728AE8F5D00384BA1 /* MeshtasticDataModel v 7.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModel v 7.xcdatamodel"; sourceTree = ""; }; DDB6ABD828B0A4BA00384BA1 /* BluetoothModes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BluetoothModes.swift; sourceTree = ""; }; DDB6ABDA28B0AC6000384BA1 /* DistanceText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DistanceText.swift; sourceTree = ""; }; + DDB6ABDF28B13AC700384BA1 /* DeviceRoles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceRoles.swift; sourceTree = ""; }; + DDB6ABE128B13FB500384BA1 /* GpsFormats.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GpsFormats.swift; sourceTree = ""; }; + DDB6ABE328B13FFF00384BA1 /* ScreenIntervals.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenIntervals.swift; sourceTree = ""; }; + DDB6ABE528B1406100384BA1 /* LoraConfigEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoraConfigEnums.swift; sourceTree = ""; }; + DDB6ABE728B141AF00384BA1 /* WiFiModes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WiFiModes.swift; sourceTree = ""; }; DDC2E15426CE248E0042C5E4 /* Meshtastic.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Meshtastic.app; sourceTree = BUILT_PRODUCTS_DIR; }; DDC2E15726CE248E0042C5E4 /* MeshtasticApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshtasticApp.swift; sourceTree = ""; }; DDC2E15B26CE248F0042C5E4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = ../Assets.xcassets; sourceTree = ""; }; @@ -321,8 +331,13 @@ DD8ED9C6289CE4A100B3B0AB /* Enums */ = { isa = PBXGroup; children = ( - DD8ED9C7289CE4B900B3B0AB /* RoutingError.swift */, DDB6ABD828B0A4BA00384BA1 /* BluetoothModes.swift */, + DDB6ABDF28B13AC700384BA1 /* DeviceRoles.swift */, + DDB6ABE528B1406100384BA1 /* LoraConfigEnums.swift */, + DDB6ABE128B13FB500384BA1 /* GpsFormats.swift */, + DD8ED9C7289CE4B900B3B0AB /* RoutingError.swift */, + DDB6ABE328B13FFF00384BA1 /* ScreenIntervals.swift */, + DDB6ABE728B141AF00384BA1 /* WiFiModes.swift */, ); path = Enums; sourceTree = ""; @@ -672,6 +687,7 @@ DD5394FE276BA0EF00AD86B1 /* PositionEntityExtension.swift in Sources */, DD913639270DFF4C00D7ACF3 /* LocalNotificationManager.swift in Sources */, DD4C158C2824A91E0032668E /* module_config.pb.swift in Sources */, + DDB6ABE828B141AF00384BA1 /* WiFiModes.swift in Sources */, DD4F23CD28779A3C001D37CB /* TelemetryLog.swift in Sources */, DD6B85A828009258000ACD6B /* ShareChannel.swift in Sources */, DDB6ABD628AE742000384BA1 /* BluetoothConfig.swift in Sources */, @@ -691,6 +707,7 @@ DD6193772862F90F00E59241 /* CannedMessagesConfig.swift in Sources */, DD41582628582E9B009B0E59 /* DeviceConfig.swift in Sources */, DD1BF2F92776FE2E008C8D2F /* UserMessageList.swift in Sources */, + DDB6ABE628B1406100384BA1 /* LoraConfigEnums.swift in Sources */, DDB2CC6E27F3EB47009C5FCC /* telemetry.pb.swift in Sources */, DD23A50F26FD1B4400D9B90C /* PeripheralModel.swift in Sources */, DDB6ABDB28B0AC6000384BA1 /* DistanceText.swift in Sources */, @@ -708,6 +725,7 @@ DD2553592855B52700E55709 /* PositionConfig.swift in Sources */, DDB3107228A6224100F1DE3D /* device_metadata.pb.swift in Sources */, DDAF8C6326ED0A230058C060 /* admin.pb.swift in Sources */, + DDB6ABE028B13AC700384BA1 /* DeviceRoles.swift in Sources */, DD86D40C287F401000BAEB7A /* SaveChannelQRCode.swift in Sources */, DD8ED9C8289CE4B900B3B0AB /* RoutingError.swift in Sources */, C9483F6D2773017500998F6B /* MapView.swift in Sources */, @@ -721,11 +739,13 @@ DDA6B2E928419CF2003E8C16 /* MeshPackets.swift in Sources */, DDCE4E2C2869F92900BE9F8F /* UserConfig.swift in Sources */, DD6193752862F6E600E59241 /* ExternalNotificationConfig.swift in Sources */, + DDB6ABE428B13FFF00384BA1 /* ScreenIntervals.swift in Sources */, DD86D40A287F04F100BAEB7A /* InvalidVersion.swift in Sources */, DDD94A502845C8F5004A87A0 /* DateTimeText.swift in Sources */, C9A88B57278B559900BD810A /* apponly.pb.swift in Sources */, DD4C158E2824AA7E0032668E /* config.pb.swift in Sources */, DD47E3D926F3093800029299 /* MessageBubble.swift in Sources */, + DDB6ABE228B13FB500384BA1 /* GpsFormats.swift in Sources */, DD415828285859C4009B0E59 /* TelemetryConfig.swift in Sources */, DD73FD1128750779000852D6 /* LocationHistory.swift in Sources */, C9697F9D279336B700250207 /* LocalMBTileOverlay.swift in Sources */, diff --git a/Meshtastic/Enums/BluetoothModes.swift b/Meshtastic/Enums/BluetoothModes.swift index 003b0a3f..c631defa 100644 --- a/Meshtastic/Enums/BluetoothModes.swift +++ b/Meshtastic/Enums/BluetoothModes.swift @@ -16,11 +16,11 @@ enum BluetoothModes: Int, CaseIterable, Identifiable { get { switch self { case .randomPin: - return "Random" + return "Random PIN" case .fixedPin: - return "Fixed" + return "Fixed PIN" case .noPin: - return "None" + return "No PIN (Just Works)" } } } diff --git a/Meshtastic/Enums/DeviceRoles.swift b/Meshtastic/Enums/DeviceRoles.swift new file mode 100644 index 00000000..ba793bd3 --- /dev/null +++ b/Meshtastic/Enums/DeviceRoles.swift @@ -0,0 +1,48 @@ +// +// DeviceRoles.swift +// Meshtastic +// +// Copyright(c) Garth Vander Houwen 8/20/22. +// + +import Foundation + +// Default of 0 is Client +enum DeviceRoles: Int, CaseIterable, Identifiable { + + case client = 0 + case clientMute = 1 + case router = 2 + case routerClient = 3 + + var id: Int { self.rawValue } + var description: String { + get { + switch self { + + case .client: + 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." + 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 + } + } +} diff --git a/Meshtastic/Enums/GpsFormats.swift b/Meshtastic/Enums/GpsFormats.swift new file mode 100644 index 00000000..5f3dd4f6 --- /dev/null +++ b/Meshtastic/Enums/GpsFormats.swift @@ -0,0 +1,56 @@ +// +// GpsFormats.swift +// Meshtastic +// +// Copyright(c) Garth Vander Houwen 8/20/22. +// + +import Foundation + +enum GpsFormats: Int, CaseIterable, Identifiable { + + case gpsFormatDec = 0 + case gpsFormatDms = 1 + case gpsFormatUtm = 2 + case gpsFormatMgrs = 3 + case gpsFormatOlc = 4 + case gpsFormatOsgr = 5 + + var id: Int { self.rawValue } + var description: String { + get { + switch self { + case .gpsFormatDec: + return "Decimal Degrees Format" + case .gpsFormatDms: + return "Degrees Minutes Seconds" + case .gpsFormatUtm: + return "Universal Transverse Mercator" + case .gpsFormatMgrs: + return "Military Grid Reference System" + case .gpsFormatOlc: + return "Open Location Code (aka Plus Codes)" + case .gpsFormatOsgr: + return "Ordnance Survey Grid Reference" + } + } + } + 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 + } + } +} diff --git a/Meshtastic/Enums/LoraConfigEnums.swift b/Meshtastic/Enums/LoraConfigEnums.swift new file mode 100644 index 00000000..15615541 --- /dev/null +++ b/Meshtastic/Enums/LoraConfigEnums.swift @@ -0,0 +1,181 @@ +// +// LoraConfig.swift +// Meshtastic +// +// Copyright(c) Garth Vander Houwen 8/20/22. +// + +import Foundation + +enum RegionCodes : Int, CaseIterable, Identifiable { + + case unset = 0 + case us = 1 + case eu433 = 2 + case eu868 = 3 + case cn = 4 + case jp = 5 + case anz = 6 + case kr = 7 + case tw = 8 + case ru = 9 + case `in` = 10 + case nz865 = 11 + case th = 12 + + var id: Int { self.rawValue } + var description: String { + get { + switch self { + case .unset: + return "Please set a region" + case .us: + return "United States" + case .eu433: + return "European Union 433mhz" + case .eu868: + return "European Union 868mhz" + case .cn: + return "China" + case .jp: + return "Japan" + case .anz: + return "Australia / New Zealand" + case .kr: + return "Korea" + case .tw: + return "Taiwan" + case .ru: + return "Russia" + case .in: + return "India" + case .nz865: + return "New Zealand 865mhz" + case .th: + return "Thailand" + } + } + } + + func protoEnumValue() -> Config.LoRaConfig.RegionCode { + + switch self { + + case .unset: + return Config.LoRaConfig.RegionCode.unset + case .us: + return Config.LoRaConfig.RegionCode.us + case .eu433: + return Config.LoRaConfig.RegionCode.eu433 + case .eu868: + return Config.LoRaConfig.RegionCode.eu868 + case .cn: + return Config.LoRaConfig.RegionCode.cn + case .jp: + return Config.LoRaConfig.RegionCode.jp + case .anz: + return Config.LoRaConfig.RegionCode.anz + case .kr: + return Config.LoRaConfig.RegionCode.kr + case .tw: + return Config.LoRaConfig.RegionCode.tw + case .ru: + return Config.LoRaConfig.RegionCode.ru + case .in: + return Config.LoRaConfig.RegionCode.in + case .nz865: + return Config.LoRaConfig.RegionCode.nz865 + case .th: + return Config.LoRaConfig.RegionCode.th + } + } +} + +enum ModemPresets : Int, CaseIterable, Identifiable { + + case LongFast = 0 + case LongSlow = 1 + case VLongSlow = 2 + case MedSlow = 3 + case MedFast = 4 + case ShortSlow = 5 + case ShortFast = 6 + + var id: Int { self.rawValue } + var description: String { + get { + switch self { + + case .LongFast: + return "Long Range - Fast" + case .LongSlow: + return "Long Range - Slow" + case .VLongSlow: + return "Very Long Range - Slow" + case .MedSlow: + return "Medium Range - Slow" + case .MedFast: + return "Medium Range - Fast" + case .ShortSlow: + return "Short Range - Slow" + case .ShortFast: + return "Short Range - Fast" + } + } + } + func protoEnumValue() -> Config.LoRaConfig.ModemPreset { + + switch self { + + case .LongFast: + return Config.LoRaConfig.ModemPreset.longFast + case .LongSlow: + return Config.LoRaConfig.ModemPreset.longSlow + case .VLongSlow: + return Config.LoRaConfig.ModemPreset.vlongSlow + case .MedSlow: + return Config.LoRaConfig.ModemPreset.medSlow + case .MedFast: + return Config.LoRaConfig.ModemPreset.medFast + case .ShortSlow: + return Config.LoRaConfig.ModemPreset.shortSlow + case .ShortFast: + return Config.LoRaConfig.ModemPreset.shortFast + + } + } +} + +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" + } + } + } +} diff --git a/Meshtastic/Enums/ScreenIntervals.swift b/Meshtastic/Enums/ScreenIntervals.swift new file mode 100644 index 00000000..74731e11 --- /dev/null +++ b/Meshtastic/Enums/ScreenIntervals.swift @@ -0,0 +1,73 @@ +// +// ScreenIntervals.swift +// Meshtastic +// +// Copyright(c) Garth Vander Houwen 8/20/22. +// + +import Foundation + +// Default of 0 is One Minute +enum ScreenOnIntervals: Int, CaseIterable, Identifiable { + + case oneMinute = 60 + case fiveMinutes = 300 + case tenMinutes = 0 + case fifteenMinutes = 900 + case thirtyMinutes = 1800 + case oneHour = 3600 + case max = 31536000 // One Year + + 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" + case .thirtyMinutes: + return "Thirty Minutes" + case .oneHour: + return "One Hour" + case .max: + return "Always On" + } + } + } +} + +// Default of 0 is off +enum ScreenCarouselIntervals: Int, CaseIterable, Identifiable { + + case off = 0 + case thirtySeconds = 30 + case oneMinute = 60 + case fiveMinutes = 300 + case tenMinutes = 600 + case fifteenMinutes = 900 + + var id: Int { self.rawValue } + var description: String { + get { + switch self { + case .off: + return "Off" + case .thirtySeconds: + return "Thirty Seconds" + case .oneMinute: + return "One Minute" + case .fiveMinutes: + return "Five Minutes" + case .tenMinutes: + return "Ten Minutes" + case .fifteenMinutes: + return "Fifteen Minutes" + } + } + } +} diff --git a/Meshtastic/Enums/WiFiModes.swift b/Meshtastic/Enums/WiFiModes.swift new file mode 100644 index 00000000..cfb33099 --- /dev/null +++ b/Meshtastic/Enums/WiFiModes.swift @@ -0,0 +1,42 @@ +// +// WiFiModes.swift +// Meshtastic +// +// Copyright(c) Garth Vander Houwen 8/20/22. +// + +import Foundation + +enum WiFiModes: Int, CaseIterable, Identifiable { + + case client = 0 + case accessPoint = 1 + case accessPointHidden = 2 + + var id: Int { self.rawValue } + var description: String { + get { + switch self { + case .client: + return "Client" + case .accessPoint: + return "Software Access Point" + case .accessPointHidden: + return "Software Access Point (Hidden)" + + } + } + } + func protoEnumValue() -> Config.WiFiConfig.WiFiMode { + + switch self { + + case .client: + return Config.WiFiConfig.WiFiMode.client + case .accessPoint: + return Config.WiFiConfig.WiFiMode.accessPoint + case .accessPointHidden: + return Config.WiFiConfig.WiFiMode.accessPointHidden + } + } +} diff --git a/Meshtastic/Helpers/Extensions.swift b/Meshtastic/Helpers/Extensions.swift index 42b350e0..8f6abe9e 100644 --- a/Meshtastic/Helpers/Extensions.swift +++ b/Meshtastic/Helpers/Extensions.swift @@ -23,6 +23,17 @@ extension Date { } } +extension Int { + + func numberOfDigits() -> Int { + if abs(self) < 10 { + return 1 + } else { + return 1 + (self/10).numberOfDigits() + } + } +} + extension String { /// Create `Data` from hexadecimal string representation diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index dbc7eb0c..1f8fb415 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -92,6 +92,92 @@ func localConfig (config: Config, meshlogging: Bool, context:NSManagedObjectCont } } + if config.payloadVariant == Config.OneOf_PayloadVariant.bluetooth(config.bluetooth) { + + var isDefault = false + + if (try! config.bluetooth.jsonString()) == "{}" { + + isDefault = true + print("📶 Default Bluetooth config") + if meshlogging { MeshLogger.log("🖥️ Default Bluetooth config \(String(nodeNum))") } + + } else { + + if meshlogging { MeshLogger.log("🖥️ Custom Bluetooth config \(String(nodeNum))") } + print("📶 Custom Bluetooth 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].bluetoothConfig == nil { + + let newBluetoothConfig = BluetoothConfigEntity(context: context) + + if isDefault { + + newBluetoothConfig.enabled = true + newBluetoothConfig.mode = Int32(config.bluetooth.mode.rawValue) + newBluetoothConfig.fixedPin = Int32("123456") ?? 123456 + + } else { + + newBluetoothConfig.enabled = config.bluetooth.enabled + newBluetoothConfig.mode = Int32(config.bluetooth.mode.rawValue) + newBluetoothConfig.fixedPin = Int32(config.display.autoScreenCarouselSecs) + + } + fetchedNode[0].bluetoothConfig = newBluetoothConfig + + } else { + + if isDefault { + + fetchedNode[0].displayConfig?.screenOnSeconds = 0 + fetchedNode[0].displayConfig?.screenCarouselInterval = 0 + fetchedNode[0].displayConfig?.gpsFormat = 0 + fetchedNode[0].displayConfig?.compassNorthTop = false + + } 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) + fetchedNode[0].displayConfig?.compassNorthTop = config.display.compassNorthTop + } + } + + 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)") + } + } else { + + print("💥 No Nodes found in local database matching node number \(nodeNum) unable to save Display Config") + } + + } catch { + + let nsError = error as NSError + print("💥 Fetching node for core data DisplayConfigEntity failed: \(nsError)") + } + } + if config.payloadVariant == Config.OneOf_PayloadVariant.display(config.display) { var isDefault = false @@ -99,11 +185,12 @@ func localConfig (config: Config, meshlogging: Bool, context:NSManagedObjectCont if (try! config.display.jsonString()) == "{}" { isDefault = true - print("🖥️ Default Display config") + + if meshlogging { MeshLogger.log("🖥️ Default Display config \(String(nodeNum))") } } else { - print("🖥️ Custom Display config") + if meshlogging { MeshLogger.log("🖥️ Custom Display config \(String(nodeNum))") } } let fetchNodeInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") diff --git a/Meshtastic/Views/Settings/Config/BluetoothConfig.swift b/Meshtastic/Views/Settings/Config/BluetoothConfig.swift index 5f5c7e8f..7b01ea15 100644 --- a/Meshtastic/Views/Settings/Config/BluetoothConfig.swift +++ b/Meshtastic/Views/Settings/Config/BluetoothConfig.swift @@ -23,7 +23,13 @@ struct BluetoothConfig: View { @State var mode = 0 /// Specified pin for PairingMode.FixedPin - @State var fixedPin = 123456 + @State var fixedPin = "123456" + + let numberFormatter: NumberFormatter = { + let formatter = NumberFormatter() + formatter.numberStyle = .none + return formatter + }() var body: some View { @@ -40,16 +46,36 @@ struct BluetoothConfig: View { .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - Picker("Pairing PIN", selection: $mode ) { + Picker("Pairing Mode", selection: $mode ) { ForEach(BluetoothModes.allCases) { bm in Text(bm.description) } } .pickerStyle(DefaultPickerStyle()) - if mode == 2 { - + if mode == 1 { + HStack { + Label("Fixed PIN", systemImage: "wallet.pass") + TextField("Fixed PIN", text: $fixedPin) + .foregroundColor(.gray) + .onChange(of: fixedPin, perform: { value in + + let digitCount = fixedPin.utf8.count + // Only mess with the value if it is too big + if digitCount > 6 || digitCount < 6 { + + fixedPin = "123456" + } + + if digitCount < 6 { + + fixedPin = "123456" + } + }) + .foregroundColor(.gray) + } + .keyboardType(.decimalPad) } } } @@ -78,7 +104,7 @@ struct BluetoothConfig: View { var bc = Config.BluetoothConfig() bc.enabled = enabled bc.mode = BluetoothModes(rawValue: mode)?.protoEnumValue() ?? Config.BluetoothConfig.PairingMode.randomPin - bc.fixedPin = UInt32(fixedPin) + bc.fixedPin = UInt32(fixedPin) ?? 123456 let adminMessageId = 0//bleManager.saveBluetoothConfig(config: bc, fromUser: node!.user!, toUser: node!.user!) @@ -109,7 +135,7 @@ struct BluetoothConfig: View { self.enabled = node!.bluetoothConfig?.enabled ?? true self.mode = Int(node!.bluetoothConfig?.mode ?? 0) - self.fixedPin = Int(node!.bluetoothConfig?.fixedPin ?? 123456) + //self.fixedPin = (String(node!.bluetoothConfig?.fixedPin) ?? "123456") self.hasChanges = false self.initialLoad = false } @@ -132,7 +158,7 @@ struct BluetoothConfig: View { if node != nil && node!.bluetoothConfig != nil { - if newFixedPin != node!.bluetoothConfig!.fixedPin { hasChanges = true } + if newFixedPin != String(node!.bluetoothConfig!.fixedPin) { hasChanges = true } } } diff --git a/Meshtastic/Views/Settings/Config/DeviceConfig.swift b/Meshtastic/Views/Settings/Config/DeviceConfig.swift index 0d17f68f..809b25f8 100644 --- a/Meshtastic/Views/Settings/Config/DeviceConfig.swift +++ b/Meshtastic/Views/Settings/Config/DeviceConfig.swift @@ -6,46 +6,6 @@ // import SwiftUI -// Default of 0 is Client -enum DeviceRoles: Int, CaseIterable, Identifiable { - - case client = 0 - case clientMute = 1 - case router = 2 - case routerClient = 3 - - var id: Int { self.rawValue } - var description: String { - get { - switch self { - - case .client: - 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." - 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 diff --git a/Meshtastic/Views/Settings/Config/DisplayConfig.swift b/Meshtastic/Views/Settings/Config/DisplayConfig.swift index 13d9ab13..6a1e6e25 100644 --- a/Meshtastic/Views/Settings/Config/DisplayConfig.swift +++ b/Meshtastic/Views/Settings/Config/DisplayConfig.swift @@ -7,119 +7,6 @@ import SwiftUI -enum GpsFormats: Int, CaseIterable, Identifiable { - - case gpsFormatDec = 0 - case gpsFormatDms = 1 - case gpsFormatUtm = 2 - case gpsFormatMgrs = 3 - case gpsFormatOlc = 4 - case gpsFormatOsgr = 5 - - var id: Int { self.rawValue } - var description: String { - get { - switch self { - case .gpsFormatDec: - return "Decimal Degrees Format" - case .gpsFormatDms: - return "Degrees Minutes Seconds" - case .gpsFormatUtm: - return "Universal Transverse Mercator" - case .gpsFormatMgrs: - return "Military Grid Reference System" - case .gpsFormatOlc: - return "Open Location Code (aka Plus Codes)" - case .gpsFormatOsgr: - return "Ordnance Survey Grid Reference" - } - } - } - 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 -enum ScreenOnIntervals: Int, CaseIterable, Identifiable { - - case oneMinute = 60 - case fiveMinutes = 300 - case tenMinutes = 0 - case fifteenMinutes = 900 - case thirtyMinutes = 1800 - case oneHour = 3600 - case max = 31536000 // One Year - - 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" - case .thirtyMinutes: - return "Thirty Minutes" - case .oneHour: - return "One Hour" - case .max: - return "Always On" - } - } - } -} - -// Default of 0 is off -enum ScreenCarouselIntervals: Int, CaseIterable, Identifiable { - - case off = 0 - case thirtySeconds = 30 - case oneMinute = 60 - case fiveMinutes = 300 - case tenMinutes = 600 - case fifteenMinutes = 900 - - var id: Int { self.rawValue } - var description: String { - get { - switch self { - case .off: - return "Off" - case .thirtySeconds: - return "Thirty Seconds" - case .oneMinute: - return "One Minute" - case .fiveMinutes: - return "Five Minutes" - case .tenMinutes: - return "Ten Minutes" - case .fifteenMinutes: - return "Fifteen Minutes" - } - } - } -} - struct DisplayConfig: View { @Environment(\.managedObjectContext) var context diff --git a/Meshtastic/Views/Settings/Config/LoRaConfig.swift b/Meshtastic/Views/Settings/Config/LoRaConfig.swift index 9b30f94e..2e63c168 100644 --- a/Meshtastic/Views/Settings/Config/LoRaConfig.swift +++ b/Meshtastic/Views/Settings/Config/LoRaConfig.swift @@ -7,179 +7,6 @@ import SwiftUI -enum RegionCodes : Int, CaseIterable, Identifiable { - - case unset = 0 - case us = 1 - case eu433 = 2 - case eu868 = 3 - case cn = 4 - case jp = 5 - case anz = 6 - case kr = 7 - case tw = 8 - case ru = 9 - case `in` = 10 - case nz865 = 11 - case th = 12 - - var id: Int { self.rawValue } - var description: String { - get { - switch self { - case .unset: - return "Please set a region" - case .us: - return "United States" - case .eu433: - return "European Union 433mhz" - case .eu868: - return "European Union 868mhz" - case .cn: - return "China" - case .jp: - return "Japan" - case .anz: - return "Australia / New Zealand" - case .kr: - return "Korea" - case .tw: - return "Taiwan" - case .ru: - return "Russia" - case .in: - return "India" - case .nz865: - return "New Zealand 865mhz" - case .th: - return "Thailand" - } - } - } - - func protoEnumValue() -> Config.LoRaConfig.RegionCode { - - switch self { - - case .unset: - return Config.LoRaConfig.RegionCode.unset - case .us: - return Config.LoRaConfig.RegionCode.us - case .eu433: - return Config.LoRaConfig.RegionCode.eu433 - case .eu868: - return Config.LoRaConfig.RegionCode.eu868 - case .cn: - return Config.LoRaConfig.RegionCode.cn - case .jp: - return Config.LoRaConfig.RegionCode.jp - case .anz: - return Config.LoRaConfig.RegionCode.anz - case .kr: - return Config.LoRaConfig.RegionCode.kr - case .tw: - return Config.LoRaConfig.RegionCode.tw - case .ru: - return Config.LoRaConfig.RegionCode.ru - case .in: - return Config.LoRaConfig.RegionCode.in - case .nz865: - return Config.LoRaConfig.RegionCode.nz865 - case .th: - return Config.LoRaConfig.RegionCode.th - } - } -} - -enum ModemPresets : Int, CaseIterable, Identifiable { - - case LongFast = 0 - case LongSlow = 1 - case VLongSlow = 2 - case MedSlow = 3 - case MedFast = 4 - case ShortSlow = 5 - case ShortFast = 6 - - var id: Int { self.rawValue } - var description: String { - get { - switch self { - - case .LongFast: - return "Long Range - Fast" - case .LongSlow: - return "Long Range - Slow" - case .VLongSlow: - return "Very Long Range - Slow" - case .MedSlow: - return "Medium Range - Slow" - case .MedFast: - return "Medium Range - Fast" - case .ShortSlow: - return "Short Range - Slow" - case .ShortFast: - return "Short Range - Fast" - } - } - } - func protoEnumValue() -> Config.LoRaConfig.ModemPreset { - - switch self { - - case .LongFast: - return Config.LoRaConfig.ModemPreset.longFast - case .LongSlow: - return Config.LoRaConfig.ModemPreset.longSlow - case .VLongSlow: - return Config.LoRaConfig.ModemPreset.vlongSlow - case .MedSlow: - return Config.LoRaConfig.ModemPreset.medSlow - case .MedFast: - return Config.LoRaConfig.ModemPreset.medFast - case .ShortSlow: - return Config.LoRaConfig.ModemPreset.shortSlow - case .ShortFast: - return Config.LoRaConfig.ModemPreset.shortFast - - } - } -} - -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 diff --git a/Meshtastic/Views/Settings/Config/WiFiConfig.swift b/Meshtastic/Views/Settings/Config/WiFiConfig.swift index e322a72a..1378be55 100644 --- a/Meshtastic/Views/Settings/Config/WiFiConfig.swift +++ b/Meshtastic/Views/Settings/Config/WiFiConfig.swift @@ -7,40 +7,6 @@ import SwiftUI -enum WiFiModes: Int, CaseIterable, Identifiable { - - case client = 0 - case accessPoint = 1 - case accessPointHidden = 2 - - var id: Int { self.rawValue } - var description: String { - get { - switch self { - case .client: - return "Client" - case .accessPoint: - return "Software Access Point" - case .accessPointHidden: - return "Software Access Point (Hidden)" - - } - } - } - func protoEnumValue() -> Config.WiFiConfig.WiFiMode { - - switch self { - - case .client: - return Config.WiFiConfig.WiFiMode.client - case .accessPoint: - return Config.WiFiConfig.WiFiMode.accessPoint - case .accessPointHidden: - return Config.WiFiConfig.WiFiMode.accessPointHidden - } - } -} - struct WiFiConfig: View { @Environment(\.managedObjectContext) var context @@ -125,12 +91,12 @@ struct WiFiConfig: View { // Only mess with the value if it is too big if totalBytes > 63 { - let firstNBytes = Data(ssid.utf8.prefix(63)) + let firstNBytes = Data(password.utf8.prefix(63)) if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) { // Set the shortName back to the last place where it was the right size - ssid = maxBytesString + password = maxBytesString } } }) From af8f8ff9ac0531abbb99a5767df504a54dab73c2 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sat, 20 Aug 2022 12:31:52 -0700 Subject: [PATCH 4/4] Finish hooking up the ble config --- Meshtastic/Helpers/BLEManager.swift | 29 +++++++++++++++++++ Meshtastic/Helpers/MeshPackets.swift | 25 ++++++++-------- .../Settings/Config/BluetoothConfig.swift | 2 +- 3 files changed, 42 insertions(+), 14 deletions(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index a6a78c44..08a77c6c 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -1048,6 +1048,35 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph return 0 } + public func saveBluetoothConfig(config: Config.BluetoothConfig, fromUser: UserEntity, toUser: UserEntity) -> Int64 { + + var adminPacket = AdminMessage() + adminPacket.setConfig.bluetooth = config + + var meshPacket: MeshPacket = MeshPacket() + meshPacket.to = UInt32(connectedPeripheral.num) + meshPacket.from = 0 //UInt32(connectedPeripheral.num) + meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Int64 { var adminPacket = AdminMessage() diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index 1f8fb415..79419d51 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -131,7 +131,7 @@ func localConfig (config: Config, meshlogging: Bool, context:NSManagedObjectCont newBluetoothConfig.enabled = config.bluetooth.enabled newBluetoothConfig.mode = Int32(config.bluetooth.mode.rawValue) - newBluetoothConfig.fixedPin = Int32(config.display.autoScreenCarouselSecs) + newBluetoothConfig.fixedPin = Int32(config.bluetooth.fixedPin) } fetchedNode[0].bluetoothConfig = newBluetoothConfig @@ -140,41 +140,40 @@ func localConfig (config: Config, meshlogging: Bool, context:NSManagedObjectCont if isDefault { - fetchedNode[0].displayConfig?.screenOnSeconds = 0 - fetchedNode[0].displayConfig?.screenCarouselInterval = 0 - fetchedNode[0].displayConfig?.gpsFormat = 0 - fetchedNode[0].displayConfig?.compassNorthTop = false + fetchedNode[0].bluetoothConfig?.enabled = true + fetchedNode[0].bluetoothConfig?.mode = Int32(config.bluetooth.mode.rawValue) + fetchedNode[0].bluetoothConfig?.fixedPin = Int32("123456") ?? 123456 } 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) - fetchedNode[0].displayConfig?.compassNorthTop = config.display.compassNorthTop + fetchedNode[0].bluetoothConfig?.enabled = config.bluetooth.enabled + fetchedNode[0].bluetoothConfig?.mode = Int32(config.bluetooth.mode.rawValue) + fetchedNode[0].bluetoothConfig?.fixedPin = Int32(config.bluetooth.fixedPin) + } } do { try context.save() - if meshlogging { MeshLogger.log("💾 Updated Display Config for node number: \(String(nodeNum))") } + if meshlogging { MeshLogger.log("💾 Updated Bluetooth Config for node number: \(String(nodeNum))") } } catch { context.rollback() let nsError = error as NSError - print("💥 Error Updating Core Data DisplayConfigEntity: \(nsError)") + print("💥 Error Updating Core Data BluetoothConfigEntity: \(nsError)") } } else { - print("💥 No Nodes found in local database matching node number \(nodeNum) unable to save Display Config") + print("💥 No Nodes found in local database matching node number \(nodeNum) unable to save Bluetooth Config") } } catch { let nsError = error as NSError - print("💥 Fetching node for core data DisplayConfigEntity failed: \(nsError)") + print("💥 Fetching node for core data BluetoothConfigEntity failed: \(nsError)") } } diff --git a/Meshtastic/Views/Settings/Config/BluetoothConfig.swift b/Meshtastic/Views/Settings/Config/BluetoothConfig.swift index 7b01ea15..e75e4f1d 100644 --- a/Meshtastic/Views/Settings/Config/BluetoothConfig.swift +++ b/Meshtastic/Views/Settings/Config/BluetoothConfig.swift @@ -106,7 +106,7 @@ struct BluetoothConfig: View { bc.mode = BluetoothModes(rawValue: mode)?.protoEnumValue() ?? Config.BluetoothConfig.PairingMode.randomPin bc.fixedPin = UInt32(fixedPin) ?? 123456 - let adminMessageId = 0//bleManager.saveBluetoothConfig(config: bc, fromUser: node!.user!, toUser: node!.user!) + let adminMessageId = bleManager.saveBluetoothConfig(config: bc, fromUser: node!.user!, toUser: node!.user!) if adminMessageId > 0 {