Settings rework - new async location handler

This commit is contained in:
Garth Vander Houwen 2023-12-06 12:32:17 -08:00
parent fb66e3b250
commit 126cdfbdb3
20 changed files with 1193 additions and 545 deletions

View file

@ -37,7 +37,6 @@ struct CannedMessagesConfig: View {
@State var messages = ""
var body: some View {
VStack {
Form {
if node != nil && node?.metadata == nil && node?.num ?? 0 != bleManager.connectedPeripheral?.num ?? 0 {
Text("There has been no response to a request for device metadata over the admin channel for this node.")

View file

@ -41,150 +41,151 @@ struct DetectionSensorConfig: View {
@State var monitorPin = 0
var body: some View {
Form {
if node != nil && node?.metadata == nil && node?.num ?? 0 != bleManager.connectedPeripheral?.num ?? 0 {
Text("There has been no response to a request for device metadata over the admin channel for this node.")
.font(.callout)
.foregroundColor(.orange)
} else if node != nil && node?.num ?? 0 != bleManager.connectedPeripheral?.num ?? 0 {
// Let users know what is going on if they are using remote admin and don't have the config yet
if node?.detectionSensorConfig == nil {
Text("Detection Sensor config data was requested over the admin channel but no response has been returned from the remote node. You can check the status of admin message requests in the admin message log.")
VStack {
Form {
if node != nil && node?.metadata == nil && node?.num ?? 0 != bleManager.connectedPeripheral?.num ?? 0 {
Text("There has been no response to a request for device metadata over the admin channel for this node.")
.font(.callout)
.foregroundColor(.orange)
} else {
Text("Remote administration for: \(node?.user?.longName ?? "Unknown")")
.font(.title3)
.onAppear {
setDetectionSensorValues()
}
}
} else if node != nil && node?.num ?? 0 == bleManager.connectedPeripheral?.num ?? 0 {
Text("Configuration for: \(node?.user?.longName ?? "Unknown")")
.font(.title3)
} else {
Text("Please connect to a radio to configure settings.")
.font(.callout)
.foregroundColor(.orange)
}
Section(header: Text("options")) {
Toggle(isOn: $enabled) {
Label("enabled", systemImage: "dot.radiowaves.right")
Text("Enables the detection sensor module, it needs to be enabled on both the node with the sensor, and any nodes that you want to receive detection sensor text messages or view the detection sensor log and chart.")
.font(.caption)
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
.listRowSeparator(.visible)
if enabled {
HStack {
Picker(selection: $role, label: Text("Role")) {
ForEach(DetectionSensorRole.allCases, id: \.self) { r in
Text(r.description)
.tag(r)
}
}
.pickerStyle(SegmentedPickerStyle())
.padding(.top, 5)
.padding(.bottom, 5)
}
}
}
if enabled && role == .client {
Section(header: Text("Client options")) {
Toggle(isOn: $detectionNotificationsEnabled) {
Label("Enable Notifications", systemImage: "bell.badge")
Text("Detection sensor messages are received as text messages. If you enable notifications you will recieve a notification for each detection message received and a corresponding unread message badge.")
.font(.caption)
}
.listRowSeparator(.visible)
}
}
if enabled && role == .sensor {
Section(header: Text("Sensor options")) {
Toggle(isOn: $sendBell) {
Label("Send Bell", systemImage: "bell")
Text("Send ASCII bell with alert message. Useful for triggering external notification on bell.")
.font(.caption)
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
.listRowSeparator(.visible)
HStack {
Label("Name", systemImage: "signature")
TextField("Friendly name", text: $name, axis: .vertical)
.foregroundColor(.gray)
.autocapitalization(.none)
.disableAutocorrection(true)
.onChange(of: name, perform: { _ in
let totalBytes = name.utf8.count
// Only mess with the value if it is too big
if totalBytes > 20 {
let firstNBytes = Data(name.utf8.prefix(20))
if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) {
// Set the shortName back to the last place where it was the right size
name = maxBytesString
}
}
})
.foregroundColor(.gray)
}
.listRowSeparator(.hidden)
Text("Friendly name used to format message sent to mesh. Example: A name \"Motion\" would result in a message \"Motion detected\"")
.font(.caption)
.foregroundStyle(.gray)
.listRowSeparator(.visible)
.offset(y: -10)
Picker("GPIO Pin to monitor", selection: $monitorPin) {
ForEach(0..<46) {
if $0 == 0 {
Text("unset")
} else {
Text("Pin \($0)")
}
}
}
.pickerStyle(DefaultPickerStyle())
Toggle(isOn: $detectionTriggeredHigh) {
Label("Detection trigger High", systemImage: "dial.high")
Text("Whether or not the GPIO pin state detection is triggered on HIGH (1) or LOW (0)")
.font(.caption)
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Toggle(isOn: $usePullup) {
Label("Uses pullup resistor", systemImage: "arrow.up.to.line")
Text(" Whether or not use INPUT_PULLUP mode for GPIO pin. Only applicable if the board uses pull-up resistors on the pin")
} else if node != nil && node?.num ?? 0 != bleManager.connectedPeripheral?.num ?? 0 {
// Let users know what is going on if they are using remote admin and don't have the config yet
if node?.detectionSensorConfig == nil {
Text("Detection Sensor config data was requested over the admin channel but no response has been returned from the remote node. You can check the status of admin message requests in the admin message log.")
.font(.callout)
.foregroundColor(.orange)
} else {
Text("Remote administration for: \(node?.user?.longName ?? "Unknown")")
.font(.title3)
.onAppear {
setDetectionSensorValues()
}
}
} else if node != nil && node?.num ?? 0 == bleManager.connectedPeripheral?.num ?? 0 {
Text("Configuration for: \(node?.user?.longName ?? "Unknown")")
.font(.title3)
} else {
Text("Please connect to a radio to configure settings.")
.font(.callout)
.foregroundColor(.orange)
}
Section(header: Text("options")) {
Toggle(isOn: $enabled) {
Label("enabled", systemImage: "dot.radiowaves.right")
Text("Enables the detection sensor module, it needs to be enabled on both the node with the sensor, and any nodes that you want to receive detection sensor text messages or view the detection sensor log and chart.")
.font(.caption)
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
.listRowSeparator(.visible)
if enabled {
HStack {
Picker(selection: $role, label: Text("Role")) {
ForEach(DetectionSensorRole.allCases, id: \.self) { r in
Text(r.description)
.tag(r)
}
}
.pickerStyle(SegmentedPickerStyle())
.padding(.top, 5)
.padding(.bottom, 5)
}
}
}
Section(header: Text("update.interval")) {
Picker("Minimum time between detection broadcasts", selection: $minimumBroadcastSecs) {
ForEach(UpdateIntervals.allCases) { ui in
Text(ui.description).tag(ui.rawValue)
if enabled && role == .client {
Section(header: Text("Client options")) {
Toggle(isOn: $detectionNotificationsEnabled) {
Label("Enable Notifications", systemImage: "bell.badge")
Text("Detection sensor messages are received as text messages. If you enable notifications you will recieve a notification for each detection message received and a corresponding unread message badge.")
.font(.caption)
}
}
.pickerStyle(DefaultPickerStyle())
.listRowSeparator(.hidden)
Text("Mininum time between detection broadcasts. Default is 45 seconds.")
.font(.caption)
.foregroundStyle(.gray)
.listRowSeparator(.visible)
Picker("State Broadcast Interval", selection: $stateBroadcastSecs) {
Text("Never").tag(0)
ForEach(UpdateIntervals.allCases) { ui in
Text(ui.description).tag(ui.rawValue)
}
}
.pickerStyle(DefaultPickerStyle())
.listRowSeparator(.hidden)
Text("How often to send detection sensor state to mesh regardless of detection. Default is Never.")
.font(.caption)
.foregroundStyle(.gray)
}
if enabled && role == .sensor {
Section(header: Text("Sensor options")) {
Toggle(isOn: $sendBell) {
Label("Send Bell", systemImage: "bell")
Text("Send ASCII bell with alert message. Useful for triggering external notification on bell.")
.font(.caption)
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
.listRowSeparator(.visible)
HStack {
Label("Name", systemImage: "signature")
TextField("Friendly name", text: $name, axis: .vertical)
.foregroundColor(.gray)
.autocapitalization(.none)
.disableAutocorrection(true)
.onChange(of: name, perform: { _ in
let totalBytes = name.utf8.count
// Only mess with the value if it is too big
if totalBytes > 20 {
let firstNBytes = Data(name.utf8.prefix(20))
if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) {
// Set the shortName back to the last place where it was the right size
name = maxBytesString
}
}
})
.foregroundColor(.gray)
}
.listRowSeparator(.hidden)
Text("Friendly name used to format message sent to mesh. Example: A name \"Motion\" would result in a message \"Motion detected\"")
.font(.caption)
.foregroundStyle(.gray)
.listRowSeparator(.visible)
.offset(y: -10)
Picker("GPIO Pin to monitor", selection: $monitorPin) {
ForEach(0..<46) {
if $0 == 0 {
Text("unset")
} else {
Text("Pin \($0)")
}
}
}
.pickerStyle(DefaultPickerStyle())
Toggle(isOn: $detectionTriggeredHigh) {
Label("Detection trigger High", systemImage: "dial.high")
Text("Whether or not the GPIO pin state detection is triggered on HIGH (1) or LOW (0)")
.font(.caption)
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Toggle(isOn: $usePullup) {
Label("Uses pullup resistor", systemImage: "arrow.up.to.line")
Text(" Whether or not use INPUT_PULLUP mode for GPIO pin. Only applicable if the board uses pull-up resistors on the pin")
.font(.caption)
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
}
Section(header: Text("update.interval")) {
Picker("Minimum time between detection broadcasts", selection: $minimumBroadcastSecs) {
ForEach(UpdateIntervals.allCases) { ui in
Text(ui.description).tag(ui.rawValue)
}
}
.pickerStyle(DefaultPickerStyle())
.listRowSeparator(.hidden)
Text("Mininum time between detection broadcasts. Default is 45 seconds.")
.font(.caption)
.foregroundStyle(.gray)
.listRowSeparator(.visible)
Picker("State Broadcast Interval", selection: $stateBroadcastSecs) {
Text("Never").tag(0)
ForEach(UpdateIntervals.allCases) { ui in
Text(ui.description).tag(ui.rawValue)
}
}
.pickerStyle(DefaultPickerStyle())
.listRowSeparator(.hidden)
Text("How often to send detection sensor state to mesh regardless of detection. Default is Never.")
.font(.caption)
.foregroundStyle(.gray)
}
}
}
}

View file

@ -32,137 +32,138 @@ struct ExternalNotificationConfig: View {
@State var nagTimeout = 0
var body: some View {
Form {
if node != nil && node?.metadata == nil && node?.num ?? 0 != bleManager.connectedPeripheral?.num ?? 0 {
Text("There has been no response to a request for device metadata over the admin channel for this node.")
.font(.callout)
.foregroundColor(.orange)
} else if node != nil && node?.num ?? 0 != bleManager.connectedPeripheral?.num ?? 0 {
// Let users know what is going on if they are using remote admin and don't have the config yet
if node?.externalNotificationConfig == nil {
Text("External notification config data was requested over the admin channel but no response has been returned from the remote node. You can check the status of admin message requests in the admin message log.")
VStack {
Form {
if node != nil && node?.metadata == nil && node?.num ?? 0 != bleManager.connectedPeripheral?.num ?? 0 {
Text("There has been no response to a request for device metadata over the admin channel for this node.")
.font(.callout)
.foregroundColor(.orange)
} else {
Text("Remote administration for: \(node?.user?.longName ?? "Unknown")")
} else if node != nil && node?.num ?? 0 != bleManager.connectedPeripheral?.num ?? 0 {
// Let users know what is going on if they are using remote admin and don't have the config yet
if node?.externalNotificationConfig == nil {
Text("External notification config data was requested over the admin channel but no response has been returned from the remote node. You can check the status of admin message requests in the admin message log.")
.font(.callout)
.foregroundColor(.orange)
} else {
Text("Remote administration for: \(node?.user?.longName ?? "Unknown")")
.font(.title3)
.onAppear {
setExternalNotificationValues()
}
}
} else if node != nil && node?.num ?? 0 == bleManager.connectedPeripheral?.num ?? 0 {
Text("Configuration for: \(node?.user?.longName ?? "Unknown")")
.font(.title3)
.onAppear {
setExternalNotificationValues()
}
} else {
Text("Please connect to a radio to configure settings.")
.font(.callout)
.foregroundColor(.orange)
}
} else if node != nil && node?.num ?? 0 == bleManager.connectedPeripheral?.num ?? 0 {
Text("Configuration for: \(node?.user?.longName ?? "Unknown")")
.font(.title3)
} else {
Text("Please connect to a radio to configure settings.")
.font(.callout)
.foregroundColor(.orange)
}
Section(header: Text("options")) {
Toggle(isOn: $enabled) {
Label("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))
Toggle(isOn: $usePWM) {
Label("Use PWM Buzzer", systemImage: "light.beacon.max.fill")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Text("Use a PWM output (like the RAK Buzzer) for tunes instead of an on/off output. This will ignore the output, output duration and active settings and use the device config buzzer GPIO option instead.")
.font(.caption)
}
Section(header: Text("Advanced GPIO Options")) {
Section(header: Text("Primary GPIO")
.font(.caption)
.foregroundColor(.gray)
.textCase(.uppercase)) {
Toggle(isOn: $active) {
Label("Active", systemImage: "togglepower")
Section(header: Text("options")) {
Toggle(isOn: $enabled) {
Label("enabled", systemImage: "megaphone")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Text("If enabled, the 'output' Pin will be pulled active high, disabled means active low.")
.font(.caption)
Picker("Output pin GPIO", selection: $output) {
ForEach(0..<46) {
if $0 == 0 {
Text("unset")
} else {
Text("Pin \($0)")
}
}
Toggle(isOn: $alertBell) {
Label("Alert when receiving a bell", systemImage: "bell")
}
.pickerStyle(DefaultPickerStyle())
Picker("GPIO Output Duration", selection: $outputMilliseconds ) {
ForEach(OutputIntervals.allCases) { oi in
Text(oi.description)
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Toggle(isOn: $alertMessage) {
Label("Alert when receiving a message", systemImage: "message")
}
.pickerStyle(DefaultPickerStyle())
Text("When using in GPIO mode, keep the output on for this long. ")
.font(.caption)
Picker("Nag timeout", selection: $nagTimeout ) {
ForEach(OutputIntervals.allCases) { oi in
Text(oi.description)
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Toggle(isOn: $usePWM) {
Label("Use PWM Buzzer", systemImage: "light.beacon.max.fill")
}
.pickerStyle(DefaultPickerStyle())
Text("Specifies how long the monitored GPIO should output.")
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Text("Use a PWM output (like the RAK Buzzer) for tunes instead of an on/off output. This will ignore the output, output duration and active settings and use the device config buzzer GPIO option instead.")
.font(.caption)
}
Section(header: Text("Optional GPIO")
.font(.caption)
.foregroundColor(.gray)
.textCase(.uppercase)) {
Toggle(isOn: $alertBellBuzzer) {
Label("Alert GPIO buzzer when receiving a bell", systemImage: "bell")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Toggle(isOn: $alertBellVibra) {
Label("Alert GPIO vibra motor when receiving a bell", systemImage: "bell")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Toggle(isOn: $alertMessageBuzzer) {
Label("Alert GPIO buzzer when receiving a message", systemImage: "message")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Toggle(isOn: $alertMessageBuzzer) {
Label("Alert GPIO vibra motor when receiving a message", systemImage: "message")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Picker("Output pin buzzer GPIO ", selection: $outputBuzzer) {
ForEach(0..<46) {
if $0 == 0 {
Text("unset")
} else {
Text("Pin \($0)")
Section(header: Text("Advanced GPIO Options")) {
Section(header: Text("Primary GPIO")
.font(.caption)
.foregroundColor(.gray)
.textCase(.uppercase)) {
Toggle(isOn: $active) {
Label("Active", systemImage: "togglepower")
}
}
}
.pickerStyle(DefaultPickerStyle())
Picker("Output pin vibra GPIO", selection: $outputVibra) {
ForEach(0..<46) {
if $0 == 0 {
Text("unset")
} else {
Text("Pin \($0)")
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Text("If enabled, the 'output' Pin will be pulled active high, disabled means active low.")
.font(.caption)
Picker("Output pin GPIO", selection: $output) {
ForEach(0..<46) {
if $0 == 0 {
Text("unset")
} else {
Text("Pin \($0)")
}
}
}
.pickerStyle(DefaultPickerStyle())
Picker("GPIO Output Duration", selection: $outputMilliseconds ) {
ForEach(OutputIntervals.allCases) { oi in
Text(oi.description)
}
}
.pickerStyle(DefaultPickerStyle())
Text("When using in GPIO mode, keep the output on for this long. ")
.font(.caption)
Picker("Nag timeout", selection: $nagTimeout ) {
ForEach(OutputIntervals.allCases) { oi in
Text(oi.description)
}
}
.pickerStyle(DefaultPickerStyle())
Text("Specifies how long the monitored GPIO should output.")
.font(.caption)
}
Section(header: Text("Optional GPIO")
.font(.caption)
.foregroundColor(.gray)
.textCase(.uppercase)) {
Toggle(isOn: $alertBellBuzzer) {
Label("Alert GPIO buzzer when receiving a bell", systemImage: "bell")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Toggle(isOn: $alertBellVibra) {
Label("Alert GPIO vibra motor when receiving a bell", systemImage: "bell")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Toggle(isOn: $alertMessageBuzzer) {
Label("Alert GPIO buzzer when receiving a message", systemImage: "message")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Toggle(isOn: $alertMessageBuzzer) {
Label("Alert GPIO vibra motor when receiving a message", systemImage: "message")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Picker("Output pin buzzer GPIO ", selection: $outputBuzzer) {
ForEach(0..<46) {
if $0 == 0 {
Text("unset")
} else {
Text("Pin \($0)")
}
}
}
.pickerStyle(DefaultPickerStyle())
Picker("Output pin vibra GPIO", selection: $outputVibra) {
ForEach(0..<46) {
if $0 == 0 {
Text("unset")
} else {
Text("Pin \($0)")
}
}
}
.pickerStyle(DefaultPickerStyle())
}
}
.pickerStyle(DefaultPickerStyle())
}
}
.disabled(self.bleManager.connectedPeripheral == nil || node?.externalNotificationConfig == nil)
}
.disabled(self.bleManager.connectedPeripheral == nil || node?.externalNotificationConfig == nil)
Button {
isPresentingSaveConfirm = true
} label: {

View file

@ -25,176 +25,177 @@ struct MQTTConfig: View {
@State var root = "msh"
var body: some View {
Form {
if node != nil && node?.metadata == nil && node?.num ?? 0 != bleManager.connectedPeripheral?.num ?? 0 {
Text("There has been no response to a request for device metadata over the admin channel for this node.")
.font(.callout)
.foregroundColor(.orange)
} else if node != nil && node?.num ?? 0 != bleManager.connectedPeripheral?.num ?? 0 {
// Let users know what is going on if they are using remote admin and don't have the config yet
if node?.mqttConfig == nil {
Text("MQTT config data was requested over the admin channel but no response has been returned from the remote node. You can check the status of admin message requests in the admin message log.")
VStack {
Form {
if node != nil && node?.metadata == nil && node?.num ?? 0 != bleManager.connectedPeripheral?.num ?? 0 {
Text("There has been no response to a request for device metadata over the admin channel for this node.")
.font(.callout)
.foregroundColor(.orange)
} else {
Text("Remote administration for: \(node?.user?.longName ?? "Unknown")")
} else if node != nil && node?.num ?? 0 != bleManager.connectedPeripheral?.num ?? 0 {
// Let users know what is going on if they are using remote admin and don't have the config yet
if node?.mqttConfig == nil {
Text("MQTT config data was requested over the admin channel but no response has been returned from the remote node. You can check the status of admin message requests in the admin message log.")
.font(.callout)
.foregroundColor(.orange)
} else {
Text("Remote administration for: \(node?.user?.longName ?? "Unknown")")
.font(.title3)
.onAppear {
setMqttValues()
}
}
} else if node != nil && node?.num ?? 0 == bleManager.connectedPeripheral?.num ?? 0 {
Text("Configuration for: \(node?.user?.longName ?? "Unknown")")
.font(.title3)
.onAppear {
setMqttValues()
}
} else {
Text("Please connect to a radio to configure settings.")
.font(.callout)
.foregroundColor(.orange)
}
} else if node != nil && node?.num ?? 0 == bleManager.connectedPeripheral?.num ?? 0 {
Text("Configuration for: \(node?.user?.longName ?? "Unknown")")
.font(.title3)
} else {
Text("Please connect to a radio to configure settings.")
Section(header: Text("options")) {
Toggle(isOn: $enabled) {
Label("enabled", systemImage: "dot.radiowaves.right")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Toggle(isOn: $proxyToClientEnabled) {
Label("mqtt.clientproxy", systemImage: "iphone.radiowaves.left.and.right")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Text("If both MQTT and the client proxy are enabled your mobile device will utalize an available network connection to connect to the specified MQTT server.")
.font(.caption2)
Toggle(isOn: $encryptionEnabled) {
Label("Encryption Enabled", systemImage: "lock.icloud")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Toggle(isOn: $jsonEnabled) {
Label("JSON Enabled", systemImage: "ellipsis.curlybraces")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Text("JSON mode is a limited, unencrypted MQTT output.")
.font(.caption2)
Toggle(isOn: $tlsEnabled) {
Label("TLS Enabled", systemImage: "checkmark.shield.fill")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Text("Your MQTT Server must support TLS.")
.font(.caption2)
}
Section(header: Text("Custom Server")) {
HStack {
Label("Address", systemImage: "server.rack")
TextField("Server Address", text: $address)
.foregroundColor(.gray)
.autocapitalization(.none)
.disableAutocorrection(true)
.onChange(of: address, perform: { _ in
let totalBytes = address.utf8.count
// Only mess with the value if it is too big
if totalBytes > 62 {
let firstNBytes = Data(username.utf8.prefix(62))
if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) {
// Set the shortName back to the last place where it was the right size
address = maxBytesString
}
}
hasChanges = true
})
.foregroundColor(.gray)
.keyboardType(.default)
}
.autocorrectionDisabled()
HStack {
Label("mqtt.username", systemImage: "person.text.rectangle")
TextField("mqtt.username", text: $username)
.foregroundColor(.gray)
.autocapitalization(.none)
.disableAutocorrection(true)
.onChange(of: username, perform: { _ in
let totalBytes = username.utf8.count
// Only mess with the value if it is too big
if totalBytes > 62 {
let firstNBytes = Data(username.utf8.prefix(62))
if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) {
// Set the shortName back to the last place where it was the right size
username = maxBytesString
}
}
hasChanges = true
})
.foregroundColor(.gray)
}
.keyboardType(.default)
.scrollDismissesKeyboard(.interactively)
HStack {
Label("password", systemImage: "wallet.pass")
TextField("password", text: $password)
.foregroundColor(.gray)
.autocapitalization(.none)
.disableAutocorrection(true)
.onChange(of: password, perform: { _ in
let totalBytes = password.utf8.count
// Only mess with the value if it is too big
if totalBytes > 62 {
let firstNBytes = Data(password.utf8.prefix(62))
if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) {
// Set the shortName back to the last place where it was the right size
password = maxBytesString
}
}
hasChanges = true
})
.foregroundColor(.gray)
}
.keyboardType(.default)
.scrollDismissesKeyboard(.interactively)
HStack {
Label("Root Topic", systemImage: "tree")
TextField("Root Topic", text: $root)
.foregroundColor(.gray)
.onChange(of: root, perform: { _ in
let totalBytes = root.utf8.count
// Only mess with the value if it is too big
if totalBytes > 14 {
let firstNBytes = Data(root.utf8.prefix(14))
if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) {
// Set the shortName back to the last place where it was the right size
root = maxBytesString
}
}
})
.foregroundColor(.gray)
}
.keyboardType(.asciiCapable)
.scrollDismissesKeyboard(.interactively)
.disableAutocorrection(true)
Text("The root topic to use for MQTT messages. Default is \"msh\". This is useful if you want to use a single MQTT server for multiple meshtastic networks and separate them via ACLs")
.font(.caption2)
}
Text("You can set uplink and downlink for each channel.")
.font(.callout)
.foregroundColor(.orange)
}
Section(header: Text("options")) {
Toggle(isOn: $enabled) {
Label("enabled", systemImage: "dot.radiowaves.right")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Toggle(isOn: $proxyToClientEnabled) {
Label("mqtt.clientproxy", systemImage: "iphone.radiowaves.left.and.right")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Text("If both MQTT and the client proxy are enabled your mobile device will utalize an available network connection to connect to the specified MQTT server.")
.font(.caption2)
Toggle(isOn: $encryptionEnabled) {
Label("Encryption Enabled", systemImage: "lock.icloud")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Toggle(isOn: $jsonEnabled) {
Label("JSON Enabled", systemImage: "ellipsis.curlybraces")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Text("JSON mode is a limited, unencrypted MQTT output.")
.font(.caption2)
Toggle(isOn: $tlsEnabled) {
Label("TLS Enabled", systemImage: "checkmark.shield.fill")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Text("Your MQTT Server must support TLS.")
.font(.caption2)
}
Section(header: Text("Custom Server")) {
HStack {
Label("Address", systemImage: "server.rack")
TextField("Server Address", text: $address)
.foregroundColor(.gray)
.autocapitalization(.none)
.disableAutocorrection(true)
.onChange(of: address, perform: { _ in
let totalBytes = address.utf8.count
// Only mess with the value if it is too big
if totalBytes > 62 {
let firstNBytes = Data(username.utf8.prefix(62))
if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) {
// Set the shortName back to the last place where it was the right size
address = maxBytesString
}
}
hasChanges = true
})
.foregroundColor(.gray)
.keyboardType(.default)
}
.autocorrectionDisabled()
HStack {
Label("mqtt.username", systemImage: "person.text.rectangle")
TextField("mqtt.username", text: $username)
.foregroundColor(.gray)
.autocapitalization(.none)
.disableAutocorrection(true)
.onChange(of: username, perform: { _ in
let totalBytes = username.utf8.count
// Only mess with the value if it is too big
if totalBytes > 62 {
let firstNBytes = Data(username.utf8.prefix(62))
if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) {
// Set the shortName back to the last place where it was the right size
username = maxBytesString
}
}
hasChanges = true
})
.foregroundColor(.gray)
}
.keyboardType(.default)
.scrollDismissesKeyboard(.interactively)
HStack {
Label("password", systemImage: "wallet.pass")
TextField("password", text: $password)
.foregroundColor(.gray)
.autocapitalization(.none)
.disableAutocorrection(true)
.onChange(of: password, perform: { _ in
let totalBytes = password.utf8.count
// Only mess with the value if it is too big
if totalBytes > 62 {
let firstNBytes = Data(password.utf8.prefix(62))
if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) {
// Set the shortName back to the last place where it was the right size
password = maxBytesString
}
}
hasChanges = true
})
.foregroundColor(.gray)
}
.keyboardType(.default)
.scrollDismissesKeyboard(.interactively)
HStack {
Label("Root Topic", systemImage: "tree")
TextField("Root Topic", text: $root)
.foregroundColor(.gray)
.onChange(of: root, perform: { _ in
let totalBytes = root.utf8.count
// Only mess with the value if it is too big
if totalBytes > 14 {
let firstNBytes = Data(root.utf8.prefix(14))
if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) {
// Set the shortName back to the last place where it was the right size
root = maxBytesString
}
}
})
.foregroundColor(.gray)
}
.keyboardType(.asciiCapable)
.scrollDismissesKeyboard(.interactively)
.disableAutocorrection(true)
Text("The root topic to use for MQTT messages. Default is \"msh\". This is useful if you want to use a single MQTT server for multiple meshtastic networks and separate them via ACLs")
.font(.caption2)
}
Text("You can set uplink and downlink for each channel.")
.font(.callout)
.scrollDismissesKeyboard(.interactively)
.disabled(self.bleManager.connectedPeripheral == nil || node?.mqttConfig == nil)
}
.scrollDismissesKeyboard(.interactively)
.disabled(self.bleManager.connectedPeripheral == nil || node?.mqttConfig == nil)
Button {
isPresentingSaveConfirm = true
} label: {

View file

@ -26,9 +26,7 @@ struct SerialConfig: View {
@State var mode = 0
var body: some View {
VStack {
Form {
if node != nil && node?.metadata == nil && node?.num ?? 0 != bleManager.connectedPeripheral?.num ?? 0 {
Text("There has been no response to a request for device metadata over the admin channel for this node.")

View file

@ -27,72 +27,72 @@ struct StoreForwardConfig: View {
@State var historyReturnWindow = 0
var body: some View {
Form {
if node != nil && node?.metadata == nil && node?.num ?? 0 != bleManager.connectedPeripheral?.num ?? 0 {
Text("There has been no response to a request for device metadata over the admin channel for this node.")
.font(.callout)
.foregroundColor(.orange)
} else if node != nil && node?.num ?? 0 != bleManager.connectedPeripheral?.num ?? 0 {
// Let users know what is going on if they are using remote admin and don't have the config yet
if node?.storeForwardConfig == nil {
Text("Store and forward config data was requested over the admin channel but no response has been returned from the remote node. You can check the status of admin message requests in the admin message log.")
VStack {
Form {
if node != nil && node?.metadata == nil && node?.num ?? 0 != bleManager.connectedPeripheral?.num ?? 0 {
Text("There has been no response to a request for device metadata over the admin channel for this node.")
.font(.callout)
.foregroundColor(.orange)
} else {
Text("Remote administration for: \(node?.user?.longName ?? "Unknown")")
} else if node != nil && node?.num ?? 0 != bleManager.connectedPeripheral?.num ?? 0 {
// Let users know what is going on if they are using remote admin and don't have the config yet
if node?.storeForwardConfig == nil {
Text("Store and forward config data was requested over the admin channel but no response has been returned from the remote node. You can check the status of admin message requests in the admin message log.")
.font(.callout)
.foregroundColor(.orange)
} else {
Text("Remote administration for: \(node?.user?.longName ?? "Unknown")")
.font(.title3)
.onAppear {
setDetectionSensorValues()
}
}
} else if node != nil && node?.num ?? 0 == bleManager.connectedPeripheral?.num ?? 0 {
Text("Configuration for: \(node?.user?.longName ?? "Unknown")")
.font(.title3)
.onAppear {
setDetectionSensorValues()
}
} else {
Text("Please connect to a radio to configure settings.")
.font(.callout)
.foregroundColor(.orange)
}
Section(header: Text("options")) {
Toggle(isOn: $enabled) {
Label("enabled", systemImage: "envelope.arrow.triangle.branch")
}
Toggle(isOn: $heartbeat) {
Label("storeforward.heartbeat", systemImage: "waveform.path.ecg")
}
Picker("Number of records", selection: $records) {
Text("unset").tag(0)
Text("25").tag(25)
Text("50").tag(50)
Text("75").tag(75)
Text("100").tag(100)
}
.pickerStyle(DefaultPickerStyle())
Picker("History Return Max", selection: $historyReturnMax ) {
Text("unset").tag(0)
Text("25").tag(25)
Text("50").tag(50)
Text("75").tag(75)
Text("100").tag(100)
}
.pickerStyle(DefaultPickerStyle())
Picker("History Return Window", selection: $historyReturnWindow ) {
Text("unset").tag(0)
Text("One Minute").tag(60)
Text("Five Minutes").tag(300)
Text("Ten Minutes").tag(600)
Text("Fifteen Minutes").tag(900)
Text("Thirty Minutes").tag(1800)
Text("One Hour").tag(3600)
}
.pickerStyle(DefaultPickerStyle())
}
} else if node != nil && node?.num ?? 0 == bleManager.connectedPeripheral?.num ?? 0 {
Text("Configuration for: \(node?.user?.longName ?? "Unknown")")
.font(.title3)
} else {
Text("Please connect to a radio to configure settings.")
.font(.callout)
.foregroundColor(.orange)
}
Section(header: Text("options")) {
Toggle(isOn: $enabled) {
Label("enabled", systemImage: "envelope.arrow.triangle.branch")
}
Toggle(isOn: $heartbeat) {
Label("storeforward.heartbeat", systemImage: "waveform.path.ecg")
}
Picker("Number of records", selection: $records) {
Text("unset").tag(0)
Text("25").tag(25)
Text("50").tag(50)
Text("75").tag(75)
Text("100").tag(100)
}
.pickerStyle(DefaultPickerStyle())
Picker("History Return Max", selection: $historyReturnMax ) {
Text("unset").tag(0)
Text("25").tag(25)
Text("50").tag(50)
Text("75").tag(75)
Text("100").tag(100)
}
.pickerStyle(DefaultPickerStyle())
Picker("History Return Window", selection: $historyReturnWindow ) {
Text("unset").tag(0)
Text("One Minute").tag(60)
Text("Five Minutes").tag(300)
Text("Ten Minutes").tag(600)
Text("Fifteen Minutes").tag(900)
Text("Thirty Minutes").tag(1800)
Text("One Hour").tag(3600)
}
.pickerStyle(DefaultPickerStyle())
}
.scrollDismissesKeyboard(.interactively)
.disabled(self.bleManager.connectedPeripheral == nil || node?.storeForwardConfig == nil)
}
.scrollDismissesKeyboard(.interactively)
.disabled(self.bleManager.connectedPeripheral == nil || node?.storeForwardConfig == nil)
Button {
isPresentingSaveConfirm = true
} label: {

View file

@ -23,7 +23,6 @@ struct TelemetryConfig: View {
@State var environmentDisplayFahrenheit = false
var body: some View {
VStack {
Form {
if node != nil && node?.metadata == nil && node?.num ?? 0 != bleManager.connectedPeripheral?.num ?? 0 {