From 20822309298676d97f33f773d9c705af5cc60aab Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Thu, 9 Feb 2023 22:59:39 -0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=BB=20ham=20params?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Meshtastic/Helpers/BLEManager.swift | 24 +++ Meshtastic/Views/Settings/UserConfig.swift | 203 +++++++++++---------- 2 files changed, 129 insertions(+), 98 deletions(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 48a7fca4..e8b47d24 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -1132,6 +1132,30 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { return 0 } + public func saveLicensedUser(ham: HamParameters, fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Int64 { + + var adminPacket = AdminMessage() + adminPacket.setHamMode = ham + var meshPacket: MeshPacket = MeshPacket() + meshPacket.to = UInt32(toUser.num) + meshPacket.from = UInt32(fromUser.num) + meshPacket.channel = UInt32(adminIndex) + meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Int64 { var adminPacket = AdminMessage() diff --git a/Meshtastic/Views/Settings/UserConfig.swift b/Meshtastic/Views/Settings/UserConfig.swift index d664da7d..966e1e75 100644 --- a/Meshtastic/Views/Settings/UserConfig.swift +++ b/Meshtastic/Views/Settings/UserConfig.swift @@ -5,6 +5,7 @@ // Copyright (c) Garth Vander Houwen 6/27/22. // import SwiftUI +import CoreData struct UserConfig: View { @@ -14,44 +15,56 @@ struct UserConfig: View { var node: NodeInfoEntity? + enum Field: Hashable { + case frequencyOverride + } + @State private var isPresentingFactoryResetConfirm: Bool = false @State private var isPresentingSaveConfirm: Bool = false - @State private var isPresentatingHamSheet: Bool = false @State var hasChanges = false @State var shortName = "" @State var longName = "" @State var isLicensed = false @State var overrideDutyCycle = false - @State var frequencyOverride = 0.0 + @State var overrideFrequency: Float = 0.0 @State var txPower = 0 + @FocusState var focusedField: Field? + + let floatFormatter: NumberFormatter = { + let formatter = NumberFormatter() + formatter.numberStyle = .decimal + return formatter + }() + var body: some View { - + VStack { Form { Section(header: Text("User Details")) { - HStack { - Label("Long Name", systemImage: "person.crop.rectangle.fill") - TextField("Long Name", text: $longName) - .onChange(of: longName, perform: { value in - let totalBytes = longName.utf8.count - // Only mess with the value if it is too big - if totalBytes > 36 { - let firstNBytes = Data(longName.utf8.prefix(36)) - if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) { - // Set the longName back to the last place where it was the right size - longName = maxBytesString + HStack { + Label(isLicensed ? "Call Sign" : "Long Name", systemImage: "person.crop.rectangle.fill") + TextField("Long Name", text: $longName) + .onChange(of: longName, perform: { value in + let totalBytes = longName.utf8.count + // Only mess with the value if it is too big + if totalBytes > 36 { + let firstNBytes = Data(longName.utf8.prefix(36)) + if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) { + // Set the longName back to the last place where it was the right size + longName = maxBytesString + } } - } - }) - } - .keyboardType(.default) - .disableAutocorrection(true) - Text("Long name can be up to 36 bytes long.") - .font(.caption) + }) + } + .keyboardType(.default) + .disableAutocorrection(true) + Text("\(String(isLicensed ? "Call Sign" : "Long Name")) can be up to 36 bytes long.") + .font(.caption) + HStack { Label("Short Name", systemImage: "circlebadge.fill") - TextField("Long Name", text: $shortName) + TextField("Short Name", text: $shortName) .foregroundColor(.gray) .onChange(of: shortName, perform: { value in let totalBytes = shortName.utf8.count @@ -68,17 +81,45 @@ struct UserConfig: View { } .keyboardType(.asciiCapable) .disableAutocorrection(true) - Text("The short name is used in maps and messaging and will be appended to the last 4 of the device MAC address to set the device's BLE Name. It can be up to 4 bytes long.") + Text("The short name will be appended to the last 4 of the device MAC address to set the device's BLE Name. It can be up to 4 bytes long.") .font(.caption) // Only manage ham mode for the locally connected node if node?.num ?? 0 > 0 && node?.num ?? 0 == bleManager.connectedPeripheral?.num ?? 0 { Toggle(isOn: $isLicensed) { - Label("Licensed User", systemImage: "person.text.rectangle") + Label("Licensed Operator", systemImage: "person.text.rectangle") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - Text("Enable only if you are a licensed amateur radio user for your region.") - .font(.caption) + if isLicensed { + + Text("Onboarding for licensed operators requires firmware 2.0.20 or greater. Make sure to refer to your local regulations and contact the local amateur frequency coordinators with questions.") + .font(.caption2) + Text("What licensed operator mode does:\n* Sets the node name to your call sign \n* Broadcasts node info every 10 minutes \n* Overrides frequency, dutycycle and tx power \n* Disables encryption") + .font(.caption2) + + HStack { + Label("Frequency", systemImage: "waveform.path.ecg") + Spacer() + TextField("Frequency Override", value: $overrideFrequency, formatter: floatFormatter) + .toolbar { + ToolbarItemGroup(placement: .keyboard) { + Button("dismiss.keyboard") { + focusedField = nil + } + .font(.subheadline) + } + } + .keyboardType(.decimalPad) + .scrollDismissesKeyboard(.immediately) + .focused($focusedField, equals: .frequencyOverride) + } + HStack { + Image(systemName: "antenna.radiowaves.left.and.right") + .foregroundColor(.accentColor) + Stepper("\(txPower)db Transmit Power", value: $txPower, in: 0...30, step: 1) + .padding(5) + } + } } } } @@ -104,13 +145,27 @@ struct UserConfig: View { let connectedUser = getUser(id: bleManager.connectedPeripheral.num, context: context) let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context) if connectedNode != nil { - var u = User() - u.shortName = shortName - u.longName = longName - let adminMessageId = bleManager.saveUser(config: u, fromUser: connectedUser, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) - if adminMessageId > 0 { - hasChanges = false - goBack() + + if !isLicensed { + var u = User() + u.shortName = shortName + u.longName = longName + let adminMessageId = bleManager.saveUser(config: u, fromUser: connectedUser, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) + if adminMessageId > 0 { + hasChanges = false + goBack() + } + } else { + var ham = HamParameters() + //ham.shortName = shortName + ham.callSign = longName + ham.txPower = Int32(txPower) + ham.frequency = overrideFrequency + let adminMessageId = bleManager.saveLicensedUser(ham: ham, fromUser: connectedUser, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) + if adminMessageId > 0 { + hasChanges = false + goBack() + } } } } @@ -120,73 +175,20 @@ struct UserConfig: View { } Spacer() } - .sheet(isPresented: $isPresentatingHamSheet) { - - VStack { - Form { - Section(header: Text("Licensed Amateur Radio Operators")) { - Text("Enable only if you are a licensed amateur radio user for your region.") - .font(.body) - Text("* Sets the node name to your call sign") - .font(.caption) - Text("* Override frequency, dutycycle and tx power") - .font(.caption) - Text("* Disables Device Encryption") - .font(.caption) - } - Section(header: Text("Licensed User Options")) { - - HStack { - Label("Call Sign", systemImage: "person.crop.rectangle.fill") - TextField("Call Sign", text: $longName) - .onChange(of: longName, perform: { value in - let totalBytes = longName.utf8.count - // Only mess with the value if it is too big - if totalBytes > 36 { - let firstNBytes = Data(longName.utf8.prefix(36)) - if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) { - // Set the longName back to the last place where it was the right size - longName = maxBytesString - } - } - }) - } - .keyboardType(.default) - .disableAutocorrection(true) - Text("Call sign can be up to 36 bytes long.") - .font(.caption) - Toggle(isOn: $overrideDutyCycle) { - Label("Override Duty Cycle", systemImage: "figure.indoor.cycle") - } - .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - HStack { - Image(systemName: "waveform.path.ecg") - .foregroundColor(.accentColor) - Stepper("\(String(format: "Frequency: %.2f", frequencyOverride))", value: $frequencyOverride, in: 400...950, step: 0.1) - .padding(5) - } - HStack { - Image(systemName: "antenna.radiowaves.left.and.right") - .foregroundColor(.accentColor) - Stepper("\(txPower)db Transmit Power", value: $txPower, in: 0...30, step: 1) - .padding(5) - } - } - } - } - .presentationDetents([.large]) - .presentationDragIndicator(.automatic) - } - .navigationTitle("User Config") .navigationBarItems(trailing: - ZStack { - ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") + ZStack { + ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") }) .onAppear { self.bleManager.context = context - self.shortName = node?.user!.shortName ?? "" - self.longName = node?.user!.longName ?? "" + self.shortName = node?.user?.shortName ?? "" + self.longName = node?.user?.longName ?? "" + self.isLicensed = node?.user?.isLicensed ?? false + self.txPower = Int(node?.loRaConfig?.txPower ?? 0) + self.overrideFrequency = node?.loRaConfig?.overrideFrequency ?? 0.00 + + self.hasChanges = false } .onChange(of: shortName) { newShort in @@ -200,10 +202,15 @@ struct UserConfig: View { } } .onChange(of: isLicensed) { newIsLicensed in - - if isLicensed { - isPresentatingHamSheet = true + if node != nil && node!.user != nil { + if newIsLicensed != node?.user!.isLicensed { hasChanges = true } } } + .onChange(of: overrideFrequency) { newOverrideFrequency in + hasChanges = true + } + .onChange(of: txPower) { newTxPower in + hasChanges = true + } } }