mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
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>
419 lines
21 KiB
Markdown
419 lines
21 KiB
Markdown
# 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 **0–7** inclusive. Proto: *"Maximum number of hops. This can't be greater than 7."* |
|
||
| `txEnabled` | Toggle | Boolean |
|
||
| `txPower` | `Stepper(in: 1...30, step: 1)` | Integer **1–30 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 **7–12**. 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 **5–8**. 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 0–48) | Integer |
|
||
| `buzzerGPIO` | Picker (GPIO list 0–48) | 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.0–6.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 min–72 hours or never |
|
||
| `broadcastSmartMinimumDistance` | `TextField` (integer) | `UInt32`, no additional validation |
|
||
| `broadcastSmartMinimumIntervalSecs` | `UpdateIntervalPicker(config: .smartBroadcastMinimum)` | Predefined options: 15 s–1 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: 1–72 hours |
|
||
| `mapPositionPrecision` | `Slider(in: 12...15, step: 1)` | Integer **12–15** 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 min–72 hours |
|
||
| `deviceTelemetryEnabled` | Toggle | Boolean; only shown on firmware ≥ 2.7.12 |
|
||
| `environmentUpdateInterval` | `UpdateIntervalPicker(config: .broadcastShort)` | Predefined options: 30 min–72 hours |
|
||
| `environmentMeasurementEnabled` | Toggle | Boolean |
|
||
| `environmentScreenEnabled` | Toggle | Boolean |
|
||
| `environmentDisplayFahrenheit` | Toggle | Boolean |
|
||
| `powerMeasurementEnabled` | Toggle | Boolean |
|
||
| `powerUpdateInterval` | `UpdateIntervalPicker(config: .broadcastShort)` | Predefined options: 30 min–72 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: 0–72 hours |
|
||
| `stateBroadcastSecs` | `UpdateIntervalPicker(config: .detectionSensorState)` | Predefined options: 0–72 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 0–49) | 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 0–49) | Integer |
|
||
| `txd` | Picker (GPIO list 0–49) | 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 **0–31** (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 min–72 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` (1–30), `current` (0–31) | 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 |
|