mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
Merge pull request #314 from meshtastic/2.0.14_Working_Changes
Working Changes for 2.0.14
This commit is contained in:
commit
e23c1d5166
6 changed files with 160 additions and 48 deletions
|
|
@ -1075,7 +1075,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.0.12;
|
||||
MARKETING_VERSION = 2.0.14;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
|
|
@ -1108,7 +1108,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.0.12;
|
||||
MARKETING_VERSION = 2.0.14;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ struct MapViewSwiftUI: UIViewRepresentable {
|
|||
switch annotation {
|
||||
|
||||
case _ as MKClusterAnnotation:
|
||||
let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "nodeGroup") as? MKMarkerAnnotationView ?? MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: "nodeGroup")
|
||||
let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "nodeGroup") as? MKMarkerAnnotationView ?? MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: "NodeGroup")
|
||||
annotationView.markerTintColor = .brown//.systemRed
|
||||
annotationView.displayPriority = .defaultLow
|
||||
annotationView.tag = -1
|
||||
|
|
@ -124,9 +124,21 @@ struct MapViewSwiftUI: UIViewRepresentable {
|
|||
annotationView.tag = -1
|
||||
annotationView.canShowCallout = true
|
||||
annotationView.glyphText = "📟"
|
||||
annotationView.clusteringIdentifier = "nodeGroup"
|
||||
annotationView.markerTintColor = UIColor(.indigo)
|
||||
annotationView.displayPriority = .required
|
||||
|
||||
let latest = parent.positions.last(where: { $0.nodePosition?.num ?? 0 == positionAnnotation.nodePosition?.num ?? -1 })
|
||||
|
||||
if latest == positionAnnotation {
|
||||
annotationView.markerTintColor = .systemRed
|
||||
annotationView.displayPriority = .required
|
||||
annotationView.titleVisibility = .visible
|
||||
}
|
||||
else {
|
||||
annotationView.markerTintColor = UIColor(.indigo)
|
||||
annotationView.displayPriority = .defaultHigh
|
||||
annotationView.titleVisibility = .adaptive
|
||||
annotationView.clusteringIdentifier = "nodeGroup"
|
||||
}
|
||||
|
||||
annotationView.titleVisibility = .adaptive
|
||||
let leftIcon = UIImageView(image: annotationView.glyphText?.image())
|
||||
leftIcon.backgroundColor = UIColor(.indigo)
|
||||
|
|
@ -172,7 +184,7 @@ struct MapViewSwiftUI: UIViewRepresentable {
|
|||
}
|
||||
annotationView.clusteringIdentifier = "waypointGroup"
|
||||
annotationView.markerTintColor = UIColor(.accentColor)
|
||||
annotationView.displayPriority = .required
|
||||
annotationView.displayPriority = .defaultHigh
|
||||
annotationView.titleVisibility = .adaptive
|
||||
let leftIcon = UIImageView(image: annotationView.glyphText?.image())
|
||||
leftIcon.backgroundColor = UIColor(.accentColor)
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ struct DeviceMetricsLog: View {
|
|||
var body: some View {
|
||||
NavigationStack {
|
||||
let oneDayAgo = Calendar.current.date(byAdding: .day, value: -3, to: Date())
|
||||
let data = node.telemetries!.filtered(using: NSPredicate(format: "metricsType == 0 && time !=nil && time >= %@", oneDayAgo! as CVarArg))
|
||||
let data = node.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0 && time !=nil && time >= %@", oneDayAgo! as CVarArg)) ?? []
|
||||
if data.count > 0 {
|
||||
GroupBox(label: Label("battery.level.trend", systemImage: "battery.100")) {
|
||||
Chart(data.array as! [TelemetryEntity], id: \.self) {
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ struct NodeMap: View {
|
|||
}
|
||||
}
|
||||
//&& nodePosition != nil
|
||||
@FetchRequest(sortDescriptors: [NSSortDescriptor(key: "time", ascending: false)],
|
||||
@FetchRequest(sortDescriptors: [NSSortDescriptor(key: "time", ascending: true)],
|
||||
predicate: NSPredicate(format: "time >= %@ && nodePosition != nil", Calendar.current.startOfDay(for: Date()) as NSDate), animation: .none)
|
||||
private var positions: FetchedResults<PositionEntity>
|
||||
|
||||
|
|
@ -40,7 +40,7 @@ struct NodeMap: View {
|
|||
), animation: .none)
|
||||
private var waypoints: FetchedResults<WaypointEntity>
|
||||
|
||||
@State private var mapType: MKMapType?
|
||||
@State private var mapType: MKMapType = .standard
|
||||
@State var waypointCoordinate: CLLocationCoordinate2D = LocationHelper.DefaultLocation
|
||||
@State var editingWaypoint: Int = 0
|
||||
@State private var presentingWaypointForm = false
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
// Copyright (c) Garth Vander Houwen 6/27/22.
|
||||
//
|
||||
import SwiftUI
|
||||
import CoreData
|
||||
|
||||
struct UserConfig: View {
|
||||
|
||||
|
|
@ -14,40 +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 var hasChanges = false
|
||||
@State var shortName = ""
|
||||
@State var longName = ""
|
||||
@State var isLicensed = false
|
||||
@State var overrideDutyCycle = false
|
||||
@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(.caption2)
|
||||
|
||||
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
|
||||
|
|
@ -64,15 +81,46 @@ 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.")
|
||||
.font(.caption)
|
||||
Text("The last 4 of the device MAC address will be appended to the short name to set the device's BLE Name. Short name can be up to 4 bytes long.")
|
||||
.font(.caption2)
|
||||
|
||||
Toggle(isOn: $isLicensed) {
|
||||
Label("Licensed User", systemImage: "person.text.rectangle")
|
||||
// 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 Operator", systemImage: "person.text.rectangle")
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
Text("Enable only if you are a licensed amateur radio user for your region.")
|
||||
.font(.caption)
|
||||
}
|
||||
}
|
||||
.disabled(bleManager.connectedPeripheral == nil)
|
||||
|
|
@ -97,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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -115,13 +177,16 @@ struct UserConfig: View {
|
|||
}
|
||||
.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
|
||||
|
|
@ -134,5 +199,16 @@ struct UserConfig: View {
|
|||
if newLong != node?.user!.longName { hasChanges = true }
|
||||
}
|
||||
}
|
||||
.onChange(of: isLicensed) { newIsLicensed in
|
||||
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