mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
📻 ham params
This commit is contained in:
parent
85c0582649
commit
2082230929
2 changed files with 129 additions and 98 deletions
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue