Meshtastic-Apple/docs/settings-validation.md
copilot-swe-agent[bot] b0142d0937
docs: add settings validation reference markdown
Agent-Logs-Url: https://github.com/meshtastic/Meshtastic-Apple/sessions/93cf8d9c-f165-4067-a258-420caf50bc86

Co-authored-by: garthvh <1795163+garthvh@users.noreply.github.com>
2026-04-19 00:05:19 +00:00

21 KiB
Raw Permalink Blame History

Meshtastic-Apple — Settings Validation Reference

This document describes every field presented in the Config and Module Config settings screens, the validation rules applied in the SwiftUI forms, and the underlying constraints from the protobuf definitions.


Shared Components

The following reusable components are used across all config forms.

UpdateIntervalPicker (Views/Settings/UpdateIntervalPicker.swift)

A reusable picker bound to an UpdateInterval value. Each usage specifies an IntervalConfiguration that restricts which time values appear. If the device's current value does not match a predefined option an orange ⚠️ warning is shown: "The configured value: (X) is not one of the optimized options." Manual/arbitrary values are accepted and transmitted but are flagged.

Configuration Allowed values (seconds)
broadcastShort 0, 1800, 3600, 7200, 10800, 14400, 18000, 21600, 43200, 64800, 86400, 129600, 172800, 259200, never
broadcastMedium 3600, 7200, 10800, 14400, 18000, 21600, 43200, 64800, 86400, 129600, 172800, 259200, never
broadcastLong 10800, 14400, 18000, 21600, 43200, 64800, 86400, 129600, 172800, 259200, never
detectionSensorMinimum 0, 15, 30, 60, 120, 300, 600, 900, 1800, 3600, 7200, 10800, 14400, 18000, 21600, 43200, 64800, 86400, 129600, 172800, 259200
detectionSensorState 0, 900, 1800, 3600, 7200, 10800, 14400, 18000, 21600, 43200, 64800, 86400, 129600, 172800, 259200
nagTimeout 0, 1, 5, 10, 15, 30, 60
paxCounter 900, 1800, 3600, 7200, 10800, 14400, 18000, 21600, 43200, 64800, 86400, 129600, 172800, 259200
rangeTestSender 0, 15, 30, 45, 60, 300, 600, 900, 1800, 3600
smartBroadcastMinimum 15, 30, 45, 60, 300, 600, 900, 1800, 3600

SecureInput (Views/Helpers/SecureInput.swift)

A text field with a visibility-toggle button and an isValid: Bool binding. When isValid is false the field is decorated with a red RoundedRectangle stroke. Used for all cryptographic-key fields in Security Config.

FloatField (local to PowerConfig.swift)

A generic float text field that accepts an isValid: (Float) -> Bool closure. On every change the closure is evaluated; if the new value is invalid the field reverts to the previous value.


Config Screens

1. LoRa Config (Config/LoRaConfig.swift)

Protobuf message: Config.LoRaConfig

Field UI Control Constraint / Validation
region Picker (enum) Values from RegionCode enum (unset, US, EU_433, EU_868, CN, JP, ANZ, KR, TW, RU, IN, NZ_865, TH, LORA_24, UA_433, UA_868, MY_433, MY_919, SG_923)
usePreset Toggle Boolean
modemPreset Picker (enum) Values from ModemPresets enum; disabled when usePreset is false
hopLimit Picker (ForEach 0..<8) Integer 07 inclusive. Proto: "Maximum number of hops. This can't be greater than 7."
txEnabled Toggle Boolean
txPower Stepper(in: 1...30, step: 1) Integer 130 dBm. Proto: "If zero, then use default max legal continuous power." (0 = default; UI starts at 1 to prevent accidental override)
channelNum TextField (integer NumberFormatter, no grouping separator) UInt32; disabled when overrideFrequency > 0
bandwidth Picker (enum BandwidthCodes) Enum-constrained
spreadFactor Picker (ForEach 7..<13) Integer 712. Proto: "A number from 7 to 12. Indicates number of chirps per symbol as 1<<spread_factor." Value 0 maps internally to 12
codingRate Picker (ForEach 5..<9) Integer 58. Proto: "The denominator of the coding rate. ie for 4/5, the value is 5." Value 0 maps internally to 8
rxBoostedGain Toggle Boolean
overrideFrequency TextField (NumberFormatter, maximumFractionDigits: 4, no grouping separator) Float; disables channelNum field when > 0
ignoreMqtt Toggle Boolean
okToMqtt Toggle Boolean

