From 6f164a18e2b33f724eaf2082b2b7395bb2fefebe Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Wed, 22 Jun 2022 09:05:56 -0700 Subject: [PATCH] More settings --- Meshtastic Apple.xcodeproj/project.pbxproj | 12 +- .../contents | 11 +- MeshtasticApple/Views/Nodes/NodeDetail.swift | 3 + .../Views/Settings/CannedMessagesConfig.swift | 8 + .../Views/Settings/DisplayConfig.swift | 2 +- .../Settings/ExternalNotificationConfig.swift | 90 ++++++ .../Views/Settings/RangeTestConfig.swift | 2 +- MeshtasticApple/Views/Settings/Settings.swift | 28 +- .../Views/Settings/TelemetryConfig.swift | 261 +++++++++++++++++- 9 files changed, 401 insertions(+), 16 deletions(-) create mode 100644 MeshtasticApple/Views/Settings/CannedMessagesConfig.swift create mode 100644 MeshtasticApple/Views/Settings/ExternalNotificationConfig.swift diff --git a/Meshtastic Apple.xcodeproj/project.pbxproj b/Meshtastic Apple.xcodeproj/project.pbxproj index 1a556d63..f496b712 100644 --- a/Meshtastic Apple.xcodeproj/project.pbxproj +++ b/Meshtastic Apple.xcodeproj/project.pbxproj @@ -34,6 +34,8 @@ DD5394FC276993AD00AD86B1 /* SwiftProtobuf in Frameworks */ = {isa = PBXBuildFile; productRef = DD5394FB276993AD00AD86B1 /* SwiftProtobuf */; }; DD5394FE276BA0EF00AD86B1 /* PositionEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5394FD276BA0EF00AD86B1 /* PositionEntityExtension.swift */; }; DD539502276DAA6A00AD86B1 /* MapLocation.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD539501276DAA6A00AD86B1 /* MapLocation.swift */; }; + DD6193752862F6E600E59241 /* ExternalNotificationConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6193742862F6E600E59241 /* ExternalNotificationConfig.swift */; }; + DD6193772862F90F00E59241 /* CannedMessagesConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6193762862F90F00E59241 /* CannedMessagesConfig.swift */; }; DD6B85A828009258000ACD6B /* ShareChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6B85A728009258000ACD6B /* ShareChannel.swift */; }; DD8169F9271F1A6100F4AB02 /* MeshLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8169F8271F1A6100F4AB02 /* MeshLogger.swift */; }; DD8169FB271F1F3A00F4AB02 /* MeshLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8169FA271F1F3A00F4AB02 /* MeshLog.swift */; }; @@ -117,6 +119,8 @@ DD5394FD276BA0EF00AD86B1 /* PositionEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PositionEntityExtension.swift; sourceTree = ""; }; DD539501276DAA6A00AD86B1 /* MapLocation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapLocation.swift; sourceTree = ""; }; DD619373285CC7D600E59241 /* MeshtasticDataModel v 4.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModel v 4.xcdatamodel"; sourceTree = ""; }; + DD6193742862F6E600E59241 /* ExternalNotificationConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExternalNotificationConfig.swift; sourceTree = ""; }; + DD6193762862F90F00E59241 /* CannedMessagesConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CannedMessagesConfig.swift; sourceTree = ""; }; DD6B85A728009258000ACD6B /* ShareChannel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareChannel.swift; sourceTree = ""; }; DD8169F8271F1A6100F4AB02 /* MeshLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshLogger.swift; sourceTree = ""; }; DD8169FA271F1F3A00F4AB02 /* MeshLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshLog.swift; sourceTree = ""; }; @@ -232,15 +236,17 @@ children = ( DD3501882852FC3B000FC853 /* Settings.swift */, DD4A911D2708C65400501B7E /* AppSettings.swift */, + DD8169FA271F1F3A00F4AB02 /* MeshLog.swift */, + DD8169FE272476C700F4AB02 /* LogDocument.swift */, DD6B85A728009258000ACD6B /* ShareChannel.swift */, DD41582528582E9B009B0E59 /* DeviceConfig.swift */, DD8EBF42285058FA00426DCA /* DisplayConfig.swift */, DD2553562855B02500E55709 /* LoRaConfig.swift */, DD2553582855B52700E55709 /* PositionConfig.swift */, + DD6193742862F6E600E59241 /* ExternalNotificationConfig.swift */, DD41582928585C32009B0E59 /* RangeTestConfig.swift */, DD415827285859C4009B0E59 /* TelemetryConfig.swift */, - DD8169FA271F1F3A00F4AB02 /* MeshLog.swift */, - DD8169FE272476C700F4AB02 /* LogDocument.swift */, + DD6193762862F90F00E59241 /* CannedMessagesConfig.swift */, ); path = Settings; sourceTree = ""; @@ -599,6 +605,7 @@ DDF924CA26FBB953009FE055 /* ConnectedDevice.swift in Sources */, DDAF8C5D26ED09490058C060 /* portnums.pb.swift in Sources */, DD9D8F2F2764403B00080993 /* Meshtastic.xcdatamodeld in Sources */, + DD6193772862F90F00E59241 /* CannedMessagesConfig.swift in Sources */, DD41582628582E9B009B0E59 /* DeviceConfig.swift in Sources */, DD1BF2F92776FE2E008C8D2F /* UserMessageList.swift in Sources */, DDB2CC6E27F3EB47009C5FCC /* telemetry.pb.swift in Sources */, @@ -621,6 +628,7 @@ DDC3B274283F411B00AC321C /* LastHeardText.swift in Sources */, DD2E65262767A01F00E45FC5 /* NodeDetail.swift in Sources */, DDA6B2E928419CF2003E8C16 /* MeshPackets.swift in Sources */, + DD6193752862F6E600E59241 /* ExternalNotificationConfig.swift in Sources */, DDD94A502845C8F5004A87A0 /* DateTimeText.swift in Sources */, C9A88B57278B559900BD810A /* apponly.pb.swift in Sources */, DD4C158E2824AA7E0032668E /* config.pb.swift in Sources */, diff --git a/MeshtasticApple/Meshtastic.xcdatamodeld/MeshtasticDataModel v 4.xcdatamodel/contents b/MeshtasticApple/Meshtastic.xcdatamodeld/MeshtasticDataModel v 4.xcdatamodel/contents index c32d279b..db1356bd 100644 --- a/MeshtasticApple/Meshtastic.xcdatamodeld/MeshtasticDataModel v 4.xcdatamodel/contents +++ b/MeshtasticApple/Meshtastic.xcdatamodeld/MeshtasticDataModel v 4.xcdatamodel/contents @@ -73,6 +73,7 @@ + @@ -99,6 +100,13 @@ + + + + + + + @@ -131,12 +139,13 @@ - + + \ No newline at end of file diff --git a/MeshtasticApple/Views/Nodes/NodeDetail.swift b/MeshtasticApple/Views/Nodes/NodeDetail.swift index 6369b514..3a1f7c39 100644 --- a/MeshtasticApple/Views/Nodes/NodeDetail.swift +++ b/MeshtasticApple/Views/Nodes/NodeDetail.swift @@ -304,6 +304,9 @@ struct NodeDetail: View { Image(systemName: "mappin.and.ellipse").foregroundColor(.accentColor) // .font(.subheadline) Text("Lat/Long:").font(.caption) Text("\(String(mappin.latitude ?? 0)) \(String(mappin.longitude ?? 0))") + + + .foregroundColor(.gray) .font(.caption) diff --git a/MeshtasticApple/Views/Settings/CannedMessagesConfig.swift b/MeshtasticApple/Views/Settings/CannedMessagesConfig.swift new file mode 100644 index 00000000..c3aea46d --- /dev/null +++ b/MeshtasticApple/Views/Settings/CannedMessagesConfig.swift @@ -0,0 +1,8 @@ +// +// CannedMessagesConfig.swift +// MeshtasticApple +// +// Created by Garth Vander Houwen on 6/22/22. +// + +import Foundation diff --git a/MeshtasticApple/Views/Settings/DisplayConfig.swift b/MeshtasticApple/Views/Settings/DisplayConfig.swift index e7467bbf..38593008 100644 --- a/MeshtasticApple/Views/Settings/DisplayConfig.swift +++ b/MeshtasticApple/Views/Settings/DisplayConfig.swift @@ -151,7 +151,7 @@ struct DisplayConfig: View { } .pickerStyle(DefaultPickerStyle()) - Text("The number of seconds the screen remains on after the user button is pressed or messages are received.") + Text("How long the screen remains on after the user button is pressed or messages are received.") .font(.caption) .listRowSeparator(.visible) diff --git a/MeshtasticApple/Views/Settings/ExternalNotificationConfig.swift b/MeshtasticApple/Views/Settings/ExternalNotificationConfig.swift new file mode 100644 index 00000000..f7a01893 --- /dev/null +++ b/MeshtasticApple/Views/Settings/ExternalNotificationConfig.swift @@ -0,0 +1,90 @@ +// +// External Notification Config.swift +// Meshtastic Apple +// +// Copyright (c) Garth Vander Houwen 6/22/22. +// +import SwiftUI + +struct ExternalNotificationConfig: View { + + @Environment(\.managedObjectContext) var context + @EnvironmentObject var bleManager: BLEManager + + var node: NodeInfoEntity + + @State var enabled = false + @State var outputMilliseconds = 0 + @State var output = 0 + @State var active = false + @State var alertMessage = false + @State var alertBell = false + + var body: some View { + + VStack { + + Form { + + Section(header: Text("Options")) { + + Toggle(isOn: $enabled) { + + Label("Module Enabled", systemImage: "megaphone") + } + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + + Toggle(isOn: $alertBell) { + + Label("Alert when receiving a bell", systemImage: "bell") + } + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + + Toggle(isOn: $alertMessage) { + + Label("Alert when receiving a message", systemImage: "message") + } + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + + + } + + Section(header: Text("GPIO")) { + + Toggle(isOn: $active) { + + Label("Active", systemImage: "togglepower") + } + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + Text("Specifies whether the external circuit is triggered when the device's GPIO is low or high.") + .font(.caption) + .listRowSeparator(.visible) + + Picker("GPIO to monitor", selection: $output) { + ForEach(0..<25) { + + Text("\($0)") + } + } + .pickerStyle(DefaultPickerStyle()) + Text("Specifies the GPIO that your external circuit is attached to on the device.") + .font(.caption) + .listRowSeparator(.visible) + + } + } + .navigationTitle("External Notification Config") + .navigationBarItems(trailing: + + ZStack { + + ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?????") + }) + .onAppear { + + self.bleManager.context = context + } + .navigationViewStyle(StackNavigationViewStyle()) + } + } +} diff --git a/MeshtasticApple/Views/Settings/RangeTestConfig.swift b/MeshtasticApple/Views/Settings/RangeTestConfig.swift index ef86378a..76924210 100644 --- a/MeshtasticApple/Views/Settings/RangeTestConfig.swift +++ b/MeshtasticApple/Views/Settings/RangeTestConfig.swift @@ -25,7 +25,7 @@ struct RangeTestConfig: View { Toggle(isOn: $enabled) { - Label("Enabled", systemImage: "figure.walk") + Label("Module Enabled", systemImage: "figure.walk") } .toggleStyle(DefaultToggleStyle()) .listRowSeparator(.visible) diff --git a/MeshtasticApple/Views/Settings/Settings.swift b/MeshtasticApple/Views/Settings/Settings.swift index 4c632a53..9d5d9dcc 100644 --- a/MeshtasticApple/Views/Settings/Settings.swift +++ b/MeshtasticApple/Views/Settings/Settings.swift @@ -47,9 +47,10 @@ struct Settings: View { Section("Radio Configuration") { - Text("Radio config values will be be enabled when there is a connected node. Save buttons will enable when there is a connected node and config changes to save.") + Text("Radio config views will be be enabled when there is a connected node. Save buttons will be enabled when there are config changes to save.") .font(.caption) .listRowSeparator(.visible) + .fixedSize(horizontal: false, vertical: true) NavigationLink { DeviceConfig(node: nodes.first(where: { $0.num == connectedNodeNum }) ?? NodeInfoEntity()) @@ -94,18 +95,28 @@ struct Settings: View { } .disabled(bleManager.connectedPeripheral == nil) } - Section("Module Configuration") { + Section("Module Configuration - Non Functional interaction preview.") { + +// NavigationLink { +// PositionConfig(node: nodes.first(where: { $0.num == connectedNodeNum }) ?? NodeInfoEntity()) +// } label: { +// +// Image(systemName: "list.bullet.rectangle.fill") +// .symbolRenderingMode(.hierarchical) +// +// Text("Canned Messages") +// } NavigationLink { - PositionConfig(node: nodes.first(where: { $0.num == connectedNodeNum }) ?? NodeInfoEntity()) + ExternalNotificationConfig(node: nodes.first(where: { $0.num == connectedNodeNum }) ?? NodeInfoEntity()) } label: { - Image(systemName: "list.bullet.rectangle.fill") + Image(systemName: "megaphone") .symbolRenderingMode(.hierarchical) - Text("Canned Messages") + Text("External Notification") } - .disabled(true) + NavigationLink { RangeTestConfig() } label: { @@ -115,7 +126,7 @@ struct Settings: View { Text("Range Test") } - .disabled(!(nodes.first(where: { $0.num == connectedNodeNum })?.myInfo?.hasWifi ?? true) || bleManager.connectedPeripheral == nil) + //.disabled(!(nodes.first(where: { $0.num == connectedNodeNum })?.myInfo?.hasWifi ?? true) || bleManager.connectedPeripheral == nil) NavigationLink { TelemetryConfig() @@ -126,10 +137,9 @@ struct Settings: View { Text("Telemetry (Sensors)") } - .disabled(true) + .disabled(false) } // Not Implemented: - // External Notifications - Not Working // Serial Config - Not sure what the point is // Store Forward Config - Not Working // WiFi Config - Would break connection to device diff --git a/MeshtasticApple/Views/Settings/TelemetryConfig.swift b/MeshtasticApple/Views/Settings/TelemetryConfig.swift index 78eb78a6..9068f5a5 100644 --- a/MeshtasticApple/Views/Settings/TelemetryConfig.swift +++ b/MeshtasticApple/Views/Settings/TelemetryConfig.swift @@ -6,13 +6,172 @@ // import SwiftUI +enum SensorTypes: Int, CaseIterable, Identifiable { + + /// No external telemetry sensor explicitly set + case notSet = 0 + + /// Moderate accuracy temperature + case dht11 = 1 + + /// High accuracy temperature + case ds18B20 = 2 + + /// Moderate accuracy temperature and humidity + case dht12 = 3 + + /// Moderate accuracy temperature and humidity + case dht21 = 4 + + /// Moderate accuracy temperature and humidity + case dht22 = 5 + + /// High accuracy temperature, pressure, humidity + case bme280 = 6 + + /// High accuracy temperature, pressure, humidity, and air resistance + case bme680 = 7 + + /// Very high accuracy temperature + case mcp9808 = 8 + + /// Moderate accuracy temperature and humidity + case shtc3 = 9 + + /// Moderate accuracy current and voltage + case ina260 = 10 + + /// Moderate accuracy current and voltage + case ina219 = 11 + + var id: Int { self.rawValue } + var description: String { + get { + switch self { + + case .notSet: + return "Not Set" + case .dht11: + return "DHT11 temperature" + case .ds18B20: + return "DS18B20 temperature" + case .dht12: + return "DHT12 temp and humidity" + case .dht21: + return "DHT21 temp and humidity" + case .dht22: + return "DHT22 temp and humidity" + case .bme280: + return "BME280 temp pressure and humidity" + case .bme680: + return "BME680 temp pressure humidity & air resistance" + case .mcp9808: + return "MCP9808 high accuracy temperature" + case .shtc3: + return "SHTC3 temp and humidity" + case .ina260: + return "INA260 current and voltage" + case .ina219: + return "INA219 current and voltage" + } + } + } +} + +// Default of 0 is off +enum ErrorRecoveryIntervals: Int, CaseIterable, Identifiable { + + case off = 0 + case fifteenSeconds = 15 + case thirtySeconds = 30 + case oneMinute = 60 + case fiveMinutes = 300 + case tenMinutes = 600 + case fifteenMinutes = 900 + case thirtyMinutes = 1800 + case oneHour = 3600 + + var id: Int { self.rawValue } + var description: String { + get { + switch self { + case .off: + return "Off" + case .fifteenSeconds: + return "Fifteen Seconds" + 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" + case .thirtyMinutes: + return "Thirty Minutes" + case .oneHour: + return "One Hour" + } + } + } +} + +enum UpdateIntervals: Int, CaseIterable, Identifiable { + + case off = 0 + case fifteenSeconds = 15 + case thirtySeconds = 30 + case oneMinute = 60 + case fiveMinutes = 300 + case tenMinutes = 600 + case fifteenMinutes = 900 + case thirtyMinutes = 1800 + case oneHour = 3600 + + var id: Int { self.rawValue } + var description: String { + get { + switch self { + case .off: + return "Off" + case .fifteenSeconds: + return "Fifteen Seconds" + 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" + case .thirtyMinutes: + return "Thirty Minutes" + case .oneHour: + return "One Hour" + } + } + } +} + struct TelemetryConfig: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager - @State var isPowerSaving = false - @State var isAlwaysPowered = false + @State var deviceUpdateInterval = 0 + @State var environmentUpdateInterval = 0 + + @State var environmentMeasurementEnabled = false + @State var environmentSensorType = 0 + @State var environmentScreenEnabled = false + @State var environmentDisplayFahrenheit = false + @State var environmentSensorPin = 0 + @State var environmentRecoveryInterval = 0 + @State var environmentReadErrorCountThreshold = 0 var body: some View { @@ -20,6 +179,104 @@ struct TelemetryConfig: View { Form { + Section(header: Text("Update Intervals")) { + + Picker("Device Metrics", selection: $deviceUpdateInterval ) { + ForEach(UpdateIntervals.allCases) { ui in + Text(ui.description) + } + } + .pickerStyle(DefaultPickerStyle()) + + Picker("Sensor Metrics", selection: $environmentUpdateInterval ) { + ForEach(UpdateIntervals.allCases) { ui in + Text(ui.description) + } + } + .pickerStyle(DefaultPickerStyle()) + //var deviceUpdateInterval: UInt32 = 0 + + //var environmentUpdateInterval: UInt32 = 0 + } + + Section(header: Text("Sensor Options")) { + + Toggle(isOn: $environmentMeasurementEnabled) { + + Label("Enabled", systemImage: "chart.xyaxis.line") + } + .toggleStyle(DefaultToggleStyle()) + .listRowSeparator(.visible) + + Picker("Sensor", selection: $environmentSensorType ) { + ForEach(SensorTypes.allCases) { st in + Text(st.description) + } + } + .pickerStyle(DefaultPickerStyle()) + + Toggle(isOn: $environmentScreenEnabled) { + + Label("Show on device screen", systemImage: "display") + } + .toggleStyle(DefaultToggleStyle()) + + Toggle(isOn: $environmentDisplayFahrenheit) { + + Label("Display Fahrenheit", systemImage: "thermometer") + } + .toggleStyle(DefaultToggleStyle()) + + Picker("GPIO Pin for sensor readings", selection: $environmentSensorPin) { + ForEach(0..<26) { + + if $0 == 0 { + + Text("Off") + + } else { + + Text("\($0)") + } + } + } + .pickerStyle(DefaultPickerStyle()) + } + + Section(header: Text("Errors")) { + + Picker("Error Count Threshold", selection: $environmentReadErrorCountThreshold) { + ForEach(0..<101) { + + if $0 == 0 { + + Text("Off") + + } else if $0 % 5 == 0 { + + Text("\($0)") + } + } + } + .pickerStyle(DefaultPickerStyle()) + Text("Sometimes sensor reads can fail. If this happens, we will retry a configurable number of attempts, each attempt will be delayed by the minimum required refresh rate for that sensor") + .font(.caption) + .listRowSeparator(.visible) + + Picker("Error Recovery Interval", selection: $environmentRecoveryInterval ) { + ForEach(ErrorRecoveryIntervals.allCases) { eri in + Text(eri.description) + } + } + .pickerStyle(DefaultPickerStyle()) + + Text("Sometimes we can end up with more failures than our error count threshold. In this case, we will stop trying to read from the sensor for a while. Wait this long until trying to read from the sensor again") + .font(.caption) + .listRowSeparator(.visible) + + + + } } .navigationTitle("Telemetry Config") .navigationBarItems(trailing: