diff --git a/Meshtastic Apple.xcodeproj/project.pbxproj b/Meshtastic Apple.xcodeproj/project.pbxproj index 6c012ab5..9a440c6e 100644 --- a/Meshtastic Apple.xcodeproj/project.pbxproj +++ b/Meshtastic Apple.xcodeproj/project.pbxproj @@ -18,6 +18,7 @@ DD23A50F26FD1B4400D9B90C /* PeripheralModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD23A50E26FD1B4400D9B90C /* PeripheralModel.swift */; }; DD2553572855B02500E55709 /* LoRaConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2553562855B02500E55709 /* LoRaConfig.swift */; }; DD2553592855B52700E55709 /* PositionConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2553582855B52700E55709 /* PositionConfig.swift */; }; + DD25535D285666C700E55709 /* PowerConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD25535C285666C700E55709 /* PowerConfig.swift */; }; DD2E65262767A01F00E45FC5 /* NodeDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2E65252767A01F00E45FC5 /* NodeDetail.swift */; }; DD3501892852FC3B000FC853 /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3501882852FC3B000FC853 /* Settings.swift */; }; DD35018B2852FC79000FC853 /* UserSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD35018A2852FC79000FC853 /* UserSettings.swift */; }; @@ -98,6 +99,7 @@ DD23A50E26FD1B4400D9B90C /* PeripheralModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeripheralModel.swift; sourceTree = ""; }; DD2553562855B02500E55709 /* LoRaConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoRaConfig.swift; sourceTree = ""; }; DD2553582855B52700E55709 /* PositionConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PositionConfig.swift; sourceTree = ""; }; + DD25535C285666C700E55709 /* PowerConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PowerConfig.swift; sourceTree = ""; }; DD2E65252767A01F00E45FC5 /* NodeDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeDetail.swift; sourceTree = ""; }; DD3501882852FC3B000FC853 /* Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = ""; }; DD35018A2852FC79000FC853 /* UserSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSettings.swift; sourceTree = ""; }; @@ -231,6 +233,7 @@ DD8EBF42285058FA00426DCA /* DisplayConfig.swift */, DD2553562855B02500E55709 /* LoRaConfig.swift */, DD2553582855B52700E55709 /* PositionConfig.swift */, + DD25535C285666C700E55709 /* PowerConfig.swift */, DD8169FA271F1F3A00F4AB02 /* MeshLog.swift */, DD8169FE272476C700F4AB02 /* LogDocument.swift */, ); @@ -592,6 +595,7 @@ DDF924CA26FBB953009FE055 /* ConnectedDevice.swift in Sources */, DDAF8C5D26ED09490058C060 /* portnums.pb.swift in Sources */, DD9D8F2F2764403B00080993 /* Meshtastic.xcdatamodeld in Sources */, + DD25535D285666C700E55709 /* PowerConfig.swift in Sources */, DD1BF2F92776FE2E008C8D2F /* UserMessageList.swift in Sources */, DDB2CC6E27F3EB47009C5FCC /* telemetry.pb.swift in Sources */, DD23A50F26FD1B4400D9B90C /* PeripheralModel.swift in Sources */, diff --git a/MeshtasticApple/Meshtastic.xcdatamodeld/MeshtasticDataModel v 3.xcdatamodel/contents b/MeshtasticApple/Meshtastic.xcdatamodeld/MeshtasticDataModel v 3.xcdatamodel/contents index a5ab71a8..7d5d50e1 100644 --- a/MeshtasticApple/Meshtastic.xcdatamodeld/MeshtasticDataModel v 3.xcdatamodel/contents +++ b/MeshtasticApple/Meshtastic.xcdatamodeld/MeshtasticDataModel v 3.xcdatamodel/contents @@ -1,5 +1,10 @@ - + + + + + + @@ -99,5 +104,6 @@ + \ No newline at end of file diff --git a/MeshtasticApple/Protobufs/config.pb.swift b/MeshtasticApple/Protobufs/config.pb.swift index 10c67cc5..e0d8a2a0 100644 --- a/MeshtasticApple/Protobufs/config.pb.swift +++ b/MeshtasticApple/Protobufs/config.pb.swift @@ -390,7 +390,7 @@ struct Config { /// If set, we are powered from a low-current source (i.e. solar), so even if it looks like we have power flowing in /// we should try to minimize power consumption as much as possible. /// YOU DO NOT NEED TO SET THIS IF YOU'VE set is_router (it is implied in that case). - var isLowPower: Bool = false + var isPowerSaving: Bool = false /// /// Circumvents the logic block for determining whether the device is powered or not. @@ -401,10 +401,6 @@ struct Config { /// If non-zero, the device will fully power off this many seconds after external power is removed. var onBatteryShutdownAfterSecs: UInt32 = 0 - /// - /// If set to true, enable power saving features of the esp32 - var isPowerSaving: Bool = false - /// /// Ratio of voltage divider for battery pin eg. 3.20 (R1=100k, R2=220k) /// Overrides the ADC_MULTIPLIER defined in variant for battery voltage calculation. @@ -1350,10 +1346,9 @@ extension Config.PowerConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImple static let protoMessageName: String = Config.protoMessageName + ".PowerConfig" static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 1: .standard(proto: "charge_current"), - 2: .standard(proto: "is_low_power"), + 2: .standard(proto: "is_power_saving"), 3: .standard(proto: "is_always_powered"), 4: .standard(proto: "on_battery_shutdown_after_secs"), - 5: .standard(proto: "is_power_saving"), 6: .standard(proto: "adc_multiplier_override"), 7: .standard(proto: "wait_bluetooth_secs"), 9: .standard(proto: "mesh_sds_timeout_secs"), @@ -1369,10 +1364,9 @@ extension Config.PowerConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImple // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { case 1: try { try decoder.decodeSingularEnumField(value: &self.chargeCurrent) }() - case 2: try { try decoder.decodeSingularBoolField(value: &self.isLowPower) }() + case 2: try { try decoder.decodeSingularBoolField(value: &self.isPowerSaving) }() case 3: try { try decoder.decodeSingularBoolField(value: &self.isAlwaysPowered) }() case 4: try { try decoder.decodeSingularUInt32Field(value: &self.onBatteryShutdownAfterSecs) }() - case 5: try { try decoder.decodeSingularBoolField(value: &self.isPowerSaving) }() case 6: try { try decoder.decodeSingularFloatField(value: &self.adcMultiplierOverride) }() case 7: try { try decoder.decodeSingularUInt32Field(value: &self.waitBluetoothSecs) }() case 9: try { try decoder.decodeSingularUInt32Field(value: &self.meshSdsTimeoutSecs) }() @@ -1388,8 +1382,8 @@ extension Config.PowerConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImple if self.chargeCurrent != .maunset { try visitor.visitSingularEnumField(value: self.chargeCurrent, fieldNumber: 1) } - if self.isLowPower != false { - try visitor.visitSingularBoolField(value: self.isLowPower, fieldNumber: 2) + if self.isPowerSaving != false { + try visitor.visitSingularBoolField(value: self.isPowerSaving, fieldNumber: 2) } if self.isAlwaysPowered != false { try visitor.visitSingularBoolField(value: self.isAlwaysPowered, fieldNumber: 3) @@ -1397,9 +1391,6 @@ extension Config.PowerConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImple if self.onBatteryShutdownAfterSecs != 0 { try visitor.visitSingularUInt32Field(value: self.onBatteryShutdownAfterSecs, fieldNumber: 4) } - if self.isPowerSaving != false { - try visitor.visitSingularBoolField(value: self.isPowerSaving, fieldNumber: 5) - } if self.adcMultiplierOverride != 0 { try visitor.visitSingularFloatField(value: self.adcMultiplierOverride, fieldNumber: 6) } @@ -1423,10 +1414,9 @@ extension Config.PowerConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImple static func ==(lhs: Config.PowerConfig, rhs: Config.PowerConfig) -> Bool { if lhs.chargeCurrent != rhs.chargeCurrent {return false} - if lhs.isLowPower != rhs.isLowPower {return false} + if lhs.isPowerSaving != rhs.isPowerSaving {return false} if lhs.isAlwaysPowered != rhs.isAlwaysPowered {return false} if lhs.onBatteryShutdownAfterSecs != rhs.onBatteryShutdownAfterSecs {return false} - if lhs.isPowerSaving != rhs.isPowerSaving {return false} if lhs.adcMultiplierOverride != rhs.adcMultiplierOverride {return false} if lhs.waitBluetoothSecs != rhs.waitBluetoothSecs {return false} if lhs.meshSdsTimeoutSecs != rhs.meshSdsTimeoutSecs {return false} diff --git a/MeshtasticApple/Views/Nodes/NodeDetail.swift b/MeshtasticApple/Views/Nodes/NodeDetail.swift index db5a979e..a1008e74 100644 --- a/MeshtasticApple/Views/Nodes/NodeDetail.swift +++ b/MeshtasticApple/Views/Nodes/NodeDetail.swift @@ -19,6 +19,8 @@ struct NodeDetail: View { var node: NodeInfoEntity var body: some View { + + let hwModelString = node.user?.hwModel ?? "UNSET" HStack { @@ -87,6 +89,8 @@ struct NodeDetail: View { HStack { if self.bleManager.connectedPeripheral != nil && self.bleManager.connectedPeripheral.num == node.num && self.bleManager.connectedPeripheral.num == node.num { + if hwModelString == "TBEAM" || hwModelString == "TECHO" || hwModelString.contains("4631") { + Button(action: { isPresentingShutdownConfirm = true @@ -111,35 +115,34 @@ struct NodeDetail: View { let success = bleManager.sendShutdown(destNum: node.num, wantResponse: false) } } + } + + Button(action: { + isPresentingRebootConfirm = true - Button(action: { - - isPresentingRebootConfirm = true - - }) { - - Image(systemName: "arrow.triangle.2.circlepath") - .symbolRenderingMode(.hierarchical) - .imageScale(.small) - .foregroundColor(Color.accentColor) - Text("Reboot") - .font(.caption) + }) { + + Image(systemName: "arrow.triangle.2.circlepath") + .symbolRenderingMode(.hierarchical) + .imageScale(.small) + .foregroundColor(Color.accentColor) + Text("Reboot") + .font(.caption) - } - .padding() - .background(Color(.systemGray6)) - .clipShape(Capsule()) - .confirmationDialog( - "Are you sure?", - isPresented: $isPresentingRebootConfirm + } + .padding() + .background(Color(.systemGray6)) + .clipShape(Capsule()) + .confirmationDialog( + "Are you sure?", + isPresented: $isPresentingRebootConfirm ) { - Button("Reboot Node?", role: .destructive) { - let success = bleManager.sendReboot(destNum: node.num, wantResponse: false) + Button("Reboot Node?", role: .destructive) { + let success = bleManager.sendReboot(destNum: node.num, wantResponse: false) } } } - } .padding(5) Divider() diff --git a/MeshtasticApple/Views/Settings/DisplayConfig.swift b/MeshtasticApple/Views/Settings/DisplayConfig.swift index aefc89ac..35e4ce92 100644 --- a/MeshtasticApple/Views/Settings/DisplayConfig.swift +++ b/MeshtasticApple/Views/Settings/DisplayConfig.swift @@ -46,6 +46,7 @@ enum ScreenOnSeconds: Int, CaseIterable, Identifiable { case fiveMinutes = 300 case tenMinutes = 600 case fifteenMinutes = 900 + case max = 2147483647 var id: Int { self.rawValue } var description: String { @@ -63,6 +64,8 @@ enum ScreenOnSeconds: Int, CaseIterable, Identifiable { return "Ten Minutes" case .fifteenMinutes: return "Fifteen Minutes" + case .max: + return "Always On" } } } @@ -107,9 +110,9 @@ struct DisplayConfig: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager - @State var isConnected: Bool = false - - @State var gpsFormat: Config.DisplayConfig.GpsCoordinateFormat = .gpsFormatDec + @State var screenOnSeconds = 0 + @State var screenCarouselInterval = 0 + @State var gpsFormat = 0 var body: some View { @@ -118,9 +121,9 @@ struct DisplayConfig: View { Form { Section(header: Text("Timing")) { - Picker("Screen on for", selection: $gpsFormat ) { - ForEach(ScreenOnSeconds.allCases) { lu in - Text(lu.description) + Picker("Screen on for", selection: $screenOnSeconds ) { + ForEach(ScreenOnSeconds.allCases) { sos in + Text(sos.description) } } .pickerStyle(DefaultPickerStyle()) @@ -129,9 +132,9 @@ struct DisplayConfig: View { .font(.caption) .listRowSeparator(.visible) - Picker("Carousel Interval", selection: $gpsFormat ) { - ForEach(ScreenCarouselSeconds.allCases) { lu in - Text(lu.description) + Picker("Carousel Interval", selection: $screenCarouselInterval ) { + ForEach(ScreenCarouselSeconds.allCases) { scs in + Text(scs.description) } } .pickerStyle(DefaultPickerStyle()) diff --git a/MeshtasticApple/Views/Settings/LoRaConfig.swift b/MeshtasticApple/Views/Settings/LoRaConfig.swift index 526dbe4d..f17e66d9 100644 --- a/MeshtasticApple/Views/Settings/LoRaConfig.swift +++ b/MeshtasticApple/Views/Settings/LoRaConfig.swift @@ -7,7 +7,6 @@ import SwiftUI - enum RegionCodes : Int, CaseIterable, Identifiable { case unset = 0 @@ -96,9 +95,9 @@ struct LoRaConfig: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager - @State var region: Config.LoRaConfig.RegionCode = .us - - @State var modemPreset: Config.LoRaConfig.ModemPreset = .longFast + @State var region = 1 + @State var modemPreset = 0 + @State var numberOfHops = 0 var body: some View { @@ -130,9 +129,20 @@ struct LoRaConfig: View { .listRowSeparator(.visible) .listRowSeparator(.visible) } - + Section(header: Text("Mesh Options")) { + + Picker("Number of hops", selection: $numberOfHops) { + ForEach(0..<8) { + if $0 == 0 { + Text("Default") + } else { + Text("\($0) Hops") + } + } + } + .pickerStyle(DefaultPickerStyle()) + } } - } .navigationTitle("LoRa Config") .navigationBarItems(trailing: diff --git a/MeshtasticApple/Views/Settings/PositionConfig.swift b/MeshtasticApple/Views/Settings/PositionConfig.swift index fc5c5c22..e511c7b1 100644 --- a/MeshtasticApple/Views/Settings/PositionConfig.swift +++ b/MeshtasticApple/Views/Settings/PositionConfig.swift @@ -114,9 +114,9 @@ struct PositionConfig: View { @State var smartPositionEnabled = true @State var deviceGpsEnabled = true @State var fixedPosition = false - @State var gpsUpdateInterval: Int32 = 0 - @State var gpsAttemptTime: Int32 = 0 - @State var positionBroadcastSeconds: Int32 = 0 + @State var gpsUpdateInterval = 0 + @State var gpsAttemptTime = 0 + @State var positionBroadcastSeconds = 0 var body: some View { diff --git a/MeshtasticApple/Views/Settings/PowerConfig.swift b/MeshtasticApple/Views/Settings/PowerConfig.swift new file mode 100644 index 00000000..fde78abd --- /dev/null +++ b/MeshtasticApple/Views/Settings/PowerConfig.swift @@ -0,0 +1,61 @@ +// +// PowerConfig.swift +// Meshtastic Apple +// +// Copyright (c) Garth Vander Houwen 6/12/22. +// + +import Foundation +import SwiftUI + +struct PowerConfig: View { + + @Environment(\.managedObjectContext) var context + @EnvironmentObject var bleManager: BLEManager + + @State var isPowerSaving = false + @State var isAlwaysPowered = false + + var body: some View { + + VStack { + + Form { + + Section(header: Text("States")) { + + Toggle(isOn: $isPowerSaving) { + + Label("Power Saving", systemImage: "powersleep") + } + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + Text("If set, we are powered from a low-current source (i.e. solar), so even if it looks like we have power flowing in we should try to minimize power consumption as much as possible.") + .font(.caption) + .listRowSeparator(.visible) + + Toggle(isOn: $isAlwaysPowered) { + + Label("Always Powered", systemImage: "powerplug.fill") + } + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + Text("Circumvents the logic block for determining whether the device is powered or not. Useful for devices with finicky ADC issues on the battery sense pins, or no battery pin at all.") + .font(.caption) + .listRowSeparator(.visible) + + } + } + } + .navigationTitle("Power Config") + .navigationBarItems(trailing: + + ZStack { + + ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.lastFourCode : "????") + }) + .onAppear { + + self.bleManager.context = context + } + .navigationViewStyle(StackNavigationViewStyle()) + } +} diff --git a/MeshtasticApple/Views/Settings/Settings.swift b/MeshtasticApple/Views/Settings/Settings.swift index 4db480ad..cbedcebd 100644 --- a/MeshtasticApple/Views/Settings/Settings.swift +++ b/MeshtasticApple/Views/Settings/Settings.swift @@ -64,7 +64,7 @@ struct Settings: View { Text("Position") } NavigationLink { - PositionConfig() + PowerConfig() } label: { Image(systemName: "bolt") @@ -72,7 +72,6 @@ struct Settings: View { Text("Power") } - .disabled(true) } Section("Module Configuration") {