From 0b11f8ed7d18b434ccbf9ba3b6d7a2e436376988 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 19 Sep 2023 17:06:47 -0700 Subject: [PATCH] Assorted cleanup --- Meshtastic.xcodeproj/project.pbxproj | 4 + Meshtastic/Enums/AppSettingsEnums.swift | 3 - .../Extensions/CLLocationCoordinate2D.swift | 42 +++++++++ Meshtastic/Helpers/BLEManager.swift | 32 +++---- Meshtastic/Helpers/LocationHelper.swift | 10 +++ .../Views/Messages/ChannelMessageList.swift | 4 +- .../Views/Messages/UserMessageList.swift | 4 +- .../Views/Nodes/Helpers/NodeMapSwiftUI.swift | 86 +++++-------------- .../Views/Nodes/Helpers/PositionPopover.swift | 28 +++--- Meshtastic/Views/Nodes/PositionLog.swift | 3 +- .../Settings/Config/PositionConfig.swift | 2 +- 11 files changed, 115 insertions(+), 103 deletions(-) diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 27c5a4f9..e7d6a2eb 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -166,6 +166,7 @@ DDDE5A1329AFEAB900490C6C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DDDE5A1229AFEAB900490C6C /* Assets.xcassets */; }; DDDE5A1429AFEAB900490C6C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DDDE5A1229AFEAB900490C6C /* Assets.xcassets */; }; DDE0F7C5295F77B700B8AAB3 /* AppSettingsEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE0F7C4295F77B700B8AAB3 /* AppSettingsEnums.swift */; }; + DDE179302ABA2482005777A8 /* LocationDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE1792F2ABA2482005777A8 /* LocationDataManager.swift */; }; DDF6B2482A9AEBF500BA6931 /* StoreForward.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF6B2472A9AEBF500BA6931 /* StoreForward.swift */; }; DDF924CA26FBB953009FE055 /* ConnectedDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF924C926FBB953009FE055 /* ConnectedDevice.swift */; }; DDFEB3BB29900C1200EE7472 /* CurrentConditionsCompact.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDFEB3BA29900C1200EE7472 /* CurrentConditionsCompact.swift */; }; @@ -389,6 +390,7 @@ DDDE5A1229AFEAB900490C6C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; DDDEE5E229DBE43E00A8E078 /* MeshtasticDataModelV11.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV11.xcdatamodel; sourceTree = ""; }; DDE0F7C4295F77B700B8AAB3 /* AppSettingsEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettingsEnums.swift; sourceTree = ""; }; + DDE1792F2ABA2482005777A8 /* LocationDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationDataManager.swift; sourceTree = ""; }; DDEE03EC29544A1000FCAD57 /* MeshtasticDataModelV4.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV4.xcdatamodel; sourceTree = ""; }; DDF6B2462A9AEB9E00BA6931 /* MeshtasticDataModelV17.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV17.xcdatamodel; sourceTree = ""; }; DDF6B2472A9AEBF500BA6931 /* StoreForward.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreForward.swift; sourceTree = ""; }; @@ -791,6 +793,7 @@ DDA6B2E828419CF2003E8C16 /* MeshPackets.swift */, DD964FBC296E6B01007C176F /* EmojiOnlyTextField.swift */, DDDB443C29F6592F00EE2349 /* NetworkManager.swift */, + DDE1792F2ABA2482005777A8 /* LocationDataManager.swift */, ); path = Helpers; sourceTree = ""; @@ -1180,6 +1183,7 @@ DD5E520E298EE33B00D21B61 /* mqtt.pb.swift in Sources */, DD994B69295F88B60013760A /* IntervalEnums.swift in Sources */, DD415828285859C4009B0E59 /* TelemetryConfig.swift in Sources */, + DDE179302ABA2482005777A8 /* LocationDataManager.swift in Sources */, DDDB443D29F6592F00EE2349 /* NetworkManager.swift in Sources */, DDB6CCFB2AAF805100945AF6 /* NodeMapSwiftUI.swift in Sources */, DD73FD1128750779000852D6 /* PositionLog.swift in Sources */, diff --git a/Meshtastic/Enums/AppSettingsEnums.swift b/Meshtastic/Enums/AppSettingsEnums.swift index d2d13979..dd0a94ed 100644 --- a/Meshtastic/Enums/AppSettingsEnums.swift +++ b/Meshtastic/Enums/AppSettingsEnums.swift @@ -85,7 +85,6 @@ enum UserTrackingModes: Int, CaseIterable, Identifiable { } enum LocationUpdateInterval: Int, CaseIterable, Identifiable { - case fiveSeconds = 5 case tenSeconds = 10 case fifteenSeconds = 15 case thirtySeconds = 30 @@ -97,8 +96,6 @@ enum LocationUpdateInterval: Int, CaseIterable, Identifiable { var id: Int { self.rawValue } var description: String { switch self { - case .fiveSeconds: - return "interval.five.seconds".localized case .tenSeconds: return "interval.ten.seconds".localized case .fifteenSeconds: diff --git a/Meshtastic/Extensions/CLLocationCoordinate2D.swift b/Meshtastic/Extensions/CLLocationCoordinate2D.swift index 32a47774..58287add 100644 --- a/Meshtastic/Extensions/CLLocationCoordinate2D.swift +++ b/Meshtastic/Extensions/CLLocationCoordinate2D.swift @@ -18,3 +18,45 @@ extension CLLocationCoordinate2D { return from.distance(from: to) } } + +extension [CLLocationCoordinate2D] { + /// Get Convex Hull For an array of CLLocationCoordinate2D positions + /// - Returns: A smaller CLLocationCoordinate2D array containing only the points necessary to create a convex hull polygon + func getConvexHull() -> [CLLocationCoordinate2D] { + /// X = longitude + /// Y = latitude + /// 2D cross product of OA and OB vectors, i.e. z-component of their 3D cross product. + /// Returns a positive value, if OAB makes a counter-clockwise turn, + /// negative for clockwise turn, and zero if the points are collinear. + func cross(P: CLLocationCoordinate2D, A: CLLocationCoordinate2D, B: CLLocationCoordinate2D) -> Double { + let part1 = (A.longitude - P.longitude) * (B.latitude - P.latitude) + let part2 = (A.latitude - P.latitude) * (B.longitude - P.longitude) + return part1 - part2; + } + // Sort points lexicographically + let points = self.sorted() { + $0.longitude == $1.longitude ? $0.latitude < $1.latitude : $0.longitude < $1.longitude + } + // Build the lower hull + var lower: [CLLocationCoordinate2D] = [] + for p in points { + while lower.count >= 2 && cross(P: lower[lower.count - 2], A: lower[lower.count - 1], B: p) <= 0 { + lower.removeLast() + } + lower.append(p) + } + // Build upper hull + var upper: [CLLocationCoordinate2D] = [] + for p in points.reversed() { + while upper.count >= 2 && cross(P: upper[upper.count-2], A: upper[upper.count-1], B: p) <= 0 { + upper.removeLast() + } + upper.append(p) + } + // Last point of upper list is omitted because it is repeated at the + // beginning of the lower list. + upper.removeLast() + // Concatenation of the lower and upper hulls gives the convex hull. + return (upper + lower) + } +} diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 1547fe77..0251a62a 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -42,6 +42,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate var lastPosition: CLLocationCoordinate2D? let emptyNodeNum: UInt32 = 4294967295 let mqttManager = MqttClientProxyManager.shared + let locationHelper = LocationHelper.shared var wantRangeTestPackets = false /* Meshtastic Service Details */ var TORADIO_characteristic: CBCharacteristic! @@ -834,27 +835,28 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate return success } - public func sendPosition(destNum: Int64, wantResponse: Bool, smartPosition: Bool) -> Bool { + public func sendPosition(destNum: Int64, wantResponse: Bool) -> Bool { var success = false let fromNodeNum = connectedPeripheral.num if fromNodeNum <= 0 || LocationHelper.currentLocation.distance(from: LocationHelper.DefaultLocation) == 0.0 { return false } - if smartPosition { - if lastPosition != nil { - let connectedNode = getNodeInfo(id: connectedPeripheral?.num ?? 0, context: context!) - if connectedNode?.positionConfig?.smartPositionEnabled ?? false { - if lastPosition!.distance(from: LocationHelper.currentLocation) < Double(connectedNode?.positionConfig?.broadcastSmartMinimumDistance ?? 50) { - return false - } - } - } - } - lastPosition = LocationHelper.currentLocation +// if smartPosition { +// if lastPosition != nil { +// let connectedNode = getNodeInfo(id: connectedPeripheral?.num ?? 0, context: context!) +// if connectedNode?.positionConfig?.smartPositionEnabled ?? false { +// if lastPosition!.distance(from: LocationHelper.currentLocation) < Double(connectedNode?.positionConfig?.broadcastSmartMinimumDistance ?? 50) { +// return false +// } +// } +// } +// } +// lastPosition = LocationHelper.currentLocation +// var locationHelper = LocationHelper() var positionPacket = Position() - positionPacket.latitudeI = Int32(LocationHelper.currentLocation.latitude * 1e7) - positionPacket.longitudeI = Int32(LocationHelper.currentLocation.longitude * 1e7) + positionPacket.latitudeI = Int32((locationFetcher.lastKnownLocation?.latitude ?? 0) * 1e7) + positionPacket.longitudeI = Int32((locationFetcher.manager.location?.coordinate.longitude ?? 0) * 1e7) positionPacket.time = UInt32(LocationHelper.currentTimestamp.timeIntervalSince1970) positionPacket.timestamp = UInt32(LocationHelper.currentTimestamp.timeIntervalSince1970) positionPacket.altitude = Int32(LocationHelper.currentAltitude) @@ -892,7 +894,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate if connectedPeripheral != nil { // Send a position out to the mesh if "share location with the mesh" is enabled in settings if UserDefaults.provideLocation { - let _ = sendPosition(destNum: connectedPeripheral.num, wantResponse: false, smartPosition: true) + let _ = sendPosition(destNum: connectedPeripheral.num, wantResponse: false) } } } diff --git a/Meshtastic/Helpers/LocationHelper.swift b/Meshtastic/Helpers/LocationHelper.swift index 14e1af70..118623b8 100644 --- a/Meshtastic/Helpers/LocationHelper.swift +++ b/Meshtastic/Helpers/LocationHelper.swift @@ -1,9 +1,12 @@ import Foundation import CoreLocation +import MapKit class LocationHelper: NSObject, ObservableObject, CLLocationManagerDelegate { static let shared = LocationHelper() var locationManager = CLLocationManager() + + //@Published var region = MKCoordinateRegion() @Published var authorizationStatus: CLAuthorizationStatus? override init() { super.init() @@ -89,6 +92,13 @@ class LocationHelper: NSObject, ObservableObject, CLLocationManagerDelegate { } } func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { +// locationManager.stopUpdatingLocation() +// locations.last.map { +// region = MKCoordinateRegion( +// center: $0.coordinate, +// span: .init(latitudeDelta: 0.01, longitudeDelta: 0.01) +// ) +// } } func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { print("Location manager error: \(error.localizedDescription)") diff --git a/Meshtastic/Views/Messages/ChannelMessageList.swift b/Meshtastic/Views/Messages/ChannelMessageList.swift index 57b27edd..f6211355 100644 --- a/Meshtastic/Views/Messages/ChannelMessageList.swift +++ b/Meshtastic/Views/Messages/ChannelMessageList.swift @@ -384,7 +384,7 @@ struct ChannelMessageList: View { focusedField = nil replyMessageId = 0 if sendPositionWithMessage { - if bleManager.sendPosition(destNum: Int64(channel.index), wantResponse: false, smartPosition: false) { + if bleManager.sendPosition(destNum: Int64(channel.index), wantResponse: false) { print("Location Sent") } } @@ -401,7 +401,7 @@ struct ChannelMessageList: View { focusedField = nil replyMessageId = 0 if sendPositionWithMessage { - if bleManager.sendPosition(destNum: Int64(channel.index), wantResponse: false, smartPosition: false) { + if bleManager.sendPosition(destNum: Int64(channel.index), wantResponse: false) { print("Location Sent") } } diff --git a/Meshtastic/Views/Messages/UserMessageList.swift b/Meshtastic/Views/Messages/UserMessageList.swift index 12d28783..4b9898ff 100644 --- a/Meshtastic/Views/Messages/UserMessageList.swift +++ b/Meshtastic/Views/Messages/UserMessageList.swift @@ -335,7 +335,7 @@ struct UserMessageList: View { focusedField = nil replyMessageId = 0 if sendPositionWithMessage { - if bleManager.sendPosition(destNum: user.num, wantResponse: true, smartPosition: false) { + if bleManager.sendPosition(destNum: user.num, wantResponse: true) { print("Location Sent") } } @@ -352,7 +352,7 @@ struct UserMessageList: View { focusedField = nil replyMessageId = 0 if sendPositionWithMessage { - if bleManager.sendPosition(destNum: user.num, wantResponse: true, smartPosition: false) { + if bleManager.sendPosition(destNum: user.num, wantResponse: true) { print("Location Sent") } } diff --git a/Meshtastic/Views/Nodes/Helpers/NodeMapSwiftUI.swift b/Meshtastic/Views/Nodes/Helpers/NodeMapSwiftUI.swift index 43f991f1..bbef8357 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeMapSwiftUI.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeMapSwiftUI.swift @@ -18,30 +18,26 @@ struct NodeMapSwiftUI: View { /// Parameters @ObservedObject var node: NodeInfoEntity @State var showUserLocation: Bool = false - /// Map State - @Namespace var mapScope + @State var positions: [PositionEntity] = [] + @State var waypoints: [WaypointEntity] = [] + /// Map State User Defaults @AppStorage("meshMapShowNodeHistory") private var showNodeHistory = false @AppStorage("meshMapShowRouteLines") private var showRouteLines = false + @AppStorage("meshMapShowConvexHull") private var showConvexHull = true @AppStorage("enableMapTraffic") private var showTraffic: Bool = true @AppStorage("enableMapPointsOfInterest") private var showPointsOfInterest: Bool = true @AppStorage("mapLayer") private var selectedMapLayer: MapLayer = .hybrid + // Map Configuration + @Namespace var mapScope @State private var mapStyle: MapStyle = MapStyle.hybrid(elevation: .realistic, pointsOfInterest: .all, showsTraffic: true) @State private var position = MapCameraPosition.automatic @State private var scene: MKLookAroundScene? @State private var isLookingAround = false @State private var isEditingSettings = false - @State private var showConvexHull = true @State private var selected: PositionEntity? - @State private var showingPopover = false - /// Data - @FetchRequest(sortDescriptors: [NSSortDescriptor(key: "name", ascending: false)], - predicate: NSPredicate( - format: "expire == nil || expire >= %@", Date() as NSDate - ), animation: .none) - private var waypoints: FetchedResults + @State private var showingPositionPopover = false var body: some View { - let nodeColor = UIColor(hex: UInt32(node.num)) let positionArray = node.positions?.array as? [PositionEntity] ?? [] let mostRecent = node.positions?.lastObject as? PositionEntity let lineCoords = positionArray.compactMap({(position) -> CLLocationCoordinate2D in @@ -51,6 +47,8 @@ struct NodeMapSwiftUI: View { if node.hasPositions { ZStack { Map(position: $position, bounds: MapCameraBounds(minimumDistance: 1, maximumDistance: .infinity), scope: mapScope) { + /// Node Color from node.num + let nodeColor = UIColor(hex: UInt32(node.num)) /// Route Lines if showRouteLines { if showRouteLines { @@ -68,7 +66,7 @@ struct NodeMapSwiftUI: View { } /// Convex Hull if showConvexHull { - let hull = getConvexHull(input: lineCoords) + let hull = lineCoords.getConvexHull() MapPolygon(coordinates: hull) .stroke(Color(nodeColor.darker()), lineWidth: 5) .foregroundStyle(Color(nodeColor).opacity(0.4)) @@ -78,6 +76,7 @@ struct NodeMapSwiftUI: View { let pf = PositionFlags(rawValue: Int(position.nodePosition?.metadata?.positionFlags ?? 3)) let formatter = MeasurementFormatter() let speedText = formatter.string(from: Measurement(value: Double(position.speed), unit: UnitSpeed.kilometersPerHour)) + let headingDegrees = Angle.degrees(Double(position.heading)) Annotation(position.latest ? node.user?.shortName ?? "?" : (pf.contains(.Speed) && position.speed > 2) ? speedText : "", coordinate: position.coordinate) { ZStack { if position.latest { @@ -85,18 +84,18 @@ struct NodeMapSwiftUI: View { .foregroundStyle(Color(nodeColor.lighter()).opacity(0.4)) .frame(width: 60, height: 60) if pf.contains(.Heading) { - Image(systemName: pf.contains(.Speed) && position.speed > 1 ? "location.north.fill" : "location.north") + Image(systemName: pf.contains(.Speed) && position.speed > 1 ? "location.north" : "hexagon") .symbolEffect(.pulse.byLayer) .padding(5) .foregroundStyle(Color(nodeColor).isLight() ? .black : .white) .background(Color(UIColor(hex: UInt32(node.num)).darker())) .clipShape(Circle()) - .rotationEffect(.degrees(Double(position.heading))) + .rotationEffect(headingDegrees) .onTapGesture { - showingPopover = true + showingPositionPopover = true selected = (selected == position ? nil : position) // <-- here } - .popover(isPresented: $showingPopover, arrowEdge: .bottom) { + .popover(isPresented: $showingPositionPopover, arrowEdge: .bottom) { PositionPopover(position: position) .padding() .opacity(0.8) @@ -110,10 +109,10 @@ struct NodeMapSwiftUI: View { .background(Color(UIColor(hex: UInt32(node.num)).darker())) .clipShape(Circle()) .onTapGesture { - showingPopover = true + showingPositionPopover = true selected = (selected == position ? nil : position) // <-- here } - .popover(isPresented: $showingPopover, arrowEdge: .bottom) { + .popover(isPresented: $showingPositionPopover, arrowEdge: .bottom) { PositionPopover(position: position) .padding() .opacity(0.8) @@ -123,12 +122,12 @@ struct NodeMapSwiftUI: View { } else { if showNodeHistory { if pf.contains(.Heading) { - Image(systemName: pf.contains(.Speed) && position.speed > 0 ? "location.north.fill" : "hexagon") + Image(systemName: pf.contains(.Speed) && position.speed > 1 ? "location.north" : "hexagon") .padding(2) .foregroundStyle(Color(UIColor(hex: UInt32(node.num)).lighter()).isLight() ? .black : .white) .background(Color(UIColor(hex: UInt32(node.num)).lighter())) .clipShape(Circle()) - .rotationEffect(.degrees(Double(position.heading))) + .rotationEffect(headingDegrees) } else { Image(systemName: "mappin.circle") .padding(2) @@ -141,6 +140,8 @@ struct NodeMapSwiftUI: View { } } .tag(position.time) + .annotationTitles(.automatic) + .annotationSubtitles(.automatic) } } .mapScope(mapScope) @@ -339,49 +340,4 @@ struct NodeMapSwiftUI: View { let lookAroundScene = MKLookAroundSceneRequest(coordinate: coordinate) return try await lookAroundScene.scene } - - func getConvexHull(input: [CLLocationCoordinate2D]) -> [CLLocationCoordinate2D] { - - // X = longitude - // Y = latitudeß - - // 2D cross product of OA and OB vectors, i.e. z-component of their 3D cross product. - // Returns a positive value, if OAB makes a counter-clockwise turn, - // negative for clockwise turn, and zero if the points are collinear. - func cross(P: CLLocationCoordinate2D, A: CLLocationCoordinate2D, B: CLLocationCoordinate2D) -> Double { - let part1 = (A.longitude - P.longitude) * (B.latitude - P.latitude) - let part2 = (A.latitude - P.latitude) * (B.longitude - P.longitude) - return part1 - part2; - } - - // Sort points lexicographically - let points = input.sorted() { - $0.longitude == $1.longitude ? $0.latitude < $1.latitude : $0.longitude < $1.longitude - } - - // Build the lower hull - var lower: [CLLocationCoordinate2D] = [] - for p in points { - while lower.count >= 2 && cross(P: lower[lower.count - 2], A: lower[lower.count - 1], B: p) <= 0 { - lower.removeLast() - } - lower.append(p) - } - - // Build upper hull - var upper: [CLLocationCoordinate2D] = [] - for p in points.reversed() { - while upper.count >= 2 && cross(P: upper[upper.count-2], A: upper[upper.count-1], B: p) <= 0 { - upper.removeLast() - } - upper.append(p) - } - - // Last point of upper list is omitted because it is repeated at the - // beginning of the lower list. - upper.removeLast() - - // Concatenation of the lower and upper hulls gives the convex hull. - return (upper + lower) - } } diff --git a/Meshtastic/Views/Nodes/Helpers/PositionPopover.swift b/Meshtastic/Views/Nodes/Helpers/PositionPopover.swift index f1070518..acac8ddf 100644 --- a/Meshtastic/Views/Nodes/Helpers/PositionPopover.swift +++ b/Meshtastic/Views/Nodes/Helpers/PositionPopover.swift @@ -79,20 +79,20 @@ struct PositionPopover: View { .padding(.bottom, 5) } /// Heading -// if pf.contains(.Heading) { -// let degrees = Angle.degrees(Double(position.heading)) -// Label { -// let heading = Measurement(value: degrees.degrees, unit: UnitAngle.degrees) -// Text("Heading: \(heading.formatted())") -// .foregroundColor(.primary) -// } icon: { -// Image(systemName: "location.north") -// .symbolRenderingMode(.hierarchical) -// .frame(width: 35) -// .rotationEffect(degrees) -// } -// .padding(.bottom, 5) -// } + if pf.contains(.Heading) { + let degrees = Angle.degrees(Double(position.heading)) + Label { + let heading = Measurement(value: degrees.degrees, unit: UnitAngle.degrees) + Text("Heading: \(heading.formatted())") + .foregroundColor(.primary) + } icon: { + Image(systemName: "location.north") + .symbolRenderingMode(.hierarchical) + .frame(width: 35) + .rotationEffect(degrees) + } + .padding(.bottom, 5) + } /// Speed if pf.contains(.Speed) { let formatter = MeasurementFormatter() diff --git a/Meshtastic/Views/Nodes/PositionLog.swift b/Meshtastic/Views/Nodes/PositionLog.swift index 0c8c034a..d8d0e5e0 100644 --- a/Meshtastic/Views/Nodes/PositionLog.swift +++ b/Meshtastic/Views/Nodes/PositionLog.swift @@ -50,7 +50,8 @@ struct PositionLog: View { Text(speed.formatted()) } TableColumn("Heading") { position in - Text("\(position.heading)°") + let heading = Measurement(value: Double(position.heading), unit: UnitAngle.degrees) + Text("\(heading.formatted())") } TableColumn("SNR") { position in Text("\(String(format: "%.2f", position.snr)) dB") diff --git a/Meshtastic/Views/Settings/Config/PositionConfig.swift b/Meshtastic/Views/Settings/Config/PositionConfig.swift index 2ed55fe2..8ad8397c 100644 --- a/Meshtastic/Views/Settings/Config/PositionConfig.swift +++ b/Meshtastic/Views/Settings/Config/PositionConfig.swift @@ -277,7 +277,7 @@ struct PositionConfig: View { Button(buttonText) { if fixedPosition { - _ = bleManager.sendPosition(destNum: node!.num, wantResponse: true, smartPosition: false) + _ = bleManager.sendPosition(destNum: node!.num, wantResponse: true) } let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context)