Dirty-tracking: All fields use .onChange comparing new value against node?.loRaConfig?.field ?? -1; hasChanges is set to true on any discrepancy.


2. Device Config (Config/DeviceConfig.swift)

Protobuf message: Config.DeviceConfig

Field UI Control Constraint / Validation
deviceRole Picker (enum) Values from DeviceRoles enum (client, clientMute, router, routerLate, repeater, tracker, sensor, tak, clientHidden, lostAndFound, takTracker). Selecting a router/routerLate/clientBase role triggers a confirmation dialog
rebroadcastMode Picker (enum RebroadcastModes) Enum-constrained
nodeInfoBroadcastSecs UpdateIntervalPicker(config: .broadcastLong) Predefined options ≥ 3 hours (10 800 s). On setInitialValues: if stored value < 10 800, it is clamped to 10 800
doubleTapAsButtonPress Toggle Boolean
tripleClickAsAdHocPing Toggle Boolean
ledHeartbeatEnabled Toggle Boolean
tzdef TextField Max 63 UTF-8 bytes. .onChange trims trailing characters until utf8.count <= 63
buttonGPIO Picker (GPIO list 048) Integer
buzzerGPIO Picker (GPIO list 048) Integer

3. Display Config (Config/DisplayConfig.swift)

Protobuf message: Config.DisplayConfig

Field UI Control Constraint / Validation
screenOnSeconds UpdateIntervalPicker(config: .broadcastShort) Predefined interval options
screenCarouselInterval Picker (enum ScreenCarouselIntervals) Enum-constrained
compassNorthTop Toggle Boolean
wakeOnTapOrMotion Toggle Boolean
flipScreen Toggle Boolean
oledType Picker (enum OledTypes) Enum-constrained
displayMode Picker (enum DisplayModes) Enum-constrained
units Picker (enum Units) Enum-constrained (metric / imperial)
use12HourClock Toggle Boolean
headingBold Toggle Boolean

All fields use standard .onChange dirty-tracking only; no custom range or byte-length validation.


4. Power Config (Config/PowerConfig.swift)

Protobuf message: Config.PowerConfig

Field UI Control Constraint / Validation
isPowerSaving Toggle Boolean; only shown for ESP32/ESP32S3 devices or specific roles
shutdownOnPowerLoss Toggle Boolean
shutdownAfterSecs UpdateIntervalPicker(config: .all) Any FixedUpdateIntervals value
adcOverride Toggle Boolean; enables the adcMultiplier field
adcMultiplier FloatField(isValid: { (2.0 ... 6.0).contains($0) }) Float 2.06.0 inclusive. Invalid input is silently reverted to the previous value. When adcOverride is false the field is hidden and 0 is sent
waitBluetoothSecs UpdateIntervalPicker Predefined interval options
lsSecs UpdateIntervalPicker Predefined interval options
minWakeSecs UpdateIntervalPicker Predefined interval options

5. Network Config (Config/NetworkConfig.swift)

Protobuf message: Config.NetworkConfig

Field UI Control Constraint / Validation
wifiEnabled Toggle Boolean; only shown on devices where hasWifi == true
wifiSsid TextField Max 32 UTF-8 bytes. .onChange trims trailing characters until utf8.count <= 32
wifiPsk TextField (.keyboardType(.asciiCapable)) Max 63 UTF-8 bytes. .onChange trims trailing characters until utf8.count <= 63
ethEnabled Toggle Boolean; only shown on devices where hasEthernet == true
udpEnabled Toggle Boolean

6. Bluetooth Config (Config/BluetoothConfig.swift)

Protobuf message: Config.BluetoothConfig

Field UI Control Constraint / Validation
enabled Toggle Boolean
mode Picker (enum BluetoothModes) randomPin, fixedPin, noPin
fixedPin TextField (.keyboardType(.decimalPad)) Exactly 6 digits, no leading zeros. .onChange: (1) removes all "0" characters if the first character is "0"; (2) truncates to 6 characters with .prefix(6) if over-length; (3) sets shortPin = true and shows red error text "BLE Pin must be 6 digits long." if under-length. Only shown when mode == .fixedPin

