More settings

This commit is contained in:
Garth Vander Houwen 2022-06-22 09:05:56 -07:00
parent c931b80935
commit 6f164a18e2
9 changed files with 401 additions and 16 deletions

View file

@ -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 = "<group>"; };
DD539501276DAA6A00AD86B1 /* MapLocation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapLocation.swift; sourceTree = "<group>"; };
DD619373285CC7D600E59241 /* MeshtasticDataModel v 4.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModel v 4.xcdatamodel"; sourceTree = "<group>"; };
DD6193742862F6E600E59241 /* ExternalNotificationConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExternalNotificationConfig.swift; sourceTree = "<group>"; };
DD6193762862F90F00E59241 /* CannedMessagesConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CannedMessagesConfig.swift; sourceTree = "<group>"; };
DD6B85A728009258000ACD6B /* ShareChannel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareChannel.swift; sourceTree = "<group>"; };
DD8169F8271F1A6100F4AB02 /* MeshLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshLogger.swift; sourceTree = "<group>"; };
DD8169FA271F1F3A00F4AB02 /* MeshLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshLog.swift; sourceTree = "<group>"; };
@ -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 = "<group>";
@ -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 */,

View file

@ -73,6 +73,7 @@
<relationship name="myInfo" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MyInfoEntity" inverseName="myInfoNode" inverseEntity="MyInfoEntity"/>
<relationship name="positionConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PositionConfigEntity" inverseName="positionConfigNode" inverseEntity="PositionConfigEntity"/>
<relationship name="positions" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="PositionEntity" inverseName="nodePosition" inverseEntity="PositionEntity"/>
<relationship name="rangeTestConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="RangeTestConfigEntity" inverseName="rangeTestConfigNode" inverseEntity="RangeTestConfigEntity"/>
<relationship name="telemetries" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="TelemetryEntity" inverseName="nodeTelemetry" inverseEntity="TelemetryEntity"/>
<relationship name="user" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="UserEntity" inverseName="userNode" inverseEntity="UserEntity"/>
<uniquenessConstraints>
@ -99,6 +100,13 @@
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<relationship name="nodePosition" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="positions" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="RangeTestConfigEntity" representedClassName="RangeTestConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="num" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="save" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="sender" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<relationship name="rangeTestConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="rangeTestConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="TelemetryEntity" representedClassName="TelemetryEntity" syncable="YES" codeGenerationType="class">
<attribute name="airUtilTx" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="barometricPressure" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
@ -131,12 +139,13 @@
<element name="LoRaConfigEntity" positionX="45" positionY="144" width="128" height="104"/>
<element name="MessageEntity" positionX="-36" positionY="63" width="128" height="215"/>
<element name="MyInfoEntity" positionX="-18" positionY="81" width="128" height="209"/>
<element name="NodeInfoEntity" positionX="-63" positionY="-18" width="128" height="224"/>
<element name="NodeInfoEntity" positionX="-63" positionY="-18" width="128" height="239"/>
<element name="PositionEntity" positionX="-54" positionY="54" width="128" height="119"/>
<element name="TelemetryEntity" positionX="160" positionY="192" width="128" height="194"/>
<element name="UserEntity" positionX="0" positionY="144" width="128" height="200"/>
<element name="DeviceConfigEntity" positionX="45" positionY="144" width="128" height="104"/>
<element name="DisplayConfigEntity" positionX="54" positionY="153" width="128" height="104"/>
<element name="PositionConfigEntity" positionX="63" positionY="162" width="128" height="149"/>
<element name="RangeTestConfigEntity" positionX="72" positionY="171" width="128" height="104"/>
</elements>
</model>

View file

@ -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)

View file

@ -0,0 +1,8 @@
//
// CannedMessagesConfig.swift
// MeshtasticApple
//
// Created by Garth Vander Houwen on 6/22/22.
//
import Foundation

View file

@ -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)

View file

@ -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())
}
}
}

View file

@ -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)

View file

@ -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

View file

@ -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: