📻 ham params

This commit is contained in:
Garth Vander Houwen 2023-02-09 22:59:39 -08:00
parent 85c0582649
commit 2082230929
2 changed files with 129 additions and 98 deletions

View file

@ -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)..<UInt32.max)
meshPacket.priority = MeshPacket.Priority.reliable
meshPacket.wantAck = true
var dataMessage = DataMessage()
dataMessage.payload = try! adminPacket.serializedData()
dataMessage.portnum = PortNum.adminApp
meshPacket.decoded = dataMessage
let messageDescription = "🛟 Saved Ham Parameters for \(toUser.longName ?? NSLocalizedString("unknown", comment: "Unknown"))"
if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) {
return Int64(meshPacket.id)
}
return 0
}
public func saveBluetoothConfig(config: Config.BluetoothConfig, fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Int64 {
var adminPacket = AdminMessage()

View file

@ -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
}
}
}