Proto field: fixedPin: UInt32 — the app transmits the string converted to UInt32.


7. Security Config (Config/SecurityConfig.swift)

Protobuf message: Config.SecurityConfig

Field UI Control Constraint / Validation
publicKey Read-only Text display Auto-derived from privateKey (Curve25519); not editable
privateKey SecureInput(isValid: $hasValidPrivateKey) 32 bytes when decoded from Base64. .onChange: decodes as Base64; if Data.count == 32 → valid and the public key display is refreshed; otherwise invalid with red border
adminKey SecureInput(isValid: $hasValidAdminKey) 32 bytes when decoded from Base64, or empty. Empty string → valid; Data.count == 32 → valid; otherwise invalid with red border
adminKey2 SecureInput(isValid: $hasValidAdminKey2) Same rule as adminKey
adminKey3 SecureInput(isValid: $hasValidAdminKey3) Same rule as adminKey
isManaged Toggle Boolean
serialEnabled Toggle Boolean
debugLogApiEnabled Toggle Boolean

Save guard: The save action includes a guard hasValidPrivateKey && hasValidAdminKey && hasValidAdminKey2 && hasValidAdminKey3 else { return } — a save is silently blocked if any key field is invalid.


8. Position Config (Config/PositionConfig.swift)

Protobuf message: Config.PositionConfig

Field UI Control Constraint / Validation
smartPositionEnabled Toggle Boolean; controls visibility of smart-position sub-fields
deviceGpsEnabled Toggle Boolean
gpsMode Picker (enum GpsModes) Enum-constrained
fixedPosition Toggle Requires firmware version check and shows a confirmation dialog
positionBroadcastSeconds UpdateIntervalPicker(config: .broadcastShort) Predefined options: 30 min72 hours or never
broadcastSmartMinimumDistance TextField (integer) UInt32, no additional validation
broadcastSmartMinimumIntervalSecs UpdateIntervalPicker(config: .smartBroadcastMinimum) Predefined options: 15 s1 hour
gpsUpdateInterval Picker (enum GpsUpdateIntervals) Enum-constrained
positionFlags (bitfield) Multiple Toggles Each toggle controls one bit (altitude, speed, heading, satellite count, sequence number, timestamp, heading, name). Combined into a single UInt32 on save

Module Config Screens

1. MQTT Config (Config/Module/MQTTConfig.swift)

Protobuf message: ModuleConfig.MQTTConfig

Field UI Control Constraint / Validation
enabled Toggle Boolean
proxyToClientEnabled Toggle Boolean
address TextField Max 62 UTF-8 bytes. .onChange trims trailing characters
username TextField Max 62 UTF-8 bytes. .onChange trims trailing characters
password TextField Max 30 UTF-8 bytes. .onChange trims trailing characters
root TextField Max 30 UTF-8 bytes. .onChange trims trailing characters. Proto default: "msh"
encryptionEnabled Toggle Boolean
jsonEnabled Toggle Boolean
tlsEnabled Toggle Boolean
mapReportingEnabled Toggle Boolean; requires a consent toggle before it can be enabled
mapPublishIntervalSecs UpdateIntervalPicker(config: .broadcastMedium) Predefined options: 172 hours
mapPositionPrecision Slider(in: 12...15, step: 1) Integer 1215 inclusive

Duty cycle warning: If the connected node's duty cycle is between 0 and 100 (exclusive), an orange warning about duty cycle restrictions is shown.


2. Telemetry Config (Config/Module/TelemetryConfig.swift)

Protobuf message: ModuleConfig.TelemetryConfig

Field UI Control Constraint / Validation
deviceUpdateInterval UpdateIntervalPicker(config: .broadcastShort) Predefined options: 30 min72 hours
deviceTelemetryEnabled Toggle Boolean; only shown on firmware ≥ 2.7.12
environmentUpdateInterval UpdateIntervalPicker(config: .broadcastShort) Predefined options: 30 min72 hours
environmentMeasurementEnabled Toggle Boolean
environmentScreenEnabled Toggle Boolean
environmentDisplayFahrenheit Toggle Boolean
powerMeasurementEnabled Toggle Boolean
powerUpdateInterval UpdateIntervalPicker(config: .broadcastShort) Predefined options: 30 min72 hours
powerScreenEnabled Toggle Boolean

