mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
324 lines
11 KiB
Swift
324 lines
11 KiB
Swift
//
|
||
// LoRaConfig.swift
|
||
// Meshtastic Apple
|
||
//
|
||
// Copyright (c) by Garth Vander Houwen 6/11/22.
|
||
//
|
||
|
||
import SwiftUI
|
||
import CoreData
|
||
import MeshtasticProtobufs
|
||
import OSLog
|
||
|
||
struct LoRaConfig: View {
|
||
|
||
enum Field: Hashable {
|
||
case channelNum
|
||
case frequencyOverride
|
||
}
|
||
|
||
let formatter: NumberFormatter = {
|
||
let formatter = NumberFormatter()
|
||
formatter.numberStyle = .decimal
|
||
formatter.groupingSeparator = ""
|
||
return formatter
|
||
}()
|
||
|
||
@Environment(\.managedObjectContext) var context
|
||
@EnvironmentObject var bleManager: BLEManager
|
||
@Environment(\.dismiss) private var goBack
|
||
@FocusState var focusedField: Field?
|
||
|
||
var node: NodeInfoEntity?
|
||
|
||
@State var hasChanges = false
|
||
@State var region: Int = 0
|
||
@State var modemPreset = 0
|
||
@State var hopLimit = 3
|
||
@State var txPower = 0
|
||
@State var txEnabled = true
|
||
@State var usePreset = true
|
||
@State var channelNum = 0
|
||
@State var bandwidth = 0
|
||
@State var spreadFactor = 0
|
||
@State var codingRate = 0
|
||
@State var rxBoostedGain = false
|
||
@State var overrideFrequency: Float = 0.0
|
||
@State var ignoreMqtt = false
|
||
@State var okToMqtt = false
|
||
|
||
let floatFormatter: NumberFormatter = {
|
||
let formatter = NumberFormatter()
|
||
formatter.numberStyle = .decimal
|
||
formatter.allowsFloats = true
|
||
formatter.maximumFractionDigits = 4
|
||
return formatter
|
||
}()
|
||
|
||
var body: some View {
|
||
VStack {
|
||
Form {
|
||
ConfigHeader(title: "LoRa", config: \.loRaConfig, node: node, onAppear: setLoRaValues)
|
||
|
||
Section(header: Text("Options")) {
|
||
|
||
VStack(alignment: .leading) {
|
||
Picker("Region", selection: $region ) {
|
||
ForEach(RegionCodes.allCases) { r in
|
||
Text(r.description)
|
||
}
|
||
}
|
||
.fixedSize()
|
||
Text("The region where you will be using your radios.")
|
||
.foregroundColor(.gray)
|
||
.font(.callout)
|
||
}
|
||
.pickerStyle(DefaultPickerStyle())
|
||
|
||
Toggle(isOn: $usePreset) {
|
||
Label("Use Preset", systemImage: "list.bullet.rectangle")
|
||
}
|
||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||
|
||
if usePreset {
|
||
VStack(alignment: .leading) {
|
||
Picker("Presets", selection: $modemPreset ) {
|
||
ForEach(ModemPresets.allCases) { m in
|
||
Text(m.description)
|
||
}
|
||
}
|
||
.pickerStyle(DefaultPickerStyle())
|
||
.fixedSize()
|
||
Text("Available modem presets, default is Long Fast.")
|
||
.foregroundColor(.gray)
|
||
.font(.callout)
|
||
}
|
||
}
|
||
}
|
||
Section(header: Text("Advanced")) {
|
||
|
||
Toggle(isOn: $ignoreMqtt) {
|
||
Label("Ignore MQTT", systemImage: "server.rack")
|
||
}
|
||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||
Toggle(isOn: $okToMqtt) {
|
||
Label("Ok to MQTT", systemImage: "network")
|
||
}
|
||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||
|
||
Toggle(isOn: $txEnabled) {
|
||
Label("Transmit Enabled", systemImage: "waveform.path")
|
||
}
|
||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||
|
||
if !usePreset {
|
||
HStack {
|
||
Picker("Bandwidth", selection: $bandwidth) {
|
||
ForEach(Bandwidths.allCases) { bw in
|
||
Text(bw.description)
|
||
.tag(bw.rawValue == 250 ? 0 : bw.rawValue)
|
||
}
|
||
}
|
||
}
|
||
HStack {
|
||
Picker("Spread Factor", selection: $spreadFactor) {
|
||
ForEach(7..<13) {
|
||
Text("\($0)")
|
||
.tag($0 == 12 ? 0 : $0)
|
||
}
|
||
}
|
||
}
|
||
HStack {
|
||
Picker("Coding Rate", selection: $codingRate) {
|
||
ForEach(5..<9) {
|
||
Text("\($0)")
|
||
.tag($0 == 8 ? 0 : $0)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
VStack(alignment: .leading) {
|
||
Picker("Number of hops", selection: $hopLimit) {
|
||
ForEach(0..<8) {
|
||
Text("\($0)")
|
||
.tag($0)
|
||
}
|
||
}
|
||
Text("Sets the maximum number of hops, default is 3. Increasing hops also increases congestion and should be used carefully. O hop broadcast messages will not get ACKs.")
|
||
.foregroundColor(.gray)
|
||
.font(.callout)
|
||
}
|
||
.pickerStyle(DefaultPickerStyle())
|
||
|
||
VStack(alignment: .leading) {
|
||
HStack {
|
||
Text("Frequency Slot")
|
||
.fixedSize()
|
||
TextField("Frequency Slot", value: $channelNum, formatter: formatter)
|
||
.toolbar {
|
||
ToolbarItemGroup(placement: .keyboard) {
|
||
Button("Dismiss") {
|
||
focusedField = nil
|
||
}
|
||
.font(.subheadline)
|
||
}
|
||
}
|
||
.keyboardType(.decimalPad)
|
||
.scrollDismissesKeyboard(.immediately)
|
||
.focused($focusedField, equals: .channelNum)
|
||
.disabled(overrideFrequency > 0.0)
|
||
}
|
||
Text("Your node’s operating frequency is calculated based on the region, modem preset, and this field. When 0, the slot is automatically calculated based on the primary channel name.")
|
||
.foregroundColor(.gray)
|
||
.font(.callout)
|
||
}
|
||
|
||
Toggle(isOn: $rxBoostedGain) {
|
||
Label("RX Boosted Gain", systemImage: "waveform.badge.plus")
|
||
}
|
||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||
|
||
HStack {
|
||
Label("Frequency Override", systemImage: "waveform.path.ecg")
|
||
Spacer()
|
||
TextField("Frequency Override", value: $overrideFrequency, formatter: floatFormatter)
|
||
.keyboardType(.decimalPad)
|
||
.scrollDismissesKeyboard(.immediately)
|
||
.focused($focusedField, equals: .frequencyOverride)
|
||
}
|
||
|
||
HStack {
|
||
Image(systemName: "antenna.radiowaves.left.and.right")
|
||
.foregroundColor(.accentColor)
|
||
Stepper("\(txPower)dBm Transmit Power", value: $txPower, in: 1...30, step: 1)
|
||
.padding(5)
|
||
}
|
||
}
|
||
}
|
||
.disabled(self.bleManager.connectedPeripheral == nil || node?.loRaConfig == nil)
|
||
|
||
SaveConfigButton(node: node, hasChanges: $hasChanges) {
|
||
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? 0, context: context)
|
||
if connectedNode != nil {
|
||
var lc = Config.LoRaConfig()
|
||
lc.hopLimit = UInt32(hopLimit)
|
||
lc.region = RegionCodes(rawValue: region)!.protoEnumValue()
|
||
lc.modemPreset = ModemPresets(rawValue: modemPreset)!.protoEnumValue()
|
||
lc.usePreset = usePreset
|
||
lc.txEnabled = txEnabled
|
||
lc.txPower = Int32(txPower)
|
||
lc.channelNum = UInt32(channelNum)
|
||
lc.bandwidth = UInt32(bandwidth)
|
||
lc.codingRate = UInt32(codingRate)
|
||
lc.spreadFactor = UInt32(spreadFactor)
|
||
lc.sx126XRxBoostedGain = rxBoostedGain
|
||
lc.overrideFrequency = overrideFrequency
|
||
lc.ignoreMqtt = ignoreMqtt
|
||
lc.configOkToMqtt = okToMqtt
|
||
if connectedNode?.num ?? -1 == node?.user?.num ?? 0 {
|
||
UserDefaults.modemPreset = modemPreset
|
||
}
|
||
let adminMessageId = bleManager.saveLoRaConfig(config: lc, fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0)
|
||
if adminMessageId > 0 {
|
||
// Should show a saved successfully alert once I know that to be true
|
||
// for now just disable the button after a successful save
|
||
hasChanges = false
|
||
goBack()
|
||
}
|
||
}
|
||
}
|
||
}
|
||
.navigationTitle("lora.config")
|
||
.navigationBarItems(
|
||
trailing: ZStack {
|
||
ConnectedDevice(
|
||
bluetoothOn: bleManager.isSwitchedOn,
|
||
deviceConnected: bleManager.connectedPeripheral != nil,
|
||
name: bleManager.connectedPeripheral?.shortName ?? "?"
|
||
)
|
||
}
|
||
)
|
||
.onFirstAppear {
|
||
// Need to request a LoRaConfig from the remote node before allowing changes
|
||
if let connectedPeripheral = bleManager.connectedPeripheral, let node {
|
||
let connectedNode = getNodeInfo(id: connectedPeripheral.num, context: context)
|
||
if let connectedNode {
|
||
if node.num != connectedNode.num {
|
||
if UserDefaults.enableAdministration {
|
||
/// 2.5 Administration with session passkey
|
||
let expiration = node.sessionExpiration ?? Date()
|
||
if expiration < Date() || node.loRaConfig == nil {
|
||
Logger.mesh.info("⚙️ Empty or expired lora config requesting via PKI admin")
|
||
|
||
_ = bleManager.requestLoRaConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0)
|
||
}
|
||
} else {
|
||
/// Legacy Administration
|
||
Logger.mesh.info("☠️ Using insecure legacy admin, empty lora config")
|
||
_ = bleManager.requestLoRaConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
.onChange(of: region) { _, newRegion in
|
||
if newRegion != node?.loRaConfig?.regionCode ?? -1 { hasChanges = true }
|
||
}
|
||
.onChange(of: usePreset) { _, newPreset in
|
||
if newPreset != node?.loRaConfig?.usePreset { hasChanges = true }
|
||
}
|
||
.onChange(of: modemPreset) { _, newModemPreset in
|
||
if newModemPreset != node?.loRaConfig?.modemPreset ?? -1 { hasChanges = true }
|
||
}
|
||
.onChange(of: hopLimit) { _, newHopLimit in
|
||
if newHopLimit != node?.loRaConfig?.hopLimit ?? -1 { hasChanges = true }
|
||
}
|
||
.onChange(of: channelNum) { _, newChannelNum in
|
||
if newChannelNum != node?.loRaConfig?.channelNum ?? -1 { hasChanges = true }
|
||
}
|
||
.onChange(of: bandwidth) { _, newBandwidth in
|
||
if newBandwidth != node?.loRaConfig?.bandwidth ?? -1 { hasChanges = true }
|
||
}
|
||
.onChange(of: codingRate) { _, newCodingRate in
|
||
if newCodingRate != node?.loRaConfig?.codingRate ?? -1 { hasChanges = true }
|
||
}
|
||
.onChange(of: spreadFactor) { _, newSpreadFactor in
|
||
if newSpreadFactor != node?.loRaConfig?.spreadFactor ?? -1 { hasChanges = true }
|
||
}
|
||
.onChange(of: rxBoostedGain) { _, newRxBoostedGain in
|
||
if newRxBoostedGain != node?.loRaConfig?.sx126xRxBoostedGain { hasChanges = true }
|
||
}
|
||
.onChange(of: overrideFrequency) { _, newOverrideFrequency in
|
||
if newOverrideFrequency != node?.loRaConfig?.overrideFrequency { hasChanges = true }
|
||
}
|
||
.onChange(of: txPower) { _, newTxPower in
|
||
if newTxPower != node?.loRaConfig?.txPower ?? -1 { hasChanges = true }
|
||
}
|
||
.onChange(of: txEnabled) { _, newTxEnabled in
|
||
if newTxEnabled != node?.loRaConfig?.txEnabled { hasChanges = true }
|
||
}
|
||
.onChange(of: ignoreMqtt) { _, newIgnoreMqtt in
|
||
if newIgnoreMqtt != node?.loRaConfig?.ignoreMqtt { hasChanges = true }
|
||
}
|
||
.onChange(of: okToMqtt) { _, newOkToMqtt in
|
||
if newOkToMqtt != node?.loRaConfig?.okToMqtt { hasChanges = true }
|
||
}
|
||
}
|
||
func setLoRaValues() {
|
||
self.hopLimit = Int(node?.loRaConfig?.hopLimit ?? 3)
|
||
self.region = Int(node?.loRaConfig?.regionCode ?? 0)
|
||
self.usePreset = node?.loRaConfig?.usePreset ?? true
|
||
self.modemPreset = Int(node?.loRaConfig?.modemPreset ?? 0)
|
||
self.txEnabled = node?.loRaConfig?.txEnabled ?? true
|
||
self.txPower = Int(node?.loRaConfig?.txPower ?? 0)
|
||
self.channelNum = Int(node?.loRaConfig?.channelNum ?? 0)
|
||
self.bandwidth = Int(node?.loRaConfig?.bandwidth ?? 0)
|
||
self.codingRate = Int(node?.loRaConfig?.codingRate ?? 0)
|
||
self.spreadFactor = Int(node?.loRaConfig?.spreadFactor ?? 0)
|
||
self.rxBoostedGain = node?.loRaConfig?.sx126xRxBoostedGain ?? false
|
||
self.overrideFrequency = node?.loRaConfig?.overrideFrequency ?? 0.0
|
||
self.ignoreMqtt = node?.loRaConfig?.ignoreMqtt ?? false
|
||
self.okToMqtt = node?.loRaConfig?.okToMqtt ?? false
|
||
self.hasChanges = false
|
||
}
|
||
}
|