Merge pull request #314 from meshtastic/2.0.14_Working_Changes

Working Changes for 2.0.14
This commit is contained in:
Garth Vander Houwen 2023-02-10 20:57:10 -08:00 committed by GitHub
commit e23c1d5166
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 160 additions and 48 deletions

View file

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

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

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

View file

@ -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) {

View file

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

View file

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