No range or byte-length validation beyond the predefined picker options.


3. Canned Messages Config (Config/Module/CannedMessagesConfig.swift)

Protobuf message: ModuleConfig.CannedMessageConfig

Field UI Control Constraint / Validation
enabled Toggle Boolean
sendBell Toggle Boolean
rotary1Enabled Toggle Boolean; mutually exclusive with updown1Enabled (the other is disabled while this is on)
updown1Enabled Toggle Boolean; mutually exclusive with rotary1Enabled
inputbrokerPinA Picker (GPIO list) Integer
inputbrokerPinB Picker (GPIO list) Integer
inputbrokerPinPress Picker (GPIO list) Integer
inputbrokerEventCw Picker (enum InputEventSets) Enum-constrained
inputbrokerEventCcw Picker (enum InputEventSets) Enum-constrained
inputbrokerEventPress Picker (enum InputEventSets) Enum-constrained
messages TextField (multiline) Max 198 UTF-8 bytes. .onChange trims trailing characters until utf8.count <= 198

4. Detection Sensor Config (Config/Module/DetectionSensorConfig.swift)

Protobuf message: ModuleConfig.DetectionSensorConfig

Field UI Control Constraint / Validation
enabled Toggle Boolean
role Segmented Picker (sensor / client) Stored in AppStorage; controls which sub-form sections are visible
sendBell Toggle Boolean; sensor role only
name TextField Max 20 UTF-8 bytes. .onChange trims trailing characters
triggerType Picker (enum) Enum-constrained
usePullup Toggle Boolean
minimumBroadcastSecs UpdateIntervalPicker(config: .detectionSensorMinimum) Predefined options: 072 hours
stateBroadcastSecs UpdateIntervalPicker(config: .detectionSensorState) Predefined options: 072 hours
monitorPin Picker (GPIO list) Integer

5. External Notification Config (Config/Module/ExternalNotificationConfig.swift)

Protobuf message: ModuleConfig.ExternalNotificationConfig

Field UI Control Constraint / Validation
enabled Toggle Boolean
alertBell Toggle Boolean
alertBellBuzzer Toggle Boolean
alertBellVibra Toggle Boolean
alertMessage Toggle Boolean
alertMessageBuzzer Toggle Boolean
alertMessageVibra Toggle Boolean
active Toggle Boolean (active-high vs active-low)
output Picker (GPIO list 049) Integer (UInt32)
outputBuzzer Picker (GPIO list) Integer
outputVibra Picker (GPIO list) Integer
outputMilliseconds Picker (OutputIntervals enum) Values (ms): 0, 1000, 2000, 3000, 4000, 5000, 10000, 15000, 30000, 60000
nagTimeout UpdateIntervalPicker(config: .nagTimeout) Predefined options: 0, 1, 5, 10, 15, 30, 60 seconds
usePWM Toggle Boolean
useI2SAsBuzzer Toggle Boolean

No string-length or numeric-range validation beyond the predefined picker options.


6. Store and Forward Config (Config/Module/StoreForwardConfig.swift)

Protobuf message: ModuleConfig.StoreAndForwardConfig

Field UI Control Constraint / Validation
enabled Toggle Boolean
isServer Toggle Boolean
heartbeat Toggle Boolean
records Picker Values: 0, 25, 50, 75, 100
historyReturnMax Picker Values: 0, 25, 50, 75, 100
historyReturnWindow Picker Values (seconds): 0, 60, 300, 600, 900, 1800, 3600, 7200

All options are predefined; no custom validation.


7. Serial Config (Config/Module/SerialConfig.swift)

Protobuf message: ModuleConfig.SerialConfig

Field UI Control Constraint / Validation
enabled Toggle Boolean
echo Toggle Boolean
rxd Picker (GPIO list 049) Integer
txd Picker (GPIO list 049) Integer
baudRate Picker (enum SerialBaudRates) Enum-constrained standard baud rates
timeout Picker Predefined timeout values
overrideConsoleSerialPort Toggle Boolean
mode Picker (enum SerialModes) Enum-constrained (default, simple, proto, textmsg, nmea, caltopo)

