mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
Initial trace route log
This commit is contained in:
parent
126cdfbdb3
commit
bd5191ccd2
7 changed files with 316 additions and 10 deletions
|
|
@ -173,6 +173,8 @@
|
|||
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 */; };
|
||||
DDE5B4042B2279A700FCDD05 /* TraceRouteLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE5B4032B2279A700FCDD05 /* TraceRouteLog.swift */; };
|
||||
DDE5B4062B227E3200FCDD05 /* TraceRouteEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE5B4052B227E3200FCDD05 /* TraceRouteEntityExtension.swift */; };
|
||||
DDE9659C2B1C3B6A00531070 /* RouteRecorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE9659B2B1C3B6A00531070 /* RouteRecorder.swift */; };
|
||||
DDF6B2482A9AEBF500BA6931 /* StoreForward.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF6B2472A9AEBF500BA6931 /* StoreForward.swift */; };
|
||||
DDF924CA26FBB953009FE055 /* ConnectedDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF924C926FBB953009FE055 /* ConnectedDevice.swift */; };
|
||||
|
|
@ -407,6 +409,8 @@
|
|||
DDDE5A1229AFEAB900490C6C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
DDDEE5E229DBE43E00A8E078 /* MeshtasticDataModelV11.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV11.xcdatamodel; sourceTree = "<group>"; };
|
||||
DDE0F7C4295F77B700B8AAB3 /* AppSettingsEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettingsEnums.swift; sourceTree = "<group>"; };
|
||||
DDE5B4032B2279A700FCDD05 /* TraceRouteLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TraceRouteLog.swift; sourceTree = "<group>"; };
|
||||
DDE5B4052B227E3200FCDD05 /* TraceRouteEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TraceRouteEntityExtension.swift; sourceTree = "<group>"; };
|
||||
DDE9659B2B1C3B6A00531070 /* RouteRecorder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RouteRecorder.swift; sourceTree = "<group>"; };
|
||||
DDEE03EC29544A1000FCAD57 /* MeshtasticDataModelV4.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV4.xcdatamodel; sourceTree = "<group>"; };
|
||||
DDF6B2462A9AEB9E00BA6931 /* MeshtasticDataModelV17.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV17.xcdatamodel; sourceTree = "<group>"; };
|
||||
|
|
@ -485,6 +489,7 @@
|
|||
DDD9E4E3284B208E003777C5 /* UserEntityExtension.swift */,
|
||||
DD964FC1297272AE007C176F /* WaypointEntityExtension.swift */,
|
||||
DDAB580E2B0DAFBC00147258 /* LocationEntityExtension.swift */,
|
||||
DDE5B4052B227E3200FCDD05 /* TraceRouteEntityExtension.swift */,
|
||||
);
|
||||
path = CoreData;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -515,6 +520,7 @@
|
|||
DD73FD1028750779000852D6 /* PositionLog.swift */,
|
||||
DD4F23CC28779A3C001D37CB /* EnvironmentMetricsLog.swift */,
|
||||
6DEDA5592A957B8E00321D2E /* DetectionSensorLog.swift */,
|
||||
DDE5B4032B2279A700FCDD05 /* TraceRouteLog.swift */,
|
||||
);
|
||||
path = Nodes;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -1152,6 +1158,7 @@
|
|||
DDDB26462AACC0B7003AFCB7 /* NodeInfoItem.swift in Sources */,
|
||||
DD2AD8A8296D2DF9001FF0E7 /* MapViewSwiftUI.swift in Sources */,
|
||||
DD5E5213298EE33B00D21B61 /* deviceonly.pb.swift in Sources */,
|
||||
DDE5B4042B2279A700FCDD05 /* TraceRouteLog.swift in Sources */,
|
||||
DD5E5208298EE33B00D21B61 /* rtttl.pb.swift in Sources */,
|
||||
DD6193792863875F00E59241 /* SerialConfig.swift in Sources */,
|
||||
DDDB263F2AABEE20003AFCB7 /* NodeList.swift in Sources */,
|
||||
|
|
@ -1263,6 +1270,7 @@
|
|||
DDAB580F2B0DAFBC00147258 /* LocationEntityExtension.swift in Sources */,
|
||||
DD5E5211298EE33B00D21B61 /* remote_hardware.pb.swift in Sources */,
|
||||
DD5E5204298EE33B00D21B61 /* xmodem.pb.swift in Sources */,
|
||||
DDE5B4062B227E3200FCDD05 /* TraceRouteEntityExtension.swift in Sources */,
|
||||
DDC2E15826CE248E0042C5E4 /* MeshtasticApp.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,71 @@
|
|||
//
|
||||
// TraceRouteEntityExtension.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Copyright(c) Garth Vander Houwen 12/7/23.
|
||||
//
|
||||
|
||||
import CoreData
|
||||
import CoreLocation
|
||||
import MapKit
|
||||
import SwiftUI
|
||||
|
||||
extension TraceRouteEntity {
|
||||
|
||||
var latitude: Double? {
|
||||
|
||||
let d = Double(latitudeI)
|
||||
if d == 0 {
|
||||
return 0
|
||||
}
|
||||
return d / 1e7
|
||||
}
|
||||
|
||||
var longitude: Double? {
|
||||
|
||||
let d = Double(longitudeI)
|
||||
if d == 0 {
|
||||
return 0
|
||||
}
|
||||
return d / 1e7
|
||||
}
|
||||
|
||||
var coordinate: CLLocationCoordinate2D? {
|
||||
if latitudeI != 0 && longitudeI != 0 {
|
||||
let coord = CLLocationCoordinate2D(latitude: latitude!, longitude: longitude!)
|
||||
return coord
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension TraceRouteHopEntity {
|
||||
|
||||
var latitude: Double? {
|
||||
|
||||
let d = Double(latitudeI)
|
||||
if d == 0 {
|
||||
return 0
|
||||
}
|
||||
return d / 1e7
|
||||
}
|
||||
|
||||
var longitude: Double? {
|
||||
|
||||
let d = Double(longitudeI)
|
||||
if d == 0 {
|
||||
return 0
|
||||
}
|
||||
return d / 1e7
|
||||
}
|
||||
|
||||
var coordinate: CLLocationCoordinate2D? {
|
||||
if latitudeI != 0 && longitudeI != 0 {
|
||||
let coord = CLLocationCoordinate2D(latitude: latitude!, longitude: longitude!)
|
||||
return coord
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -376,6 +376,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
let fromNodeNum = connectedPeripheral.num
|
||||
let routePacket = RouteDiscovery()
|
||||
var meshPacket = MeshPacket()
|
||||
meshPacket.id = UInt32.random(in: UInt32(UInt8.max)..<UInt32.max)
|
||||
meshPacket.to = UInt32(destNum)
|
||||
meshPacket.from = UInt32(fromNodeNum)
|
||||
meshPacket.wantAck = true
|
||||
|
|
@ -394,8 +395,43 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse)
|
||||
success = true
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.traceroute.sent %@".localized, String(destNum))
|
||||
MeshLogger.log("🪧 \(logString)")
|
||||
let traceRoute = TraceRouteEntity(context: context!)
|
||||
let nodes: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "NodeInfoEntity")
|
||||
nodes.predicate = NSPredicate(format: "num IN %@", [destNum, self.connectedPeripheral.num])
|
||||
do {
|
||||
guard let fetchedNodes = try context!.fetch(nodes) as? [NodeInfoEntity] else {
|
||||
return false
|
||||
}
|
||||
let receivingNode = fetchedNodes.first(where: { $0.num == destNum })
|
||||
let connectedNode = fetchedNodes.first(where: { $0.num == self.connectedPeripheral.num })
|
||||
|
||||
traceRoute.id = Int64(meshPacket.id)
|
||||
traceRoute.time = Date()
|
||||
traceRoute.node = receivingNode
|
||||
// Grab the most recent postion, within the last hour
|
||||
if connectedNode?.positions?.count ?? 0 > 0 {
|
||||
let mostRecent = connectedNode?.positions?.lastObject as! PositionEntity
|
||||
if mostRecent.time! >= Calendar.current.date(byAdding: .minute, value: -60, to: Date())! {
|
||||
traceRoute.altitude = mostRecent.altitude
|
||||
traceRoute.latitudeI = mostRecent.latitudeI
|
||||
traceRoute.longitudeI = mostRecent.longitudeI
|
||||
}
|
||||
}
|
||||
do {
|
||||
try context!.save()
|
||||
print("💾 Saved TraceRoute sent to node: \(String(receivingNode?.user?.longName ?? "unknown".localized))")
|
||||
} catch {
|
||||
context!.rollback()
|
||||
let nsError = error as NSError
|
||||
print("💥 Error Updating Core Data BluetoothConfigEntity: \(nsError)")
|
||||
}
|
||||
|
||||
let logString = String.localizedStringWithFormat("mesh.log.traceroute.sent %@".localized, String(destNum))
|
||||
MeshLogger.log("🪧 \(logString)")
|
||||
|
||||
} catch {
|
||||
|
||||
}
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
|
@ -595,16 +631,44 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
MeshLogger.log("🕸️ MESH PACKET received for Audio App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")")
|
||||
case .tracerouteApp:
|
||||
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
|
||||
if routingMessage.route.count == 0 {
|
||||
let logString = String.localizedStringWithFormat("mesh.log.traceroute.received.direct %@".localized, String(decodedInfo.packet.from))
|
||||
MeshLogger.log("🪧 \(logString)")
|
||||
} else {
|
||||
|
||||
var routeString = "\(decodedInfo.packet.to) --> "
|
||||
for node in routingMessage.route {
|
||||
routeString += "\(node) --> "
|
||||
} else {
|
||||
var routeString = "You --> "
|
||||
var hopNodes: [TraceRouteHopEntity] = []
|
||||
// for node in routingMessage.route {
|
||||
// let hopNode = getNodeInfo(id: Int64(node), context: context!)
|
||||
// let traceRouteHop = TraceRouteHopEntity(context: context!)
|
||||
// traceRouteHop.time = Date()
|
||||
// let mostRecent = hopNode?.positions?.lastObject as! PositionEntity
|
||||
// if mostRecent.time! >= Calendar.current.date(byAdding: .minute, value: -60, to: Date())! {
|
||||
// traceRouteHop.altitude = mostRecent.altitude
|
||||
// traceRouteHop.latitudeI = mostRecent.latitudeI
|
||||
// traceRouteHop.longitudeI = mostRecent.longitudeI
|
||||
// traceRouteHop.name = hopNode?.user?.longName ?? "unknown".localized
|
||||
// }
|
||||
// traceRouteHop.num = hopNode?.num ?? 0
|
||||
// if hopNode != nil {
|
||||
// hopNodes.append(traceRouteHop)
|
||||
// }
|
||||
// routeString += "\(hopNode?.user?.longName ?? "unknown".localized) --> "
|
||||
// }
|
||||
traceRoute?.routeText = routeString
|
||||
traceRoute?.hops = NSOrderedSet(array: hopNodes)
|
||||
do {
|
||||
try context!.save()
|
||||
print("💾 Saved Trace Route")
|
||||
} catch {
|
||||
context!.rollback()
|
||||
let nsError = error as NSError
|
||||
print("💥 Error Updating Core Data TraceRouteHOp: \(nsError)")
|
||||
}
|
||||
|
||||
routeString += "\(decodedInfo.packet.from)"
|
||||
let logString = String.localizedStringWithFormat("mesh.log.traceroute.received.route %@".localized, routeString)
|
||||
MeshLogger.log("🪧 \(logString)")
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="22225" systemVersion="23C5047e" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="22225" systemVersion="23C64" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||
<entity name="AmbientLightingConfigEntity" representedClassName="AmbientLightingConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="blue" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="current" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
|
|
@ -112,6 +112,9 @@
|
|||
<attribute name="outputVibra" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="usePWM" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<relationship name="externalNotificationConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="externalNotificationConfig" inverseEntity="NodeInfoEntity"/>
|
||||
<fetchedProperty name="fetchedProperty" optional="YES">
|
||||
<fetchRequest name="fetchedPropertyFetchRequest" entity="ExternalNotificationConfigEntity"/>
|
||||
</fetchedProperty>
|
||||
</entity>
|
||||
<entity name="LocationEntity" representedClassName="LocationEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="altitude" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
|
|
@ -244,6 +247,7 @@
|
|||
<relationship name="storeForwardConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="StoreForwardConfigEntity" inverseName="storeForwardConfigNode" inverseEntity="StoreForwardConfigEntity"/>
|
||||
<relationship name="telemetries" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="TelemetryEntity" inverseName="nodeTelemetry" inverseEntity="TelemetryEntity"/>
|
||||
<relationship name="telemetryConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="TelemetryConfigEntity" inverseName="telemetryConfigNode" inverseEntity="TelemetryConfigEntity"/>
|
||||
<relationship name="traceRoutes" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="TraceRouteEntity" inverseName="node" inverseEntity="TraceRouteEntity"/>
|
||||
<relationship name="user" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="UserEntity" inverseName="userNode" inverseEntity="UserEntity"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
|
|
@ -340,6 +344,28 @@
|
|||
<attribute name="voltage" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<relationship name="nodeTelemetry" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="telemetries" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="TraceRouteEntity" representedClassName="TraceRouteEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="altitude" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="id" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="latitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="longitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="num" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="response" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="route" optional="YES" attributeType="Transformable" customClassName="[UInt32]"/>
|
||||
<attribute name="routeText" optional="YES" attributeType="String"/>
|
||||
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<relationship name="hops" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="TraceRouteHopEntity" inverseName="traceRoute" inverseEntity="TraceRouteHopEntity"/>
|
||||
<relationship name="node" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="traceRoutes" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="TraceRouteHopEntity" representedClassName="TraceRouteHopEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="altitude" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="latitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="longitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="name" optional="YES" attributeType="String"/>
|
||||
<attribute name="num" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="time" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<relationship name="traceRoute" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="TraceRouteEntity" inverseName="hops" inverseEntity="TraceRouteEntity"/>
|
||||
</entity>
|
||||
<entity name="UserEntity" representedClassName="UserEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="hwModel" attributeType="String"/>
|
||||
<attribute name="isLicensed" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
|
|
|
|||
|
|
@ -46,6 +46,23 @@ public func getStoreAndForwardMessageIds(seconds: Int, context: NSManagedObjectC
|
|||
return []
|
||||
}
|
||||
|
||||
public func getTraceRoute(id: Int64, context: NSManagedObjectContext) -> TraceRouteEntity? {
|
||||
|
||||
let fetchTraceRouteRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "TraceRouteEntity")
|
||||
fetchTraceRouteRequest.predicate = NSPredicate(format: "id == %lld", Int64(id))
|
||||
|
||||
do {
|
||||
guard let fetchedTraceRoute = try context.fetch(fetchTraceRouteRequest) as? [TraceRouteEntity] else {
|
||||
return nil
|
||||
}
|
||||
if fetchedTraceRoute.count == 1 {
|
||||
return fetchedTraceRoute[0]
|
||||
}
|
||||
} catch {
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
public func getUser(id: Int64, context: NSManagedObjectContext) -> UserEntity {
|
||||
|
||||
|
|
|
|||
|
|
@ -93,9 +93,21 @@ struct NodeDetail: View {
|
|||
}
|
||||
.disabled(!node.hasDetectionSensorMetrics)
|
||||
Divider()
|
||||
if #available(iOS 17.0, macOS 14.0, *) {
|
||||
NavigationLink {
|
||||
TraceRouteLog(node: node)
|
||||
} label: {
|
||||
Image(systemName: "signpost.right.and.left")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.font(.title)
|
||||
|
||||
Text("Trace Route Log")
|
||||
.font(.title3)
|
||||
}
|
||||
.disabled(node.traceRoutes?.count ?? 0 == 0)
|
||||
Divider()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if self.bleManager.connectedPeripheral != nil && node.metadata != nil {
|
||||
HStack {
|
||||
if node.metadata?.canShutdown ?? false {
|
||||
|
|
|
|||
108
Meshtastic/Views/Nodes/TraceRouteLog.swift
Normal file
108
Meshtastic/Views/Nodes/TraceRouteLog.swift
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
//
|
||||
// TraceRouteLog.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Copyright(c) Garth Vander Houwen 12/7/23.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
#if canImport(MapKit)
|
||||
import MapKit
|
||||
#endif
|
||||
|
||||
@available(iOS 17.0, macOS 14.0, *)
|
||||
struct TraceRouteLog: View {
|
||||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
|
||||
@State private var isPresentingClearLogConfirm: Bool = false
|
||||
@State var isExporting = false
|
||||
@State var exportString = ""
|
||||
@ObservedObject var node: NodeInfoEntity
|
||||
@State private var selectedRoute: TraceRouteEntity?
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
VStack {
|
||||
List(node.traceRoutes?.reversed() as? [TraceRouteEntity] ?? [], id: \.self, selection: $selectedRoute) { route in
|
||||
Text("\(route.time?.formatted() ?? "unknown".localized) - \(route.response ? (route.hops?.count == 0 && route.response ? "Direct" : "Other") : "No Response")")
|
||||
}
|
||||
.listStyle(.plain)
|
||||
}
|
||||
.navigationTitle("Trace Route List")
|
||||
VStack {
|
||||
if selectedRoute != nil {
|
||||
Divider()
|
||||
if selectedRoute?.response ?? false && selectedRoute?.hops?.count ?? 0 > 0 {
|
||||
Text("Trace Route received by \(selectedRoute?.node?.user?.longName ?? "unknown".localized)")
|
||||
Text("Route: \(selectedRoute?.routeText ?? "unknown".localized)")
|
||||
.font(.title)
|
||||
} else if selectedRoute?.response ?? false {
|
||||
Text("Trace Route received directly by \(selectedRoute?.node?.user?.longName ?? "unknown".localized)")
|
||||
.font(.title)
|
||||
}
|
||||
let hopsArray = selectedRoute?.hops?.array as? [TraceRouteHopEntity] ?? []
|
||||
let lineCoords = hopsArray.compactMap({(hop) -> CLLocationCoordinate2D in
|
||||
return hop.coordinate ?? LocationHelper.DefaultLocation
|
||||
})
|
||||
if selectedRoute?.response ?? false {
|
||||
Map() {
|
||||
Annotation("You", coordinate: selectedRoute?.coordinate ?? LocationHelper.DefaultLocation) {
|
||||
ZStack {
|
||||
Circle()
|
||||
.fill(Color(.green))
|
||||
.strokeBorder(.white, lineWidth: 3)
|
||||
.frame(width: 15, height: 15)
|
||||
}
|
||||
}
|
||||
.annotationTitles(.automatic)
|
||||
// Direct Trace Route
|
||||
if selectedRoute?.response ?? false && selectedRoute?.hops?.count ?? 0 == 0 {
|
||||
if selectedRoute?.node?.positions?.count ?? 0 > 0 {
|
||||
let mostRecent = selectedRoute?.node?.positions?.lastObject as! PositionEntity
|
||||
var traceRouteCoords: [CLLocationCoordinate2D] = [selectedRoute?.coordinate ?? LocationsHandler.DefaultLocation, mostRecent.coordinate]
|
||||
Annotation(selectedRoute?.node?.user?.shortName ?? "???", coordinate: mostRecent.nodeCoordinate ?? LocationHelper.DefaultLocation) {
|
||||
ZStack {
|
||||
Circle()
|
||||
.fill(Color(.black))
|
||||
.strokeBorder(.white, lineWidth: 3)
|
||||
.frame(width: 15, height: 15)
|
||||
}
|
||||
}
|
||||
let dashed = StrokeStyle(
|
||||
lineWidth: 3,
|
||||
lineCap: .round, lineJoin: .round, dash: [7, 10]
|
||||
)
|
||||
MapPolyline(coordinates: traceRouteCoords)
|
||||
.stroke(.blue, style: dashed)
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
} else {
|
||||
Text("Trace Route sent to \(selectedRoute?.node?.user?.longName ?? "unknown".localized)")
|
||||
.font(.title)
|
||||
.padding(.top)
|
||||
Spacer()
|
||||
Text("\(selectedRoute?.time?.formatted() ?? "")")
|
||||
.font(.title3)
|
||||
Spacer()
|
||||
Text("No response")
|
||||
.font(.title2)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("Route Details")
|
||||
}
|
||||
.navigationBarItems(trailing:
|
||||
ZStack {
|
||||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
})
|
||||
.onAppear {
|
||||
if self.bleManager.context == nil {
|
||||
self.bleManager.context = context
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue