From 257ec71a9389f02ec27866724dfb4c10cd99a25c Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 4 Aug 2024 13:13:02 -0700 Subject: [PATCH 01/19] True bearing for the node list --- Meshtastic.xcodeproj/project.pbxproj | 4 +++ Meshtastic/Extensions/CLLocation.swift | 28 +++++++++++++++++++ .../Views/Nodes/Helpers/NodeListItem.swift | 22 +++++++++++++++ 3 files changed, 54 insertions(+) create mode 100644 Meshtastic/Extensions/CLLocation.swift diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index dca5ef1cf..aa35c3b9c 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -56,6 +56,7 @@ DD1933762B0835D500771CD5 /* PositionAltitudeChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1933752B0835D500771CD5 /* PositionAltitudeChart.swift */; }; DD1933782B084F4200771CD5 /* Measurement.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1933772B084F4200771CD5 /* Measurement.swift */; }; DD1B8F402B35E2F10022AABC /* GPSStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1B8F3F2B35E2F10022AABC /* GPSStatus.swift */; }; + DD1BD0EB2C601795008C0C70 /* CLLocation.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1BD0EA2C601795008C0C70 /* CLLocation.swift */; }; DD1BF2F92776FE2E008C8D2F /* UserMessageList.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1BF2F82776FE2E008C8D2F /* UserMessageList.swift */; }; DD2160AF28C5552500C17253 /* MQTTConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2160AE28C5552500C17253 /* MQTTConfig.swift */; }; DD23A50F26FD1B4400D9B90C /* PeripheralModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD23A50E26FD1B4400D9B90C /* PeripheralModel.swift */; }; @@ -287,6 +288,7 @@ DD1933752B0835D500771CD5 /* PositionAltitudeChart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PositionAltitudeChart.swift; sourceTree = ""; }; DD1933772B084F4200771CD5 /* Measurement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Measurement.swift; sourceTree = ""; }; DD1B8F3F2B35E2F10022AABC /* GPSStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GPSStatus.swift; sourceTree = ""; }; + DD1BD0EA2C601795008C0C70 /* CLLocation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLLocation.swift; sourceTree = ""; }; DD1BF2F82776FE2E008C8D2F /* UserMessageList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserMessageList.swift; sourceTree = ""; }; DD2160AE28C5552500C17253 /* MQTTConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MQTTConfig.swift; sourceTree = ""; }; DD23A50E26FD1B4400D9B90C /* PeripheralModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeripheralModel.swift; sourceTree = ""; }; @@ -962,6 +964,7 @@ DD007BB12AA59B9A00F5FA12 /* CoreData */, DDFFA7462B3A7F3C004730DB /* Bundle.swift */, DDDB444529F8A96500EE2349 /* Character.swift */, + DD1BD0EA2C601795008C0C70 /* CLLocation.swift */, DDDB444929F8AA3A00EE2349 /* CLLocationCoordinate2D.swift */, DDDB444B29F8AAA600EE2349 /* Color.swift */, 25C49D8F2C471AEA0024FBD1 /* Constants.swift */, @@ -1269,6 +1272,7 @@ DDA9515A2BC6624100CEA535 /* TelemetryWeather.swift in Sources */, DDB75A232A13CDA9006ED576 /* BatteryLevelCompact.swift in Sources */, DDB75A162A0594AD006ED576 /* TileOverlay.swift in Sources */, + DD1BD0EB2C601795008C0C70 /* CLLocation.swift in Sources */, DDF924CA26FBB953009FE055 /* ConnectedDevice.swift in Sources */, DD3CC6BE28E4CD9800FA9159 /* BatteryGauge.swift in Sources */, DD6193772862F90F00E59241 /* CannedMessagesConfig.swift in Sources */, diff --git a/Meshtastic/Extensions/CLLocation.swift b/Meshtastic/Extensions/CLLocation.swift new file mode 100644 index 000000000..c4a818499 --- /dev/null +++ b/Meshtastic/Extensions/CLLocation.swift @@ -0,0 +1,28 @@ +// +// CLLocation.swift +// Meshtastic +// +// Copyright(c) Garth Vander Houwen 8/4/24. +// +import Foundation +import MapKit + +func degreesToRadians(degrees: Double) -> Double { return degrees * .pi / 180.0 } +func radiansToDegrees(radians: Double) -> Double { return radians * 180.0 / .pi } + +func getBearingBetweenTwoPoints(point1: CLLocation, point2: CLLocation) -> Double { + + let lat1 = degreesToRadians(degrees: point1.coordinate.latitude) + let lon1 = degreesToRadians(degrees: point1.coordinate.longitude) + + let lat2 = degreesToRadians(degrees: point2.coordinate.latitude) + let lon2 = degreesToRadians(degrees: point2.coordinate.longitude) + + let dLon = lon2 - lon1 + + let y = sin(dLon) * cos(lat2) + let x = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(dLon) + let radiansBearing = atan2(y, x) + + return radiansToDegrees(radians: radiansBearing) +} diff --git a/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift b/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift index a68a63cdd..70762e274 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift @@ -99,6 +99,17 @@ struct NodeListItem: View { DistanceText(meters: metersAway) .font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption) .foregroundColor(.gray) + let trueBearing = getBearingBetweenTwoPoints(point1: myCoord, point2: CLLocation(latitude: lastPostion.coordinate.latitude, longitude: lastPostion.coordinate.longitude)) + let headingDegrees = Angle.degrees(trueBearing) + Image(systemName: "location.north.circle.fill") + .font(.caption) + .symbolRenderingMode(.multicolor) + .clipShape(Circle()) + .rotationEffect(headingDegrees) + let heading = Measurement(value: trueBearing, unit: UnitAngle.degrees) + Text("\(heading.formatted(.measurement(width: .narrow, numberFormatStyle: .number.precision(.fractionLength(0)))))") + .font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption) + .foregroundColor(.gray) } } } else { @@ -114,6 +125,17 @@ struct NodeListItem: View { DistanceText(meters: metersAway) .font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption) .foregroundColor(.secondary) + let trueBearing = getBearingBetweenTwoPoints(point1: myCoord, point2: CLLocation(latitude: lastPostion.coordinate.latitude, longitude: lastPostion.coordinate.longitude)) + let headingDegrees = Angle.degrees(trueBearing) + Image(systemName: "location.north.circle.fill") + .font(.caption) + .symbolRenderingMode(.multicolor) + .clipShape(Circle()) + .rotationEffect(headingDegrees) + let heading = Measurement(value: trueBearing, unit: UnitAngle.degrees) + Text("\(heading.formatted(.measurement(width: .narrow, numberFormatStyle: .number.precision(.fractionLength(0)))))") + .font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption) + .foregroundColor(.gray) } } } From d9547c4362b1a649ee38d4597081742ee84c9bf1 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 4 Aug 2024 15:53:59 -0700 Subject: [PATCH 02/19] little feets --- Localizable.xcstrings | 3 - Meshtastic.xcodeproj/project.pbxproj | 12 +++ .../CoreData/NodeInfoEntityExtension.swift | 2 +- Meshtastic/Measurement/CustomFormatters.swift | 18 ++++ Meshtastic/MeshtasticApp.swift | 2 +- .../Nodes/Helpers/Map/PositionPopover.swift | 14 ++- .../Views/Nodes/Helpers/NodeDetail.swift | 2 +- .../Views/Nodes/Helpers/NodeInfoItem.swift | 88 +++++++++---------- .../Views/Nodes/Helpers/NodeListItem.swift | 2 +- 9 files changed, 90 insertions(+), 53 deletions(-) create mode 100644 Meshtastic/Measurement/CustomFormatters.swift diff --git a/Localizable.xcstrings b/Localizable.xcstrings index 37750d4d3..d80208ddc 100644 --- a/Localizable.xcstrings +++ b/Localizable.xcstrings @@ -723,9 +723,6 @@ }, "Altitude is Mean Sea Level" : { - }, - "Altitude: %@" : { - }, "Always point north" : { diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 204e3b638..0e490c662 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -57,6 +57,7 @@ DD1933782B084F4200771CD5 /* Measurement.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1933772B084F4200771CD5 /* Measurement.swift */; }; DD1B8F402B35E2F10022AABC /* GPSStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1B8F3F2B35E2F10022AABC /* GPSStatus.swift */; }; DD1BD0EB2C601795008C0C70 /* CLLocation.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1BD0EA2C601795008C0C70 /* CLLocation.swift */; }; + DD1BD0EE2C603C91008C0C70 /* CustomFormatters.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1BD0ED2C603C91008C0C70 /* CustomFormatters.swift */; }; DD1BF2F92776FE2E008C8D2F /* UserMessageList.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1BF2F82776FE2E008C8D2F /* UserMessageList.swift */; }; DD2160AF28C5552500C17253 /* MQTTConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2160AE28C5552500C17253 /* MQTTConfig.swift */; }; DD23A50F26FD1B4400D9B90C /* PeripheralModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD23A50E26FD1B4400D9B90C /* PeripheralModel.swift */; }; @@ -289,6 +290,7 @@ DD1933772B084F4200771CD5 /* Measurement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Measurement.swift; sourceTree = ""; }; DD1B8F3F2B35E2F10022AABC /* GPSStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GPSStatus.swift; sourceTree = ""; }; DD1BD0EA2C601795008C0C70 /* CLLocation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLLocation.swift; sourceTree = ""; }; + DD1BD0ED2C603C91008C0C70 /* CustomFormatters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomFormatters.swift; sourceTree = ""; }; DD1BF2F82776FE2E008C8D2F /* UserMessageList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserMessageList.swift; sourceTree = ""; }; DD2160AE28C5552500C17253 /* MQTTConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MQTTConfig.swift; sourceTree = ""; }; DD23A50E26FD1B4400D9B90C /* PeripheralModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeripheralModel.swift; sourceTree = ""; }; @@ -593,6 +595,14 @@ path = CoreData; sourceTree = ""; }; + DD1BD0EC2C603C5B008C0C70 /* Measurement */ = { + isa = PBXGroup; + children = ( + DD1BD0ED2C603C91008C0C70 /* CustomFormatters.swift */, + ); + path = Measurement; + sourceTree = ""; + }; DD47E3CA26F0E50300029299 /* Nodes */ = { isa = PBXGroup; children = ( @@ -803,6 +813,7 @@ DDC2E15626CE248E0042C5E4 /* Meshtastic */ = { isa = PBXGroup; children = ( + DD1BD0EC2C603C5B008C0C70 /* Measurement */, 25F5D5BC2C3F6D7B008036E3 /* Router */, DD7709392AA1ABA1007A8BF0 /* Tips */, DD90860A26F645B700DC5189 /* Meshtastic.entitlements */, @@ -1308,6 +1319,7 @@ DDC2E18F26CE25FE0042C5E4 /* ContentView.swift in Sources */, DD2553572855B02500E55709 /* LoRaConfig.swift in Sources */, DDB6ABD928B0A4BA00384BA1 /* BluetoothModes.swift in Sources */, + DD1BD0EE2C603C91008C0C70 /* CustomFormatters.swift in Sources */, DDD9E4E4284B208E003777C5 /* UserEntityExtension.swift in Sources */, DD2553592855B52700E55709 /* PositionConfig.swift in Sources */, DD97E96828EFE9A00056DDA4 /* About.swift in Sources */, diff --git a/Meshtastic/Extensions/CoreData/NodeInfoEntityExtension.swift b/Meshtastic/Extensions/CoreData/NodeInfoEntityExtension.swift index e88a2bee7..66c915df8 100644 --- a/Meshtastic/Extensions/CoreData/NodeInfoEntityExtension.swift +++ b/Meshtastic/Extensions/CoreData/NodeInfoEntityExtension.swift @@ -63,7 +63,7 @@ public func createNodeInfo(num: Int64, context: NSManagedObjectContext) -> NodeI newNode.num = Int64(num) let newUser = UserEntity(context: context) newUser.num = Int64(num) - let userId = String(format: "%2X", num) + let userId = num.toHex() newUser.userId = "!\(userId)" let last4 = String(userId.suffix(4)) newUser.longName = "Meshtastic \(last4)" diff --git a/Meshtastic/Measurement/CustomFormatters.swift b/Meshtastic/Measurement/CustomFormatters.swift new file mode 100644 index 000000000..e14c96fde --- /dev/null +++ b/Meshtastic/Measurement/CustomFormatters.swift @@ -0,0 +1,18 @@ +// +// CustomFormatters.swift +// Meshtastic +// +// Created by Garth Vander Houwen on 8/4/24. +// + +import Foundation + +/// Custom altitude formatter that always returns the provided unit +/// Needs to be used in conjunction with logic that checks for metric and displays the right value. +public var altitudeFormatter: MeasurementFormatter { + let formatter = MeasurementFormatter() + formatter.unitOptions = .providedUnit + formatter.unitStyle = .long + formatter.numberFormatter.maximumFractionDigits = 1 + return formatter +} diff --git a/Meshtastic/MeshtasticApp.swift b/Meshtastic/MeshtasticApp.swift index ff6914406..bea45fa67 100644 --- a/Meshtastic/MeshtasticApp.swift +++ b/Meshtastic/MeshtasticApp.swift @@ -93,7 +93,7 @@ struct MeshtasticAppleApp: App { if url.absoluteString.lowercased().contains("meshtastic.org/e/#") { if let components = self.incomingUrl?.absoluteString.components(separatedBy: "#") { self.addChannels = Bool(self.incomingUrl?["add"] ?? "false") ?? false - if ((self.incomingUrl?.absoluteString.lowercased().contains("?")) != nil) { + if (((self.incomingUrl?.absoluteString.lowercased().contains("?"))) != nil) { guard let cs = components.last!.components(separatedBy: "?").first else { return } diff --git a/Meshtastic/Views/Nodes/Helpers/Map/PositionPopover.swift b/Meshtastic/Views/Nodes/Helpers/Map/PositionPopover.swift index faa497609..b3b9c18e6 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/PositionPopover.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/PositionPopover.swift @@ -10,6 +10,7 @@ import MapKit @available(iOS 17.0, macOS 14.0, *) struct PositionPopover: View { + @ObservedObject var locationsHandler = LocationsHandler.shared @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager @@ -74,8 +75,17 @@ struct PositionPopover: View { .padding(.bottom, 5) /// Altitude Label { - Text("Altitude: \(distanceFormatter.string(fromDistance: Double(position.altitude)))") - .foregroundColor(.primary) + let formatter = MeasurementFormatter() + let distanceInMeters = Measurement(value: Double(position.altitude), unit: UnitLength.meters) + let distanceInFeet = distanceInMeters.converted(to: UnitLength.feet) + if Locale.current.measurementSystem == .metric { + Text(altitudeFormatter.string(from: distanceInMeters)) + .foregroundColor(.primary) + } else { + Text(altitudeFormatter.string(from: distanceInFeet)) + .foregroundColor(.primary) + } + } icon: { Image(systemName: "mountain.2.fill") .symbolRenderingMode(.hierarchical) diff --git a/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift b/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift index 9bc5781ae..4bf38ec7f 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift @@ -65,7 +65,7 @@ struct NodeDetail: View { .symbolRenderingMode(.multicolor) } Spacer() - Text(node.user?.userId ?? "?") + Text(node.num.toHex()) .textSelection(.enabled) } diff --git a/Meshtastic/Views/Nodes/Helpers/NodeInfoItem.swift b/Meshtastic/Views/Nodes/Helpers/NodeInfoItem.swift index 1355ce570..0335a6fda 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeInfoItem.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeInfoItem.swift @@ -19,56 +19,56 @@ struct NodeInfoItem: View { ) ?? ModemPresets.longFast var body: some View { - HStack { - Spacer() - CircleText( - text: node.user?.shortName ?? "?", - color: Color(UIColor(hex: UInt32(node.num))), - circleSize: 65 - ) - if let user = node.user { - VStack(alignment: .center) { - if user.hwModel != "UNSET" { - Image(user.hardwareImage ?? "UNSET") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 65, height: 65) - .cornerRadius(5) - Text(String(node.user!.hwModel ?? "unset".localized)) + ViewThatFits(in: .horizontal) { + HStack { + Spacer() + CircleText( + text: node.user?.shortName ?? "?", + color: Color(UIColor(hex: UInt32(node.num))), + circleSize: 75 + ) + if let user = node.user { + VStack(alignment: .center) { + if user.hwModel != "UNSET" { + Image(user.hardwareImage ?? "UNSET") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 65, height: 65) + .cornerRadius(5) + Text(String(node.user!.hwModel ?? "unset".localized)) + .font(.caption2) + .frame(maxWidth: 80) + } else { + Image(systemName: "person.crop.circle.badge.questionmark") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 65, height: 65) + .cornerRadius(5) + Text(String("incomplete".localized)) + .font(.caption) + .frame(maxWidth: 80) + } + } + } + if node.snr != 0 && !node.viaMqtt { + VStack(alignment: .center) { + let signalStrength = getLoRaSignalStrength(snr: node.snr, rssi: node.rssi, preset: modemPreset) + LoRaSignalStrengthIndicator(signalStrength: signalStrength) + Text("Signal \(signalStrength.description)").font(.footnote) + Text("SNR \(String(format: "%.2f", node.snr))dB") + .foregroundColor(getSnrColor(snr: node.snr, preset: modemPreset)) .font(.caption2) - .frame(maxWidth: 80) - } else { - Image(systemName: "person.crop.circle.badge.questionmark") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 65, height: 65) - .cornerRadius(5) - Text(String("incomplete".localized)) + Text("RSSI \(node.rssi)dB") + .foregroundColor(getRssiColor(rssi: node.rssi)) .font(.caption) - .frame(maxWidth: 80) } + .frame(minWidth: 110, maxWidth: 175) } - } - - if node.snr != 0 && !node.viaMqtt { - VStack(alignment: .center) { - let signalStrength = getLoRaSignalStrength(snr: node.snr, rssi: node.rssi, preset: modemPreset) - LoRaSignalStrengthIndicator(signalStrength: signalStrength) - Text("Signal \(signalStrength.description)").font(.footnote) - Text("SNR \(String(format: "%.2f", node.snr))dB") - .foregroundColor(getSnrColor(snr: node.snr, preset: modemPreset)) - .font(.caption2) - Text("RSSI \(node.rssi)dB") - .foregroundColor(getRssiColor(rssi: node.rssi)) - .font(.caption) + if node.telemetries?.count ?? 0 > 0 { + BatteryGauge(node: node) } - .frame(minWidth: 110, maxWidth: 175) - } - - if node.telemetries?.count ?? 0 > 0 { - BatteryGauge(node: node) + Spacer() } - Spacer() } } } diff --git a/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift b/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift index 70762e274..061d45ce0 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift @@ -186,7 +186,7 @@ struct NodeListItem: View { .frame(width: 30) } if node.hasEnvironmentMetrics { - Image(systemName: "cloud.sun.rain.fill") + Image(systemName: "cloud.sun.rain") .symbolRenderingMode(.hierarchical) .font(.callout) .frame(width: 30) From 7dae25d9498e993e74bd880c935cb105c7d75a0d Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 4 Aug 2024 15:59:08 -0700 Subject: [PATCH 03/19] Less parens --- Meshtastic/MeshtasticApp.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Meshtastic/MeshtasticApp.swift b/Meshtastic/MeshtasticApp.swift index bea45fa67..4d7dc4c16 100644 --- a/Meshtastic/MeshtasticApp.swift +++ b/Meshtastic/MeshtasticApp.swift @@ -93,7 +93,7 @@ struct MeshtasticAppleApp: App { if url.absoluteString.lowercased().contains("meshtastic.org/e/#") { if let components = self.incomingUrl?.absoluteString.components(separatedBy: "#") { self.addChannels = Bool(self.incomingUrl?["add"] ?? "false") ?? false - if (((self.incomingUrl?.absoluteString.lowercased().contains("?"))) != nil) { + if self.incomingUrl?.absoluteString.lowercased().contains("?") != nil { guard let cs = components.last!.components(separatedBy: "?").first else { return } From 24a4f709fa122798be5f457341d3776d823553e9 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 4 Aug 2024 16:18:13 -0700 Subject: [PATCH 04/19] Tighten up the hardware card --- .../Views/Nodes/Helpers/NodeInfoItem.swift | 62 ++++++++++--------- .../Views/Nodes/Helpers/NodeListItem.swift | 1 - 2 files changed, 32 insertions(+), 31 deletions(-) diff --git a/Meshtastic/Views/Nodes/Helpers/NodeInfoItem.swift b/Meshtastic/Views/Nodes/Helpers/NodeInfoItem.swift index 0335a6fda..a2ec475df 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeInfoItem.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeInfoItem.swift @@ -20,54 +20,56 @@ struct NodeInfoItem: View { var body: some View { ViewThatFits(in: .horizontal) { - HStack { - Spacer() - CircleText( - text: node.user?.shortName ?? "?", - color: Color(UIColor(hex: UInt32(node.num))), - circleSize: 75 - ) + VStack { if let user = node.user { - VStack(alignment: .center) { + HStack(alignment: .center) { if user.hwModel != "UNSET" { Image(user.hardwareImage ?? "UNSET") .resizable() .aspectRatio(contentMode: .fit) - .frame(width: 65, height: 65) + .frame(width: 75, height: 75) .cornerRadius(5) Text(String(node.user!.hwModel ?? "unset".localized)) - .font(.caption2) - .frame(maxWidth: 80) + .font(.callout) } else { Image(systemName: "person.crop.circle.badge.questionmark") .resizable() .aspectRatio(contentMode: .fit) - .frame(width: 65, height: 65) + .frame(width: 75, height: 75) .cornerRadius(5) Text(String("incomplete".localized)) - .font(.caption) - .frame(maxWidth: 80) + .font(.callout) } } } - if node.snr != 0 && !node.viaMqtt { - VStack(alignment: .center) { - let signalStrength = getLoRaSignalStrength(snr: node.snr, rssi: node.rssi, preset: modemPreset) - LoRaSignalStrengthIndicator(signalStrength: signalStrength) - Text("Signal \(signalStrength.description)").font(.footnote) - Text("SNR \(String(format: "%.2f", node.snr))dB") - .foregroundColor(getSnrColor(snr: node.snr, preset: modemPreset)) - .font(.caption2) - Text("RSSI \(node.rssi)dB") - .foregroundColor(getRssiColor(rssi: node.rssi)) - .font(.caption) + HStack { + Spacer() + CircleText( + text: node.user?.shortName ?? "?", + color: Color(UIColor(hex: UInt32(node.num))), + circleSize: 75 + ) + if node.snr != 0 && !node.viaMqtt { + VStack(alignment: .center) { + let signalStrength = getLoRaSignalStrength(snr: node.snr, rssi: node.rssi, preset: modemPreset) + LoRaSignalStrengthIndicator(signalStrength: signalStrength) + Text("Signal \(signalStrength.description)").font(.footnote) + Text("SNR \(String(format: "%.2f", node.snr))dB") + .foregroundColor(getSnrColor(snr: node.snr, preset: modemPreset)) + .font(.caption2) + Text("RSSI \(node.rssi)dB") + .foregroundColor(getRssiColor(rssi: node.rssi)) + .font(.caption) + } + .frame(minWidth: 110, maxWidth: 175) + } else { + Spacer() } - .frame(minWidth: 110, maxWidth: 175) - } - if node.telemetries?.count ?? 0 > 0 { - BatteryGauge(node: node) + if node.telemetries?.count ?? 0 > 0 { + BatteryGauge(node: node) + } + Spacer() } - Spacer() } } } diff --git a/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift b/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift index 061d45ce0..daefa65e9 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift @@ -21,7 +21,6 @@ struct NodeListItem: View { LazyVStack(alignment: .leading) { HStack { VStack(alignment: .leading) { - CircleText(text: node.user?.shortName ?? "?", color: Color(UIColor(hex: UInt32(node.num))), circleSize: 70) .padding(.trailing, 5) BatteryLevelCompact(node: node, font: .caption, iconFont: .callout, color: .accentColor) From bcb5ecef63ce35f3ea6edc46254695dc8f0bab17 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 4 Aug 2024 16:54:06 -0700 Subject: [PATCH 05/19] Space everything the same --- Meshtastic/Views/Nodes/Helpers/NodeInfoItem.swift | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Meshtastic/Views/Nodes/Helpers/NodeInfoItem.swift b/Meshtastic/Views/Nodes/Helpers/NodeInfoItem.swift index a2ec475df..5fd83c77b 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeInfoItem.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeInfoItem.swift @@ -27,7 +27,7 @@ struct NodeInfoItem: View { Image(user.hardwareImage ?? "UNSET") .resizable() .aspectRatio(contentMode: .fit) - .frame(width: 75, height: 75) + .frame(width: 65, height: 65) .cornerRadius(5) Text(String(node.user!.hwModel ?? "unset".localized)) .font(.callout) @@ -35,14 +35,14 @@ struct NodeInfoItem: View { Image(systemName: "person.crop.circle.badge.questionmark") .resizable() .aspectRatio(contentMode: .fit) - .frame(width: 75, height: 75) + .frame(width: 65, height: 65) .cornerRadius(5) Text(String("incomplete".localized)) .font(.callout) } } } - HStack { + HStack(alignment: .center) { Spacer() CircleText( text: node.user?.shortName ?? "?", @@ -50,22 +50,21 @@ struct NodeInfoItem: View { circleSize: 75 ) if node.snr != 0 && !node.viaMqtt { - VStack(alignment: .center) { + Spacer() + VStack { let signalStrength = getLoRaSignalStrength(snr: node.snr, rssi: node.rssi, preset: modemPreset) LoRaSignalStrengthIndicator(signalStrength: signalStrength) Text("Signal \(signalStrength.description)").font(.footnote) Text("SNR \(String(format: "%.2f", node.snr))dB") .foregroundColor(getSnrColor(snr: node.snr, preset: modemPreset)) - .font(.caption2) + .font(.caption) Text("RSSI \(node.rssi)dB") .foregroundColor(getRssiColor(rssi: node.rssi)) .font(.caption) } - .frame(minWidth: 110, maxWidth: 175) - } else { - Spacer() } if node.telemetries?.count ?? 0 > 0 { + Spacer() BatteryGauge(node: node) } Spacer() From 249b1971803ff9477871bac853056487f95e6ba1 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 4 Aug 2024 20:12:43 -0700 Subject: [PATCH 06/19] Remove empty logs header from ios 16 --- Meshtastic/Views/Settings/Settings.swift | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Meshtastic/Views/Settings/Settings.swift b/Meshtastic/Views/Settings/Settings.swift index faae073e9..880dabf2a 100644 --- a/Meshtastic/Views/Settings/Settings.swift +++ b/Meshtastic/Views/Settings/Settings.swift @@ -243,13 +243,11 @@ struct Settings: View { var loggingSection: some View { Section(header: Text("logging")) { - if #available (iOS 17.0, *) { - NavigationLink(value: SettingsNavigationState.debugLogs) { - Label { - Text("Logs") - } icon: { - Image(systemName: "scroll") - } + NavigationLink(value: SettingsNavigationState.debugLogs) { + Label { + Text("Logs") + } icon: { + Image(systemName: "scroll") } } } @@ -401,7 +399,9 @@ struct Settings: View { radioConfigurationSection deviceConfigurationSection moduleConfigurationSection - loggingSection + if #available (iOS 17.0, *) { + loggingSection + } #if DEBUG developersSection #endif From 440f3b3494c08fa95f28f80c0f84b680858b55c0 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 4 Aug 2024 20:31:12 -0700 Subject: [PATCH 07/19] Add device metrics chart for ios 16 --- Meshtastic/Extensions/Int.swift | 4 +- Meshtastic/Views/Nodes/DeviceMetricsLog.swift | 51 +++++++++++++++++++ Meshtastic/Views/Nodes/TraceRouteLog.swift | 1 - 3 files changed, 53 insertions(+), 3 deletions(-) diff --git a/Meshtastic/Extensions/Int.swift b/Meshtastic/Extensions/Int.swift index c9087d7f6..3f2cb3588 100644 --- a/Meshtastic/Extensions/Int.swift +++ b/Meshtastic/Extensions/Int.swift @@ -18,12 +18,12 @@ extension Int { extension UInt32 { func toHex() -> String { - return String(format: "!%2X", self) + return String(format: "!%2X", self).lowercased() } } extension Int64 { func toHex() -> String { - return String(format: "!%2X", self) + return String(format: "!%2X", self).lowercased() } } diff --git a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift index 3ffce6cb8..63e17424f 100644 --- a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift +++ b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift @@ -108,6 +108,57 @@ struct DeviceMetricsLog: View { .chartLegend(position: .automatic, alignment: .bottom) } else { // Fallback on earlier versions + Chart { + ForEach(chartData, id: \.self) { point in + Plot { + LineMark( + x: .value("x", point.time!), + y: .value("y", point.batteryLevel) + ) + } + .accessibilityLabel("Line Series") + .accessibilityValue("X: \(point.time!), Y: \(point.batteryLevel)") + .foregroundStyle(batteryChartColor) + .interpolationMethod(.linear) + Plot { + PointMark( + x: .value("x", point.time!), + y: .value("y", point.channelUtilization) + ) + .symbolSize(25) + } + .accessibilityLabel("Line Series") + .accessibilityValue("X: \(point.time!), Y: \(point.channelUtilization)") + .foregroundStyle(channelUtilizationChartColor) + RuleMark(y: .value("Network Status Orange", 25)) + .lineStyle(StrokeStyle(lineWidth: 1, dash: [5, 10])) + .foregroundStyle(.orange) + RuleMark(y: .value("Network Status Red", 50)) + .lineStyle(StrokeStyle(lineWidth: 1, dash: [5, 10])) + .foregroundStyle(.red) + Plot { + PointMark( + x: .value("x", point.time!), + y: .value("y", point.airUtilTx) + ) + .symbolSize(25) + } + .accessibilityLabel("Line Series") + .accessibilityValue("X: \(point.time!), Y: \(point.airUtilTx)") + .foregroundStyle(airtimeChartColor) + } + } + .chartXAxis(content: { + AxisMarks(position: .top) + }) + .chartXAxis(.automatic) + .chartYScale(domain: 0...100) + .chartForegroundStyleScale([ + idiom == .phone ? "Battery" : "Battery Level": batteryChartColor, + "Channel Utilization": channelUtilizationChartColor, + "Airtime": airtimeChartColor + ]) + .chartLegend(position: .automatic, alignment: .bottom) } } .frame(minHeight: 240) diff --git a/Meshtastic/Views/Nodes/TraceRouteLog.swift b/Meshtastic/Views/Nodes/TraceRouteLog.swift index 9556298af..345a42991 100644 --- a/Meshtastic/Views/Nodes/TraceRouteLog.swift +++ b/Meshtastic/Views/Nodes/TraceRouteLog.swift @@ -63,7 +63,6 @@ struct TraceRouteLog: View { } .font(.title2) } - if selectedRoute?.response ?? false { if selectedRoute?.hasPositions ?? false { Map(position: $position, bounds: MapCameraBounds(minimumDistance: 1, maximumDistance: .infinity), scope: mapScope) { From d45034cb64bd218ff6207827559d8f8252669fa5 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 4 Aug 2024 21:32:47 -0700 Subject: [PATCH 08/19] Update true bearing icons --- Meshtastic/Helpers/BLEManager.swift | 2 +- Meshtastic/Views/Nodes/Helpers/NodeDetail.swift | 1 - Meshtastic/Views/Nodes/Helpers/NodeListItem.swift | 8 ++++---- Meshtastic/Views/Nodes/NodeList.swift | 1 - 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 93879e029..9b018b0e9 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -814,7 +814,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate // MeshLogger.log("🕸️ MESH PACKET received for Audio App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") MeshLogger.log("🕸️ MESH PACKET received for Audio App UNHANDLED UNHANDLED") case .tracerouteApp: - if let routingMessage = try? RouteDiscovery(serializedData: decodedInfo.packet.decoded.payload) { + if let routingMessage = try? RouteDiscovery(serializedBytes: decodedInfo.packet.decoded.payload) { let traceRoute = getTraceRoute(id: Int64(decodedInfo.packet.decoded.requestID), context: context) traceRoute?.response = true traceRoute?.route = routingMessage.route diff --git a/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift b/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift index 4bf38ec7f..68769280f 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift @@ -43,7 +43,6 @@ struct NodeDetail: View { Section("Hardware") { NodeInfoItem(node: node) } - Section("Node") { HStack { Label { diff --git a/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift b/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift index daefa65e9..ba7dee6e8 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift @@ -100,8 +100,8 @@ struct NodeListItem: View { .foregroundColor(.gray) let trueBearing = getBearingBetweenTwoPoints(point1: myCoord, point2: CLLocation(latitude: lastPostion.coordinate.latitude, longitude: lastPostion.coordinate.longitude)) let headingDegrees = Angle.degrees(trueBearing) - Image(systemName: "location.north.circle.fill") - .font(.caption) + Image(systemName: "location.north") + .font(.callout) .symbolRenderingMode(.multicolor) .clipShape(Circle()) .rotationEffect(headingDegrees) @@ -126,8 +126,8 @@ struct NodeListItem: View { .foregroundColor(.secondary) let trueBearing = getBearingBetweenTwoPoints(point1: myCoord, point2: CLLocation(latitude: lastPostion.coordinate.latitude, longitude: lastPostion.coordinate.longitude)) let headingDegrees = Angle.degrees(trueBearing) - Image(systemName: "location.north.circle.fill") - .font(.caption) + Image(systemName: "location.north") + .font(.callout) .symbolRenderingMode(.multicolor) .clipShape(Circle()) .rotationEffect(headingDegrees) diff --git a/Meshtastic/Views/Nodes/NodeList.swift b/Meshtastic/Views/Nodes/NodeList.swift index 3773593b6..10df42996 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -31,7 +31,6 @@ struct NodeList: View { @State private var hopsAway: Double = -1.0 @State private var roleFilter = false @State private var deviceRoles: Set = [] - @State private var isPresentingTraceRouteSentAlert = false @State private var isPresentingPositionSentAlert = false @State private var isPresentingPositionFailedAlert = false From f4bcad2c683a529127d53f839525537684a4c55c Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 4 Aug 2024 21:42:02 -0700 Subject: [PATCH 09/19] Linting fixes --- Meshtastic/Extensions/UserDefaults.swift | 2 +- Meshtastic/Helpers/BLEManager.swift | 17 ++++++++--------- .../Views/MapKitMap/Custom/MapViewSwiftUI.swift | 2 +- Meshtastic/Views/Messages/UserMessageList.swift | 2 +- .../Helpers/Actions/DeleteNodeButton.swift | 6 +----- Meshtastic/Views/Nodes/MeshMap.swift | 2 +- 6 files changed, 13 insertions(+), 18 deletions(-) diff --git a/Meshtastic/Extensions/UserDefaults.swift b/Meshtastic/Extensions/UserDefaults.swift index 0fbd680bb..ca8441be2 100644 --- a/Meshtastic/Extensions/UserDefaults.swift +++ b/Meshtastic/Extensions/UserDefaults.swift @@ -158,7 +158,7 @@ extension UserDefaults { @UserDefault(.firmwareVersion, defaultValue: "0.0.0") static var firmwareVersion: String - + @UserDefault(.environmentEnableWeatherKit, defaultValue: true) static var environmentEnableWeatherKit: Bool diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 9b018b0e9..7a683df8b 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -590,7 +590,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate return } do { - let logRecord = try LogRecord(serializedData: characteristic.value!) + let logRecord = try LogRecord(serializedBytes: characteristic.value!) var message = logRecord.source.isEmpty ? logRecord.message : "[\(logRecord.source)] \(logRecord.message)" switch logRecord.level { case .debug: @@ -627,7 +627,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate var decodedInfo = FromRadio() do { - decodedInfo = try FromRadio(serializedData: characteristic.value!) + decodedInfo = try FromRadio(serializedBytes: characteristic.value!) } catch { Logger.services.error("💥 \(error.localizedDescription, privacy: .public) \(characteristic.value!, privacy: .public)") @@ -870,7 +870,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } } case .neighborinfoApp: - if let neighborInfo = try? NeighborInfo(serializedData: decodedInfo.packet.decoded.payload) { + if let neighborInfo = try? NeighborInfo(serializedBytes: decodedInfo.packet.decoded.payload) { // MeshLogger.log("🕸️ MESH PACKET received for Neighbor Info App UNHANDLED") MeshLogger.log("🕸️ MESH PACKET received for Neighbor Info App UNHANDLED \(neighborInfo)") } @@ -902,7 +902,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest() fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(connectedPeripheral.num)) do { - let fetchedNodeInfo = try context.fetch(fetchNodeInfoRequest) ?? [] + let fetchedNodeInfo = try context.fetch(fetchNodeInfoRequest) if fetchedNodeInfo.count == 1 { // Subscribe to Mqtt Client Proxy if enabled if fetchedNodeInfo[0].mqttConfig != nil && fetchedNodeInfo[0].mqttConfig?.enabled ?? false && fetchedNodeInfo[0].mqttConfig?.proxyToClientEnabled ?? false { @@ -1132,7 +1132,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate return success } - @MainActor + @MainActor public func getPositionFromPhoneGPS(destNum: Int64) -> Position? { var positionPacket = Position() if #available(iOS 17.0, macOS 14.0, *) { @@ -1265,7 +1265,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } if connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected { connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) - let logString = String.localizedStringWithFormat("mesh.log.sharelocation %@".localized, String(fromNodeNum)) Logger.services.debug("📍 \(logString)") return true @@ -1516,14 +1515,14 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate let decodedString = base64UrlString.base64urlToBase64() if let decodedData = Data(base64Encoded: decodedString) { do { - let channelSet: ChannelSet = try ChannelSet(serializedData: decodedData) + let channelSet: ChannelSet = try ChannelSet(serializedBytes: decodedData) for cs in channelSet.settings { if addChannels { // We are trying to add a channel so lets get the last index let fetchMyInfoRequest = MyInfoEntity.fetchRequest() fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", Int64(connectedPeripheral.num)) do { - let fetchedMyInfo = try context.fetch(fetchMyInfoRequest) ?? [] + let fetchedMyInfo = try context.fetch(fetchMyInfoRequest) if fetchedMyInfo.count == 1 { i = Int32(fetchedMyInfo[0].channels?.count ?? -1) myInfo = fetchedMyInfo[0] @@ -3001,7 +3000,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } func storeAndForwardPacket(packet: MeshPacket, connectedNodeNum: Int64, context: NSManagedObjectContext) { - if let storeAndForwardMessage = try? StoreAndForward(serializedData: packet.decoded.payload) { + if let storeAndForwardMessage = try? StoreAndForward(serializedBytes: packet.decoded.payload) { // Handle each of the store and forward request / response messages switch storeAndForwardMessage.rr { case .unset: diff --git a/Meshtastic/Views/MapKitMap/Custom/MapViewSwiftUI.swift b/Meshtastic/Views/MapKitMap/Custom/MapViewSwiftUI.swift index bf196f6d7..99c3c7665 100644 --- a/Meshtastic/Views/MapKitMap/Custom/MapViewSwiftUI.swift +++ b/Meshtastic/Views/MapKitMap/Custom/MapViewSwiftUI.swift @@ -110,7 +110,7 @@ struct MapViewSwiftUI: UIViewRepresentable { overlay.minimumZ = UserDefaults.mapTileServer.zoomRange.startIndex overlay.maximumZ = UserDefaults.mapTileServer.zoomRange.endIndex mapView.addOverlay(overlay, level: UserDefaults.mapTilesAboveLabels ? .aboveLabels : .aboveRoads) - + case .satellite: mapView.mapType = .satellite case .hybrid: diff --git a/Meshtastic/Views/Messages/UserMessageList.swift b/Meshtastic/Views/Messages/UserMessageList.swift index 6514ead8f..ce0eb1820 100644 --- a/Meshtastic/Views/Messages/UserMessageList.swift +++ b/Meshtastic/Views/Messages/UserMessageList.swift @@ -14,7 +14,7 @@ struct UserMessageList: View { @EnvironmentObject var appState: AppState @EnvironmentObject var bleManager: BLEManager @Environment(\.managedObjectContext) var context - + // Keyboard State @FocusState var messageFieldFocused: Bool // View State Items diff --git a/Meshtastic/Views/Nodes/Helpers/Actions/DeleteNodeButton.swift b/Meshtastic/Views/Nodes/Helpers/Actions/DeleteNodeButton.swift index 71c3a317c..ecf16f13f 100644 --- a/Meshtastic/Views/Nodes/Helpers/Actions/DeleteNodeButton.swift +++ b/Meshtastic/Views/Nodes/Helpers/Actions/DeleteNodeButton.swift @@ -3,16 +3,12 @@ import OSLog import SwiftUI struct DeleteNodeButton: View { - var bleManager: BLEManager + var bleManager: BLEManager var context: NSManagedObjectContext - var connectedNode: NodeInfoEntity - var node: NodeInfoEntity - @Environment(\.dismiss) private var dismiss - @State private var isPresentingAlert = false var body: some View { diff --git a/Meshtastic/Views/Nodes/MeshMap.swift b/Meshtastic/Views/Nodes/MeshMap.swift index 71cbfaf28..2dc8d1051 100644 --- a/Meshtastic/Views/Nodes/MeshMap.swift +++ b/Meshtastic/Views/Nodes/MeshMap.swift @@ -111,7 +111,7 @@ struct MeshMap: View { } .onChange(of: router.navigationState) { guard case .map(let selectedNodeNum) = router.navigationState else { return } - //TODO: handle deep link for waypoints + // TODO: handle deep link for waypoints } .onChange(of: (selectedMapLayer)) { newMapLayer in switch selectedMapLayer { From ce65d07e6593230fc19f5c5a58824f7b2e81e576 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 4 Aug 2024 21:46:20 -0700 Subject: [PATCH 10/19] more lint fixes --- Meshtastic/Persistence/Persistence.swift | 33 ------------------- .../MapKitMap/Custom/MapViewSwiftUI.swift | 1 - 2 files changed, 34 deletions(-) diff --git a/Meshtastic/Persistence/Persistence.swift b/Meshtastic/Persistence/Persistence.swift index 96808c739..93ba7f16c 100644 --- a/Meshtastic/Persistence/Persistence.swift +++ b/Meshtastic/Persistence/Persistence.swift @@ -103,39 +103,6 @@ extension NSPersistentContainer { case invalidSource(String) } - /// Restore a persistent store for a URL `backupURL`. - /// **Be very careful with this**. To restore a persistent store, the current persistent store must be removed from the container. When that happens, **all currently loaded Core Data objects** will become invalid. Using them after restoring will cause your app to crash. When calling this method you **must** ensure that you do not continue to use any previously fetched managed objects or existing fetched results controllers. **If this method does not throw, that does not mean your app is safe.** You need to take extra steps to prevent crashes. The details vary depending on the nature of your app. - /// - Parameter backupURL: A file URL containing backup copies of all currently loaded persistent stores. - /// - Throws: `CopyPersistentStoreError` in various situations. - /// - Returns: Nothing. If no errors are thrown, the restore is complete. -// func restorePersistentStore(from backupURL: URL) throws -> Void { -// guard backupURL.isFileURL else { -// throw CopyPersistentStoreErrors.invalidSource("Backup URL must be a file URL") -// } -// -// for persistentStoreDescription in persistentStoreDescriptions { -// guard let loadedStoreURL = persistentStoreDescription.url else { -// continue -// } -// guard FileManager.default.fileExists(atPath: backupURL.path) else { -// throw CopyPersistentStoreErrors.invalidSource("Missing backup store for \(backupURL)") -// } -// do { -// let storeOptions = persistentStoreDescription.options -// let configurationName = persistentStoreDescription.configuration -// let storeType = persistentStoreDescription.type -// -// // Replace the current store with the backup copy. This has a side effect of removing the current store from the Core Data stack. -// // When restoring, it's necessary to use the current persistent store coordinator. -// try persistentStoreCoordinator.replacePersistentStore(at: loadedStoreURL, destinationOptions: storeOptions, withPersistentStoreFrom: backupURL, sourceOptions: storeOptions, ofType: storeType) -// // Add the persistent store at the same location we've been using, because it was removed in the previous step. -// try persistentStoreCoordinator.addPersistentStore(ofType: storeType, configurationName: configurationName, at: loadedStoreURL, options: storeOptions) -// } catch { -// throw CopyPersistentStoreErrors.copyStoreError("Could not restore: \(error.localizedDescription)") -// } -// } -// } -// /// Restore backup persistent stores located in the directory referenced by `backupURL`. /// /// **Be very careful with this**. To restore a persistent store, the current persistent store must be removed from the container. When that happens, **all currently loaded Core Data objects** will become invalid. Using them after restoring will cause your app to crash. When calling this method you **must** ensure that you do not continue to use any previously fetched managed objects or existing fetched results controllers. **If this method does not throw, that does not mean your app is safe.** You need to take extra steps to prevent crashes. The details vary depending on the nature of your app. diff --git a/Meshtastic/Views/MapKitMap/Custom/MapViewSwiftUI.swift b/Meshtastic/Views/MapKitMap/Custom/MapViewSwiftUI.swift index 99c3c7665..b7629326d 100644 --- a/Meshtastic/Views/MapKitMap/Custom/MapViewSwiftUI.swift +++ b/Meshtastic/Views/MapKitMap/Custom/MapViewSwiftUI.swift @@ -110,7 +110,6 @@ struct MapViewSwiftUI: UIViewRepresentable { overlay.minimumZ = UserDefaults.mapTileServer.zoomRange.startIndex overlay.maximumZ = UserDefaults.mapTileServer.zoomRange.endIndex mapView.addOverlay(overlay, level: UserDefaults.mapTilesAboveLabels ? .aboveLabels : .aboveRoads) - case .satellite: mapView.mapType = .satellite case .hybrid: From 30f111510a755c805d2738fd9b91da1e7242d524 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 4 Aug 2024 21:57:13 -0700 Subject: [PATCH 11/19] Remove some duplicate logic --- .../Views/Helpers/LoRaSignalStrength.swift | 56 +++++++++---------- 1 file changed, 27 insertions(+), 29 deletions(-) diff --git a/Meshtastic/Views/Helpers/LoRaSignalStrength.swift b/Meshtastic/Views/Helpers/LoRaSignalStrength.swift index 829fd8372..c207d0842 100644 --- a/Meshtastic/Views/Helpers/LoRaSignalStrength.swift +++ b/Meshtastic/Views/Helpers/LoRaSignalStrength.swift @@ -14,36 +14,34 @@ struct LoRaSignalStrengthMeter: View { var compact: Bool var body: some View { - if snr != 0.0 && rssi != 0 { - let signalStrength = getLoRaSignalStrength(snr: snr, rssi: rssi, preset: preset) - let gradient = Gradient(colors: [.red, .orange, .yellow, .green]) - if !compact { - VStack { - LoRaSignalStrengthIndicator(signalStrength: signalStrength) - Text("Signal \(signalStrength.description)").font(.footnote) - Text("SNR \(String(format: "%.2f", snr))dB") - .foregroundColor(getSnrColor(snr: snr, preset: ModemPresets.longFast)) - .font(.caption2) - Text("RSSI \(rssi)dB") - .foregroundColor(getRssiColor(rssi: rssi)) - .font(.caption2) - } - } else { - VStack { - Gauge(value: Double(signalStrength.rawValue), in: 0...3) { - } currentValueLabel: { - Image(systemName: "dot.radiowaves.left.and.right") - .font(.callout) - .frame(width: 30) - Text("Signal \(signalStrength.description)") - .font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption) - .foregroundColor(.gray) - .fixedSize() - } - .gaugeStyle(.accessoryLinear) - .tint(gradient) - .font(.caption) + let signalStrength = getLoRaSignalStrength(snr: snr, rssi: rssi, preset: preset) + let gradient = Gradient(colors: [.red, .orange, .yellow, .green]) + if !compact { + VStack { + LoRaSignalStrengthIndicator(signalStrength: signalStrength) + Text("Signal \(signalStrength.description)").font(.footnote) + Text("SNR \(String(format: "%.2f", snr))dB") + .foregroundColor(getSnrColor(snr: snr, preset: ModemPresets.longFast)) + .font(.caption2) + Text("RSSI \(rssi)dB") + .foregroundColor(getRssiColor(rssi: rssi)) + .font(.caption2) + } + } else { + VStack { + Gauge(value: Double(signalStrength.rawValue), in: 0...3) { + } currentValueLabel: { + Image(systemName: "dot.radiowaves.left.and.right") + .font(.callout) + .frame(width: 30) + Text("Signal \(signalStrength.description)") + .font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption) + .foregroundColor(.gray) + .fixedSize() } + .gaugeStyle(.accessoryLinear) + .tint(gradient) + .font(.caption) } } } From 03f7d6d37df168cf266a2bc217292efca377a001 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 4 Aug 2024 22:06:31 -0700 Subject: [PATCH 12/19] Revert a couple of lint fixes --- Meshtastic/Helpers/BLEManager.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 7a683df8b..9b798819c 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -590,7 +590,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate return } do { - let logRecord = try LogRecord(serializedBytes: characteristic.value!) + let logRecord = try LogRecord(serializedData: characteristic.value!) var message = logRecord.source.isEmpty ? logRecord.message : "[\(logRecord.source)] \(logRecord.message)" switch logRecord.level { case .debug: @@ -627,7 +627,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate var decodedInfo = FromRadio() do { - decodedInfo = try FromRadio(serializedBytes: characteristic.value!) + decodedInfo = try FromRadio(serializedData: characteristic.value!) } catch { Logger.services.error("💥 \(error.localizedDescription, privacy: .public) \(characteristic.value!, privacy: .public)") From 80dba6eccd443a23f032500d5734021af09a74b6 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 4 Aug 2024 22:17:49 -0700 Subject: [PATCH 13/19] revert linting changes --- Meshtastic/Helpers/BLEManager.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 9b798819c..475235de8 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -814,7 +814,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate // MeshLogger.log("🕸️ MESH PACKET received for Audio App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") MeshLogger.log("🕸️ MESH PACKET received for Audio App UNHANDLED UNHANDLED") case .tracerouteApp: - if let routingMessage = try? RouteDiscovery(serializedBytes: decodedInfo.packet.decoded.payload) { + if let routingMessage = try? RouteDiscovery(serializedData: decodedInfo.packet.decoded.payload) { let traceRoute = getTraceRoute(id: Int64(decodedInfo.packet.decoded.requestID), context: context) traceRoute?.response = true traceRoute?.route = routingMessage.route @@ -870,7 +870,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } } case .neighborinfoApp: - if let neighborInfo = try? NeighborInfo(serializedBytes: decodedInfo.packet.decoded.payload) { + if let neighborInfo = try? NeighborInfo(serializedData: decodedInfo.packet.decoded.payload) { // MeshLogger.log("🕸️ MESH PACKET received for Neighbor Info App UNHANDLED") MeshLogger.log("🕸️ MESH PACKET received for Neighbor Info App UNHANDLED \(neighborInfo)") } @@ -1515,7 +1515,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate let decodedString = base64UrlString.base64urlToBase64() if let decodedData = Data(base64Encoded: decodedString) { do { - let channelSet: ChannelSet = try ChannelSet(serializedBytes: decodedData) + let channelSet: ChannelSet = try ChannelSet(serializedData: decodedData) for cs in channelSet.settings { if addChannels { // We are trying to add a channel so lets get the last index @@ -3000,7 +3000,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } func storeAndForwardPacket(packet: MeshPacket, connectedNodeNum: Int64, context: NSManagedObjectContext) { - if let storeAndForwardMessage = try? StoreAndForward(serializedBytes: packet.decoded.payload) { + if let storeAndForwardMessage = try? StoreAndForward(serializedData: packet.decoded.payload) { // Handle each of the store and forward request / response messages switch storeAndForwardMessage.rr { case .unset: From e8b6f00dc2abf8486aab9efaee66c413f74d5e3d Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Mon, 5 Aug 2024 19:35:41 -0700 Subject: [PATCH 14/19] Device hardware updates --- Meshtastic/Resources/DeviceHardware.json | 64 +++++++++++++------ .../Views/Nodes/Helpers/NodeInfoItem.swift | 31 ++++++++- Meshtastic/Views/Nodes/NodeList.swift | 1 + 3 files changed, 73 insertions(+), 23 deletions(-) diff --git a/Meshtastic/Resources/DeviceHardware.json b/Meshtastic/Resources/DeviceHardware.json index ad538bd01..16c50d253 100644 --- a/Meshtastic/Resources/DeviceHardware.json +++ b/Meshtastic/Resources/DeviceHardware.json @@ -5,7 +5,7 @@ "platformioTarget": "tlora-v2", "architecture": "esp32", "activelySupported": false, - "displayName": "T-LoRa V2" + "displayName": "LILYGO T-LoRa V2" }, { "hwModel": 2, @@ -13,7 +13,7 @@ "platformioTarget": "tlora-v1", "architecture": "esp32", "activelySupported": false, - "displayName": "T-LoRa V1" + "displayName": "LILYGO T-LoRa V1" }, { "hwModel": 3, @@ -21,7 +21,7 @@ "platformioTarget": "tlora-v2-1-1_6", "architecture": "esp32", "activelySupported": true, - "displayName": "T-LoRa V2.1-1.6" + "displayName": "LILYGO T-LoRa V2.1-1.6" }, { "hwModel": 4, @@ -29,7 +29,7 @@ "platformioTarget": "tbeam", "architecture": "esp32", "activelySupported": true, - "displayName": "T-Beam" + "displayName": "LILYGO T-Beam" }, { "hwModel": 5, @@ -45,7 +45,7 @@ "platformioTarget": "tbeam0_7", "architecture": "esp32", "activelySupported": false, - "displayName": "T-Beam V0.7" + "displayName": "LILYGO T-Beam V0.7" }, { "hwModel": 7, @@ -53,7 +53,7 @@ "platformioTarget": "t-echo", "architecture": "nrf52840", "activelySupported": true, - "displayName": "T-Echo" + "displayName": "LILYGO T-Echo" }, { "hwModel": 8, @@ -61,7 +61,7 @@ "platformioTarget": "tlora-v1_3", "architecture": "esp32", "activelySupported": false, - "displayName": "T-LoRa V1.1-1.3" + "displayName": "LILYGO T-LoRa V1.1-1.3" }, { "hwModel": 9, @@ -69,7 +69,7 @@ "platformioTarget": "rak4631", "architecture": "nrf52840", "activelySupported": true, - "displayName": "RAK4631" + "displayName": "RAK WisBlock 4631" }, { "hwModel": 10, @@ -93,7 +93,7 @@ "platformioTarget": "tbeam-s3-core", "architecture": "esp32-s3", "activelySupported": true, - "displayName": "T-Beam S3 Core" + "displayName": "LILYGO T-Beam S3 Core" }, { "hwModel": 13, @@ -101,7 +101,7 @@ "platformioTarget": "rak11200", "architecture": "esp32", "activelySupported": false, - "displayName": "RAK11200" + "displayName": "RAK WisBlock 11200" }, { "hwModel": 14, @@ -117,7 +117,7 @@ "platformioTarget": "tlora-v2-1-1_8", "architecture": "esp32", "activelySupported": true, - "displayName": "T-LoRa V2.1-1.8" + "displayName": "LILYGO T-LoRa V2.1-1.8" }, { "hwModel": 16, @@ -125,7 +125,7 @@ "platformioTarget": "tlora-t3s3-v1", "architecture": "esp32-s3", "activelySupported": true, - "displayName": "T-LoRa T3-S3" + "displayName": "LILYGO T-LoRa T3-S3" }, { "hwModel": 17, @@ -143,6 +143,14 @@ "activelySupported": true, "displayName": "Nano G2 Ultra" }, + { + "hwModel": 21, + "hwModelSlug": "WIO_WM1110", + "platformioTarget": "wio-tracker-wm1110", + "architecture": "nrf52840", + "activelySupported": true, + "displayName": "Seeed Wio WM1110 Tracker" + }, { "hwModel": 25, "hwModelSlug": "STATION_G1", @@ -157,7 +165,7 @@ "platformioTarget": "rak11310", "architecture": "rp2040", "activelySupported": true, - "displayName": "RAK11310" + "displayName": "RAK WisBlock 11310" }, { "hwModel": 29, @@ -165,7 +173,7 @@ "platformioTarget": "canaryone", "architecture": "nrf52840", "activelySupported": true, - "displayName": "CanaryOne" + "displayName": "Canary One" }, { "hwModel": 30, @@ -277,7 +285,7 @@ "platformioTarget": "t-deck", "architecture": "esp32-s3", "activelySupported": true, - "displayName": "T-Deck" + "displayName": "LILYGO T-Deck" }, { "hwModel": 51, @@ -285,7 +293,7 @@ "platformioTarget": "t-watch-s3", "architecture": "esp32-s3", "activelySupported": true, - "displayName": "T-Watch S3" + "displayName": "LILYGO T-Watch S3" }, { "hwModel": 52, @@ -360,11 +368,27 @@ "displayName": "RadioMaster 900 Bandit Nano" }, { - "hwModel": 21, - "hwModelSlug": "WIO_WM1110", - "platformioTarget": "wio-tracker-wm1110", + "hwModel": 67, + "hwModelSlug": "HELTEC_VISION_MASTER_E213", + "platformioTarget": "heltec-vision-master-e213", + "architecture": "esp32-s3", + "activelySupported": true, + "displayName": "Heltec Vision Master E213" + }, + { + "hwModel": 68, + "hwModelSlug": "HELTEC_VISION_MASTER_E290", + "platformioTarget": "heltec-vision-master-e290", + "architecture": "esp32-s3", + "activelySupported": true, + "displayName": "Heltec Vision Master E290" + }, + { + "hwModel": 71, + "hwModelSlug": "TRACKER_T1000_E", + "platformioTarget": "tracker-t1000-e", "architecture": "nrf52840", "activelySupported": true, - "displayName": "Seeed Wio WM1110 Tracker" + "displayName": "Seeed Card Tracker T1000-E" } ] diff --git a/Meshtastic/Views/Nodes/Helpers/NodeInfoItem.swift b/Meshtastic/Views/Nodes/Helpers/NodeInfoItem.swift index 5fd83c77b..34dcdecee 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeInfoItem.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeInfoItem.swift @@ -11,8 +11,9 @@ import MapKit struct NodeInfoItem: View { - @ObservedObject - var node: NodeInfoEntity + @ObservedObject var node: NodeInfoEntity + @State private var currentDevice: DeviceHardware? + @State private var deviceHardware: [DeviceHardware] = [] var modemPreset: ModemPresets = ModemPresets( rawValue: UserDefaults.modemPreset @@ -29,7 +30,7 @@ struct NodeInfoItem: View { .aspectRatio(contentMode: .fit) .frame(width: 65, height: 65) .cornerRadius(5) - Text(String(node.user!.hwModel ?? "unset".localized)) + Text(String(currentDevice?.displayName ?? (node.user?.hwModel ?? "unset".localized))) .font(.callout) } else { Image(systemName: "person.crop.circle.badge.questionmark") @@ -41,6 +42,30 @@ struct NodeInfoItem: View { .font(.callout) } } + .onAppear(perform: { + if currentDevice == nil { + Api().loadDeviceHardwareData { (hw) in + for device in hw { + let currentHardware = node.user?.hwModel ?? "UNSET" + let deviceString = device.hwModelSlug.replacingOccurrences(of: "_", with: "") + if deviceString == currentHardware { + currentDevice = device + } + } + } + } + }) + .onChange(of: node) { newNode in + Api().loadDeviceHardwareData { (hw) in + for device in hw { + let currentHardware = newNode.user?.hwModel ?? "UNSET" + let deviceString = device.hwModelSlug.replacingOccurrences(of: "_", with: "") + if deviceString == currentHardware { + currentDevice = device + } + } + } + } } HStack(alignment: .center) { Spacer() diff --git a/Meshtastic/Views/Nodes/NodeList.swift b/Meshtastic/Views/Nodes/NodeList.swift index 10df42996..7e9cf8658 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -37,6 +37,7 @@ struct NodeList: View { @State private var isPresentingDeleteNodeAlert = false @State private var deleteNodeId: Int64 = 0 + var boolFilters: [Bool] {[ isOnline, isFavorite, From 204b2ebb321a5fb829cc30026f8a45891b59c0ed Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 6 Aug 2024 09:25:53 -0700 Subject: [PATCH 15/19] Store hardware model id --- Meshtastic.xcodeproj/project.pbxproj | 4 +- Meshtastic/Helpers/MeshPackets.swift | 1 + .../Meshtastic.xcdatamodeld/.xccurrentversion | 2 +- .../contents | 449 ++++++++++++++++++ 4 files changed, 454 insertions(+), 2 deletions(-) create mode 100644 Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 42.xcdatamodel/contents diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 0e490c662..cca267e76 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -291,6 +291,7 @@ DD1B8F3F2B35E2F10022AABC /* GPSStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GPSStatus.swift; sourceTree = ""; }; DD1BD0EA2C601795008C0C70 /* CLLocation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLLocation.swift; sourceTree = ""; }; DD1BD0ED2C603C91008C0C70 /* CustomFormatters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomFormatters.swift; sourceTree = ""; }; + DD1BD0F12C61D3AD008C0C70 /* MeshtasticDataModelV 42.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 42.xcdatamodel"; sourceTree = ""; }; DD1BF2F82776FE2E008C8D2F /* UserMessageList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserMessageList.swift; sourceTree = ""; }; DD2160AE28C5552500C17253 /* MQTTConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MQTTConfig.swift; sourceTree = ""; }; DD23A50E26FD1B4400D9B90C /* PeripheralModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeripheralModel.swift; sourceTree = ""; }; @@ -1828,6 +1829,7 @@ DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */ = { isa = XCVersionGroup; children = ( + DD1BD0F12C61D3AD008C0C70 /* MeshtasticDataModelV 42.xcdatamodel */, DD2984A82C5AEF7500B1268D /* MeshtasticDataModelV 41.xcdatamodel */, DD68BAE72C417A74004C01A0 /* MeshtasticDataModelV 40.xcdatamodel */, DD3D17DC2C3D7B1400561584 /* MeshtasticDataModelV 39.xcdatamodel */, @@ -1870,7 +1872,7 @@ DD5D0A9A2931AD6B00F7EA61 /* MeshtasticDataModelV2.xcdatamodel */, DD3CC6BB28E366DF00FA9159 /* MeshtasticDataModel.xcdatamodel */, ); - currentVersion = DD2984A82C5AEF7500B1268D /* MeshtasticDataModelV 41.xcdatamodel */; + currentVersion = DD1BD0F12C61D3AD008C0C70 /* MeshtasticDataModelV 42.xcdatamodel */; name = Meshtastic.xcdatamodeld; path = Meshtastic/Meshtastic.xcdatamodeld; sourceTree = ""; diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index bb3687b55..7b237b2cb 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -287,6 +287,7 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje newUser.longName = nodeInfo.user.longName newUser.shortName = nodeInfo.user.shortName newUser.hwModel = String(describing: nodeInfo.user.hwModel).uppercased() + newUser.hwModelId = Int32(nodeInfo.user.hwModel.rawValue) newUser.isLicensed = nodeInfo.user.isLicensed newUser.role = Int32(nodeInfo.user.role.rawValue) newNode.user = newUser diff --git a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion index 9deb5562c..3ddf90f8d 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion +++ b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion @@ -3,6 +3,6 @@ _XCCurrentVersionName - MeshtasticDataModelV 41.xcdatamodel + MeshtasticDataModelV 42.xcdatamodel diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 42.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 42.xcdatamodel/contents new file mode 100644 index 000000000..bb7621d82 --- /dev/null +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 42.xcdatamodel/contents @@ -0,0 +1,449 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 894bd6673ceb79dc2017e129a1553d4068b1afe1 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 6 Aug 2024 12:57:46 -0700 Subject: [PATCH 16/19] Display name updates --- Meshtastic/Helpers/MeshPackets.swift | 6 ++++ .../contents | 1 + .../Views/Nodes/Helpers/NodeInfoItem.swift | 28 +------------------ 3 files changed, 8 insertions(+), 27 deletions(-) diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index 7b237b2cb..6e259c847 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -288,6 +288,12 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje newUser.shortName = nodeInfo.user.shortName newUser.hwModel = String(describing: nodeInfo.user.hwModel).uppercased() newUser.hwModelId = Int32(nodeInfo.user.hwModel.rawValue) + Task { + Api().loadDeviceHardwareData { (hw) in + let dh = hw.first(where: { $0.hwModel == newUser.hwModelId }) + newUser.hwDisplayName = dh?.displayName + } + } newUser.isLicensed = nodeInfo.user.isLicensed newUser.role = Int32(nodeInfo.user.role.rawValue) newNode.user = newUser diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 42.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 42.xcdatamodel/contents index bb7621d82..6b2eaa00b 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 42.xcdatamodel/contents +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 42.xcdatamodel/contents @@ -414,6 +414,7 @@ + diff --git a/Meshtastic/Views/Nodes/Helpers/NodeInfoItem.swift b/Meshtastic/Views/Nodes/Helpers/NodeInfoItem.swift index 34dcdecee..69235f112 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeInfoItem.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeInfoItem.swift @@ -12,8 +12,6 @@ import MapKit struct NodeInfoItem: View { @ObservedObject var node: NodeInfoEntity - @State private var currentDevice: DeviceHardware? - @State private var deviceHardware: [DeviceHardware] = [] var modemPreset: ModemPresets = ModemPresets( rawValue: UserDefaults.modemPreset @@ -30,7 +28,7 @@ struct NodeInfoItem: View { .aspectRatio(contentMode: .fit) .frame(width: 65, height: 65) .cornerRadius(5) - Text(String(currentDevice?.displayName ?? (node.user?.hwModel ?? "unset".localized))) + Text(String(node.user?.hwDisplayName ?? (node.user?.hwModel ?? "unset".localized))) .font(.callout) } else { Image(systemName: "person.crop.circle.badge.questionmark") @@ -42,30 +40,6 @@ struct NodeInfoItem: View { .font(.callout) } } - .onAppear(perform: { - if currentDevice == nil { - Api().loadDeviceHardwareData { (hw) in - for device in hw { - let currentHardware = node.user?.hwModel ?? "UNSET" - let deviceString = device.hwModelSlug.replacingOccurrences(of: "_", with: "") - if deviceString == currentHardware { - currentDevice = device - } - } - } - } - }) - .onChange(of: node) { newNode in - Api().loadDeviceHardwareData { (hw) in - for device in hw { - let currentHardware = newNode.user?.hwModel ?? "UNSET" - let deviceString = device.hwModelSlug.replacingOccurrences(of: "_", with: "") - if deviceString == currentHardware { - currentDevice = device - } - } - } - } } HStack(alignment: .center) { Spacer() From dbdf869c8447905951cdc79f153a57b9ac0dbf21 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 6 Aug 2024 14:53:32 -0700 Subject: [PATCH 17/19] Pretty display name --- Meshtastic/Helpers/MeshPackets.swift | 7 +++++++ Meshtastic/Persistence/UpdateCoreData.swift | 14 ++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index 6e259c847..d2987e46d 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -361,6 +361,13 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje fetchedNode[0].user!.isLicensed = nodeInfo.user.isLicensed fetchedNode[0].user!.role = Int32(nodeInfo.user.role.rawValue) fetchedNode[0].user!.hwModel = String(describing: nodeInfo.user.hwModel).uppercased() + fetchedNode[0].user!.hwModelId = Int32(nodeInfo.user.hwModel.rawValue) + Task { + Api().loadDeviceHardwareData { (hw) in + let dh = hw.first(where: { $0.hwModel == fetchedNode[0].user!.hwModelId }) + fetchedNode[0].user!.hwDisplayName = dh?.displayName + } + } } else { if fetchedNode[0].user == nil && nodeInfo.num > Constants.minimumNodeNum { diff --git a/Meshtastic/Persistence/UpdateCoreData.swift b/Meshtastic/Persistence/UpdateCoreData.swift index 3fac0312c..507950ceb 100644 --- a/Meshtastic/Persistence/UpdateCoreData.swift +++ b/Meshtastic/Persistence/UpdateCoreData.swift @@ -177,6 +177,13 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) newUser.shortName = newUserMessage.shortName newUser.role = Int32(newUserMessage.role.rawValue) newUser.hwModel = String(describing: newUserMessage.hwModel).uppercased() + newUser.hwModelId = Int32(newUserMessage.hwModel.rawValue) + Task { + Api().loadDeviceHardwareData { (hw) in + let dh = hw.first(where: { $0.hwModel == newUser.hwModelId }) + newUser.hwDisplayName = dh?.displayName + } + } newNode.user = newUser if UserDefaults.newNodeNotifications { @@ -257,6 +264,13 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) fetchedNode[0].user!.shortName = nodeInfoMessage.user.shortName fetchedNode[0].user!.role = Int32(nodeInfoMessage.user.role.rawValue) fetchedNode[0].user!.hwModel = String(describing: nodeInfoMessage.user.hwModel).uppercased() + fetchedNode[0].user!.hwModelId = Int32(nodeInfoMessage.user.hwModel.rawValue) + Task { + Api().loadDeviceHardwareData { (hw) in + let dh = hw.first(where: { $0.hwModel == fetchedNode[0].user!.hwModelId }) + fetchedNode[0].user!.hwDisplayName = dh?.displayName + } + } } } else if packet.hopStart != 0 && packet.hopLimit <= packet.hopStart { fetchedNode[0].hopsAway = Int32(packet.hopStart - packet.hopLimit) From 91022d223eaa84a1e5d0c699b39546c5089a555b Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 6 Aug 2024 15:26:16 -0700 Subject: [PATCH 18/19] Crash less --- Meshtastic/Persistence/UpdateCoreData.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Meshtastic/Persistence/UpdateCoreData.swift b/Meshtastic/Persistence/UpdateCoreData.swift index 507950ceb..c407f4143 100644 --- a/Meshtastic/Persistence/UpdateCoreData.swift +++ b/Meshtastic/Persistence/UpdateCoreData.swift @@ -267,7 +267,7 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) fetchedNode[0].user!.hwModelId = Int32(nodeInfoMessage.user.hwModel.rawValue) Task { Api().loadDeviceHardwareData { (hw) in - let dh = hw.first(where: { $0.hwModel == fetchedNode[0].user!.hwModelId }) + let dh = hw.first(where: { $0.hwModel == fetchedNode[0].user?.hwModelId ?? 0 }) fetchedNode[0].user!.hwDisplayName = dh?.displayName } } From 6bb8d03a9807f4d6cd63313786ee5b3ccb1ee96f Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 6 Aug 2024 21:56:43 -0700 Subject: [PATCH 19/19] Add display name to search --- Meshtastic/Resources/DeviceHardware.json | 8 ++++++++ Meshtastic/Views/Messages/UserList.swift | 2 +- Meshtastic/Views/Nodes/NodeList.swift | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Meshtastic/Resources/DeviceHardware.json b/Meshtastic/Resources/DeviceHardware.json index 16c50d253..09eb2e6c1 100644 --- a/Meshtastic/Resources/DeviceHardware.json +++ b/Meshtastic/Resources/DeviceHardware.json @@ -367,6 +367,14 @@ "activelySupported": true, "displayName": "RadioMaster 900 Bandit Nano" }, + { + "hwModel": 66, + "hwModelSlug": "HELTEC_VISION_MASTER_T190", + "platformioTarget": "heltec-vision-master-T190", + "architecture": "esp32-s3", + "activelySupported": true, + "displayName": "Heltec Vision Master T190" + }, { "hwModel": 67, "hwModelSlug": "HELTEC_VISION_MASTER_E213", diff --git a/Meshtastic/Views/Messages/UserList.swift b/Meshtastic/Views/Messages/UserList.swift index 5c7214fe7..13eb837df 100644 --- a/Meshtastic/Views/Messages/UserList.swift +++ b/Meshtastic/Views/Messages/UserList.swift @@ -237,7 +237,7 @@ struct UserList: View { private func searchUserList() { /// Case Insensitive Search Text Predicates - let searchPredicates = ["userId", "numString", "hwModel", "longName", "shortName"].map { property in + let searchPredicates = ["userId", "numString", "hwModel", "hwDisplayName", "longName", "shortName"].map { property in return NSPredicate(format: "%K CONTAINS[c] %@", property, searchText) } /// Create a compound predicate using each text search preicate as an OR diff --git a/Meshtastic/Views/Nodes/NodeList.swift b/Meshtastic/Views/Nodes/NodeList.swift index 7e9cf8658..c442b315a 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -344,7 +344,7 @@ struct NodeList: View { private func searchNodeList() async { /// Case Insensitive Search Text Predicates - let searchPredicates = ["user.userId", "user.numString", "user.hwModel", "user.longName", "user.shortName"].map { property in + let searchPredicates = ["user.userId", "user.numString", "user.hwModel", "user.hwDisplayName", "user.longName", "user.shortName"].map { property in return NSPredicate(format: "%K CONTAINS[c] %@", property, searchText) } /// Create a compound predicate using each text search preicate as an OR