8. Range Test Config (Config/Module/RangeTestConfig.swift)

Protobuf message: ModuleConfig.RangeTestConfig

Field UI Control Constraint / Validation
enabled Toggle Boolean
sender UpdateIntervalPicker(config: .rangeTestSender) Predefined options: 0, 15, 30, 45, 60, 300, 600, 900, 1800, 3600 seconds
save Toggle Boolean

9. RTTTL Config (Config/Module/RtttlConfig.swift)

Protobuf: transmitted as a plain string via saveRtttlConfig.

Field UI Control Constraint / Validation
ringtone TextField Max 228 UTF-8 bytes. .onChange trims trailing characters until utf8.count <= 228. Value is stripped of leading/trailing whitespace before being sent

10. Ambient Lighting Config (Config/Module/AmbientLightingConfig.swift)

Protobuf message: ModuleConfig.AmbientLightingConfig

Field UI Control Constraint / Validation
ledState Toggle Boolean
current Stepper(in: 0...31, step: 1) Integer 031 (LED current level)
color ColorPicker SwiftUI color picker; converted to RGB UInt32 on save

11. TAK Module Config (Config/Module/TAKModuleConfig.swift)

Protobuf message: ModuleConfig.TAKConfig

Field UI Control Constraint / Validation
enabled Toggle Boolean; entire form only shown for roles with TAK access (tak, takTracker)
team Picker (enum TakTeams) Enum-constrained (cyan, white, yellow, orange, magenta, red, maroon, purple, dark blue, blue, green, dark green, brown, …)
role Picker (enum TakRoles) Enum-constrained (team member, team lead, HQ, sniper, medic, RTO, FO, casevac, transport, ground, air, control, Blue Force, EOD, intelligence, recon, belligerent, sus, exercise participant)

12. Pax Counter Config (Config/Module/PaxCounterConfig.swift)

Protobuf message: ModuleConfig.PaxcounterConfig

Field UI Control Constraint / Validation
enabled Toggle Boolean
paxcounterUpdateInterval UpdateIntervalPicker(config: .paxCounter) Predefined options: 15 min72 hours

Validation Patterns Summary

Pattern Where Used Details
UTF-8 byte truncation All string TextField fields .onChange loop: while utf8.count > limit { dropLast() }
Numeric stepper range txPower (130), current (031) Enforced by SwiftUI Stepper(in:)
Enum picker All categorical fields Statically typed; only valid enum cases selectable
Predefined interval picker All timing fields UpdateIntervalPicker with IntervalConfiguration; out-of-range values shown with ⚠️ warning
Slider range mapPositionPrecision Slider(in: 12...15, step: 1)
Float closure validation adcMultiplier FloatField(isValid:) reverts to previous value on invalid input
Base64 + byte-count key validation privateKey, adminKey ×3 Data(base64Encoded:)?.count == 32; SecureInput shows red border when isValid == false
Guard-on-save Security Config Save is blocked until all four key fields are valid
PIN digit enforcement fixedPin (Bluetooth) Leading-zero removal; .prefix(6) truncation; shortPin flag triggers red error text
Minimum value clamp on load nodeInfoBroadcastSecs Stored value < 10 800 s is clamped to 10 800 s when the view initialises
Conditional field visibility Many screens Fields hidden/disabled based on device capabilities (hasWifi, hasEthernet), firmware version (checkIsVersionSupported), device role, or toggle state
Mutual exclusion rotary1Enabled / updown1Enabled Each disables the other's toggle
Confirmation dialog on change Device Config deviceRole, Position Config fixedPosition Destructive/special roles require user confirmation
Dirty-tracking (hasChanges) All screens @State var hasChanges = false set via .onChange comparing new value against node?.config?.field ?? -1; controls SaveConfigButton enabled state

String Field Byte Limits at a Glance

Field Screen Max UTF-8 Bytes
wifiSsid Network Config 32
wifiPsk Network Config 63
tzdef Device Config 63
address (MQTT) MQTT Config 62
username (MQTT) MQTT Config 62
root (MQTT topic) MQTT Config 30
password (MQTT) MQTT Config 30
messages (canned) Canned Messages Config 198
name (detection sensor) Detection Sensor Config 20
ringtone (RTTTL) RTTTL Config 228