mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
Merge branch 'main' into 2.5.8
This commit is contained in:
commit
386042b8df
15 changed files with 934 additions and 186 deletions
|
|
@ -194,6 +194,9 @@
|
|||
},
|
||||
"256 bit" : {
|
||||
|
||||
},
|
||||
"A Trace Route was sent, no response has been received." : {
|
||||
|
||||
},
|
||||
"about" : {
|
||||
"localizations" : {
|
||||
|
|
@ -17579,6 +17582,9 @@
|
|||
},
|
||||
"Rotary 1" : {
|
||||
|
||||
},
|
||||
"Route Back: %@" : {
|
||||
|
||||
},
|
||||
"Route Lines" : {
|
||||
|
||||
|
|
@ -20156,7 +20162,7 @@
|
|||
"Store and forward clients can request history from routers on the network." : {
|
||||
|
||||
},
|
||||
"Store and forward router devices must also be in the router or router client device role and requires a ESP32 device with PSRAM." : {
|
||||
"Store and forward router devices require a ESP32 device with PSRAM." : {
|
||||
|
||||
},
|
||||
"storeforward" : {
|
||||
|
|
@ -21859,14 +21865,27 @@
|
|||
"Trace Route Log" : {
|
||||
|
||||
},
|
||||
"Trace route received directly by %@" : {
|
||||
|
||||
"Trace route received directly by %@ with a SNR of %@ dB" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "new",
|
||||
"value" : "Trace route received directly by %1$@ with a SNR of %2$@ dB"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Trace Route Sent" : {
|
||||
|
||||
},
|
||||
"Trace route sent to %@" : {
|
||||
|
||||
},
|
||||
"Trace route to %@ was not sent." : {
|
||||
|
||||
},
|
||||
"Trace Route was rate limited. You can send a trace route a maximum of once every thirty seconds." : {
|
||||
|
||||
},
|
||||
"Traffic" : {
|
||||
|
||||
|
|
|
|||
|
|
@ -100,6 +100,7 @@
|
|||
DD6193752862F6E600E59241 /* ExternalNotificationConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6193742862F6E600E59241 /* ExternalNotificationConfig.swift */; };
|
||||
DD6193772862F90F00E59241 /* CannedMessagesConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6193762862F90F00E59241 /* CannedMessagesConfig.swift */; };
|
||||
DD6193792863875F00E59241 /* SerialConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6193782863875F00E59241 /* SerialConfig.swift */; };
|
||||
DD6D5A332CA1178300ED3032 /* TraceRoute.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6D5A322CA1178300ED3032 /* TraceRoute.swift */; };
|
||||
DD6F65722C6AB8EC0053C113 /* SecureInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6F65712C6AB8EC0053C113 /* SecureInput.swift */; };
|
||||
DD6F65742C6CB80A0053C113 /* View.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6F65732C6CB80A0053C113 /* View.swift */; };
|
||||
DD6F65762C6EA5490053C113 /* AckErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6F65752C6EA5490053C113 /* AckErrors.swift */; };
|
||||
|
|
@ -365,6 +366,8 @@
|
|||
DD6193762862F90F00E59241 /* CannedMessagesConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CannedMessagesConfig.swift; sourceTree = "<group>"; };
|
||||
DD6193782863875F00E59241 /* SerialConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SerialConfig.swift; sourceTree = "<group>"; };
|
||||
DD68BAE72C417A74004C01A0 /* MeshtasticDataModelV 40.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 40.xcdatamodel"; sourceTree = "<group>"; };
|
||||
DD6D5A322CA1178300ED3032 /* TraceRoute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TraceRoute.swift; sourceTree = "<group>"; };
|
||||
DD6D5A342CA13BA600ED3032 /* MeshtasticDataModelV 45.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 45.xcdatamodel"; sourceTree = "<group>"; };
|
||||
DD6F65712C6AB8EC0053C113 /* SecureInput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureInput.swift; sourceTree = "<group>"; };
|
||||
DD6F65732C6CB80A0053C113 /* View.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = View.swift; sourceTree = "<group>"; };
|
||||
DD6F65752C6EA5490053C113 /* AckErrors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AckErrors.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -747,6 +750,14 @@
|
|||
path = Module;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DD6D5A312CA1176A00ED3032 /* Layouts */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DD6D5A322CA1178300ED3032 /* TraceRoute.swift */,
|
||||
);
|
||||
path = Layouts;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DD6F65772C6EAB860053C113 /* Help */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
|
@ -901,6 +912,7 @@
|
|||
DDC2E18726CE24E40042C5E4 /* Views */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DD6D5A312CA1176A00ED3032 /* Layouts */,
|
||||
C9483F6B2773016700998F6B /* MapKitMap */,
|
||||
DDC2E18D26CE25CB0042C5E4 /* Helpers */,
|
||||
DD47E3D726F2F21A00029299 /* Bluetooth */,
|
||||
|
|
@ -1433,6 +1445,7 @@
|
|||
DD6F65742C6CB80A0053C113 /* View.swift in Sources */,
|
||||
DD1933762B0835D500771CD5 /* PositionAltitudeChart.swift in Sources */,
|
||||
DD415828285859C4009B0E59 /* TelemetryConfig.swift in Sources */,
|
||||
DD6D5A332CA1178300ED3032 /* TraceRoute.swift in Sources */,
|
||||
DDB6CCFB2AAF805100945AF6 /* NodeMapSwiftUI.swift in Sources */,
|
||||
BCB613872C69A0FB00485544 /* AppIntentErrors.swift in Sources */,
|
||||
DD73FD1128750779000852D6 /* PositionLog.swift in Sources */,
|
||||
|
|
@ -1686,7 +1699,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.5.7;
|
||||
MARKETING_VERSION = 2.5.8;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
|
|
@ -1720,7 +1733,7 @@
|
|||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.5.7;
|
||||
MARKETING_VERSION = 2.5.8;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
|
|
@ -1752,7 +1765,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.5.7;
|
||||
MARKETING_VERSION = 2.5.8;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
|
@ -1785,7 +1798,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.5.7;
|
||||
MARKETING_VERSION = 2.5.8;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
|
@ -1897,6 +1910,7 @@
|
|||
DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */ = {
|
||||
isa = XCVersionGroup;
|
||||
children = (
|
||||
DD6D5A342CA13BA600ED3032 /* MeshtasticDataModelV 45.xcdatamodel */,
|
||||
DD7CF8DA2C93663C008BD10E /* MeshtasticDataModelV 44.xcdatamodel */,
|
||||
DD7E235F2C7AA3E50078ACDF /* MeshtasticDataModelV 43.xcdatamodel */,
|
||||
DD1BD0F12C61D3AD008C0C70 /* MeshtasticDataModelV 42.xcdatamodel */,
|
||||
|
|
@ -1942,7 +1956,7 @@
|
|||
DD5D0A9A2931AD6B00F7EA61 /* MeshtasticDataModelV2.xcdatamodel */,
|
||||
DD3CC6BB28E366DF00FA9159 /* MeshtasticDataModel.xcdatamodel */,
|
||||
);
|
||||
currentVersion = DD7CF8DA2C93663C008BD10E /* MeshtasticDataModelV 44.xcdatamodel */;
|
||||
currentVersion = DD6D5A342CA13BA600ED3032 /* MeshtasticDataModelV 45.xcdatamodel */;
|
||||
name = Meshtastic.xcdatamodeld;
|
||||
path = Meshtastic/Meshtastic.xcdatamodeld;
|
||||
sourceTree = "<group>";
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ extension NodeInfoEntity {
|
|||
}
|
||||
|
||||
var hasPositions: Bool {
|
||||
return positions?.count ?? 0 > 0
|
||||
return self.positions?.count ?? 0 > 0
|
||||
}
|
||||
|
||||
var hasDeviceMetrics: Bool {
|
||||
|
|
|
|||
|
|
@ -10,36 +10,6 @@ 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? {
|
||||
|
|
|
|||
|
|
@ -468,15 +468,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
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: .hour, value: -24, to: Date())! {
|
||||
traceRoute.altitude = mostRecent.altitude
|
||||
traceRoute.latitudeI = mostRecent.latitudeI
|
||||
traceRoute.longitudeI = mostRecent.longitudeI
|
||||
traceRoute.hasPositions = true
|
||||
}
|
||||
}
|
||||
do {
|
||||
try context.save()
|
||||
Logger.data.info("💾 Saved TraceRoute sent to node: \(String(receivingNode?.user?.longName ?? "unknown".localized), privacy: .public)")
|
||||
|
|
@ -601,7 +592,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:
|
||||
|
|
@ -622,14 +613,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
// Ignore fail to parse as LogRecord
|
||||
}
|
||||
|
||||
case LEGACY_LOGRADIO_UUID:
|
||||
if characteristic.value == nil || characteristic.value!.isEmpty {
|
||||
return
|
||||
}
|
||||
if let log = String(data: characteristic.value!, encoding: .utf8) {
|
||||
handleRadioLog(radioLog: log)
|
||||
}
|
||||
|
||||
case FROMRADIO_UUID:
|
||||
|
||||
if characteristic.value == nil || characteristic.value!.isEmpty {
|
||||
|
|
@ -638,7 +621,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)")
|
||||
|
|
@ -653,6 +636,21 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
)
|
||||
mqttManager.mqttClientProxy?.publish(message)
|
||||
} else if decodedInfo.payloadVariant == FromRadio.OneOf_PayloadVariant.clientNotification(decodedInfo.clientNotification) {
|
||||
if decodedInfo.clientNotification.hasReplyID {
|
||||
/// Set Sent bool on TraceRouteEntity to false if we got rate limited
|
||||
if decodedInfo.clientNotification.message.starts(with: "TraceRoute") {
|
||||
let traceRoute = getTraceRoute(id: Int64(decodedInfo.clientNotification.replyID), context: context)
|
||||
traceRoute?.sent = false
|
||||
do {
|
||||
try context.save()
|
||||
Logger.data.info("💾 [TraceRouteEntity] Trace Route Rate Limited")
|
||||
} catch {
|
||||
context.rollback()
|
||||
let nsError = error as NSError
|
||||
Logger.data.error("💥 [TraceRouteEntity] Error Updating Core Data: \(nsError, privacy: .public)")
|
||||
}
|
||||
}
|
||||
}
|
||||
let manager = LocalNotificationManager()
|
||||
manager.notifications = [
|
||||
Notification(
|
||||
|
|
@ -695,8 +693,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
disconnectPeripheral(reconnect: false)
|
||||
try container.restorePersistentStore(from: databasePath)
|
||||
UserDefaults.preferredPeripheralNum = Int(myInfo?.myNodeNum ?? 0)
|
||||
connectTo(peripheral: peripheral)
|
||||
context.refreshAllObjects()
|
||||
Logger.data.notice("🗂️ Restored Core data for /\(UserDefaults.preferredPeripheralNum, privacy: .public)")
|
||||
connectTo(peripheral: peripheral)
|
||||
} catch {
|
||||
Logger.data.error("🗂️ Restore Core data copy error: \(error, privacy: .public)")
|
||||
}
|
||||
|
|
@ -836,36 +835,43 @@ 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
|
||||
if routingMessage.route.count == 0 {
|
||||
let logString = String.localizedStringWithFormat("mesh.log.traceroute.received.direct %@".localized, String(decodedInfo.packet.from))
|
||||
let snr = routingMessage.snrBack.count > 0 ? routingMessage.snrBack[0] / 4 : 0
|
||||
traceRoute?.snr = Float(snr)
|
||||
let logString = String.localizedStringWithFormat("mesh.log.traceroute.received.direct %@".localized, String(snr))
|
||||
MeshLogger.log("🪧 \(logString)")
|
||||
|
||||
} else {
|
||||
var routeString = "You --> "
|
||||
var hopNodes: [TraceRouteHopEntity] = []
|
||||
for node in routingMessage.route {
|
||||
let connectedHop = TraceRouteHopEntity(context: context)
|
||||
connectedHop.name = traceRoute?.node?.user?.longName ?? "unknown".localized
|
||||
connectedHop.time = Date()
|
||||
if let mostRecent = traceRoute?.node?.positions?.lastObject as? PositionEntity, mostRecent.time! >= Calendar.current.date(byAdding: .hour, value: -24, to: Date())! {
|
||||
connectedHop.altitude = mostRecent.altitude
|
||||
connectedHop.latitudeI = mostRecent.latitudeI
|
||||
connectedHop.longitudeI = mostRecent.longitudeI
|
||||
traceRoute?.hasPositions = true
|
||||
}
|
||||
hopNodes.append(connectedHop)
|
||||
var routeString = "You --> "
|
||||
for (index, node) in routingMessage.route.enumerated() {
|
||||
var hopNode = getNodeInfo(id: Int64(node), context: context)
|
||||
if hopNode == nil && hopNode?.num ?? 0 > 0 && node != 4294967295 {
|
||||
hopNode = createNodeInfo(num: Int64(node), context: context)
|
||||
}
|
||||
let traceRouteHop = TraceRouteHopEntity(context: context)
|
||||
traceRouteHop.time = Date()
|
||||
if hopNode?.hasPositions ?? false {
|
||||
traceRoute?.hasPositions = true
|
||||
if let mostRecent = hopNode?.positions?.lastObject as? PositionEntity, mostRecent.time! >= Calendar.current.date(byAdding: .minute, value: -60, to: Date())! {
|
||||
if routingMessage.snrTowards.count >= index + 1 {
|
||||
traceRouteHop.snr = Float(routingMessage.snrTowards[index] / 4)
|
||||
}
|
||||
if let hn = hopNode, hn.hasPositions {
|
||||
if let mostRecent = hn.positions?.lastObject as? PositionEntity, mostRecent.time! >= Calendar.current.date(byAdding: .hour, value: -24, to: Date())! {
|
||||
traceRouteHop.altitude = mostRecent.altitude
|
||||
traceRouteHop.latitudeI = mostRecent.latitudeI
|
||||
traceRouteHop.longitudeI = mostRecent.longitudeI
|
||||
traceRouteHop.name = hopNode?.user?.longName ?? "unknown".localized
|
||||
} else {
|
||||
traceRoute?.hasPositions = false
|
||||
traceRoute?.hasPositions = true
|
||||
}
|
||||
} else {
|
||||
traceRoute?.hasPositions = false
|
||||
}
|
||||
traceRouteHop.num = hopNode?.num ?? 0
|
||||
if hopNode != nil {
|
||||
|
|
@ -874,10 +880,39 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
}
|
||||
hopNodes.append(traceRouteHop)
|
||||
}
|
||||
routeString += "\(hopNode?.user?.longName ?? (node == 4294967295 ? "Repeater" : String(hopNode?.num.toHex() ?? "unknown".localized))) \(hopNode?.viaMqtt ?? false ? "MQTT" : "") --> "
|
||||
routeString += "\(hopNode?.user?.longName ?? (node == 4294967295 ? "Repeater" : String(hopNode?.num.toHex() ?? "unknown".localized))) \(hopNode?.viaMqtt ?? false ? "MQTT" : "") (\(traceRouteHop.snr > 0 ? hopNode?.snr ?? 0.0 : 0.0)dB) --> "
|
||||
}
|
||||
var routeBackString = traceRoute?.node?.user?.longName ?? "unknown".localized
|
||||
for (index, node) in routingMessage.routeBack.enumerated() {
|
||||
var hopNode = getNodeInfo(id: Int64(node), context: context)
|
||||
if hopNode == nil && hopNode?.num ?? 0 > 0 && node != 4294967295 {
|
||||
hopNode = createNodeInfo(num: Int64(node), context: context)
|
||||
}
|
||||
let traceRouteHop = TraceRouteHopEntity(context: context)
|
||||
traceRouteHop.time = Date()
|
||||
traceRouteHop.back = true
|
||||
if routingMessage.snrBack.count >= index + 1 {
|
||||
traceRouteHop.snr = Float(routingMessage.snrBack[index] / 4)
|
||||
}
|
||||
if let hn = hopNode, hn.hasPositions {
|
||||
if let mostRecent = hn.positions?.lastObject as? PositionEntity, mostRecent.time! >= Calendar.current.date(byAdding: .hour, value: -24, to: Date())! {
|
||||
traceRouteHop.altitude = mostRecent.altitude
|
||||
traceRouteHop.latitudeI = mostRecent.latitudeI
|
||||
traceRouteHop.longitudeI = mostRecent.longitudeI
|
||||
traceRoute?.hasPositions = true
|
||||
}
|
||||
}
|
||||
traceRouteHop.num = hopNode?.num ?? 0
|
||||
if hopNode != nil {
|
||||
if decodedInfo.packet.rxTime > 0 {
|
||||
hopNode?.lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(decodedInfo.packet.rxTime)))
|
||||
}
|
||||
hopNodes.append(traceRouteHop)
|
||||
}
|
||||
routeBackString += "\(hopNode?.user?.longName ?? (node == 4294967295 ? "Repeater" : String(hopNode?.num.toHex() ?? "unknown".localized))) \(hopNode?.viaMqtt ?? false ? "MQTT" : "") (\(traceRouteHop.snr > 0 ? hopNode?.snr ?? 0.0 : 0.0)dB) --> "
|
||||
}
|
||||
routeString += traceRoute?.node?.user?.longName ?? "unknown".localized
|
||||
traceRoute?.routeText = routeString
|
||||
traceRoute?.routeBackText = routeBackString
|
||||
traceRoute?.hops = NSOrderedSet(array: hopNodes)
|
||||
do {
|
||||
try context.save()
|
||||
|
|
@ -892,7 +927,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)")
|
||||
}
|
||||
|
|
@ -915,7 +950,8 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
|
|||
lastConnectionError = ""
|
||||
isSubscribed = true
|
||||
Logger.mesh.info("🤜 [BLE] Want Config Complete. ID:\(decodedInfo.configCompleteID)")
|
||||
sendTime()
|
||||
if sendTime() {
|
||||
}
|
||||
peripherals.removeAll(where: { $0.peripheral.state == CBPeripheralState.disconnected })
|
||||
// Config conplete returns so we don't read the characteristic again
|
||||
|
||||
|
|
|
|||
|
|
@ -84,15 +84,15 @@ import OSLog
|
|||
if smartPostion {
|
||||
let age = -location.timestamp.timeIntervalSinceNow
|
||||
if age > 10 {
|
||||
Logger.services.warning("📍 [App] Bad Location \(self.count, privacy: .public): Too Old \(age, privacy: .public) seconds ago \(location, privacy: .private)")
|
||||
Logger.services.info("📍 [App] Smart Position - Bad Location: Too Old \(age, privacy: .public) seconds ago \(location, privacy: .private)")
|
||||
return false
|
||||
}
|
||||
if location.horizontalAccuracy < 0 {
|
||||
Logger.services.warning("📍 [App] Bad Location \(self.count, privacy: .public): Horizontal Accuracy: \(location.horizontalAccuracy) \(location, privacy: .private)")
|
||||
Logger.services.info("📍 [App] Smart Position - Bad Location: Horizontal Accuracy: \(location.horizontalAccuracy) \(location, privacy: .private)")
|
||||
return false
|
||||
}
|
||||
if location.horizontalAccuracy > 5 {
|
||||
Logger.services.warning("📍 [App] Bad Location \(self.count, privacy: .public): Horizontal Accuracy: \(location.horizontalAccuracy) \(location, privacy: .private)")
|
||||
Logger.services.info("📍 [App] Smart Position - Bad Location: Horizontal Accuracy: \(location.horizontalAccuracy) \(location, privacy: .private)")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -876,7 +876,7 @@ func textMessageAppPacket(
|
|||
if fetchedUsers.first(where: { $0.num == packet.from }) != nil {
|
||||
newMessage.fromUser = fetchedUsers.first(where: { $0.num == packet.from })
|
||||
/// Set the public key for the message
|
||||
if newMessage.fromUser?.pkiEncrypted ?? false {
|
||||
if newMessage.fromUser?.pkiEncrypted ?? false && packet.pkiEncrypted {
|
||||
newMessage.pkiEncrypted = true
|
||||
newMessage.publicKey = packet.publicKey
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@
|
|||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>_XCCurrentVersionName</key>
|
||||
<string>MeshtasticDataModelV 44.xcdatamodel</string>
|
||||
<string>MeshtasticDataModelV 45.xcdatamodel</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
|||
|
|
@ -428,11 +428,14 @@
|
|||
</entity>
|
||||
<entity name="TraceRouteHopEntity" representedClassName="TraceRouteHopEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="altitude" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="destination" optional="YES" attributeType="Boolean" defaultValueString="NO" 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="snr" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="time" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="towards" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<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">
|
||||
|
|
|
|||
|
|
@ -0,0 +1,476 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="23231" systemVersion="23G5075b" 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"/>
|
||||
<attribute name="green" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="ledState" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="red" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="ambientLightingConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="ambientLightingConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="BluetoothConfigEntity" representedClassName="BluetoothConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="deviceLoggingEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="fixedPin" optional="YES" attributeType="Integer 32" defaultValueString="123456" usesScalarValueType="YES"/>
|
||||
<attribute name="mode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="bluetoothConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="bluetoothConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="CannedMessageConfigEntity" representedClassName="CannedMessageConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerEventCcw" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerEventCw" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerEventPress" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerPinA" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerPinB" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
|
||||
<attribute name="inputbrokerPinPress" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
|
||||
<attribute name="messages" optional="YES" attributeType="String" minValueString="0" maxValueString="198"/>
|
||||
<attribute name="rotary1Enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="sendBell" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="updown1Enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<relationship name="cannedMessagesConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="cannedMessageConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="ChannelEntity" representedClassName="ChannelEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="downlinkEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="id" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="index" attributeType="Integer 32" minValueString="0" maxValueString="13" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="mute" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="name" optional="YES" attributeType="String"/>
|
||||
<attribute name="positionPrecision" optional="YES" attributeType="Integer 32" defaultValueString="32" usesScalarValueType="YES"/>
|
||||
<attribute name="psk" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="role" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="uplinkEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<relationship name="myInfoChannel" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MyInfoEntity" inverseName="channels" inverseEntity="MyInfoEntity"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="index"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="DetectionSensorConfigEntity" representedClassName="DetectionSensorConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="detectionTriggeredHigh" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="minimumBroadcastSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="monitorPin" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="name" optional="YES" attributeType="String"/>
|
||||
<attribute name="sendBell" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="stateBroadcastSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="usePullup" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<relationship name="detectionSensorConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="detectionSensorConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="DeviceConfigEntity" representedClassName="DeviceConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="buttonGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="buzzerGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="debugLogEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="disableTripleClick" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="doubleTapAsButtonPress" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="isManaged" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="ledHeartbeatEnabled" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="nodeInfoBroadcastSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="rebroadcastMode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="role" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="serialEnabled" optional="YES" attributeType="Boolean" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="tzdef" optional="YES" attributeType="String"/>
|
||||
<relationship name="deviceConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="deviceConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="DeviceMetadataEntity" representedClassName="DeviceMetadataEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="canShutdown" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="deviceStateVersion" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="firmwareVersion" optional="YES" attributeType="String"/>
|
||||
<attribute name="hasBluetooth" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="hasEthernet" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="hasWifi" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="hwModel" optional="YES" attributeType="String"/>
|
||||
<attribute name="positionFlags" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="role" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<relationship name="metadataNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="metadata" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="DisplayConfigEntity" representedClassName="DisplayConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="compassNorthTop" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="displayMode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="flipScreen" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="gpsFormat" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="headingBold" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="oledType" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="screenCarouselInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="screenOnSeconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="units" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="wakeOnTapOrMotion" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<relationship name="displayConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="displayConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="ExternalNotificationConfigEntity" representedClassName="ExternalNotificationConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="active" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertBell" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertBellBuzzer" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertBellVibra" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertMessage" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertMessageBuzzer" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="alertMessageVibra" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="nagTimeout" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="output" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="outputBuzzer" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="outputMilliseconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="outputVibra" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="useI2SAsBuzzer" optional="YES" attributeType="Boolean" 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"/>
|
||||
</entity>
|
||||
<entity name="LocationEntity" representedClassName="LocationEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="altitude" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="heading" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="id" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
|
||||
<attribute name="latitudeI" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="longitudeI" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="speed" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="routeLocation" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="RouteEntity" inverseName="locations" inverseEntity="RouteEntity"/>
|
||||
</entity>
|
||||
<entity name="LoRaConfigEntity" representedClassName="LoRaConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="bandwidth" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="channelNum" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="codingRate" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="frequencyOffset" optional="YES" attributeType="Float" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="hopLimit" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="ignoreMqtt" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="modemPreset" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="okToMqtt" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="overrideDutyCycle" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="overrideFrequency" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="regionCode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="spreadFactor" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="sx126xRxBoostedGain" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="txEnabled" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="txPower" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="usePreset" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<relationship name="loRaConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="loRaConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="MessageEntity" representedClassName="MessageEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="ackError" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="ackSNR" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="ackTimestamp" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="admin" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="adminDescription" optional="YES" attributeType="String"/>
|
||||
<attribute name="channel" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="isEmoji" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="messageId" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="messagePayload" optional="YES" attributeType="String" defaultValueString=""/>
|
||||
<attribute name="messagePayloadMarkdown" optional="YES" attributeType="String"/>
|
||||
<attribute name="messageTimestamp" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="pkiEncrypted" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="portNum" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="publicKey" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="read" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="realACK" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="receivedACK" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="replyID" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="rssi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<relationship name="fromUser" optional="YES" maxCount="1" deletionRule="Nullify" ordered="YES" destinationEntity="UserEntity" inverseName="sentMessages" inverseEntity="UserEntity"/>
|
||||
<relationship name="toUser" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="UserEntity" inverseName="receivedMessages" inverseEntity="UserEntity"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="messageId"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="MQTTConfigEntity" representedClassName="MQTTConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="address" optional="YES" attributeType="String"/>
|
||||
<attribute name="enabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="encryptionEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="jsonEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="mapPositionPrecision" optional="YES" attributeType="Integer 32" defaultValueString="13" usesScalarValueType="YES"/>
|
||||
<attribute name="mapPublishIntervalSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="mapReportingEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="password" optional="YES" attributeType="String" maxValueString="30"/>
|
||||
<attribute name="proxyToClientEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="root" optional="YES" attributeType="String" defaultValueString="msh"/>
|
||||
<attribute name="tlsEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="username" optional="YES" attributeType="String" maxValueString="30"/>
|
||||
<relationship name="mqttConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="mqttConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="MyInfoEntity" representedClassName="MyInfoEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="adminIndex" optional="YES" attributeType="Integer 32" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="bleName" optional="YES" attributeType="String"/>
|
||||
<attribute name="minAppVersion" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="myNodeNum" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="peripheralId" optional="YES" attributeType="String"/>
|
||||
<attribute name="rebootCount" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="channels" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="ChannelEntity" inverseName="myInfoChannel" inverseEntity="ChannelEntity"/>
|
||||
<relationship name="myInfoNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="myInfo" inverseEntity="NodeInfoEntity"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="myNodeNum"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="NetworkConfigEntity" representedClassName="NetworkConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="dns" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="ethEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="gateway" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="ip" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="ntpServer" optional="YES" attributeType="String"/>
|
||||
<attribute name="subnet" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="wifiEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="wifiMode" optional="YES" attributeType="Integer 32" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="wifiPsk" optional="YES" attributeType="String" minValueString="0" maxValueString="60"/>
|
||||
<attribute name="wifiSsid" optional="YES" attributeType="String" minValueString="0" maxValueString="30"/>
|
||||
<relationship name="networkConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="networkConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="NodeInfoEntity" representedClassName="NodeInfoEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="bleName" optional="YES" attributeType="String"/>
|
||||
<attribute name="channel" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="favorite" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="firstHeard" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="hopsAway" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="id" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="lastHeard" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="num" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="peripheralId" optional="YES" attributeType="String"/>
|
||||
<attribute name="rssi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="sessionExpiration" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="sessionPasskey" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="viaMqtt" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<relationship name="ambientLightingConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="AmbientLightingConfigEntity" inverseName="ambientLightingConfigNode" inverseEntity="AmbientLightingConfigEntity"/>
|
||||
<relationship name="bluetoothConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="BluetoothConfigEntity" inverseName="bluetoothConfigNode" inverseEntity="BluetoothConfigEntity"/>
|
||||
<relationship name="cannedMessageConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="CannedMessageConfigEntity" inverseName="cannedMessagesConfigNode" inverseEntity="CannedMessageConfigEntity"/>
|
||||
<relationship name="detectionSensorConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="DetectionSensorConfigEntity" inverseName="detectionSensorConfigNode" inverseEntity="DetectionSensorConfigEntity"/>
|
||||
<relationship name="deviceConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="DeviceConfigEntity" inverseName="deviceConfigNode" inverseEntity="DeviceConfigEntity"/>
|
||||
<relationship name="displayConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="DisplayConfigEntity" inverseName="displayConfigNode" inverseEntity="DisplayConfigEntity"/>
|
||||
<relationship name="externalNotificationConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="ExternalNotificationConfigEntity" inverseName="externalNotificationConfigNode" inverseEntity="ExternalNotificationConfigEntity"/>
|
||||
<relationship name="loRaConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="LoRaConfigEntity" inverseName="loRaConfigNode" inverseEntity="LoRaConfigEntity"/>
|
||||
<relationship name="metadata" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="DeviceMetadataEntity" inverseName="metadataNode" inverseEntity="DeviceMetadataEntity"/>
|
||||
<relationship name="mqttConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MQTTConfigEntity" inverseName="mqttConfigNode" inverseEntity="MQTTConfigEntity"/>
|
||||
<relationship name="myInfo" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MyInfoEntity" inverseName="myInfoNode" inverseEntity="MyInfoEntity"/>
|
||||
<relationship name="networkConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NetworkConfigEntity" inverseName="networkConfigNode" inverseEntity="NetworkConfigEntity"/>
|
||||
<relationship name="pax" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="PaxCounterEntity" inverseName="paxNode" inverseEntity="PaxCounterEntity"/>
|
||||
<relationship name="paxCounterConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PaxCounterConfigEntity" inverseName="paxCounterConfigNode" inverseEntity="PaxCounterConfigEntity"/>
|
||||
<relationship name="positionConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PositionConfigEntity" inverseName="positionConfigNode" inverseEntity="PositionConfigEntity"/>
|
||||
<relationship name="positions" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="PositionEntity" inverseName="nodePosition" inverseEntity="PositionEntity"/>
|
||||
<relationship name="powerConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PowerConfigEntity" inverseName="powerConfigNode" inverseEntity="PowerConfigEntity"/>
|
||||
<relationship name="rangeTestConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="RangeTestConfigEntity" inverseName="rangeTestConfigNode" inverseEntity="RangeTestConfigEntity"/>
|
||||
<relationship name="rtttlConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="RTTTLConfigEntity" inverseName="rtttlConfigNode" inverseEntity="RTTTLConfigEntity"/>
|
||||
<relationship name="securityConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="SecurityConfigEntity" inverseName="securityConfigNode" inverseEntity="SecurityConfigEntity"/>
|
||||
<relationship name="serialConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="SerialConfigEntity" inverseName="serialConfigNode" inverseEntity="SerialConfigEntity"/>
|
||||
<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>
|
||||
<constraint value="num"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="PaxCounterConfigEntity" representedClassName="PaxCounterConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="bleThreshold" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="updateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="wifiThreshold" optional="YES" attributeType="Integer 32" defaultValueString="-80" usesScalarValueType="YES"/>
|
||||
<relationship name="paxCounterConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="paxCounterConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="PaxCounterEntity" representedClassName="PaxCounterEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="ble" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="uptime" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="wifi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="paxNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="pax" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="PositionConfigEntity" representedClassName="PositionConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="broadcastSmartMinimumDistance" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="broadcastSmartMinimumIntervalSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="deviceGpsEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="fixedPosition" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="gpsAttemptTime" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="gpsEnGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="gpsMode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="gpsUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="positionBroadcastSeconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="positionFlags" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="rxGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="smartPositionEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="txGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="positionConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="positionConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="PositionEntity" representedClassName="PositionEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="altitude" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="heading" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="latest" optional="YES" attributeType="Boolean" defaultValueString="NO" 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="precisionBits" optional="YES" attributeType="Integer 32" defaultValueString="32" usesScalarValueType="YES"/>
|
||||
<attribute name="rssi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="satsInView" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="seqNo" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="speed" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<relationship name="nodePosition" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="positions" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="PowerConfigEntity" representedClassName="PowerConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="adcMultiplierOverride" optional="YES" attributeType="Float" usesScalarValueType="YES"/>
|
||||
<attribute name="deviceBatteryInaAddress" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="isPowerSaving" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="lsSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="minWakeSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="onBatteryShutdownAfterSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="waitBluetoothSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="powerConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="powerConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="RangeTestConfigEntity" representedClassName="RangeTestConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="save" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="sender" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
|
||||
<relationship name="rangeTestConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="rangeTestConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="RouteEntity" representedClassName="RouteEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="color" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="date" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="distance" optional="YES" attributeType="Double" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="elevationGain" optional="YES" attributeType="Double" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="endDate" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="id" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="name" optional="YES" attributeType="String"/>
|
||||
<attribute name="notes" optional="YES" attributeType="String"/>
|
||||
<relationship name="locations" optional="YES" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="LocationEntity" inverseName="routeLocation" inverseEntity="LocationEntity"/>
|
||||
</entity>
|
||||
<entity name="RTTTLConfigEntity" representedClassName="RTTTLConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="ringtone" optional="YES" attributeType="String" maxValueString="228" defaultValueString=""/>
|
||||
<relationship name="rtttlConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="rtttlConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="SecurityConfigEntity" representedClassName="SecurityConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="adminChannelEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="adminKey" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="bluetoothLoggingEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="debugLogApiEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="isManaged" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="privateKey" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="publicKey" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="serialEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<relationship name="securityConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="securityConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="SerialConfigEntity" representedClassName="SerialConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="baudRate" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="echo" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="mode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="overrideConsoleSerialPort" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="rxd" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="timeout" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="txd" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="serialConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="serialConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="StoreForwardConfigEntity" representedClassName="StoreForwardConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="enabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="heartbeat" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="historyReturnMax" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="historyReturnWindow" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="isRouter" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="lastHeartbeat" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="lastRequest" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="records" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="storeForwardConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="storeForwardConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="TelemetryConfigEntity" representedClassName="TelemetryConfigEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="deviceUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="environmentDisplayFahrenheit" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="environmentMeasurementEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="environmentScreenEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="environmentUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="powerMeasurementEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="powerScreenEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="powerUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="telemetryConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="telemetryConfig" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="TelemetryEntity" representedClassName="TelemetryEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="airUtilTx" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="barometricPressure" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="batteryLevel" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="channelUtilization" optional="YES" attributeType="Float" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="current" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="gasResistance" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="iaq" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="metricsType" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="numOnlineNodes" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="numPacketsRx" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="numPacketsRxBad" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="numPacketsTx" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="numTotalNodes" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="relativeHumidity" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="rssi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="temperature" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="uptimeSeconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="voltage" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="weight" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="windDirection" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="windGust" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="windLull" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="windSpeed" 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="hasPositions" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="id" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="response" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="routeBackText" optional="YES" attributeType="String"/>
|
||||
<attribute name="routeText" optional="YES" attributeType="String"/>
|
||||
<attribute name="sent" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<relationship name="hops" optional="YES" toMany="YES" deletionRule="Cascade" 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="back" optional="YES" attributeType="Boolean" defaultValueString="NO" 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="snr" optional="YES" attributeType="Float" defaultValueString="0.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="hwDisplayName" optional="YES" attributeType="String"/>
|
||||
<attribute name="hwModel" attributeType="String"/>
|
||||
<attribute name="hwModelId" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="isLicensed" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="keyMatch" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="lastMessage" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="longName" attributeType="String"/>
|
||||
<attribute name="mute" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="newPublicKey" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="num" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="numString" optional="YES" attributeType="String"/>
|
||||
<attribute name="pkiEncrypted" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="publicKey" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="role" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="shortName" attributeType="String"/>
|
||||
<attribute name="userId" attributeType="String"/>
|
||||
<relationship name="receivedMessages" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="MessageEntity" inverseName="toUser" inverseEntity="MessageEntity"/>
|
||||
<relationship name="sentMessages" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="MessageEntity" inverseName="fromUser" inverseEntity="MessageEntity"/>
|
||||
<relationship name="userNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="user" inverseEntity="NodeInfoEntity"/>
|
||||
</entity>
|
||||
<entity name="WaypointEntity" representedClassName="WaypointEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="created" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="expire" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="icon" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="id" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="lastUpdated" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="latitudeI" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="locked" attributeType="Integer 64" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="longDescription" optional="YES" attributeType="String" maxValueString="100"/>
|
||||
<attribute name="longitudeI" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="name" attributeType="String" minValueString="1" maxValueString="30"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="id"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
</model>
|
||||
|
|
@ -391,6 +391,22 @@
|
|||
"activelySupported": true,
|
||||
"displayName": "Heltec Vision Master E290"
|
||||
},
|
||||
{
|
||||
"hwModel": 69,
|
||||
"hwModelSlug": "HELTEC_MESH_NODE_T114",
|
||||
"platformioTarget": "heltec-mesh-node-t114",
|
||||
"architecture": "nrf52840",
|
||||
"activelySupported": false,
|
||||
"displayName": "Heltec Mesh Node T114"
|
||||
},
|
||||
{
|
||||
"hwModel": 70,
|
||||
"hwModelSlug": "SENSECAP_INDICATOR",
|
||||
"platformioTarget": "seeed-sensecap-indicator",
|
||||
"architecture": "esp32-s3",
|
||||
"activelySupported": true,
|
||||
"displayName": "SenseCAP Indicator"
|
||||
},
|
||||
{
|
||||
"hwModel": 71,
|
||||
"hwModelSlug": "TRACKER_T1000_E",
|
||||
|
|
|
|||
68
Meshtastic/Views/Layouts/TraceRoute.swift
Normal file
68
Meshtastic/Views/Layouts/TraceRoute.swift
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
//
|
||||
// TraceRoute.swift
|
||||
// Meshtastic
|
||||
//
|
||||
// Created by Garth Vander Houwen on 9/22/24.
|
||||
//
|
||||
import SwiftUI
|
||||
|
||||
struct Rotation: LayoutValueKey {
|
||||
static let defaultValue: Binding<Angle>? = nil
|
||||
}
|
||||
|
||||
struct TraceRouteComponent<V: View>: View {
|
||||
var animation: Animation?
|
||||
@ViewBuilder let content: () -> V
|
||||
@State private var rotation: Angle = .zero
|
||||
|
||||
var body: some View {
|
||||
content()
|
||||
.rotationEffect(rotation)
|
||||
.layoutValue(key: Rotation.self, value: $rotation.animation(animation))
|
||||
}
|
||||
}
|
||||
|
||||
struct TraceRoute: Layout {
|
||||
var animatableData: AnimatablePair<CGFloat, CGFloat> {
|
||||
get {
|
||||
AnimatablePair(rotation.radians, radius)
|
||||
}
|
||||
set {
|
||||
rotation = Angle.radians(newValue.first)
|
||||
radius = newValue.second
|
||||
}
|
||||
}
|
||||
|
||||
var radius: CGFloat
|
||||
var rotation: Angle
|
||||
|
||||
func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
|
||||
let maxSize = subviews.map { $0.sizeThatFits(proposal) }.reduce(CGSize.zero) {
|
||||
return CGSize(width: max($0.width, $1.width), height: max($0.height, $1.height))
|
||||
}
|
||||
return CGSize(width: (maxSize.width / 2 + radius) * 2,
|
||||
height: (maxSize.height / 2 + radius) * 2)
|
||||
}
|
||||
|
||||
func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
|
||||
let angleStep = (Angle.degrees(360).radians / Double(subviews.count))
|
||||
|
||||
for (index, subview) in subviews.enumerated() {
|
||||
let angle = angleStep * CGFloat(index) + rotation.radians
|
||||
|
||||
var point = CGPoint(x: 0, y: -radius).applying(CGAffineTransform(rotationAngle: angle))
|
||||
point.x += bounds.midX
|
||||
point.y += bounds.midY
|
||||
|
||||
subview.place(at: point, anchor: .center, proposal: .unspecified)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
if index % 2 == 0 {
|
||||
subview[Rotation.self]?.wrappedValue = .zero
|
||||
} else {
|
||||
subview[Rotation.self]?.wrappedValue = .radians(angle)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,14 +6,15 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import CoreData
|
||||
import OSLog
|
||||
import MapKit
|
||||
|
||||
|
||||
struct TraceRouteLog: View {
|
||||
private var idiom: UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom }
|
||||
@ObservedObject var locationsHandler = LocationsHandler.shared
|
||||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
|
||||
@State private var isPresentingClearLogConfirm: Bool = false
|
||||
@State var isExporting = false
|
||||
@State var exportString = ""
|
||||
|
|
@ -24,117 +25,188 @@ struct TraceRouteLog: View {
|
|||
@State var mapStyle: MapStyle = MapStyle.standard(elevation: .realistic, emphasis: MapStyle.StandardEmphasis.muted, pointsOfInterest: .all, showsTraffic: true)
|
||||
@State var position = MapCameraPosition.automatic
|
||||
let distanceFormatter = MKDistanceFormatter()
|
||||
/// State for the circle of routes
|
||||
var modemPreset: ModemPresets = ModemPresets(rawValue: UserDefaults.modemPreset) ?? ModemPresets.longFast
|
||||
@State private var indexes: Int = 0
|
||||
@State var angle: Angle = .zero
|
||||
@State var animation: Animation?
|
||||
|
||||
var body: some View {
|
||||
HStack(alignment: .top) {
|
||||
VStack {
|
||||
VStack {
|
||||
List(node.traceRoutes?.reversed() as? [TraceRouteEntity] ?? [], id: \.self, selection: $selectedRoute) { route in
|
||||
|
||||
Label {
|
||||
Text("\(route.time?.formatted() ?? "unknown".localized) - \(route.response ? (route.hops?.count == 0 && route.response ? "Direct" : "\(route.hops?.count ?? 0) \(route.hops?.count ?? 0 == 1 ? "Hop": "Hops")") : "No Response")")
|
||||
Text("\(route.time?.formatted() ?? "unknown".localized) - \(route.response ? (route.hops?.count == 0 && route.response ? "Direct" : "\(route.hops?.count ?? 0) \(route.hops?.count ?? 0 == 1 ? "Hop": "Hops")") : (route.sent ? "No Response" : "Not Sent"))")
|
||||
.font(.callout)
|
||||
} icon: {
|
||||
Image(systemName: route.response ? (route.hops?.count == 0 && route.response ? "person.line.dotted.person" : "point.3.connected.trianglepath.dotted") : "person.slash")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
}
|
||||
.swipeActions {
|
||||
Button(role: .destructive) {
|
||||
context.delete(route)
|
||||
do {
|
||||
try context.save()
|
||||
} catch let error as NSError {
|
||||
Logger.data.error("\(error.localizedDescription)")
|
||||
}
|
||||
} label: {
|
||||
Label("delete", systemImage: "trash")
|
||||
}
|
||||
}
|
||||
}
|
||||
.listStyle(.plain)
|
||||
}
|
||||
.frame(minHeight: 200, maxHeight: 230)
|
||||
VStack {
|
||||
.frame(minHeight: CGFloat(node.traceRoutes?.count ?? 0 * 40), maxHeight: 250)
|
||||
Divider()
|
||||
ScrollView {
|
||||
if selectedRoute != nil {
|
||||
if selectedRoute?.response ?? false && selectedRoute?.hops?.count ?? 0 > 0 {
|
||||
|
||||
Label {
|
||||
Text("Route: \(selectedRoute?.routeText ?? "unknown".localized)")
|
||||
} icon: {
|
||||
Image(systemName: "signpost.right.and.left")
|
||||
Image(systemName: "signpost.right")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
}
|
||||
.font(.title2)
|
||||
.font(.title3)
|
||||
Label {
|
||||
Text("Route Back: \(selectedRoute?.routeBackText ?? "unknown".localized)")
|
||||
} icon: {
|
||||
Image(systemName: "signpost.left")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
}
|
||||
.font(.title3)
|
||||
} else if selectedRoute?.response ?? false {
|
||||
Label {
|
||||
Text("Trace route received directly by \(selectedRoute?.node?.user?.longName ?? "unknown".localized)")
|
||||
Text("Trace route received directly by \(selectedRoute?.node?.user?.longName ?? "unknown".localized) with a SNR of \(String(format: "%.2f", selectedRoute?.node?.snr ?? 0.0)) dB")
|
||||
} icon: {
|
||||
Image(systemName: "signpost.right.and.left")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
}
|
||||
.font(.title2)
|
||||
}
|
||||
if selectedRoute?.response ?? false {
|
||||
if selectedRoute?.hasPositions ?? false {
|
||||
Map(position: $position, bounds: MapCameraBounds(minimumDistance: 1, maximumDistance: .infinity), scope: mapScope) {
|
||||
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 {
|
||||
let 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: 2,
|
||||
lineCap: .round, lineJoin: .round, dash: [7, 10]
|
||||
)
|
||||
MapPolyline(coordinates: traceRouteCoords)
|
||||
.stroke(.blue, style: dashed)
|
||||
}
|
||||
} else if selectedRoute?.hops?.count ?? 0 == 0 {
|
||||
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
}
|
||||
VStack {
|
||||
/// Distance
|
||||
if selectedRoute?.node?.positions?.count ?? 0 > 0,
|
||||
selectedRoute?.coordinate != nil,
|
||||
let mostRecent = selectedRoute?.node?.positions?.lastObject as? PositionEntity {
|
||||
|
||||
let startPoint = CLLocation(latitude: selectedRoute?.coordinate?.latitude ?? LocationsHandler.DefaultLocation.latitude, longitude: selectedRoute?.coordinate?.longitude ?? LocationsHandler.DefaultLocation.longitude)
|
||||
|
||||
if startPoint.distance(from: CLLocation(latitude: LocationsHandler.DefaultLocation.latitude, longitude: LocationsHandler.DefaultLocation.longitude)) > 0.0 {
|
||||
let metersAway = selectedRoute?.coordinate?.distance(from: CLLocationCoordinate2D(latitude: mostRecent.latitude ?? LocationsHandler.DefaultLocation.latitude, longitude: mostRecent.longitude ?? LocationsHandler.DefaultLocation.longitude))
|
||||
Label {
|
||||
Text("distance".localized + ": \(distanceFormatter.string(fromDistance: Double(metersAway ?? 0)))")
|
||||
.foregroundColor(.primary)
|
||||
} icon: {
|
||||
Image(systemName: "lines.measurement.horizontal")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
VStack {
|
||||
.font(.title3)
|
||||
} else if !(selectedRoute?.sent ?? true) {
|
||||
Label {
|
||||
Text("Trace route sent to \(selectedRoute?.node?.user?.longName ?? "unknown".localized)")
|
||||
VStack {
|
||||
Text("Trace route to \(selectedRoute?.node?.user?.longName ?? "unknown".localized) was not sent.")
|
||||
.font(idiom == .phone ? .body : .largeTitle)
|
||||
.fontWeight(.semibold)
|
||||
Text("Trace Route was rate limited. You can send a trace route a maximum of once every thirty seconds.")
|
||||
.font(idiom == .phone ? .caption : .body)
|
||||
.foregroundStyle(.secondary)
|
||||
.padding()
|
||||
}
|
||||
} icon: {
|
||||
Image(systemName: "signpost.right.and.left")
|
||||
Image(systemName: "square.and.arrow.up.trianglebadge.exclamationmark")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
}
|
||||
.font(.title3)
|
||||
Spacer()
|
||||
} else {
|
||||
Label {
|
||||
VStack {
|
||||
Text("Trace route sent to \(selectedRoute?.node?.user?.longName ?? "unknown".localized)")
|
||||
.font(idiom == .phone ? .body : .largeTitle)
|
||||
.fontWeight(.semibold)
|
||||
Text("A Trace Route was sent, no response has been received.")
|
||||
.font(idiom == .phone ? .caption : .body)
|
||||
.foregroundStyle(.secondary)
|
||||
.padding()
|
||||
}
|
||||
} icon: {
|
||||
Image(systemName: "signpost.right.and.left")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
}
|
||||
}
|
||||
if selectedRoute?.hops?.count ?? 0 >= 3 {
|
||||
HStack(alignment: .center) {
|
||||
GeometryReader { geometry in
|
||||
let size = ((geometry.size.width >= geometry.size.height ? geometry.size.height : geometry.size.width) / 2) - (idiom == .phone ? 45 : 85)
|
||||
Spacer()
|
||||
TraceRoute(radius: size < 600 ? size : 600, rotation: angle) {
|
||||
contents()
|
||||
}
|
||||
.padding(.leading, idiom == .phone ? 0 : 20)
|
||||
Spacer()
|
||||
}
|
||||
.scaledToFit()
|
||||
}
|
||||
.onAppear {
|
||||
// Set the view rotation animation after the view appeared,
|
||||
// to avoid animating initial rotation
|
||||
DispatchQueue.main.async {
|
||||
indexes = (selectedRoute?.hops?.array.count ?? 0) * 2
|
||||
animation = .easeInOut(duration: 1.0)
|
||||
withAnimation(.easeInOut(duration: 2.0)) {
|
||||
angle = (angle == .degrees(-90) ? .degrees(-90) : .degrees(-90))
|
||||
}
|
||||
}
|
||||
}
|
||||
.onTapGesture {
|
||||
withAnimation(.easeInOut(duration: 2.0)) {
|
||||
angle = (angle == .degrees(-90) ? .degrees(90) : .degrees(-90))
|
||||
}
|
||||
}
|
||||
}
|
||||
if selectedRoute?.hasPositions ?? false {
|
||||
// Map(position: $position, bounds: MapCameraBounds(minimumDistance: 1, maximumDistance: .infinity), scope: mapScope) {
|
||||
// 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 {
|
||||
// let 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: 2,
|
||||
// lineCap: .round, lineJoin: .round, dash: [7, 10]
|
||||
// )
|
||||
// MapPolyline(coordinates: traceRouteCoords)
|
||||
// .stroke(.blue, style: dashed)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// .frame(maxWidth: .infinity, minHeight: 250)
|
||||
// if selectedRoute?.response ?? false {
|
||||
// VStack {
|
||||
// /// Distance
|
||||
// if selectedRoute?.node?.positions?.count ?? 0 > 0,
|
||||
// selectedRoute?.coordinate != nil,
|
||||
// let mostRecent = selectedRoute?.node?.positions?.lastObject as? PositionEntity {
|
||||
// let startPoint = CLLocation(latitude: selectedRoute?.coordinate?.latitude ?? LocationsHandler.DefaultLocation.latitude, longitude: selectedRoute?.coordinate?.longitude ?? LocationsHandler.DefaultLocation.longitude)
|
||||
// if startPoint.distance(from: CLLocation(latitude: LocationsHandler.DefaultLocation.latitude, longitude: LocationsHandler.DefaultLocation.longitude)) > 0.0 {
|
||||
// let metersAway = selectedRoute?.coordinate?.distance(from: CLLocationCoordinate2D(latitude: mostRecent.latitude ?? LocationsHandler.DefaultLocation.latitude, longitude: mostRecent.longitude ?? LocationsHandler.DefaultLocation.longitude))
|
||||
// Label {
|
||||
// Text("distance".localized + ": \(distanceFormatter.string(fromDistance: Double(metersAway ?? 0)))")
|
||||
// .foregroundColor(.primary)
|
||||
// } icon: {
|
||||
// Image(systemName: "lines.measurement.horizontal")
|
||||
// .symbolRenderingMode(.hierarchical)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
Spacer()
|
||||
.padding(.bottom, 125)
|
||||
}
|
||||
} else {
|
||||
ContentUnavailableView("Select a Trace Route", systemImage: "signpost.right.and.left")
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
.edgesIgnoringSafeArea(.bottom)
|
||||
}
|
||||
.navigationTitle("Trace Route Log")
|
||||
}
|
||||
|
|
@ -143,4 +215,69 @@ struct TraceRouteLog: View {
|
|||
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?")
|
||||
})
|
||||
}
|
||||
@ViewBuilder func contents(animation: Animation? = nil) -> some View {
|
||||
ForEach(0..<indexes, id: \.self) { idx in
|
||||
TraceRouteComponent(animation: animation) {
|
||||
let hops = selectedRoute?.hops?.array as? [TraceRouteHopEntity] ?? [] // getTraceRouteHops(context: PersistenceController.preview.container.viewContext)//
|
||||
if idx % 2 == 0 {
|
||||
let i = idx / 2
|
||||
let snrColor = getSnrColor(snr: hops[i].snr, preset: modemPreset)
|
||||
VStack {
|
||||
let nodeColor = UIColor(hex: UInt32(truncatingIfNeeded: hops[i].num))
|
||||
CircleText(text: String(hops[i].num.toHex().suffix(4)), color: Color(nodeColor), circleSize: idiom == .phone ? 70 : 125)
|
||||
Text("\(String(format: "%.2f", hops[i].snr)) dB")
|
||||
.font(idiom == .phone ? .caption2 : .headline)
|
||||
.foregroundColor(snrColor)
|
||||
.allowsTightening(true)
|
||||
.fontWeight(.semibold)
|
||||
}
|
||||
} else {
|
||||
let i = (idx - 1) / 2
|
||||
let snrColor = getSnrColor(snr: hops[i].snr, preset: modemPreset)
|
||||
Image(systemName: "arrowshape.right.fill")
|
||||
.resizable()
|
||||
.frame(width: idiom == .phone ? 25 : 60, height: idiom == .phone ? 25 : 60)
|
||||
.foregroundColor(snrColor.opacity(0.7))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getTraceRouteHops(context: NSManagedObjectContext) -> [TraceRouteHopEntity] {
|
||||
/// static let context = PersistenceController.preview.container.viewContext
|
||||
var array = [TraceRouteHopEntity]()
|
||||
let trh1 = TraceRouteHopEntity(context: context)
|
||||
trh1.num = 366311664
|
||||
trh1.snr = 12.5
|
||||
let trh2 = TraceRouteHopEntity(context: context)
|
||||
trh2.num = 3662955168
|
||||
trh2.snr = -115.00
|
||||
let trh3 = TraceRouteHopEntity(context: context)
|
||||
trh3.num = 3663982804
|
||||
trh3.snr = 17.5
|
||||
let trh4 = TraceRouteHopEntity(context: context)
|
||||
trh4.num = 4202719792
|
||||
trh4.snr = 7.0
|
||||
let trh5 = TraceRouteHopEntity(context: context)
|
||||
trh5.num = 603700594
|
||||
trh5.snr = 8.9
|
||||
let trh6 = TraceRouteHopEntity(context: context)
|
||||
trh6.num = 836212501
|
||||
trh6.snr = -24.0
|
||||
let trh7 = TraceRouteHopEntity(context: context)
|
||||
trh7.num = 3663116644
|
||||
trh7.snr = -6.0
|
||||
let trh8 = TraceRouteHopEntity(context: context)
|
||||
trh8.num = 8362955168
|
||||
trh8.snr = 7.5
|
||||
array.append(trh1)
|
||||
array.append(trh2)
|
||||
array.append(trh3)
|
||||
array.append(trh4)
|
||||
array.append(trh5)
|
||||
array.append(trh6)
|
||||
array.append(trh7)
|
||||
array.append(trh8)
|
||||
return array
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ struct StoreForwardConfig: View {
|
|||
}
|
||||
VStack {
|
||||
if isRouter {
|
||||
Text("Store and forward router devices must also be in the router or router client device role and requires a ESP32 device with PSRAM.")
|
||||
Text("Store and forward router devices require a ESP32 device with PSRAM.")
|
||||
.foregroundColor(.gray)
|
||||
.font(.callout)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -37,11 +37,13 @@ struct LogDetail: View {
|
|||
List {
|
||||
/// Time
|
||||
Label {
|
||||
Text("log.time".localized + ":")
|
||||
.font(idiom == .phone ? .caption : .title)
|
||||
.frame(width: idiom == .phone ? 115 : 190, alignment: .trailing)
|
||||
Text(log.date.formatted(dateFormatStyle))
|
||||
.font(idiom == .phone ? .caption : .title)
|
||||
HStack {
|
||||
Text("log.time".localized + ":")
|
||||
.font(idiom == .phone ? .caption : .title)
|
||||
.frame(width: idiom == .phone ? 115 : 190, alignment: .trailing)
|
||||
Text(log.date.formatted(dateFormatStyle))
|
||||
.font(idiom == .phone ? .caption : .title)
|
||||
}
|
||||
} icon: {
|
||||
Image(systemName: "timer")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
|
|
@ -53,11 +55,13 @@ struct LogDetail: View {
|
|||
.listSectionSeparator(.visible, edges: .bottom)
|
||||
/// Subsystem
|
||||
Label {
|
||||
Text("log.subsystem".localized + ":")
|
||||
.font(idiom == .phone ? .caption : .title)
|
||||
.frame(width: idiom == .phone ? 115 : 190, alignment: .trailing)
|
||||
Text(log.subsystem)
|
||||
.font(idiom == .phone ? .caption : .title)
|
||||
HStack {
|
||||
Text("log.subsystem".localized + ":")
|
||||
.font(idiom == .phone ? .caption : .title)
|
||||
.frame(width: idiom == .phone ? 115 : 190, alignment: .trailing)
|
||||
Text(log.subsystem)
|
||||
.font(idiom == .phone ? .caption : .title)
|
||||
}
|
||||
} icon: {
|
||||
Image(systemName: "gear")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
|
|
@ -68,11 +72,13 @@ struct LogDetail: View {
|
|||
.listRowSeparator(.visible)
|
||||
/// Process
|
||||
Label {
|
||||
Text("log.process".localized + ":")
|
||||
.font(idiom == .phone ? .caption : .title)
|
||||
.frame(width: idiom == .phone ? 115 : 190, alignment: .trailing)
|
||||
Text(log.process)
|
||||
.font(idiom == .phone ? .caption : .title)
|
||||
HStack {
|
||||
Text("log.process".localized + ":")
|
||||
.font(idiom == .phone ? .caption : .title)
|
||||
.frame(width: idiom == .phone ? 115 : 190, alignment: .trailing)
|
||||
Text(log.process)
|
||||
.font(idiom == .phone ? .caption : .title)
|
||||
}
|
||||
} icon: {
|
||||
Image(systemName: "tag")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
|
|
@ -83,12 +89,13 @@ struct LogDetail: View {
|
|||
.listRowSeparator(.visible)
|
||||
/// Level
|
||||
Label {
|
||||
Text("log.level".localized + ":")
|
||||
.font(idiom == .phone ? .caption : .title)
|
||||
.frame(width: idiom == .phone ? 115 : 190, alignment: .trailing)
|
||||
Text(log.level.description)
|
||||
.font(idiom == .phone ? .caption : .title)
|
||||
.foregroundStyle(log.level.color)
|
||||
HStack {
|
||||
Text("log.level".localized + ":")
|
||||
.font(idiom == .phone ? .caption : .title)
|
||||
.frame(width: idiom == .phone ? 115 : 190, alignment: .trailing)
|
||||
Text(log.level.description)
|
||||
.font(idiom == .phone ? .caption : .title)
|
||||
.foregroundStyle(log.level.color) }
|
||||
} icon: {
|
||||
Image(systemName: "stairs")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
|
|
@ -99,11 +106,13 @@ struct LogDetail: View {
|
|||
.listRowSeparator(.visible)
|
||||
/// Category
|
||||
Label {
|
||||
Text("log.category".localized + ":")
|
||||
.font(idiom == .phone ? .caption : .title)
|
||||
.frame(width: idiom == .phone ? 115 : 190, alignment: .trailing)
|
||||
Text(log.category)
|
||||
.font(idiom == .phone ? .caption : .title)
|
||||
HStack {
|
||||
Text("log.category".localized + ":")
|
||||
.font(idiom == .phone ? .caption : .title)
|
||||
.frame(width: idiom == .phone ? 115 : 190, alignment: .trailing)
|
||||
Text(log.category)
|
||||
.font(idiom == .phone ? .caption : .title)
|
||||
}
|
||||
} icon: {
|
||||
Image(systemName: "square.grid.2x2")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue